castle-web-cli 0.4.183 → 0.4.185
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-failures.d.ts +1 -0
- package/dist/agent-failures.js +31 -4
- package/dist/agent-prompts.d.ts +0 -3
- package/dist/agent-prompts.js +14 -28
- package/dist/agent.d.ts +1 -13
- package/dist/agent.js +191 -938
- package/dist/byo-accounts.d.ts +4 -4
- package/dist/byo-accounts.js +17 -38
- package/dist/byo-auth.d.ts +2 -5
- package/dist/byo-auth.js +4 -52
- package/dist/byo-login.d.ts +4 -5
- package/dist/byo-login.js +28 -56
- package/dist/mcpPlaytest.js +1 -2
- package/dist/metering.d.ts +35 -23
- package/dist/metering.js +57 -52
- package/dist/openrouter-catalog.d.ts +0 -1
- package/dist/openrouter-catalog.js +0 -11
- package/dist/platformDoc.d.ts +1 -1
- package/dist/platformDoc.js +9 -12
- package/dist/serve.js +3 -5
- package/dist/shell/assets/index-BHWfx7Tz.css +1 -0
- package/dist/shell/assets/index-BMu42DTS.js +449 -0
- package/dist/shell/index.html +2 -2
- package/kits/physics-2d/CLAUDE.md +5 -1
- package/kits/physics-2d/castle.json +1 -1
- package/kits/physics-2d/editors/SceneEditor.jsx +2 -1
- package/kits/physics-2d/engine/autoInspector.jsx +32 -3
- package/kits/physics-2d/engine/fields/fields.jsx +305 -5
- package/kits/physics-2d/engine/fields/fields.module.css +76 -0
- package/kits/physics-2d/engine/paletteField.jsx +8 -3
- package/kits/physics-2d/engine/spriteField.jsx +3 -3
- package/kits/physics-2d/engine/ui.jsx +5 -277
- package/kits/physics-2d/engine/ui.module.css +0 -69
- package/package.json +1 -1
- package/dist/shell/assets/index-DnNy-Z5u.js +0 -447
- package/dist/shell/assets/index-cdsH2lna.css +0 -1
|
@@ -2,14 +2,18 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import { icons } from './icons';
|
|
4
4
|
import { FieldRow, useMediaQuery } from './fields/fields';
|
|
5
|
+
import fieldStyles from './fields/fields.module.css';
|
|
5
6
|
import styles from './ui.module.css';
|
|
6
|
-
export { styles };
|
|
7
|
+
export { fieldStyles, styles };
|
|
7
8
|
export {
|
|
8
9
|
CheckboxField,
|
|
9
10
|
ColorField,
|
|
10
11
|
FieldNote,
|
|
11
12
|
FieldRow,
|
|
13
|
+
FileField,
|
|
14
|
+
followAnchor,
|
|
12
15
|
NumberField,
|
|
16
|
+
SelectField,
|
|
13
17
|
TextField,
|
|
14
18
|
useCoarsePointer,
|
|
15
19
|
} from './fields/fields';
|
|
@@ -629,279 +633,3 @@ export function ConfirmDialog({ title, message, confirmLabel = 'Delete', danger
|
|
|
629
633
|
</div>
|
|
630
634
|
);
|
|
631
635
|
}
|
|
632
|
-
export function SelectField({ label, value, onChange, options, overridden, defaultValue, onReset }) {
|
|
633
|
-
return (
|
|
634
|
-
<FieldRow label={label} overridden={overridden} defaultValue={defaultValue} onReset={onReset}>
|
|
635
|
-
<select
|
|
636
|
-
className={styles.select}
|
|
637
|
-
value={value ?? ''}
|
|
638
|
-
onChange={(event) => onChange(event.target.value)}>
|
|
639
|
-
{options.map((option) => {
|
|
640
|
-
const value = typeof option === 'string' ? option : option.value;
|
|
641
|
-
const label = typeof option === 'string' ? option : option.label;
|
|
642
|
-
return (
|
|
643
|
-
<option key={value} value={value}>
|
|
644
|
-
{label}
|
|
645
|
-
</option>
|
|
646
|
-
);
|
|
647
|
-
})}
|
|
648
|
-
</select>
|
|
649
|
-
</FieldRow>
|
|
650
|
-
);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
function basenameOf(path) {
|
|
654
|
-
if (!path) return '';
|
|
655
|
-
return path.slice(path.lastIndexOf('/') + 1);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
let measureCanvasCtx;
|
|
659
|
-
function measureTextWidth(text, font) {
|
|
660
|
-
if (!measureCanvasCtx) {
|
|
661
|
-
measureCanvasCtx = document.createElement('canvas').getContext('2d');
|
|
662
|
-
}
|
|
663
|
-
measureCanvasCtx.font = font;
|
|
664
|
-
return measureCanvasCtx.measureText(text).width;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// Keep the filename intact; the directory is what yields. Binary-search the
|
|
668
|
-
// prefix of the directory until `{prefix}…/{basename}` fits `maxWidth`.
|
|
669
|
-
function fitPathKeepBasename(path, maxWidth, font) {
|
|
670
|
-
const measure = (text) => measureTextWidth(text, font);
|
|
671
|
-
if (maxWidth <= 0 || measure(path) <= maxWidth) return path;
|
|
672
|
-
const slash = path.lastIndexOf('/');
|
|
673
|
-
const dir = slash > 0 ? path.slice(0, slash) : '';
|
|
674
|
-
const base = slash > 0 ? path.slice(slash + 1) : path;
|
|
675
|
-
const ellipsed = `…/${base}`;
|
|
676
|
-
if (!dir || measure(ellipsed) > maxWidth) return fitKeepEnd(base, maxWidth, measure);
|
|
677
|
-
let lo = 0;
|
|
678
|
-
let hi = dir.length;
|
|
679
|
-
let best = ellipsed;
|
|
680
|
-
while (lo <= hi) {
|
|
681
|
-
const mid = (lo + hi) >> 1;
|
|
682
|
-
const candidate = `${dir.slice(0, mid)}…/${base}`;
|
|
683
|
-
if (measure(candidate) <= maxWidth) {
|
|
684
|
-
best = candidate;
|
|
685
|
-
lo = mid + 1;
|
|
686
|
-
} else {
|
|
687
|
-
hi = mid - 1;
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
return best;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
function fitKeepEnd(text, maxWidth, measure) {
|
|
694
|
-
if (measure(text) <= maxWidth) return text;
|
|
695
|
-
if (measure('…') > maxWidth) return '';
|
|
696
|
-
let lo = 0;
|
|
697
|
-
let hi = text.length;
|
|
698
|
-
let best = '…';
|
|
699
|
-
while (lo <= hi) {
|
|
700
|
-
const mid = (lo + hi) >> 1;
|
|
701
|
-
const candidate = `…${text.slice(text.length - mid)}`;
|
|
702
|
-
if (measure(candidate) <= maxWidth) {
|
|
703
|
-
best = candidate;
|
|
704
|
-
lo = mid + 1;
|
|
705
|
-
} else {
|
|
706
|
-
hi = mid - 1;
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
return best;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
function FilePathLabel({ path }) {
|
|
713
|
-
const ref = useRef(null);
|
|
714
|
-
const [shown, setShown] = useState(path);
|
|
715
|
-
useLayoutEffect(() => {
|
|
716
|
-
const node = ref.current;
|
|
717
|
-
if (!node) return undefined;
|
|
718
|
-
function fit() {
|
|
719
|
-
const max = node.clientWidth;
|
|
720
|
-
if (max <= 0) return;
|
|
721
|
-
const cs = getComputedStyle(node);
|
|
722
|
-
const font = cs.font && cs.font !== '0px' ? cs.font : `${cs.fontWeight} ${cs.fontSize} ${cs.fontFamily}`;
|
|
723
|
-
setShown(fitPathKeepBasename(path, max, font));
|
|
724
|
-
}
|
|
725
|
-
fit();
|
|
726
|
-
const observer = new ResizeObserver(fit);
|
|
727
|
-
observer.observe(node);
|
|
728
|
-
return () => observer.disconnect();
|
|
729
|
-
}, [path]);
|
|
730
|
-
return (
|
|
731
|
-
<span ref={ref} className={styles.filePickerPath} title={path}>
|
|
732
|
-
{shown}
|
|
733
|
-
</span>
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
function pickerRowChrome(list) {
|
|
738
|
-
if (!list) return 26;
|
|
739
|
-
const box = getComputedStyle(list);
|
|
740
|
-
const opt = list.querySelector('button');
|
|
741
|
-
const op = opt ? getComputedStyle(opt) : null;
|
|
742
|
-
return (
|
|
743
|
-
(parseFloat(box.paddingLeft) || 0) +
|
|
744
|
-
(parseFloat(box.paddingRight) || 0) +
|
|
745
|
-
(parseFloat(box.borderLeftWidth) || 0) +
|
|
746
|
-
(parseFloat(box.borderRightWidth) || 0) +
|
|
747
|
-
(op ? (parseFloat(op.paddingLeft) || 0) + (parseFloat(op.paddingRight) || 0) : 16)
|
|
748
|
-
);
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
function longestPathWidth(list, paths) {
|
|
752
|
-
if (!list || !paths.length) return 0;
|
|
753
|
-
const probe = document.createElement('span');
|
|
754
|
-
probe.style.cssText = 'position:absolute;left:-9999px;white-space:nowrap;pointer-events:none;';
|
|
755
|
-
const opt = list.querySelector('button');
|
|
756
|
-
if (opt) {
|
|
757
|
-
const s = getComputedStyle(opt);
|
|
758
|
-
probe.style.font = s.font && s.font !== '0px' ? s.font : `${s.fontWeight} ${s.fontSize} ${s.fontFamily}`;
|
|
759
|
-
}
|
|
760
|
-
list.appendChild(probe);
|
|
761
|
-
let max = 0;
|
|
762
|
-
for (const path of paths) {
|
|
763
|
-
probe.textContent = path || 'None';
|
|
764
|
-
max = Math.max(max, probe.offsetWidth);
|
|
765
|
-
}
|
|
766
|
-
probe.remove();
|
|
767
|
-
return max;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// Place a body-portaled popover now and again on every resize or scroll, so it
|
|
771
|
-
// keeps to its anchor. Scroll is captured because it doesn't bubble, and the
|
|
772
|
-
// anchor can sit in any scrolling ancestor. Returns the cleanup.
|
|
773
|
-
export function followAnchor(place) {
|
|
774
|
-
place();
|
|
775
|
-
window.addEventListener('resize', place);
|
|
776
|
-
window.addEventListener('scroll', place, true);
|
|
777
|
-
return () => {
|
|
778
|
-
window.removeEventListener('resize', place);
|
|
779
|
-
window.removeEventListener('scroll', place, true);
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// File path field. Closed chrome shows the basename; the open list shows full
|
|
784
|
-
// paths. Callers pass the candidate paths — this does not decide what a sprite
|
|
785
|
-
// or sound is. `allowEmpty` adds a None row that writes ''.
|
|
786
|
-
export function FileField({
|
|
787
|
-
label,
|
|
788
|
-
value,
|
|
789
|
-
onChange,
|
|
790
|
-
files = [],
|
|
791
|
-
allowEmpty = false,
|
|
792
|
-
overridden,
|
|
793
|
-
defaultValue,
|
|
794
|
-
onReset,
|
|
795
|
-
}) {
|
|
796
|
-
const current = value ?? '';
|
|
797
|
-
const [open, setOpen] = useState(false);
|
|
798
|
-
const buttonRef = useRef(null);
|
|
799
|
-
const listRef = useRef(null);
|
|
800
|
-
const [pos, setPos] = useState(null);
|
|
801
|
-
|
|
802
|
-
const paths = [];
|
|
803
|
-
if (allowEmpty) paths.push('');
|
|
804
|
-
for (const path of files) {
|
|
805
|
-
if (path && !paths.includes(path)) paths.push(path);
|
|
806
|
-
}
|
|
807
|
-
if (current && !paths.includes(current)) paths.splice(allowEmpty ? 1 : 0, 0, current);
|
|
808
|
-
|
|
809
|
-
useLayoutEffect(() => {
|
|
810
|
-
if (!open || !buttonRef.current) return undefined;
|
|
811
|
-
function place() {
|
|
812
|
-
const anchor = buttonRef.current.getBoundingClientRect();
|
|
813
|
-
const list = listRef.current;
|
|
814
|
-
const margin = 8;
|
|
815
|
-
const maxWidth = window.innerWidth - margin * 2;
|
|
816
|
-
const needed = longestPathWidth(list, paths) + pickerRowChrome(list) + 4;
|
|
817
|
-
const width = Math.min(Math.max(needed, anchor.width), maxWidth);
|
|
818
|
-
// Grow left from the field if the paths need more than the control width.
|
|
819
|
-
let left = anchor.right - width;
|
|
820
|
-
if (left < margin) left = margin;
|
|
821
|
-
if (left + width > window.innerWidth - margin) {
|
|
822
|
-
left = Math.max(margin, window.innerWidth - width - margin);
|
|
823
|
-
}
|
|
824
|
-
const gap = 4;
|
|
825
|
-
const height = list?.getBoundingClientRect().height ?? 0;
|
|
826
|
-
let top = anchor.bottom + gap;
|
|
827
|
-
if (height && top + height > window.innerHeight - margin) {
|
|
828
|
-
top = Math.max(margin, anchor.top - gap - height);
|
|
829
|
-
}
|
|
830
|
-
setPos({ top, left, width });
|
|
831
|
-
}
|
|
832
|
-
return followAnchor(place);
|
|
833
|
-
}, [open, paths.length]);
|
|
834
|
-
|
|
835
|
-
useEffect(() => {
|
|
836
|
-
if (!open) return undefined;
|
|
837
|
-
function onKeyDown(event) {
|
|
838
|
-
if (event.key === 'Escape') setOpen(false);
|
|
839
|
-
}
|
|
840
|
-
function onPointerDown(event) {
|
|
841
|
-
if (listRef.current?.contains(event.target) || buttonRef.current?.contains(event.target)) {
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
setOpen(false);
|
|
845
|
-
}
|
|
846
|
-
window.addEventListener('keydown', onKeyDown);
|
|
847
|
-
window.addEventListener('pointerdown', onPointerDown);
|
|
848
|
-
return () => {
|
|
849
|
-
window.removeEventListener('keydown', onKeyDown);
|
|
850
|
-
window.removeEventListener('pointerdown', onPointerDown);
|
|
851
|
-
};
|
|
852
|
-
}, [open]);
|
|
853
|
-
|
|
854
|
-
return (
|
|
855
|
-
<FieldRow
|
|
856
|
-
label={label}
|
|
857
|
-
overridden={overridden}
|
|
858
|
-
defaultValue={defaultValue ? basenameOf(defaultValue) : defaultValue}
|
|
859
|
-
onReset={onReset}>
|
|
860
|
-
<button
|
|
861
|
-
ref={buttonRef}
|
|
862
|
-
type="button"
|
|
863
|
-
className={cx(styles.select, styles.fileField)}
|
|
864
|
-
aria-haspopup="listbox"
|
|
865
|
-
aria-expanded={open}
|
|
866
|
-
onClick={() => setOpen((next) => !next)}>
|
|
867
|
-
<span className={cx(styles.fileFieldLabel, !current && styles.fileFieldEmpty)}>
|
|
868
|
-
{current ? basenameOf(current) : 'None'}
|
|
869
|
-
</span>
|
|
870
|
-
</button>
|
|
871
|
-
{open
|
|
872
|
-
? createPortal(
|
|
873
|
-
<div
|
|
874
|
-
ref={listRef}
|
|
875
|
-
className={styles.filePicker}
|
|
876
|
-
role="listbox"
|
|
877
|
-
aria-label={label}
|
|
878
|
-
style={pos ?? { visibility: 'hidden' }}>
|
|
879
|
-
{paths.map((path) => {
|
|
880
|
-
const selected = path === current;
|
|
881
|
-
return (
|
|
882
|
-
<button
|
|
883
|
-
key={path || '__none'}
|
|
884
|
-
type="button"
|
|
885
|
-
role="option"
|
|
886
|
-
aria-selected={selected}
|
|
887
|
-
className={cx(
|
|
888
|
-
styles.filePickerOption,
|
|
889
|
-
selected && styles.filePickerOptionSelected,
|
|
890
|
-
!path && styles.fileFieldEmpty
|
|
891
|
-
)}
|
|
892
|
-
title={path || 'None'}
|
|
893
|
-
onClick={() => {
|
|
894
|
-
onChange(path);
|
|
895
|
-
setOpen(false);
|
|
896
|
-
}}>
|
|
897
|
-
{path ? <FilePathLabel path={path} /> : 'None'}
|
|
898
|
-
</button>
|
|
899
|
-
);
|
|
900
|
-
})}
|
|
901
|
-
</div>,
|
|
902
|
-
document.body
|
|
903
|
-
)
|
|
904
|
-
: null}
|
|
905
|
-
</FieldRow>
|
|
906
|
-
);
|
|
907
|
-
}
|
|
@@ -1742,7 +1742,6 @@
|
|
|
1742
1742
|
}
|
|
1743
1743
|
|
|
1744
1744
|
/* --field-border is set by an overridden FieldRow (fields/fields.module.css). */
|
|
1745
|
-
.select,
|
|
1746
1745
|
.textarea {
|
|
1747
1746
|
width: 100%;
|
|
1748
1747
|
border: 1px solid var(--field-border, var(--castle-inspector-border));
|
|
@@ -1753,35 +1752,6 @@
|
|
|
1753
1752
|
outline: 0;
|
|
1754
1753
|
}
|
|
1755
1754
|
|
|
1756
|
-
.select {
|
|
1757
|
-
appearance: none;
|
|
1758
|
-
-webkit-appearance: none;
|
|
1759
|
-
padding-right: 26px;
|
|
1760
|
-
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8l3.5-3.5' fill='none' stroke='%23888888' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
1761
|
-
background-repeat: no-repeat;
|
|
1762
|
-
background-position: right 8px center;
|
|
1763
|
-
text-overflow: ellipsis;
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
.fileField {
|
|
1767
|
-
display: flex;
|
|
1768
|
-
align-items: center;
|
|
1769
|
-
font: inherit;
|
|
1770
|
-
box-shadow: none;
|
|
1771
|
-
cursor: pointer;
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
.fileFieldLabel {
|
|
1775
|
-
min-width: 0;
|
|
1776
|
-
overflow: hidden;
|
|
1777
|
-
text-overflow: ellipsis;
|
|
1778
|
-
white-space: nowrap;
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
|
-
.fileFieldEmpty {
|
|
1782
|
-
color: var(--castle-inspector-muted);
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
1755
|
/* PaletteColorField: swatch + hex in the closed chrome. */
|
|
1786
1756
|
.paletteColorField {
|
|
1787
1757
|
gap: 8px;
|
|
@@ -1920,45 +1890,6 @@
|
|
|
1920
1890
|
font-size: 5.5px;
|
|
1921
1891
|
}
|
|
1922
1892
|
|
|
1923
|
-
.filePicker {
|
|
1924
|
-
position: fixed;
|
|
1925
|
-
z-index: 200;
|
|
1926
|
-
box-sizing: border-box;
|
|
1927
|
-
max-height: min(240px, calc(100vh - 16px));
|
|
1928
|
-
overflow: auto;
|
|
1929
|
-
padding: 4px;
|
|
1930
|
-
border: 1px solid var(--castle-inspector-border);
|
|
1931
|
-
border-radius: var(--castle-radius);
|
|
1932
|
-
background: var(--castle-workspace-bg);
|
|
1933
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
.filePickerOption {
|
|
1937
|
-
display: block;
|
|
1938
|
-
width: 100%;
|
|
1939
|
-
overflow: hidden;
|
|
1940
|
-
text-align: left;
|
|
1941
|
-
border: 0;
|
|
1942
|
-
border-radius: 3px;
|
|
1943
|
-
background: transparent;
|
|
1944
|
-
color: var(--castle-inspector-text);
|
|
1945
|
-
font: inherit;
|
|
1946
|
-
font-size: 13px;
|
|
1947
|
-
padding: 6px 8px;
|
|
1948
|
-
cursor: pointer;
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
.filePickerPath {
|
|
1952
|
-
display: block;
|
|
1953
|
-
overflow: hidden;
|
|
1954
|
-
white-space: nowrap;
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
.filePickerOption:hover,
|
|
1958
|
-
.filePickerOptionSelected {
|
|
1959
|
-
background: var(--castle-inspector-input-bg);
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
1893
|
.textarea {
|
|
1963
1894
|
min-height: 0;
|
|
1964
1895
|
resize: vertical;
|