feat(ocr): add a Mistral-based OCR adapter for KVM frames
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## Pending
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- add a Mistral-based OCR adapter for KVM frames (ocr)
|
||||||
|
- introduces createMistralKvmOcrEngine backed by @push.rocks/smartai/ocr
|
||||||
|
- exports the new OCR adapter from the public package entrypoint
|
||||||
|
- adds tests for frame-to-image adaptation and rejecting unsupported crop options
|
||||||
|
- documents the bundled OCR integration and updated package capabilities
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
"@types/node": "^25.6.0"
|
"@types/node": "^25.6.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@push.rocks/smartai": "^4.1.0",
|
||||||
"puppeteer": "^24.42.0"
|
"puppeteer": "^24.42.0"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.28.2",
|
"packageManager": "pnpm@10.28.2",
|
||||||
|
|||||||
Generated
+270
@@ -8,6 +8,9 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@push.rocks/smartai':
|
||||||
|
specifier: ^4.1.0
|
||||||
|
version: 4.1.0(typescript@6.0.3)(ws@8.20.0)(zod@3.25.76)
|
||||||
puppeteer:
|
puppeteer:
|
||||||
specifier: ^24.42.0
|
specifier: ^24.42.0
|
||||||
version: 24.43.0(typescript@6.0.3)
|
version: 24.43.0(typescript@6.0.3)
|
||||||
@@ -27,6 +30,79 @@ importers:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
'@ai-sdk/anthropic@3.0.78':
|
||||||
|
resolution: {integrity: sha512-0OY12G20cUt6iU6htpEA1491Oz++NVxZxlmWGX4B7rSbeZ5pnDmOu6YtW9BKzdZlNx5Gn23i6WMxyZFoMKNcgA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
|
||||||
|
'@ai-sdk/gateway@3.0.115':
|
||||||
|
resolution: {integrity: sha512-xonmGfN9pt54WdKqMzWe68BRYS3rsYvraBzioyA0gfNcecHs8Ir5qk/X8grJSyZ95hghjWiOphrK6bAc11E6SA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
|
||||||
|
'@ai-sdk/google@3.0.75':
|
||||||
|
resolution: {integrity: sha512-XAm31ftiOrzlb8NjDzT7kw0xw+4lmgFdGFn1QKM73nXFFKyN1kWLESBV75UGNfjXP8X1YJ0YydnMVqO0jaPghw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
|
||||||
|
'@ai-sdk/groq@3.0.39':
|
||||||
|
resolution: {integrity: sha512-BZAr6DjCbzWQ0Qn1/TSsHo/bmCt4JaAMb4A7HCSUZBQCAcOjne/03D0sVjHnQhUC3TpwcmYiv7tHAviK7BluRw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
|
||||||
|
'@ai-sdk/mistral@3.0.37':
|
||||||
|
resolution: {integrity: sha512-KkdaMjs4C2y+vrZWJE990E3ZxBFiOTHQ94ZlquuIttpphcqJTMxNoIpnKT/4UzMVWXL0BUEE2vs+1UEVXkN8Kg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
|
||||||
|
'@ai-sdk/openai-compatible@2.0.47':
|
||||||
|
resolution: {integrity: sha512-Enm5UlL0zUCrW3792opk5h7hRWxZOZzDe6eQYVFqX9LUOGGCe1h8MZWAGim765nwzgnjlpeYOsuzZmLtRsTPlg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
|
||||||
|
'@ai-sdk/openai@3.0.64':
|
||||||
|
resolution: {integrity: sha512-epO4iS6QwktaY2PF6uBcPnDTJ3BxPOfsGS7/OEtBe3GtNj7C8h8gMDVtIe5K8W16HNDbn0tbR4dcQfpfs+XVFg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
|
||||||
|
'@ai-sdk/perplexity@3.0.33':
|
||||||
|
resolution: {integrity: sha512-aNt6pTAzq+akadDXVdg2SjN2dODtaVlkKbw8/35c+sekr+Tx0sJwVqMR1udxrjLzhQvz8qtfsWRuz+hB9pmOnQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
|
||||||
|
'@ai-sdk/provider-utils@4.0.27':
|
||||||
|
resolution: {integrity: sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
|
||||||
|
'@ai-sdk/provider@3.0.10':
|
||||||
|
resolution: {integrity: sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@ai-sdk/xai@3.0.91':
|
||||||
|
resolution: {integrity: sha512-7zhOCe7hoLfJ5VMTLejzAjBuhaBWIakfd6ZBeQBveshP+DKAPTbl08wTVqnjRLqP1Dw2zN2oT3eyz1EtiFzyBg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
|
||||||
|
'@anthropic-ai/sdk@0.95.2':
|
||||||
|
resolution: {integrity: sha512-Egddwo3sheo1PzUrMkZnH6VkQYwS0h/b/i8vSK8Ta9M45UQipAMeDFH57dYuDAfXMEUUGeKw6CMlremgMZgrSQ==}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.0 || ^4.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
zod:
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@api.global/typedrequest-interfaces@2.0.2':
|
'@api.global/typedrequest-interfaces@2.0.2':
|
||||||
resolution: {integrity: sha512-D+mkr4IiUZ/eUgrdp5jXjBKOW/iuMcl0z2ZLQsLLypKX/psFGD3viZJ58FNRa+/1OSM38JS5wFyoWl8oPEFLrw==}
|
resolution: {integrity: sha512-D+mkr4IiUZ/eUgrdp5jXjBKOW/iuMcl0z2ZLQsLLypKX/psFGD3viZJ58FNRa+/1OSM38JS5wFyoWl8oPEFLrw==}
|
||||||
|
|
||||||
@@ -908,6 +984,10 @@ packages:
|
|||||||
'@nodable/entities@2.1.0':
|
'@nodable/entities@2.1.0':
|
||||||
resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==}
|
resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==}
|
||||||
|
|
||||||
|
'@opentelemetry/api@1.9.1':
|
||||||
|
resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
'@oxc-project/types@0.129.0':
|
'@oxc-project/types@0.129.0':
|
||||||
resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==}
|
resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==}
|
||||||
|
|
||||||
@@ -992,6 +1072,9 @@ packages:
|
|||||||
'@push.rocks/qenv@6.1.4':
|
'@push.rocks/qenv@6.1.4':
|
||||||
resolution: {integrity: sha512-NlDwrb3KJVBCeEXIWaYRZXZLOvHhDoo+n2X5akcGCDjn5HyP0C9/opn2RDpCnSt+hoValKpp89wcX4BEB+gWjA==}
|
resolution: {integrity: sha512-NlDwrb3KJVBCeEXIWaYRZXZLOvHhDoo+n2X5akcGCDjn5HyP0C9/opn2RDpCnSt+hoValKpp89wcX4BEB+gWjA==}
|
||||||
|
|
||||||
|
'@push.rocks/smartai@4.1.0':
|
||||||
|
resolution: {integrity: sha512-yUOwPegR+H31B1dEoCXOha2LfScTcyqSxD8h32gcEdfyGwc/e0B1W3GFFUs/JlXAX7jWt6CxumF1zNFEgxiL2g==}
|
||||||
|
|
||||||
'@push.rocks/smartarchive@5.2.2':
|
'@push.rocks/smartarchive@5.2.2':
|
||||||
resolution: {integrity: sha512-EEh3X5f5EAERx6qYmqPFsAAWYSlodmEYxFTKsa4jUK4AFb5Dn/vK5Jsx2A46PKriu8mQJIMEfGWrkLU4kTi5tw==}
|
resolution: {integrity: sha512-EEh3X5f5EAERx6qYmqPFsAAWYSlodmEYxFTKsa4jUK4AFb5Dn/vK5Jsx2A46PKriu8mQJIMEfGWrkLU4kTi5tw==}
|
||||||
|
|
||||||
@@ -1513,6 +1596,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-7kAlrB3n7/BHyw+uLq83d5jdadPUcDkdMOUSGxvpXjrJ++G0hTedTnoNChjybIxhZ/Gk7sCrfIOLkMAB0LhRBA==}
|
resolution: {integrity: sha512-7kAlrB3n7/BHyw+uLq83d5jdadPUcDkdMOUSGxvpXjrJ++G0hTedTnoNChjybIxhZ/Gk7sCrfIOLkMAB0LhRBA==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@stablelib/base64@1.0.1':
|
||||||
|
resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==}
|
||||||
|
|
||||||
|
'@standard-schema/spec@1.1.0':
|
||||||
|
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||||
|
|
||||||
'@tempfix/lenis@1.3.20':
|
'@tempfix/lenis@1.3.20':
|
||||||
resolution: {integrity: sha512-ypeB0FuHLHOCQXW4d0RQ69txPJJH+1CHcpsZIUdcv2t1vR0IVyQr2vHihtde9UOXhjzqEnUphWon/UcJNsa0YA==}
|
resolution: {integrity: sha512-ypeB0FuHLHOCQXW4d0RQ69txPJJH+1CHcpsZIUdcv2t1vR0IVyQr2vHihtde9UOXhjzqEnUphWon/UcJNsa0YA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1633,6 +1722,10 @@ packages:
|
|||||||
'@ungap/structured-clone@1.3.1':
|
'@ungap/structured-clone@1.3.1':
|
||||||
resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==}
|
resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==}
|
||||||
|
|
||||||
|
'@vercel/oidc@3.2.0':
|
||||||
|
resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==}
|
||||||
|
engines: {node: '>= 20'}
|
||||||
|
|
||||||
acme-client@5.4.0:
|
acme-client@5.4.0:
|
||||||
resolution: {integrity: sha512-mORqg60S8iML6XSmVjqjGHJkINrCGLMj2QvDmFzI9vIlv1RGlyjmw3nrzaINJjkNsYXC41XhhD5pfy7CtuGcbA==}
|
resolution: {integrity: sha512-mORqg60S8iML6XSmVjqjGHJkINrCGLMj2QvDmFzI9vIlv1RGlyjmw3nrzaINJjkNsYXC41XhhD5pfy7CtuGcbA==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
@@ -1645,6 +1738,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
|
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
|
||||||
engines: {node: '>= 8.0.0'}
|
engines: {node: '>= 8.0.0'}
|
||||||
|
|
||||||
|
ai@6.0.184:
|
||||||
|
resolution: {integrity: sha512-j//zHkKvj5ra27l8izHco8cj1g1Pr7vx1ZK+hrzrkHvndgIRmdfZKOb6+RAPpvbk42qGIsuYvlYbGlVAu3erNQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
zod: ^3.25.76 || ^4.1.8
|
||||||
|
|
||||||
ansi-256-colors@1.1.0:
|
ansi-256-colors@1.1.0:
|
||||||
resolution: {integrity: sha1-kQ3lDvzHwJ49gvL4er1rcAwYgYo=}
|
resolution: {integrity: sha1-kQ3lDvzHwJ49gvL4er1rcAwYgYo=}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2004,6 +2103,10 @@ packages:
|
|||||||
events-universal@1.0.1:
|
events-universal@1.0.1:
|
||||||
resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
|
resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
|
||||||
|
|
||||||
|
eventsource-parser@3.0.8:
|
||||||
|
resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
exif-parser@0.1.12:
|
exif-parser@0.1.12:
|
||||||
resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==}
|
resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==}
|
||||||
|
|
||||||
@@ -2032,6 +2135,9 @@ packages:
|
|||||||
fast-json-stable-stringify@2.1.0:
|
fast-json-stable-stringify@2.1.0:
|
||||||
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||||
|
|
||||||
|
fast-sha256@1.3.0:
|
||||||
|
resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==}
|
||||||
|
|
||||||
fast-xml-builder@1.2.0:
|
fast-xml-builder@1.2.0:
|
||||||
resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==}
|
resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==}
|
||||||
|
|
||||||
@@ -2275,6 +2381,13 @@ packages:
|
|||||||
json-parse-even-better-errors@2.3.1:
|
json-parse-even-better-errors@2.3.1:
|
||||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||||
|
|
||||||
|
json-schema-to-ts@3.1.1:
|
||||||
|
resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
json-schema@0.4.0:
|
||||||
|
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
|
||||||
|
|
||||||
ky@1.14.3:
|
ky@1.14.3:
|
||||||
resolution: {integrity: sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==}
|
resolution: {integrity: sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -2587,6 +2700,18 @@ packages:
|
|||||||
once@1.4.0:
|
once@1.4.0:
|
||||||
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
|
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
|
||||||
|
|
||||||
|
openai@6.38.0:
|
||||||
|
resolution: {integrity: sha512-AoMplt2UalrpgUDMh3L09QWjNRlgJPipclQvA6sYAaeF6nHNBMgmikAZGmcYLn8on4d9sQY9Q8bOLfrBS7Lc8g==}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
ws: ^8.18.0
|
||||||
|
zod: ^3.25 || ^4.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
ws:
|
||||||
|
optional: true
|
||||||
|
zod:
|
||||||
|
optional: true
|
||||||
|
|
||||||
os-tmpdir@1.0.2:
|
os-tmpdir@1.0.2:
|
||||||
resolution: {integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=}
|
resolution: {integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2905,6 +3030,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-z+s5vv4KzFPJVddGab0xX2n7kQPGMdNUX5l9T8EJqsXdKTWpcxmAqWHpsgHEXoC1taGBCc7b79bi62M5kdbrxQ==}
|
resolution: {integrity: sha512-z+s5vv4KzFPJVddGab0xX2n7kQPGMdNUX5l9T8EJqsXdKTWpcxmAqWHpsgHEXoC1taGBCc7b79bi62M5kdbrxQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
standardwebhooks@1.0.0:
|
||||||
|
resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==}
|
||||||
|
|
||||||
streamx@2.25.0:
|
streamx@2.25.0:
|
||||||
resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==}
|
resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==}
|
||||||
|
|
||||||
@@ -2997,6 +3125,9 @@ packages:
|
|||||||
trough@2.2.0:
|
trough@2.2.0:
|
||||||
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
|
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
|
||||||
|
|
||||||
|
ts-algebra@2.0.0:
|
||||||
|
resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==}
|
||||||
|
|
||||||
tslib@1.14.1:
|
tslib@1.14.1:
|
||||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||||
|
|
||||||
@@ -3193,6 +3324,80 @@ packages:
|
|||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
|
'@ai-sdk/anthropic@3.0.78(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 3.0.10
|
||||||
|
'@ai-sdk/provider-utils': 4.0.27(zod@3.25.76)
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
|
'@ai-sdk/gateway@3.0.115(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 3.0.10
|
||||||
|
'@ai-sdk/provider-utils': 4.0.27(zod@3.25.76)
|
||||||
|
'@vercel/oidc': 3.2.0
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
|
'@ai-sdk/google@3.0.75(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 3.0.10
|
||||||
|
'@ai-sdk/provider-utils': 4.0.27(zod@3.25.76)
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
|
'@ai-sdk/groq@3.0.39(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 3.0.10
|
||||||
|
'@ai-sdk/provider-utils': 4.0.27(zod@3.25.76)
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
|
'@ai-sdk/mistral@3.0.37(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 3.0.10
|
||||||
|
'@ai-sdk/provider-utils': 4.0.27(zod@3.25.76)
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
|
'@ai-sdk/openai-compatible@2.0.47(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 3.0.10
|
||||||
|
'@ai-sdk/provider-utils': 4.0.27(zod@3.25.76)
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
|
'@ai-sdk/openai@3.0.64(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 3.0.10
|
||||||
|
'@ai-sdk/provider-utils': 4.0.27(zod@3.25.76)
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
|
'@ai-sdk/perplexity@3.0.33(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 3.0.10
|
||||||
|
'@ai-sdk/provider-utils': 4.0.27(zod@3.25.76)
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
|
'@ai-sdk/provider-utils@4.0.27(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/provider': 3.0.10
|
||||||
|
'@standard-schema/spec': 1.1.0
|
||||||
|
eventsource-parser: 3.0.8
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
|
'@ai-sdk/provider@3.0.10':
|
||||||
|
dependencies:
|
||||||
|
json-schema: 0.4.0
|
||||||
|
|
||||||
|
'@ai-sdk/xai@3.0.91(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/openai-compatible': 2.0.47(zod@3.25.76)
|
||||||
|
'@ai-sdk/provider': 3.0.10
|
||||||
|
'@ai-sdk/provider-utils': 4.0.27(zod@3.25.76)
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
|
'@anthropic-ai/sdk@0.95.2(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
json-schema-to-ts: 3.1.1
|
||||||
|
standardwebhooks: 1.0.0
|
||||||
|
optionalDependencies:
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
'@api.global/typedrequest-interfaces@2.0.2': {}
|
'@api.global/typedrequest-interfaces@2.0.2': {}
|
||||||
|
|
||||||
'@api.global/typedrequest-interfaces@3.0.19': {}
|
'@api.global/typedrequest-interfaces@3.0.19': {}
|
||||||
@@ -4455,6 +4660,8 @@ snapshots:
|
|||||||
|
|
||||||
'@nodable/entities@2.1.0': {}
|
'@nodable/entities@2.1.0': {}
|
||||||
|
|
||||||
|
'@opentelemetry/api@1.9.1': {}
|
||||||
|
|
||||||
'@oxc-project/types@0.129.0': {}
|
'@oxc-project/types@0.129.0': {}
|
||||||
|
|
||||||
'@pdf-lib/standard-fonts@1.0.0':
|
'@pdf-lib/standard-fonts@1.0.0':
|
||||||
@@ -4644,6 +4851,32 @@ snapshots:
|
|||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
yaml: 2.8.4
|
yaml: 2.8.4
|
||||||
|
|
||||||
|
'@push.rocks/smartai@4.1.0(typescript@6.0.3)(ws@8.20.0)(zod@3.25.76)':
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/anthropic': 3.0.78(zod@3.25.76)
|
||||||
|
'@ai-sdk/google': 3.0.75(zod@3.25.76)
|
||||||
|
'@ai-sdk/groq': 3.0.39(zod@3.25.76)
|
||||||
|
'@ai-sdk/mistral': 3.0.37(zod@3.25.76)
|
||||||
|
'@ai-sdk/openai': 3.0.64(zod@3.25.76)
|
||||||
|
'@ai-sdk/perplexity': 3.0.33(zod@3.25.76)
|
||||||
|
'@ai-sdk/provider': 3.0.10
|
||||||
|
'@ai-sdk/xai': 3.0.91(zod@3.25.76)
|
||||||
|
'@anthropic-ai/sdk': 0.95.2(zod@3.25.76)
|
||||||
|
'@push.rocks/smartpdf': 4.2.2(typescript@6.0.3)
|
||||||
|
ai: 6.0.184(zod@3.25.76)
|
||||||
|
openai: 6.38.0(ws@8.20.0)(zod@3.25.76)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- aws-crt
|
||||||
|
- bare-abort-controller
|
||||||
|
- bare-buffer
|
||||||
|
- bufferutil
|
||||||
|
- react-native-b4a
|
||||||
|
- supports-color
|
||||||
|
- typescript
|
||||||
|
- utf-8-validate
|
||||||
|
- ws
|
||||||
|
- zod
|
||||||
|
|
||||||
'@push.rocks/smartarchive@5.2.2':
|
'@push.rocks/smartarchive@5.2.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartdelay': 3.1.0
|
'@push.rocks/smartdelay': 3.1.0
|
||||||
@@ -5531,6 +5764,10 @@ snapshots:
|
|||||||
'@smithy/core': 3.24.0
|
'@smithy/core': 3.24.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@stablelib/base64@1.0.1': {}
|
||||||
|
|
||||||
|
'@standard-schema/spec@1.1.0': {}
|
||||||
|
|
||||||
'@tempfix/lenis@1.3.20': {}
|
'@tempfix/lenis@1.3.20': {}
|
||||||
|
|
||||||
'@tokenizer/inflate@0.4.1':
|
'@tokenizer/inflate@0.4.1':
|
||||||
@@ -5647,6 +5884,8 @@ snapshots:
|
|||||||
|
|
||||||
'@ungap/structured-clone@1.3.1': {}
|
'@ungap/structured-clone@1.3.1': {}
|
||||||
|
|
||||||
|
'@vercel/oidc@3.2.0': {}
|
||||||
|
|
||||||
acme-client@5.4.0:
|
acme-client@5.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@peculiar/x509': 1.14.3
|
'@peculiar/x509': 1.14.3
|
||||||
@@ -5663,6 +5902,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
humanize-ms: 1.2.1
|
humanize-ms: 1.2.1
|
||||||
|
|
||||||
|
ai@6.0.184(zod@3.25.76):
|
||||||
|
dependencies:
|
||||||
|
'@ai-sdk/gateway': 3.0.115(zod@3.25.76)
|
||||||
|
'@ai-sdk/provider': 3.0.10
|
||||||
|
'@ai-sdk/provider-utils': 4.0.27(zod@3.25.76)
|
||||||
|
'@opentelemetry/api': 1.9.1
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
ansi-256-colors@1.1.0: {}
|
ansi-256-colors@1.1.0: {}
|
||||||
|
|
||||||
ansi-escapes@4.3.2:
|
ansi-escapes@4.3.2:
|
||||||
@@ -6026,6 +6273,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bare-abort-controller
|
- bare-abort-controller
|
||||||
|
|
||||||
|
eventsource-parser@3.0.8: {}
|
||||||
|
|
||||||
exif-parser@0.1.12: {}
|
exif-parser@0.1.12: {}
|
||||||
|
|
||||||
extend@3.0.2: {}
|
extend@3.0.2: {}
|
||||||
@@ -6054,6 +6303,8 @@ snapshots:
|
|||||||
|
|
||||||
fast-json-stable-stringify@2.1.0: {}
|
fast-json-stable-stringify@2.1.0: {}
|
||||||
|
|
||||||
|
fast-sha256@1.3.0: {}
|
||||||
|
|
||||||
fast-xml-builder@1.2.0:
|
fast-xml-builder@1.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-expression-matcher: 1.5.0
|
path-expression-matcher: 1.5.0
|
||||||
@@ -6365,6 +6616,13 @@ snapshots:
|
|||||||
|
|
||||||
json-parse-even-better-errors@2.3.1: {}
|
json-parse-even-better-errors@2.3.1: {}
|
||||||
|
|
||||||
|
json-schema-to-ts@3.1.1:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.28.6
|
||||||
|
ts-algebra: 2.0.0
|
||||||
|
|
||||||
|
json-schema@0.4.0: {}
|
||||||
|
|
||||||
ky@1.14.3: {}
|
ky@1.14.3: {}
|
||||||
|
|
||||||
lines-and-columns@1.2.4: {}
|
lines-and-columns@1.2.4: {}
|
||||||
@@ -6858,6 +7116,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
wrappy: 1.0.2
|
wrappy: 1.0.2
|
||||||
|
|
||||||
|
openai@6.38.0(ws@8.20.0)(zod@3.25.76):
|
||||||
|
optionalDependencies:
|
||||||
|
ws: 8.20.0
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
os-tmpdir@1.0.2: {}
|
os-tmpdir@1.0.2: {}
|
||||||
|
|
||||||
p-finally@1.0.0: {}
|
p-finally@1.0.0: {}
|
||||||
@@ -7255,6 +7518,11 @@ snapshots:
|
|||||||
signal-exit: 3.0.7
|
signal-exit: 3.0.7
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
|
|
||||||
|
standardwebhooks@1.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@stablelib/base64': 1.0.1
|
||||||
|
fast-sha256: 1.3.0
|
||||||
|
|
||||||
streamx@2.25.0:
|
streamx@2.25.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
events-universal: 1.0.1
|
events-universal: 1.0.1
|
||||||
@@ -7381,6 +7649,8 @@ snapshots:
|
|||||||
|
|
||||||
trough@2.2.0: {}
|
trough@2.2.0: {}
|
||||||
|
|
||||||
|
ts-algebra@2.0.0: {}
|
||||||
|
|
||||||
tslib@1.14.1: {}
|
tslib@1.14.1: {}
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|||||||
@@ -1,6 +1,37 @@
|
|||||||
# @push.rocks/smartkvm
|
# @push.rocks/smartkvm
|
||||||
|
|
||||||
Programmable browser-based visual KVM automation with frame capture and keyboard transport.
|
🎛️ Programmable visual KVM automation for browser-based KVM devices.
|
||||||
|
|
||||||
|
`@push.rocks/smartkvm` turns a remote machine that only exposes a visual KVM UI into a clean TypeScript control surface: capture frames in, send keyboard events out, and optionally run terminal commands through OCR-readable command wrappers.
|
||||||
|
|
||||||
|
Its core interfaces are transport-focused and model-agnostic, while the package also ships a ready-to-use Mistral OCR adapter via `@push.rocks/smartai/ocr` for teams that want a working OCR path out of the box.
|
||||||
|
|
||||||
|
## Issue Reporting and Security
|
||||||
|
|
||||||
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||||
|
|
||||||
|
## What It Does
|
||||||
|
|
||||||
|
Many machines behind visual KVMs do not expose SSH, RDP, WinRM, a PTY, or any native management plane. The only reliable automation channel is often this:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
video feed in
|
||||||
|
keyboard events out
|
||||||
|
```
|
||||||
|
|
||||||
|
`smartkvm` makes that channel programmable.
|
||||||
|
|
||||||
|
The current driver opens the KVM web UI with Puppeteer, focuses the viewer, captures frames from a `video`, `canvas`, or viewer wrapper, and sends keyboard input through Chromium. The public interfaces are generic, so future drivers can target JetKVM native WebRTC/data channels, PiKVM-style APIs, TinyPilot, GL.iNet Comet, or HDMI capture plus USB HID without changing consumer code.
|
||||||
|
|
||||||
|
## Highlights
|
||||||
|
|
||||||
|
- 🚀 `SmartBrowserKvm` for Puppeteer-powered browser KVM automation.
|
||||||
|
- 📸 Frame capture as base64 PNG from `video`, `canvas`, or element screenshots.
|
||||||
|
- ⌨️ Keyboard transport with text typing, individual keys, and shortcuts.
|
||||||
|
- 🧠 Pluggable OCR interface plus a Mistral OCR adapter powered by `@push.rocks/smartai/ocr`.
|
||||||
|
- 🧪 Terminal command wrappers with start/end markers and exit code parsing.
|
||||||
|
- 🧰 Minimal SmartAgent-compatible tools without importing `@push.rocks/smartagent`.
|
||||||
|
- 🔌 Generic `IKvmDriver` abstraction ready for non-Puppeteer drivers later.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@@ -8,24 +39,30 @@ Programmable browser-based visual KVM automation with frame capture and keyboard
|
|||||||
pnpm add @push.rocks/smartkvm
|
pnpm add @push.rocks/smartkvm
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Mental Model
|
||||||
|
|
||||||
|
`smartkvm` has three layers:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
SmartBrowserKvm
|
||||||
|
opens the KVM web UI, captures frames, sends keyboard events
|
||||||
|
|
||||||
|
SmartKvmTerminal
|
||||||
|
uses any IKvmDriver plus any IOcrEngine to type wrapped commands and parse OCR text
|
||||||
|
|
||||||
|
createMistralKvmOcrEngine()
|
||||||
|
provides a ready IOcrEngine backed by Mistral Document AI OCR
|
||||||
|
|
||||||
|
createSmartKvmTools()
|
||||||
|
exposes terminal actions as small tool objects for agent frameworks
|
||||||
|
```
|
||||||
|
|
||||||
|
The package deliberately does not implement mouse automation APIs, native JetKVM/WebRTC, native PiKVM APIs, terminal region auto-detection, or keyboard layout detection. The core still accepts any `IOcrEngine`; the Mistral adapter is just the first bundled OCR implementation.
|
||||||
|
|
||||||
|
## Quick Start: Browser KVM Transport
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import {
|
import { SmartBrowserKvm } from '@push.rocks/smartkvm';
|
||||||
SmartBrowserKvm,
|
|
||||||
SmartKvmTerminal,
|
|
||||||
type IOcrEngine,
|
|
||||||
} from '@push.rocks/smartkvm';
|
|
||||||
|
|
||||||
const ocrEngine: IOcrEngine = {
|
|
||||||
async recognize(frame) {
|
|
||||||
// Plug in an OCR implementation here.
|
|
||||||
return {
|
|
||||||
text: '',
|
|
||||||
confidence: 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const kvm = new SmartBrowserKvm({
|
const kvm = new SmartBrowserKvm({
|
||||||
url: 'https://jetkvm.local',
|
url: 'https://jetkvm.local',
|
||||||
@@ -38,30 +75,410 @@ const kvm = new SmartBrowserKvm({
|
|||||||
|
|
||||||
await kvm.connect();
|
await kvm.connect();
|
||||||
|
|
||||||
|
await kvm.typeText('hello from smartkvm');
|
||||||
|
await kvm.pressKey('Enter');
|
||||||
|
|
||||||
|
const frame = await kvm.captureFrame();
|
||||||
|
console.log(frame.mimeType, frame.width, frame.height, frame.dataBase64.slice(0, 32));
|
||||||
|
|
||||||
|
await kvm.disconnect();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start: Terminal With Mistral OCR
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
SmartBrowserKvm,
|
||||||
|
SmartKvmTerminal,
|
||||||
|
createMistralKvmOcrEngine,
|
||||||
|
} from '@push.rocks/smartkvm';
|
||||||
|
|
||||||
|
const kvm = new SmartBrowserKvm({
|
||||||
|
url: 'https://jetkvm.local',
|
||||||
|
kind: 'jetkvm',
|
||||||
|
headless: false,
|
||||||
|
ignoreHttpsErrors: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await kvm.connect();
|
||||||
|
|
||||||
|
const terminal = new SmartKvmTerminal({
|
||||||
|
kvm,
|
||||||
|
ocrEngine: createMistralKvmOcrEngine({
|
||||||
|
apiKey: process.env.MISTRAL_API_KEY,
|
||||||
|
}),
|
||||||
|
osHint: 'linux',
|
||||||
|
shellHint: 'bash',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await terminal.runCommand('uname -a');
|
||||||
|
console.log(result.combinedText);
|
||||||
|
|
||||||
|
await kvm.disconnect();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start: Terminal Commands Through OCR
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
SmartBrowserKvm,
|
||||||
|
SmartKvmTerminal,
|
||||||
|
type IOcrEngine,
|
||||||
|
} from '@push.rocks/smartkvm';
|
||||||
|
|
||||||
|
const ocrEngine: IOcrEngine = {
|
||||||
|
async recognize(frame, options) {
|
||||||
|
// Plug in your OCR engine here, for example Tesseract, a local OCR service,
|
||||||
|
// a screenshot OCR pipeline, or any implementation that returns text.
|
||||||
|
return {
|
||||||
|
text: '',
|
||||||
|
confidence: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const kvm = new SmartBrowserKvm({
|
||||||
|
url: 'https://some-kvm.local',
|
||||||
|
kind: 'generic',
|
||||||
|
headless: false,
|
||||||
|
ignoreHttpsErrors: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await kvm.connect();
|
||||||
|
|
||||||
const terminal = new SmartKvmTerminal({
|
const terminal = new SmartKvmTerminal({
|
||||||
kvm,
|
kvm,
|
||||||
ocrEngine,
|
ocrEngine,
|
||||||
osHint: 'linux',
|
osHint: 'linux',
|
||||||
shellHint: 'bash',
|
shellHint: 'bash',
|
||||||
|
commandTimeoutMs: 30_000,
|
||||||
|
ocrPollIntervalMs: 500,
|
||||||
});
|
});
|
||||||
|
|
||||||
await terminal.bootstrap();
|
await terminal.bootstrap();
|
||||||
const result = await terminal.runCommand('uname -a');
|
|
||||||
console.log(result);
|
const result = await terminal.runCommand('pwd');
|
||||||
|
|
||||||
|
if (result.completed) {
|
||||||
|
console.log('exit:', result.exitCode);
|
||||||
|
console.log(result.combinedText);
|
||||||
|
} else {
|
||||||
|
console.log('command timed out');
|
||||||
|
console.log(result.rawOcrText);
|
||||||
|
}
|
||||||
|
|
||||||
await kvm.disconnect();
|
await kvm.disconnect();
|
||||||
```
|
```
|
||||||
|
|
||||||
## Scope
|
## SmartBrowserKvm
|
||||||
|
|
||||||
This package is transport-focused and AI-agnostic. It automates browser-based visual KVM devices by opening the KVM web UI, focusing the viewer, capturing frames, and sending keyboard input. OCR and AI/model integrations are intentionally pluggable and external.
|
`SmartBrowserKvm` implements `IKvmDriver` with Puppeteer.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const kvm = new SmartBrowserKvm({
|
||||||
|
url: 'https://kvm.local',
|
||||||
|
kind: 'jetkvm',
|
||||||
|
username: 'admin',
|
||||||
|
password: 'admin',
|
||||||
|
headless: false,
|
||||||
|
viewerSelector: 'video, canvas',
|
||||||
|
captureSelector: 'video, canvas',
|
||||||
|
ignoreHttpsErrors: true,
|
||||||
|
userDataDir: '.nogit/kvm-profile',
|
||||||
|
executablePath: '/usr/bin/chromium',
|
||||||
|
timeoutMs: 30_000,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Browser Options
|
||||||
|
|
||||||
|
- `url`: the KVM web UI URL.
|
||||||
|
- `kind`: optional device hint, one of `jetkvm`, `glinet`, `pikvm`, `tinypilot`, or `generic`.
|
||||||
|
- `username` and `password`: optional credentials for generic login forms.
|
||||||
|
- `headless`: Puppeteer headless mode, defaults to `true`.
|
||||||
|
- `viewerSelector`: element that receives keyboard focus, defaults to `video, canvas`.
|
||||||
|
- `captureSelector`: element used for capture, defaults to `viewerSelector` and then `video, canvas`.
|
||||||
|
- `ignoreHttpsErrors`: accepts self-signed KVM certificates through Puppeteer `acceptInsecureCerts`.
|
||||||
|
- `userDataDir`: persists cookies and login state.
|
||||||
|
- `executablePath`: uses a specific Chromium or Chrome binary.
|
||||||
|
- `timeoutMs`: initial page load and viewer detection timeout, defaults to `30000`.
|
||||||
|
|
||||||
|
### Connection Flow
|
||||||
|
|
||||||
|
`connect()` launches Chromium, opens `url`, attempts a generic login when credentials are supplied, waits for the viewer to become ready, and clicks the viewer once to focus it.
|
||||||
|
|
||||||
|
Generic login looks for common username and password fields, including `input[name="username"]`, `input[autocomplete="username"]`, `input[type="email"]`, `input[type="text"]`, `input[name="password"]`, and `input[type="password"]`. If matching fields are not found, login is skipped silently.
|
||||||
|
|
||||||
|
Viewer readiness accepts direct `video`, direct `canvas`, a wrapper containing either, or a generic visible wrapper with a non-zero bounding box. Video readiness requires `videoWidth` and `videoHeight`; canvas readiness requires `width` and `height`.
|
||||||
|
|
||||||
|
### Frame Capture
|
||||||
|
|
||||||
|
`captureFrame()` returns an `IKvmFrame`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IKvmFrame {
|
||||||
|
timestamp: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
mimeType: 'image/png' | 'image/jpeg';
|
||||||
|
dataBase64: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Capture strategy:
|
||||||
|
|
||||||
|
- Draw a selected `video` to an internal canvas and return PNG base64.
|
||||||
|
- Draw a selected `canvas` to another canvas and return PNG base64.
|
||||||
|
- If the selected element is a wrapper, capture its inner `video` or `canvas`.
|
||||||
|
- If no media element is present, fall back to a Puppeteer element screenshot.
|
||||||
|
|
||||||
|
### Keyboard Control
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
await kvm.typeText('whoami', { delayMs: 10 });
|
||||||
|
await kvm.pressKey('Enter');
|
||||||
|
await kvm.pressShortcut(['Control', 'Alt', 'T']);
|
||||||
|
```
|
||||||
|
|
||||||
|
Every keyboard method focuses the viewer first. `pressShortcut()` presses keys in order and releases them in reverse order.
|
||||||
|
|
||||||
|
There is intentionally no public mouse automation API in v1. The only mouse action is the internal click used to focus the viewer.
|
||||||
|
|
||||||
|
## SmartKvmTerminal
|
||||||
|
|
||||||
|
`SmartKvmTerminal` uses any `IKvmDriver` and any `IOcrEngine` to type wrapped terminal commands and parse OCR text into command results.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const terminal = new SmartKvmTerminal({
|
||||||
|
kvm,
|
||||||
|
ocrEngine,
|
||||||
|
osHint: 'windows',
|
||||||
|
shellHint: 'powershell',
|
||||||
|
commandTimeoutMs: 45_000,
|
||||||
|
ocrPollIntervalMs: 500,
|
||||||
|
ocrMaxAttempts: 120,
|
||||||
|
ocrCrop: {
|
||||||
|
x: 0,
|
||||||
|
y: 120,
|
||||||
|
width: 1280,
|
||||||
|
height: 600,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bootstrap Shortcuts
|
||||||
|
|
||||||
|
`bootstrap()` can open a terminal using keyboard-only defaults:
|
||||||
|
|
||||||
|
- `windows`: `Meta + R`, types `powershell -NoLogo`, then presses `Enter`.
|
||||||
|
- `macos`: `Meta + Space`, types `Terminal`, then presses `Enter`.
|
||||||
|
- `linux`: `Control + Alt + T`.
|
||||||
|
- `unknown`: does nothing.
|
||||||
|
|
||||||
|
Browser-based KVMs can intercept or remap shortcuts. `bootstrap()` intentionally implements only the generic path.
|
||||||
|
|
||||||
|
### Running Commands
|
||||||
|
|
||||||
|
`runCommand(command)` creates a wrapped command, types it, presses `Enter`, then polls OCR until the end marker appears, the timeout is reached, or `ocrMaxAttempts` is reached.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const result = await terminal.runCommand('uname -a');
|
||||||
|
|
||||||
|
console.log(result.commandId);
|
||||||
|
console.log(result.completed);
|
||||||
|
console.log(result.timedOut);
|
||||||
|
console.log(result.exitCode);
|
||||||
|
console.log(result.combinedText);
|
||||||
|
console.log(result.rawOcrText);
|
||||||
|
```
|
||||||
|
|
||||||
|
Command timeouts do not throw. They return:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
completed: false,
|
||||||
|
timedOut: true,
|
||||||
|
combinedText: rawOcrText,
|
||||||
|
rawOcrText,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Infrastructure failures still throw, for example an unconnected KVM, a missing viewer selector, a missing capture selector, or a media element without a frame.
|
||||||
|
|
||||||
|
### Observing Text
|
||||||
|
|
||||||
|
`observeText()` captures one frame and sends it to the configured OCR engine:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const visibleText = await terminal.observeText();
|
||||||
|
```
|
||||||
|
|
||||||
|
OCR is called with `{ language: 'eng', crop: options.ocrCrop }`.
|
||||||
|
|
||||||
|
## Mistral OCR Adapter
|
||||||
|
|
||||||
|
`createMistralKvmOcrEngine()` returns an `IOcrEngine` backed by `@push.rocks/smartai/ocr`, which uses Mistral Document AI OCR with `mistral-ocr-latest` by default.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createMistralKvmOcrEngine } from '@push.rocks/smartkvm';
|
||||||
|
|
||||||
|
const ocrEngine = createMistralKvmOcrEngine({
|
||||||
|
apiKey: process.env.MISTRAL_API_KEY,
|
||||||
|
confidenceScoresGranularity: 'page',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
- `apiKey`: Mistral API key, required unless `transport` is supplied.
|
||||||
|
- `model`: OCR model, defaults to `mistral-ocr-latest`.
|
||||||
|
- `endpointUrl`: override the Mistral OCR endpoint.
|
||||||
|
- `confidenceScoresGranularity`: `'page'` | `'word'`, defaults to `'page'` in the KVM adapter.
|
||||||
|
- `tableFormat`: `'markdown'` | `'html'`.
|
||||||
|
- `extractHeader` and `extractFooter`: pass-through Mistral OCR flags.
|
||||||
|
- `transport`: test/custom transport hook inherited from `@push.rocks/smartai/ocr`.
|
||||||
|
|
||||||
|
Current limitation: `IOcrRecognizeOptions.crop` is rejected by this adapter because the Mistral endpoint receives the full KVM frame. Use a separate image-cropping OCR engine if you need terminal-region cropping today.
|
||||||
|
|
||||||
|
## Command Wrappers
|
||||||
|
|
||||||
|
The wrapper utilities make terminal output parseable through OCR by adding simple markers:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
SMARTKVM_START_<commandId>
|
||||||
|
SMARTKVM_END_<commandId>_<exitCode>
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
createWrappedKvmCommand,
|
||||||
|
parseWrappedKvmCommandOutput,
|
||||||
|
} from '@push.rocks/smartkvm';
|
||||||
|
|
||||||
|
const wrapped = createWrappedKvmCommand('echo hello', 'bash');
|
||||||
|
|
||||||
|
console.log(wrapped.textToType);
|
||||||
|
|
||||||
|
const parsed = parseWrappedKvmCommandOutput({
|
||||||
|
commandId: wrapped.commandId,
|
||||||
|
startMarker: wrapped.startMarker,
|
||||||
|
endMarkerPrefix: wrapped.endMarkerPrefix,
|
||||||
|
rawText: `
|
||||||
|
prompt
|
||||||
|
${wrapped.startMarker}
|
||||||
|
hello
|
||||||
|
${wrapped.endMarkerPrefix}0
|
||||||
|
prompt
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(parsed.completed); // true
|
||||||
|
console.log(parsed.exitCode); // 0
|
||||||
|
console.log(parsed.combinedText); // hello
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported shell hints are `bash`, `zsh`, `sh`, `powershell`, `cmd`, and `unknown`. `unknown` uses the POSIX-style wrapper.
|
||||||
|
|
||||||
|
The parser is intentionally simple and deterministic. It tolerates whitespace and line endings, but it does not do fuzzy OCR correction in v1.
|
||||||
|
|
||||||
|
## SmartAgent-Compatible Tools
|
||||||
|
|
||||||
|
`createSmartKvmTools()` returns small tool objects without importing `@push.rocks/smartagent`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createSmartKvmTools } from '@push.rocks/smartkvm';
|
||||||
|
|
||||||
|
const tools = createSmartKvmTools({ terminal });
|
||||||
|
|
||||||
|
const runCommandTool = tools.find((tool) => tool.name === 'kvm_terminal_run_command');
|
||||||
|
const observeTool = tools.find((tool) => tool.name === 'kvm_terminal_observe');
|
||||||
|
|
||||||
|
const commandResult = await runCommandTool?.execute({ command: 'hostname' });
|
||||||
|
const currentText = await observeTool?.execute({});
|
||||||
|
```
|
||||||
|
|
||||||
|
Included tools:
|
||||||
|
|
||||||
|
- `kvm_terminal_run_command`: accepts `{ command: string }` and returns `IKvmTerminalCommandResult`.
|
||||||
|
- `kvm_terminal_observe`: accepts `{}` and returns OCR text from the current frame.
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
Root exports:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export * from './smartkvm.interfaces.js';
|
||||||
|
export * from './smartkvm.classes.browserkvm.js';
|
||||||
|
export * from './smartkvm.classes.kvmterminal.js';
|
||||||
|
export * from './smartkvm.commandwrappers.js';
|
||||||
|
export * from './smartkvm.tools.smartagent.js';
|
||||||
|
export * from './smartkvm.ocr.smartai.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
Important types:
|
||||||
|
|
||||||
|
- `IKvmDriver`: generic transport interface for connect, disconnect, focus, capture, typing, key presses, shortcuts, and wait.
|
||||||
|
- `IKvmFrame`: timestamped base64 frame payload.
|
||||||
|
- `IOcrEngine`: pluggable OCR contract.
|
||||||
|
- `IKvmTerminalOptions`: terminal transport, OCR, OS hint, shell hint, timeout, polling, attempts, and crop options.
|
||||||
|
- `IKvmTerminalCommandResult`: parsed command result with markers, completion state, timeout state, exit code, combined output, and raw OCR text.
|
||||||
|
- `IWrappedKvmCommand`: generated command wrapper metadata and typed command string.
|
||||||
|
- `ISmartKvmTool`: minimal tool shape for agent integrations.
|
||||||
|
- `ISmartKvmMistralOcrEngineOptions`: options for the bundled Mistral OCR adapter.
|
||||||
|
|
||||||
|
## Driver Scope
|
||||||
|
|
||||||
|
The public abstraction is intentionally broader than Puppeteer. `SmartBrowserKvm` is the first concrete driver, but consumers should depend on `IKvmDriver` when possible.
|
||||||
|
|
||||||
|
Future drivers can implement the same interface for:
|
||||||
|
|
||||||
|
- JetKVM native WebRTC or data-channel control.
|
||||||
|
- GL.iNet Comet and PiKVM-compatible APIs.
|
||||||
|
- TinyPilot.
|
||||||
|
- Custom HDMI capture plus USB HID control.
|
||||||
|
|
||||||
## Manual Browser Test
|
## Manual Browser Test
|
||||||
|
|
||||||
Automated tests do not require real KVM hardware. To run the manual browser smoke test, set:
|
Automated tests do not require real KVM hardware. The browser smoke test only runs when `SMARTKVM_TEST_URL` is set.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
SMARTKVM_TEST_URL=https://your-kvm.local pnpm test
|
SMARTKVM_TEST_URL=https://your-kvm.local pnpm test
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional variables are `SMARTKVM_TEST_USERNAME`, `SMARTKVM_TEST_PASSWORD`, and `SMARTKVM_TEST_HEADLESS=false`.
|
Optional variables:
|
||||||
|
|
||||||
|
- `SMARTKVM_TEST_USERNAME`
|
||||||
|
- `SMARTKVM_TEST_PASSWORD`
|
||||||
|
- `SMARTKVM_TEST_HEADLESS=false`
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm install
|
||||||
|
pnpm test
|
||||||
|
pnpm run build
|
||||||
|
tsbuild check "test/**/*"
|
||||||
|
```
|
||||||
|
|
||||||
|
The package uses ESM, TypeScript, Puppeteer, and the push.rocks test/build stack.
|
||||||
|
|
||||||
|
## License and Legal Information
|
||||||
|
|
||||||
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
|
||||||
|
|
||||||
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
### Trademarks
|
||||||
|
|
||||||
|
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
||||||
|
|
||||||
|
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||||
|
|
||||||
|
### Company Information
|
||||||
|
|
||||||
|
Task Venture Capital GmbH
|
||||||
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||||
|
|
||||||
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
|
import { createMistralKvmOcrEngine } from '../ts/smartkvm.ocr.smartai.js';
|
||||||
|
import type { IKvmFrame } from '../ts/smartkvm.interfaces.js';
|
||||||
|
|
||||||
|
const createDummyFrame = (): IKvmFrame => ({
|
||||||
|
timestamp: Date.now(),
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
mimeType: 'image/png',
|
||||||
|
dataBase64: 'iVBORw0KGgo=',
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('createMistralKvmOcrEngine should adapt KVM frames to smartai OCR', async () => {
|
||||||
|
const calls: unknown[] = [];
|
||||||
|
const ocrEngine = createMistralKvmOcrEngine({
|
||||||
|
transport: {
|
||||||
|
process: async (request) => {
|
||||||
|
calls.push(request);
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
markdown: 'terminal text',
|
||||||
|
confidence_scores: {
|
||||||
|
average_page_confidence_score: 0.93,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
model: 'mistral-ocr-latest',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await ocrEngine.recognize(createDummyFrame());
|
||||||
|
|
||||||
|
expect(calls.length).toEqual(1);
|
||||||
|
expect((calls[0] as any).document.image_url).toEqual('data:image/png;base64,iVBORw0KGgo=');
|
||||||
|
expect((calls[0] as any).confidence_scores_granularity).toEqual('page');
|
||||||
|
expect(result.text).toEqual('terminal text');
|
||||||
|
expect(result.confidence).toEqual(0.93);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('createMistralKvmOcrEngine should reject unsupported crop options', async () => {
|
||||||
|
const ocrEngine = createMistralKvmOcrEngine({
|
||||||
|
transport: {
|
||||||
|
process: async () => {
|
||||||
|
throw new Error('should not call OCR');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let error: Error | undefined;
|
||||||
|
try {
|
||||||
|
await ocrEngine.recognize(createDummyFrame(), {
|
||||||
|
crop: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (caughtError) {
|
||||||
|
error = caughtError instanceof Error ? caughtError : new Error(String(caughtError));
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error?.message).toEqual('Mistral KVM OCR does not support crop options yet.');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -3,3 +3,4 @@ export * from './smartkvm.classes.browserkvm.js';
|
|||||||
export * from './smartkvm.classes.kvmterminal.js';
|
export * from './smartkvm.classes.kvmterminal.js';
|
||||||
export * from './smartkvm.commandwrappers.js';
|
export * from './smartkvm.commandwrappers.js';
|
||||||
export * from './smartkvm.tools.smartagent.js';
|
export * from './smartkvm.tools.smartagent.js';
|
||||||
|
export * from './smartkvm.ocr.smartai.js';
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { createMistralOcrEngine } from '@push.rocks/smartai/ocr';
|
||||||
|
import type {
|
||||||
|
ISmartAiMistralOcrTransport,
|
||||||
|
TSmartAiMistralOcrConfidenceScoresGranularity,
|
||||||
|
TSmartAiMistralOcrTableFormat,
|
||||||
|
} from '@push.rocks/smartai/ocr';
|
||||||
|
import type { IOcrEngine, IOcrResult } from './smartkvm.interfaces.js';
|
||||||
|
|
||||||
|
export interface ISmartKvmMistralOcrEngineOptions {
|
||||||
|
apiKey?: string;
|
||||||
|
model?: string;
|
||||||
|
endpointUrl?: string;
|
||||||
|
transport?: ISmartAiMistralOcrTransport;
|
||||||
|
includeImageBase64?: boolean;
|
||||||
|
tableFormat?: TSmartAiMistralOcrTableFormat;
|
||||||
|
extractHeader?: boolean;
|
||||||
|
extractFooter?: boolean;
|
||||||
|
confidenceScoresGranularity?: TSmartAiMistralOcrConfidenceScoresGranularity;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createMistralKvmOcrEngine = (
|
||||||
|
options: ISmartKvmMistralOcrEngineOptions = {}
|
||||||
|
): IOcrEngine => {
|
||||||
|
const smartAiOcrEngine = createMistralOcrEngine({
|
||||||
|
...options,
|
||||||
|
confidenceScoresGranularity: options.confidenceScoresGranularity ?? 'page',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
recognize: async (frame, recognizeOptions = {}): Promise<IOcrResult> => {
|
||||||
|
if (recognizeOptions.crop) {
|
||||||
|
throw new Error('Mistral KVM OCR does not support crop options yet.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await smartAiOcrEngine.recognizeImage({
|
||||||
|
dataBase64: frame.dataBase64,
|
||||||
|
mimeType: frame.mimeType,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: result.text,
|
||||||
|
confidence: result.confidence,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user