castle-web-cli 0.4.129 → 0.4.131
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/dist/agent-prompts.js +4 -4
- package/dist/filesChanged.d.ts +2 -0
- package/dist/filesChanged.js +6 -2
- package/dist/ide.js +15 -4
- package/dist/importBrowse.js +1 -0
- package/dist/index.js +10 -4
- package/dist/init.js +63 -15
- package/dist/native/loop.js +1 -1
- package/dist/native/tools.js +1 -1
- package/dist/serve.d.ts +1 -0
- package/dist/serve.js +20 -1
- package/dist/shell/assets/index-BpOLUyVO.js +441 -0
- package/dist/shell/assets/{index-CAq6f9B5.css → index-D6K-0YDB.css} +1 -1
- package/dist/shell/index.html +2 -2
- package/kits/physics-2d/CLAUDE.md +9 -10
- package/kits/physics-2d/castle.json +1 -1
- package/kits/physics-2d/editors/PlayOnly.jsx +16 -6
- package/kits/physics-2d/editors/PxArtEditor.jsx +95 -31
- package/kits/physics-2d/editors/SceneEditor.jsx +10 -10
- package/kits/physics-2d/editors/SingleEditor.jsx +37 -8
- package/kits/physics-2d/editors/StyleEditor.jsx +7 -4
- package/kits/physics-2d/editors/pixelEditorChrome.jsx +7 -7
- package/kits/physics-2d/editors/pixelInspector.jsx +13 -7
- package/kits/physics-2d/editors/pxArtTimeline.jsx +29 -11
- package/kits/physics-2d/editors/pxArtTimeline.module.css +11 -0
- package/kits/physics-2d/engine/ScenePlayer.jsx +2 -2
- package/kits/physics-2d/engine/blueprint.js +23 -5
- package/kits/physics-2d/engine/liveReload.js +33 -21
- package/kits/physics-2d/engine/scene.js +17 -5
- package/kits/physics-2d/engine/systemRegistry.js +1 -1
- package/kits/physics-2d/engine/ui.jsx +2 -2
- package/kits/physics-2d/engine/ui.module.css +42 -0
- package/kits/physics-3d/CLAUDE.md +108 -0
- package/kits/physics-3d/behaviors/Body.jsx +51 -0
- package/kits/physics-3d/behaviors/Door.jsx +31 -0
- package/kits/physics-3d/behaviors/Lookable.jsx +11 -0
- package/kits/physics-3d/behaviors/Model.jsx +14 -0
- package/kits/physics-3d/behaviors/Pickup.jsx +39 -0
- package/kits/physics-3d/behaviors/Player.jsx +172 -0
- package/kits/physics-3d/behaviors/Shape.jsx +75 -0
- package/kits/physics-3d/behaviors/Solid.jsx +11 -0
- package/kits/physics-3d/behaviors/Transform.jsx +24 -0
- package/kits/physics-3d/blueprints/barrel.scene +15 -0
- package/kits/physics-3d/blueprints/crate.scene +29 -0
- package/kits/physics-3d/blueprints/door.scene +35 -0
- package/kits/physics-3d/blueprints/gem.scene +26 -0
- package/kits/physics-3d/blueprints/pillar.scene +25 -0
- package/kits/physics-3d/blueprints/platform.scene +23 -0
- package/kits/physics-3d/blueprints/player.scene +29 -0
- package/kits/physics-3d/blueprints/rock.scene +14 -0
- package/kits/physics-3d/blueprints/statue.scene +26 -0
- package/kits/physics-3d/blueprints/tree.scene +14 -0
- package/kits/physics-3d/blueprints/wall.scene +26 -0
- package/kits/{basic-2d → physics-3d}/castle.json +23 -5
- package/kits/physics-3d/docs/pxmodel-format.md +111 -0
- package/kits/physics-3d/drawings/crate.pxart +26 -0
- package/kits/physics-3d/drawings/door-edge.pxart +26 -0
- package/kits/physics-3d/drawings/door.pxart +27 -0
- package/kits/physics-3d/drawings/face.pxart +26 -0
- package/kits/physics-3d/drawings/floor.pxart +27 -0
- package/kits/physics-3d/drawings/platform.pxart +27 -0
- package/kits/physics-3d/drawings/statue.pxart +27 -0
- package/kits/physics-3d/drawings/wall.pxart +27 -0
- package/kits/physics-3d/engine3d/PlayOnly3D.jsx +36 -0
- package/kits/physics-3d/engine3d/PxModelEditor.jsx +619 -0
- package/kits/physics-3d/engine3d/Scene3DEditor.jsx +618 -0
- package/kits/physics-3d/engine3d/Scene3DPlayer.jsx +309 -0
- package/kits/physics-3d/engine3d/editor3dData.js +114 -0
- package/kits/physics-3d/engine3d/editor3dInspector.jsx +404 -0
- package/kits/physics-3d/engine3d/editorChrome.jsx +218 -0
- package/kits/physics-3d/engine3d/editorWorld.js +249 -0
- package/kits/physics-3d/engine3d/materials.js +174 -0
- package/kits/physics-3d/engine3d/meshops.js +123 -0
- package/kits/physics-3d/engine3d/modelEditorWorld.js +439 -0
- package/kits/physics-3d/engine3d/modelFiles.js +136 -0
- package/kits/physics-3d/engine3d/pxmodel.js +265 -0
- package/kits/physics-3d/engine3d/thumbnails.js +137 -0
- package/kits/physics-3d/engine3d/world3d.js +216 -0
- package/kits/{basic-2d → physics-3d}/eslint.config.js +10 -25
- package/kits/physics-3d/index.html +20 -0
- package/kits/physics-3d/main.jsx +30 -0
- package/kits/physics-3d/models/barrel.pxmodel +10 -0
- package/kits/physics-3d/models/player.pxmodel +222 -0
- package/kits/physics-3d/models/rock.pxmodel +30 -0
- package/kits/physics-3d/models/tree.pxmodel +10 -0
- package/kits/{basic-2d → physics-3d}/package-lock.json +140 -119
- package/kits/{basic-2d → physics-3d}/package.json +11 -9
- package/kits/{basic-2d → physics-3d}/pnpm-lock.yaml +162 -137
- package/kits/physics-3d/scenes/main.scene +398 -0
- package/kits/physics-3d/scenes/model-lab.scene +111 -0
- package/kits/physics-3d/systems/physics3d.js +356 -0
- package/package.json +5 -2
- package/dist/shell/assets/index-DKu9ejyh.js +0 -436
- package/kits/basic-2d/CLAUDE.md +0 -221
- package/kits/basic-2d/behaviors/Camera.jsx +0 -43
- package/kits/basic-2d/behaviors/Collider.jsx +0 -213
- package/kits/basic-2d/behaviors/Layout.jsx +0 -53
- package/kits/basic-2d/behaviors/Sprite.jsx +0 -357
- package/kits/basic-2d/behaviors/tint.js +0 -47
- package/kits/basic-2d/blueprints/cauldron.scene +0 -20
- package/kits/basic-2d/docs/pxart-format.md +0 -377
- package/kits/basic-2d/drawings/cauldron.pxart +0 -113
- package/kits/basic-2d/editors/BlueprintLibrary.jsx +0 -270
- package/kits/basic-2d/editors/ErrorBoundary.jsx +0 -59
- package/kits/basic-2d/editors/PlayOnly.jsx +0 -31
- package/kits/basic-2d/editors/PxArtEditor.jsx +0 -1092
- package/kits/basic-2d/editors/SceneEditor.jsx +0 -1780
- package/kits/basic-2d/editors/SelectionOverlay.jsx +0 -909
- package/kits/basic-2d/editors/SingleEditor.jsx +0 -122
- package/kits/basic-2d/editors/behaviorRegistry.js +0 -34
- package/kits/basic-2d/editors/editorHistory.js +0 -157
- package/kits/basic-2d/editors/inspectorSheet.js +0 -13
- package/kits/basic-2d/editors/pixelCanvas.js +0 -11
- package/kits/basic-2d/editors/pixelEditorChrome.jsx +0 -74
- package/kits/basic-2d/editors/pixelGeometry.js +0 -140
- package/kits/basic-2d/editors/pixelInspector.jsx +0 -633
- package/kits/basic-2d/editors/pxArtEditorModel.js +0 -732
- package/kits/basic-2d/editors/pxArtPlayback.js +0 -92
- package/kits/basic-2d/editors/pxArtTimeline.jsx +0 -752
- package/kits/basic-2d/editors/pxArtTimeline.module.css +0 -506
- package/kits/basic-2d/editors/pxArtTools.js +0 -232
- package/kits/basic-2d/editors/useArtboardFit.js +0 -105
- package/kits/basic-2d/engine/ScenePlayer.jsx +0 -209
- package/kits/basic-2d/engine/SceneUI.jsx +0 -59
- package/kits/basic-2d/engine/assets.js +0 -15
- package/kits/basic-2d/engine/autoInspector.jsx +0 -70
- package/kits/basic-2d/engine/behaviorExtensions.js +0 -32
- package/kits/basic-2d/engine/blueprint.js +0 -557
- package/kits/basic-2d/engine/collider.js +0 -200
- package/kits/basic-2d/engine/files.js +0 -141
- package/kits/basic-2d/engine/liveReload.js +0 -88
- package/kits/basic-2d/engine/pxart.js +0 -1032
- package/kits/basic-2d/engine/pxartSmooth.js +0 -222
- package/kits/basic-2d/engine/scene.js +0 -696
- package/kits/basic-2d/engine/spriteGeometry.js +0 -32
- package/kits/basic-2d/engine/systemRegistry.js +0 -16
- package/kits/basic-2d/engine/ui.jsx +0 -695
- package/kits/basic-2d/engine/ui.module.css +0 -2287
- package/kits/basic-2d/index.html +0 -24
- package/kits/basic-2d/main.jsx +0 -24
- package/kits/basic-2d/scenes/main.scene +0 -16
- package/kits/basic-2d/scripts/draw.mjs +0 -121
- /package/kits/{basic-2d → physics-3d}/.prettierrc +0 -0
- /package/kits/{basic-2d → physics-3d}/vite.config.js +0 -0
package/dist/agent-prompts.js
CHANGED
|
@@ -897,12 +897,12 @@ export function buildTaskPrompt(opts) {
|
|
|
897
897
|
// -- blind whole-file rewrites made parallel agents clobber each other's
|
|
898
898
|
// edits. Read-then-edit is slower but safe; that is the right tradeoff.)
|
|
899
899
|
const wrapUpList = opts.plan
|
|
900
|
-
? "the 90-progress write, the `castle-web save-version` for your paths,
|
|
901
|
-
: "the 90-progress write, the `castle-web save-version` for your paths,
|
|
900
|
+
? "the 90-progress write, the `castle-web save-version` for your paths, writing the notes file, and the handoff lines"
|
|
901
|
+
: "the 90-progress write, the `castle-web save-version` for your paths, and writing the notes file";
|
|
902
902
|
const wrapUp = opts.backend === "claude" || opts.backend === "smith"
|
|
903
903
|
? `\n- Wrap up in ONE tool call, not several: once your last file edit is done, combine ${wrapUpList} into a single shell command (\`;\`-separated so the notes land even if the restart hiccups). Then stop -- no extra turns after it.`
|
|
904
904
|
: "";
|
|
905
|
-
return `You are a background build agent for the Castle deck "${opts.deckLabel}" (current directory). A separate conversation agent dispatched you with one task. Follow the deck's CLAUDE.md / AGENTS.md conventions
|
|
905
|
+
return `You are a background build agent for the Castle deck "${opts.deckLabel}" (current directory). A separate conversation agent dispatched you with one task. Follow the deck's CLAUDE.md / AGENTS.md conventions. Do not reload the served deck -- the person applies your changes when they are ready. \`npm run restart\` exists, but it is HIGHLY flow breaking: it reloads every panel they have open, including the one they are working in. Use it only when a change clearly requires restarting their whole experience.${quickReference}${layout}${deckSource}
|
|
906
906
|
|
|
907
907
|
Your task (id ${opts.taskId}): ${opts.title}
|
|
908
908
|
|
|
@@ -919,7 +919,7 @@ Operating rules:
|
|
|
919
919
|
- NEVER run \`restore-version\`. Going back to an earlier version is the user's decision alone -- not yours, not even to undo your own work. If your change is wrong, fix it forward.
|
|
920
920
|
- Other task agents may be editing this same deck IN PARALLEL. When you change an existing file, READ it first and make a targeted edit to just the part you need -- never overwrite a whole file you have not read. A blind full-file rewrite clobbers other agents' in-flight changes. Being a bit slower and careful here is the right tradeoff.
|
|
921
921
|
- Favor real, editable assets: for game objects, characters, and scenery, make pixel-art drawings and place them as real actors in the scene rather than code-drawn shapes -- it keeps the deck editable in the editor and remixable. (Data-driven UI like health bars, score/text, and HUD gauges, plus dynamic things like bullets/particles/effects, stay procedural -- don't force those into drawings.)
|
|
922
|
-
- Respect art ownership: if YOUR task is to create art, make it as real, editable drawing files (NOT hand-written pixel grids or code-drawn shapes), following the deck's CLAUDE.md / AGENTS.md for the drawing format and exact command. But if your prompt only REFERENCES drawing names (a sibling task is creating them in parallel), point the scene at those names and do NOT create the drawing files yourself -- two agents drawing the same sprites clobber each other. The deck's CLAUDE.md / AGENTS.md is the source of truth for how to make art. Reference the intended drawing name from the scene right away (the actor renders a plain-block fallback until the file exists)
|
|
922
|
+
- Respect art ownership: if YOUR task is to create art, make it as real, editable drawing files (NOT hand-written pixel grids or code-drawn shapes), following the deck's CLAUDE.md / AGENTS.md for the drawing format and exact command. But if your prompt only REFERENCES drawing names (a sibling task is creating them in parallel), point the scene at those names and do NOT create the drawing files yourself -- two agents drawing the same sprites clobber each other. The deck's CLAUDE.md / AGENTS.md is the source of truth for how to make art. Reference the intended drawing name from the scene right away (the actor renders a plain-block fallback until the file exists); a newly written drawing file shows up for the person on their next reload, which is theirs to trigger.
|
|
923
923
|
- Castle decks are played on phones -- any control you build MUST be touch-friendly by default: respond to touch and drag on the canvas (and tap where natural), never keyboard-only. Keep keyboard working too as a bonus, but a control that only works with a keyboard is broken for most players. (Use the kit's pointer/touch input -- see its CLAUDE.md.)
|
|
924
924
|
- Narrate in plain prose as you work -- the user watches your output stream live. Lead with a short sentence saying what you're about to do BEFORE your first tool call (never open with a silent tool call), then keep dropping a brief plain line every few steps as you go. A line of thinking or a quick "now wiring the paddle to the keys" is exactly what they want to see.
|
|
925
925
|
- Update your progress VERY frequently: write a bare integer 0-100 to ${opts.progressPath} (e.g. \`echo 30 > ${opts.progressPath}\`) every time you advance -- at least every 10 points, or every 20 for properly small tasks. Start near 10, write 90 just before wrapping up. Never let it sit stale while you work.
|
package/dist/filesChanged.d.ts
CHANGED
|
@@ -22,4 +22,6 @@ export declare function installFilesChangedWatcher(opts: {
|
|
|
22
22
|
broadcast: (msg: FilesChangedMessage) => void;
|
|
23
23
|
/** Drop Vite transform caches (so `import.meta.glob` rescans new files). */
|
|
24
24
|
invalidate: () => void;
|
|
25
|
+
/** Records what was broadcast, so we can see what fires and when. */
|
|
26
|
+
log?: (line: string) => void;
|
|
25
27
|
}): void;
|
package/dist/filesChanged.js
CHANGED
|
@@ -14,7 +14,7 @@ const IGNORED_SEGMENTS = new Set(['node_modules', '.castle', '.git']);
|
|
|
14
14
|
const DEBOUNCE_MS = 100;
|
|
15
15
|
const MAX_WAIT_MS = 500;
|
|
16
16
|
export function installFilesChangedWatcher(opts) {
|
|
17
|
-
const { vite, projectDir, broadcast, invalidate } = opts;
|
|
17
|
+
const { vite, projectDir, broadcast, invalidate, log } = opts;
|
|
18
18
|
const pending = new Map();
|
|
19
19
|
let debounceTimer = null;
|
|
20
20
|
let maxTimer = null;
|
|
@@ -45,7 +45,11 @@ export function installFilesChangedWatcher(opts) {
|
|
|
45
45
|
for (const p of change.affected)
|
|
46
46
|
affected.add(p);
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
const affectedList = [...affected].sort();
|
|
49
|
+
log?.(`[watch] ${changes
|
|
50
|
+
.map((c) => `${c.event} ${c.path}${c.affected.length ? ` (+${c.affected.length})` : ''}`)
|
|
51
|
+
.join(', ')} | affected ${affectedList.length}\n`);
|
|
52
|
+
broadcast({ type: 'files_changed', changes, affected: affectedList });
|
|
49
53
|
};
|
|
50
54
|
const schedule = () => {
|
|
51
55
|
if (debounceTimer)
|
package/dist/ide.js
CHANGED
|
@@ -414,13 +414,24 @@ function handleVersionSave(deckDir, req, res) {
|
|
|
414
414
|
// Never forced. Unsaved work that a restore would overwrite comes back as a 409
|
|
415
415
|
// naming the files, which the panel shows -- saving a version first is the fix,
|
|
416
416
|
// and it is one click away in the same panel.
|
|
417
|
-
function handleVersionRestore(deckDir, req, res) {
|
|
417
|
+
function handleVersionRestore(deckDir, req, res, restart) {
|
|
418
418
|
withJsonBody(req, res, (body) => {
|
|
419
419
|
if (typeof body.id !== 'string' || !body.id) {
|
|
420
420
|
return sendJson(res, 400, { error: 'Missing version id.' });
|
|
421
421
|
}
|
|
422
422
|
try {
|
|
423
423
|
const outcome = applyVersionRestore(deckDir, body.id);
|
|
424
|
+
// A restore rewrites the deck wholesale, so every panel rendering it is
|
|
425
|
+
// showing the wrong thing. Reload them from here rather than from the
|
|
426
|
+
// panel that clicked Restore: a restore can DELETE a file, and a reload
|
|
427
|
+
// racing ahead of Vite's invalidation asks for a module that no longer
|
|
428
|
+
// exists. `restart` invalidates first, then broadcasts, in that order.
|
|
429
|
+
//
|
|
430
|
+
// After the response, not before: dropping every transform means the
|
|
431
|
+
// whole deck is rebuilt on the next load, and the panel that clicked
|
|
432
|
+
// Restore should not sit waiting on that.
|
|
433
|
+
if (!outcome.alreadyThere)
|
|
434
|
+
setImmediate(() => restart?.());
|
|
424
435
|
sendJson(res, 200, {
|
|
425
436
|
ok: true,
|
|
426
437
|
restored: outcome.applied.length,
|
|
@@ -684,7 +695,7 @@ function handleFilesDelete(deckDir, req, res) {
|
|
|
684
695
|
// The builtin Files + code-editor backend: list / read / write deck files and
|
|
685
696
|
// report kit-owned editor extensions. Paths are deck-relative; resolveDeckPath
|
|
686
697
|
// rejects traversal and protected dirs.
|
|
687
|
-
function handleFilesApi(deckDir, req, res, reqPath) {
|
|
698
|
+
function handleFilesApi(deckDir, req, res, reqPath, restart) {
|
|
688
699
|
const action = reqPath.slice(FILES_API_PREFIX.length);
|
|
689
700
|
if (action === 'info') {
|
|
690
701
|
const config = readEditorConfig(deckDir);
|
|
@@ -751,7 +762,7 @@ function handleFilesApi(deckDir, req, res, reqPath) {
|
|
|
751
762
|
return true;
|
|
752
763
|
}
|
|
753
764
|
if (action === 'restore-version') {
|
|
754
|
-
handleVersionRestore(deckDir, req, res);
|
|
765
|
+
handleVersionRestore(deckDir, req, res, restart);
|
|
755
766
|
return true;
|
|
756
767
|
}
|
|
757
768
|
if (action === 'list') {
|
|
@@ -1060,7 +1071,7 @@ export function createIdeServer(opts) {
|
|
|
1060
1071
|
return handleCoverApi(deckDir, req, res);
|
|
1061
1072
|
}
|
|
1062
1073
|
if (reqPath.startsWith(FILES_API_PREFIX)) {
|
|
1063
|
-
return handleFilesApi(deckDir, req, res, reqPath);
|
|
1074
|
+
return handleFilesApi(deckDir, req, res, reqPath, opts.restart);
|
|
1064
1075
|
}
|
|
1065
1076
|
if (reqPath.startsWith(IMPORT_API_PREFIX)) {
|
|
1066
1077
|
return handleImportApi(deckDir, req, res, reqPath, opts.restart);
|
package/dist/importBrowse.js
CHANGED
|
@@ -38,6 +38,7 @@ const BOOKMARKS_PLAYLIST_PREFIX = 'bookmarks-';
|
|
|
38
38
|
// an update to this list.
|
|
39
39
|
const KIT_DECK_IDS = [
|
|
40
40
|
'ckRZGFW4iPrx', // physics-2d
|
|
41
|
+
'JH0SclbPVP0y', // physics-3d
|
|
41
42
|
];
|
|
42
43
|
// Listing is several server round trips, and the picker is opened, closed and
|
|
43
44
|
// reopened while someone decides. Cached briefly so that costs once.
|
package/dist/index.js
CHANGED
|
@@ -87,7 +87,7 @@ function getWsPort(dir) {
|
|
|
87
87
|
}
|
|
88
88
|
function usage() {
|
|
89
89
|
console.log(`Usage:
|
|
90
|
-
castle-web init <dir> [--kit NAME] (kits: physics-2d (default),
|
|
90
|
+
castle-web init <dir> [--kit NAME] (kits: physics-2d (default), physics-3d, none)
|
|
91
91
|
castle-web serve [dir] [--port PORT] [--host HOST] [--open] [--detach]
|
|
92
92
|
castle-web restart [--port PORT]
|
|
93
93
|
castle-web screenshot [--out FILE] [--port PORT]
|
|
@@ -106,7 +106,7 @@ function usage() {
|
|
|
106
106
|
|
|
107
107
|
Kits:
|
|
108
108
|
physics-2d Actor / behavior / scene framework for 2D games with matter-js physics (RigidBody / Collider / Joints on top of Layout / Drawing / Camera). Default if --kit is omitted.
|
|
109
|
-
|
|
109
|
+
physics-3d The same model in 3D: three.js rendering and rapier physics, with a scene editor and a voxel model editor. Built on physics-2d, which it imports.
|
|
110
110
|
none Minimal vite + canvas setup with the castle-web SDK and no kit.
|
|
111
111
|
|
|
112
112
|
After init, read the scaffolded deck's CLAUDE.md (also AGENTS.md) for the kit's full author guide.`);
|
|
@@ -225,9 +225,15 @@ async function main() {
|
|
|
225
225
|
const ws = new WS(`ws://localhost:${wsPort}`);
|
|
226
226
|
ws.on('open', () => {
|
|
227
227
|
ws.send(JSON.stringify({ type: 'restart' }));
|
|
228
|
-
setTimeout(() => {
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
ws.close();
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}, 100);
|
|
232
|
+
});
|
|
233
|
+
ws.on('error', () => {
|
|
234
|
+
console.error('Could not connect. Is castle-web serve running?');
|
|
235
|
+
process.exit(1);
|
|
229
236
|
});
|
|
230
|
-
ws.on('error', () => { console.error('Could not connect. Is castle-web serve running?'); process.exit(1); });
|
|
231
237
|
break;
|
|
232
238
|
}
|
|
233
239
|
case 'screenshot': {
|
package/dist/init.js
CHANGED
|
@@ -38,7 +38,7 @@ const DEFAULT_KIT = 'physics-2d';
|
|
|
38
38
|
// Registry version of castle-web-sdk to inject when scaffolding from a
|
|
39
39
|
// globally-installed castle-web (not from inside the workspace). Bumped
|
|
40
40
|
// alongside cli/sdk version bumps.
|
|
41
|
-
const PUBLISHED_SDK_VERSION = '0.4.
|
|
41
|
+
const PUBLISHED_SDK_VERSION = '0.4.15';
|
|
42
42
|
// The account the first-party kits are (to be) published under, so a kit
|
|
43
43
|
// imported from the CLI's copy is named the same as one fetched from the server.
|
|
44
44
|
const KIT_AUTHOR = 'castle';
|
|
@@ -347,6 +347,51 @@ function withoutFileTypes(editor) {
|
|
|
347
347
|
// been published: an unpublished one can only be named as the CLI's own copy,
|
|
348
348
|
// while a published one is pinned by deckId so the deck can be told about kit
|
|
349
349
|
// releases and take them, without waiting for a CLI release.
|
|
350
|
+
// One kit copied into `imports/<alias>/`, then the same again for every kit IT
|
|
351
|
+
// imports -- all of them side by side at the deck root, never nested.
|
|
352
|
+
//
|
|
353
|
+
// Flattening is not a tidiness choice: a kit refers to its own dependencies as
|
|
354
|
+
// `@imports/<alias>/...`, which resolves against the deck being served, and
|
|
355
|
+
// there is only ever one deck. A kit left inside another kit's `imports/` sits
|
|
356
|
+
// one level too deep and nothing it names resolves. `addImportTo` already
|
|
357
|
+
// flattens this way for fetched decks (see addTransitiveImports); this is the
|
|
358
|
+
// same rule for the copies the CLI ships.
|
|
359
|
+
//
|
|
360
|
+
// A dependency the CLI does not ship keeps the pin the kit declared, and
|
|
361
|
+
// `install` fetches it from the server (restoreMissingImports) -- so a kit may
|
|
362
|
+
// depend on any published deck, not only on its neighbours in kits/.
|
|
363
|
+
function copyKitImport(projectDir, kitDir, kit, alias, pins, copied) {
|
|
364
|
+
if (copied.has(alias))
|
|
365
|
+
return;
|
|
366
|
+
copied.set(alias, kitDir);
|
|
367
|
+
const importDir = path.join(projectDir, IMPORTS_DIR, alias);
|
|
368
|
+
const nestedImports = path.join(kitDir, IMPORTS_DIR);
|
|
369
|
+
fs.mkdirSync(importDir, { recursive: true });
|
|
370
|
+
fs.cpSync(kitDir, importDir, {
|
|
371
|
+
recursive: true,
|
|
372
|
+
verbatimSymlinks: true,
|
|
373
|
+
// The kit's own `imports/` is left behind with the rest of its derived
|
|
374
|
+
// state: its contents are being re-placed at the deck root instead.
|
|
375
|
+
filter: (src) => src === kitDir || (src !== nestedImports && !KIT_COPY_EXCLUDE.has(path.basename(src))),
|
|
376
|
+
});
|
|
377
|
+
const kitConfig = readJsonFile(path.join(kitDir, 'castle.json')) ?? {};
|
|
378
|
+
pins[alias] = makeKitPin(kitConfig, kit);
|
|
379
|
+
const kitPins = (kitConfig.imports ?? {});
|
|
380
|
+
for (const [depAlias, depPin] of Object.entries(kitPins)) {
|
|
381
|
+
if (copied.has(depAlias) || pins[depAlias])
|
|
382
|
+
continue;
|
|
383
|
+
const depKit = depAlias.startsWith(`${KIT_AUTHOR}.`)
|
|
384
|
+
? depAlias.slice(KIT_AUTHOR.length + 1)
|
|
385
|
+
: depAlias;
|
|
386
|
+
const depKitDir = path.join(getKitsDir(), depKit);
|
|
387
|
+
if (fs.existsSync(depKitDir) && fs.statSync(depKitDir).isDirectory()) {
|
|
388
|
+
copyKitImport(projectDir, depKitDir, depKit, depAlias, pins, copied);
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
pins[depAlias] = depPin;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
350
395
|
function scaffoldFromKitImport(kit, projectDir) {
|
|
351
396
|
const kitDir = requireKitDir(kit);
|
|
352
397
|
// Qualified like any other import (`<author>.<deck>`), and qualified NOW even
|
|
@@ -355,15 +400,10 @@ function scaffoldFromKitImport(kit, projectDir) {
|
|
|
355
400
|
// kits under the castle account turns the pin into a deckId; the name it is
|
|
356
401
|
// already known by stays put.
|
|
357
402
|
const alias = `${KIT_AUTHOR}.${kit}`;
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
recursive: true,
|
|
362
|
-
verbatimSymlinks: true,
|
|
363
|
-
filter: (src) => src === kitDir || !KIT_COPY_EXCLUDE.has(path.basename(src)),
|
|
364
|
-
});
|
|
403
|
+
const pins = {};
|
|
404
|
+
const copied = new Map();
|
|
405
|
+
copyKitImport(projectDir, kitDir, kit, alias, pins, copied);
|
|
365
406
|
const kitConfig = readJsonFile(path.join(kitDir, 'castle.json')) ?? {};
|
|
366
|
-
const kitPkg = readJsonFile(path.join(kitDir, 'package.json')) ?? {};
|
|
367
407
|
const title = typeof kitConfig.title === 'string' ? kitConfig.title : kit;
|
|
368
408
|
writeDeckIndexHtml(projectDir, alias, title, deckMainFile(kitDir));
|
|
369
409
|
writeStarterScene(projectDir, kitDir, alias);
|
|
@@ -379,16 +419,22 @@ function scaffoldFromKitImport(kit, projectDir) {
|
|
|
379
419
|
// types too, which a copy taken at scaffold time never could.
|
|
380
420
|
writeJsonFile(path.join(projectDir, 'castle.json'), {
|
|
381
421
|
...(kitConfig.editor ? { editor: withoutFileTypes(kitConfig.editor) } : {}),
|
|
382
|
-
imports:
|
|
422
|
+
imports: pins,
|
|
383
423
|
});
|
|
384
424
|
// The kit's code runs from THIS deck's node_modules, so its dependencies are
|
|
385
425
|
// declared here (see syncImportDependencies, which does the same for imports
|
|
386
|
-
// added later).
|
|
426
|
+
// added later). Every copied kit contributes, the scaffolding kit first, so
|
|
427
|
+
// where two of them name the same package the one being scaffolded from wins.
|
|
387
428
|
const { sdkRef, cliCommand } = resolveScaffoldRefs();
|
|
388
429
|
const dependencies = {};
|
|
389
|
-
for (const
|
|
390
|
-
|
|
391
|
-
|
|
430
|
+
for (const depKitDir of copied.values()) {
|
|
431
|
+
const depPkg = readJsonFile(path.join(depKitDir, 'package.json')) ?? {};
|
|
432
|
+
for (const [name, range] of Object.entries(depPkg.dependencies ?? {})) {
|
|
433
|
+
if (dependencies[name] !== undefined)
|
|
434
|
+
continue;
|
|
435
|
+
dependencies[name] =
|
|
436
|
+
name === 'castle-web-sdk' && String(range).startsWith('file:') ? sdkRef : String(range);
|
|
437
|
+
}
|
|
392
438
|
}
|
|
393
439
|
writeJsonFile(path.join(projectDir, 'package.json'), {
|
|
394
440
|
name: path.basename(projectDir),
|
|
@@ -399,7 +445,9 @@ function scaffoldFromKitImport(kit, projectDir) {
|
|
|
399
445
|
});
|
|
400
446
|
fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), makeImportedKitClaudeMd(alias));
|
|
401
447
|
appendCommonInstructions(projectDir);
|
|
402
|
-
|
|
448
|
+
for (const importedAlias of copied.keys()) {
|
|
449
|
+
lockImportTree(path.join(projectDir, IMPORTS_DIR, importedAlias));
|
|
450
|
+
}
|
|
403
451
|
}
|
|
404
452
|
// The scaffold as version 1, so a creator can always get back to the pristine
|
|
405
453
|
// deck. AFTER the install: it rewrites package.json and writes a lockfile, so a
|
package/dist/native/loop.js
CHANGED
|
@@ -313,7 +313,7 @@ const ROLE_FRAMING = {
|
|
|
313
313
|
// actually running `npm run restart` in a real shell (they have no
|
|
314
314
|
// native restart tool, and naming one there would misdirect them into
|
|
315
315
|
// hallucinating a tool call that doesn't exist for their backend).
|
|
316
|
-
"
|
|
316
|
+
"A native restart tool exists, and is preferable to running `npm run restart` in bash when you do need one. But reloading is HIGHLY flow breaking either way -- it reloads every panel the person has open, including the one they are working in -- so do not reload after ordinary file changes. Use it only when a change clearly requires restarting their whole experience.",
|
|
317
317
|
"Work to completion, then reply with your final message text.",
|
|
318
318
|
].join(" "),
|
|
319
319
|
};
|
package/dist/native/tools.js
CHANGED
|
@@ -551,7 +551,7 @@ const TOOLS = [
|
|
|
551
551
|
},
|
|
552
552
|
{
|
|
553
553
|
name: "restart",
|
|
554
|
-
description: "Reload the deck in every connected browser tab, dropping Vite's stale module cache first (same effect as `npm run restart`, applied in-process).
|
|
554
|
+
description: "Reload the deck in every connected browser tab, dropping Vite's stale module cache first (same effect as `npm run restart`, applied in-process). HIGHLY flow breaking: it reloads every panel the person has open, including the one they are working in. Do NOT call it after ordinary file changes -- the person applies those when they are ready. Use it only when a change clearly requires restarting their whole experience.",
|
|
555
555
|
parameters: { type: "object", properties: {} },
|
|
556
556
|
// Task-only: the router never edits the deck, so it never has a reason
|
|
557
557
|
// to reload it.
|
package/dist/serve.d.ts
CHANGED
package/dist/serve.js
CHANGED
|
@@ -12,6 +12,24 @@ import { autoUpdateImports } from './imports.js';
|
|
|
12
12
|
import * as config from './config.js';
|
|
13
13
|
import { graphql as castleGraphql } from './api.js';
|
|
14
14
|
import { executeCommand } from './castle-host/host.js';
|
|
15
|
+
// The log is a tail, not an archive: append, and once it passes the cap drop
|
|
16
|
+
// whole lines off the front until it is back under.
|
|
17
|
+
const LOG_MAX_BYTES = 2 * 1024 * 1024;
|
|
18
|
+
const LOG_KEEP_BYTES = Math.floor(LOG_MAX_BYTES * 0.8);
|
|
19
|
+
export function appendCapped(file, line) {
|
|
20
|
+
try {
|
|
21
|
+
fs.appendFileSync(file, line);
|
|
22
|
+
if (fs.statSync(file).size <= LOG_MAX_BYTES)
|
|
23
|
+
return;
|
|
24
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
25
|
+
const cut = text.length - LOG_KEEP_BYTES;
|
|
26
|
+
const nl = text.indexOf('\n', cut);
|
|
27
|
+
fs.writeFileSync(file, text.slice(nl < 0 ? cut : nl + 1));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// logging must never take the serve down
|
|
31
|
+
}
|
|
32
|
+
}
|
|
15
33
|
function isPortFree(port) {
|
|
16
34
|
return new Promise((resolve) => {
|
|
17
35
|
const srv = net.createServer();
|
|
@@ -361,6 +379,7 @@ export async function serve(dir, options = {}) {
|
|
|
361
379
|
projectDir,
|
|
362
380
|
broadcast,
|
|
363
381
|
invalidate: () => invalidateModuleCaches(vite),
|
|
382
|
+
log: (line) => appendCapped(logFile, line),
|
|
364
383
|
});
|
|
365
384
|
await vite.listen();
|
|
366
385
|
// The PTY WebSocket upgrade is handled directly on Vite's HTTP server rather
|
|
@@ -535,7 +554,7 @@ function startWSServer(port, projectDir, logFile, screenshotsDir, viteHolder) {
|
|
|
535
554
|
}
|
|
536
555
|
if (msg.type === 'log') {
|
|
537
556
|
const line = `[${msg.level}] ${msg.msg}\n`;
|
|
538
|
-
|
|
557
|
+
appendCapped(logFile, line);
|
|
539
558
|
if (msg.level === 'error')
|
|
540
559
|
process.stderr.write(line);
|
|
541
560
|
else
|