Compare commits

...

592 Commits

Author SHA1 Message Date
Roman Sokolov
3d1d3727dc hikvision-doorbell: fixes (#1970)
* Let's try to fix the plugin freezing

* hikvision-doorbell version up after merging from main
2026-01-24 08:18:59 -08:00
Koushik Dutta
079878b663 core: allow PATH in terminal service 2026-01-21 16:21:15 -08:00
Koushik Dutta
0d02ea8f08 core: support cwd in terminalservice 2026-01-21 15:15:50 -08:00
Koushik Dutta
f23ad06eef snapshot: verify acls
Some checks failed
Build SDK / Build (push) Has been cancelled
2026-01-19 22:16:44 -08:00
Koushik Dutta
3c8b513c31 sdk: update 2026-01-19 21:34:46 -08:00
Koushik Dutta
35df17334c sdk: AccessControls 2026-01-19 21:21:12 -08:00
Koushik Dutta
2fff8b0044 predict: add segmentation models to onnx/coreml and refactor openvino 2026-01-18 13:58:28 -08:00
Koushik Dutta
f415e4f2e1 rebroadcast: publish beta with native rtmp support 2026-01-18 12:59:31 -08:00
Koushik Dutta
9607bcddcf core: publish 2026-01-17 12:24:48 -08:00
Koushik Dutta
1c7f16ed9f openvino: fix single segmentation shape crash 2026-01-17 12:24:44 -08:00
Koushik Dutta
961cb36a97 openvino: wip segmentation 2026-01-17 12:16:55 -08:00
Raman Gupta
a4d28791ed server: python rpc should use create_task instead of run_coroutine_threadsafe (#1953)
run_coroutine_threadsafe is designed for scheduling coroutines from a
different thread onto the event loop. Since readLoop is already running
as an async function on the event loop, using create_task is the correct
and more efficient approach.

This removes unnecessary thread-safe queue overhead for every RPC message.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 09:34:44 -08:00
Koushik Dutta
c1895df062 videoanalysis: fixup detection set 2026-01-12 15:10:36 -08:00
Koushik Dutta
bb902467eb videoanalysis: improve logging 2026-01-12 10:06:35 -08:00
Koushik Dutta
7202e99ab0 detect: publish betas 2026-01-10 15:30:07 -08:00
Koushik Dutta
38bac58fc6 openvino: new model, use huggingface as model source 2026-01-10 15:02:20 -08:00
Koushik Dutta
af8abb6072 rebroadcast: publish rtmp support beta 2026-01-09 12:45:48 -08:00
Koushik Dutta
7ef868e42d rebroadcast: rtmp window acks 2026-01-09 12:35:11 -08:00
Koushik Dutta
0185680791 rebroadcast: remove some bit shifting in favor of read/write uintbe 2026-01-08 21:35:04 -08:00
Koushik Dutta
1349bb7433 rebroadcast: slop rtmp implementation 2026-01-08 21:29:05 -08:00
Koushik Dutta
85074aaa7a detect: pubish betas 2026-01-08 09:37:00 -08:00
Koushik Dutta
beb7ec60ba detect: pubish betas 2026-01-08 09:13:42 -08:00
Koushik Dutta
126c96904b amcrest: publish 2026-01-08 08:31:57 -08:00
Koushik Dutta
70b7b4fa98 coreml: publish beta 2026-01-08 08:30:48 -08:00
Koushik Dutta
2cd73b5a6a openvino: new test model 2026-01-07 10:16:05 -08:00
Koushik Dutta
d6f13c7128 openvino: migrate to hugging face, remove old models. 2026-01-06 16:58:22 -08:00
Koushik Dutta
df1b389ef2 diagnostics: relax person detect for new models 2026-01-06 15:12:19 -08:00
Koushik Dutta
976204c439 coreml: change default model 2026-01-06 15:11:46 -08:00
Joey Stout
1adee0beb8 tuya: bump the tuya plugin and fix for devices (#1963)
* replace tool to use `ffmpeg` and bump v0.0.8

* format code

* wip

* wip: update components

* wip: remove websocket for cameras since they are not supported

* wip: allow changing between different login methods

It will prefer logging in with `Tuya (Smart Life) App` if there was no previous `userId`. Else, it will fall back to `Tuya Developer Account`.

* wip: fetch rtsp from Tuya Sharing SDK

* wip

* feat: add support for light accessory in camera

* fix: resolve indicator not updating

* wip: prevent setting motion if device has no motion detection

* improve mqtt reconnect, also update status

* bump version

* update commit

* bump to beta 3

* quick fix

* changelog

* fixchangelog

* bump version

* fix: resolve mqtt connection issues

* chore: bump version

* fix: use correct property for checking connection state

* chore: update changelog

* chore: bump version

* fix: ensure timeout is actually correct and bound corretly

* chore: update changelog

* bump version

* fix: fix setTimeout undefined function

* chore: update changelog

* fix: fix issue with camera not found

---------

Co-authored-by: ErrorErrorError <16653389+ErrorErrorError@users.noreply.github.com>
Co-authored-by: Erik Bautista Santibanez <erikbautista15@gmail.com>
2026-01-05 11:27:17 -08:00
radinsky
f5a10dd1cc wyze: add preset support (get/goto) and relevant webhook control (#1951)
* wyze: add preset support (get/goto) and relevant webhook control

* Update PTZ presets publishing

* Remove unnecessary ptzCapabilities emit

* removed unnecessary/debug leftovers

* Remove HttpRequestHandler and unused proprietry webhook gotopreset
2026-01-02 13:20:38 -08:00
Jackson Tomlinson
293a940771 amcrest: handle HTTP/1.0 responses in event listener (#1957)
Some Dahua/Amcrest NVRs (e.g., AMDV7208M) respond with HTTP/1.0 instead of
HTTP/1.1. The event listener was only checking for 'HTTP/1.1 200 OK',
causing it to throw 'expected boundary' errors and crash when receiving
HTTP/1.0 responses.

This fix adds support for both HTTP versions.

Fixes motion detection not working on older Dahua OEM NVRs.
2026-01-01 20:51:54 -08:00
Koushik Dutta
67728883cc core: publish oauth login fix 2025-12-31 12:15:35 -08:00
Koushik Dutta
5d02217a3e snapshot: make web hosted images bypass hotlink protection 2025-12-27 19:27:30 -08:00
Koushik Dutta
63a88e727a Merge branch 'main' of github.com:koush/scrypted 2025-12-27 19:07:57 -08:00
Koushik Dutta
1145caeb58 snapshot: make web hosted images bypass hotlink protection 2025-12-27 19:07:50 -08:00
apocaliss92
2cc7ab08fd reolink: add nvr support (#1947)
* work nvr

* Fix interfaces persisting

* Work

* Fix adopt imploding scrypted

* Skip undefined battery level values

* Preserve auth sessions on restart

* Move nvr creation in proper function

* Restore original createDevice with isNvr addition

* Typo

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-12-27 09:21:54 -08:00
The Beholder
bfb8c233f4 openvino: avoid CLIP startup timeout by loading HF cache first (#1949)
Scrypted could restart the OpenVINO plugin on startup in offline/firewalled setups because CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32") triggers HuggingFace Hub network checks/retries that exceed the plugin startup watchdog.
Update predict/clip.py to:
- Load the CLIP processor from the local HF cache first (local_files_only=True) so startup is fast/offline-safe.
- Refresh the processor cache online asynchronously in a background thread (asyncio.to_thread) so update checks don’t block startup.
- Add simple log prints to indicate cache load vs refresh success/failure.
2025-12-26 18:38:13 -08:00
Koushik Dutta
ebe6bcc58f client: add worker/fork support to web client 2025-12-16 12:22:03 -08:00
Koushik Dutta
3b0042c922 diagnostics: fix ai slop tests 2025-12-15 16:33:35 -08:00
Koushik Dutta
2f4cd9807b diagnostics: clip/det tests 2025-12-14 15:07:11 -08:00
Koushik Dutta
1711d2a6f7 diagnostics: clip/det tests 2025-12-14 15:04:01 -08:00
Koushik Dutta
2818120b68 diagnostics: url tests 2025-12-13 10:05:51 -08:00
Koushik Dutta
61cf589800 sdk: update tool calls to include id 2025-12-09 16:03:26 -08:00
Koushik Dutta
2c267f6b26 coreml: disable auto restart to work around coreml caching bug filling macos disk until reboot 2025-12-09 12:25:31 -08:00
Koushik Dutta
aa85e7ec19 rebroadcast: avoid mjpeg codecs and warn 2025-12-06 12:38:22 -08:00
Koushik Dutta
e585a48084 sdk: update 2025-12-04 19:35:36 -08:00
Koushik Dutta
465b4a80bb Merge branch 'main' of github.com:koush/scrypted 2025-12-02 08:54:22 -08:00
Koushik Dutta
1b7e24fda7 openvino: publish beta, add notes on 2025.4.0 2025-12-02 08:53:29 -08:00
René
8ec6c61784 docker: Update Watchtower image name in docker-compose.yml (#1937)
I guess Image name is wrong, at least the image which was mentioned here doesn’t exist.
2025-12-01 08:41:36 -08:00
Koushik Dutta
e1f9397ef9 docker: switch to nicholas-fedor/watchtower 2025-11-30 17:39:50 -08:00
Koushik Dutta
3e54db1658 reolink: publish 2025-11-27 18:24:35 -08:00
apocaliss92
a7cc8d0e11 reolink: Check deviceInfo exists (#1935)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-11-27 17:38:21 -08:00
Koushik Dutta
be4b772436 install: remove gstreamer 2025-11-27 12:54:25 -08:00
Koushik Dutta
5e0afa627c reolink: publish 2025-11-27 08:49:25 -08:00
apocaliss92
70c46f9894 - reolink: check and fix netData (#1934)
- reolink: restrict homehub streams to RTSP

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-11-27 08:42:35 -08:00
Koushik Dutta
fe94472282 Revert "Reolink: add check for net data, enable/disable RTMP/RTSP/ONVIF/HTTPS when necessary (#1931)"
This reverts commit 370a82dc56.
2025-11-27 08:19:19 -08:00
apocaliss92
c559212b2b reolink/hikvision: Add detection sources (#1932)
* add pluginId to detection objects

* add detection sourceId to hik

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-11-26 12:00:57 -08:00
Koushik Dutta
10b097480f reolink: publish 2025-11-26 08:42:51 -08:00
Koushik Dutta
14050d4e3a unifi-protect: fixup ws timeouts 2025-11-26 08:41:44 -08:00
apocaliss92
370a82dc56 Reolink: add check for net data, enable/disable RTMP/RTSP/ONVIF/HTTPS when necessary (#1931)
- unify methods to get specific abilities
- allow only RTSP streams for homehub devices

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-11-26 07:29:32 -08:00
Koushik Dutta
77dd8cf2a8 reolink: fix dep 2025-11-25 16:13:56 -08:00
Koushik Dutta
2b2a5c3dd8 diagnostics: fix metadata retrieval failrue 2025-11-25 09:16:36 -08:00
Koushik Dutta
6a952bf104 diagnostics: fix metadata retrieval failrue 2025-11-25 08:45:56 -08:00
Koushik Dutta
72c7736b2a snapshot: aspect ratio description 2025-11-17 15:19:58 -08:00
Koushik Dutta
c6771ce8ae snapshot: publish with aspect ratio override fixes 2025-11-17 15:16:57 -08:00
Koushik Dutta
e691c71224 snapshot: publish beta with aspect ratio override 2025-11-17 14:57:37 -08:00
Koushik Dutta
d22183faa7 Merge branch 'main' of github.com:koush/scrypted 2025-11-17 11:06:18 -08:00
Koushik Dutta
12ce2dc6ce core: publish new lxc updater 2025-11-17 11:06:13 -08:00
Koushik Dutta
b4b17d420e windows: Upgrade node.js to version 22.21.0 2025-11-17 08:51:02 -08:00
Koushik Dutta
b69dd024e5 core: use new builtin docker image updater 2025-11-16 20:48:34 -08:00
Koushik Dutta
b43fdf83e2 openvino: legacy gpu crash fix for text recognition 2025-11-16 18:13:12 -08:00
Koushik Dutta
c4a12fe493 unifi-protect: publishb eta 2025-11-16 11:42:27 -08:00
Koushik Dutta
3c8a3132e5 Merge branch 'main' of github.com:koush/scrypted 2025-11-16 11:33:45 -08:00
Koushik Dutta
ef65a413e7 server: fix EventEmitter import 2025-11-16 11:33:40 -08:00
Koushik Dutta
7219c8bee3 hikvision: ensure device probe has data 2025-11-16 09:06:41 -08:00
Koushik Dutta
86160a74ac openvino: note vgg failure on latest openivno 2025-11-15 18:24:43 -08:00
Koushik Dutta
0dc7aec5c9 docker: update openvino legacy packages 2025-11-15 18:10:00 -08:00
Koushik Dutta
ec6ccb5826 cloud: code cleanup and alert clearing 2025-11-13 15:23:26 -08:00
Koushik Dutta
ef55c3f366 cloud: add periodic cloudflare health check 2025-11-13 11:16:05 -08:00
Koushik Dutta
923dff378c snapshot/rebroadcast: publish new privacy mode 2025-11-13 09:43:18 -08:00
Koushik Dutta
6356702ba3 rebroadcast: privacy mode 2025-11-13 09:40:28 -08:00
Koushik Dutta
a2576d5741 webrtc: fix https://github.com/koush/scrypted/issues/1909 2025-11-09 11:02:06 -08:00
Koushik Dutta
6e5782d734 common: fix microphone nre in BrowserSignalingSession 2025-11-09 10:52:19 -08:00
Koushik Dutta
7583d072cc sdk: add Buttons type 2025-11-09 10:05:40 -08:00
Koushik Dutta
34f0529691 videoanalysis: prefer libav for stability 2025-11-09 08:33:18 -08:00
Koushik Dutta
4ad594074a server: remove python cluster mode port logging 2025-11-09 08:23:06 -08:00
Koushik Dutta
8dba09e047 beta 2025-11-07 09:15:58 -08:00
Koushik Dutta
56b4a04e56 postbeta 2025-11-07 09:15:58 -08:00
Koushik Dutta
90f546c422 docker: fixup intel dockerfile 2025-11-07 08:40:47 -08:00
Koushik Dutta
ace1c74ec2 server: prevent invalid media converter from crashing all conversions 2025-11-07 08:08:16 -08:00
Koushik Dutta
99c0c53405 core/sdk: fix missing interface acl crash 2025-11-06 10:30:03 -08:00
Koushik Dutta
55fb215cab core/sdk: fix missing interface acl crash 2025-11-06 10:29:14 -08:00
Koushik Dutta
d8e17e9216 core: remove watchtower from proxmox totally 2025-11-05 10:32:25 -08:00
Koushik Dutta
618a33028b proxmox: install v0.143.0 2025-11-05 10:29:33 -08:00
Koushik Dutta
536d8f03ae proxmox: add install override 2025-11-05 09:33:20 -08:00
Koushik Dutta
6e5c73b48c proxmox: lxc setup fixes 2025-11-05 09:17:49 -08:00
Koushik Dutta
94c4b663f6 proxmox: lxc setup fixes 2025-11-05 09:15:32 -08:00
Koushik Dutta
c95cca0f81 proxmox: remove watchtower 2025-11-05 09:04:23 -08:00
Koushik Dutta
d515cc47d0 core: temporarily disable lxc-docker abort-on-container-exit 2025-11-05 08:52:26 -08:00
Koushik Dutta
12e60efd35 core: prevent apt updates 2025-11-05 08:02:19 -08:00
Koushik Dutta
9107558bab diagnostics: cloud ipv4 and ipv6 check 2025-11-03 14:38:08 -08:00
Koushik Dutta
a8bb431efb install: use last working release for nvidia-legacy 2025-10-31 10:15:37 -07:00
Koushik Dutta
22ffac1170 docker: fix nvidia legacy to use specific cudnn 2025-10-31 09:05:47 -07:00
Koushik Dutta
2f45e72bd3 client: add dev hook 2025-10-30 09:43:00 -07:00
Koushik Dutta
5749a522db docker: move amd opencl into amd image only 2025-10-30 08:31:11 -07:00
Koushik Dutta
38037d31b3 install: add nvidia legacy 2025-10-29 21:08:42 -07:00
Koushik Dutta
dd6e5cf854 postbeta 2025-10-29 20:47:42 -07:00
Koushik Dutta
f9b8715cc0 install: add nvidia legacy 2025-10-29 20:47:24 -07:00
Koushik Dutta
3186480f44 werift: update 2025-10-29 11:36:04 -07:00
Koushik Dutta
25521699e8 webrtc: update werift and publish beta 2025-10-29 11:03:12 -07:00
Koushik Dutta
b87906911c sdk: rollup terser support 2025-10-29 10:53:27 -07:00
Koushik Dutta
55e67c9eda sdk: update deps 2025-10-29 09:33:27 -07:00
Koushik Dutta
54c56ac4ce core: add platform images 2025-10-28 11:45:15 -07:00
Koushik Dutta
547db5bbbd install: update ha 2025-10-28 11:11:26 -07:00
Koushik Dutta
5b789b35ec postrelease 2025-10-28 10:13:05 -07:00
Koushik Dutta
bde3dfb9a8 server: verup 2025-10-28 10:12:56 -07:00
Koushik Dutta
d751ac8871 postbeta 2025-10-23 07:57:30 -07:00
Roman Sokolov
d6afbcef26 hikvision-doorbell: Added signaling to listenLoop and updated README.md (#1911) 2025-10-22 08:07:28 -07:00
Koushik Dutta
457fbc594e client: improve base url detection 2025-10-21 23:34:45 -07:00
Koushik Dutta
aadb190c13 client: dont sent query token to connectRPCObject if accessing without a CORS request. 2025-10-21 10:34:29 -07:00
Koushik Dutta
f9a1668e5d core: publish lxc docker image fix 2025-10-13 08:56:58 -07:00
Koushik Dutta
70672e2a87 Merge branch 'main' of github.com:koush/scrypted 2025-10-12 21:28:04 -07:00
Koushik Dutta
cab0afaa53 proxmox: reimplement image cleanup 2025-10-12 21:28:00 -07:00
Roman Sokolov
e0764a54cc hikvision-doorbell: Version 2 of the hikvision-doorbell plugin (#1907) 2025-10-12 09:28:53 -07:00
Koushik Dutta
1e825b84bc diagnostics: use cloudflare to check date. check scrypted services 2025-10-08 08:32:42 -07:00
Koushik Dutta
946e8d3414 openvino: rollback pypi package 2025-10-07 19:29:04 -07:00
Koushik Dutta
3043b058d7 openvino: publish with new openvino, add m model 2025-10-07 09:51:29 -07:00
Koushik Dutta
65fa8dd7f9 unifi-protect: Fix 2 way 2025-10-03 08:54:13 -07:00
Koushik Dutta
c6a93cf245 sdk/client: update 2025-10-03 08:36:14 -07:00
Koushik Dutta
911b3f6014 sdk/client: update 2025-10-03 08:03:47 -07:00
Koushik Dutta
8b5d3eaeae docker: remove apt-key usage 2025-10-02 22:04:21 -07:00
Koushik Dutta
8099df4a2a rebroadcast: prevent buffering from buggy RTSP clients like frigate from causing memory leaks in scrypted 2025-10-01 09:16:30 -07:00
Koushik Dutta
e703efc1aa core: fix missing module types 2025-09-28 16:18:14 -07:00
Koushik Dutta
e9dc5a4254 server: package-lock.json 2025-09-28 12:15:23 -07:00
Koushik Dutta
5ae0bb10ff postbeta 2025-09-28 12:15:18 -07:00
Koushik Dutta
da417f3d5c server: @scrypted/node-pty update 2025-09-28 12:15:08 -07:00
Koushik Dutta
b00fd7e684 docker: split out amd flavor for future rocm 2025-09-28 11:19:46 -07:00
Koushik Dutta
d5ce4e24c4 docker: split out amd flavor for future rocm 2025-09-28 11:18:55 -07:00
Koushik Dutta
1e09b62795 install: update amd links 2025-09-28 11:12:53 -07:00
Koushik Dutta
dd256e7a39 install: update amd links 2025-09-28 11:11:55 -07:00
Koushik Dutta
f6457bf475 install: update amd links 2025-09-28 10:59:43 -07:00
Koushik Dutta
5008220c26 install: update amd links 2025-09-28 10:58:03 -07:00
Koushik Dutta
8504319b27 install: update amd links 2025-09-28 10:45:19 -07:00
Koushik Dutta
61cc544313 install: update intel compute runtime 2025-09-28 10:34:24 -07:00
Koushik Dutta
4e6066a7c9 videoanalysis: make zone config less weird 2025-09-27 08:52:00 -07:00
Koushik Dutta
22b790c7f5 Update install-scrypted-docker-compose.sh 2025-09-26 09:12:31 -07:00
Koushik Dutta
65ab977d4f install/docker: switch to global dns 2025-09-25 12:34:36 -07:00
Koushik Dutta
6e1f5cbfa7 install/docker: switch to global dns 2025-09-25 12:28:29 -07:00
Koushik Dutta
4d7be52b98 install/docker: switch to global dns 2025-09-25 12:19:23 -07:00
Koushik Dutta
0b0a43fefc unifi-protect: switch to discovery mode, allow device reassociation in case ids flap 2025-09-24 21:02:51 -07:00
Koushik Dutta
1449debbd3 unifi-protect: switch to discovery mode, allow device reassociation in case ids flap 2025-09-24 20:45:25 -07:00
Koushik Dutta
4e24e44246 unifi-protect: switch to discovery mode, allow device reassociation in case ids flap 2025-09-24 12:57:56 -07:00
Koushik Dutta
e4d62668b7 server/rpc: fixup rpc serializer buffer serialization 2025-09-23 22:45:44 -07:00
Koushik Dutta
a4a3731b94 rebroadcast: always use scrypted rtsp parser for consistency 2025-09-23 20:01:29 -07:00
Koushik Dutta
f4a55ee76b rebroadcast: always use scrypted rtsp parser for consistency 2025-09-23 12:47:16 -07:00
Koushik Dutta
29714f82d5 client: include query headers in connectRPCObject 2025-09-22 22:43:49 -07:00
Koushik Dutta
c2054fc7e0 client: fix connectRPCObject always using scrypted cloud 2025-09-22 22:36:49 -07:00
Koushik Dutta
50b312b290 client: fix connectRPCObject always using scrypted cloud 2025-09-22 22:20:53 -07:00
Koushik Dutta
db8a3ec40b docker: auto detect devices 2025-09-22 20:10:43 -07:00
Koushik Dutta
ef07691eef docker: auto detect devices 2025-09-22 20:08:00 -07:00
Koushik Dutta
8f1c5fdf3c snapshot/sdk: add resolution property 2025-09-19 11:41:48 -07:00
Koushik Dutta
f7ac2883ec core: fix bug where changing password screws up user provider native id 2025-09-19 09:28:03 -07:00
Koushik Dutta
1a87e0daa1 hikvision: publish 2025-09-19 08:31:38 -07:00
Koushik Dutta
2e17e58060 client: cluster peer connect cleanups 2025-09-18 11:17:20 -07:00
Koushik Dutta
c9b88e6d8f client: implement connectRPCObject timeouts, fix typo 2025-09-17 09:00:49 -07:00
Koushik Dutta
eaa6da005b sdk/client: add optional dedicated connections and lifetime to connectRPCObject 2025-09-17 08:31:44 -07:00
Koushik Dutta
ca855bb9a6 client: fix http connection type reporting 2025-09-06 08:41:24 -07:00
Koushik Dutta
774f987a66 common: using-holder 2025-09-06 07:59:02 -07:00
Koushik Dutta
0b6ef28ae8 router: use go tar 2025-09-05 11:57:27 -07:00
Koushik Dutta
677e78e328 openvino: update deps to latest, publish beta 2025-09-05 10:03:28 -07:00
Koushik Dutta
0f1f1c56fb install: intel npu script fix 2025-09-05 09:13:38 -07:00
Koushik Dutta
27e7e4c9e2 install: intel npu script fix 2025-09-05 09:08:05 -07:00
Koushik Dutta
b6b193bf80 install: update intel npu driver 2025-09-05 08:43:41 -07:00
Koushik Dutta
25f52eb528 install: update intel npu driver check for proxmox 2025-09-05 08:41:59 -07:00
Koushik Dutta
69d110b234 install: update intel compute runtime 2025-09-05 08:36:38 -07:00
Koushik Dutta
7240f328b3 client: update ScryptedClientConnectionType 2025-09-03 12:11:00 -07:00
Koushik Dutta
7bf133745b werift: update 2025-09-03 08:48:45 -07:00
Koushik Dutta
77ba56cf38 webrtc: werift fixes + object leaks 2025-09-02 21:48:53 -07:00
Koushik Dutta
ea6d404f12 webrtc: fix typing and variable scope 2025-09-02 12:03:42 -07:00
Koushik Dutta
40a1221f11 werift: update 2025-09-01 12:38:42 -07:00
Koushik Dutta
22444eb63d server/webrtc: restructure 2025-08-31 21:51:15 -07:00
Koushik Dutta
2a6f542e06 sdk: update 2025-08-31 21:26:17 -07:00
Koushik Dutta
ec49e4630f webrtc: update werift, datachannel connectRPCObject, publish 2025-08-31 21:14:42 -07:00
Koushik Dutta
9de2b480ff webrtc: wip connectRPCObject 2025-08-28 11:31:37 -07:00
Koushik Dutta
442e8d53f7 server: package-lock 2025-08-28 09:45:57 -07:00
Koushik Dutta
f718d435bd homekit: update tsconfig 2025-08-28 09:44:48 -07:00
Koushik Dutta
8bbd112f60 webrtc: wip datachannels 2025-08-28 09:44:41 -07:00
Koushik Dutta
6c98fa62be client: rpc exports 2025-08-28 08:55:49 -07:00
Koushik Dutta
2e56a7f7a9 homekit: update deps 2025-08-28 08:40:21 -07:00
Koushik Dutta
8304c1d065 postbeta 2025-08-28 08:32:22 -07:00
Koushik Dutta
21d0ca99e6 Merge branch 'main' of github.com:koush/scrypted 2025-08-27 09:20:56 -07:00
Koushik Dutta
fa14f4ca83 webrtc: fix intercom detection regression 2025-08-27 09:20:52 -07:00
Koushik Dutta
8ae0a33cbe werift: update 2025-08-26 16:44:19 -07:00
Koushik Dutta
dea55e4fcd server: fix bug in connectRPCObject 2025-08-26 16:30:01 -07:00
Koushik Dutta
9eab88572e postbeta 2025-08-26 09:51:54 -07:00
Koushik Dutta
427c3e2f7b Merge branch 'main' of github.com:koush/scrypted 2025-08-26 09:51:42 -07:00
Koushik Dutta
98f97a51e8 postbeta 2025-08-26 09:51:32 -07:00
Koushik Dutta
529b4d30fb postbeta 2025-08-26 09:48:40 -07:00
Brett Jia
eaabd02bfe server: bind single address if cluster address is 127.0.0.1 (python) (#1877)
A continuation of https://github.com/koush/scrypted/pull/1820 for the missing Python half.
2025-08-26 09:23:32 -07:00
Koushik Dutta
7a67c70ef7 webrtc: wip transmission window updates 2025-08-25 16:48:04 -07:00
Koushik Dutta
b784995ebb webrtc: wip transmission window updates 2025-08-25 16:33:25 -07:00
Koushik Dutta
d4da11bb2c webrtc: wip data channel generator 2025-08-25 12:03:30 -07:00
Koushik Dutta
f556ae7ff3 webrtc/sdk: initial lossless datachannel api 2025-08-25 10:02:39 -07:00
Koushik Dutta
8bb999aa64 webrtc: clean up intercom setup 2025-08-25 09:12:43 -07:00
Koushik Dutta
de99d59162 server: package lock 2025-08-02 11:41:05 -07:00
Koushik Dutta
438a6d7fe9 postbeta 2025-08-02 11:41:02 -07:00
Koushik Dutta
b9b3a48a08 server: improve plugin connection errors 2025-08-02 11:40:53 -07:00
Koushik Dutta
5c42740ab1 server: package lock 2025-08-02 11:39:51 -07:00
Koushik Dutta
e988e5fb96 postbeta 2025-08-02 11:39:48 -07:00
Koushik Dutta
9c8cbc750a server: improve plugin connection errors 2025-08-02 11:39:36 -07:00
Koushik Dutta
01e15fb070 server: package lock 2025-08-02 11:17:22 -07:00
Koushik Dutta
7aa02d6e4a postbeta 2025-08-02 11:17:08 -07:00
Koushik Dutta
9c9be9db22 server: improve plugin connection errors 2025-08-02 11:16:59 -07:00
Koushik Dutta
cc78c072ce rpc: support pickling 2025-07-31 20:54:48 -07:00
Koushik Dutta
48c489b898 rpc: support dataclasses annotation, fix formatting 2025-07-31 11:05:55 -07:00
Koushik Dutta
2cbcc05428 server: formatting 2025-07-31 11:04:16 -07:00
Koushik Dutta
58a722cfa8 ha: bump version 2025-07-31 11:03:50 -07:00
Koushik Dutta
2c16c4625e doorbird: publish 2025-07-31 11:03:24 -07:00
Nils Sowen
60613ee947 doorbird: fixing hangups in HomeKit and broken audio transmission; adding audio filter options (#1858)
* doorbird: working example with large buffer

* doorbird: improving documentation; improving audio delays; fixed audio transmission

* doorbird: add audio filtering options for more speech clarity

* doorbird: reverting accidental deletion

* doorbird: reverting unwanted change

* doorbird: reverted unwanted changes

* doorbird: reverted unwanted changes

* doorbird: fixed non-working echo cancellation; included Copilot comments

* doorbird: remove throttling as it is not really needed

* doorbird: remove throttling as it is not really needed
2025-07-29 09:16:27 -07:00
Koushik Dutta
f69dd06513 Merge branch 'main' of github.com:koush/scrypted 2025-07-28 11:09:33 -07:00
Koushik Dutta
d011419208 diagnostics: validate system time 2025-07-28 11:09:27 -07:00
Koushik Dutta
789be6bd57 install: nvidia lxc/docker notes 2025-07-27 11:57:39 -07:00
Koushik Dutta
45e1b7091e install: more nvidia on proxmox fixes 2025-07-27 11:33:19 -07:00
Koushik Dutta
f2ab923c79 install: Update install-nvidia-container-toolkit.sh 2025-07-27 11:04:49 -07:00
apocaliss92
4c3d5133f6 sdk: sourceId on detection
* Add utility attributes to get camera data

* Remove NativebjectDetector

* Remove zones

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-07-26 14:15:33 -07:00
Koushik Dutta
d2edfc5ecc core: publish 2025-07-25 11:53:58 -07:00
Koushik Dutta
4c5ae94c7c github: fix docker action 2025-07-25 11:40:35 -07:00
Koushik Dutta
f30efbecec server: verup 2025-07-25 11:04:53 -07:00
Koushik Dutta
4ecb1f3c85 postbeta 2025-07-25 11:04:34 -07:00
Koushik Dutta
3ca0234530 postrelease 2025-07-25 11:04:27 -07:00
Koushik Dutta
b784399afa server: verup 2025-07-25 11:04:16 -07:00
Koushik Dutta
0f16568edb tapo: fix broken plugin on windows 2025-07-22 11:11:16 -07:00
Koushik Dutta
7ecee115a6 sdk: remove object tracker 2025-07-20 10:14:16 -07:00
Koushik Dutta
34eb2be551 sdk: use mcp for tool call 2025-07-16 10:18:13 -07:00
Koushik Dutta
27ff0c8c80 Merge branch 'main' of github.com:koush/scrypted 2025-07-16 08:45:06 -07:00
Koushik Dutta
51c5df6802 core: build fix 2025-07-16 08:45:01 -07:00
Koushik Dutta
328bd78771 docker: fix grep error 2025-07-15 11:47:02 -07:00
Koushik Dutta
3d2ae6384f sdk: add support for custom interface descriptors 2025-07-13 13:32:53 -07:00
Koushik Dutta
e1ba16f708 openvino: use explicit shape for CRAFT 2025-07-13 13:10:12 -07:00
Koushik Dutta
6f47e39bf3 sdk: add level to externals, support rollup externals 2025-07-13 08:01:22 -07:00
Koushik Dutta
e38c3c975f server: dead code 2025-07-13 07:57:18 -07:00
Koushik Dutta
9c75b074b5 sdk: chat completion capabilties 2025-07-11 21:30:23 -07:00
Koushik Dutta
299d926eae install: add nvidia to install script 2025-07-10 19:53:13 -07:00
Koushik Dutta
22d0ce4f82 install: add nvidia to install script 2025-07-10 19:45:27 -07:00
Koushik Dutta
53c2b7cb58 postbeta 2025-07-10 08:52:43 -07:00
Koushik Dutta
86548f6fa4 server: add plugin node_volumes to path 2025-07-10 08:52:31 -07:00
Koushik Dutta
0e1e641f8f intel: fix oneapi path 2025-07-07 13:57:59 -07:00
Koushik Dutta
58e0a748c4 intel: fix oneapi path 2025-07-07 12:49:01 -07:00
Koushik Dutta
b4a58df53a intel: fix oneapi path 2025-07-07 10:40:33 -07:00
Koushik Dutta
b83b7ff559 intel: fix missing gpg 2025-07-07 10:37:15 -07:00
Koushik Dutta
de2173567e onnx: bump deps 2025-07-07 09:39:34 -07:00
Koushik Dutta
9c931b21dc ncnn: update 2025-07-07 09:18:57 -07:00
Koushik Dutta
5291afad6a install: update nvidia 2025-07-07 08:29:13 -07:00
Koushik Dutta
e1ac1ace87 install: update intel libs 2025-07-07 08:25:42 -07:00
Koushik Dutta
1f6f1a82aa Merge branch 'main' of github.com:koush/scrypted 2025-07-07 08:06:04 -07:00
Koushik Dutta
70af66a875 router: add cron 2025-07-07 08:05:55 -07:00
Koushik Dutta
b7bab5b2e2 vscode-typescript 2025-07-06 13:45:24 -07:00
Koushik Dutta
5d5686a9e7 common: util functions 2025-07-06 11:26:13 -07:00
Koushik Dutta
1eb5012e9b sdk: alternate streamChatCompletion signature 2025-07-05 09:28:02 -07:00
Koushik Dutta
3574e72e4f sdk: publish 2025-07-05 07:30:04 -07:00
Koushik Dutta
b7ff4dfd5e sdk: alternate streamChatCompletion signature 2025-07-05 07:29:32 -07:00
Koushik Dutta
e0ed953963 sdk: publish 2025-07-05 07:18:27 -07:00
Koushik Dutta
930690a4ba sdk: alternate streamChatCompletion signature 2025-07-05 07:16:33 -07:00
Koushik Dutta
1aa4d45caa sdk: update 2025-07-03 23:45:36 -07:00
Koushik Dutta
28fb2b0853 packages/deferred: publish 2025-07-03 23:02:48 -07:00
Koushik Dutta
4fae4fba3b sdk: update 2025-07-03 20:05:26 -07:00
Vitor Furlanetti
b72c8f59eb server: Fallback pip to latin (#1841) 2025-07-02 21:34:29 -07:00
Koushik Dutta
369ad59324 amcrest/http: fix http authentication when it includes query parameters 2025-07-02 09:05:24 -07:00
Koushik Dutta
51ac5a1042 core: fix first run missing users 2025-06-24 10:16:03 -07:00
Koushik Dutta
200c107e97 reolink: fix vs caching 2025-06-18 14:01:16 -07:00
Koushik Dutta
35139abe30 openvino: note int8 2025-06-18 09:39:47 -07:00
Koushik Dutta
dc7f305687 predict: publish clip 2025-06-17 20:40:45 -07:00
Koushik Dutta
2a479dd38a onnx: clip 2025-06-17 10:55:21 -07:00
Koushik Dutta
d32f9bb07a coreml: clip 2025-06-17 10:33:38 -07:00
Koushik Dutta
a33bed0b44 openvino: clip threads 2025-06-17 10:25:34 -07:00
Koushik Dutta
f9847f6f72 predict: wip clip 2025-06-17 10:14:11 -07:00
Koushik Dutta
add53d07f3 core: publish ui 2025-06-17 09:39:54 -07:00
Koushik Dutta
db21159299 sdk: fix broken package lock 2025-06-17 09:36:03 -07:00
Koushik Dutta
6fa7f06852 postbeta 2025-06-17 09:22:19 -07:00
Koushik Dutta
58387e5046 postbeta 2025-06-17 09:15:00 -07:00
Koushik Dutta
1589908698 sdk: fix python Buffer mapping 2025-06-17 09:11:25 -07:00
Koushik Dutta
d0183c29a8 sdk: add support for text embeddings 2025-06-17 09:07:35 -07:00
Koushik Dutta
99dcdd12cf postbeta 2025-06-16 08:41:56 -07:00
Koushik Dutta
b1861e4630 server: update deps 2025-06-16 08:41:47 -07:00
Koushik Dutta
193bfce979 core: publish 2025-06-14 19:56:03 -07:00
Koushik Dutta
5b7cc826a6 sdk/client: fix build issues 2025-06-14 19:54:05 -07:00
Koushik Dutta
8484d75e82 core: publish 2025-06-14 18:57:43 -07:00
Koushik Dutta
e8fef925bb ring: fix startup crash due to server changes 2025-06-14 16:01:11 -07:00
Koushik Dutta
fa200e1bbf sdk: update 2025-06-14 15:35:27 -07:00
Koushik Dutta
df0991b882 Merge branch 'main' of github.com:koush/scrypted 2025-06-14 13:30:03 -07:00
Koushik Dutta
93ff686000 sdk: add openai api for types 2025-06-14 13:29:58 -07:00
gtfrog
6ae9a5618d amcrest: fix NaN resolution values due to newline/cr, and add support for PAL named resolutions (#1833) 2025-06-14 10:35:43 -07:00
Koushik Dutta
c882b9a04e sdk: publish 2025-06-13 11:17:02 -07:00
Koushik Dutta
af4269be49 docker: include killall 2025-06-13 10:39:29 -07:00
Koushik Dutta
61ad99a3f6 docker: update flavors 2025-06-12 22:28:35 -07:00
Koushik Dutta
d71bbf1824 docker: better tags 2025-06-12 22:17:26 -07:00
Koushik Dutta
74674dab00 docker: lint 2025-06-12 21:35:58 -07:00
Koushik Dutta
247f860a23 intel: fix curl/gpg interaction maybe 2025-06-12 21:21:57 -07:00
Koushik Dutta
a801fe1f4e intel: fix curl/gpg interaction maybe 2025-06-12 21:18:28 -07:00
Koushik Dutta
6744851256 intel: add logging 2025-06-12 21:13:13 -07:00
Koushik Dutta
10569731aa intel: fix curl usage 2025-06-12 21:08:29 -07:00
Koushik Dutta
4965b1f99a intel: bump npu 2025-06-12 20:44:32 -07:00
Koushik Dutta
510250c60b intel: bump npu 2025-06-12 20:44:12 -07:00
Koushik Dutta
8e33775b0e docker: fix builds 2025-06-12 20:34:51 -07:00
Koushik Dutta
1077bd1f56 docker: add intel builds 2025-06-12 20:23:04 -07:00
Koushik Dutta
a485d8ae69 install: prep intel llm deps 2025-06-12 20:20:15 -07:00
Koushik Dutta
17f42762e7 install: prep intel llm deps 2025-06-12 20:08:08 -07:00
Koushik Dutta
49943a5408 postbeta 2025-06-09 12:09:39 -07:00
Koushik Dutta
585c638220 server: keepalive needs an explicit non-default duration. 2025-06-09 12:09:26 -07:00
Koushik Dutta
6767892c63 unifi-protect: fix login failures 2025-06-04 08:30:56 -07:00
Koushik Dutta
289555c03e unifi-protect: update api 2025-06-03 20:52:51 -07:00
Koushik Dutta
a563e17c56 core: publish ui 2025-05-31 09:02:33 -07:00
Koushik Dutta
54c317b217 detect: fix custom classifier filtering 2025-05-29 07:56:35 -07:00
Koushik Dutta
0df9c31480 core: publish ui 2025-05-28 12:05:47 -07:00
Koushik Dutta
19c8436256 Merge branch 'main' of github.com:koush/scrypted 2025-05-28 11:59:05 -07:00
Koushik Dutta
b73526674a detect: add custom classifier filtering 2025-05-28 11:59:00 -07:00
LV Nilesh
fd863f4ba3 Update Dockerfile.full (#1818) 2025-05-26 19:41:59 -07:00
LV Nilesh
634b65c216 Update Dockerfile.lite (#1817) 2025-05-26 19:41:51 -07:00
Brett Jia
548086403b server: bind single address if cluster address is 127.0.0.1 (#1820) 2025-05-26 19:41:17 -07:00
LV Nilesh
867432cd82 docker: Update Dockerfile to noble (#1813) 2025-05-24 21:25:54 -07:00
Koushik Dutta
b3cc914772 Merge branch 'main' of github.com:koush/scrypted 2025-05-23 10:01:39 -07:00
Koushik Dutta
b297a4d3d6 webrtc: fix possible crash if no video stream is negotiated 2025-05-23 10:01:32 -07:00
Mehmet Bayram
8144588bcf hikvision: improve supplemental light mode handling (#1812) 2025-05-22 20:13:37 -07:00
Koushik Dutta
f3265f5fb6 detect: cluster fixes 2025-05-21 12:33:04 -07:00
Koushik Dutta
f686812f01 core: update ui 2025-05-21 12:32:52 -07:00
Koushik Dutta
552787e06b detect: custom model support 2025-05-20 22:01:26 -07:00
Koushik Dutta
3c4de5af39 core: publish ui update 2025-05-20 21:12:49 -07:00
Koushik Dutta
e08df29373 ncnn: fp16 math 2025-05-20 10:34:42 -07:00
Koushik Dutta
1efb624681 proxmox: bump to 139 2025-05-13 08:04:48 -07:00
apocaliss92
09afc6c96c reolink: Fix events stopping for NVRs (#1804)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-05-09 20:38:17 -07:00
Koushik Dutta
666d2903e4 install: remove intel debug symbols 2025-05-08 18:31:42 -07:00
Koushik Dutta
24eb60bce1 install: bump ha 2025-05-08 13:21:32 -07:00
Koushik Dutta
d1951687be postbeta 2025-05-08 07:06:10 -07:00
Koushik Dutta
3c3c2c1610 core: patch lxc updater 2025-05-07 16:20:54 -07:00
Koushik Dutta
0f9106c639 postrelease 2025-05-07 10:18:33 -07:00
Koushik Dutta
ab00ade016 install: bump node 2025-05-07 09:32:35 -07:00
Koushik Dutta
6cfc3db05c server: fix package lock 2025-04-29 11:43:02 -07:00
Koushik Dutta
95aa58ce38 postbeta 2025-04-28 20:30:02 -07:00
Koushik Dutta
0d88b4746b ncnn: publish face/text 2025-04-28 12:54:15 -07:00
Koushik Dutta
8c4beeb3a0 Merge branch 'main' of github.com:koush/scrypted 2025-04-28 12:09:32 -07:00
Koushik Dutta
4846cfaddf ncnn: face recognition support 2025-04-28 12:09:26 -07:00
Koushik Dutta
4e14f7fd6f common: rtsp server basic auth fix 2025-04-28 12:09:13 -07:00
Roman Sokolov
266be72606 Fixed an issue for some devices. They send screen width as not even value. (#1797) 2025-04-27 14:04:00 -07:00
Koushik Dutta
6a1970c075 ncnn: update model list 2025-04-27 10:15:54 -07:00
Koushik Dutta
0575d98424 ncnn: publish 2025-04-26 21:31:06 -07:00
Koushik Dutta
cdf42fc1a2 rebroadcast: fix url escaping for basic auth 2025-04-24 19:23:23 -07:00
Koushik Dutta
fc1fabc49e common/webrtc: expand h265 keyframe types 2025-04-22 22:20:24 -07:00
Koushik Dutta
4e08daecb2 Merge branch 'main' of github.com:koush/scrypted 2025-04-21 09:02:30 -07:00
Koushik Dutta
58b27805ba common: fix sdp default rtpmap props 2025-04-21 09:02:25 -07:00
Koushik Dutta
b37c6bbd06 postbeta 2025-04-19 12:07:22 -07:00
Koushik Dutta
8eca02d819 server: move cluster fork timeout to prior to fork 2025-04-19 12:07:07 -07:00
Koushik Dutta
0efdb34114 postbeta 2025-04-19 10:53:40 -07:00
Koushik Dutta
1a25100de2 server: replace mime with mime-type which isnt esmodule 2025-04-19 10:53:30 -07:00
Koushik Dutta
51e0a8836d videoanalysis: fix occupancy sensor picking 2025-04-19 08:11:43 -07:00
Koushik Dutta
562d0839b7 videoanalysis: fix smart sensor picking 2025-04-19 08:10:37 -07:00
Koushik Dutta
e3df6accea videoanalysis: make sure duplciate nvr vs camera detections dont cause ui weirdness 2025-04-18 12:43:26 -07:00
Koushik Dutta
03d159a89c server: remove debug code 2025-04-18 11:51:00 -07:00
Koushik Dutta
4ead4726a9 postbeta 2025-04-18 11:49:45 -07:00
Koushik Dutta
b06ef623b3 server: fix potential socket leak if cluster server is down 2025-04-18 11:49:36 -07:00
Koushik Dutta
8edb157e2a snapshot: fix crop and scale 2025-04-17 16:03:03 -07:00
Koushik Dutta
155a1ceb38 rpc: publish 2025-04-15 15:10:30 -07:00
Koushik Dutta
1cb6212fc6 webrtc: implement default clocks for assigned payload types 2025-04-15 07:53:28 -07:00
Koushik Dutta
d1bfed3019 install: make sure amd installer updates repos? 2025-04-10 20:35:37 -07:00
Koushik Dutta
6bf10d4aff install: update intel repos 2025-04-10 19:31:11 -07:00
Koushik Dutta
3ceef8ff87 install: update intel repos 2025-04-10 19:10:41 -07:00
Koushik Dutta
df1155cf82 postbeta 2025-04-10 10:52:51 -07:00
Koushik Dutta
453469ed98 server: implement sendStream backpressure handling 2025-04-10 10:52:42 -07:00
Koushik Dutta
2e4dbceb0e postbeta 2025-04-10 10:47:58 -07:00
Koushik Dutta
c620a4e126 server: ensure sendStream terminates on connection close 2025-04-10 10:47:48 -07:00
Koushik Dutta
5698551b7e common: fix h265 aggregation packet recency check 2025-04-09 15:33:50 -07:00
Koushik Dutta
9e655c0a53 postbeta 2025-04-09 09:37:54 -07:00
Koushik Dutta
35dadaab93 server: enable tcp keepalive for cluster 2025-04-09 09:37:45 -07:00
pir8radio
76487091da install: windows script requests elevation to to admin automatically (#1788)
let the script relaunch itself with admin privileges.  Can be a pain, when launching powershell as admin, you can't drag and drop the script into a powershell window.   This just makes it easier for people new to powershell.
2025-04-09 08:24:07 -07:00
Koushik Dutta
3c5b8bc940 common: fix h265 sei prefix vs suffix separation 2025-04-08 12:28:50 -07:00
Koushik Dutta
37e5c49729 github: remove router build 2025-04-08 10:45:50 -07:00
Koushik Dutta
16fc4407c1 use mime rather than send 2025-04-08 09:00:51 -07:00
Koushik Dutta
3e76c1b1d3 snapshot: remove audio feed in prebuffer request 2025-04-08 08:32:59 -07:00
Koushik Dutta
d6c6e3c594 install: fix macos install 2025-04-07 10:55:54 -07:00
apocaliss92
6c904da49b reolink: Add locallink endpoint (#1782)
* Add API to check wifi connectivity

* Comment removed

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-04-07 08:30:58 -07:00
Koushik Dutta
6ac790f824 webhook: prevent enable on internal types, fixup code to not require mixin existence 2025-04-07 08:17:38 -07:00
Koushik Dutta
3638f80cef mqtt: prevent enable on internal types 2025-04-07 08:17:23 -07:00
Koushik Dutta
809b632417 server: add more cluster logging 2025-04-05 14:07:35 -07:00
Koushik Dutta
f53330c861 webrtc: improve sending codec information only after a marker packet 2025-04-05 10:00:12 -07:00
Koushik Dutta
66455c8f01 rebroadcast: fix bug where stream may be started on fragmented key frame 2025-04-05 09:57:21 -07:00
Koushik Dutta
e6eb61f04f webrtc: more h265 packetizer fixes 2025-04-04 22:48:51 -07:00
Koushik Dutta
adeb3d837e Merge branch 'main' of github.com:koush/scrypted 2025-04-04 15:58:05 -07:00
Koushik Dutta
3da3f85513 webrtc: fix h265 packetizer ap resent for chrome 2025-04-04 15:58:00 -07:00
Koushik Dutta
cbe251e345 docker: bump node to 22 2025-04-04 12:18:59 -07:00
Koushik Dutta
dfa2dacde4 postbeta 2025-04-04 11:40:16 -07:00
Koushik Dutta
9395253b50 server: more express 5.0 fixes 2025-04-04 11:40:04 -07:00
Koushik Dutta
e020ee1517 server: fix breakage caused by path-to-regexp updates 2025-04-04 11:39:03 -07:00
Koushik Dutta
a7ecb9b5e5 postbeta 2025-04-04 11:39:03 -07:00
Koushik Dutta
24f9b0fca3 postbeta 2025-04-04 11:39:03 -07:00
Koushik Dutta
a78ad99f50 server: update deps 2025-04-04 11:39:03 -07:00
Koushik Dutta
c26c5e94a4 server: fix type error in createRpcIoPeer 2025-04-04 11:38:55 -07:00
Koushik Dutta
2d93a69c91 homekit/rebroadcast: fix prebuffer calculation, remove prebuffer warning 2025-04-02 22:12:12 -07:00
Koushik Dutta
1c52297e74 sdk: remove legacy flush/queued from VideoFrame 2025-04-02 20:58:25 -07:00
Koushik Dutta
5bc76642cc webrtc: fix ice restart 2025-04-02 20:57:37 -07:00
Koushik Dutta
15fa27029d webrtc: update werift with connection related hang fixes 2025-04-02 12:34:50 -07:00
Koushik Dutta
553678ed1a rebroadcast: Fix cluster addresses not being used 2025-04-02 12:33:45 -07:00
Koushik Dutta
b3b7265263 rebroadcast: wipe acodec arguments if encoder arguments are explicitly provided 2025-04-01 20:51:07 -07:00
Koushik Dutta
301213fc5f rebroadcast: remove dead code 2025-03-31 11:07:28 -07:00
Koushik Dutta
da393ae4e0 rebroadcast: ensure audio mute/no audio is in sync 2025-03-31 10:57:09 -07:00
Koushik Dutta
9376fc4ba6 common: fixup codec info in packet delivery 2025-03-28 12:58:32 -07:00
Koushik Dutta
51d4aa7b3e sdk: update 2025-03-27 09:38:28 -07:00
Koushik Dutta
30334e5bd0 webrtc: support for alternateCodecs and codec switching 2025-03-26 23:58:21 -07:00
Koushik Dutta
dffc05d165 external: update werift 2025-03-26 19:40:45 -07:00
Koushik Dutta
172e5b3ccb sdk: add codec switch hint 2025-03-25 16:15:51 -07:00
Koushik Dutta
cad60e7730 webrtc/common: fix sdp construction when audio is sideband copied. maybe get rid of this process? 2025-03-25 09:08:46 -07:00
Koushik Dutta
131458576c webrtc: fix handshake negotiation resetting the sender 2025-03-24 20:41:16 -07:00
Koushik Dutta
0d7b47e1e9 webrtc: update 2025-03-22 20:56:56 -07:00
Koushik Dutta
1a33384115 webrtc: fixup h265 keyframe hunting 2025-03-21 23:51:24 -07:00
Koushik Dutta
1fa5f66b44 Merge branch 'main' of github.com:koush/scrypted 2025-03-21 23:21:24 -07:00
Koushik Dutta
1032d444fb google-home: fix build 2025-03-21 23:19:52 -07:00
Koushik Dutta
2883824690 webrtc: add alternate codecs 2025-03-21 19:32:51 -07:00
Koushik Dutta
a505394852 sdk: add audio request hints 2025-03-21 19:22:59 -07:00
Koushik Dutta
1240f401d7 alexa: clean up removed devices 2025-03-21 08:08:27 -07:00
Koushik Dutta
b49faaa033 webrtc: fix rtcp pli from browser 2025-03-20 23:24:37 -07:00
Koushik Dutta
cbb1d4533a vscode-typescript: update sample 2025-03-20 23:24:18 -07:00
Koushik Dutta
a5a027bd6d ncnn: doc flags 2025-03-20 22:30:29 -07:00
Koushik Dutta
94acd0e800 unifi-protect: Fixup codec reporting 2025-03-19 13:19:20 -07:00
Koushik Dutta
cabdd91a92 unifi-protect: update sdk 2025-03-19 13:14:36 -07:00
Koushik Dutta
93c9b62e87 common: disposable queue 2025-03-19 09:11:15 -07:00
Koushik Dutta
21c771d50f common: deferred dispose 2025-03-18 19:08:34 -07:00
Koushik Dutta
bb2ecd7bd8 reolink: fix optional chaining for mainEncType 2025-03-18 11:57:02 -07:00
apocaliss92
6c0864b883 reolink: Disable siren polling (#1774)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-03-18 11:54:33 -07:00
Koushik Dutta
3c8ef7a2cf core: publiush 2025-03-18 09:14:21 -07:00
Koushik Dutta
6afecc8185 homekit: workaround homekit requesting low resolution streams 2025-03-17 10:42:16 -07:00
Koushik Dutta
ea16381b7a homekit: build fixes 2025-03-17 10:19:55 -07:00
Koushik Dutta
09d3ac587f webrtc: publish h265 support 2025-03-16 23:31:05 -07:00
Koushik Dutta
3872cb391a rebroadcast: publish with h265 parsing fixes 2025-03-16 23:22:39 -07:00
Koushik Dutta
7d985937ca webrtc: send desired codecs 2025-03-16 21:16:09 -07:00
Koushik Dutta
cef8482b93 sdk: partial revert of ffmpeg input changes 2025-03-16 19:52:47 -07:00
Koushik Dutta
f729c76346 various: remove defunct ffmpeg args 2025-03-16 19:09:40 -07:00
Koushik Dutta
9aa9498aae sdk: video stream negotiation cleanups 2025-03-16 18:56:48 -07:00
Koushik Dutta
afe832a32a webrtc: h265 beta, requires new nvr plugin or nvr will break 2025-03-15 23:23:35 -07:00
Koushik Dutta
be6a81c9a2 common: fix h265 agg parsing 2025-03-15 22:21:41 -07:00
Koushik Dutta
964bb27d48 common: fix h265 idr seeking 2025-03-15 22:00:24 -07:00
Koushik Dutta
6bca83b338 webrtc: provide h265 hint when available 2025-03-15 17:12:23 -07:00
Koushik Dutta
11860409f1 snapshot: fix h265 prebuffer snapshot 2025-03-15 16:32:00 -07:00
Koushik Dutta
6743f76e09 webrtc: wip h265 2025-03-15 16:24:48 -07:00
Koushik Dutta
771d90ea73 rebroadcast: publish 2025-03-15 08:11:39 -07:00
Koushik Dutta
ba28899dc3 rebroadcast: beta 2025-03-15 00:52:44 -07:00
Koushik Dutta
3e57c90208 rebroadcast: fix mixin order 2025-03-15 00:52:07 -07:00
Koushik Dutta
e155584373 reolink: publish 2025-03-13 21:12:08 -07:00
Koushik Dutta
4a3968956e snapshot: publish 2025-03-13 21:11:07 -07:00
Koushik Dutta
78c259d14e hikvision: light/alarm 2025-03-13 21:10:12 -07:00
Koushik Dutta
6f4b360d2a nanokvm: persist settings from device creation 2025-03-13 15:27:39 -07:00
Koushik Dutta
642795dd9d nanokvm: ip hint 2025-03-13 15:26:39 -07:00
Koushik Dutta
4b3d37b628 nanokvm: heartbeat 2025-03-13 15:22:33 -07:00
Koushik Dutta
8e28e6d19e nanokvm: fixup keywords 2025-03-13 11:29:25 -07:00
Koushik Dutta
d13171551d nanokvm: readme 2025-03-13 11:28:16 -07:00
Koushik Dutta
ea1474c21e nanokvm: publish 2025-03-13 11:22:41 -07:00
Koushik Dutta
7abff3a91b nanokvm: fixup modifiers 2025-03-13 11:14:50 -07:00
Koushik Dutta
40f11a0053 nanokvm: remove logging 2025-03-13 10:48:42 -07:00
Koushik Dutta
1d3450455b core: publish 2025-03-13 10:13:38 -07:00
Koushik Dutta
bd3b4ac387 client: publish 2025-03-13 10:12:56 -07:00
Koushik Dutta
6787153c30 nanokvm: initial commit 2025-03-13 10:05:15 -07:00
Koushik Dutta
7024daba53 sdk: kvm input 2025-03-12 16:32:57 -07:00
Koushik Dutta
28a2e0d898 sdk: shim for es modules 2025-03-12 10:13:19 -07:00
Koushik Dutta
a7757a9a54 docker: bump node version 2025-03-12 10:03:42 -07:00
Koushik Dutta
84fc40e1e5 postbeta 2025-03-12 09:46:36 -07:00
Koushik Dutta
b3b8f6bc70 server: remove eseval 2025-03-12 09:46:28 -07:00
Koushik Dutta
7962606a2b postbeta 2025-03-12 09:32:31 -07:00
Koushik Dutta
9c2ea7d2bc server: another attempt at node/esmodule interop with tsc 2025-03-12 09:32:20 -07:00
Koushik Dutta
4e653a9942 Revert "server: fixup es-eval"
This reverts commit 51836ca59f.
2025-03-12 09:19:29 -07:00
Koushik Dutta
167db0f11a router: usbmuxd persistence 2025-03-12 09:07:04 -07:00
Koushik Dutta
49064de767 postbeta 2025-03-12 09:01:21 -07:00
Koushik Dutta
51836ca59f server: fixup es-eval 2025-03-12 09:01:12 -07:00
Koushik Dutta
d64ed629b0 router: install usbmuxd for iphone tethering 2025-03-11 23:40:01 -07:00
Koushik Dutta
83a9ad2250 rebroadcast: reorder mixin order for regular (ie, non webrtc) cameras to be first to prevent stream flapping due to mixin change noise 2025-03-11 15:24:40 -07:00
Koushik Dutta
7f9358a3b5 webrtc: fix potential webrtc camera thread leak 2025-03-11 14:45:30 -07:00
Koushik Dutta
9cf3d6c912 webrtc: leak and crash fixes 2025-03-11 14:41:51 -07:00
Koushik Dutta
5e3d1c423c common: build fixes 2025-03-11 10:05:24 -07:00
Koushik Dutta
64fa68f2d0 core: publish 2025-03-11 10:05:12 -07:00
Koushik Dutta
45cc859636 router: enable all ip forwarding, install coturn 2025-03-10 11:27:28 -07:00
Koushik Dutta
1984bb44ba router: enable all ip forwarding, install coturn 2025-03-10 11:24:37 -07:00
Koushik Dutta
1164e4b15b router: ipv6 forwarding 2025-03-10 00:43:58 -07:00
Koushik Dutta
9e5fbc5251 postbeta 2025-03-09 19:54:38 -07:00
Koushik Dutta
6f52390067 Merge branch 'main' of github.com:koush/scrypted 2025-03-09 19:54:09 -07:00
Koushik Dutta
d34afab6a4 server: ensure proper ip type checking 2025-03-09 19:53:36 -07:00
Koushik Dutta
2edc74f75b postbeta 2025-03-09 10:13:56 -07:00
Koushik Dutta
e913131f90 server: log denied address 2025-03-09 10:13:43 -07:00
Koushik Dutta
bca752addb postbeta 2025-03-09 09:55:16 -07:00
Koushik Dutta
43fc6c9fc9 server: fix address check 2025-03-09 09:54:48 -07:00
Koushik Dutta
448e2c4e6e router: fix names 2025-03-08 23:51:15 -08:00
Koushik Dutta
625ea7981e router: fix paths 2025-03-08 23:32:58 -08:00
Koushik Dutta
1be806eb8e docker: add router builds 2025-03-08 23:31:10 -08:00
Koushik Dutta
e4b71ffbd4 postbeta 2025-03-08 23:16:51 -08:00
Koushik Dutta
16f4cafea3 server: fix listen sets to listen all and reject on unauthorized address 2025-03-08 23:16:42 -08:00
Koushik Dutta
f78df27341 ncnn: initial commit 2025-03-08 14:08:53 -08:00
Koushik Dutta
57d4e4b9bd core: publish 2025-03-07 21:20:42 -08:00
Koushik Dutta
cb1c062b5e core: publish 2025-03-07 21:19:07 -08:00
Koushik Dutta
e3b996562c Merge branch 'main' of github.com:koush/scrypted 2025-03-07 21:11:22 -08:00
Koushik Dutta
c96bf237b5 core: publish 2025-03-07 21:11:20 -08:00
apocaliss92
5bde86fd15 hikvision: Ptz + presets implemented (#1764)
* Hikvision: Ptz + presets implemented

* Sweeter ptz commands

* Presets fetching

* Capabilities used to enable PTZ

* Redundant call removed

* Logs removed

* Variable moved

* Revert some async changes

* Persist hasPtz

* Move alarm and light to same block

* Log removed

* Fix ptz presets selection

* never disable ptz

* Make devices and ptz caps user-selectable

* undefined check for presets result

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-03-07 19:31:20 -08:00
Koushik Dutta
5075920308 postbeta 2025-03-05 08:34:16 -08:00
Koushik Dutta
9e4845b868 server: allow interface name in SCRYPTED_CLUSTER_ADDRESS 2025-03-05 08:34:05 -08:00
Koushik Dutta
0cab8f2faf core: fix buikd 2025-03-04 20:52:32 -08:00
Koushik Dutta
510321b7d6 postbeta 2025-03-04 19:46:46 -08:00
Koushik Dutta
efb0a39e52 server: fixup SCRYPTED_SERVER_LISTEN_HOSTNAMES 2025-03-04 19:46:13 -08:00
Koushik Dutta
467d89ccaf postbeta 2025-03-04 18:58:40 -08:00
Brett Jia
19832c9537 python: partial repl reimplementation (#1763)
* python: partial repl reimplementation

* make more readable?

* document questionable design choices
2025-03-04 14:37:55 -08:00
Koushik Dutta
0b24e57262 sdk: cleanup peer dependencies 2025-03-04 10:00:40 -08:00
Koushik Dutta
f406969140 sdk: remove col1/2 for description 2025-03-04 09:35:32 -08:00
Koushik Dutta
4db26a1779 sdk: relax types 2025-03-04 08:15:32 -08:00
Koushik Dutta
bfc82d0010 sdk: network types 2025-03-03 19:05:47 -08:00
Koushik Dutta
df3a3d279c router: fix file path 2025-03-03 10:53:33 -08:00
Koushik Dutta
bb7f2a0c9b postbeta 2025-03-03 10:38:28 -08:00
Koushik Dutta
3f83d4b8f7 server: improve plugin kill race conditions on update 2025-03-03 10:38:18 -08:00
Koushik Dutta
779fa1df9c Merge branch 'main' of github.com:koush/scrypted 2025-03-03 09:43:56 -08:00
Koushik Dutta
1c08313e8b server: cleanup runtime worker hooks 2025-03-03 09:43:51 -08:00
root
64e8dc2cc9 router: fix nft flush 2025-03-03 17:42:47 +00:00
Koushik Dutta
1914fa60ea router: file naming consistency 2025-03-03 08:39:25 -08:00
Koushik Dutta
8073a80bae router: typo 2025-03-03 08:28:26 -08:00
Koushik Dutta
cd7d45155f proxmox: fixup confusion around reset script and storage 2025-03-03 08:00:38 -08:00
Koushik Dutta
6f6ccff5b1 server: missing dhcp client 2025-03-03 07:55:48 -08:00
Koushik Dutta
4ea8049d22 server: additional service files 2025-03-03 07:55:02 -08:00
Koushik Dutta
8354564157 postbeta 2025-03-02 18:50:14 -08:00
Koushik Dutta
26518f0693 server: reduced listening address set 2025-03-02 18:48:01 -08:00
Koushik Dutta
72df40c422 postbeta 2025-03-02 15:02:45 -08:00
Koushik Dutta
fe1b677381 server: limit address binding in cluster mode 2025-03-02 14:57:56 -08:00
Koushik Dutta
16a9abeb9e Merge branch 'main' of github.com:koush/scrypted 2025-02-28 21:05:08 -08:00
Koushik Dutta
ba07aa7765 router: caddy 2025-02-28 21:05:04 -08:00
apocaliss92
35e508a01e snapshot: Allow sleeping cameras to have longer lived snapshots (#1747)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-27 22:30:30 -08:00
apocaliss92
1e709a058d reolink: Add polling to reolink discovered devices (#1744)
* Add polling to reolink discovered devices

* Don't poll battery devices

* Logic moved to forever cycle

* Poll battery devices only when they wake up

* Wrap each poll in try/catch

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-27 22:29:12 -08:00
Mehmet Bayram
f5a4bab0a8 hikvision: Improve handling of supplemental light and alarm (#1739)
* hikvision: Remove settings from alarm switch
Dynamically choose supplemental light mode
Rename floodlight to supplemental light
Prevent child device removal if no devices detected
Report devices when settings are saved
Remove unused functions

* fix: push DeviceProvider interface

* hikvision: Add README support to Alarm Switch and update interfaces

* hikvision: add device release method

* hikvision: add IP check before accessing alarm and light capabilities

* hikvision: Centralize alarm activation logic
2025-02-27 22:28:24 -08:00
Koushik Dutta
e373a3935e router: fixup policies 2025-02-26 19:15:46 -08:00
Koushik Dutta
f9f9762046 docker: router shuffling 2025-02-26 11:20:35 -08:00
Koushik Dutta
4b6751785c Merge branch 'main' of github.com:koush/scrypted 2025-02-25 21:14:20 -08:00
Koushik Dutta
133cbcf5f5 sdk: add col 2025-02-25 21:14:14 -08:00
LV Nilesh
817c171757 docker: update BASE (#1748)
https://github.com/koush/scrypted/pull/1745#issuecomment-2677176155
2025-02-25 09:04:36 -08:00
LV Nilesh
9af9359b26 docker: Update Dockerfile.full.header (#1749) 2025-02-25 09:04:17 -08:00
Koushik Dutta
b684ced629 install: nftables 2025-02-23 18:54:58 -08:00
apocaliss92
977db49f87 reolink: Add optional chain to live check (#1746)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-22 23:53:13 -08:00
Koushik Dutta
992fe98f5e core/sdk: update 2025-02-22 19:39:08 -08:00
Koushik Dutta
a74157168e sdk: update 2025-02-22 14:20:55 -08:00
Koushik Dutta
0eed5241f0 sdk: update 2025-02-22 14:20:00 -08:00
Koushik Dutta
c0680736e7 sdk: update 2025-02-21 19:25:41 -08:00
Koushik Dutta
c345f173d2 docker: set SHELL 2025-02-21 08:25:06 -08:00
Koushik Dutta
1ed10cd1cb sdk: radio support 2025-02-20 12:06:51 -08:00
Koushik Dutta
9426db12aa client/server: update deps 2025-02-19 14:56:53 -08:00
Koushik Dutta
a35e821f79 intall: router dockerfile updates 2025-02-19 14:56:39 -08:00
Koushik Dutta
983794d5d0 plugins: remove usage of builtin in favor of internal 2025-02-19 14:56:00 -08:00
Koushik Dutta
cd3e2340b8 rebroadcast: most camera plugins depend on this, so ensure snapshot and webrtc are downloaded as well 2025-02-19 14:55:32 -08:00
Koushik Dutta
bb82eb6bde sdk: Update 2025-02-19 14:28:56 -08:00
Koushik Dutta
5006bb90fc postbeta 2025-02-18 13:53:13 -08:00
Koushik Dutta
7134ef114a server: fixup staged cleanup 2025-02-18 13:53:03 -08:00
Koushik Dutta
2f5b1f6526 postbeta 2025-02-18 13:46:21 -08:00
Koushik Dutta
f88f0a25db server: try staged cleanup 2025-02-18 13:46:11 -08:00
Koushik Dutta
d2810b09ed docker: lite/systemd test 2025-02-17 23:06:33 -08:00
Koushik Dutta
f103ddf660 openvino: fix npu/gpu crashes caused by dynamic input sizes 2025-02-16 15:32:02 -08:00
Koushik Dutta
47edffa56d openvino: regenerate face embedding model 2025-02-16 11:50:05 -08:00
Koushik Dutta
86a5a73276 openvino: workaround for npu crash https://github.com/openvinotoolkit/openvino/issues/29003#issuecomment-2660865184 2025-02-15 08:17:02 -08:00
Koushik Dutta
1a47015558 openvino: more precise npu usage 2025-02-14 11:58:57 -08:00
Koushik Dutta
0de9812760 videoanalysis: fix zone names with hyphens 2025-02-14 11:46:36 -08:00
Koushik Dutta
10d16dab21 hikvision: remove dead code/ 2025-02-14 09:05:38 -08:00
Koushik Dutta
f3f4bbc77f hikvision-doorbell: fix buikd 2025-02-13 13:54:42 -08:00
Koushik Dutta
029f788407 hikvision: cleanup 2025-02-12 21:55:13 -08:00
Mehmet Bayram
89b93eb2f4 hikvision: Add Supplemental Light & Alarm Support to Hikvision Plugin (#1737)
* feat: add supplemental light control for Hikvision cameras

* Add supplemental light control as device

* Add alarm switch with audio/light alarm settings

* Get alarm trigger configuration and capabilities for audio and white light alarms

* Simplify alarm settings retrieval
2025-02-12 21:24:38 -08:00
Koushik Dutta
6fd35e54e6 openvino: rollback openvino 2025-02-12 15:36:57 -08:00
Koushik Dutta
118c404525 sdk: fix and publish 2025-02-12 15:01:49 -08:00
Koushik Dutta
833ecb721f tensorflow-lite: pass through forked flag 2025-02-12 13:39:55 -08:00
Koushik Dutta
a8f1e74278 tensorflow-lite: pass through forked flag 2025-02-12 13:38:18 -08:00
apocaliss92
1075fb4491 sdk: Sensors interface (#1731)
* Sensors interface

* Sensors as mapping type

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-12 13:02:25 -08:00
Koushik Dutta
f09a797ebf core: publish with ui fixes 2025-02-12 09:55:56 -08:00
Koushik Dutta
1e0fdee7b6 Merge branch 'main' of github.com:koush/scrypted 2025-02-12 09:29:15 -08:00
Koushik Dutta
be6375e9f4 tensorflow-lite: add cluster aware 2025-02-12 09:28:06 -08:00
apocaliss92
e5ba39f886 reolink: Forward battery cams detections (#1730)
* Reolink: Forward battery cams detections

* Used right return

* log removed

* use ability to determine events source

* Reuse same branching for events

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-11 11:43:31 -08:00
Brett Jia
6841b74a26 core: exit terminal's subprocess on generator end (#1733) 2025-02-10 16:18:21 -08:00
Koushik Dutta
a3f45e2c49 predict: favor ipv4 for file downloads 2025-02-10 09:14:34 -08:00
Koushik Dutta
b664ccd24f Update install-scrypted-proxmox.sh 2025-02-09 12:00:18 -08:00
Koushik Dutta
3f244b586f install: fix broken intel runtime installer 2025-02-09 11:24:33 -08:00
Koushik Dutta
9f828739de install: update intel libs 2025-02-09 09:08:46 -08:00
Koushik Dutta
b2cef35bc0 openvino: beta with latest 2025-02-09 09:02:02 -08:00
Koushik Dutta
3e54540db7 webrtc: update werift for chrome 132 compatibility 2025-02-08 17:51:04 -08:00
Koushik Dutta
debd7f2c40 onvif: fix onvif ptz with onvif plugin cams 2025-02-08 09:43:29 -08:00
Koushik Dutta
adbc2aaed9 onvif: add text overlays 2025-02-08 09:41:48 -08:00
Koushik Dutta
6024b4ceaf reolink/hikvision: publish 2025-02-08 08:26:28 -08:00
apocaliss92
3a065febb5 hikvision: fix get/patch osd settings (#1728)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-08 08:23:24 -08:00
apocaliss92
1e2c3e0ca7 reolink: Add pir sensor device (#1729)
* Reolink: Add pir sensor device

* Reolink: add siren to hub cameras

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-08 08:23:07 -08:00
Koushik Dutta
61dfddeab2 Create config.yml 2025-02-07 22:02:39 -08:00
Koushik Dutta
b902873d44 hikvision: overlay support 2025-02-07 20:14:14 -08:00
Koushik Dutta
a38d803b86 reolink: overlay support 2025-02-07 13:58:25 -08:00
Koushik Dutta
9c3dab18da sdk/client/server/core/amcrest: add support for video text overlays 2025-02-07 13:07:55 -08:00
Koushik Dutta
2ceb2cd9c3 alexa: maybe fix alexa when no detection types are available 2025-02-05 12:49:04 -08:00
Koushik Dutta
a5fd1c0278 postbeta 2025-02-05 12:09:25 -08:00
Koushik Dutta
2c717cb4fc postrelease 2025-02-05 12:09:03 -08:00
Koushik Dutta
df10c4e5f2 server: fixup address, make it available on cluster manager 2025-02-05 12:08:50 -08:00
Koushik Dutta
7c51bb420e postbeta 2025-02-05 11:51:38 -08:00
Koushik Dutta
367eafff5c postrelease 2025-02-05 11:51:26 -08:00
Koushik Dutta
ea628a7130 wip: unifi 2024-11-28 08:47:11 -08:00
373 changed files with 39644 additions and 11111 deletions

2
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
# disable blank issue creation
blank_issues_enabled: false

View File

@@ -8,7 +8,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: self-hosted
env:
NODE_VERSION: '20'
NODE_VERSION: '22'
strategy:
matrix:
BASE: ["noble"]
@@ -77,13 +77,14 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
build-nvidia:
name: Push NVIDIA Docker image to Docker Hub
build-vendor:
name: Push Vendor Docker image to Docker Hub
needs: build
runs-on: self-hosted
strategy:
matrix:
BASE: ["noble"]
VENDOR: ["nvidia", "nvidia-legacy", "intel", "amd"]
steps:
- name: Check out the repo
uses: actions/checkout@v3
@@ -138,11 +139,11 @@ jobs:
build-args: |
BASE=ghcr.io/koush/scrypted-common:${{ matrix.BASE }}-full
context: install/docker/
file: install/docker/Dockerfile.nvidia
file: install/docker/Dockerfile.${{ matrix.VENDOR }}
platforms: linux/amd64,linux/arm64
push: true
tags: |
koush/scrypted-common:${{ matrix.BASE }}-nvidia
ghcr.io/koush/scrypted-common:${{ matrix.BASE }}-nvidia
koush/scrypted-common:${{ matrix.BASE }}-${{ matrix.VENDOR }}
ghcr.io/koush/scrypted-common:${{ matrix.BASE }}-${{ matrix.VENDOR }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@@ -20,9 +20,13 @@ jobs:
strategy:
matrix:
BASE: [
["noble-nvidia", ".s6"],
["noble-full", ".s6"],
["noble-lite", ""],
["noble-nvidia", ".s6", "noble-nvidia", "nvidia"],
["noble-nvidia-legacy", ".s6", "noble-nvidia-legacy", "nvidia-legacy"],
["noble-intel", ".s6", "noble-intel", "intel"],
["noble-amd", ".s6", "noble-amd", "amd"],
["noble-full", ".s6", "noble-full", "full"],
["noble-lite", "", "noble-lite", "lite"],
["noble-lite", ".router", "noble-router", "router"],
]
steps:
- name: Check out the repo
@@ -93,17 +97,25 @@ jobs:
file: install/docker/Dockerfile${{ matrix.BASE[1] }}
platforms: linux/amd64,linux/arm64
push: true
# when publishing a tag (beta or latest), platform and version, create some tags as follows.
# using beta 0.0.1 as an example
# koush/scrypted:v0.0.1-noble-full
# koush/scrypted:beta
# koush/scrypted:beta-nvidia|intel|full|router|lite
# using latest 0.0.2 as an example:
# koush/scrypted:v0.0.2-noble-full
# koush/scrypted:latest
# koush/scrypted:nvidia|intel|full|router|lite
tags: |
${{ format('koush/scrypted:v{1}-{0}', matrix.BASE[0], github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
${{ matrix.BASE[0] == 'noble-full' && format('koush/scrypted:{0}', github.event.inputs.tag) || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'noble-nvidia' && 'koush/scrypted:nvidia' || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'noble-full' && 'koush/scrypted:full' || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'noble-lite' && 'koush/scrypted:lite' || '' }}
${{ format('koush/scrypted:v{0}-{1}', github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION, matrix.BASE[2]) }}
${{ matrix.BASE[2] == 'noble-full' && format('koush/scrypted:{0}', github.event.inputs.tag) || '' }}
${{ github.event.inputs.tag == 'latest' && format('koush/scrypted:{0}', matrix.BASE[3]) || '' }}
${{ github.event.inputs.tag != 'latest' && format('koush/scrypted:{0}-{1}', github.event.inputs.tag, matrix.BASE[3]) || '' }}
${{ format('ghcr.io/koush/scrypted:v{1}-{0}', matrix.BASE[0], github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
${{ matrix.BASE[0] == 'noble-full' && format('ghcr.io/koush/scrypted:{0}', github.event.inputs.tag) || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'noble-nvidia' && 'ghcr.io/koush/scrypted:nvidia' || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'noble-full' && 'ghcr.io/koush/scrypted:full' || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'noble-lite' && 'ghcr.io/koush/scrypted:lite' || '' }}
${{ matrix.BASE[2] == 'noble-full' && format('ghcr.io/koush/scrypted:{0}', github.event.inputs.tag) || '' }}
${{ github.event.inputs.tag == 'latest' && format('ghcr.io/koush/scrypted:{0}', matrix.BASE[3]) || ''}}
${{ github.event.inputs.tag != 'latest' && format('ghcr.io/koush/scrypted:{0}-{1}', github.event.inputs.tag, matrix.BASE[3]) || '' }}
cache-from: type=gha
cache-to: type=gha,mode=max

3674
common/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,11 +12,12 @@
"license": "ISC",
"dependencies": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/types": "^0.5.27",
"http-auth-utils": "^5.0.1",
"typescript": "^5.5.3"
},
"devDependencies": {
"@types/node": "^20.11.0",
"@types/node": "^20.19.11",
"monaco-editor": "^0.50.0",
"ts-node": "^10.9.2"
}

View File

@@ -9,6 +9,16 @@ export function createAsyncQueue<T>() {
const waiting: Deferred<T>[] = [];
const queued: { item: T, dequeued?: Deferred<void> }[] = [];
const wait = async (index: number) => {
const q = queued[index];
if (!q)
return;
if (!q.dequeued) {
q.dequeued = new Deferred<void>();
}
return q.dequeued.promise;
}
const dequeue = async () => {
if (queued.length) {
const { item, dequeued: enqueue } = queued.shift()!;
@@ -66,7 +76,7 @@ export function createAsyncQueue<T>() {
dequeued?.reject(new Error('abort'));
};
dequeued?.promise.catch(() => {}).finally(() => signal.removeEventListener('abort', h));
dequeued?.promise.catch(() => { }).finally(() => signal.removeEventListener('abort', h));
signal.addEventListener('abort', h);
return true;
@@ -123,6 +133,9 @@ export function createAsyncQueue<T>() {
}
return {
[Symbol.dispose]() {
end(new Error('async queue disposed'));
},
get ended() {
return ended;
},
@@ -151,13 +164,14 @@ export function createAsyncQueue<T>() {
dequeue,
get queue() {
return queue();
}
},
wait,
}
}
export function createAsyncQueueFromGenerator<T>(generator: AsyncGenerator<T>) {
const q = createAsyncQueue<T>();
(async() => {
(async () => {
try {
for await (const i of generator) {
await q.enqueue(i);

View File

@@ -7,7 +7,6 @@ const { systemManager } = sdk;
export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
hasEnabledMixin: { [id: string]: string } = {};
pluginsComponent: Promise<any>;
unshiftMixin = false;
constructor(nativeId?: string, public autoIncludeToken = 'v4') {
super(nativeId);
@@ -45,6 +44,10 @@ export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
return this.hasEnabledMixin[device.id] === this.autoIncludeToken;
}
shouldUnshiftMixin(device: ScryptedDevice) {
return false;
}
async maybeEnableMixin(device: ScryptedDevice) {
if (!device || device.mixins?.includes(this.id))
return;
@@ -61,7 +64,7 @@ export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
this.log.i('auto enabling mixin for ' + device.name)
const mixins = (device.mixins || []).slice();
if (this.unshiftMixin)
if (this.shouldUnshiftMixin(device))
mixins.unshift(this.id);
else
mixins.push(this.id);
@@ -77,5 +80,5 @@ export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
this.storage.setItem('hasEnabledMixin', JSON.stringify(this.hasEnabledMixin));
}
abstract canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[] | null | undefined | void>;
abstract canMixin(type: ScryptedDeviceType | string, interfaces: string[]): Promise<string[] | null | undefined | void>;
}

5
common/src/devices.ts Normal file
View File

@@ -0,0 +1,5 @@
import type { SystemManager } from '@scrypted/types';
export function getAllDevices<T>(systemManager: SystemManager) {
return Object.keys(systemManager.getSystemState()).map(id => systemManager.getDeviceById<T>(id));
}

View File

@@ -2,6 +2,7 @@ import type * as monacoEditor from 'monaco-editor';
export interface StandardLibs {
'@types/node/globals.d.ts': string,
'@types/node/module.d.ts': string,
'@types/node/buffer.d.ts': string,
'@types/node/process.d.ts': string,
'@types/node/events.d.ts': string,

View File

@@ -116,6 +116,7 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
export function createMonacoEvalDefaults(extraLibs: { [lib: string]: string }) {
const standardlibs: StandardLibs = {
"@types/node/globals.d.ts": readFileAsString('@types/node/globals.d.ts'),
"@types/node/module.d.ts": readFileAsString('@types/node/module.d.ts'),
"@types/node/buffer.d.ts": readFileAsString('@types/node/buffer.d.ts'),
"@types/node/process.d.ts": readFileAsString('@types/node/process.d.ts'),
"@types/node/events.d.ts": readFileAsString('@types/node/events.d.ts'),

8
common/src/json.ts Normal file
View File

@@ -0,0 +1,8 @@
export function safeParseJson(value: string) {
try {
return JSON.parse(value);
}
catch (e) {
}
}

View File

@@ -1,96 +0,0 @@
export interface RefreshPromise<T> {
promise: Promise<T>;
cacheDuration: number;
}
export function singletonPromise<T>(rp: undefined | RefreshPromise<T>, method: () => Promise<T>, cacheDuration = 0) {
if (rp?.promise)
return rp;
const promise = method();
if (!rp) {
rp = {
promise,
cacheDuration,
}
}
else {
rp.promise = promise;
}
promise.finally(() => setTimeout(() => rp.promise = undefined, rp.cacheDuration));
return rp;
}
export class TimeoutError<T> extends Error {
constructor(public promise: Promise<T>) {
super('Operation Timed Out');
}
}
export function timeoutPromise<T>(timeout: number, promise: Promise<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
const t = setTimeout(() => reject(new TimeoutError(promise)), timeout);
promise
.then(v => {
clearTimeout(t);
resolve(v);
})
.catch(e => {
clearTimeout(t);
reject(e);
});
})
}
export function timeoutFunction<T>(timeout: number, f: (isTimedOut: () => boolean) => Promise<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
let isTimedOut = false;
const promise = f(() => isTimedOut);
const t = setTimeout(() => {
isTimedOut = true;
reject(new TimeoutError(promise));
}, timeout);
promise
.then(v => {
clearTimeout(t);
resolve(v);
})
.catch(e => {
clearTimeout(t);
reject(e);
});
})
}
export function createPromiseDebouncer<T>() {
let current: Promise<T>;
return (func: () => Promise<T>): Promise<T> => {
if (!current)
current = func().finally(() => current = undefined);
return current;
}
}
export function createMapPromiseDebouncer<T>() {
const map = new Map<string, Promise<T>>();
return (key: any, debounce: number, func: () => Promise<T>): Promise<T> => {
const keyStr = JSON.stringify(key);
let value = map.get(keyStr);
if (!value) {
value = func().finally(() => {
if (!debounce) {
map.delete(keyStr);
return;
}
setTimeout(() => map.delete(keyStr), debounce);
});
map.set(keyStr, value);
}
return value;
}
}

1
common/src/promise-utils.ts Symbolic link
View File

@@ -0,0 +1 @@
../../server/src/promise-utils.ts

View File

@@ -110,7 +110,9 @@ export class BrowserSignalingSession implements RTCSignalingSession {
await this.microphone.replaceTrack(mic.getTracks()[0]);
}
this.microphone.track.enabled = enabled;
if (this.microphone?.track) {
this.microphone.track.enabled = enabled;
}
}
close() {

View File

@@ -93,8 +93,15 @@ export const H265_NAL_TYPE_AGG = 48;
export const H265_NAL_TYPE_VPS = 32;
export const H265_NAL_TYPE_SPS = 33;
export const H265_NAL_TYPE_PPS = 34;
export const H265_NAL_TYPE_IDR_N = 19;
export const H265_NAL_TYPE_IDR_W = 20;
export const H265_NAL_TYPE_BLA_W_LP = 16;
export const H265_NAL_TYPE_BLA_W_RADL = 17;
export const H265_NAL_TYPE_BLA_N_LP = 18;
export const H265_NAL_TYPE_IDR_W_RADL = 19;
export const H265_NAL_TYPE_IDR_N_LP = 20;
export const H265_NAL_TYPE_CRA_NUT = 21;
export const H265_NAL_TYPE_FU = 49;
export const H265_NAL_TYPE_SEI_PREFIX = 39;
export const H265_NAL_TYPE_SEI_SUFFIX = 40;
export function findH264NaluType(streamChunk: StreamChunk, naluType: number) {
if (streamChunk.type !== 'h264')
@@ -161,10 +168,10 @@ export function findH265NaluTypeInNalu(nalu: Buffer, naluType: number) {
return;
}
export function getNaluTypes(streamChunk: StreamChunk) {
export function getStartedH264NaluTypes(streamChunk: StreamChunk) {
if (streamChunk.type !== 'h264')
return new Set<number>();
return getNaluTypesInNalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12))
return getNaluTypesInNalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12), true)
}
export function getNaluTypesInNalu(nalu: Buffer, fuaRequireStart = false, fuaRequireEnd = false) {
@@ -205,10 +212,10 @@ export function getNaluTypesInNalu(nalu: Buffer, fuaRequireStart = false, fuaReq
return ret;
}
export function getH265NaluTypes(streamChunk: StreamChunk) {
export function getStartedH265NaluTypes(streamChunk: StreamChunk) {
if (streamChunk.type !== 'h265')
return new Set<number>();
return getNaluTypesInH265Nalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12))
return getNaluTypesInH265Nalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12), true)
}
export function getNaluTypesInH265Nalu(nalu: Buffer, fuaRequireStart = false, fuaRequireEnd = false) {
@@ -216,7 +223,7 @@ export function getNaluTypesInH265Nalu(nalu: Buffer, fuaRequireStart = false, fu
const naluType = parseH265NaluType(nalu[0]);
if (naluType === H265_NAL_TYPE_AGG) {
ret.add(H265_NAL_TYPE_AGG);
let pos = 1;
let pos = 2;
while (pos < nalu.length) {
const naluLength = nalu.readUInt16BE(pos);
pos += 2;
@@ -225,6 +232,23 @@ export function getNaluTypesInH265Nalu(nalu: Buffer, fuaRequireStart = false, fu
pos += naluLength;
}
}
else if (naluType === H265_NAL_TYPE_FU) {
ret.add(H265_NAL_TYPE_FU);
const fuaType = nalu[2] & 0x3F; // 6 bits
if (fuaRequireStart) {
const isFuStart = !!(nalu[2] & 0x80);
if (isFuStart)
ret.add(fuaType);
}
else if (fuaRequireEnd) {
const isFuEnd = !!(nalu[2] & 0x40);
if (isFuEnd)
ret.add(fuaType);
}
else {
ret.add(fuaType);
}
}
else {
ret.add(naluType);
}
@@ -232,6 +256,26 @@ export function getNaluTypesInH265Nalu(nalu: Buffer, fuaRequireStart = false, fu
return ret;
}
export function isH265KeyFrameRelatedInSet(naluTypes: Set<number>, allowCodecInfo = true) {
if (naluTypes.has(H265_NAL_TYPE_IDR_N_LP)
|| naluTypes.has(H265_NAL_TYPE_IDR_W_RADL)
|| naluTypes.has(H265_NAL_TYPE_CRA_NUT)
|| naluTypes.has(H265_NAL_TYPE_BLA_N_LP)
|| naluTypes.has(H265_NAL_TYPE_BLA_W_LP)
|| naluTypes.has(H265_NAL_TYPE_BLA_W_RADL)) {
return true;
}
if (allowCodecInfo) {
if (naluTypes.has(H265_NAL_TYPE_VPS)
|| naluTypes.has(H265_NAL_TYPE_SPS)
|| naluTypes.has(H265_NAL_TYPE_PPS))
return true;
}
return false;
}
export function createRtspParser(options?: StreamParserOptions): RtspStreamParser {
let resolve: any;
@@ -255,20 +299,15 @@ export function createRtspParser(options?: StreamParserOptions): RtspStreamParse
for (let prebufferIndex = 0; prebufferIndex < streamChunks.length; prebufferIndex++) {
const streamChunk = streamChunks[prebufferIndex];
if (streamChunk.type === 'h264') {
const naluTypes = getNaluTypes(streamChunk);
const naluTypes = getStartedH264NaluTypes(streamChunk);
if (naluTypes.has(H264_NAL_TYPE_SPS) || naluTypes.has(H264_NAL_TYPE_IDR)) {
return streamChunks.slice(prebufferIndex);
}
}
else if (streamChunk.type === 'h265') {
const naluTypes = getH265NaluTypes(streamChunk);
const naluTypes = getStartedH265NaluTypes(streamChunk);
if (naluTypes.has(H265_NAL_TYPE_VPS)
|| naluTypes.has(H265_NAL_TYPE_SPS)
|| naluTypes.has(H265_NAL_TYPE_PPS)
|| naluTypes.has(H265_NAL_TYPE_IDR_N)
|| naluTypes.has(H265_NAL_TYPE_IDR_W)
) {
if (isH265KeyFrameRelatedInSet(naluTypes)) {
return streamChunks.slice(prebufferIndex);
}
}
@@ -650,9 +689,12 @@ export class RtspClient extends RtspBase {
// @ts-ignore
const { parseHTTPHeadersQuotedKeyValueSet } = await import('http-auth-utils/dist/utils');
const authedUrl = new URL(this.url);
const username = decodeURIComponent(authedUrl.username);
const password = decodeURIComponent(authedUrl.password);
if (this.wwwAuthenticate.includes('Basic')) {
const parsedUrl = new URL(this.url);
const hash = BASIC.computeHash({ username: parsedUrl.username, password: parsedUrl.password });
const hash = BASIC.computeHash({ username, password });
return `Basic ${hash}`;
}
@@ -672,10 +714,6 @@ export class RtspClient extends RtspBase {
REQUIRED_WWW_AUTHENTICATE_KEYS,
) as DigestWWWAuthenticateData;
const authedUrl = new URL(this.url);
const username = decodeURIComponent(authedUrl.username);
const password = decodeURIComponent(authedUrl.password);
const strippedUrl = new URL(url.toString());
strippedUrl.username = '';
strippedUrl.password = '';

View File

@@ -175,6 +175,8 @@ export type RTPMap = ReturnType<typeof parseRtpMap>;
export function parseRtpMap(mline: ReturnType<typeof parseMLine>, rtpmap: string) {
const mlineType = mline.type;
const match = rtpmap?.match(/a=rtpmap:([\d]+) (.*?)\/([\d]+)(\/([\d]+))?/);
let channels = parseInt(match?.[5]) || undefined;
let payloadType = parseInt(match?.[1]);
rtpmap = rtpmap?.toLowerCase();
@@ -222,14 +224,20 @@ export function parseRtpMap(mline: ReturnType<typeof parseMLine>, rtpmap: string
if (mline.payloadTypes?.includes(0)) {
codec = 'pcm_mulaw';
ffmpegEncoder = 'pcm_mulaw';
payloadType = 0;
channels = 1;
}
else if (mline.payloadTypes?.includes(8)) {
codec = 'pcm_alaw';
ffmpegEncoder = 'pcm_alaw';
payloadType = 8;
channels = 1;
}
else if (mline.payloadTypes?.includes(14)) {
codec = 'mp3';
ffmpegEncoder = 'mp3';
payloadType = 14;
channels = 2;
}
else {
// ffmpeg seems to omit the rtpmap type for pcm alaw when creating sdp?
@@ -239,17 +247,29 @@ export function parseRtpMap(mline: ReturnType<typeof parseMLine>, rtpmap: string
// https://en.wikipedia.org/wiki/RTP_payload_formats
codec = 'pcm_alaw';
ffmpegEncoder = 'pcm_alaw';
payloadType = 8;
channels = 1;
}
}
// assigned payload types do not need to provide a clock, there is a default.
let clock = parseInt(match?.[3]);
if (!clock) {
clock = undefined;
if (codec === 'pcm_mulaw' || codec === 'pcm_alaw')
clock = 8000;
else if (codec === 'pcm_s16be')
clock = 16000;
}
return {
line: rtpmap,
codec,
ffmpegEncoder,
rawCodec: match?.[2],
clock: parseInt(match?.[3]),
channels: parseInt(match?.[5]) || undefined,
payloadType: parseInt(match?.[1]),
clock,
channels,
payloadType,
}
}
@@ -359,3 +379,33 @@ export function getSpsPps(
pps: Buffer.from(pps, 'base64'),
}
}
export function getSpsPpsVps(
section: {
fmtp: {
payloadType: number;
parameters: {
[key: string]: string;
};
}[]
}
) {
const parameters = section?.fmtp?.[0]?.parameters;
if (!parameters) {
return {
sps: undefined,
pps: undefined,
vps: undefined,
};
}
const sps = parameters['sprop-sps'];
const pps = parameters['sprop-pps'];
const vps = parameters['sprop-vps'];
return {
sps: sps ? Buffer.from(sps, 'base64') : undefined,
pps: pps ? Buffer.from(pps, 'base64') : undefined,
vps: vps ? Buffer.from(vps, 'base64') : undefined,
}
}

View File

@@ -2,7 +2,6 @@ import { Socket as DatagramSocket } from "dgram";
import { once } from "events";
import { Duplex } from "stream";
import { FFMPEG_FRAGMENTED_MP4_OUTPUT_ARGS, MP4Atom, parseFragmentedMP4 } from "./ffmpeg-mp4-parser-session";
import { readLength } from "./read-stream";
export interface StreamParser {
container: string;
@@ -25,59 +24,11 @@ export interface StreamParserOptions {
export interface StreamChunk {
startStream?: Buffer;
chunks: Buffer[];
type?: string;
type: string;
width?: number;
height?: number;
}
// function checkTsPacket(pkt: Buffer) {
// const pid = ((pkt[1] & 0x1F) << 8) | pkt[2];
// if (pid == 256) {
// // found video stream
// if ((pkt[3] & 0x20) && (pkt[4] > 0)) {
// // have AF
// if (pkt[5] & 0x40) {
// // found keyframe
// console.log('keyframe');
// }
// }
// }
// }
function createLengthParser(length: number, verify?: (concat: Buffer) => void) {
async function* parse(socket: Duplex): AsyncGenerator<StreamChunk> {
let pending: Buffer[] = [];
let pendingSize = 0;
while (true) {
const data: Buffer = socket.read();
if (!data) {
await once(socket, 'readable');
continue;
}
pending.push(data);
pendingSize += data.length;
if (pendingSize < length)
continue;
const concat = Buffer.concat(pending);
verify?.(concat);
const remaining = concat.length % length;
const left = concat.slice(0, concat.length - remaining);
const right = concat.slice(concat.length - remaining);
pending = [right];
pendingSize = right.length;
yield {
chunks: [left],
};
}
}
return parse;
}
export function createDgramParser() {
async function* parse(socket: DatagramSocket, width: number, height: number, type: string) {
while (true) {
@@ -91,65 +42,6 @@ export function createDgramParser() {
return parse;
}
export function createMpegTsParser(options?: StreamParserOptions): StreamParser {
return {
container: 'mpegts',
outputArguments: [
...(options?.vcodec || []),
...(options?.acodec || []),
'-f', 'mpegts',
],
parse: createLengthParser(188, concat => {
if (concat[0] != 0x47) {
throw new Error('Invalid sync byte in mpeg-ts packet. Terminating stream.')
}
}),
findSyncFrame(streamChunks): StreamChunk[] {
for (let prebufferIndex = 0; prebufferIndex < streamChunks.length; prebufferIndex++) {
const streamChunk = streamChunks[prebufferIndex];
for (let chunkIndex = 0; chunkIndex < streamChunk.chunks.length; chunkIndex++) {
const chunk = streamChunk.chunks[chunkIndex];
let offset = 0;
while (offset + 188 < chunk.length) {
const pkt = chunk.subarray(offset, offset + 188);
const pid = ((pkt[1] & 0x1F) << 8) | pkt[2];
if (pid == 256) {
// found video stream
if ((pkt[3] & 0x20) && (pkt[4] > 0)) {
// have AF
if (pkt[5] & 0x40) {
// we found the sync frame, but also need to send the pat and pmt
// which might be at the start of this chunk before the keyframe.
// yolo!
return streamChunks.slice(prebufferIndex);
// const chunks = streamChunk.chunks.slice(chunkIndex + 1);
// const take = chunk.subarray(offset);
// chunks.unshift(take);
// const remainingChunks = streamChunks.slice(prebufferIndex + 1);
// const ret = Object.assign({}, streamChunk);
// ret.chunks = chunks;
// return [
// ret,
// ...remainingChunks
// ];
}
}
}
offset += 188;
}
}
}
return findSyncFrame(streamChunks);
}
}
}
export async function* parseMp4StreamChunks(parser: AsyncGenerator<MP4Atom>) {
let ftyp: MP4Atom;
let moov: MP4Atom;
@@ -213,54 +105,3 @@ export const PIXEL_FORMAT_RGB24: RawVideoPixelFormat = {
name: 'rgb24',
computeLength: (width, height) => width * height * 3,
}
export function createRawVideoParser(options: RawVideoParserOptions): StreamParser {
const pixelFormat = options?.pixelFormat || PIXEL_FORMAT_YUV420P;
let filter: string;
const { size, everyNFrames } = options;
if (size) {
filter = `scale=${size.width}:${size.height}`;
}
if (everyNFrames && everyNFrames > 1) {
if (filter)
filter += ',';
else
filter = '';
filter = filter + `select=not(mod(n\\,${everyNFrames}))`
}
const inputArguments: string[] = [];
if (options.size)
inputArguments.push('-s', `${options.size.width}x${options.size.height}`);
inputArguments.push('-pix_fmt', pixelFormat.name);
return {
inputArguments,
container: 'rawvideo',
outputArguments: [
'-s', `${options.size.width}x${options.size.height}`,
'-an',
'-vcodec', 'rawvideo',
'-pix_fmt', pixelFormat.name,
'-f', 'rawvideo',
],
async *parse(socket: Duplex, width: number, height: number): AsyncGenerator<StreamChunk> {
width = size?.width || width;
height = size?.height || height
if (!width || !height)
throw new Error("error parsing rawvideo, unknown width and height");
const toRead = pixelFormat.computeLength(width, height);
while (true) {
const buffer = await readLength(socket, toRead);
yield {
chunks: [buffer],
width,
height,
}
}
},
findSyncFrame,
}
}

View File

@@ -0,0 +1,98 @@
export abstract class AsyncUsingHolderBase<T> {
constructor(private _value: T) {
}
get value(): T {
return this._value;
}
async [Symbol.asyncDispose]() {
await this.release();
}
abstract asyncDispose(value: T): Promise<void>;
detach() {
const value = this._value;
this._value = undefined;
return value;
}
async replace(value: T) {
this.release();
this._value = value;
}
async release() {
const released = this.detach();
if (released)
await this.asyncDispose(released);
}
}
export abstract class UsingHolderBase<T> {
constructor(private _value: T) {
}
get value(): T {
return this._value;
}
[Symbol.dispose]() {
this.release();
}
abstract dispose(value: T): void;
detach() {
const value = this._value;
this._value = undefined;
return value;
}
replace(value: T) {
this.release();
this._value = value;
}
release() {
const released = this.detach();
if (released)
this.dispose(released);
}
}
export class UsingHolder<T extends Disposable> extends UsingHolderBase<T> {
dispose(value: T) {
value?.[Symbol.dispose]();
}
transferClosure<V>(closure: (value: UsingHolder<T>) => Promise<V>) {
return (async () => {
using attached = new UsingHolder(this.detach());
return await closure(attached);
})();
}
}
export class AsyncUsingHolder<T extends AsyncDisposable> extends AsyncUsingHolderBase<T> {
async asyncDispose(value: T) {
value?.[Symbol.asyncDispose]();
}
transferClosure<V>(closure: (value: AsyncUsingHolder<T>) => Promise<V>) {
return (async () => {
await using attached = new AsyncUsingHolder(this.detach());
return await closure(attached);
})();
}
}
export class DisposableHolder<T> extends UsingHolderBase<T> {
constructor(value: T, private _dispose: (value: T) => void) {
super(value);
}
dispose(value: T) {
this._dispose(value);
}
}

View File

@@ -23,7 +23,9 @@ export function createService<T, V>(options: ForkOptions, create: (t: Promise<T>
currentFork = sdk.fork<T>(options);
currentFork.worker.on('exit', () => currentResult = undefined);
currentResult = create(currentFork.result);
currentResult.catch(() => currentResult = undefined);
currentResult.catch(() => {
currentResult = undefined;
});
return currentResult;
},
@@ -50,7 +52,12 @@ export function createZygote<T>(options?: ForkOptions): Zygote<T> {
}
const gen = next();
return () => gen.next().value as PluginFork<T>;
return () => {
const ret = gen.next();
if (ret.done || !ret.value)
throw new Error('Zygote exhausted');
return ret.value;
};
}

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"module": "commonjs",
"module": "Node16",
"moduleResolution": "Node16",
"target": "esnext",
"noImplicitAny": true,

View File

@@ -1,6 +1,6 @@
# Home Assistant Addon Configuration
name: Scrypted
version: "v0.130.1-noble-full"
version: "v0.143.0-noble-full"
slug: scrypted
description: Scrypted is a high performance home video integration and automation platform
url: "https://github.com/koush/scrypted"

View File

@@ -1,4 +1,4 @@
ARG BASE="20-jammy-full"
ARG BASE="noble-full"
FROM ghcr.io/koush/scrypted-common:${BASE}
WORKDIR /

View File

@@ -1,4 +1,4 @@
ARG BASE="16-jammy"
ARG BASE="noble-full"
FROM ghcr.io/koush/scrypted-common:${BASE}
WORKDIR /
@@ -8,4 +8,4 @@ WORKDIR /scrypted/server
RUN npm install
RUN npm run build
CMD npm run serve-no-build
CMD ["npm", "run", "serve-no-build"]

View File

@@ -0,0 +1,7 @@
ARG BASE="ghcr.io/koush/scrypted-common:noble-amd"
FROM $BASE
ENV SCRYPTED_DOCKER_FLAVOR="amd"
# amd opencl
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-amd-graphics.sh | bash

View File

@@ -6,8 +6,8 @@
# This common file will be used by both Docker and the linux
# install script.
################################################################
ARG BASE="jammy"
FROM ubuntu:${BASE} as header
ARG BASE="noble"
FROM ubuntu:${BASE} AS header
ENV DEBIAN_FRONTEND=noninteractive
@@ -19,7 +19,7 @@ RUN apt-get update && apt-get -y install \
apt-get -y update && \
apt-get -y upgrade
ARG NODE_VERSION=20
ARG NODE_VERSION=22
RUN apt-get install -y ca-certificates curl gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
@@ -35,19 +35,6 @@ RUN apt-get -y install \
python3-setuptools \
python3-wheel
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
RUN echo "Installing gstreamer."
# python-codecs pygobject dependencies
RUN apt-get -y install libcairo2-dev libgirepository1.0-dev
RUN apt-get -y install \
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav \
gstreamer1.0-vaapi
# python3 gstreamer bindings
RUN echo "Installing gstreamer bindings."
RUN apt-get -y install \
python3-gst-1.0
# allow pip to install to system
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
@@ -61,7 +48,7 @@ RUN python3 -m pip install debugpy
################################################################
# Begin section generated from template/Dockerfile.full.footer
################################################################
FROM header as base
FROM header AS base
# vulkan
RUN apt -y install libvulkan1
@@ -69,12 +56,9 @@ RUN apt -y install libvulkan1
# intel opencl for openvino
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
# NPU driver will SIGILL on openvino prior to 2024.5.0
# intel NPU
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
# amd opencl
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-amd-graphics.sh | bash
# python 3.9 from ppa.
# 3.9 is the version with prebuilt support for tensorflow lite
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
@@ -92,7 +76,7 @@ RUN python3.9 -m pip install debugpy
# Coral Edge TPU
# https://coral.ai/docs/accelerator/get-started/#runtime-on-linux
RUN echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | tee /etc/apt/sources.list.d/coral-edgetpu.list
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
RUN curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /etc/apt/trusted.gpg.d/coral-edgetpu.gpg
RUN apt-get -y update && apt-get -y install libedgetpu1-std
# set default shell to bash

View File

@@ -0,0 +1,16 @@
ARG BASE="ghcr.io/koush/scrypted-common:noble-intel"
FROM $BASE
ENV SCRYPTED_DOCKER_FLAVOR="intel"
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-oneapi.sh | bash
ENV LD_LIBRARY_PATH=/opt/intel/oneapi/tcm/latest/lib
ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/intel/oneapi/umf/latest/lib
# gcc4.8 does not have a latest link however, it does seem to point to a relative lib path
ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/intel/oneapi/tbb/latest/env/../lib/intel64/gcc4.8
ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/intel/oneapi/tbb/latest/lib
ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/intel/oneapi/mkl/latest/lib
ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/intel/oneapi/compiler/latest/opt/compiler/lib
ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/opt/intel/oneapi/compiler/latest/lib

View File

@@ -1,5 +1,7 @@
ARG BASE="jammy"
FROM ubuntu:${BASE} as header
ARG BASE="noble-lite"
FROM ubuntu:${BASE} AS header
ENV SCRYPTED_DOCKER_FLAVOR="lite"
ENV DEBIAN_FRONTEND=noninteractive
@@ -10,7 +12,7 @@ RUN apt-get update && apt-get -y install \
apt-get -y update && \
apt-get -y upgrade
ARG NODE_VERSION=20
ARG NODE_VERSION=22
RUN apt-get install -y ca-certificates curl gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
@@ -21,9 +23,8 @@ ENV SCRYPTED_INSTALL_ENVIRONMENT="docker"
ENV SCRYPTED_CAN_RESTART="true"
ENV SCRYPTED_VOLUME="/server/volume"
ENV SCRYPTED_INSTALL_PATH="/server"
ENV SHELL="/bin/bash"
RUN test -f "/usr/bin/python3" && test -f "/usr/bin/python3.12"
ENV SCRYPTED_PYTHON_PATH="/usr/bin/python3"
ENV SCRYPTED_PYTHON312_PATH="/usr/bin/python3.12"
ENV SCRYPTED_DOCKER_FLAVOR="lite"

View File

@@ -1,6 +1,8 @@
ARG BASE="ghcr.io/koush/scrypted-common:20-jammy-full"
ARG BASE="ghcr.io/koush/scrypted-common:noble-nvidia"
FROM $BASE
ENV SCRYPTED_DOCKER_FLAVOR="nvidia"
ENV NVIDIA_DRIVER_CAPABILITIES=all
ENV NVIDIA_VISIBLE_DEVICES=all

View File

@@ -0,0 +1,11 @@
ARG BASE="ghcr.io/koush/scrypted-common:noble-nvidia-legacy"
FROM $BASE
ENV SCRYPTED_DOCKER_FLAVOR="nvidia"
ENV NVIDIA_DRIVER_CAPABILITIES=all
ENV NVIDIA_VISIBLE_DEVICES=all
# nvidia cudnn/libcublas etc.
# for some reason this is not provided by the nvidia container toolkit
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-nvidia-graphics-legacy.sh | bash

View File

@@ -0,0 +1,60 @@
ARG BASE="noble-lite"
FROM ghcr.io/koush/scrypted-common:${BASE}
ENV SCRYPTED_DOCKER_FLAVOR="router"
# tools
RUN apt -y update && apt -y install nano net-tools dnsutils dnsmasq vlan bridge-utils netplan.io nftables isc-dhcp-client cron
RUN rm -f /etc/systemd/system/multi-user.target.wants/dnsmasq.service
RUN rm -f /etc/systemd/system/sysinit.target.wants/systemd-resolved.service
# go + caddy
RUN GO_VERSION=1.25.1 && ARCH=$(dpkg --print-architecture) && \
if [ "$ARCH" = "amd64" ]; then GOARCH="amd64"; \
elif [ "$ARCH" = "arm64" ]; then GOARCH="arm64"; \
elif [ "$ARCH" = "armhf" ]; then GOARCH="armv6l"; \
else echo "Unsupported architecture: $ARCH" && exit 1; fi && \
curl -LO "https://go.dev/dl/go${GO_VERSION}.linux-${GOARCH}.tar.gz" && \
tar -C /usr/local -xzf "go${GO_VERSION}.linux-${GOARCH}.tar.gz" && \
rm "go${GO_VERSION}.linux-${GOARCH}.tar.gz"
ENV PATH=$PATH:/usr/local/go/bin
RUN apt install -y debian-keyring debian-archive-keyring apt-transport-https
RUN curl -1sLf 'https://dl.cloudsmith.io/public/caddy/xcaddy/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-xcaddy-archive-keyring.gpg
RUN curl -1sLf 'https://dl.cloudsmith.io/public/caddy/xcaddy/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-xcaddy.list
RUN apt -y update
RUN apt -y install xcaddy
RUN xcaddy build --with github.com/caddy-dns/cloudflare --output /usr/local/bin/caddy
# nftables
COPY ./router/scrypted-nftables.service /etc/systemd/system
RUN systemctl enable scrypted-nftables
RUN bash -c 'echo include \"/etc/nftables.d/*.conf\"\; > /etc/nftables.conf'
RUN mkdir -p /etc/nftables.d
COPY ./router/01-scrypted.conf /etc/nftables.d
# ipv6 forwarding
COPY ./router/scrypted-ip-forwarding.service /etc/systemd/system
RUN systemctl enable scrypted-ip-forwarding
# install turn server, but disable it too set it up on a per interface basis.
RUN apt -y update && apt -y install coturn && systemctl disable coturn && rm /usr/lib/systemd/system/coturn.service
# install usbmuxd for iphone tethering
# ensure the pairing info stays in persistent storage
RUN apt -y update && apt -y install usbmuxd && rm /usr/lib/systemd/system/usbmuxd.service && ln -sf /server/volume/plugins/\@scrypted/router/usbmuxd /var/lib/lockdown
WORKDIR /
# cache bust
ADD "https://www.random.org/cgi-bin/randbyte?nbytes=10&format=h" skipcache
ARG SCRYPTED_INSTALL_VERSION="latest"
RUN test -n "$SCRYPTED_INSTALL_VERSION"
RUN npx -y scrypted@latest install-server ${SCRYPTED_INSTALL_VERSION}
COPY ./router/scrypted-dhcp-watcher.service /etc/systemd/system/scrypted-dhcp-watcher.service
RUN systemctl enable scrypted-dhcp-watcher
COPY ./router/scrypted.service /etc/systemd/system/scrypted.service
RUN systemctl enable scrypted
WORKDIR /
CMD ["/sbin/init"]

View File

@@ -1,4 +1,4 @@
ARG BASE="20-jammy-full"
ARG BASE="noble-full"
FROM ghcr.io/koush/scrypted-common:${BASE}
# avahi advertiser support
@@ -8,6 +8,9 @@ RUN apt-get update && apt-get -y install \
libavahi-compat-libdnssd-dev \
xz-utils
# killall
RUN apt -y install psmisc
# copy configurations and scripts
COPY fs /

View File

@@ -1,3 +1,3 @@
./docker-build.sh
docker build -t ghcr.io/koush/scrypted:20-jammy-full.nvidia -f Dockerfile.nvidia .
docker build -t ghcr.io/koush/scrypted:nvidia -f Dockerfile.nvidia .

View File

@@ -2,9 +2,9 @@
set -x
NODE_VERSION=20
NODE_VERSION=22
SCRYPTED_INSTALL_VERSION=beta
IMAGE_BASE=jammy
IMAGE_BASE=noble
FLAVOR=full
BASE=$NODE_VERSION-$IMAGE_BASE-$FLAVOR
echo $BASE

View File

@@ -0,0 +1,45 @@
import os
from ruamel.yaml import YAML
# Define the devices to check for
devices_to_check = [
"/dev/dri",
"/dev/accel",
"/dev/apex_0",
"/dev/apex_1",
"/dev/kfd",
"/dev/bus/usb"
]
# Use ruamel.yaml with better formatting preservation
yaml = YAML()
yaml.preserve_quotes = True
# Explicitly set roundtrip mode for comment preservation
yaml.typ = 'rt'
# Match the original formatting - 4 space indentation
yaml.indent = 4
# No special block sequence indentation
yaml.block_seq_indent = 0
# Don't wrap lines
yaml.width = None
# Preserve unicode
yaml.allow_unicode = True
# Read the docker-compose.yml file
with open('docker-compose.yml', 'r') as file:
compose_data = yaml.load(file)
# Get a direct reference to the devices key
scrypted_service = compose_data['services']['scrypted']
devices = scrypted_service.setdefault('devices', [])
# Check for devices and add them if they exist
for device_path in devices_to_check:
if os.path.exists(device_path):
device_mapping = f"{device_path}:{device_path}"
if device_mapping not in devices:
devices.append(device_mapping)
# Write the modified docker-compose.yml file (preserving comments and formatting)
with open('docker-compose.yml', 'w') as file:
yaml.dump(compose_data, file)

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# run as privileged so all the devices can be detected and only the necessary ones passed through.
docker run --rm \
--privileged \
-v "$(pwd):/app" \
-w /app \
python:3.12-slim \
sh -c "pip install -q --root-user-action=ignore ruamel.yaml && python docker-compose-setup.py"

View File

@@ -45,10 +45,14 @@ services:
# - SCRYPTED_DOCKER_AVAHI=true
# NVIDIA (Part 1 of 2)
# runtime: nvidia
# nvidia runtime: nvidia
# NVIDIA (Part 2 of 2) - Use NVIDIA image, and remove subsequent default image.
# image: ghcr.io/koush/scrypted:nvidia
# Valid images:
# ghcr.io/koush/scrypted
# ghcr.io/koush/scrypted:nvidia
# ghcr.io/koush/scrypted:intel
# ghcr.io/koush/scrypted:lite
image: ghcr.io/koush/scrypted
volumes:
@@ -128,6 +132,12 @@ services:
labels:
- "com.centurylinklabs.watchtower.scope=scrypted"
# Use global DNS servers to avoid issues with some local DNS servers.
# particularly with npm registry, etc.
dns:
- ${SCRYPTED_DNS_SERVER_0:-1.1.1.1}
- ${SCRYPTED_DNS_SERVER_1:-8.8.8.8}
# watchtower manages updates for Scrypted.
watchtower:
environment:
@@ -135,7 +145,7 @@ services:
- WATCHTOWER_HTTP_API_UPDATE=true
- WATCHTOWER_SCOPE=scrypted
- WATCHTOWER_HTTP_API_PERIODIC_POLLS=${WATCHTOWER_HTTP_API_PERIODIC_POLLS:-true}
image: containrrr/watchtower
image: nickfedor/watchtower
container_name: scrypted-watchtower
restart: unless-stopped
volumes:
@@ -148,3 +158,11 @@ services:
- 10444:8080
# check for updates once an hour (interval is in seconds)
command: --interval 3600 --cleanup --scope scrypted
# Use global DNS servers to avoid issues with some local DNS servers.
# particularly with npm registry, etc.
dns:
- ${SCRYPTED_DNS_SERVER_0:-1.1.1.1}
- ${SCRYPTED_DNS_SERVER_1:-8.8.8.8}
# LXC usage only
# lxc profiles: ["disabled"]

View File

@@ -21,21 +21,34 @@ else
distro="noble"
fi
apt -y update
apt -y install rsync gpg
# the deb no longer seems to install a key?
gpg --keyserver keyserver.ubuntu.com --recv-keys 9386B48A1A693C5C
gpg --export --armor 9386B48A1A693C5C | tee /etc/apt/trusted.gpg.d/amdgpu.asc
# https://amdgpu-install.readthedocs.io/en/latest/install-prereq.html#installing-the-installer-package
FILENAME=$(curl -s -L https://repo.radeon.com/amdgpu-install/latest/ubuntu/$distro/ | grep -o 'amdgpu-install_[^ ]*' | cut -d'"' -f1)
if [ -z "$FILENAME" ]
then
echo "AMD graphics package can not be installed. Could not find the package name."
exit 1
fi
# AMD keeps breaking these links. Use hard links.
# FILENAME=$(curl -s -L https://repo.radeon.com/amdgpu-install/latest/ubuntu/$distro/ | grep -o 'amdgpu-install_[^ ]*' | cut -d'"' -f1)
# if [ -z "$FILENAME" ]
# then
# echo "AMD graphics package can not be installed. Could not find the package name."
# exit 1
# fi
set -e
mkdir -p /tmp/amd
cd /tmp/amd
curl -O -L http://repo.radeon.com/amdgpu-install/latest/ubuntu/$distro/$FILENAME
apt -y install rsync
# curl -O -L https://repo.radeon.com/amdgpu-install/latest/ubuntu/$distro/$FILENAME
FILENAME=amdgpu-install_7.0.1.70001-1_all.deb
curl -O -L https://repo.radeon.com/amdgpu-install/7.0.1/ubuntu/$distro/$FILENAME
dpkg -i $FILENAME
apt -y update
amdgpu-install --usecase=opencl --no-dkms -y --accept-eula
cd /tmp
rm -rf /tmp/amd

View File

@@ -4,6 +4,25 @@ then
exit 0
fi
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
# needs either ubuntu 22.0.4 or 24.04
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
then
echo "Intel graphics package can not be installed. Ubuntu version could not be detected when checking lsb-release and /etc/os-release."
exit 1
fi
if [ -n "$UBUNTU_22_04" ]
then
distro="jammy"
else
distro="noble"
fi
# no errors beyond this point
set -e
@@ -21,13 +40,26 @@ set -e
# need intel-media-va-driver-non-free, but all the other intel packages are installed from Intel github.
echo "Installing Intel graphics packages."
apt-get update && apt-get install -y gpg-agent &&
rm -f /usr/share/keyrings/intel-graphics.gpg &&
curl -L https://repositories.intel.com/graphics/intel-graphics.key | gpg --dearmor --yes --output /usr/share/keyrings/intel-graphics.gpg &&
echo 'deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu jammy arc' | tee /etc/apt/sources.list.d/intel.gpu.jammy.list &&
apt-get -y update &&
apt-get -y install intel-media-va-driver-non-free &&
apt-get -y dist-upgrade;
if [ "$distro" == "jammy" ]
then
apt-get update && apt-get install -y gpg-agent &&
rm -f /usr/share/keyrings/intel-graphics.gpg &&
curl -L https://repositories.intel.com/graphics/intel-graphics.key | gpg --dearmor --yes --output /usr/share/keyrings/intel-graphics.gpg &&
echo "deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu $distro arc" | tee /etc/apt/sources.list.d/intel.gpu.$distro.list &&
apt-get -y update &&
apt-get -y install intel-media-va-driver-non-free &&
apt-get -y dist-upgrade;
else
apt-get update && apt-get install -y gpg-agent &&
rm -f /usr/share/keyrings/intel-graphics.gpg &&
curl -L https://repositories.intel.com/gpu/intel-graphics.key | gpg --dearmor --yes --output /usr/share/keyrings/intel-graphics.gpg &&
echo "deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/gpu/ubuntu $distro unified" | tee /etc/apt/sources.list.d/intel-gpu-$distro.list &&
apt-get -y update &&
apt-get -y install intel-media-va-driver-non-free &&
apt-get -y dist-upgrade;
fi
rm -rf /tmp/gpu && mkdir -p /tmp/gpu && cd /tmp/gpu
@@ -37,41 +69,50 @@ apt-get install -y ocl-icd-libopencl1
# install 24.35.30872.22 for legacy support. Then install latest.
# https://github.com/intel/compute-runtime/issues/770#issuecomment-2515166915
# https://github.com/intel/compute-runtime/releases/tag/24.35.30872.22
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.20/intel-igc-core_1.0.17537.20_amd64.deb
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.20/intel-igc-opencl_1.0.17537.20_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-dbgsym_1.3.30872.22_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-legacy1-dbgsym_1.3.30872.22_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-legacy1_1.3.30872.22_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu_1.3.30872.22_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-dbgsym_24.35.30872.22_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-legacy1-dbgsym_24.35.30872.22_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-legacy1_24.35.30872.22_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd_24.35.30872.22_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/libigdgmm12_22.5.0_amd64.deb
# original legacy packages
# # https://github.com/intel/compute-runtime/releases/tag/24.35.30872.22
# curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.20/intel-igc-core_1.0.17537.20_amd64.deb
# curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.20/intel-igc-opencl_1.0.17537.20_amd64.deb
# # curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-dbgsym_1.3.30872.22_amd64.ddeb
# # curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-legacy1-dbgsym_1.3.30872.22_amd64.ddeb
# curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-legacy1_1.3.30872.22_amd64.deb
# curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu_1.3.30872.22_amd64.deb
# # curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-dbgsym_24.35.30872.22_amd64.ddeb
# # curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-legacy1-dbgsym_24.35.30872.22_amd64.ddeb
# curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-legacy1_24.35.30872.22_amd64.deb
# curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd_24.35.30872.22_amd64.deb
# curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/libigdgmm12_22.5.0_amd64.deb
# new legacy packages
# https://github.com/intel/compute-runtime/releases/tag/24.35.30872.36
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-level-zero-gpu-legacy1-dbgsym_1.5.30872.36_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-level-zero-gpu-legacy1_1.5.30872.36_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1-dbgsym_24.35.30872.36_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/libigdgmm12_22.5.0_amd64.deb
dpkg -i *.deb
rm -f *.deb
# https://github.com/intel/compute-runtime/releases/tag/24.45.31740.9
# https://github.com/intel/compute-runtime/releases
# note that at time of commit, IGC supports ubuntu 24.04 only possibly due to their builder being on 24.04.
IGC_VERSION=2_2.1.12+18087_amd64
COMPUTE_VERSION=24.45.31740.9
ZERO_GPU_VERSION=1.6.31740.9_amd64
LIBIGDGMM_VERSION=22.5.2_amd64
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v2.1.12/intel-igc-core-$IGC_VERSION.deb
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v2.1.12/intel-igc-opencl-$IGC_VERSION.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-level-zero-gpu-dbgsym_$ZERO_GPU_VERSION.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-level-zero-gpu_$ZERO_GPU_VERSION.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-opencl-icd-dbgsym_"$COMPUTE_VERSION"_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-opencl-icd_"$COMPUTE_VERSION"_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/libigdgmm12_$LIBIGDGMM_VERSION.deb
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v2.18.5/intel-igc-core-2_2.18.5+19820_amd64.deb
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v2.18.5/intel-igc-opencl-2_2.18.5+19820_amd64.deb
# curl -O -L https://github.com/intel/compute-runtime/releases/download/25.35.35096.9/intel-ocloc-dbgsym_25.35.35096.9-0_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/25.35.35096.9/intel-ocloc_25.35.35096.9-0_amd64.deb
# curl -O -L https://github.com/intel/compute-runtime/releases/download/25.35.35096.9/intel-opencl-icd-dbgsym_25.35.35096.9-0_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/25.35.35096.9/intel-opencl-icd_25.35.35096.9-0_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/25.35.35096.9/libigdgmm12_22.8.1_amd64.deb
# curl -O -L https://github.com/intel/compute-runtime/releases/download/25.35.35096.9/libze-intel-gpu1-dbgsym_25.35.35096.9-0_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/25.35.35096.9/libze-intel-gpu1_25.35.35096.9-0_amd64.deb
set +e
dpkg -i *.deb
set -e
# the legacy + latest process says this may be necessary but it does not seem to be in a clean environment.
apt-get install --fix-broken
apt-get -y install --fix-broken
cd /tmp && rm -rf /tmp/gpu

View File

@@ -9,11 +9,17 @@ UBUNTU_24_04=$(lsb_release -r | grep "24.04")
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
then
# proxmox is compatible with ubuntu 22.04, check for /etc/pve directory
if [ -d "/etc/pve" ]
then
# proxmox is compatible with intel's ubuntu builds, check for /etc/pve directory
# then determine debian version
version=$(cat /etc/debian_version 2>/dev/null)
# Determine if it's Debian 12 or 13
if [[ "$version" == 12* ]]; then
UBUNTU_22_04=true
elif [[ "$version" == 13* ]]; then
UBUNTU_24_04=true
fi
fi
# needs either ubuntu 22.0.4 or 24.04
@@ -25,8 +31,10 @@ fi
if [ -n "$UBUNTU_22_04" ]
then
ubuntu_distro=ubuntu2204
distro="22.04_amd64"
else
ubuntu_distro=ubuntu2404
distro="24.04_amd64"
fi
@@ -38,22 +46,24 @@ set -e
rm -rf /tmp/npu && mkdir -p /tmp/npu && cd /tmp/npu
# level zero must also be installed
LEVEL_ZERO_VERSION=1.19.2
LEVEL_ZERO_VERSION=1.24.2
# https://github.com/oneapi-src/level-zero
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v"$LEVEL_ZERO_VERSION"/level-zero_"$LEVEL_ZERO_VERSION"+u$distro.deb
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v"$LEVEL_ZERO_VERSION"/level-zero-devel_"$LEVEL_ZERO_VERSION"+u$distro.deb
# npu driver
# https://github.com/intel/linux-npu-driver
NPU_VERSION=1.10.0
NPU_VERSION_DATE=20241107-11729849322
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-driver-compiler-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
NPU_VERSION=1.23.0
NPU_VERSION_DATE=20250827-17270089246
NPU_TAR_FILENAME=linux-npu-driver-v"$NPU_VERSION"."$NPU_VERSION_DATE"-$ubuntu_distro.tar.gz
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/"$NPU_TAR_FILENAME"
tar xzvf "$NPU_TAR_FILENAME"
# firmware can only be installed on host. will cause problems inside container.
if [ -n "$INTEL_FW_NPU" ]
if [ ! -z "$INTEL_FW_NPU" ]
then
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-fw-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
rm *fw-npu*
fi
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-level-zero-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
apt -y update
apt -y install libtbb12

View File

@@ -0,0 +1,18 @@
if [ "$(uname -m)" = "x86_64" ]
then
apt -y update
apt -y install gpg
# download the key to system keyring
curl -1sLf https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | gpg --dearmor --yes --output /usr/share/keyrings/oneapi-archive-keyring.gpg
# add signed entry to apt sources and configure the APT client to use Intel repository:
echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | tee /etc/apt/sources.list.d/oneAPI.list
apt -y update
apt -y install intel-oneapi-mkl-sycl-blas intel-oneapi-runtime-dnnl intel-oneapi-runtime-compilers
else
echo "NVIDIA graphics will not be installed on this architecture."
fi
exit 0

View File

@@ -1,3 +1,5 @@
apt -y install lsb-release
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
@@ -9,6 +11,16 @@ set -e
# https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=24.04&target_type=deb_network
# Do not apt install nvidia-open, must use cuda-drivers.
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
then
# proxmox is compatible with ubuntu 22.04, check for /etc/pve directory
if [ -d "/etc/pve" ]
then
apt -y install pve-headers-$(uname -r)
UBUNTU_22_04=true
fi
fi
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
then
echo "NVIDIA container toolkit can not be installed. Ubuntu version could not be detected when checking lsb-release and /etc/os-release."
@@ -36,8 +48,16 @@ curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --yes --dea
tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
apt -y update
# is there a way to get a versioned package automatically?
apt -y install cuda-drivers
apt -y install nvidia-container-toolkit
# cuda-drivers does not work with blackwell for some reason, container toolkit it broken IIRC.
apt -y install nvidia-open
nvidia-ctk runtime configure --runtime=docker
systemctl restart docker
if [ ! -d "/etc/pve" ]
then
apt -y install nvidia-container-toolkit
nvidia-ctk runtime configure --runtime=docker
systemctl restart docker
fi
# need this if running inside lxc...
# nvidia-ctk config --set nvidia-container-cli.no-cgroups --in-place

View File

@@ -0,0 +1,54 @@
if [ "$(uname -m)" = "x86_64" ]
then
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
# needs either ubuntu 22.0.4 or 24.04
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
then
echo "NVIDIA graphics package can not be installed. Ubuntu version could not be detected when checking lsb-release and /etc/os-release."
exit 1
fi
if [ -n "$UBUNTU_22_04" ]
then
distro="ubuntu2204"
else
distro="ubuntu2404"
fi
echo "Installing NVIDIA graphics packages."
apt update -q \
&& apt install -y wget \
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$distro/$(uname -m)/cuda-keyring_1.1-1_all.deb \
&& dpkg -i /cuda-keyring.deb \
&& apt update -q \
&& apt install -y cuda-nvcc-12-6 libcublas-12-6 libcudnn9-cuda-12=9.10.2.21-1 cuda-libraries-12-6;
if [ "$?" != "0" ]
then
echo "Error: NVIDIA graphics packages failed to install."
exit 1
fi
# Update: the libnvidia-opencl.so.1 file is not present in the container image, it is
# mounted via the nvidia container runtime. This is why the following check is commented out.
# this file is present but for some reason the icd file is not created by nvidia runtime.
# if [ ! -f "/usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.1" ]
# then
# echo "Error: NVIDIA OpenCL library not found."
# exit 1
# fi
# the container runtime doesn't mount this file for some reason. seems to be a bug.
# https://github.com/NVIDIA/nvidia-container-toolkit/issues/682
# but the contents are simply the .so file, which is a symlink the nvidia runtime
# will mount in.
mkdir -p /etc/OpenCL/vendors/
echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd
else
echo "NVIDIA graphics will not be installed on this architecture."
fi
exit 0

View File

@@ -23,7 +23,7 @@ then
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$distro/$(uname -m)/cuda-keyring_1.1-1_all.deb \
&& dpkg -i /cuda-keyring.deb \
&& apt update -q \
&& apt install -y cuda-nvcc-12-6 libcublas-12-6 libcudnn9-cuda-12 cuda-libraries-12-6;
&& apt install -y cuda-nvcc-12-9 libcublas-12-9 libcudnn9-cuda-12 cuda-libraries-12-9;
if [ "$?" != "0" ]
then

View File

@@ -13,6 +13,8 @@ then
fi
function readyn() {
echo
echo
if [ ! -z "$SCRYPTED_NONINTERACTIVE" ]
then
yn="y"
@@ -51,6 +53,9 @@ rm -rf $SCRYPTED_HOME/install.json
rm -rf $SCRYPTED_HOME/package.json
rm -rf $SCRYPTED_HOME/package-lock.json
# must get this value as grep returns non zero if empty
HAS_NVIDIA=$(lspci | grep -i nvidia)
set -e
cd $SCRYPTED_HOME
@@ -93,6 +98,24 @@ else
sudo apt -y purge apparmor || true
fi
if [ ! -z "$HAS_NVIDIA" ]
then
readyn "NVIDIA GPU detected. Use NVIDIA image for GPU acceleration?"
if [ "$yn" == "y" ]
then
readyn "NVIDIA image requires the NVIDIA Drivers and Container Toolkit to be installed. This script can install them for you. Install NVIDIA Drivers and Container Toolkit for GPU acceleration?"
if [ "$yn" == "y" ]
then
curl -fsSL https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-nvidia-container-toolkit.sh -o install-nvidia-container-toolkit.sh
chmod +x install-nvidia-container-toolkit.sh
./install-nvidia-container-toolkit.sh
rm install-nvidia-container-toolkit.sh
fi
sed -i 's/'#' nvidia //g' $DOCKER_COMPOSE_YML
sed -i 's/ghcr.io\/koush\/scrypted/ghcr.io\/koush\/scrypted:nvidia/g' $DOCKER_COMPOSE_YML
fi
fi
readyn "Install avahi-daemon? This is the recommended for reliable HomeKit discovery and pairing."
if [ "$yn" == "y" ]
then
@@ -163,4 +186,4 @@ echo
echo
echo "Optional:"
echo "Scrypted NVR Recording storage directory can be configured with an additional script located at:"
echo "https://docs.scrypted.app/scrypted-nvr/recording-storage.html#docker-volume"
echo "https://docs.scrypted.app/scrypted-nvr/storage/docker.html"

View File

@@ -0,0 +1,55 @@
table ip nat {
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
jump postrouting_scrypted
}
chain postrouting_scrypted {
}
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
jump prerouting_scrypted;
}
chain prerouting_scrypted {
}
}
table ip filter {
chain FORWARD {
type filter hook forward priority filter; policy drop;
jump forward_scrypted
}
chain forward_scrypted {
}
}
table ip6 nat {
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
jump postrouting_scrypted
}
chain postrouting_scrypted {
}
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
jump prerouting_scrypted;
}
chain prerouting_scrypted {
}
}
table ip6 filter {
chain FORWARD {
type filter hook forward priority filter; policy drop;
jump forward_scrypted
}
chain forward_scrypted {
}
}

View File

@@ -0,0 +1,11 @@
[Unit]
Description=Scrypted DHCP Watcher
After=network.target
[Service]
ExecStart=/etc/dhcp/scrypted-dhcp-watcher
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,17 @@
[Unit]
Description=ipv6 forwarding
After=network.target
Conflicts=shutdown.target
DefaultDependencies=no
[Service]
Type=oneshot
RemainAfterExit=yes
StandardInput=null
ProtectSystem=full
ProtectHome=true
ExecStart=bash -c "sysctl -w net.ipv4.ip_forward=1 && sysctl -w net.ipv6.conf.all.forwarding=1 && sysctl -w net.ipv6.conf.default.forwarding=1"
ExecReload=bash -c "sysctl -w net.ipv4.ip_forward=1 && sysctl -w net.ipv6.conf.all.forwarding=1 && sysctl -w net.ipv6.conf.default.forwarding=1"
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,18 @@
[Unit]
Description=nftables
Documentation=man:nft(8) http://wiki.nftables.org
After=network.target
Conflicts=shutdown.target
DefaultDependencies=no
[Service]
Type=oneshot
RemainAfterExit=yes
StandardInput=null
ProtectSystem=full
ProtectHome=true
ExecStart=/usr/sbin/nft -f /etc/nftables.conf
ExecReload=/usr/sbin/nft -f /etc/nftables.conf
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,24 @@
[Unit]
Description=Scrypted
After=network.target
[Service]
WorkingDirectory=/server
ExecStart=/bin/sh -c "ulimit -c 0; exec npm exec scrypted-serve"
Restart=always
RestartSec=5
Environment="SCRYPTED_INSTALL_ENVIRONMENT=docker"
Environment="SCRYPTED_CAN_RESTART=true"
Environment="SCRYPTED_VOLUME=/server/volume"
Environment="SCRYPTED_INSTALL_PATH=/server"
Environment="SCRYPTED_PYTHON_PATH=/usr/bin/python3"
Environment="SCRYPTED_PYTHON312_PATH=/usr/bin/python3.12"
Environment="SCRYPTED_DOCKER_FLAVOR=lite"
Environment="DEBIAN_FRONTEND=noninteractive"
Environment="NODE_OPTIONS=--dns-result-order=ipv4first"
Environment="SCRYPTED_BASE_VERSION=20250101"
Environment="SHELL=/bin/bash"
[Install]
WantedBy=multi-user.target

View File

@@ -1,7 +1,7 @@
################################################################
# Begin section generated from template/Dockerfile.full.footer
################################################################
FROM header as base
FROM header AS base
# vulkan
RUN apt -y install libvulkan1
@@ -9,12 +9,9 @@ RUN apt -y install libvulkan1
# intel opencl for openvino
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
# NPU driver will SIGILL on openvino prior to 2024.5.0
# intel NPU
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
# amd opencl
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-amd-graphics.sh | bash
# python 3.9 from ppa.
# 3.9 is the version with prebuilt support for tensorflow lite
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
@@ -32,7 +29,7 @@ RUN python3.9 -m pip install debugpy
# Coral Edge TPU
# https://coral.ai/docs/accelerator/get-started/#runtime-on-linux
RUN echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | tee /etc/apt/sources.list.d/coral-edgetpu.list
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
RUN curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /etc/apt/trusted.gpg.d/coral-edgetpu.gpg
RUN apt-get -y update && apt-get -y install libedgetpu1-std
# set default shell to bash

View File

@@ -3,8 +3,8 @@
# This common file will be used by both Docker and the linux
# install script.
################################################################
ARG BASE="jammy"
FROM ubuntu:${BASE} as header
ARG BASE="noble"
FROM ubuntu:${BASE} AS header
ENV DEBIAN_FRONTEND=noninteractive
@@ -16,7 +16,7 @@ RUN apt-get update && apt-get -y install \
apt-get -y update && \
apt-get -y upgrade
ARG NODE_VERSION=20
ARG NODE_VERSION=22
RUN apt-get install -y ca-certificates curl gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
@@ -32,19 +32,6 @@ RUN apt-get -y install \
python3-setuptools \
python3-wheel
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
RUN echo "Installing gstreamer."
# python-codecs pygobject dependencies
RUN apt-get -y install libcairo2-dev libgirepository1.0-dev
RUN apt-get -y install \
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav \
gstreamer1.0-vaapi
# python3 gstreamer bindings
RUN echo "Installing gstreamer bindings."
RUN apt-get -y install \
python3-gst-1.0
# allow pip to install to system
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED

View File

@@ -39,15 +39,13 @@ launchctl unload ~/Library/LaunchAgents/app.scrypted.server.plist || echo ""
echo "Installing Scrypted dependencies..."
RUN_IGNORE xcode-select --install
RUN brew update
RUN_IGNORE brew install node@20
# dlib
RUN brew install cmake
# seems to be necessary for python-codecs' pycairo dependency or something?
RUN_IGNORE gobject-introspection libffi pkg-config
# gstreamer plugins
RUN_IGNORE brew install gstreamer
# in sequoia, brew node is unusable because it is not signed and can't access local network unless run as root.
# https://developer.apple.com/forums/thread/766270
RUN_IGNORE curl -L https://nodejs.org/dist/v22.14.0/node-v22.14.0.pkg -o /tmp/node.pkg
RUN_IGNORE sudo installer -pkg /tmp/node.pkg -target /
NODE_PATH=/usr/local # used to pass var test
NODE_BIN_PATH=/usr/local/bin
ARCH=$(arch)
if [ "$ARCH" = "arm64" ]
@@ -82,17 +80,15 @@ echo "Installing Scrypted Launch Agent..."
RUN mkdir -p ~/Library/LaunchAgents
NODE_PATH=$(brew --prefix node@20)
if [ ! -d "$NODE_PATH" ]
then
echo "Unable to determine node@20 path."
echo "Unable to determine node path."
exit 1
fi
NODE_BIN_PATH=$NODE_PATH/bin
if [ ! -d "$NODE_BIN_PATH" ]
then
echo "Unable to determine node@20 bin path."
echo "Unable to determine node bin path."
echo "$NODE_BIN_PATH does not exist."
exit 1
fi
@@ -155,7 +151,7 @@ cat > ~/Library/LaunchAgents/app.scrypted.server.plist <<EOT
<key>NODE_OPTIONS</key>
<string>$NODE_OPTIONS</string>
<key>PATH</key>
<string>$NODE_BIN_PATH:$PYTHON_BIN_PATH:$BREW_PREFIX/bin:$BREW_PREFIX/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<string>$PYTHON_BIN_PATH:$NODE_BIN_PATH:$BREW_PREFIX/bin:$BREW_PREFIX/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>HOME</key>
<string>/Users/$USER</string>
<key>SCRYPTED_PYTHON_PATH</key>

View File

@@ -1,4 +1,13 @@
#Requires -RunAsAdministrator
# Check if the script is running as administrator
$IsAdmin = [System.Security.Principal.WindowsPrincipal] [System.Security.Principal.WindowsIdentity]::GetCurrent()
$AdminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator
if (-not $IsAdmin.IsInRole($AdminRole)) {
# If not, relaunch the script with elevated privileges
$ScriptPath = $PSCommandPath
Start-Process powershell -ArgumentList "-File `"$ScriptPath`"" -Verb RunAs
exit
}
# Set-PSDebug -Trace 1
@@ -10,7 +19,7 @@ sc.exe stop scrypted.exe
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
# Install node.js
choco upgrade -y nodejs-lts --version=20.18.0
choco upgrade -y nodejs-lts --version=22.21.0
# Install VC Redist, which is necessary for portable python
choco install -y vcredist140
@@ -25,7 +34,7 @@ $SCRYPTED_WINDOWS_PYTHON_VERSION="-3.9"
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
# Workaround Windows Node no longer creating %APPDATA%\npm which causes npx to fail
# Fixed in newer versions of NPM but not the one bundled with Node 20
# Fixed in newer versions of NPM but not the one bundled with Node 2x
# https://github.com/nodejs/node/issues/53538
npm i -g npm

View File

@@ -4,15 +4,37 @@ cd /root/.scrypted
# always immediately upgrade everything in case there's a broken update.
# this will also be preferable for troubleshooting via lxc reboot.
export DEBIAN_FRONTEND=noninteractive
yes | dpkg --configure -a
apt -y --fix-broken install && apt -y update && apt -y dist-upgrade
# auto updates may break the system?
# watchtower stopped working after a docker update, so disabling for now.
# yes | dpkg --configure -a
# apt -y --fix-broken install && apt -y update && apt -y dist-upgrade
function cleanup() {
IS_UP=$(docker compose ps scrypted -a | grep Up)
# Only clean up when scrypted is running to safely free space without risking its image deletion
if [ -z "$IS_UP" ]; then
echo "scrypted is not running, skipping cleanup to preserve its image"
return
fi
echo $(date) > .last_cleanup
echo "scrypted is running, proceeding with cleanup to free space"
docker container prune -f
docker image prune -a -f
}
# force a pull to ensure we have the latest images.
# not using --pull always cause that fails everything on network down
docker compose pull
(sleep 60 && cleanup) &
# do not daemonize, when it exits, systemd will restart it.
# force a recreate as .env may have changed.
# furthermore force recreate gets the container back into a known state
# which is preferable in case the user has made manual changes and then restarts.
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum | head -c 32) docker compose up --force-recreate --abort-on-container-exit
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum | head -c 32) docker compose up --force-recreate
# abort on container exit is problematic if watchtower is the one that aborts.
# this is also redundant now that watchtower is disabled.
# WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum | head -c 32) docker compose up --force-recreate --abort-on-container-exit

View File

@@ -18,7 +18,10 @@ function readyn() {
}
cd /tmp
SCRYPTED_VERSION=v0.120.0
if [ -z "$SCRYPTED_VERSION" ]
then
SCRYPTED_VERSION=v0.143.0
fi
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
if [ -z "$VMID" ]
then
@@ -139,7 +142,12 @@ then
echo "#############################################################################"
echo -e "\033[32mPaste the following command into this shell to install to local-lvm instead:\033[0m"
echo ""
echo "bash $0 --storage local-lvm"
if [ -n "$SCRYPTED_RESTORE" ]
then
echo "bash $0 --storage local-lvm"
else
echo "SCRYPTED_RESTORE=true bash $0 --storage local-lvm"
fi
echo "#############################################################################"
echo ""
echo ""

View File

@@ -27,7 +27,8 @@ async function getAuth(options: AuthFetchOptions, url: string | URL, method: str
++credential.count;
const nc = ('00000000' + credential.count).slice(-8);
const cnonce = [...Array(24)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
const uri = new URL(url).pathname;
const parsedURL = new URL(url);
const uri = parsedURL.pathname + parsedURL.search;
const { DIGEST, buildAuthorizationHeader } = await import('http-auth-utils');

View File

@@ -9,23 +9,23 @@
"version": "1.3.20",
"license": "ISC",
"dependencies": {
"@scrypted/client": "^1.3.3",
"@scrypted/types": "^0.3.30",
"engine.io-client": "^6.5.3",
"@scrypted/client": "^1.3.13",
"@scrypted/types": "^0.5.9",
"engine.io-client": "^6.6.3",
"readline-sync": "^1.4.10",
"semver": "^7.5.4",
"tslib": "^2.6.2"
"semver": "^7.7.1",
"tslib": "^2.8.1"
},
"bin": {
"scrypted": "dist/packages/cli/src/main.js"
},
"devDependencies": {
"@types/node": "^20.9.4",
"@types/node": "^22.13.10",
"@types/readline-sync": "^1.4.8",
"@types/semver": "^7.5.6",
"rimraf": "^5.0.5",
"ts-node": "^10.9.1",
"typescript": "^5.3.2"
"@types/semver": "^7.5.8",
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
},
"node_modules/@cspotcode/source-map-support": {
@@ -44,6 +44,7 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
@@ -81,30 +82,24 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@scrypted/client": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@scrypted/client/-/client-1.3.3.tgz",
"integrity": "sha512-Wuy7x02TCRy1buaDNX8NOIaL1j4ZXu4dqTTJsKHlPe3+umsBvpwbylD+YyyU8ghQJC6a40Bs5UMsvnCvNa/1fg==",
"version": "1.3.13",
"resolved": "https://registry.npmjs.org/@scrypted/client/-/client-1.3.13.tgz",
"integrity": "sha512-jxXnGCoHIwuB7PobPJyqYy9THljR2UJOILxO++HiNq/i0nqRUECYvVfU5frN/ZnP6nmQoiRKrl8ErGWVBT7ecg==",
"license": "ISC",
"dependencies": {
"@scrypted/types": "^0.3.4",
"engine.io-client": "^6.5.3",
"follow-redirects": "^1.15.4",
"rimraf": "^5.0.5"
"engine.io-client": "^6.6.3",
"follow-redirects": "^1.15.9",
"rimraf": "^6.0.1"
},
"peerDependencies": {
"@scrypted/types": "^0.5.9"
}
},
"node_modules/@scrypted/types": {
"version": "0.3.30",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.30.tgz",
"integrity": "sha512-1k+JVSR6WSNmE/5mLdqfrTmV3uRbvZp0OwKb8ikNi39ysBuC000tQGcEdXZqhYqRgWdhDTWtxXe9XsYoAZGKmA==",
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.5.9.tgz",
"integrity": "sha512-Qt/gLdzDqYwgOArpLrEErPb91mhAOmN0NFFOFwX9G/5vSV5Xvm6ixQhPWF4f+up3G9ecPVPMPtZCsWmhxAD1hA==",
"license": "ISC"
},
"node_modules/@socket.io/component-emitter": {
@@ -137,12 +132,13 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.9.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.4.tgz",
"integrity": "sha512-wmyg8HUhcn6ACjsn8oKYjkN/zUzQeNtMy44weTJSM6p4MMzEOuKbA3OjJ267uPCOW7Xex9dyrNTful8XTQYoDA==",
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
"undici-types": "~6.20.0"
}
},
"node_modules/@types/readline-sync": {
@@ -152,10 +148,11 @@
"dev": true
},
"node_modules/@types/semver": {
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
"dev": true
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true,
"license": "MIT"
},
"node_modules/acorn": {
"version": "8.8.2",
@@ -179,9 +176,10 @@
}
},
"node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"license": "MIT",
"engines": {
"node": ">=12"
},
@@ -193,6 +191,7 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"license": "MIT",
"engines": {
"node": ">=12"
},
@@ -209,12 +208,14 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -223,6 +224,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
@@ -233,7 +235,8 @@
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/create-require": {
"version": "1.1.1",
@@ -242,9 +245,10 @@
"dev": true
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -282,23 +286,26 @@
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"node_modules/engine.io-client": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
"integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.11.0",
"xmlhttprequest-ssl": "~2.0.0"
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-parser": {
@@ -310,15 +317,16 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
@@ -329,11 +337,12 @@
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.0",
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
@@ -344,21 +353,23 @@
}
},
"node_modules/glob": {
"version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz",
"integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^2.3.5",
"minimatch": "^9.0.1",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
"path-scurry": "^1.10.1"
"jackspeak": "^4.0.1",
"minimatch": "^10.0.0",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.17"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -368,6 +379,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -375,34 +387,31 @@
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/jackspeak": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz",
"integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": ">=14"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dependencies": {
"yallist": "^4.0.0"
},
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz",
"integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==",
"license": "ISC",
"engines": {
"node": ">=10"
"node": "20 || >=22"
}
},
"node_modules/make-error": {
@@ -412,23 +421,25 @@
"dev": true
},
"node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
@@ -438,37 +449,37 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^9.1.1 || ^10.0.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.3.tgz",
"integrity": "sha512-B7gr+F6MkqB3uzINHXNctGieGsRTMwIBgxkp0yq/5BwcuDzD4A8wQpHQW6vDAm1uKSLQghmRdD9sKqf2vJ1cEg==",
"engines": {
"node": "14 || >=16.14"
}
},
"node_modules/readline-sync": {
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz",
@@ -478,29 +489,29 @@
}
},
"node_modules/rimraf": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz",
"integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
"integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
"license": "ISC",
"dependencies": {
"glob": "^10.3.7"
"glob": "^11.0.0",
"package-json-from-dist": "^1.0.0"
},
"bin": {
"rimraf": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=14"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dependencies": {
"lru-cache": "^6.0.0"
},
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
@@ -512,6 +523,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
@@ -523,6 +535,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -531,6 +544,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"license": "ISC",
"engines": {
"node": ">=14"
},
@@ -542,6 +556,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
@@ -559,6 +574,7 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -572,6 +588,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -579,12 +596,14 @@
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -596,6 +615,7 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
@@ -611,6 +631,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -622,15 +643,17 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -670,15 +693,17 @@
}
},
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/typescript": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
"integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -688,10 +713,11 @@
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT"
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
@@ -703,6 +729,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
@@ -717,6 +744,7 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
@@ -734,6 +762,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@@ -750,6 +779,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -758,6 +788,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -771,12 +802,14 @@
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -790,6 +823,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -798,15 +832,16 @@
}
},
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
@@ -818,18 +853,13 @@
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",

View File

@@ -16,19 +16,19 @@
"author": "",
"license": "ISC",
"dependencies": {
"@scrypted/client": "^1.3.3",
"@scrypted/types": "^0.3.30",
"engine.io-client": "^6.5.3",
"@scrypted/client": "^1.3.13",
"@scrypted/types": "^0.5.9",
"engine.io-client": "^6.6.3",
"readline-sync": "^1.4.10",
"semver": "^7.5.4",
"tslib": "^2.6.2"
"semver": "^7.7.1",
"tslib": "^2.8.1"
},
"devDependencies": {
"@types/node": "^20.9.4",
"@types/node": "^22.13.10",
"@types/readline-sync": "^1.4.8",
"@types/semver": "^7.5.6",
"rimraf": "^5.0.5",
"ts-node": "^10.9.1",
"typescript": "^5.3.2"
"@types/semver": "^7.5.8",
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
}

View File

@@ -166,8 +166,6 @@ async function main() {
}
}
const args = ffmpegInput.inputArguments ? [...ffmpegInput.inputArguments] : [];
if (ffmpegInput.h264FilterArguments)
args.push(...ffmpegInput.h264FilterArguments);
console.log('ffplay', ...args);
child_process.spawn('ffplay', args, {
stdio: 'inherit',

View File

@@ -7,7 +7,7 @@ export async function connectShell(sdk: ScryptedStatic, ...cmd: string[]) {
throw Error("@scrypted/core does not provide a Terminal Service");
}
const termSvcDirect = await sdk.connectRPCObject<StreamService>(termSvc);
const termSvcDirect = await sdk.connectRPCObject<StreamService<Buffer|string, Buffer>>(termSvc);
const dataQueue = createAsyncQueue<Buffer>();
const ctrlQueue = createAsyncQueue<any>();

View File

@@ -1,25 +1,27 @@
{
"name": "@scrypted/client",
"version": "1.3.10",
"version": "1.3.26",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/client",
"version": "1.3.10",
"version": "1.3.26",
"license": "ISC",
"dependencies": {
"@scrypted/types": "^0.3.100",
"engine.io-client": "^6.6.2",
"engine.io-client": "^6.6.3",
"follow-redirects": "^1.15.9",
"rimraf": "^6.0.1"
},
"devDependencies": {
"@types/ip": "^1.1.3",
"@types/node": "^22.10.7",
"@types/ws": "^8.5.13",
"@types/node": "^24.0.10",
"@types/ws": "^8.18.1",
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
"typescript": "^5.8.3"
},
"peerDependencies": {
"@scrypted/types": "^0.5.52"
}
},
"node_modules/@cspotcode/source-map-support": {
@@ -27,6 +29,7 @@
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
@@ -34,10 +37,32 @@
"node": ">=12"
}
},
"node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
"license": "MIT",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/brace-expansion": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"license": "MIT",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
@@ -51,88 +76,101 @@
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@scrypted/types": {
"version": "0.3.100",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.100.tgz",
"integrity": "sha512-s/07QCxjMWqODgWj2UpLehzeo2cGFrCA9X8mvpG3owT/+q+sb8v/UUcw9TLHGSN6yIriNhceg3i9WO07kEIT6A==",
"license": "ISC"
"version": "0.5.52",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.5.52.tgz",
"integrity": "sha512-c1ra1ENnoC8MqVHf7QQcXIU+5BvQnhU4x5oqx4b20LtoB0/TTXthYFFvEDBvLenBivUr8Bb6dWrji7TZXVax1g==",
"license": "ISC",
"peer": true,
"dependencies": {
"openai": "^6.1.0"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
"dev": true
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/@types/ip": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@types/ip/-/ip-1.1.3.tgz",
"integrity": "sha512-64waoJgkXFTYnCYDUWgSATJ/dXEBanVkaP5d4Sbk7P6U7cTTMhxVyROTckc6JKdwCrgnAjZMn0k3177aQxtDEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "22.10.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
"integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
"version": "24.5.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.1.tgz",
"integrity": "sha512-/SQdmUP2xa+1rdx7VwB9yPq8PaKej8TD5cQ+XfKDPWWC+VDJU4rvVVagXqKUzhKjtFoNA8rXDJAkCxQPAe00+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
"undici-types": "~7.12.0"
}
},
"node_modules/@types/ws": {
"version": "8.5.13",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
"integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -140,10 +178,11 @@
}
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
@@ -152,18 +191,23 @@
}
},
"node_modules/acorn-walk": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz",
"integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==",
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
@@ -172,9 +216,10 @@
}
},
"node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
@@ -186,25 +231,14 @@
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dependencies": {
"balanced-match": "^1.0.0"
}
"dev": true,
"license": "MIT"
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
@@ -215,13 +249,15 @@
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
@@ -238,11 +274,12 @@
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "2.1.2"
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@@ -258,6 +295,7 @@
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
@@ -265,17 +303,19 @@
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"node_modules/engine.io-client": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz",
"integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==",
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
@@ -285,24 +325,47 @@
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-client/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/engine.io-parser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
"integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==",
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
@@ -313,11 +376,12 @@
}
},
"node_modules/foreground-child": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.0",
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
@@ -328,13 +392,14 @@
}
},
"node_modules/glob": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz",
"integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==",
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^4.0.1",
"minimatch": "^10.0.0",
"foreground-child": "^3.3.1",
"jackspeak": "^4.1.1",
"minimatch": "^10.0.3",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
@@ -353,6 +418,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -360,12 +426,14 @@
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/jackspeak": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz",
"integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
@@ -377,9 +445,10 @@
}
},
"node_modules/lru-cache": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz",
"integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==",
"version": "11.2.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz",
"integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==",
"license": "ISC",
"engines": {
"node": "20 || >=22"
}
@@ -388,14 +457,16 @@
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
"dev": true,
"license": "ISC"
},
"node_modules/minimatch": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
"@isaacs/brace-expansion": "^5.0.0"
},
"engines": {
"node": "20 || >=22"
@@ -408,24 +479,50 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/openai": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-6.1.0.tgz",
"integrity": "sha512-5sqb1wK67HoVgGlsPwcH2bUbkg66nnoIYKoyV9zi5pZPqh7EWlmSrSDjAh4O5jaIg/0rIlcDKBtWvZBuacmGZg==",
"license": "Apache-2.0",
"peer": true,
"bin": {
"openai": "bin/cli"
},
"peerDependencies": {
"ws": "^8.18.0",
"zod": "^3.25 || ^4.0"
},
"peerDependenciesMeta": {
"ws": {
"optional": true
},
"zod": {
"optional": true
}
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -434,6 +531,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
@@ -449,6 +547,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
"integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
"license": "ISC",
"dependencies": {
"glob": "^11.0.0",
"package-json-from-dist": "^1.0.0"
@@ -467,6 +566,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
@@ -478,6 +578,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -486,6 +587,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"license": "ISC",
"engines": {
"node": ">=14"
},
@@ -497,6 +599,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
@@ -514,6 +617,7 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -527,6 +631,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -534,12 +639,14 @@
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -548,9 +655,10 @@
}
},
"node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
@@ -566,6 +674,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -577,6 +686,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -586,6 +696,7 @@
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -625,9 +736,9 @@
}
},
"node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -639,9 +750,9 @@
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"version": "7.12.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
"dev": true,
"license": "MIT"
},
@@ -649,12 +760,14 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
@@ -669,6 +782,7 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
@@ -686,6 +800,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@@ -702,6 +817,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -710,6 +826,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -723,12 +840,14 @@
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -742,6 +861,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -749,30 +869,10 @@
"node": ">=8"
}
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
"integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
@@ -782,6 +882,7 @@
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/client",
"version": "1.3.10",
"version": "1.3.26",
"description": "",
"main": "dist/packages/client/src/index.js",
"scripts": {
@@ -13,14 +13,16 @@
"license": "ISC",
"devDependencies": {
"@types/ip": "^1.1.3",
"@types/node": "^22.10.7",
"@types/ws": "^8.5.13",
"@types/node": "^24.0.10",
"@types/ws": "^8.18.1",
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
"typescript": "^5.8.3"
},
"peerDependencies": {
"@scrypted/types": "^0.5.52"
},
"dependencies": {
"@scrypted/types": "^0.3.100",
"engine.io-client": "^6.6.2",
"engine.io-client": "^6.6.3",
"follow-redirects": "^1.15.9",
"rimraf": "^6.0.1"
}

View File

@@ -1,21 +1,19 @@
import { MediaObjectCreateOptions, RTCConnectionManagement, RTCSignalingSession, ScryptedStatic } from "@scrypted/types";
import { ConnectRPCObjectOptions, ForkOptions, ForkWorker, MediaObjectCreateOptions, PluginFork, ScryptedInterface, ScryptedInterfaceProperty, ScryptedStatic } from "@scrypted/types";
import * as eio from 'engine.io-client';
import { SocketOptions } from 'engine.io-client';
import { Deferred } from "../../../common/src/deferred";
import { timeoutPromise } from "../../../common/src/promise-utils";
import { BrowserSignalingSession, waitPeerConnectionIceConnected, waitPeerIceConnectionClosed } from "../../../common/src/rtc-signaling";
import { DataChannelDebouncer } from "../../../plugins/webrtc/src/datachannel-debouncer";
import type { ClusterObject, ConnectRPCObject } from '../../../server/src/cluster/connect-rpc-object';
import { domFetch } from "../../../server/src/fetch";
import { httpFetch } from '../../../server/src/fetch/http-fetch';
import type { IOSocket } from '../../../server/src/io';
import { MediaObject } from '../../../server/src/plugin/mediaobject';
import { PluginAPIProxy, PluginRemote } from "../../../server/src/plugin/plugin-api";
import { attachPluginRemote } from '../../../server/src/plugin/plugin-remote';
import { RpcPeer } from '../../../server/src/rpc';
import { createRpcDuplexSerializer, createRpcSerializer } from '../../../server/src/rpc-serializer';
import packageJson from '../package.json';
import { isIPAddress } from "./ip";
import { domFetch } from "../../../server/src/fetch";
import { httpFetch } from '../../../server/src/fetch/http-fetch';
export * as rpc from '../../../server/src/rpc';
export * as rpc_serializer from '../../../server/src/rpc-serializer';
let fetcher: typeof httpFetch | typeof domFetch;
try {
@@ -33,6 +31,15 @@ const sourcePeerId = RpcPeer.generateId();
type IOClientSocket = eio.Socket & IOSocket;
interface InternalFork extends Pick<ScryptedClientStatic, 'loginResult' | 'username' | 'address' | 'connectionType'> {
extraHeaders: {
[header: string]: string,
};
transports?: string[] | undefined;
clientName?: string;
admin: boolean;
};
function once(socket: IOClientSocket, event: 'open' | 'message') {
return new Promise<any[]>((resolve, reject) => {
const err = (e: any) => {
@@ -52,7 +59,13 @@ function once(socket: IOClientSocket, event: 'open' | 'message') {
});
}
export type ScryptedClientConnectionType = 'http' | 'webrtc' | 'http-direct';
/**
* The type of connection used by the Scrypted client.
* http-cloud is through Scrypted Cloud
* http-direct is a direct connection to the Scrypted server via one of the local network interfaces or public IP addresses.
* http is a direct connection with the base url or browser url.
*/
export type ScryptedClientConnectionType = 'http-cloud' | 'http-direct' | 'http';
export interface ScryptedClientStatic extends ScryptedStatic {
userId?: string;
@@ -60,18 +73,16 @@ export interface ScryptedClientStatic extends ScryptedStatic {
admin: boolean;
disconnect(): void;
onClose?: Function;
rtcConnectionManagement?: RTCConnectionManagement;
browserSignalingSession?: BrowserSignalingSession;
address?: string;
connectionType: ScryptedClientConnectionType;
rpcPeer: RpcPeer;
loginResult: ScryptedClientLoginResult;
fork<T>(options: ForkOptions & { worker: Worker }): PluginFork<T>;
}
export interface ScryptedConnectionOptions {
direct?: boolean;
local?: boolean;
webrtc?: boolean;
baseUrl?: string;
previousLoginResult?: ScryptedClientLoginResult;
}
@@ -112,19 +123,59 @@ export async function logoutScryptedClient(baseUrl?: string) {
return response.body;
}
export function getCurrentBaseUrl() {
// an endpoint within scrypted will be served at /endpoint/[org/][id]
// find the endpoint prefix and anything prior to that will be the server base url.
const url = new URL(window.location.href);
url.search = '';
url.hash = '';
let endpointPath = window.location.pathname;
const parts = endpointPath.split('/');
const index = parts.findIndex(p => p === 'endpoint');
if (index === -1) {
// console.warn('path not recognized, does not contain the segment "endpoint".')
return undefined;
function getBaseUrl(href: string) {
try {
// an endpoint within scrypted will be served at /endpoint/[org/][id]
// find the endpoint prefix and anything prior to that will be the server base url.
const url = new URL(href);
url.search = '';
url.hash = '';
let endpointPath = url.pathname;
const parts = endpointPath.split('/');
const index = parts.findIndex(p => p === 'endpoint');
if (index === -1) {
// console.warn('path not recognized, does not contain the segment "endpoint".')
return;
}
return { url, parts, index };
}
catch (e) {
}
}
function importMetaUrlWithoutAssetsPath() {
// @ts-ignore
const url = new URL(import.meta.url);
const parts = url.pathname.split('/');
parts.pop();
parts.pop();
parts.push('public')
parts.push('');
url.pathname = parts.join('/');
return url.toString();
}
export function getCurrentBaseUrlRaw() {
const url = getBaseUrl(window.location.href)
|| getBaseUrl(document.baseURI)
|| getBaseUrl(importMetaUrlWithoutAssetsPath());
if (!url) {
try {
return getBaseUrl(process.env.SCRYPTED_ENDPOINT_PATH);
}
catch (e) {
}
}
return url;
}
export function getCurrentBaseUrl() {
const s = getCurrentBaseUrlRaw();
if (!s) {
return;
}
const { url, parts, index } = s;
const keep = parts.slice(0, index);
keep.push('');
url.pathname = keep.join('/');
@@ -385,11 +436,10 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
const eioPath = `endpoint/${pluginId}/engine.io/api`;
const eioEndpoint = baseUrl ? new URL(eioPath, baseUrl).pathname : '/' + eioPath;
// https://github.com/socketio/engine.io/issues/690
const cacehBust = Math.random().toString(36).substring(3, 10);
const eioOptions: Partial<SocketOptions> = {
const eioOptions: eio.SocketOptions = {
path: eioEndpoint,
query: {
cacehBust,
cacheBust: Math.random().toString(36).substring(3, 10),
},
withCredentials: true,
extraHeaders,
@@ -399,10 +449,6 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
const explicitBaseUrl = baseUrl || `${globalThis.location.protocol}//${globalThis.location.host}`;
// underlying webrtc rpc transport may queue up messages before its ready to be to be handled.
// watch for this flush.
const flush = new Deferred<void>();
const addresses: string[] = [];
const localAddressDefault = isNotChromeOrIsInstalledApp;
@@ -426,28 +472,12 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
}
const tryAddresses = !!addresses.length;
const webrtcLastFailedKey = 'webrtcLastFailed';
const canUseWebrtc = !!globalThis.RTCPeerConnection;
let tryWebrtc = canUseWebrtc && options.webrtc;
// try webrtc by default on scrypted cloud.
// but webrtc takes a while to fail, so backoff if it fails to prevent continual slow starts.
if (scryptedCloud && canUseWebrtc && globalThis.localStorage && options.webrtc === undefined) {
tryWebrtc = true;
const webrtcLastFailed = parseFloat(localStorage.getItem(webrtcLastFailedKey));
// if webrtc has failed in the past day, dont attempt to use it.
const now = Date.now();
if (webrtcLastFailed < now && webrtcLastFailed > now - 1 * 24 * 60 * 60 * 1000) {
tryWebrtc = false;
console.warn('WebRTC API connection recently failed. Skipping.')
}
}
console.log({
tryLocalAddressess: tryAddresses,
tryWebrtc,
});
const localEioOptions: Partial<SocketOptions> = {
const localEioOptions: eio.SocketOptions = {
...eioOptions,
extraHeaders: {
...eioOptions.extraHeaders,
@@ -484,145 +514,11 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
}
}
if (tryWebrtc) {
console.log('trying webrtc');
const webrtcEioOptions: Partial<SocketOptions> = {
path: '/endpoint/@scrypted/webrtc/engine.io/',
query: {
cacehBust,
},
withCredentials: true,
extraHeaders,
rejectUnauthorized: false,
transports: options?.transports,
};
const check = new eio.Socket(explicitBaseUrl, webrtcEioOptions) as IOClientSocket;
sockets.push(check);
promises.push((async () => {
await once(check, 'open');
const connectionManagementId = `connectionManagement-${Math.random()}`;
const updateSessionId = `updateSessionId-${Math.random()}`;
check.send(JSON.stringify({
pluginId,
updateSessionId,
connectionManagementId,
}));
const dcDeferred = new Deferred<RTCDataChannel>();
const session = new BrowserSignalingSession();
const droppedMessages: any[] = [];
session.onPeerConnection = async pc => {
pc.ondatachannel = e => {
e.channel.onmessage = message => droppedMessages.push(message);
e.channel.binaryType = 'arraybuffer';
dcDeferred.resolve(e.channel)
};
}
const pcPromise = session.pcDeferred.promise;
const serializer = createRpcSerializer({
sendMessageBuffer: buffer => check.send(buffer),
sendMessageFinish: message => check.send(JSON.stringify(message)),
});
const upgradingPeer = new RpcPeer(clientName || 'webrtc-upgrade', "api", (message, reject, serializationContext) => {
try {
serializer.sendMessage(message, reject, serializationContext);
}
catch (e) {
reject?.(e as Error);
}
});
check.on('message', data => {
if (data.constructor === Buffer || data.constructor === ArrayBuffer) {
serializer.onMessageBuffer(Buffer.from(data));
}
else {
serializer.onMessageFinish(JSON.parse(data as string));
}
});
serializer.setupRpcPeer(upgradingPeer);
// is this an issue?
// const readyClose = new Promise<RpcPeer>((resolve, reject) => {
// check.on('close', () => reject(new Error('closed')))
// })
upgradingPeer.params['session'] = session;
const pc = await pcPromise;
console.log('peer connection received');
await waitPeerConnectionIceConnected(pc);
console.log('waiting for data channel');
const dc = await dcDeferred.promise;
console.log('datachannel received', Date.now() - start);
const debouncer = new DataChannelDebouncer(dc, e => {
console.error('datachannel send error', e);
rpcPeer.kill('datachannel send error');
});
const dcSerializer = createRpcDuplexSerializer({
write: (data) => debouncer.send(data),
});
while (droppedMessages.length) {
const message = droppedMessages.shift();
dc.dispatchEvent(message);
}
const rpcPeer = new RpcPeer('webrtc-client', "api", (message, reject, serializationContext) => {
try {
dcSerializer.sendMessage(message, reject, serializationContext);
}
catch (e) {
reject?.(e as Error);
pc.close();
}
});
dcSerializer.setupRpcPeer(rpcPeer);
rpcPeer.params['connectionManagementId'] = connectionManagementId;
rpcPeer.params['updateSessionId'] = updateSessionId;
rpcPeer.params['browserSignalingSession'] = session;
waitPeerIceConnectionClosed(pc).then(() => check.close());
check.on('close', () => {
console.log('datachannel upgrade cancelled/closed');
pc.close()
});
await new Promise(resolve => {
let buffers: Buffer[] = [];
dc.onmessage = message => {
buffers.push(Buffer.from(message.data));
resolve(undefined);
flush.promise.finally(() => {
if (buffers) {
for (const buffer of buffers) {
dcSerializer.onData(Buffer.from(buffer));
}
buffers = undefined;
}
dc.onmessage = message => dcSerializer.onData(Buffer.from(message.data));
});
};
});
return {
ready: check,
connectionType: 'webrtc',
rpcPeer,
};
})());
}
const p2pPromises = [...promises];
promises.push((async () => {
const waitDuration = tryWebrtc ? 10000 : (tryAddresses ? 1000 : 0);
const waitDuration = tryAddresses ? 1000 : 0;
console.log('waiting', waitDuration);
if (waitDuration) {
// give the peer to peers a second, but then try connecting directly.
@@ -643,16 +539,13 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
return {
ready: check,
address: explicitBaseUrl,
connectionType: 'http',
connectionType: scryptedCloud ? 'http-cloud' : 'http',
};
})());
const any = Promise.any(promises);
let { ready, connectionType, address, rpcPeer } = await any;
if (tryWebrtc && connectionType !== 'webrtc')
localStorage.setItem(webrtcLastFailedKey, Date.now().toString());
console.log('connected', connectionType, address)
socket = ready;
@@ -683,7 +576,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
});
socket.on('message', data => {
if (data.constructor === Buffer || data.constructor === ArrayBuffer) {
serializer.onMessageBuffer(Buffer.from(data));
serializer.onMessageBuffer(Buffer.from(data as string));
}
else {
serializer.onMessageFinish(JSON.parse(data as string));
@@ -692,7 +585,6 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
serializer.setupRpcPeer(rpcPeer);
}
setTimeout(() => flush.resolve(undefined), 0);
const scrypted = await attachPluginRemote(rpcPeer, undefined);
const {
serverVersion,
@@ -701,6 +593,8 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
endpointManager,
mediaManager,
clusterManager,
pluginHostAPI,
pluginRemoteAPI,
} = scrypted;
console.log('api attached', Date.now() - start);
@@ -708,20 +602,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
return new MediaObject(mimeType, data, options) as any;
}
const { browserSignalingSession, connectionManagementId, updateSessionId } = rpcPeer.params;
if (updateSessionId && browserSignalingSession) {
systemManager.getComponent('plugins').then(async plugins => {
const updateSession: (session: RTCSignalingSession) => Promise<void> = await plugins.getHostParam('@scrypted/webrtc', updateSessionId);
if (!updateSession)
return;
await updateSession(browserSignalingSession);
console.log('signaling channel upgraded.');
socket.removeAllListeners();
socket.close();
});
}
const [admin, rtcConnectionManagement] = await Promise.all([
const [admin] = await Promise.all([
(async () => {
try {
// info is
@@ -732,18 +613,6 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
}
return false;
})(),
(async () => {
let rtcConnectionManagement: RTCConnectionManagement;
if (connectionManagementId) {
try {
const plugins = await systemManager.getComponent('plugins');
rtcConnectionManagement = await plugins.getHostParam('@scrypted/webrtc', connectionManagementId);
return rtcConnectionManagement;
}
catch (e) {
}
}
})(),
]);
console.log('api initialized', Date.now() - start);
@@ -752,110 +621,131 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
.map(id => systemManager.getDeviceById(id))
.find(device => device.pluginId === '@scrypted/core' && device.nativeId === `user:${username}`);
const clusterPeers = new Map<number, Promise<RpcPeer>>();
const ensureClusterPeer = (clusterObject: ClusterObject) => {
let clusterPeerPromise = clusterPeers.get(clusterObject.port);
if (!clusterPeerPromise) {
clusterPeerPromise = (async () => {
const eioPath = 'engine.io/connectRPCObject';
const eioEndpoint = baseUrl ? new URL(eioPath, baseUrl).pathname : '/' + eioPath;
const clusterPeerOptions = {
path: eioEndpoint,
query: {
cacehBust,
clusterObject: JSON.stringify(clusterObject),
},
withCredentials: true,
extraHeaders,
rejectUnauthorized: false,
transports: options?.transports,
};
const connectRPCObject = clusterSetup(address, connectionType, queryToken, extraHeaders, options?.transports, sourcePeerId, clientName);
const clusterPeerSocket = new eio.Socket(explicitBaseUrl, clusterPeerOptions);
let peerReady = false;
clusterPeerSocket.on('close', () => {
clusterPeers.delete(clusterObject.port);
if (!peerReady) {
throw new Error("peer disconnected before setup completed");
}
});
try {
await once(clusterPeerSocket, 'open');
const serializer = createRpcDuplexSerializer({
write: data => clusterPeerSocket.send(data),
});
clusterPeerSocket.on('message', data => serializer.onData(Buffer.from(data)));
const clusterPeer = new RpcPeer(clientName || 'engine.io-client', "cluster-proxy", (message, reject, serializationContext) => {
try {
serializer.sendMessage(message, reject, serializationContext);
}
catch (e) {
reject?.(e as Error);
}
});
serializer.setupRpcPeer(clusterPeer);
clusterPeer.tags.localPort = sourcePeerId;
peerReady = true;
return clusterPeer;
}
catch (e) {
console.error('failure ipc connect', e);
clusterPeerSocket.close();
throw e;
}
})();
clusterPeers.set(clusterObject.port, clusterPeerPromise);
}
return clusterPeerPromise;
const loginResult: ScryptedClientLoginResult = {
username,
token,
directAddress,
localAddresses,
externalAddresses,
scryptedCloud,
queryToken,
authorization,
cloudAddress,
hostname,
serverId,
};
const resolveObject = async (proxyId: string, sourcePeerPort: number) => {
const sourcePeer = await clusterPeers.get(sourcePeerPort);
if (sourcePeer?.remoteWeakProxies) {
return Object.values(sourcePeer.remoteWeakProxies).find(
v => v.deref()?.__cluster?.proxyId == proxyId
)?.deref();
}
return null;
}
type ForkType = ScryptedClientStatic['fork'];
const fork: ForkType = (forkOptions) => {
const { worker } = forkOptions;
const connectRPCObject = async (value: any) => {
const clusterObject: ClusterObject = value?.__cluster;
if (!clusterObject) {
return value;
}
const serializer = createRpcSerializer({
sendMessageBuffer: buffer => worker.postMessage(buffer),
sendMessageFinish: message => worker.postMessage(JSON.stringify(message)),
});
const { port, proxyId } = clusterObject;
const threadPeer = new RpcPeer("main-client", 'thread', (message, reject, serializationContext) => {
try {
serializer.sendMessage(message, reject, serializationContext);
}
catch (e) {
reject?.(e as Error);
}
});
// check if object is already connected
const resolved = await resolveObject(proxyId, port);
if (resolved) {
return resolved;
}
rpcPeer.killed.finally(() => threadPeer.kill('main rpc peer killed'));
try {
const clusterPeerPromise = ensureClusterPeer(clusterObject);
const clusterPeer = await clusterPeerPromise;
const connectRPCObject: ConnectRPCObject = await clusterPeer.getParam('connectRPCObject');
const newValue = await connectRPCObject(clusterObject);
if (!newValue)
throw new Error('ipc object not found?');
return newValue;
}
catch (e) {
console.error('failure ipc', e);
return value;
}
}
worker.addEventListener('message', async event => {
if (event.data instanceof Uint8Array) {
serializer.onMessageBuffer(Buffer.from(event.data));
}
else {
serializer.onMessageFinish(JSON.parse(event.data));
}
});
serializer.setupRpcPeer(threadPeer);
// there is no worker close event?
const forkApi = new PluginAPIProxy(pluginHostAPI, mediaManager);
threadPeer.killed.finally(() => {
forkApi.removeListeners();
worker.terminate();
});
const internalFork: InternalFork = {
loginResult,
username,
address,
connectionType,
extraHeaders,
transports: options?.transports,
clientName,
admin,
};
threadPeer.params['client'] = internalFork;
const result = (async () => {
const getRemote = await threadPeer.getParam('getRemote');
const remote = await getRemote(forkApi, pluginId, {
serverVersion
}) as PluginRemote;
await remote.setSystemState(systemManager.getSystemState());
forkApi.listen((id, eventDetails, eventData) => {
// ScryptedDevice events will be handled specially and repropagated by the remote.
if (eventDetails.eventInterface === ScryptedInterface.ScryptedDevice) {
if (eventDetails.property === ScryptedInterfaceProperty.id) {
// a change on the id property means device was deleted
remote.updateDeviceState(eventData, undefined);
}
else {
// a change on anything else is a descriptor update
remote.updateDeviceState(id, systemManager.getSystemState()[id]);
}
return;
}
if (eventDetails.property && !eventDetails.mixinId) {
remote.notify(id, eventDetails, systemManager.getSystemState()[id]?.[eventDetails.property]).catch(() => { });
}
else {
remote.notify(id, eventDetails, eventData).catch(() => { });
}
});
const fork = await threadPeer.getParam('fork');
return fork;
})();
result.catch(() => {
threadPeer.kill('fork setup failed');
worker.terminate();
});
return {
[Symbol.dispose]() {
worker.terminate();
threadPeer.kill('disposed');
},
result,
worker: {
terminate() {
worker.terminate();
},
nativeWorker: worker,
} as any as ForkWorker,
};
};
const ret: ScryptedClientStatic = {
userId: userDevice?.id,
serverVersion,
username,
pluginRemoteAPI: undefined,
pluginRemoteAPI,
address,
connectionType,
admin,
@@ -867,25 +757,11 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
disconnect() {
rpcPeer.kill('disconnect requested');
},
pluginHostAPI: undefined,
rtcConnectionManagement,
browserSignalingSession,
pluginHostAPI,
rpcPeer,
loginResult: {
username,
token,
directAddress,
localAddresses,
externalAddresses,
scryptedCloud,
queryToken,
authorization,
cloudAddress,
hostname,
serverId,
},
loginResult,
connectRPCObject,
fork: undefined,
fork,
connect: undefined,
}
@@ -905,3 +781,308 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
throw e;
}
}
function clusterSetup(address: string, connectionType: ScryptedClientConnectionType, queryToken: any, extraHeaders: { [header: string]: string }, transports: string[] | undefined, sourcePeerId: string, clientName?: string) {
const clusterPeers = new Map<number, Promise<RpcPeer>>();
const finalizationRegistry = new FinalizationRegistry((clusterPeer: RpcPeer) => {
clusterPeer.kill('object finalized');
});
const ensureClusterPeer = (clusterObject: ClusterObject, connectRPCObjectOptions?: ConnectRPCObjectOptions) => {
// If dedicatedTransport is true, don't reuse existing cluster peers
if (!connectRPCObjectOptions?.dedicatedTransport) {
let clusterPeerPromise = clusterPeers.get(clusterObject.port);
if (clusterPeerPromise)
return clusterPeerPromise;
}
const clusterPeerPromise = (async () => {
const eioPath = 'engine.io/connectRPCObject';
const eioEndpoint = new URL(eioPath, address).pathname;
const eioQueryToken = connectionType === 'http' ? undefined : queryToken;
const clusterPeerOptions: eio.SocketOptions = {
path: eioEndpoint,
query: {
cacheBust: Math.random().toString(36).substring(3, 10),
clusterObject: JSON.stringify(clusterObject),
...eioQueryToken,
},
withCredentials: true,
extraHeaders,
rejectUnauthorized: false,
transports,
};
const clusterPeerSocket = new eio.Socket(address, clusterPeerOptions);
let peerReady = false;
// Timeout handling for dedicated transports
let receiveTimeout: NodeJS.Timeout | undefined;
let sendTimeout: NodeJS.Timeout | undefined;
let clusterPeer: RpcPeer | undefined;
const clearTimers = () => {
if (receiveTimeout) {
clearTimeout(receiveTimeout);
receiveTimeout = undefined;
}
if (sendTimeout) {
clearTimeout(sendTimeout);
sendTimeout = undefined;
}
};
const resetReceiveTimeout = connectRPCObjectOptions?.dedicatedTransport?.receiveTimeout ? () => {
if (receiveTimeout) {
clearTimeout(receiveTimeout);
}
receiveTimeout = setTimeout(() => {
if (clusterPeer) {
clusterPeer.kill('receive timeout');
}
}, connectRPCObjectOptions.dedicatedTransport.receiveTimeout);
} : undefined;
const resetSendTimeout = connectRPCObjectOptions?.dedicatedTransport?.sendTimeout ? () => {
if (sendTimeout) {
clearTimeout(sendTimeout);
}
sendTimeout = setTimeout(() => {
if (clusterPeer) {
clusterPeer.kill('send timeout');
}
}, connectRPCObjectOptions.dedicatedTransport.sendTimeout);
} : undefined;
clusterPeerSocket.on('close', () => {
clusterPeer?.kill('socket closed');
// Only remove from clusterPeers if it's not a dedicated transport
if (!connectRPCObjectOptions?.dedicatedTransport) {
clusterPeers.delete(clusterObject.port);
}
if (!peerReady) {
throw new Error("peer disconnected before setup completed");
}
});
try {
await once(clusterPeerSocket, 'open');
const serializer = createRpcDuplexSerializer({
write: data => {
resetSendTimeout?.();
clusterPeerSocket.send(data);
},
});
clusterPeerSocket.on('message', data => {
resetReceiveTimeout?.();
serializer.onData(Buffer.from(data));
});
clusterPeer = new RpcPeer(clientName || 'engine.io-client', "cluster-proxy", (message, reject, serializationContext) => {
try {
resetSendTimeout?.();
serializer.sendMessage(message, reject, serializationContext);
}
catch (e) {
reject?.(e as Error);
}
});
clusterPeer.killedSafe.finally(() => {
clearTimers();
clusterPeerSocket.close();
});
serializer.setupRpcPeer(clusterPeer);
clusterPeer.tags.localPort = sourcePeerId;
peerReady = true;
// Initialize timeouts if configured
resetReceiveTimeout?.();
resetSendTimeout?.();
return clusterPeer;
}
catch (e) {
clearTimers();
console.error('failure ipc connect', e);
clusterPeerSocket.close();
throw e;
}
})();
// Only store in clusterPeers if it's not a dedicated transport
if (!connectRPCObjectOptions?.dedicatedTransport) {
clusterPeers.set(clusterObject.port, clusterPeerPromise);
}
return clusterPeerPromise;
};
const resolveObject = async (proxyId: string, sourcePeerPort: number) => {
const sourcePeer = await clusterPeers.get(sourcePeerPort);
if (sourcePeer?.remoteWeakProxies) {
return Object.values(sourcePeer.remoteWeakProxies).find(
v => v.deref()?.__cluster?.proxyId == proxyId
)?.deref();
}
return null;
}
const connectRPCObject = async (value: any, options?: ConnectRPCObjectOptions) => {
const clusterObject: ClusterObject = value?.__cluster;
if (!clusterObject) {
return value;
}
const { port, proxyId } = clusterObject;
// check if object is already connected
const resolved = await resolveObject(proxyId, port);
if (resolved) {
return resolved;
}
try {
const clusterPeerPromise = ensureClusterPeer(clusterObject, options);
const clusterPeer = await clusterPeerPromise;
const connectRPCObject: ConnectRPCObject = await clusterPeer.getParam('connectRPCObject');
try {
const newValue = await connectRPCObject(clusterObject);
if (!newValue)
throw new Error('ipc object not found?');
// If dedicatedTransport is true, register the object for cleanup
if (options?.dedicatedTransport) {
finalizationRegistry.register(newValue, clusterPeer);
}
return newValue;
}
catch (e) {
// If we have a clusterPeer and this is a dedicated transport, kill the connection
// to prevent resource leaks when connectRPCObject fails
if (options?.dedicatedTransport) {
clusterPeer.kill('connectRPCObject failed');
}
throw e;
}
}
catch (e) {
console.error('failure ipc', e);
return value;
}
}
return connectRPCObject;
}
export async function connectScryptedClientFork(forkMain: (client: ScryptedClientStatic) => Promise<any>) {
const start = Date.now();
try {
const serializer = createRpcSerializer({
sendMessageBuffer: buffer => self.postMessage(buffer),
sendMessageFinish: message => self.postMessage(JSON.stringify(message)),
});
const rpcPeer = new RpcPeer('thread', "main-client", (message, reject, serializationContext) => {
try {
serializer.sendMessage(message, reject, serializationContext);
}
catch (e) {
reject?.(e as Error);
}
});
self.addEventListener('message', event => {
if (event.data instanceof Uint8Array) {
serializer.onMessageBuffer(Buffer.from(event.data));
}
else {
serializer.onMessageFinish(JSON.parse(event.data));
}
});
serializer.setupRpcPeer(rpcPeer);
const scrypted = await attachPluginRemote(rpcPeer, undefined);
const {
serverVersion,
systemManager,
deviceManager,
endpointManager,
mediaManager,
clusterManager,
pluginHostAPI,
pluginRemoteAPI,
} = scrypted;
console.log('api attached', Date.now() - start);
mediaManager.createMediaObject = async<T extends MediaObjectCreateOptions>(data: any, mimeType: string, options: T) => {
return new MediaObject(mimeType, data, options) as any;
}
console.log('api initialized', Date.now() - start);
const {
loginResult,
username,
address,
connectionType,
extraHeaders,
transports,
clientName,
admin,
} = await rpcPeer.getParam('client') as InternalFork;
const { queryToken } = loginResult;
const userDevice = Object.keys(systemManager.getSystemState())
.map(id => systemManager.getDeviceById(id))
.find(device => device.pluginId === '@scrypted/core' && device.nativeId === `user:${username}`);
const connectRPCObject = clusterSetup(address, connectionType, queryToken, extraHeaders, transports, sourcePeerId, clientName);
type ForkType = ScryptedClientStatic['fork'];
const fork: ForkType = (forkOptions) => {
throw new Error('not implemented');
};
const ret: ScryptedClientStatic = {
userId: userDevice?.id,
serverVersion,
username,
pluginRemoteAPI,
address,
connectionType,
admin,
systemManager,
clusterManager,
deviceManager,
endpointManager,
mediaManager,
disconnect() {
rpcPeer.kill('disconnect requested');
},
pluginHostAPI,
rpcPeer,
loginResult,
connectRPCObject,
fork,
connect: undefined,
}
rpcPeer.killed.finally(() => {
self.close();
ret.onClose?.();
});
const forked = await forkMain(ret);
rpcPeer.params['fork'] = forked;
}
catch (e) {
self.close();
throw e;
}
}

View File

@@ -1,17 +1,17 @@
{
"name": "@scrypted/rpc",
"version": "0.0.5",
"name": "@scrypted/deferred",
"version": "0.0.8",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/rpc",
"version": "0.0.5",
"name": "@scrypted/deferred",
"version": "0.0.8",
"license": "ISC",
"devDependencies": {
"@types/node": "^18.11.18",
"rimraf": "^4.1.1",
"typescript": "^4.7.4"
"@types/node": "^24.0.10",
"rimraf": "^6.0.1",
"typescript": "^5.8.3"
}
},
"../../common": {
@@ -43,19 +43,141 @@
"../sdk/types": {
"extraneous": true
},
"node_modules/@types/node": {
"version": "18.11.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
"dev": true
},
"node_modules/rimraf": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.1.tgz",
"integrity": "sha512-Z4Y81w8atcvaJuJuBB88VpADRH66okZAuEm+Jtaufa+s7rZmIz+Hik2G53kGaNytE7lsfXyWktTmfVz0H9xuDg==",
"node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
"dev": true,
"bin": {
"rimraf": "dist/cjs/src/bin.js"
"license": "MIT",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/brace-expansion": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@types/node": {
"version": "24.0.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
"integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.8.0"
}
},
"node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
@@ -64,38 +186,793 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/typescript": {
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
"node_modules/glob": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.3.1",
"jackspeak": "^4.1.1",
"minimatch": "^10.0.3",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/jackspeak": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/lru-cache": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
"integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
"dev": true,
"license": "ISC",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/minimatch": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
"dev": true,
"license": "ISC",
"dependencies": {
"@isaacs/brace-expansion": "^5.0.0"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
"integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
"dev": true,
"license": "ISC",
"dependencies": {
"glob": "^11.0.0",
"package-json-from-dist": "^1.0.0"
},
"bin": {
"rimraf": "dist/esm/bin.mjs"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"dev": true,
"license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
}
},
"dependencies": {
"@types/node": {
"version": "18.11.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
"@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
"dev": true
},
"@isaacs/brace-expansion": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"dev": true,
"requires": {
"@isaacs/balanced-match": "^4.0.1"
}
},
"@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"requires": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
}
},
"@types/node": {
"version": "24.0.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
"integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
"dev": true,
"requires": {
"undici-types": "~7.8.0"
}
},
"ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true
},
"ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
}
},
"eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
},
"emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
"foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"requires": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
}
},
"glob": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
"dev": true,
"requires": {
"foreground-child": "^3.3.1",
"jackspeak": "^4.1.1",
"minimatch": "^10.0.3",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
}
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"jackspeak": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
"dev": true,
"requires": {
"@isaacs/cliui": "^8.0.2"
}
},
"lru-cache": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
"integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
"dev": true
},
"minimatch": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
"dev": true,
"requires": {
"@isaacs/brace-expansion": "^5.0.0"
}
},
"minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true
},
"package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true
},
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true
},
"path-scurry": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
"dev": true,
"requires": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
}
},
"rimraf": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.1.tgz",
"integrity": "sha512-Z4Y81w8atcvaJuJuBB88VpADRH66okZAuEm+Jtaufa+s7rZmIz+Hik2G53kGaNytE7lsfXyWktTmfVz0H9xuDg==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
"integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
"dev": true,
"requires": {
"glob": "^11.0.0",
"package-json-from-dist": "^1.0.0"
}
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"requires": {
"shebang-regex": "^3.0.0"
}
},
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
"typescript": {
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
"signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true
},
"string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"requires": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
}
},
"string-width-cjs": {
"version": "npm:string-width@4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
}
}
},
"strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"requires": {
"ansi-regex": "^6.0.1"
}
},
"strip-ansi-cjs": {
"version": "npm:strip-ansi@6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.1"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
}
}
},
"typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true
},
"undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"dev": true
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}
},
"wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"requires": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
}
},
"wrap-ansi-cjs": {
"version": "npm:wrap-ansi@7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
}
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/deferred",
"version": "0.0.5",
"version": "0.0.8",
"description": "",
"main": "dist/index.js",
"scripts": {
@@ -12,8 +12,8 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^18.11.18",
"rimraf": "^4.1.1",
"typescript": "^4.7.4"
"@types/node": "^24.0.10",
"rimraf": "^6.0.1",
"typescript": "^5.8.3"
}
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/rpc",
"version": "0.0.7",
"version": "0.0.8",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/rpc",
"version": "0.0.7",
"version": "0.0.8",
"license": "ISC",
"devDependencies": {
"@types/node": "^18.11.18",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/rpc",
"version": "0.0.7",
"version": "0.0.8",
"description": "",
"main": "dist/index.js",
"scripts": {

View File

@@ -1,4 +1,4 @@
{
"scrypted.debugHost": "127.0.0.1",
"scrypted.debugHost": "scrypted-server",
}

View File

@@ -1,6 +1,21 @@
<details>
<summary>Changelog</summary>
### 0.3.6
alexa: maybe fix alexa when no detection types are available
### 0.3.4
Alexa: add option to not auto enable devices (#1615)
### 0.3.3
google-home/alexa: republish with new sdk for media converter
### 0.3.2
alexa: fix syncedDevices being undefined

View File

@@ -1,11 +1,12 @@
{
"name": "@scrypted/alexa",
"version": "0.3.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/alexa",
"version": "0.3.4",
"version": "0.3.7",
"dependencies": {
"axios": "^1.3.4",
"uuid": "^9.0.0"
@@ -14,7 +15,7 @@
"@scrypted/common": "../../common",
"@scrypted/sdk": "../../sdk",
"@types/debug": "^4.1.12",
"@types/node": "^18.4.2"
"@types/node": "^22.13.11"
}
},
"../../common": {
@@ -24,35 +25,41 @@
"license": "ISC",
"dependencies": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/server": "file:../server",
"http-auth-utils": "^5.0.1",
"typescript": "^5.3.3"
"typescript": "^5.5.3"
},
"devDependencies": {
"@types/node": "^20.11.0",
"monaco-editor": "^0.50.0",
"ts-node": "^10.9.2"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.5",
"version": "0.5.10",
"dev": true,
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
@@ -64,11 +71,9 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"stringify-object": "^3.3.0",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21"
"@types/node": "^22.10.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.11"
}
},
"../common": {
@@ -98,10 +103,14 @@
"dev": true
},
"node_modules/@types/node": {
"version": "18.14.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz",
"integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==",
"dev": true
"version": "22.13.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.11.tgz",
"integrity": "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
@@ -193,6 +202,13 @@
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT"
},
"node_modules/uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
@@ -202,4 +218,4 @@
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/alexa",
"version": "0.3.4",
"version": "0.3.7",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",
@@ -42,6 +42,6 @@
"@scrypted/common": "../../common",
"@scrypted/sdk": "../../sdk",
"@types/debug": "^4.1.12",
"@types/node": "^18.4.2"
"@types/node": "^22.13.11"
}
}

View File

@@ -660,17 +660,26 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
const deviceHandler = alexaDeviceHandlers.get(mapName);
if (deviceHandler) {
const getDevice = () => {
const device = systemManager.getDeviceById(directive.endpoint.endpointId);
if (!device) {
response.send(deviceErrorResponse("NO_SUCH_ENDPOINT", "The device doesn't exist in Scrypted", directive));
if (!device || !device.mixins.includes(this.id)) {
response.send(deviceErrorResponse("NO_SUCH_ENDPOINT", "The device doesn't exist in Scrypted or was removed from the Alexa Plugin", directive));
this.deleteEndpoints(directive.endpoint.endpointId).catch(() => { });
return;
}
return device;
}
if (deviceHandler) {
const device = getDevice();
if (!device)
return;
await deviceHandler.apply(this, [request, response, directive, device]);
return;
} else {
this.console.error(`no handler for: ${mapName}`);
if (!getDevice())
return;
}
// it is better to send a non-specific response than an error, as the API might get rate throttled
@@ -718,6 +727,9 @@ class HttpResponseLoggingImpl implements AlexaHttpResponse {
sendSocket(socket: any, options: HttpResponseOptions): void {
this.response.sendSocket(socket, options);
}
sendStream(stream: AsyncGenerator<Buffer, void>, options?: HttpResponseOptions): void {
this.response.sendStream(stream, options);
}
}
export default AlexaPlugin;

View File

@@ -126,68 +126,69 @@ export async function getCameraCapabilities(device: ScryptedDevice): Promise<Dis
];
if (device.interfaces.includes(ScryptedInterface.ObjectDetector)) {
const detectionTypes = await (device as any as ObjectDetector).getObjectTypes();
const classNames = detectionTypes.classes.filter(t => t !== 'ring' && t !== 'motion').map(type => type.toLowerCase());
capabilities.push(
{
"type": "AlexaInterface",
"interface": "Alexa.SmartVision.ObjectDetectionSensor",
"version": "1.0",
"properties": {
"supported": [{
"name": "objectDetectionClasses"
}],
"proactivelyReported": true,
"retrievable": true
},
"configuration": {
"objectDetectionConfiguration": classNames.map(type => ({
"imageNetClass": type
}))
}
} as DiscoveryCapability
);
capabilities.push(
{
"type": "AlexaInterface",
"interface": "Alexa.DataController",
"instance": "Camera.SmartVisionData",
"version": "1.0",
"properties": undefined,
"configuration": {
"targetCapability": {
"name": "Alexa.SmartVision.ObjectDetectionSensor",
"version": "1.0"
const detectionTypes = await (device as any as ObjectDetector).getObjectTypes().catch(() => {}) || undefined;
const classNames = detectionTypes?.classes?.filter(t => t !== 'ring' && t !== 'motion').map(type => type.toLowerCase()).filter(c => !!c);
if (classNames?.length) {
capabilities.push(
{
"type": "AlexaInterface",
"interface": "Alexa.SmartVision.ObjectDetectionSensor",
"version": "1.0",
"properties": {
"supported": [{
"name": "objectDetectionClasses"
}],
"proactivelyReported": true,
"retrievable": true
},
"dataRetrievalSchema": {
"type": "JSON",
"schema": "SmartVisionData"
},
"supportedAccess": ["BY_IDENTIFIER", "BY_TIMESTAMP_RANGE"]
}
} as DiscoveryCapability
);
}
if (device.interfaces.includes(ScryptedInterface.MotionSensor)) {
capabilities.push(
{
"type": "AlexaInterface",
"interface": "Alexa.MotionSensor",
"version": "3",
"properties": {
"supported": [
{
"name": "detectionState"
}
],
"proactivelyReported": true,
"retrievable": true
}
} as DiscoveryCapability
);
"configuration": {
"objectDetectionConfiguration": classNames.map(type => ({
"imageNetClass": type
}))
}
} as DiscoveryCapability
);
capabilities.push(
{
"type": "AlexaInterface",
"interface": "Alexa.DataController",
"instance": "Camera.SmartVisionData",
"version": "1.0",
"properties": undefined,
"configuration": {
"targetCapability": {
"name": "Alexa.SmartVision.ObjectDetectionSensor",
"version": "1.0"
},
"dataRetrievalSchema": {
"type": "JSON",
"schema": "SmartVisionData"
},
"supportedAccess": ["BY_IDENTIFIER", "BY_TIMESTAMP_RANGE"]
}
} as DiscoveryCapability
);
}
if (device.interfaces.includes(ScryptedInterface.MotionSensor)) {
capabilities.push(
{
"type": "AlexaInterface",
"interface": "Alexa.MotionSensor",
"version": "3",
"properties": {
"supported": [
{
"name": "detectionState"
}
],
"proactivelyReported": true,
"retrievable": true
}
} as DiscoveryCapability
);
}
}
return capabilities;

View File

@@ -8,7 +8,7 @@ export interface SupportedType {
setState?(device: ScryptedDevice, payload: any): Promise<Partial<Report>>;
}
export const supportedTypes = new Map<ScryptedDeviceType, SupportedType>();
export const supportedTypes = new Map<ScryptedDeviceType | string, SupportedType>();
import '../handlers';
import './camera';

View File

@@ -1,4 +1,4 @@
{
"scrypted.debugHost": "127.0.0.1",
"scrypted.debugHost": "scrypted-nvr",
}

View File

@@ -1,21 +1,23 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.164",
"version": "0.0.168",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/amcrest",
"version": "0.0.164",
"version": "0.0.168",
"license": "Apache",
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"content-type": "^1.0.5"
"content-type": "^1.0.5",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@types/content-type": "^1.1.8",
"@types/node": "^20.11.30"
"@types/node": "^22.19.3",
"@types/xml2js": "^0.4.14"
}
},
"../../common": {
@@ -24,34 +26,43 @@
"license": "ISC",
"dependencies": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/server": "file:../server",
"@scrypted/types": "^0.5.27",
"http-auth-utils": "^5.0.1",
"typescript": "^5.3.3"
"typescript": "^5.5.3"
},
"devDependencies": {
"@types/node": "^20.11.0",
"@types/node": "^20.19.11",
"monaco-editor": "^0.50.0",
"ts-node": "^10.9.2"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.29",
"version": "0.5.55",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.27.1",
"@rollup/plugin-commonjs": "^28.0.9",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.3.0",
"@rollup/plugin-virtual": "^3.0.2",
"adm-zip": "^0.5.16",
"axios": "^1.10.0",
"babel-loader": "^10.0.0",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"openai": "^6.1.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"rimraf": "^6.0.1",
"rollup": "^4.52.5",
"tmp": "^0.2.3",
"ts-loader": "^9.5.4",
"tslib": "^2.8.1",
"typescript": "^5.9.3",
"webpack": "^5.99.9",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
@@ -63,11 +74,9 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"stringify-object": "^3.3.0",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21"
"@types/node": "^24.9.2",
"ts-node": "^10.9.2",
"typedoc": "^0.28.14"
}
},
"node_modules/@scrypted/common": {
@@ -85,12 +94,23 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.11.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
"integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
"version": "22.19.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
"integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
"undici-types": "~6.21.0"
}
},
"node_modules/@types/xml2js": {
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/content-type": {
@@ -101,11 +121,40 @@
"node": ">= 0.6"
}
},
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC"
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/xml2js": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
"license": "MIT",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.164",
"version": "0.0.168",
"description": "Amcrest Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",
@@ -39,10 +39,12 @@
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"content-type": "^1.0.5"
"content-type": "^1.0.5",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@types/content-type": "^1.1.8",
"@types/node": "^20.11.30"
"@types/node": "^22.19.3",
"@types/xml2js": "^0.4.14"
}
}

View File

@@ -1,12 +1,12 @@
import { AuthFetchCredentialState, HttpFetchOptions, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
import { AuthFetchCredentialState, authHttpFetch, HttpFetchOptions } from '@scrypted/common/src/http-auth-fetch';
import { readLine } from '@scrypted/common/src/read-stream';
import { parseHeaders, readBody } from '@scrypted/common/src/rtsp-server';
import { MediaStreamConfiguration, Point } from '@scrypted/sdk';
import contentType from 'content-type';
import { IncomingMessage } from 'http';
import { EventEmitter, Readable } from 'stream';
import { createRtspMediaStreamOptions, Destroyable, UrlMediaStreamOptions } from '../../rtsp/src/rtsp';
import { getDeviceInfo } from './probe';
import { MediaStreamConfiguration, MediaStreamOptions, Point } from '@scrypted/sdk';
export interface AmcrestObjectDetails {
Action: string;
@@ -81,8 +81,11 @@ async function readAmcrestMessage(client: Readable): Promise<string[]> {
}
}
function findValue(blob: string, prefix: string, key: string) {
const lines = blob.split('\n');
function getLines(blob: string) {
return blob.split(/\r?\n/).filter(line => line);
}
function findValue(lines: string[], prefix: string, key: string) {
const value = lines.find(line => line.startsWith(`${prefix}.${key}`));
if (!value)
return;
@@ -124,7 +127,7 @@ const amcrestResolutions = {
"720P": [1280, 720],
"D1": [704, 480],
"HD1": [352, 480],
"BCIF": [704, 240],
"BCIF": [528, 240],
"2CIF": [704, 240],
"CIF": [352, 240],
"QCIF": [176, 120],
@@ -133,7 +136,21 @@ const amcrestResolutions = {
"QVGA": [320, 240]
};
function fromAmcrestResolution(resolution: string) {
const palAmcrestResolutions = {
"D1": [704, 576],
"HD1": [352, 576],
"BCIF": [528, 288],
"2CIF": [704, 288],
"CIF": [352, 288],
"QCIF": [176, 144],
};
function fromAmcrestResolution(resolution: string, videoStandard: string) {
if (videoStandard === 'PAL') {
const named = palAmcrestResolutions[resolution];
if (named)
return named;
}
const named = amcrestResolutions[resolution];
if (named)
return named;
@@ -251,8 +268,8 @@ export class AmcrestCameraClient {
continue;
if (ignore === boundaryEnd)
continue;
// dahua bugs out and sends this.
if (ignore === 'HTTP/1.1 200 OK') {
// dahua bugs out and sends this (handle both HTTP/1.0 and HTTP/1.1).
if (ignore === 'HTTP/1.1 200 OK' || ignore === 'HTTP/1.0 200 OK') {
const message = await readAmcrestMessage(stream);
this.console.log('ignoring dahua http message', message);
message.unshift('');
@@ -438,6 +455,12 @@ export class AmcrestCameraClient {
this.console.log(capsResponse.body);
const videoStandardResponse = await this.request({
url: `http://${this.ip}/cgi-bin/configManager.cgi?action=getConfig&name=VideoStandard`,
responseType: 'text',
});
this.console.log(videoStandardResponse.body);
const formatNumber = Math.max(0, parseInt(options.id?.substring('channel'.length)) - 1);
const format = options.id === 'channel0' ? 'MainFormat' : 'ExtraFormat';
const encode = `Encode[${cameraNumber - 1}].${format}[${formatNumber}]`;
@@ -493,17 +516,19 @@ export class AmcrestCameraClient {
const caps = `caps[${cameraNumber - 1}].${format}[${formatNumber}]`;
const singleCaps = `caps.${format}[${formatNumber}]`;
const capsLines = getLines(capsResponse.body);
const videoStandard = findValue(getLines(videoStandardResponse.body), 'table', 'VideoStandard');
const findCaps = (key: string) => {
const found = findValue(capsResponse.body, caps, key);
const found = findValue(capsLines, caps, key);
if (found)
return found;
// ad410 doesnt return a camera number if accessed directly
if (cameraNumber - 1 === 0)
return findValue(capsResponse.body, singleCaps, key);
return findValue(capsLines, singleCaps, key);
}
const resolutions = findCaps('Video.ResolutionTypes').split(',').map(fromAmcrestResolution);
const resolutions = findCaps('Video.ResolutionTypes').split(',').map(r => fromAmcrestResolution(r, videoStandard));
const bitrates = findCaps('Video.BitRateOptions').split(',').map(s => parseInt(s) * 1000);
const fpsMax = parseInt(findCaps('Video.FPSMax'));
const vso: MediaStreamConfiguration = {
@@ -533,6 +558,7 @@ export class AmcrestCameraClient {
responseType: 'text',
});
this.console.log(encodeResponse.body);
const encodeLines = getLines(encodeResponse.body);
for (let i = 0; i < vsos.length; i++) {
const vso = vsos[i];
@@ -544,27 +570,27 @@ export class AmcrestCameraClient {
encName = `table.Encode[${cameraNumber - 1}].ExtraFormat[${i - 1}]`;
}
const videoCodec = fromAmcrestVideoCodec(findValue(encodeResponse.body, encName, 'Video.Compression'));
const audioCodec = fromAmcrestAudioCodec(findValue(encodeResponse.body, encName, 'Audio.Compression'));
const videoCodec = fromAmcrestVideoCodec(findValue(encodeLines, encName, 'Video.Compression'));
const audioCodec = fromAmcrestAudioCodec(findValue(encodeLines, encName, 'Audio.Compression'));
if (vso.audio)
vso.audio.codec = audioCodec;
vso.video.codec = videoCodec;
const width = findValue(encodeResponse.body, encName, 'Video.Width');
const height = findValue(encodeResponse.body, encName, 'Video.Height');
const width = findValue(encodeLines, encName, 'Video.Width');
const height = findValue(encodeLines, encName, 'Video.Height');
if (width && height) {
vso.video.width = parseInt(width);
vso.video.height = parseInt(height);
}
const videoEnable = findValue(encodeResponse.body, encName, 'VideoEnable');
const videoEnable = findValue(encodeLines, encName, 'VideoEnable');
if (videoEnable?.trim() === 'false') {
this.console.warn('Video stream is disabled and should likely be enabled:', encName);
continue;
}
const encodeOptions = findValue(encodeResponse.body, encName, 'Video.BitRate');
const encodeOptions = findValue(encodeLines, encName, 'Video.BitRate');
if (!encodeOptions)
continue;

View File

@@ -1,14 +1,13 @@
import { automaticallyConfigureSettings, checkPluginNeedsAutoConfigure } from "@scrypted/common/src/autoconfigure-codecs";
import { ffmpegLogInitialOutput } from '@scrypted/common/src/media-helpers';
import { readLength } from "@scrypted/common/src/read-stream";
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, Lock, MediaObject, MediaStreamOptions, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, Reboot, RequestPictureOptions, RequestRecordingStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, VideoCameraConfiguration, VideoRecorder } from "@scrypted/sdk";
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, Lock, MediaObject, MediaStreamOptions, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, Reboot, RequestPictureOptions, RequestRecordingStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, VideoCameraConfiguration, VideoRecorder, VideoTextOverlay, VideoTextOverlays } from "@scrypted/sdk";
import child_process, { ChildProcess } from 'child_process';
import { PassThrough, Readable, Stream } from "stream";
import { OnvifIntercom } from "../../onvif/src/onvif-intercom";
import { createRtspMediaStreamOptions, RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
import { AmcrestCameraClient, AmcrestEvent, AmcrestEventData } from "./amcrest-api";
import { amcrestAutoConfigureSettings, autoconfigureSettings } from "./amcrest-configure";
import { group } from "console";
const { mediaManager } = sdk;
@@ -23,7 +22,7 @@ const rtspChannelSetting: Setting = {
placeholder: '1',
};
class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration, Camera, Intercom, Lock, VideoRecorder, Reboot, ObjectDetector {
class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration, Camera, Intercom, Lock, VideoRecorder, Reboot, ObjectDetector, VideoTextOverlays {
eventStream: Stream;
cp: ChildProcess;
client: AmcrestCameraClient;
@@ -43,6 +42,92 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
this.updateDeviceInfo();
}
async getVideoTextOverlays(): Promise<Record<string, VideoTextOverlay>> {
const client = this.getClient();
const response = await client.request({
method: "GET",
url: `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=getConfig&name=VideoWidget`,
responseType: "text",
headers: {
"Content-Type": "application/xml",
},
});
const body: string = response.body;
if (!body.startsWith("<")) {
const encodeBlend = '.EncodeBlend';
const config: Record<string, VideoTextOverlay> = {};
for (const line of body.split(/\r?\n/).filter(l => l.includes(encodeBlend + '='))) {
const trimmed = line.trim();
if (!trimmed) continue;
const splitIndex = trimmed.indexOf("=");
if (splitIndex === -1) continue;
// remove encodeBlend
let key = trimmed.substring(0, splitIndex);
key = key.substring(0, key.length - encodeBlend.length);
config[key] = {
readonly: true,
};
}
const textValue = '.Text';
for (const line of body.split(/\r?\n/).filter(l => l.includes(textValue + '='))) {
const trimmed = line.trim();
if (!trimmed) continue;
const splitIndex = trimmed.indexOf("=");
if (splitIndex === -1) continue;
// remove encodeBlend
let key = trimmed.substring(0, splitIndex);
key = key.substring(0, key.length - textValue.length);
const text = trimmed.substring(splitIndex + 1).trim();
const c = config[key];
if (!c)
continue;
delete c.readonly;
c.text = text;
}
return config;
} else {
throw new Error('invalid response');
// const json = await xml2js.parseStringPromise(body);
// return { json, xml: body };
}
}
async setVideoTextOverlay(id: string, value: VideoTextOverlay): Promise<void> {
// trim the table. off id
if (id.startsWith('table.'))
id = id.substring('table.'.length);
const client = this.getClient();
if (value.text) {
const enableUrl = `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=setConfig&${id}.EncodeBlend=true&${id}.PreviewBlend=true`;
await client.request({
method: "GET",
url: enableUrl,
responseType: "text",
});
const textUrl = `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=setConfig&${id}.Text=${encodeURIComponent(
value.text
)}`;
await client.request({
method: "GET",
url: textUrl,
responseType: "text",
});
}
else {
const disableUrl = `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=setConfig&${id}.EncodeBlend=false&${id}.PreviewBlend=false`;
await client.request({
method: "GET",
url: disableUrl,
responseType: "text",
});
}
}
async reboot() {
const client = this.getClient();
await client.reboot();
@@ -631,6 +716,7 @@ class AmcrestProvider extends RtspProvider {
ScryptedInterface.Camera,
ScryptedInterface.AudioSensor,
ScryptedInterface.MotionSensor,
ScryptedInterface.VideoTextOverlays,
];
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/cloud",
"version": "0.2.49",
"version": "0.2.51",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/cloud",
"version": "0.2.49",
"version": "0.2.51",
"dependencies": {
"@eneris/push-receiver": "^4.3.0",
"@scrypted/common": "file:../../common",

View File

@@ -52,5 +52,5 @@
"@types/node": "^22.10.1",
"ts-node": "^10.9.2"
},
"version": "0.2.49"
"version": "0.2.51"
}

View File

@@ -240,8 +240,10 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
upnpClient = upnp.createClient();
upnpStatus = 'Starting';
randomBytes = crypto.randomBytes(16).toString('base64');
healthCheckToken = crypto.randomBytes(16).toString('hex');
reverseConnections = new Set<Duplex>();
cloudflaredLoginController?: AbortController;
healthCheckInterval?: NodeJS.Timeout;
get portForwardingDisabled() {
return this.storageSettings.values.forwardingMode === 'Disabled' || this.storageSettings.values.forwardingMode === 'Default';
@@ -852,6 +854,12 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
}
res.end();
}
else if (url.pathname === '/_punch/cloudflared_callback') {
res.writeHead(200);
res.write(this.healthCheckToken);
res.end();
return;
}
else if (url.pathname === '/web/') {
const validDomain = this.getSSLHostname();
if (validDomain) {
@@ -1122,6 +1130,9 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
maxDelay: 300000,
});
// Start health check after cloudflared is successfully started
this.startHealthCheck();
await once(this.cloudflared.child, 'exit').catch(() => { });
// the successfully started cloudflared process may exit at some point, loop and allow it to restart.
this.console.error('cloudflared exited');
@@ -1131,6 +1142,8 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
this.console.error('cloudflared error', e);
}
finally {
clearInterval(this.healthCheckInterval);
this.healthCheckInterval = undefined;
this.cloudflared = undefined;
this.cloudflareTunnel = undefined;
this.updateExternalAddresses();
@@ -1138,6 +1151,59 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
}
}
async startHealthCheck() {
// Clear any existing health check interval
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
// Local failure counter - only accessible within this method
let failureCount = 0;
const maxFailuresBeforeRestart = 3;
const alertTitle = 'Cloudflared health check failed 3 times consecutively. Restarting cloudflared process.';
const check = async () => {
// Only perform health check if cloudflare is enabled and we have a tunnel URL
if (!this.storageSettings.values.cloudflareEnabled || !this.cloudflareTunnel) {
return;
}
try {
const healthCheckUrl = `${this.cloudflareTunnel}/_punch/cloudflared_callback`;
this.console.log(`Performing health check: ${healthCheckUrl}`);
const response = await httpFetch({
url: healthCheckUrl,
responseType: 'text',
timeout: 30000, // 30 second timeout
});
this.log.clearAlert(alertTitle);
if (response.body !== this.healthCheckToken) {
throw new Error(`Health check failed: Expected token ${this.healthCheckToken}, got ${response.body}`);
}
failureCount = 0;
this.console.log('Cloudflared health check passed');
} catch (error) {
failureCount++;
this.console.error(`Cloudflared health check failed (${failureCount}/${maxFailuresBeforeRestart}):`, error);
if (failureCount >= maxFailuresBeforeRestart) {
this.console.warn('3 consecutive health check failures detected. Restarting cloudflared process.');
this.log.a(alertTitle);
this.cloudflared?.child?.kill();
failureCount = 0;
}
}
};
// Start a new health check interval (every 2 minutes)
this.healthCheckInterval = setInterval(check, 2 * 60 * 1000); // Run every 2 minutes
}
get serverIdentifier() {
const serverIdentifier = `${this.storageSettings.values.registrationSecret}@${this.storageSettings.values.serverId}`;
return serverIdentifier;

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/core",
"version": "0.3.111",
"version": "0.3.146",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/core",
"version": "0.3.111",
"version": "0.3.146",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",
@@ -77,6 +77,7 @@
"license": "ISC",
"dependencies": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/types": "^0.5.27",
"http-auth-utils": "^5.0.1",
"typescript": "^5.5.3"
},
@@ -88,28 +89,29 @@
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.100",
"version": "0.5.33",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@babel/preset-typescript": "^7.27.1",
"@rollup/plugin-commonjs": "^28.0.5",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.2",
"@rollup/plugin-virtual": "^3.0.2",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"axios": "^1.10.0",
"babel-loader": "^10.0.0",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"openai": "^5.3.0",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"rollup": "^4.43.0",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"ts-loader": "^9.5.2",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"typescript": "^5.8.3",
"webpack": "^5.99.9",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
@@ -122,9 +124,9 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^22.10.1",
"@types/node": "^24.0.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.11"
"typedoc": "^0.28.5"
}
},
"node_modules/@scrypted/common": {
@@ -276,6 +278,7 @@
"version": "file:../../common",
"requires": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/types": "^0.5.27",
"@types/node": "^20.11.0",
"http-auth-utils": "^5.0.1",
"monaco-editor": "^0.50.0",
@@ -286,28 +289,29 @@
"@scrypted/sdk": {
"version": "file:../../sdk",
"requires": {
"@babel/preset-typescript": "^7.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@babel/preset-typescript": "^7.27.1",
"@rollup/plugin-commonjs": "^28.0.5",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.2",
"@rollup/plugin-virtual": "^3.0.2",
"@types/node": "^22.10.1",
"@types/node": "^24.0.1",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"axios": "^1.10.0",
"babel-loader": "^10.0.0",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"openai": "^5.3.0",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"rollup": "^4.43.0",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tslib": "^2.8.1",
"typedoc": "^0.26.11",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"typedoc": "^0.28.5",
"typescript": "^5.8.3",
"webpack": "^5.99.9",
"webpack-bundle-analyzer": "^4.10.2"
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/core",
"version": "0.3.111",
"version": "0.3.146",
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
"author": "Scrypted",
"license": "Apache-2.0",
@@ -33,10 +33,6 @@
"ScryptedSettings",
"SystemSettings",
"Settings"
],
"pluginDependencies": [
"@scrypted/snapshot",
"@scrypted/webrtc"
]
},
"dependencies": {

View File

@@ -72,7 +72,6 @@ function createVideoCamera(devices: VideoCamera[], console: Console): VideoCamer
container: 'rawvideo',
mediaStreamOptions: (await createVideoStreamOptions())?.[0],
inputArguments: [],
h264FilterArguments: [],
};
for (let i = 0; i < inputs.length; i++) {
@@ -102,7 +101,7 @@ function createVideoCamera(devices: VideoCamera[], console: Console): VideoCamer
let i = dim * dim - 1;
filter.push(`[${prev}][pos${i}] overlay=shortest=1:x=${curx % 1920}:y=${cury % 1080}`);
ffmpegInput.h264FilterArguments.push(
ffmpegInput.inputArguments.push(
'-filter_complex',
filter.join(' '),
);

View File

@@ -155,6 +155,8 @@ export class ClusterCore extends ScryptedDeviceBase implements Settings, Readme,
}
async getReadmeMarkdown(): Promise<string> {
return `Manage Scrypted's cluster mode. Run storage devices and compute services on separate servers.`;
return `Manage Scrypted's cluster mode. Run storage devices and compute services on separate servers.
[Read Documentation](https://docs.scrypted.app/maintenance/cluster.html).`;
}
}

View File

@@ -1,5 +1,5 @@
import { DeviceState, MixinProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk";
import { typeToIcon } from "../../../../manage.scrypted.app/src/device-icons";
import { typeToIcon } from "../../../../manage.scrypted.app/src/util/device-icons";
export class LauncherMixin extends ScryptedDeviceBase implements MixinProvider, Readme {
async getReadmeMarkdown(): Promise<string> {

View File

@@ -1,4 +1,5 @@
import { readFileAsString, tsCompile } from '@scrypted/common/src/eval/scrypted-eval';
import { sleep } from '@scrypted/common/src/sleep';
import sdk, { DeviceProvider, HttpRequest, HttpRequestHandler, HttpResponse, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, SettingValue, Settings } from '@scrypted/sdk';
import { StorageSettings } from "@scrypted/sdk/storage-settings";
import { writeFileSync } from 'fs';
@@ -8,14 +9,14 @@ import yaml from 'yaml';
import { getUsableNetworkAddresses } from '../../../server/src/ip';
import { AggregateCore, AggregateCoreNativeId } from './aggregate-core';
import { AutomationCore, AutomationCoreNativeId } from './automations-core';
import { ClusterCore, ClusterCoreNativeId } from './cluster';
import { LauncherMixin } from './launcher-mixin';
import { MediaCore } from './media-core';
import { checkLegacyLxc, checkLxc } from './platform/lxc';
import { checkLegacyLxc, checkLxc, checkLxcVersionUpdateNeeded } from './platform/lxc';
import { ConsoleServiceNativeId, PluginSocketService, ReplServiceNativeId } from './plugin-socket-service';
import { ScriptCore, ScriptCoreNativeId, newScript } from './script-core';
import { TerminalService, TerminalServiceNativeId, newTerminalService } from './terminal-service';
import { UsersCore, UsersNativeId } from './user';
import { ClusterCore, ClusterCoreNativeId } from './cluster';
const { deviceManager, endpointManager } = sdk;
@@ -63,7 +64,10 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
'Default',
'latest',
'beta',
`v${sdk.serverVersion}-jammy-full`,
'intel',
'amd',
'nvidia',
`v${sdk.serverVersion}-noble-full`,
],
combobox: true,
onPut: (ov, nv) => {
@@ -108,7 +112,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
(async () => {
await deviceManager.onDeviceDiscovered(
{
name: 'Cluster',
name: 'Cluster Manager',
nativeId: ClusterCoreNativeId,
interfaces: [ScryptedInterface.Settings, ScryptedInterface.Readme, ScryptedInterface.ScryptedSettings],
type: ScryptedDeviceType.Builtin,
@@ -121,7 +125,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'Media Core',
nativeId: 'mediacore',
interfaces: [ScryptedInterface.DeviceProvider, ScryptedInterface.BufferConverter, ScryptedInterface.HttpRequestHandler],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
@@ -131,7 +135,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'Scripts',
nativeId: ScriptCoreNativeId,
interfaces: [ScryptedInterface.ScryptedSystemDevice, ScryptedInterface.ScryptedDeviceCreator, ScryptedInterface.DeviceProvider, ScryptedInterface.DeviceCreator, ScryptedInterface.Readme],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
@@ -141,7 +145,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'Terminal Service',
nativeId: TerminalServiceNativeId,
interfaces: [ScryptedInterface.StreamService, ScryptedInterface.TTY, ScryptedInterface.ClusterForkInterface],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
@@ -151,7 +155,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'REPL Service',
nativeId: ReplServiceNativeId,
interfaces: [ScryptedInterface.StreamService],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
@@ -161,7 +165,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'Console Service',
nativeId: ConsoleServiceNativeId,
interfaces: [ScryptedInterface.StreamService],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
@@ -172,7 +176,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'Automations',
nativeId: AutomationCoreNativeId,
interfaces: [ScryptedInterface.ScryptedSystemDevice, ScryptedInterface.ScryptedDeviceCreator, ScryptedInterface.DeviceProvider, ScryptedInterface.DeviceCreator, ScryptedInterface.Readme],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
@@ -185,7 +189,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
ScryptedInterface.MixinProvider,
ScryptedInterface.Readme,
],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
});
(async () => {
@@ -206,10 +210,73 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'Scrypted Users',
nativeId: UsersNativeId,
interfaces: [ScryptedInterface.ScryptedSystemDevice, ScryptedInterface.ScryptedDeviceCreator, ScryptedInterface.DeviceProvider, ScryptedInterface.DeviceCreator, ScryptedInterface.Readme],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
// check on workers immediately and once an hour.
this.updateWorkers();
setInterval(() => this.updateWorkers(), 60 * 1000 * 60);
// check on worker images once an hour.
// checking immediately is problematic as a failed update may cause a restart loop on startup.
// images are also pruned 1 minute after startup, so avoid that.
setInterval(() => this.updateWorkerImages(), 60 * 1000 * 60);
}
async updateWorkers() {
const workers = await sdk.clusterManager?.getClusterWorkers();
if (!workers)
return;
for (const [id, worker] of Object.entries(workers)) {
const forked = sdk.fork<ReturnType<typeof fork>>({
clusterWorkerId: id,
runtime: 'node',
});
(async () => {
try {
const result = await forked.result;
result.checkLxc();
}
catch (e) {
forked.worker.terminate();
}
})();
}
}
async updateWorkerImages() {
const workers = await sdk.clusterManager?.getClusterWorkers();
if (!workers)
return;
for (const [id, worker] of Object.entries(workers)) {
const forked = sdk.fork<ReturnType<typeof fork>>({
clusterWorkerId: id,
runtime: 'node',
});
(async () => {
try {
const result = await forked.result;
if (!await result.checkLxcVersionUpdateNeeded()) {
return;
}
// restart the worker to pick up the new image.
const clusterFork = await sdk.systemManager.getComponent('cluster-fork');
const serviceControl = await clusterFork.getServiceControl(worker.id);
await serviceControl.restart().catch(() => { });
}
catch (e) {
}
finally {
await sleep(1000);
forked.worker.terminate();
}
})();
}
}
async getSettings(): Promise<Setting[]> {
@@ -315,7 +382,6 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
const dockerCompose = yaml.parseDocument(readFileAsString('/root/.scrypted/docker-compose.yml'));
// @ts-ignore
dockerCompose.contents.get('services').get('scrypted').set('image', `ghcr.io/koush/scrypted${releaseChannel}`);
yaml.stringify(dockerCompose);
writeFileSync('/root/.scrypted/docker-compose.yml', yaml.stringify(dockerCompose));
this.setPullImage();
@@ -332,5 +398,16 @@ export async function fork() {
tsCompile,
newScript,
newTerminalService,
checkLxcVersionUpdateNeeded,
checkLxc: async () => {
try {
// console.warn('Checking for LXC installation...');
await checkLxc();
}
finally {
await sleep(1000);
process.exit(0);
}
}
}
}

View File

@@ -1,5 +1,9 @@
import fs from 'fs';
import { Deferred } from '@scrypted/common/src/deferred';
import { readFileAsString } from '@scrypted/common/src/eval/scrypted-eval';
import sdk from '@scrypted/sdk';
import fs, { writeFileSync } from 'fs';
import http from 'http';
import yaml from 'yaml';
export const SCRYPTED_INSTALL_ENVIRONMENT_LXC = 'lxc';
export const SCRYPTED_INSTALL_ENVIRONMENT_LXC_DOCKER = 'lxc-docker';
@@ -18,6 +22,119 @@ export async function checkLxc() {
if (process.env.SCRYPTED_INSTALL_ENVIRONMENT !== SCRYPTED_INSTALL_ENVIRONMENT_LXC_DOCKER)
return;
await checkLxcCompose();
await checkLxcScript();
}
async function dockerRequest(options: http.RequestOptions, body?: string) {
const deferred = new Deferred<string>();
const req = http.request({
socketPath: '/var/run/docker.sock',
method: options.method,
path: options.path,
headers: {
'Host': 'localhost',
...options.headers
}
});
req.on('response', (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
deferred.resolve(data);
});
});
req.on('error', (err) => {
deferred.reject(err);
});
if (body) {
req.write(body);
}
req.end();
return deferred.promise;
}
async function dockerPullScryptedTag(tag: string) {
return dockerRequest({
method: 'POST',
path: `/v1.41/images/create?fromImage=ghcr.io%2Fkoush%2Fscrypted&tag=${tag}`,
});
}
async function dockerImageLsScryptedTag(tag: string) {
// List all images and find the specific one
const data = await dockerRequest({
method: 'GET',
path: '/v1.41/images/json'
});
const images = JSON.parse(data);
// Filter for your specific image
const targetImage = images.find(image => {
return image.RepoTags && image.RepoTags.some(t =>
t === `ghcr.io/koush/scrypted:${tag}`
);
});
if (!targetImage) {
throw new Error('Image not found');
}
return targetImage.Id;
}
async function dockerGetScryptedContainerImageId() {
// List running containers filtered by name
const data = await dockerRequest({
method: 'GET',
path: '/v1.41/containers/json?filters={"name":["scrypted"],"status":["running"]}'
});
const containers = JSON.parse(data);
if (!containers.length)
throw new Error('No running container named "scrypted" found');
const container = containers[0];
return container.ImageID;
}
export async function checkLxcVersionUpdateNeeded() {
if (process.env.SCRYPTED_INSTALL_ENVIRONMENT !== SCRYPTED_INSTALL_ENVIRONMENT_LXC_DOCKER)
return;
const dockerCompose = yaml.parseDocument(readFileAsString('/root/.scrypted/docker-compose.yml'));
// @ts-ignore
const image: string = dockerCompose.contents.get('services').get('scrypted').get('image');
const label = image.split(':')[1] || 'latest';
await dockerPullScryptedTag(label);
const imageId = await dockerImageLsScryptedTag(label);
const containerImageId = await dockerGetScryptedContainerImageId();
console.warn('LXC Scrypted latest image ID:', imageId);
console.warn('LXC Scrypted running image ID:', containerImageId);
return containerImageId !== imageId;
}
async function checkLxcCompose() {
// the lxc-docker used watchtower for automatic updates but watchtower started crashing in the lxc environment
// after a docker update.
// watchtower was removed from the lxc as a result.
// however existing installations may still have watchtower in their docker-compose.yml and need it removed.
const dockerCompose = yaml.parseDocument(readFileAsString('/root/.scrypted/docker-compose.yml'));
// @ts-ignore
const watchtower = dockerCompose.contents.get('services').get('watchtower');
if (watchtower.get('profiles'))
return;
watchtower.set('profiles', ['disabled']);
writeFileSync('/root/.scrypted/docker-compose.yml', yaml.stringify(dockerCompose));
}
async function checkLxcScript() {
const foundDockerComposeSh = await fs.promises.readFile(DOCKER_COMPOSE_SH_PATH, 'utf8');
const dockerComposeSh = await fs.promises.readFile(LXC_DOCKER_COMPOSE_SH_PATH, 'utf8');
@@ -30,6 +147,8 @@ export async function checkLxc() {
return;
}
// console.warn('lxc needs updating', sdk.clusterManager.getClusterWorkerId());
// console.warn(foundDockerComposeSh);
await fs.promises.copyFile(LXC_DOCKER_COMPOSE_SH_PATH, DOCKER_COMPOSE_SH_PATH);
await fs.promises.chmod(DOCKER_COMPOSE_SH_PATH, 0o755);
}
}

View File

@@ -93,7 +93,7 @@ export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, De
let script = new Script(nativeId);
let worker: ForkWorker;
const triggerDeviceDiscover = async (name: string, type: ScryptedDeviceType, interfaces: string[]) => {
const triggerDeviceDiscover = async (name: string, type: ScryptedDeviceType | string, interfaces: string[]) => {
const e = this.scripts.get(nativeId);
if (e?.script == script)
e.script = undefined;

View File

@@ -7,7 +7,7 @@ import { ScriptCoreNativeId } from "./script-core";
const { deviceManager } = sdk;
export class Script extends ScryptedDeviceBase implements Scriptable, Program, ScriptDeviceImpl {
constructor(nativeId: string, public triggerDeviceDiscover?: (name: string, type: ScryptedDeviceType, interfaces: string[]) => Promise<string>) {
constructor(nativeId: string, public triggerDeviceDiscover?: (name: string, type: ScryptedDeviceType | string, interfaces: string[]) => Promise<string>) {
super(nativeId);
}

View File

@@ -1,7 +1,7 @@
import sdk, { ClusterForkInterface, ClusterForkInterfaceOptions, ScryptedDeviceBase, ScryptedInterface, ScryptedNativeId, StreamService, TTYSettings } from "@scrypted/sdk";
import type { IPty, spawn as ptySpawn } from 'node-pty';
import { createAsyncQueue } from '@scrypted/common/src/async-queue'
import { createAsyncQueue } from '@scrypted/common/src/async-queue';
import sdk, { ClusterForkInterface, ClusterForkInterfaceOptions, ScryptedDeviceBase, ScryptedInterface, ScryptedNativeId, StreamService, TTY, TTYSettings } from "@scrypted/sdk";
import { ChildProcess, spawn as childSpawn } from "child_process";
import type { IPty, spawn as ptySpawn } from 'node-pty';
import path from 'path';
export const TerminalServiceNativeId = 'terminalservice';
@@ -19,12 +19,24 @@ function toSpawnPathEnv(paths: string[]): string {
class InteractiveTerminal {
cp: IPty
constructor(cmd: string[], paths: string[], spawn: typeof ptySpawn) {
constructor(cmd: string[], paths: string[], spawn: typeof ptySpawn, cwd?: string) {
const spawnPath = toSpawnPathEnv(paths);
if (cmd?.length) {
this.cp = spawn(cmd[0], cmd.slice(1), { env: { ...process.env, PATH: spawnPath } });
this.cp = spawn(cmd[0], cmd.slice(1), {
env: {
...process.env,
PATH: spawnPath,
},
cwd,
});
} else {
this.cp = spawn(process.env.SHELL as string, [], { env: { ...process.env, PATH: spawnPath } });
this.cp = spawn(process.env.SHELL as string, [], {
env: {
...process.env,
PATH: spawnPath,
},
cwd,
});
}
}
@@ -111,7 +123,7 @@ class NoninteractiveTerminal {
}
export class TerminalService extends ScryptedDeviceBase implements StreamService<Buffer | string, Buffer>, ClusterForkInterface {
export class TerminalService extends ScryptedDeviceBase implements StreamService<Buffer | string, Buffer>, ClusterForkInterface, TTY {
private forks: { [clusterWorkerId: string]: TerminalService } = {};
private forkClients: 0;
@@ -186,7 +198,7 @@ export class TerminalService extends ScryptedDeviceBase implements StreamService
async connectStream(input: AsyncGenerator<Buffer | string, void>, options?: any): Promise<AsyncGenerator<Buffer, void>> {
let cp: InteractiveTerminal | NoninteractiveTerminal = null;
const queue = createAsyncQueue<Buffer>();
const extraPaths = await this.getExtraPaths();
const extraPaths = [...options?.env?.PATH?.split(path.delimiter) || [], ...await this.getExtraPaths()];
if (this.isFork) {
this.forkClients++;
@@ -259,7 +271,7 @@ export class TerminalService extends ScryptedDeviceBase implements StreamService
let spawn: typeof ptySpawn;
try {
spawn = require('@scrypted/node-pty').spawn as typeof ptySpawn;
cp = new InteractiveTerminal(cmd, extraPaths, spawn);
cp = new InteractiveTerminal(cmd, extraPaths, spawn, options?.cwd);
}
catch (e) {
this.console.error('Error starting pty', e);
@@ -278,6 +290,8 @@ export class TerminalService extends ScryptedDeviceBase implements StreamService
}
catch (e) {
this.console.log(e);
}
finally {
cp?.kill();
}
})();

View File

@@ -1,4 +1,4 @@
import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedUser, ScryptedUserAccessControl, Setting, Settings, SettingValue } from "@scrypted/sdk";
import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceManifest, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedUser, ScryptedUserAccessControl, Setting, Settings, SettingValue } from "@scrypted/sdk";
import { addAccessControlsForInterface } from "@scrypted/sdk/acl";
import { StorageSettings } from "@scrypted/sdk/storage-settings";
export const UsersNativeId = 'users';
@@ -111,7 +111,7 @@ export class User extends ScryptedDeviceBase implements Settings, ScryptedUser {
const { username, admin } = user;
const nativeId = `user:${username}`;
const aclId = await sdk.deviceManager.onDeviceDiscovered({
providerNativeId: this.nativeId,
providerNativeId: UsersNativeId,
name: username.toString(),
nativeId,
interfaces: [
@@ -132,7 +132,13 @@ export class UsersCore extends ScryptedDeviceBase implements Readme, DeviceProvi
deviceCreator: 'Scrypted User',
};
this.syncUsers();
this.syncUsers()
.then(length => {
if (!length) {
this.console.log('no users found, looping for first user');
setInterval(() => this.syncUsers(), 60 * 1000);
}
})
}
async getDevice(nativeId: string): Promise<any> {
@@ -192,7 +198,7 @@ export class UsersCore extends ScryptedDeviceBase implements Readme, DeviceProvi
async syncUsers() {
const usersService = await sdk.systemManager.getComponent('users');
const users: DBUser[] = await usersService.getAllUsers();
await sdk.deviceManager.onDevicesChanged({
const manifest: DeviceManifest = {
providerNativeId: this.nativeId,
devices: users.map(user => ({
name: user.username,
@@ -203,6 +209,16 @@ export class UsersCore extends ScryptedDeviceBase implements Readme, DeviceProvi
],
type: ScryptedDeviceType.Person,
})),
})
};
const nativeIds = new Set(manifest.devices.map(d => d.nativeId));
for (const nativeId of sdk.deviceManager.getNativeIds()) {
nativeIds.delete(nativeId);
}
if (nativeIds.size) {
// add any missing users.
await sdk.deviceManager.onDevicesChanged(manifest);
}
return manifest.devices.length;
}
}

View File

@@ -1,34 +1,42 @@
{
"name": "@scrypted/coreml",
"version": "0.1.76",
"version": "0.1.89",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/coreml",
"version": "0.1.76",
"version": "0.1.89",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.77",
"version": "0.5.22",
"dev": true,
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.26.0",
"@babel/preset-typescript": "^7.27.1",
"@rollup/plugin-commonjs": "^28.0.5",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.2",
"@rollup/plugin-virtual": "^3.0.2",
"adm-zip": "^0.5.16",
"axios": "^1.7.7",
"babel-loader": "^9.2.1",
"axios": "^1.10.0",
"babel-loader": "^10.0.0",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"openai": "^5.3.0",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.1",
"rollup": "^4.43.0",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"typescript": "^5.5.4",
"webpack": "^5.95.0",
"ts-loader": "^9.5.2",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"webpack": "^5.99.9",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
@@ -41,11 +49,9 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^22.8.1",
"@types/stringify-object": "^4.0.5",
"stringify-object": "^3.3.0",
"@types/node": "^24.0.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.10"
"typedoc": "^0.28.5"
}
},
"../sdk": {
@@ -60,23 +66,29 @@
"@scrypted/sdk": {
"version": "file:../../sdk",
"requires": {
"@babel/preset-typescript": "^7.26.0",
"@types/node": "^22.8.1",
"@types/stringify-object": "^4.0.5",
"@babel/preset-typescript": "^7.27.1",
"@rollup/plugin-commonjs": "^28.0.5",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.2",
"@rollup/plugin-virtual": "^3.0.2",
"@types/node": "^24.0.1",
"adm-zip": "^0.5.16",
"axios": "^1.7.7",
"babel-loader": "^9.2.1",
"axios": "^1.10.0",
"babel-loader": "^10.0.0",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"openai": "^5.3.0",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.1",
"stringify-object": "^3.3.0",
"rollup": "^4.43.0",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"typedoc": "^0.26.10",
"typescript": "^5.5.4",
"webpack": "^5.95.0",
"tslib": "^2.8.1",
"typedoc": "^0.28.5",
"typescript": "^5.8.3",
"webpack": "^5.99.9",
"webpack-bundle-analyzer": "^4.10.2"
}
}

View File

@@ -33,6 +33,8 @@
"runtime": "python",
"type": "API",
"interfaces": [
"ScryptedSystemDevice",
"DeviceCreator",
"Settings",
"DeviceProvider",
"ClusterForkInterface",
@@ -48,5 +50,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.76"
"version": "0.1.89"
}

View File

@@ -14,6 +14,9 @@ from scrypted_sdk import Setting, SettingValue
from common import yolo
from coreml.face_recognition import CoreMLFaceRecognition
from coreml.custom_detection import CoreMLCustomDetection
from coreml.clip_embedding import CoreMLClipEmbedding
from coreml.segment import CoreMLSegmentation
try:
from coreml.text_recognition import CoreMLTextRecognition
@@ -26,18 +29,11 @@ predictExecutor = concurrent.futures.ThreadPoolExecutor(1, "CoreML-Predict")
availableModels = [
"Default",
"scrypted_yolov10m_320",
"scrypted_yolov10n_320",
"scrypted_yolo_nas_s_320",
"scrypted_yolov9e_320",
"scrypted_yolov9c_320",
"scrypted_yolov9s_320",
"scrypted_yolov9t_320",
"scrypted_yolov6n_320",
"scrypted_yolov6s_320",
"scrypted_yolov8n_320",
"ssdlite_mobilenet_v2",
"yolov4-tiny",
"scrypted_yolov9t_relu_test",
"scrypted_yolov9c_relu",
"scrypted_yolov9m_relu",
"scrypted_yolov9s_relu",
"scrypted_yolov9t_relu",
]
@@ -77,58 +73,24 @@ class CoreMLPlugin(
def __init__(self, nativeId: str | None = None, forked: bool = False):
super().__init__(nativeId=nativeId, forked=forked)
# this used to work but a bug in macos is causing recompilation of the coreml models every time it restarts
# and the cache is not reused and also not cleared until the whole system reboots.
self.periodic_restart = False
self.custom_models = {}
model = self.storage.getItem("model") or "Default"
if model == "Default" or model not in availableModels:
if model != "Default":
self.storage.setItem("model", "Default")
model = "scrypted_yolov9c_320"
self.yolo = "yolo" in model
self.scrypted_yolov10n = "scrypted_yolov10" in model
self.scrypted_yolo_nas = "scrypted_yolo_nas" in model
self.scrypted_yolo = "scrypted_yolo" in model
self.scrypted_model = "scrypted" in model
model_version = "v8"
mlmodel = "model" if self.scrypted_yolo else model
model = "scrypted_yolov9c_relu"
self.modelName = model
print(f"model: {model}")
if not self.yolo:
# todo convert these to mlpackage
modelFile = self.downloadFile(
f"https://github.com/koush/coreml-models/raw/main/{model}/{mlmodel}.mlmodel",
f"{model}.mlmodel",
)
else:
if self.scrypted_yolo:
files = [
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/{mlmodel}.mlmodel",
f"{model}/{model}.mlpackage/Manifest.json",
]
for f in files:
p = self.downloadFile(
f"https://github.com/koush/coreml-models/raw/main/{f}",
f"{model_version}/{f}",
)
modelFile = os.path.dirname(p)
else:
files = [
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/FeatureDescriptions.json",
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/Metadata.json",
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/{mlmodel}.mlmodel",
f"{model}/{model}.mlpackage/Manifest.json",
]
for f in files:
p = self.downloadFile(
f"https://github.com/koush/coreml-models/raw/main/{f}",
f"{model_version}/{f}",
)
modelFile = os.path.dirname(p)
model_path = self.downloadHuggingFaceModelLocalFallback(model)
modelFile = os.path.join(model_path, f"{model}.mlpackage")
print(model_path, modelFile)
self.model = ct.models.MLModel(modelFile)
self.modelspec = self.model.get_spec()
@@ -143,13 +105,15 @@ class CoreMLPlugin(
self.faceDevice = None
self.textDevice = None
self.clipDevice = None
self.segmentDevice = None
if not self.forked:
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
async def prepareRecognitionModels(self):
try:
devices = [
await scrypted_sdk.deviceManager.onDeviceDiscovered(
{
"nativeId": "facerecognition",
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
@@ -159,10 +123,10 @@ class CoreMLPlugin(
],
"name": "CoreML Face Recognition",
},
]
)
if CoreMLTextRecognition:
devices.append(
await scrypted_sdk.deviceManager.onDeviceDiscovered(
{
"nativeId": "textrecognition",
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
@@ -174,9 +138,29 @@ class CoreMLPlugin(
},
)
await scrypted_sdk.deviceManager.onDevicesChanged(
await scrypted_sdk.deviceManager.onDeviceDiscovered(
{
"devices": devices,
"nativeId": "clipembedding",
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
"interfaces": [
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
scrypted_sdk.ScryptedInterface.TextEmbedding.value,
scrypted_sdk.ScryptedInterface.ImageEmbedding.value,
],
"name": "CoreML CLIP Embedding",
}
)
await scrypted_sdk.deviceManager.onDeviceDiscovered(
{
"nativeId": "segment",
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
"interfaces": [
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
],
"name": "CoreML Segmentation",
}
)
except:
@@ -186,10 +170,22 @@ class CoreMLPlugin(
if nativeId == "facerecognition":
self.faceDevice = self.faceDevice or CoreMLFaceRecognition(self, nativeId)
return self.faceDevice
if nativeId == "textrecognition":
elif nativeId == "textrecognition":
self.textDevice = self.textDevice or CoreMLTextRecognition(self, nativeId)
return self.textDevice
raise Exception("unknown device")
elif nativeId == "clipembedding":
self.clipDevice = self.clipDevice or CoreMLClipEmbedding(self, nativeId)
return self.clipDevice
elif nativeId == "segment":
self.segmentDevice = self.segmentDevice or CoreMLSegmentation(self, nativeId)
return self.segmentDevice
custom_model = self.custom_models.get(nativeId, None)
if custom_model:
return custom_model
custom_model = CoreMLCustomDetection(self, nativeId)
self.custom_models[nativeId] = custom_model
await custom_model.reportDevice(nativeId, custom_model.providedName)
return custom_model
async def getSettings(self) -> list[Setting]:
model = self.storage.getItem("model") or "Default"
@@ -222,94 +218,8 @@ class CoreMLPlugin(
return out_dicts
async def detect_once(self, input: Image.Image, settings: Any, src_size, cvss):
objs = []
# run in executor if this is the plugin loop
if self.yolo:
out_dict = await self.queue_batch({self.input_name: input})
if self.scrypted_yolov10n:
results = list(out_dict.values())[0][0]
objs = yolo.parse_yolov10(results)
ret = self.create_detection_result(objs, src_size, cvss)
return ret
if self.scrypted_yolo_nas:
predictions = list(out_dict.values())
objs = yolo.parse_yolo_nas(predictions)
ret = self.create_detection_result(objs, src_size, cvss)
return ret
if self.scrypted_yolo:
results = list(out_dict.values())[0][0]
objs = yolo.parse_yolov9(results)
ret = self.create_detection_result(objs, src_size, cvss)
return ret
out_blob = out_dict["Identity"]
objects = yolo.parse_yolo_region(
out_blob,
(input.width, input.height),
(81, 82, 135, 169, 344, 319),
# (23,27, 37,58, 81,82),
False,
)
for r in objects:
obj = Prediction(
r["classId"],
r["confidence"],
Rectangle(
r["xmin"],
r["ymin"],
r["xmax"],
r["ymax"],
),
)
objs.append(obj)
# what about output[1]?
# 26 26
# objects = yolo.parse_yolo_region(out_blob, (input.width, input.height), (23,27, 37,58, 81,82))
ret = self.create_detection_result(objs, src_size, cvss)
return ret
out_dict = await asyncio.get_event_loop().run_in_executor(
predictExecutor,
lambda: self.model.predict(
{"image": input, "confidenceThreshold": self.minThreshold}
),
)
coordinatesList = out_dict["coordinates"]
for index, confidenceList in enumerate(out_dict["confidence"]):
values = confidenceList
maxConfidenceIndex = max(range(len(values)), key=values.__getitem__)
maxConfidence = confidenceList[maxConfidenceIndex]
if maxConfidence < self.minThreshold:
continue
coordinates = coordinatesList[index]
def torelative(value: float):
return value * self.inputheight
x = torelative(coordinates[0])
y = torelative(coordinates[1])
w = torelative(coordinates[2])
h = torelative(coordinates[3])
w2 = w / 2
h2 = h / 2
l = x - w2
t = y - h2
obj = Prediction(
maxConfidenceIndex, maxConfidence, Rectangle(l, t, l + w, t + h)
)
objs.append(obj)
out_dict = await self.queue_batch({self.input_name: input})
results = list(out_dict.values())[0][0]
objs = yolo.parse_yolov9(results)
ret = self.create_detection_result(objs, src_size, cvss)
return ret

View File

@@ -0,0 +1,85 @@
from __future__ import annotations
import asyncio
import concurrent.futures
import os
from typing import Any
import coremltools as ct
import numpy as np
from PIL import Image
from scrypted_sdk import ObjectsDetected
from predict.clip import ClipEmbedding
class CoreMLClipEmbedding(ClipEmbedding):
def __init__(self, plugin, nativeId: str):
super().__init__(plugin=plugin, nativeId=nativeId)
self.predictExecutor = concurrent.futures.ThreadPoolExecutor(1, "predict-clip")
def getFiles(self):
return [
"text.mlpackage/Manifest.json",
"text.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
"text.mlpackage/Data/com.apple.CoreML/model.mlmodel",
"vision.mlpackage/Manifest.json",
"vision.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
"vision.mlpackage/Data/com.apple.CoreML/model.mlmodel",
]
def loadModel(self, files):
# find the xml file in the files list
text_manifest = [f for f in files if f.lower().endswith('text.mlpackage/manifest.json')]
if not text_manifest:
raise ValueError("No XML model file found in the provided files list")
text_manifest = text_manifest[0]
vision_manifest = [f for f in files if f.lower().endswith('vision.mlpackage/manifest.json')]
if not vision_manifest:
raise ValueError("No XML model file found in the provided files list")
vision_manifest = vision_manifest[0]
textModel = ct.models.MLModel(os.path.dirname(text_manifest))
visionModel = ct.models.MLModel(os.path.dirname(vision_manifest))
return textModel, visionModel
async def detect_once(self, input: Image.Image, settings: Any, src_size, cvss):
def predict():
inputs = self.processor(images=input, return_tensors="np", padding="max_length", truncation=True)
_, vision_model = self.model
vision_predictions = vision_model.predict({'x': inputs['pixel_values']})
image_embeds = vision_predictions['var_877']
# this is a hack to utilize the existing image massaging infrastructure
embedding = bytearray(image_embeds.astype(np.float32).tobytes())
ret: ObjectsDetected = {
"detections": [
{
"embedding": embedding,
}
],
"inputDimensions": src_size
}
return ret
ret = await asyncio.get_event_loop().run_in_executor(
self.predictExecutor, lambda: predict()
)
return ret
async def getTextEmbedding(self, input):
def predict():
inputs = self.processor(text=input, return_tensors="np", padding="max_length", truncation=True)
text_model, _ = self.model
text_predictions = text_model.predict({'input_ids_1': inputs['input_ids'].astype(np.float32), 'attention_mask_1': inputs['attention_mask'].astype(np.float32)})
text_embeds = text_predictions['var_1050']
return bytearray(text_embeds.astype(np.float32).tobytes())
ret = await asyncio.get_event_loop().run_in_executor(
self.predictExecutor, lambda: predict()
)
return ret

View File

@@ -0,0 +1,60 @@
from __future__ import annotations
import asyncio
import concurrent.futures
import os
import coremltools as ct
import numpy as np
import scrypted_sdk
from PIL import Image
from predict.custom_detect import CustomDetection
class CoreMLCustomDetection(CustomDetection):
def __init__(self, plugin, nativeId: str):
super().__init__(plugin=plugin, nativeId=nativeId)
self.prefer_relu = True
self.detectExecutor = concurrent.futures.ThreadPoolExecutor(1, "detect-custom")
def loadModel(self, files: list[str]):
# find the xml file in the files list
manifest_files = [f for f in files if f.lower().endswith('manifest.json')]
if not manifest_files:
raise ValueError("No Manifest.json file found in the provided files list")
manifest_file = manifest_files[0]
modelFile = os.path.dirname(manifest_file)
model = ct.models.MLModel(modelFile)
inputName = model.get_spec().description.input[0].name
return model, inputName
async def predictModel(self, input: Image.Image) -> scrypted_sdk.ObjectsDetected:
model, inputName = self.model
def predict():
if self.model_config.get("mean", None) and self.model_config.get("std", None):
im = np.array(input)
im = im.astype(np.float32) / 255.0
mean = np.array(self.model_config.get("mean", None), dtype=np.float32)
std = np.array(self.model_config.get("std", None), dtype=np.float32)
im = (im - mean) / std
# Convert HWC to CHW
im = im.transpose(2, 0, 1) # Channels first
im = im.astype(np.float32)
im = np.ascontiguousarray(im)
im = np.expand_dims(im, axis=0)
out_dict = model.predict({inputName: im})
else:
out_dict = model.predict({inputName: input})
results = list(out_dict.values())[0][0]
return results
results = await asyncio.get_event_loop().run_in_executor(
self.detectExecutor, lambda: predict()
)
return results

Some files were not shown because too many files have changed in this diff Show More