castle-web-cli 0.4.11 → 0.4.13
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.d.ts +31 -0
- package/dist/agent-prompts.js +104 -0
- package/dist/agent.d.ts +17 -0
- package/dist/agent.js +952 -0
- package/dist/chat-client.d.ts +1 -0
- package/dist/chat-client.js +425 -0
- package/dist/commonInstructions.d.ts +1 -0
- package/dist/commonInstructions.js +8 -0
- package/dist/ide-client.js +46 -14
- package/dist/ide.d.ts +2 -0
- package/dist/ide.js +348 -36
- package/dist/init.js +12 -1
- package/dist/serve.js +18 -3
- package/kits/basic-2d/CLAUDE.md +3 -1
- package/kits/basic-3d/.prettierrc +8 -0
- package/kits/basic-3d/CLAUDE.md +162 -0
- package/kits/basic-3d/behaviors/Camera.jsx +56 -0
- package/kits/basic-3d/behaviors/Collider.jsx +78 -0
- package/kits/basic-3d/behaviors/Mesh.jsx +82 -0
- package/kits/basic-3d/behaviors/Model.jsx +61 -0
- package/kits/basic-3d/behaviors/Transform.jsx +35 -0
- package/kits/basic-3d/editors/App.jsx +147 -0
- package/kits/basic-3d/editors/CodeEditor.jsx +112 -0
- package/kits/basic-3d/editors/FileBrowser.jsx +143 -0
- package/kits/basic-3d/editors/ModelEditor.jsx +400 -0
- package/kits/basic-3d/editors/PlayOnly.jsx +14 -0
- package/kits/basic-3d/editors/SceneEditor.jsx +1087 -0
- package/kits/basic-3d/editors/behaviorRegistry.js +24 -0
- package/kits/basic-3d/editors/editorHistory.js +52 -0
- package/kits/basic-3d/editors/viewportRig.js +90 -0
- package/kits/basic-3d/engine/ScenePlayer.jsx +55 -0
- package/kits/basic-3d/engine/SceneUI.jsx +67 -0
- package/kits/basic-3d/engine/SceneViewport.jsx +102 -0
- package/kits/basic-3d/engine/TouchControls.jsx +136 -0
- package/kits/basic-3d/engine/autoInspector.jsx +51 -0
- package/kits/basic-3d/engine/files.js +73 -0
- package/kits/basic-3d/engine/scene.js +502 -0
- package/kits/basic-3d/engine/threeUtil.js +260 -0
- package/kits/basic-3d/engine/ui.jsx +352 -0
- package/kits/basic-3d/engine/ui.module.css +944 -0
- package/kits/basic-3d/eslint.config.js +51 -0
- package/kits/basic-3d/index.html +11 -0
- package/kits/basic-3d/main.jsx +10 -0
- package/kits/basic-3d/models/block.model +14 -0
- package/kits/basic-3d/package-lock.json +2713 -0
- package/kits/basic-3d/package.json +41 -0
- package/kits/basic-3d/scenes/main.scene +76 -0
- package/kits/basic-3d/vite.config.js +1 -0
- package/package.json +6 -1
package/dist/ide.js
CHANGED
|
@@ -27,10 +27,11 @@ const SCROLLBACK = 4000;
|
|
|
27
27
|
// upgrade handler on Vite's HTTP server).
|
|
28
28
|
export const IDE_ASSET_PREFIX = '/__castle/ide/';
|
|
29
29
|
export const PTY_WS_PATH = '/__castle/pty';
|
|
30
|
-
// Injected into every served deck's <head>. When the deck runs embedded in
|
|
31
|
-
// IDE shell iframe, Ctrl+T moves keyboard focus out to the
|
|
32
|
-
//
|
|
33
|
-
//
|
|
30
|
+
// Injected into every served deck's <head>. When the deck runs embedded in
|
|
31
|
+
// the IDE shell iframe, Ctrl+T moves keyboard focus out to the panel (the
|
|
32
|
+
// chat input or the terminal, whichever view is active) -- but while the
|
|
33
|
+
// iframe holds focus the parent sees no keydowns, so the deck page has to
|
|
34
|
+
// forward Ctrl+T itself. No-op for a standalone deck.
|
|
34
35
|
export const DECK_FOCUS_SCRIPT = `<script>(function(){if(window.parent===window)return;` +
|
|
35
36
|
`document.addEventListener('keydown',function(e){` +
|
|
36
37
|
`if(e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.shiftKey&&(e.key==='t'||e.key==='T')){` +
|
|
@@ -62,7 +63,7 @@ function clampSize(value, fallback) {
|
|
|
62
63
|
return fallback;
|
|
63
64
|
return Math.max(2, Math.min(1000, Math.floor(n)));
|
|
64
65
|
}
|
|
65
|
-
function rawDataToString(data) {
|
|
66
|
+
export function rawDataToString(data) {
|
|
66
67
|
if (Array.isArray(data))
|
|
67
68
|
return Buffer.concat(data).toString('utf8');
|
|
68
69
|
if (Buffer.isBuffer(data))
|
|
@@ -287,16 +288,38 @@ function resolveIdeAsset(asset) {
|
|
|
287
288
|
return null;
|
|
288
289
|
}
|
|
289
290
|
}
|
|
290
|
-
|
|
291
|
-
|
|
291
|
+
// React's package "exports" hides umd/ from require.resolve; resolve the
|
|
292
|
+
// package root via its package.json (always exported) and join from there.
|
|
293
|
+
const umdAssets = {
|
|
294
|
+
'react.js': { pkg: 'react', rel: 'umd/react.production.min.js' },
|
|
295
|
+
'react-dom.js': { pkg: 'react-dom', rel: 'umd/react-dom.production.min.js' },
|
|
296
|
+
'marked.js': { pkg: 'marked', rel: 'lib/marked.umd.js' },
|
|
297
|
+
};
|
|
298
|
+
const fromUmd = umdAssets[asset];
|
|
299
|
+
if (fromUmd) {
|
|
300
|
+
try {
|
|
301
|
+
const pkgRoot = path.dirname(require_.resolve(`${fromUmd.pkg}/package.json`));
|
|
302
|
+
const filePath = path.join(pkgRoot, fromUmd.rel);
|
|
303
|
+
return fs.existsSync(filePath)
|
|
304
|
+
? { filePath, contentType: 'text/javascript; charset=utf-8' }
|
|
305
|
+
: null;
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (asset === 'ide-client.js' || asset === 'chat-client.js') {
|
|
312
|
+
const filePath = path.join(DIST_DIR, asset);
|
|
292
313
|
return fs.existsSync(filePath)
|
|
293
314
|
? { filePath, contentType: 'text/javascript; charset=utf-8' }
|
|
294
315
|
: null;
|
|
295
316
|
}
|
|
296
317
|
return null;
|
|
297
318
|
}
|
|
298
|
-
// Lucide glyphs; currentColor so the button :hover styles drive them.
|
|
299
|
-
|
|
319
|
+
// Lucide glyphs; currentColor so the button :hover styles drive them. The
|
|
320
|
+
// panel toggle reads as "building" (hammer), not terminal -- the terminal is
|
|
321
|
+
// just one view inside the panel.
|
|
322
|
+
const HAMMER_ICON_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 12-8.373 8.373a1 1 0 1 1-3-3L12 9" /><path d="m18 15 4-4" /><path d="m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172V7l-2.26-2.26a6 6 0 0 0-4.202-1.756L9 2.96l.92.82A6.18 6.18 0 0 1 12 8.4V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5" /></svg>`;
|
|
300
323
|
const COG_ICON_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" /><circle cx="12" cy="12" r="3" /></svg>`;
|
|
301
324
|
const IDE_STYLES = `
|
|
302
325
|
:root { color-scheme: light; }
|
|
@@ -311,7 +334,10 @@ const IDE_STYLES = `
|
|
|
311
334
|
--term-header-h: 48px;
|
|
312
335
|
background: #ffffff;
|
|
313
336
|
color: #1f2328;
|
|
314
|
-
|
|
337
|
+
/* Same stack as the kit editor (engine/ui.module.css --castle-font-body)
|
|
338
|
+
so the shell + chat read as one app with the deck's editor. */
|
|
339
|
+
font-family: baltomobile, Balto, ui-sans-serif, system-ui, -apple-system,
|
|
340
|
+
BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
315
341
|
display: flex;
|
|
316
342
|
overflow: hidden;
|
|
317
343
|
}
|
|
@@ -336,15 +362,29 @@ const IDE_STYLES = `
|
|
|
336
362
|
flex-direction: column;
|
|
337
363
|
}
|
|
338
364
|
body.term-dark #term { background: #1a1b26; }
|
|
339
|
-
/*
|
|
340
|
-
|
|
341
|
-
|
|
365
|
+
/* Panel content area below the header. The chat view and the terminal view
|
|
366
|
+
are both mounted, absolutely filling it; the inactive one is lifted to
|
|
367
|
+
visibility:hidden (not display:none) so xterm keeps its measured layout
|
|
368
|
+
-- same trick as the closed-panel state below. */
|
|
369
|
+
#panel-body {
|
|
342
370
|
flex: 1 1 0;
|
|
343
371
|
min-height: 0;
|
|
344
|
-
|
|
372
|
+
position: relative;
|
|
373
|
+
}
|
|
374
|
+
#term-host {
|
|
375
|
+
position: absolute;
|
|
376
|
+
inset: 0;
|
|
345
377
|
padding: 6px;
|
|
346
378
|
overscroll-behavior: contain;
|
|
347
379
|
}
|
|
380
|
+
body:not(.term-view) #term-host { visibility: hidden; }
|
|
381
|
+
#chat-host {
|
|
382
|
+
position: absolute;
|
|
383
|
+
inset: 0;
|
|
384
|
+
display: flex;
|
|
385
|
+
flex-direction: column;
|
|
386
|
+
}
|
|
387
|
+
body.term-view #chat-host { visibility: hidden; }
|
|
348
388
|
|
|
349
389
|
/* Header strip for the terminal panel -- mirrors the deck's editor header
|
|
350
390
|
(same height, 1px bottom border, white background, 16px title). A real
|
|
@@ -362,13 +402,6 @@ const IDE_STYLES = `
|
|
|
362
402
|
}
|
|
363
403
|
body.term-open #term-header { display: flex; }
|
|
364
404
|
body.term-dark #term-header { background: #1a1b26; border-bottom-color: #2a2c3d; }
|
|
365
|
-
#term-header .term-header-title {
|
|
366
|
-
font-size: 16px;
|
|
367
|
-
line-height: 1;
|
|
368
|
-
letter-spacing: 0.01em;
|
|
369
|
-
color: #222222;
|
|
370
|
-
}
|
|
371
|
-
body.term-dark #term-header .term-header-title { color: #c0caf5; }
|
|
372
405
|
|
|
373
406
|
/* Terminal hidden: the panel stays mounted at its full 500px size (so xterm
|
|
374
407
|
keeps its measured layout) -- just lifted out of flow and made invisible,
|
|
@@ -441,15 +474,16 @@ const IDE_STYLES = `
|
|
|
441
474
|
right: 7px;
|
|
442
475
|
z-index: 2147483000;
|
|
443
476
|
display: none;
|
|
444
|
-
width:
|
|
445
|
-
padding:
|
|
477
|
+
width: 300px;
|
|
478
|
+
padding: 10px 12px;
|
|
446
479
|
background: #ffffff;
|
|
447
480
|
color: #222222;
|
|
448
481
|
border: 1px solid #000000;
|
|
449
482
|
border-radius: 4px;
|
|
450
483
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
|
|
451
|
-
font-size:
|
|
484
|
+
font-size: 13px;
|
|
452
485
|
}
|
|
486
|
+
#term-settings-panel .row > span { white-space: nowrap; }
|
|
453
487
|
body.term-settings-open #term-settings-panel { display: block; }
|
|
454
488
|
#term-settings-panel .row {
|
|
455
489
|
display: flex;
|
|
@@ -457,22 +491,256 @@ const IDE_STYLES = `
|
|
|
457
491
|
justify-content: space-between;
|
|
458
492
|
gap: 10px;
|
|
459
493
|
}
|
|
460
|
-
#term-settings-panel .
|
|
494
|
+
#term-settings-panel .row + .row { margin-top: 8px; }
|
|
495
|
+
/* Shared segmented control -- the settings popover rows and the panel
|
|
496
|
+
header's chat | terminal switch. */
|
|
497
|
+
.seg {
|
|
461
498
|
display: flex;
|
|
462
499
|
border: 1px solid #000000;
|
|
463
500
|
border-radius: 4px;
|
|
464
501
|
overflow: hidden;
|
|
465
502
|
}
|
|
466
|
-
|
|
467
|
-
padding:
|
|
503
|
+
.seg button {
|
|
504
|
+
padding: 5px 12px;
|
|
468
505
|
font: inherit;
|
|
506
|
+
font-size: 14px;
|
|
469
507
|
background: #ffffff;
|
|
470
508
|
color: #222222;
|
|
471
509
|
border: 0;
|
|
472
510
|
cursor: pointer;
|
|
473
511
|
}
|
|
474
|
-
|
|
475
|
-
|
|
512
|
+
.seg button + button { border-left: 1px solid #000000; }
|
|
513
|
+
.seg button.active { background: #222222; color: #ffffff; }
|
|
514
|
+
body.term-dark #term-header .seg { border-color: #2a2c3d; }
|
|
515
|
+
body.term-dark #term-header .seg button { background: #1a1b26; color: #c0caf5; }
|
|
516
|
+
body.term-dark #term-header .seg button + button { border-left-color: #2a2c3d; }
|
|
517
|
+
body.term-dark #term-header .seg button.active { background: #c0caf5; color: #1a1b26; }
|
|
518
|
+
|
|
519
|
+
/* -- Agent chat view ----------------------------------------------------
|
|
520
|
+
Matches the editor chrome: Inter, 13px, 1px #e1e4e8 borders, small radii,
|
|
521
|
+
no shadows. */
|
|
522
|
+
#chat-messages {
|
|
523
|
+
flex: 1 1 0;
|
|
524
|
+
min-height: 0;
|
|
525
|
+
overflow-y: auto;
|
|
526
|
+
padding: 14px 14px 8px;
|
|
527
|
+
display: flex;
|
|
528
|
+
flex-direction: column;
|
|
529
|
+
gap: 14px;
|
|
530
|
+
/* Sits between the deck editor's desktop (16px) and narrow (13-14px)
|
|
531
|
+
text sizes -- the iframe crosses that breakpoint as panes resize. */
|
|
532
|
+
font-size: 15px;
|
|
533
|
+
line-height: 1.45;
|
|
534
|
+
}
|
|
535
|
+
#chat-empty { color: #6e7781; }
|
|
536
|
+
.msg-activity {
|
|
537
|
+
color: #6e7781;
|
|
538
|
+
font-size: 12px;
|
|
539
|
+
margin-top: 6px;
|
|
540
|
+
animation: chat-pulse 1.4s ease-in-out infinite;
|
|
541
|
+
}
|
|
542
|
+
@keyframes chat-pulse { 50% { opacity: 0.35; } }
|
|
543
|
+
.msg-interrupted { color: #6e7781; font-size: 12px; margin-top: 6px; }
|
|
544
|
+
.msg-log {
|
|
545
|
+
color: #6e7781;
|
|
546
|
+
font-size: 13px;
|
|
547
|
+
padding: 0 2px;
|
|
548
|
+
}
|
|
549
|
+
.msg-text { word-break: break-word; }
|
|
550
|
+
.msg-user .msg-text { white-space: pre-wrap; }
|
|
551
|
+
/* Markdown content (assistant replies + task notes). */
|
|
552
|
+
.msg-text > :first-child, .task-notes > :first-child { margin-top: 0; }
|
|
553
|
+
.msg-text > :last-child, .task-notes > :last-child { margin-bottom: 0; }
|
|
554
|
+
.msg-text p, .task-notes p { margin: 0.55em 0; }
|
|
555
|
+
.msg-text ul, .msg-text ol, .task-notes ul, .task-notes ol {
|
|
556
|
+
margin: 0.55em 0;
|
|
557
|
+
padding-left: 1.4em;
|
|
558
|
+
}
|
|
559
|
+
.msg-text li { margin: 0.15em 0; }
|
|
560
|
+
.msg-text h1, .msg-text h2, .msg-text h3, .msg-text h4 {
|
|
561
|
+
font-size: 1em;
|
|
562
|
+
margin: 0.7em 0 0.35em;
|
|
563
|
+
}
|
|
564
|
+
.msg-text code, .task-notes code {
|
|
565
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
|
|
566
|
+
font-size: 0.85em;
|
|
567
|
+
background: #f2f4f7;
|
|
568
|
+
border-radius: 3px;
|
|
569
|
+
padding: 1px 4px;
|
|
570
|
+
}
|
|
571
|
+
.msg-text pre {
|
|
572
|
+
background: #f6f8fa;
|
|
573
|
+
border: 1px solid #e1e4e8;
|
|
574
|
+
border-radius: 4px;
|
|
575
|
+
padding: 8px 10px;
|
|
576
|
+
overflow-x: auto;
|
|
577
|
+
margin: 0.55em 0;
|
|
578
|
+
}
|
|
579
|
+
.msg-text pre code { background: none; padding: 0; }
|
|
580
|
+
.msg-image {
|
|
581
|
+
display: block;
|
|
582
|
+
max-width: 220px;
|
|
583
|
+
max-height: 150px;
|
|
584
|
+
border: 1px solid #cccccc;
|
|
585
|
+
border-radius: 4px;
|
|
586
|
+
margin-top: 6px;
|
|
587
|
+
}
|
|
588
|
+
.msg-user { align-self: flex-end; max-width: 85%; }
|
|
589
|
+
.msg-user .msg-text {
|
|
590
|
+
background: #f2f4f7;
|
|
591
|
+
border: 1px solid #cccccc;
|
|
592
|
+
border-radius: 4px;
|
|
593
|
+
padding: 8px 10px;
|
|
594
|
+
}
|
|
595
|
+
/* Each agent turn is its own bordered card -- turn boundaries read at a
|
|
596
|
+
glance instead of running together as one wall of text. */
|
|
597
|
+
.msg-assistant {
|
|
598
|
+
align-self: stretch;
|
|
599
|
+
border: 1px solid #cccccc;
|
|
600
|
+
border-radius: 4px;
|
|
601
|
+
background: #ffffff;
|
|
602
|
+
padding: 8px 10px;
|
|
603
|
+
}
|
|
604
|
+
.msg-assistant.streaming .msg-text::after {
|
|
605
|
+
content: '\\258c';
|
|
606
|
+
opacity: 0.5;
|
|
607
|
+
animation: chat-blink 1s steps(1) infinite;
|
|
608
|
+
}
|
|
609
|
+
@keyframes chat-blink { 50% { opacity: 0; } }
|
|
610
|
+
.msg-error .msg-text { color: #a40e26; }
|
|
611
|
+
.task-card {
|
|
612
|
+
border: 1px solid #e1e4e8;
|
|
613
|
+
border-radius: 6px;
|
|
614
|
+
padding: 7px 10px;
|
|
615
|
+
cursor: pointer;
|
|
616
|
+
}
|
|
617
|
+
.task-card.waiting { opacity: 0.6; }
|
|
618
|
+
/* A finished task's filled pie IS the check-off control -- click it to
|
|
619
|
+
clear the row. Visible even in bar mode (the bar has no icon to click). */
|
|
620
|
+
.task-ack-pie { cursor: pointer; }
|
|
621
|
+
.task-ack-pie:hover { box-shadow: 0 0 0 2px #cfe0ff; }
|
|
622
|
+
body.progress-bars .task-pie.task-ack-pie { display: block; }
|
|
623
|
+
.task-notes {
|
|
624
|
+
margin-top: 6px;
|
|
625
|
+
color: #57606a;
|
|
626
|
+
word-break: break-word;
|
|
627
|
+
font-size: 14px;
|
|
628
|
+
}
|
|
629
|
+
/* Live stream of a running task agent (text lines + [tool labels]) shown
|
|
630
|
+
on expand -- capped height, auto-scrolled, fading at both scroll edges. */
|
|
631
|
+
.task-feed {
|
|
632
|
+
margin-top: 6px;
|
|
633
|
+
max-height: 150px;
|
|
634
|
+
overflow-y: auto;
|
|
635
|
+
font-size: 12px;
|
|
636
|
+
line-height: 1.5;
|
|
637
|
+
color: #57606a;
|
|
638
|
+
cursor: default;
|
|
639
|
+
word-break: break-word;
|
|
640
|
+
-webkit-mask-image: linear-gradient(
|
|
641
|
+
to bottom,
|
|
642
|
+
transparent,
|
|
643
|
+
#000000 14px,
|
|
644
|
+
#000000 calc(100% - 14px),
|
|
645
|
+
transparent
|
|
646
|
+
);
|
|
647
|
+
mask-image: linear-gradient(
|
|
648
|
+
to bottom,
|
|
649
|
+
transparent,
|
|
650
|
+
#000000 14px,
|
|
651
|
+
#000000 calc(100% - 14px),
|
|
652
|
+
transparent
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
.task-row { display: flex; align-items: center; gap: 8px; }
|
|
656
|
+
.task-title {
|
|
657
|
+
flex: 1 1 auto;
|
|
658
|
+
min-width: 0;
|
|
659
|
+
overflow: hidden;
|
|
660
|
+
text-overflow: ellipsis;
|
|
661
|
+
white-space: nowrap;
|
|
662
|
+
}
|
|
663
|
+
.task-title::first-letter { text-transform: uppercase; }
|
|
664
|
+
.task-status { color: #6e7781; font-size: 12px; flex: 0 0 auto; }
|
|
665
|
+
.task-status.failed, .task-status.interrupted { color: #a40e26; }
|
|
666
|
+
.task-pie {
|
|
667
|
+
width: 15px;
|
|
668
|
+
height: 15px;
|
|
669
|
+
border-radius: 50%;
|
|
670
|
+
flex: 0 0 15px;
|
|
671
|
+
border: 1px solid #d0d7de;
|
|
672
|
+
}
|
|
673
|
+
.task-bar { display: none; margin-top: 6px; height: 3px; border-radius: 2px; background: #eaeef2; overflow: hidden; }
|
|
674
|
+
.task-bar > div { height: 100%; background: #0969da; }
|
|
675
|
+
body.progress-bars .task-bar { display: block; }
|
|
676
|
+
body.progress-bars .task-pie { display: none; }
|
|
677
|
+
/* The task board: pinned above the conversation, one row per task until
|
|
678
|
+
the user checks it off. Grows freely -- hiding tasks behind a scroll
|
|
679
|
+
made them too easy to forget. Same text size as the chat. */
|
|
680
|
+
#chat-strip {
|
|
681
|
+
flex: 0 0 auto;
|
|
682
|
+
border-bottom: 1px solid #cccccc;
|
|
683
|
+
padding: 8px 14px;
|
|
684
|
+
display: flex;
|
|
685
|
+
flex-direction: column;
|
|
686
|
+
gap: 4px;
|
|
687
|
+
font-size: 15px;
|
|
688
|
+
background: #f2f4f7;
|
|
689
|
+
}
|
|
690
|
+
.task-card { background: #ffffff; }
|
|
691
|
+
/* Input row styled like the kit editor's inspector controls: 1px black
|
|
692
|
+
borders, 4px radius, same text size; the button matches the kit .button
|
|
693
|
+
(subtle drop shadow). */
|
|
694
|
+
#chat-input-row {
|
|
695
|
+
flex: 0 0 auto;
|
|
696
|
+
display: flex;
|
|
697
|
+
align-items: stretch;
|
|
698
|
+
gap: 8px;
|
|
699
|
+
padding: 10px 14px;
|
|
700
|
+
border-top: 1px solid #cccccc;
|
|
701
|
+
}
|
|
702
|
+
#chat-input {
|
|
703
|
+
flex: 1 1 auto;
|
|
704
|
+
resize: none;
|
|
705
|
+
font: inherit;
|
|
706
|
+
font-size: 15px;
|
|
707
|
+
line-height: 1.4;
|
|
708
|
+
border: 1px solid #000000;
|
|
709
|
+
border-radius: 4px;
|
|
710
|
+
padding: 5px 8px;
|
|
711
|
+
outline: none;
|
|
712
|
+
max-height: 120px;
|
|
713
|
+
background: #ffffff;
|
|
714
|
+
color: #222222;
|
|
715
|
+
}
|
|
716
|
+
#chat-send {
|
|
717
|
+
font: inherit;
|
|
718
|
+
font-size: 15px;
|
|
719
|
+
padding: 6px 10px;
|
|
720
|
+
border: 1px solid #000000;
|
|
721
|
+
border-radius: 4px;
|
|
722
|
+
background: #ffffff;
|
|
723
|
+
color: #222222;
|
|
724
|
+
cursor: pointer;
|
|
725
|
+
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
|
|
726
|
+
}
|
|
727
|
+
#chat-send:hover { background: #eeeeee; }
|
|
728
|
+
/* Pending image attachments (pasted or picked) shown as chips above the
|
|
729
|
+
input; click a chip to remove it. */
|
|
730
|
+
#chat-pending {
|
|
731
|
+
flex: 0 0 auto;
|
|
732
|
+
display: flex;
|
|
733
|
+
gap: 6px;
|
|
734
|
+
padding: 8px 14px 0;
|
|
735
|
+
border-top: 1px solid #cccccc;
|
|
736
|
+
}
|
|
737
|
+
#chat-pending img {
|
|
738
|
+
height: 44px;
|
|
739
|
+
border: 1px solid #cccccc;
|
|
740
|
+
border-radius: 4px;
|
|
741
|
+
cursor: pointer;
|
|
742
|
+
}
|
|
743
|
+
#chat-pending + #chat-input-row { border-top: 0; }
|
|
476
744
|
|
|
477
745
|
/* Let the xterm light theme show through, and hide every scrollbar xterm
|
|
478
746
|
can draw -- the native viewport one AND the renderer overlay one. */
|
|
@@ -509,21 +777,65 @@ const IDE_STYLES = `
|
|
|
509
777
|
.xterm .xterm-scrollable-element > .shadow { display: none !important; }
|
|
510
778
|
`;
|
|
511
779
|
const IDE_BODY = `
|
|
512
|
-
<button id="term-settings" type="button" tabindex="-1" title="
|
|
513
|
-
<button id="term-toggle" type="button" tabindex="-1" title="toggle
|
|
780
|
+
<button id="term-settings" type="button" tabindex="-1" title="panel settings" aria-label="panel settings">${COG_ICON_SVG}</button>
|
|
781
|
+
<button id="term-toggle" type="button" tabindex="-1" title="toggle panel" aria-label="toggle panel">${HAMMER_ICON_SVG}</button>
|
|
514
782
|
<div id="term-settings-panel">
|
|
515
783
|
<div class="row">
|
|
516
|
-
<span>
|
|
784
|
+
<span>Router agent</span>
|
|
785
|
+
<div class="seg" id="agent-router-seg">
|
|
786
|
+
<button type="button" tabindex="-1" data-backend="cursor">Cursor</button>
|
|
787
|
+
<button type="button" tabindex="-1" data-backend="claude">Claude</button>
|
|
788
|
+
</div>
|
|
789
|
+
</div>
|
|
790
|
+
<div class="row">
|
|
791
|
+
<span>Task agents</span>
|
|
792
|
+
<div class="seg" id="agent-tasks-seg">
|
|
793
|
+
<button type="button" tabindex="-1" data-backend="cursor">Cursor</button>
|
|
794
|
+
<button type="button" tabindex="-1" data-backend="claude">Claude</button>
|
|
795
|
+
</div>
|
|
796
|
+
</div>
|
|
797
|
+
<div class="row">
|
|
798
|
+
<span>Claude model</span>
|
|
799
|
+
<div class="seg" id="agent-model-seg">
|
|
800
|
+
<button type="button" tabindex="-1" data-model="sonnet">Sonnet</button>
|
|
801
|
+
<button type="button" tabindex="-1" data-model="opus">Opus</button>
|
|
802
|
+
<button type="button" tabindex="-1" data-model="fable">Fable</button>
|
|
803
|
+
</div>
|
|
804
|
+
</div>
|
|
805
|
+
<div class="row">
|
|
806
|
+
<span>Progress</span>
|
|
807
|
+
<div class="seg" id="chat-progress-seg">
|
|
808
|
+
<button type="button" tabindex="-1" data-progress="pie">Pie</button>
|
|
809
|
+
<button type="button" tabindex="-1" data-progress="bar">Bar</button>
|
|
810
|
+
</div>
|
|
811
|
+
</div>
|
|
812
|
+
<div class="row">
|
|
813
|
+
<span>Terminal theme</span>
|
|
517
814
|
<div class="seg" id="term-theme-seg">
|
|
518
|
-
<button type="button" tabindex="-1" data-theme="light">
|
|
519
|
-
<button type="button" tabindex="-1" data-theme="dark">
|
|
815
|
+
<button type="button" tabindex="-1" data-theme="light">Light</button>
|
|
816
|
+
<button type="button" tabindex="-1" data-theme="dark">Dark</button>
|
|
520
817
|
</div>
|
|
521
818
|
</div>
|
|
522
819
|
</div>
|
|
523
820
|
<div id="deck"><iframe id="deck-frame" src="/index.html" title="deck" allow="autoplay; clipboard-read; clipboard-write; fullscreen; gamepad"></iframe></div>
|
|
524
|
-
<div id="term"
|
|
821
|
+
<div id="term">
|
|
822
|
+
<div id="term-header">
|
|
823
|
+
<div class="seg" id="panel-view-seg">
|
|
824
|
+
<button type="button" tabindex="-1" data-view="chat">Chat</button>
|
|
825
|
+
<button type="button" tabindex="-1" data-view="terminal">Terminal</button>
|
|
826
|
+
</div>
|
|
827
|
+
</div>
|
|
828
|
+
<div id="panel-body">
|
|
829
|
+
<div id="chat-host"></div>
|
|
830
|
+
<div id="term-host"></div>
|
|
831
|
+
</div>
|
|
832
|
+
</div>
|
|
525
833
|
<script src="${IDE_ASSET_PREFIX}xterm.js"></script>
|
|
526
834
|
<script src="${IDE_ASSET_PREFIX}addon-fit.js"></script>
|
|
835
|
+
<script src="${IDE_ASSET_PREFIX}react.js"></script>
|
|
836
|
+
<script src="${IDE_ASSET_PREFIX}react-dom.js"></script>
|
|
837
|
+
<script src="${IDE_ASSET_PREFIX}marked.js"></script>
|
|
838
|
+
<script type="module" src="${IDE_ASSET_PREFIX}chat-client.js"></script>
|
|
527
839
|
<script type="module" src="${IDE_ASSET_PREFIX}ide-client.js"></script>
|
|
528
840
|
`;
|
|
529
841
|
function renderIdePage(deckLabel) {
|
|
@@ -537,7 +849,7 @@ function renderIdePage(deckLabel) {
|
|
|
537
849
|
<link rel="stylesheet" href="${IDE_ASSET_PREFIX}xterm.css" />
|
|
538
850
|
<style>${IDE_STYLES}</style>
|
|
539
851
|
</head>
|
|
540
|
-
<body>${IDE_BODY}</body>
|
|
852
|
+
<body class="term-open">${IDE_BODY}</body>
|
|
541
853
|
</html>
|
|
542
854
|
`;
|
|
543
855
|
}
|
package/dist/init.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
+
import { COMMON_INSTRUCTIONS } from './commonInstructions.js';
|
|
4
5
|
import { getCliEntryPath, getKitsDir, getRepoRoot, getSdkPackagePath, toPosixPath } from './localPaths.js';
|
|
5
6
|
import { serve } from './serve.js';
|
|
6
7
|
const INDEX_HTML = `<!DOCTYPE html>
|
|
@@ -34,7 +35,7 @@ const DEFAULT_KIT = 'basic-2d';
|
|
|
34
35
|
// Registry version of castle-web-sdk to inject when scaffolding from a
|
|
35
36
|
// globally-installed castle-web (not from inside the workspace). Bumped
|
|
36
37
|
// alongside cli/sdk version bumps.
|
|
37
|
-
const PUBLISHED_SDK_VERSION = '0.4.
|
|
38
|
+
const PUBLISHED_SDK_VERSION = '0.4.3';
|
|
38
39
|
// Never copied into a fresh deck: build/dependency junk, and castle.json (a
|
|
39
40
|
// fresh deck has no deckId until its first save-deck).
|
|
40
41
|
const KIT_COPY_EXCLUDE = new Set(['node_modules', '.castle', 'dist', '.git', 'castle.json']);
|
|
@@ -54,6 +55,14 @@ function makeClaudeMd() {
|
|
|
54
55
|
return `# Castle Experimental Web\n\nSee https://github.com/castle-xyz/castle-experimental-web for the agent guide.\n`;
|
|
55
56
|
}
|
|
56
57
|
}
|
|
58
|
+
// Append the cli-owned common guidance to the deck's CLAUDE.md. Runs after
|
|
59
|
+
// the kit's (or bare) CLAUDE.md is written; the AGENTS.md symlink picks the
|
|
60
|
+
// appended content up for free.
|
|
61
|
+
function appendCommonInstructions(projectDir) {
|
|
62
|
+
const claudePath = path.join(projectDir, 'CLAUDE.md');
|
|
63
|
+
const existing = fs.existsSync(claudePath) ? fs.readFileSync(claudePath, 'utf8').trimEnd() + '\n\n' : '';
|
|
64
|
+
fs.writeFileSync(claudePath, existing + COMMON_INSTRUCTIONS);
|
|
65
|
+
}
|
|
57
66
|
function tryMakeAgentsSymlink(agentsPath) {
|
|
58
67
|
try {
|
|
59
68
|
fs.symlinkSync('CLAUDE.md', agentsPath);
|
|
@@ -96,6 +105,7 @@ function scaffoldBare(projectDir) {
|
|
|
96
105
|
fs.writeFileSync(path.join(projectDir, 'index.html'), INDEX_HTML);
|
|
97
106
|
fs.writeFileSync(path.join(projectDir, 'game.js'), GAME_JS);
|
|
98
107
|
fs.writeFileSync(path.join(projectDir, 'CLAUDE.md'), makeClaudeMd());
|
|
108
|
+
appendCommonInstructions(projectDir);
|
|
99
109
|
ensureAgentsSymlink(projectDir);
|
|
100
110
|
fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify(makePackageJson(projectDir), null, 2) + '\n');
|
|
101
111
|
}
|
|
@@ -186,6 +196,7 @@ function scaffoldFromKit(kit, projectDir) {
|
|
|
186
196
|
if (!fs.existsSync(claudePath)) {
|
|
187
197
|
fs.writeFileSync(claudePath, makeClaudeMd());
|
|
188
198
|
}
|
|
199
|
+
appendCommonInstructions(projectDir);
|
|
189
200
|
ensureAgentsSymlink(projectDir);
|
|
190
201
|
}
|
|
191
202
|
export async function init(dir, opts = {}) {
|
package/dist/serve.js
CHANGED
|
@@ -5,6 +5,7 @@ import { spawn } from 'child_process';
|
|
|
5
5
|
import { createServer } from 'vite';
|
|
6
6
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
7
7
|
import { createIdeServer, DECK_FOCUS_SCRIPT } from './ide.js';
|
|
8
|
+
import { createAgentServer } from './agent.js';
|
|
8
9
|
import * as config from './config.js';
|
|
9
10
|
function isPortFree(port) {
|
|
10
11
|
return new Promise((resolve) => {
|
|
@@ -21,7 +22,7 @@ async function findFreePorts(startPort) {
|
|
|
21
22
|
}
|
|
22
23
|
throw new Error(`No free port pair found starting from ${startPort}`);
|
|
23
24
|
}
|
|
24
|
-
function castlePlugin(wsPort, ideServer) {
|
|
25
|
+
function castlePlugin(wsPort, ideServer, agentServer) {
|
|
25
26
|
return {
|
|
26
27
|
name: 'castle-dev',
|
|
27
28
|
transformIndexHtml: {
|
|
@@ -54,6 +55,8 @@ function castlePlugin(wsPort, ideServer) {
|
|
|
54
55
|
const reqPath = req.url.split('?')[0];
|
|
55
56
|
if (ideServer.handleHttpRequest(req, res, reqPath))
|
|
56
57
|
return;
|
|
58
|
+
if (agentServer.handleHttpRequest(req, res, reqPath))
|
|
59
|
+
return;
|
|
57
60
|
}
|
|
58
61
|
next();
|
|
59
62
|
});
|
|
@@ -179,13 +182,23 @@ export async function serve(dir, options = {}) {
|
|
|
179
182
|
deckLabel: path.basename(projectDir),
|
|
180
183
|
});
|
|
181
184
|
process.on('exit', () => ideServer.shutdown());
|
|
185
|
+
// The agent panel backend: router conversation + background task agents,
|
|
186
|
+
// state under .castle/agent/. Its WebSocket shares Vite's HTTP server.
|
|
187
|
+
const agentServer = createAgentServer({
|
|
188
|
+
deckDir: projectDir,
|
|
189
|
+
deckLabel: path.basename(projectDir),
|
|
190
|
+
});
|
|
191
|
+
process.on('exit', () => agentServer.shutdown());
|
|
182
192
|
const vite = await createServer({
|
|
183
193
|
root: projectDir,
|
|
184
|
-
plugins: [castlePlugin(wsPort, ideServer)],
|
|
194
|
+
plugins: [castlePlugin(wsPort, ideServer, agentServer)],
|
|
185
195
|
server: {
|
|
186
196
|
port,
|
|
187
197
|
strictPort: true,
|
|
188
198
|
host: options.host,
|
|
199
|
+
// The serve is reached via tailnet/LAN hostnames (not just localhost);
|
|
200
|
+
// vite's host check would reject those.
|
|
201
|
+
allowedHosts: true,
|
|
189
202
|
open: options.open ? true : undefined,
|
|
190
203
|
hmr: false,
|
|
191
204
|
proxy: {
|
|
@@ -204,7 +217,9 @@ export async function serve(dir, options = {}) {
|
|
|
204
217
|
// than through Vite's proxy (Vite's ws proxy didn't forward this path).
|
|
205
218
|
if (vite.httpServer) {
|
|
206
219
|
vite.httpServer.on('upgrade', (req, socket, head) => {
|
|
207
|
-
ideServer.handleUpgrade(req, socket, head)
|
|
220
|
+
if (!ideServer.handleUpgrade(req, socket, head)) {
|
|
221
|
+
agentServer.handleUpgrade(req, socket, head);
|
|
222
|
+
}
|
|
208
223
|
});
|
|
209
224
|
}
|
|
210
225
|
vite.printUrls();
|
package/kits/basic-2d/CLAUDE.md
CHANGED
|
@@ -111,9 +111,11 @@ Rules: every actor needs a unique `id` (any string) and almost always a `Layout`
|
|
|
111
111
|
```jsx
|
|
112
112
|
if (scene.keys.has('ArrowLeft')) layout.x -= speed * dt;
|
|
113
113
|
if (scene.keys.has('ArrowRight')) layout.x += speed * dt;
|
|
114
|
-
if (scene.keys.has('
|
|
114
|
+
if (scene.keys.has('KeyX')) /* launch ball */ ;
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
+
**Space is reserved** — the editor binds it to the play/stop toggle, so don't bind Space to a gameplay action (jump / shoot / launch / ...). Use arrows, WASD, letter keys, or on-screen buttons instead.
|
|
118
|
+
|
|
117
119
|
For HUD text use a behavior's `ui` hook (returns React); for in-world text or shapes, draw with `ctx` from `draw`. The deck has TouchControls overlay support out of the box for mobile play (arrow keys + a button); no setup needed.
|
|
118
120
|
|
|
119
121
|
## Common breakout-shaped recipe (sketch)
|