tide-commander 1.37.0 → 1.37.2
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/assets/{BossLogsModal-BStUTJH0.js → BossLogsModal-C9c6b6JM.js} +1 -1
- package/dist/assets/{BossSpawnModal-BD9dP_bi.js → BossSpawnModal-dSXYldID.js} +1 -1
- package/dist/assets/{ControlsModal-D0z4kDXm.js → ControlsModal-C_W0XTlE.js} +1 -1
- package/dist/assets/{DockerLogsModal-Cic5gzhr.js → DockerLogsModal-CUweGxhz.js} +1 -1
- package/dist/assets/{EmbeddedEditor-DyhwTp2-.js → EmbeddedEditor-DU_lcIBD.js} +1 -1
- package/dist/assets/{GmailOAuthSetup-BkoZ3mMf.js → GmailOAuthSetup-DTAkTnzL.js} +1 -1
- package/dist/assets/{GoogleOAuthSetup-BS95rc2s.js → GoogleOAuthSetup-B83vPcAW.js} +1 -1
- package/dist/assets/{IframeModal-CZhllcDM.js → IframeModal-3rIUuKKu.js} +1 -1
- package/dist/assets/{IntegrationsPanel-D-7XWz0N.js → IntegrationsPanel-CJviFK3q.js} +2 -2
- package/dist/assets/{LogViewerModal-CUnmonYV.js → LogViewerModal-C9qFaC8V.js} +1 -1
- package/dist/assets/{MonitoringModal-CF7SKx1_.js → MonitoringModal-DRZrxL7t.js} +1 -1
- package/dist/assets/{PM2LogsModal-Chjt5CBh.js → PM2LogsModal-BLzKH4ql.js} +1 -1
- package/dist/assets/{RestoreArchivedAreaModal-D7uKvdQX.js → RestoreArchivedAreaModal-DHH8QKkx.js} +1 -1
- package/dist/assets/{SaveSnapshotModal-Dl2kQHjX.js → SaveSnapshotModal-BTvvgNqk.js} +1 -1
- package/dist/assets/{Scene2DCanvas-Dxc-W5Ez.js → Scene2DCanvas-Gu0Pplgl.js} +1 -1
- package/dist/assets/{SceneManager-C-0M1QjA.js → SceneManager-BD0UwiqS.js} +1 -1
- package/dist/assets/{SkillsPanel-Bn7OWkDt.js → SkillsPanel-Btfm8DLY.js} +1 -1
- package/dist/assets/{SnapshotManager-B3yvepjG.js → SnapshotManager-BgCuvtUx.js} +1 -1
- package/dist/assets/{SpawnModal-6ZRTBZl_.js → SpawnModal-ByhvPfN7.js} +1 -1
- package/dist/assets/{SubordinateAssignmentModal-BCpmlsht.js → SubordinateAssignmentModal-5Re0nGK9.js} +1 -1
- package/dist/assets/{SupervisorPanel-DEMnY_NN.js → SupervisorPanel-DtNzjH1r.js} +1 -1
- package/dist/assets/{TriggerManagerPanel-BLkhXpZ7.js → TriggerManagerPanel-DUXzcjBS.js} +1 -1
- package/dist/assets/{WorkflowEditorPanel-Bhgbd7ue.js → WorkflowEditorPanel-CCauefUO.js} +1 -1
- package/dist/assets/{index-4TQCW-iQ.js → index-BG-fI4o6.js} +1 -1
- package/dist/assets/{index-Ju1wytuU.js → index-BakRazz4.js} +1 -1
- package/dist/assets/{index-Cj1pOXXE.js → index-Bl5TthnX.js} +3 -3
- package/dist/assets/{index-DBiqKSeA.js → index-Bma1pZ6y.js} +2 -2
- package/dist/assets/{index-C6YLfMEv.js → index-CPBP46l5.js} +1 -1
- package/dist/assets/{index-D7oUKfnS.js → index-DTO0LlmC.js} +1 -1
- package/dist/assets/{index-BeCd-RaV.js → index-DfBVuA_K.js} +1 -1
- package/dist/assets/{index-CkeDulVE.js → index-DtDea-zV.js} +1 -1
- package/dist/assets/{main-CYbmXUen.js → main-lr_lRuBZ.js} +13 -13
- package/dist/assets/{web-5JUKV_Qr.js → web-CV2dsnZj.js} +1 -1
- package/dist/assets/{web-B9TtN1N1.js → web-DOowQZI5.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/locales/de/config.json +2 -0
- package/dist/locales/en/config.json +2 -0
- package/dist/locales/es/config.json +2 -0
- package/dist/locales/fr/config.json +2 -0
- package/dist/locales/hi/config.json +2 -0
- package/dist/locales/it/config.json +2 -0
- package/dist/locales/ja/config.json +2 -0
- package/dist/locales/pt/config.json +2 -0
- package/dist/locales/ru/config.json +2 -0
- package/dist/locales/zh-CN/config.json +2 -0
- package/dist/src/packages/server/data/builtin-skills/create-building.js +91 -1
- package/dist/src/packages/server/data/index.js +24 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{bw as a}from"./main-
|
|
1
|
+
import{bw as a}from"./main-lr_lRuBZ.js";import{ImpactStyle as i,NotificationType as r}from"./index-Bma1pZ6y.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class u extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{u as HapticsWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{bw as s}from"./main-
|
|
1
|
+
import{bw as s}from"./main-lr_lRuBZ.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class f extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{f as LocalNotificationsWeb};
|
package/dist/index.html
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png" />
|
|
23
23
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
|
|
24
24
|
<title>Tide Commander</title>
|
|
25
|
-
<script type="module" crossorigin src="/assets/main-
|
|
25
|
+
<script type="module" crossorigin src="/assets/main-lr_lRuBZ.js"></script>
|
|
26
26
|
<link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-B5Qt9EMX.js">
|
|
27
27
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react--Eh9ivFN.js">
|
|
28
28
|
<link rel="modulepreload" crossorigin href="/assets/vendor-three-Chj50gSY.js">
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"stt": "Sprache-zu-Text",
|
|
30
30
|
"externalEditor": "Externer Editor",
|
|
31
31
|
"externalEditorPlaceholder": "z.B. subl, code, nvim (leer lassen für Systemstandard)",
|
|
32
|
+
"tabTitle": "Tab-Titel",
|
|
33
|
+
"tabTitlePlaceholder": "Benutzerdefinierter Browser-Tab-Titel (Standard: Tide Commander)",
|
|
32
34
|
"language": "Sprache",
|
|
33
35
|
"compactView": "Kompakte Ansicht",
|
|
34
36
|
"autoScroll": "Automatisches Scrollen",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"stt": "Speech-to-Text",
|
|
30
30
|
"externalEditor": "External Editor",
|
|
31
31
|
"externalEditorPlaceholder": "e.g., subl, code, nvim (leave empty for system default)",
|
|
32
|
+
"tabTitle": "Tab Title",
|
|
33
|
+
"tabTitlePlaceholder": "Custom browser tab title (default: Tide Commander)",
|
|
32
34
|
"language": "Language",
|
|
33
35
|
"compactView": "Compact View",
|
|
34
36
|
"autoScroll": "Auto Scroll",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"stt": "Voz a texto",
|
|
30
30
|
"externalEditor": "Editor externo",
|
|
31
31
|
"externalEditorPlaceholder": "ej., subl, code, nvim (dejar vacio para el predeterminado)",
|
|
32
|
+
"tabTitle": "Título de pestaña",
|
|
33
|
+
"tabTitlePlaceholder": "Título personalizado de la pestaña (predeterminado: Tide Commander)",
|
|
32
34
|
"language": "Idioma",
|
|
33
35
|
"compactView": "Vista compacta",
|
|
34
36
|
"autoScroll": "Desplazamiento automatico",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"stt": "Reconnaissance vocale",
|
|
30
30
|
"externalEditor": "Éditeur externe",
|
|
31
31
|
"externalEditorPlaceholder": "ex. : subl, code, nvim (laisser vide pour le défaut système)",
|
|
32
|
+
"tabTitle": "Titre de l'onglet",
|
|
33
|
+
"tabTitlePlaceholder": "Titre personnalisé de l'onglet (par défaut : Tide Commander)",
|
|
32
34
|
"language": "Langue",
|
|
33
35
|
"compactView": "Vue compacte",
|
|
34
36
|
"autoScroll": "Défilement automatique",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"stt": "स्पीच-टू-टेक्स्ट",
|
|
30
30
|
"externalEditor": "बाहरी संपादक",
|
|
31
31
|
"externalEditorPlaceholder": "जैसे, subl, code, nvim (सिस्टम डिफ़ॉल्ट के लिए खाली छोड़ें)",
|
|
32
|
+
"tabTitle": "टैब शीर्षक",
|
|
33
|
+
"tabTitlePlaceholder": "कस्टम ब्राउज़र टैब शीर्षक (डिफ़ॉल्ट: Tide Commander)",
|
|
32
34
|
"language": "भाषा",
|
|
33
35
|
"compactView": "कॉम्पैक्ट दृश्य",
|
|
34
36
|
"autoScroll": "ऑटो स्क्रॉल",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"stt": "Riconoscimento vocale",
|
|
30
30
|
"externalEditor": "Editor esterno",
|
|
31
31
|
"externalEditorPlaceholder": "es. subl, code, nvim (lascia vuoto per il predefinito di sistema)",
|
|
32
|
+
"tabTitle": "Titolo scheda",
|
|
33
|
+
"tabTitlePlaceholder": "Titolo personalizzato della scheda del browser (predefinito: Tide Commander)",
|
|
32
34
|
"language": "Lingua",
|
|
33
35
|
"compactView": "Vista compatta",
|
|
34
36
|
"autoScroll": "Scorrimento automatico",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"stt": "音声認識",
|
|
30
30
|
"externalEditor": "外部エディタ",
|
|
31
31
|
"externalEditorPlaceholder": "例: subl, code, nvim (空欄でシステムデフォルト)",
|
|
32
|
+
"tabTitle": "タブタイトル",
|
|
33
|
+
"tabTitlePlaceholder": "カスタムブラウザタブタイトル(デフォルト: Tide Commander)",
|
|
32
34
|
"language": "言語",
|
|
33
35
|
"compactView": "コンパクト表示",
|
|
34
36
|
"autoScroll": "自動スクロール",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"stt": "Fala para Texto",
|
|
30
30
|
"externalEditor": "Editor Externo",
|
|
31
31
|
"externalEditorPlaceholder": "ex., subl, code, nvim (deixe vazio para o padrao do sistema)",
|
|
32
|
+
"tabTitle": "Título da aba",
|
|
33
|
+
"tabTitlePlaceholder": "Título personalizado da aba do navegador (padrão: Tide Commander)",
|
|
32
34
|
"language": "Idioma",
|
|
33
35
|
"compactView": "Visualizacao Compacta",
|
|
34
36
|
"autoScroll": "Rolagem Automatica",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"stt": "Распознавание речи",
|
|
30
30
|
"externalEditor": "Внешний редактор",
|
|
31
31
|
"externalEditorPlaceholder": "напр., subl, code, nvim (оставьте пустым для системного по умолчанию)",
|
|
32
|
+
"tabTitle": "Заголовок вкладки",
|
|
33
|
+
"tabTitlePlaceholder": "Пользовательский заголовок вкладки браузера (по умолчанию: Tide Commander)",
|
|
32
34
|
"language": "Язык",
|
|
33
35
|
"compactView": "Компактный вид",
|
|
34
36
|
"autoScroll": "Автопрокрутка",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"stt": "语音转文字",
|
|
30
30
|
"externalEditor": "外部编辑器",
|
|
31
31
|
"externalEditorPlaceholder": "例如:subl, code, nvim(留空使用系统默认)",
|
|
32
|
+
"tabTitle": "标签页标题",
|
|
33
|
+
"tabTitlePlaceholder": "自定义浏览器标签页标题(默认:Tide Commander)",
|
|
32
34
|
"language": "语言",
|
|
33
35
|
"compactView": "紧凑视图",
|
|
34
36
|
"autoScroll": "自动滚动",
|
|
@@ -3,7 +3,7 @@ const BT3 = '```';
|
|
|
3
3
|
export const createBuilding = {
|
|
4
4
|
slug: 'create-building',
|
|
5
5
|
name: 'Create Building',
|
|
6
|
-
description: 'Create and manage buildings in Tide Commander with full control over configuration and placement',
|
|
6
|
+
description: 'Create and manage buildings and areas in Tide Commander with full control over configuration and placement',
|
|
7
7
|
allowedTools: ['Bash(jq:*)', 'Bash(curl:*)', 'Bash(cat:*)'],
|
|
8
8
|
content: `# Create Building Skill
|
|
9
9
|
|
|
@@ -575,5 +575,95 @@ ${BT3}
|
|
|
575
575
|
- Refresh Tide Commander UI to see new buildings
|
|
576
576
|
- Check "pm2 list" to verify building started correctly
|
|
577
577
|
- Position buildings inside their designated area (check areas.json for coordinates)
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## Area Management
|
|
582
|
+
|
|
583
|
+
Areas are project zones on the battlefield that group agents and buildings. File: ~/.local/share/tide-commander/areas.json
|
|
584
|
+
|
|
585
|
+
### Area Schema (DrawingArea)
|
|
586
|
+
|
|
587
|
+
**CRITICAL: Follow this schema exactly. Incorrect fields will crash the application.**
|
|
588
|
+
|
|
589
|
+
${BT3}typescript
|
|
590
|
+
{
|
|
591
|
+
"id": string, // Unique ID (e.g., "my-project-area")
|
|
592
|
+
"name": string, // Display name
|
|
593
|
+
"type": "rectangle" | "circle", // REQUIRED - shape type
|
|
594
|
+
"center": { "x": number, "z": number }, // REQUIRED - center position (NOT flat x/z)
|
|
595
|
+
"width": number, // Rectangle only - width in world units
|
|
596
|
+
"height": number, // Rectangle only - height in world units (NOT "depth")
|
|
597
|
+
"radius": number, // Circle only - radius in world units
|
|
598
|
+
"color": string, // Hex color (e.g., "#4a90d9")
|
|
599
|
+
"zIndex": number, // Stacking order (0 = bottom, higher = on top)
|
|
600
|
+
"assignedAgentIds": string[], // Agent IDs assigned to this area
|
|
601
|
+
"directories": string[], // Associated directory paths
|
|
602
|
+
"prompt": string // Optional - area-level system prompt for assigned agents
|
|
603
|
+
}
|
|
604
|
+
${BT3}
|
|
605
|
+
|
|
606
|
+
**Common mistakes to avoid:**
|
|
607
|
+
- Do NOT use flat ${BT}"x"${BT} and ${BT}"z"${BT} at root level — use ${BT}"center": {"x": ..., "z": ...}${BT}
|
|
608
|
+
- Do NOT use ${BT}"depth"${BT} — use ${BT}"height"${BT}
|
|
609
|
+
- Do NOT omit ${BT}"type"${BT} — always specify ${BT}"rectangle"${BT} or ${BT}"circle"${BT}
|
|
610
|
+
|
|
611
|
+
### Create a Rectangle Area
|
|
612
|
+
|
|
613
|
+
${BT3}bash
|
|
614
|
+
jq '.areas += [{
|
|
615
|
+
"id": "my-project-area",
|
|
616
|
+
"name": "My Project",
|
|
617
|
+
"type": "rectangle",
|
|
618
|
+
"center": {"x": 0, "z": 0},
|
|
619
|
+
"width": 12,
|
|
620
|
+
"height": 10,
|
|
621
|
+
"color": "#4a90d9",
|
|
622
|
+
"zIndex": 0,
|
|
623
|
+
"assignedAgentIds": [],
|
|
624
|
+
"directories": []
|
|
625
|
+
}]' ~/.local/share/tide-commander/areas.json > /tmp/a.json && mv /tmp/a.json ~/.local/share/tide-commander/areas.json
|
|
626
|
+
${BT3}
|
|
627
|
+
|
|
628
|
+
### Create a Circle Area
|
|
629
|
+
|
|
630
|
+
${BT3}bash
|
|
631
|
+
jq '.areas += [{
|
|
632
|
+
"id": "ops-area",
|
|
633
|
+
"name": "Operations",
|
|
634
|
+
"type": "circle",
|
|
635
|
+
"center": {"x": 10, "z": -5},
|
|
636
|
+
"radius": 6,
|
|
637
|
+
"color": "#d94a4a",
|
|
638
|
+
"zIndex": 0,
|
|
639
|
+
"assignedAgentIds": [],
|
|
640
|
+
"directories": []
|
|
641
|
+
}]' ~/.local/share/tide-commander/areas.json > /tmp/a.json && mv /tmp/a.json ~/.local/share/tide-commander/areas.json
|
|
642
|
+
${BT3}
|
|
643
|
+
|
|
644
|
+
### List Existing Areas
|
|
645
|
+
|
|
646
|
+
${BT3}bash
|
|
647
|
+
jq '.areas | map({id, name, type, center, width, height, radius})' ~/.local/share/tide-commander/areas.json
|
|
648
|
+
${BT3}
|
|
649
|
+
|
|
650
|
+
### Assign Agents to an Area
|
|
651
|
+
|
|
652
|
+
${BT3}bash
|
|
653
|
+
jq '(.areas[] | select(.id == "my-project-area")).assignedAgentIds += ["agent-id-here"]' ~/.local/share/tide-commander/areas.json > /tmp/a.json && mv /tmp/a.json ~/.local/share/tide-commander/areas.json
|
|
654
|
+
${BT3}
|
|
655
|
+
|
|
656
|
+
### Add Area-Level Prompt
|
|
657
|
+
|
|
658
|
+
${BT3}bash
|
|
659
|
+
jq '(.areas[] | select(.id == "my-project-area")).prompt = "Always run tests before committing"' ~/.local/share/tide-commander/areas.json > /tmp/a.json && mv /tmp/a.json ~/.local/share/tide-commander/areas.json
|
|
660
|
+
${BT3}
|
|
661
|
+
|
|
662
|
+
### Verify
|
|
663
|
+
|
|
664
|
+
${BT3}bash
|
|
665
|
+
jq empty ~/.local/share/tide-commander/areas.json && echo "Valid JSON"
|
|
666
|
+
jq '.areas | length' ~/.local/share/tide-commander/areas.json
|
|
667
|
+
${BT3}
|
|
578
668
|
`,
|
|
579
669
|
};
|
|
@@ -184,13 +184,36 @@ export async function saveAgentsAsync(agents) {
|
|
|
184
184
|
log.error(' Failed to save agents (async):', err);
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Validate that an area has the required fields to avoid crashes from malformed data.
|
|
189
|
+
*/
|
|
190
|
+
function isValidArea(area) {
|
|
191
|
+
return (area &&
|
|
192
|
+
typeof area.id === 'string' &&
|
|
193
|
+
typeof area.center === 'object' &&
|
|
194
|
+
area.center !== null &&
|
|
195
|
+
typeof area.center.x === 'number' &&
|
|
196
|
+
typeof area.center.z === 'number' &&
|
|
197
|
+
(area.type === 'rectangle' || area.type === 'circle'));
|
|
198
|
+
}
|
|
187
199
|
/**
|
|
188
200
|
* Load drawing areas from disk
|
|
189
201
|
*/
|
|
190
202
|
export function loadAreas() {
|
|
191
203
|
ensureDataDir();
|
|
192
204
|
const data = safeReadJsonSync(AREAS_FILE, 'Areas');
|
|
193
|
-
|
|
205
|
+
const raw = data?.areas || [];
|
|
206
|
+
const valid = [];
|
|
207
|
+
for (const area of raw) {
|
|
208
|
+
if (isValidArea(area)) {
|
|
209
|
+
valid.push(area);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
const a = area;
|
|
213
|
+
log.error(` Skipping malformed area "${a?.id ?? a?.name ?? 'unknown'}": missing required fields (center, type)`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return valid;
|
|
194
217
|
}
|
|
195
218
|
/**
|
|
196
219
|
* Save drawing areas to disk
|