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.
Files changed (36) hide show
  1. package/dist/agent-failures.d.ts +1 -0
  2. package/dist/agent-failures.js +31 -4
  3. package/dist/agent-prompts.d.ts +0 -3
  4. package/dist/agent-prompts.js +14 -28
  5. package/dist/agent.d.ts +1 -13
  6. package/dist/agent.js +191 -938
  7. package/dist/byo-accounts.d.ts +4 -4
  8. package/dist/byo-accounts.js +17 -38
  9. package/dist/byo-auth.d.ts +2 -5
  10. package/dist/byo-auth.js +4 -52
  11. package/dist/byo-login.d.ts +4 -5
  12. package/dist/byo-login.js +28 -56
  13. package/dist/mcpPlaytest.js +1 -2
  14. package/dist/metering.d.ts +35 -23
  15. package/dist/metering.js +57 -52
  16. package/dist/openrouter-catalog.d.ts +0 -1
  17. package/dist/openrouter-catalog.js +0 -11
  18. package/dist/platformDoc.d.ts +1 -1
  19. package/dist/platformDoc.js +9 -12
  20. package/dist/serve.js +3 -5
  21. package/dist/shell/assets/index-BHWfx7Tz.css +1 -0
  22. package/dist/shell/assets/index-BMu42DTS.js +449 -0
  23. package/dist/shell/index.html +2 -2
  24. package/kits/physics-2d/CLAUDE.md +5 -1
  25. package/kits/physics-2d/castle.json +1 -1
  26. package/kits/physics-2d/editors/SceneEditor.jsx +2 -1
  27. package/kits/physics-2d/engine/autoInspector.jsx +32 -3
  28. package/kits/physics-2d/engine/fields/fields.jsx +305 -5
  29. package/kits/physics-2d/engine/fields/fields.module.css +76 -0
  30. package/kits/physics-2d/engine/paletteField.jsx +8 -3
  31. package/kits/physics-2d/engine/spriteField.jsx +3 -3
  32. package/kits/physics-2d/engine/ui.jsx +5 -277
  33. package/kits/physics-2d/engine/ui.module.css +0 -69
  34. package/package.json +1 -1
  35. package/dist/shell/assets/index-DnNy-Z5u.js +0 -447
  36. 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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "castle-web-cli",
3
- "version": "0.4.183",
3
+ "version": "0.4.185",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/castle-xyz/castle-experimental-web.git"