chenbhao Claude Opus 4.6 commited on
Commit
7e3630c
·
1 Parent(s): 46f810c

refactor: integrate ink-picture as source directory

Browse files

- Move ink-picture dependencies from file: reference to direct source integration
- Remove ink-picture's own node_modules, build, package.json, tsconfig.json, biome.json
- Update all ink-picture imports to use src/ink-picture path alias
- Add ink-picture path alias to tsconfig.json
- Add is-unicode-supported, iterm2-version, jimp, node-fetch, sixel as direct deps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (42) hide show
  1. bun.lock +187 -31
  2. package.json +5 -1
  3. src/components/InlineImage.tsx +1 -1
  4. src/ink-picture/InkPictureProvider.tsx +318 -0
  5. src/ink-picture/__tests__/LocalImageShow.test.ts +0 -0
  6. src/ink-picture/components/ImageBox.tsx +44 -0
  7. src/ink-picture/components/image/Ascii.tsx +48 -0
  8. src/ink-picture/components/image/Braille.tsx +46 -0
  9. src/ink-picture/components/image/HalfBlock.tsx +46 -0
  10. src/ink-picture/components/image/ITerm2.tsx +63 -0
  11. src/ink-picture/components/image/Kitty.tsx +127 -0
  12. src/ink-picture/components/image/Sixel.tsx +63 -0
  13. src/ink-picture/components/image/index.tsx +130 -0
  14. src/ink-picture/components/image/protocol.ts +31 -0
  15. src/ink-picture/hooks/useBackgroundColor.ts +22 -0
  16. src/ink-picture/hooks/useDirectRenderer.ts +123 -0
  17. src/ink-picture/hooks/useImage.ts +71 -0
  18. src/ink-picture/hooks/useMeasuredSize.ts +27 -0
  19. src/ink-picture/hooks/usePosition.ts +137 -0
  20. src/ink-picture/hooks/useProtocol.ts +24 -0
  21. src/ink-picture/hooks/useVisibility.ts +84 -0
  22. src/ink-picture/index.ts +67 -0
  23. src/ink-picture/renderers/ascii.ts +41 -0
  24. src/ink-picture/renderers/braille.ts +74 -0
  25. src/ink-picture/renderers/halfBlock.ts +35 -0
  26. src/ink-picture/renderers/index.ts +11 -0
  27. src/ink-picture/renderers/iterm2.ts +19 -0
  28. src/ink-picture/renderers/kitty.ts +46 -0
  29. src/ink-picture/renderers/sixel.ts +8 -0
  30. src/ink-picture/renderers/types.ts +16 -0
  31. src/ink-picture/utils/ansiEscapes.ts +28 -0
  32. src/ink-picture/utils/bgColorize.ts +48 -0
  33. src/ink-picture/utils/generateKittyId.ts +56 -0
  34. src/ink-picture/utils/getBestProtocol.ts +43 -0
  35. src/ink-picture/utils/image.ts +115 -0
  36. src/ink-picture/utils/imageCache.ts +39 -0
  37. src/ink-picture/utils/queryTerminal.ts +222 -0
  38. src/ink/components/StdoutContext.tsx +15 -0
  39. src/tools/ImageShowTool/ImageShowTool.tsx +26 -2
  40. src/tools/ImageShowTool/__tests__/ShowLocalImage.test.tsx +51 -0
  41. src/tools/ImageShowTool/__tests__/ShowUrlImage.test.tsx +53 -0
  42. tsconfig.json +3 -1
bun.lock CHANGED
@@ -75,11 +75,14 @@
75
  "ignore": "^7.0.5",
76
  "indent-string": "^5.0.0",
77
  "ink": "^6.8.0",
 
 
 
78
  "jsonc-parser": "^3.3.1",
79
  "lodash-es": "^4.17.23",
80
  "lru-cache": "^11.2.7",
81
  "marked": "^17.0.5",
82
- "node-edge-tts": "^1.2.10",
83
  "onnxruntime-web": "^1.27.0",
84
  "p-map": "^7.0.4",
85
  "picomatch": "^4.0.4",
@@ -93,6 +96,7 @@
93
  "sharp": "^0.34.5",
94
  "shell-quote": "^1.8.3",
95
  "signal-exit": "^4.1.0",
 
96
  "stack-utils": "^2.0.6",
97
  "strip-ansi": "^7.2.0",
98
  "supports-hyperlinks": "^4.4.0",
@@ -240,6 +244,8 @@
240
 
241
  "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
242
 
 
 
243
  "@commander-js/extra-typings": ["@commander-js/extra-typings@14.0.0", "", { "peerDependencies": { "commander": "~14.0.0" } }, "sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg=="],
244
 
245
  "@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="],
@@ -332,6 +338,62 @@
332
 
333
  "@inquirer/type": ["@inquirer/type@2.0.0", "", { "dependencies": { "mute-stream": "^1.0.0" } }, "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag=="],
334
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
336
 
337
  "@larksuite/channel": ["@larksuite/channel@0.1.2", "", { "dependencies": { "@larksuiteoapi/node-sdk": "^1.66.1", "https-proxy-agent": "^9.0.0" } }, "sha512-9bhxF8lCFsYiOq+E9Swg1/5nqJLJNPsVLTGIdEE8hcqRKg/Dwazh4UKP0jHjiFf8yI8KKTfsEkA2P6PUFmSZQw=="],
@@ -478,6 +540,10 @@
478
 
479
  "@tavily/core": ["@tavily/core@0.6.4", "", { "dependencies": { "axios": "^1.7.7", "https-proxy-agent": "^7.0.6", "js-tiktoken": "^1.0.14" } }, "sha512-PppC0p2SwkoImLiYFT/uqDyWKPivpVsIM16HUf1Apmtbqg1YhI7Yg5Hq6eYSojC6COVCGXE4CotBnWqUmrai+A=="],
480
 
 
 
 
 
481
  "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
482
 
483
  "@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
@@ -488,6 +554,8 @@
488
 
489
  "@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="],
490
 
 
 
491
  "@types/wrap-ansi": ["@types/wrap-ansi@3.0.0", "", {}, "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g=="],
492
 
493
  "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.6", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og=="],
@@ -514,10 +582,14 @@
514
 
515
  "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
516
 
 
 
517
  "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
518
 
519
  "anynum": ["anynum@1.0.0", "", {}, "sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA=="],
520
 
 
 
521
  "asciichart": ["asciichart@1.5.25", "", {}, "sha512-PNxzXIPPOtWq8T7bgzBtk9cI2lgS4SJZthUHEiQ1aoIc3lNzGfUvIvo9LiAnq26TACo9t1/4qP6KTGAUbzX9Xg=="],
522
 
523
  "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
@@ -526,6 +598,8 @@
526
 
527
  "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
528
 
 
 
529
  "axios": ["axios@1.17.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw=="],
530
 
531
  "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
@@ -538,6 +612,8 @@
538
 
539
  "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
540
 
 
 
541
  "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
542
 
543
  "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
@@ -604,6 +680,8 @@
604
 
605
  "cssfilter": ["cssfilter@0.0.10", "", {}, "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="],
606
 
 
 
607
  "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
608
 
609
  "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
@@ -672,6 +750,8 @@
672
 
673
  "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
674
 
 
 
675
  "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
676
 
677
  "express-rate-limit": ["express-rate-limit@8.5.2", "", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A=="],
@@ -694,6 +774,8 @@
694
 
695
  "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
696
 
 
 
697
  "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
698
 
699
  "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
@@ -738,6 +820,8 @@
738
 
739
  "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
740
 
 
 
741
  "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
742
 
743
  "global-agent": ["global-agent@4.1.3", "", { "dependencies": { "globalthis": "^1.0.2", "matcher": "^4.0.0", "semver": "^7.3.5", "serialize-error": "^8.1.0" } }, "sha512-KUJEViiuFT3I97t+GYMikLPJS2Lfo/S2F+DQuBWzuzaMPnvt5yyZePzArx36fBzpGTxZjIpDbXLeySLgh+k76g=="],
@@ -782,8 +866,12 @@
782
 
783
  "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
784
 
 
 
785
  "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
786
 
 
 
787
  "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
788
 
789
  "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
@@ -816,8 +904,14 @@
816
 
817
  "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
818
 
 
 
 
 
819
  "jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
820
 
 
 
821
  "js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
822
 
823
  "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
@@ -880,6 +974,10 @@
880
 
881
  "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
882
 
 
 
 
 
883
  "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
884
 
885
  "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
@@ -908,8 +1006,6 @@
908
 
909
  "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
910
 
911
- "node-edge-tts": ["node-edge-tts@1.2.10", "", { "dependencies": { "https-proxy-agent": "^7.0.1", "ws": "^8.13.0", "yargs": "^17.7.2" }, "bin": { "node-edge-tts": "bin.js" } }, "sha512-bV2i4XU54D45+US0Zm1HcJRkifuB3W438dWyuJEHLQdKxnuqlI1kim2MOvR6Q3XUQZvfF9PoDyR1Rt7aeXhPdQ=="],
912
-
913
  "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
914
 
915
  "node-forge": ["node-forge@1.4.0", "", {}, "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ=="],
@@ -922,6 +1018,8 @@
922
 
923
  "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
924
 
 
 
925
  "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
926
 
927
  "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
@@ -948,6 +1046,14 @@
948
 
949
  "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
950
 
 
 
 
 
 
 
 
 
951
  "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
952
 
953
  "parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="],
@@ -970,6 +1076,8 @@
970
 
971
  "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
972
 
 
 
973
  "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
974
 
975
  "platform": ["platform@1.3.6", "", {}, "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="],
@@ -1026,6 +1134,8 @@
1026
 
1027
  "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
1028
 
 
 
1029
  "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
1030
 
1031
  "semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
@@ -1058,6 +1168,10 @@
1058
 
1059
  "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
1060
 
 
 
 
 
1061
  "slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="],
1062
 
1063
  "ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="],
@@ -1074,22 +1188,30 @@
1074
 
1075
  "strnum": ["strnum@2.4.0", "", { "dependencies": { "anynum": "^1.0.0" } }, "sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg=="],
1076
 
 
 
1077
  "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
1078
 
1079
  "supports-hyperlinks": ["supports-hyperlinks@4.4.0", "", { "dependencies": { "has-flag": "^5.0.1", "supports-color": "^10.2.2" } }, "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg=="],
1080
 
1081
  "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
1082
 
 
 
1083
  "terminal-size": ["terminal-size@4.0.1", "", {}, "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ=="],
1084
 
1085
  "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
1086
 
1087
  "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
1088
 
 
 
1089
  "tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="],
1090
 
1091
  "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
1092
 
 
 
1093
  "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
1094
 
1095
  "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
@@ -1106,6 +1228,8 @@
1106
 
1107
  "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
1108
 
 
 
1109
  "undici": ["undici@7.27.2", "", {}, "sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA=="],
1110
 
1111
  "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
@@ -1118,6 +1242,8 @@
1118
 
1119
  "usehooks-ts": ["usehooks-ts@3.1.1", "", { "dependencies": { "lodash.debounce": "^4.0.8" }, "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA=="],
1120
 
 
 
1121
  "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
1122
 
1123
  "vanipath": ["vanipath@1.0.10", "", {}, "sha512-50l1CcT4Me+1p2SI51sm/KYcADNC3pLDB70jMlGWdm61TIyryO4K6IvV8PCJxM/V6AILISNqidpzkFKScsxWfg=="],
@@ -1154,6 +1280,10 @@
1154
 
1155
  "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="],
1156
 
 
 
 
 
1157
  "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
1158
 
1159
  "xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="],
@@ -1284,6 +1414,40 @@
1284
 
1285
  "@inquirer/select/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
1286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1287
  "@larksuite/channel/https-proxy-agent": ["https-proxy-agent@9.1.0", "", { "dependencies": { "agent-base": "9.0.0", "debug": "^4.3.4", "proxy-agent-negotiate": "1.1.0" } }, "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA=="],
1288
 
1289
  "@larksuiteoapi/node-sdk/axios": ["axios@1.13.6", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ=="],
@@ -1394,6 +1558,8 @@
1394
 
1395
  "@typespec/ts-http-runtime/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
1396
 
 
 
1397
  "axios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
1398
 
1399
  "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -1418,6 +1584,8 @@
1418
 
1419
  "http-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
1420
 
 
 
1421
  "ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="],
1422
 
1423
  "ink/cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
@@ -1432,14 +1600,12 @@
1432
 
1433
  "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
1434
 
1435
- "node-edge-tts/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
1436
-
1437
- "node-edge-tts/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
1438
-
1439
  "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
1440
 
1441
  "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="],
1442
 
 
 
1443
  "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
1444
 
1445
  "qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
@@ -1452,6 +1618,8 @@
1452
 
1453
  "vscode-languageserver-protocol/vscode-jsonrpc": ["vscode-jsonrpc@9.0.0", "", {}, "sha512-+VvMmQPJhtvJ+8O+zu2JKIRiLxXF8NW7krWgyMGeOHrp4Cn23T5hc0v2LknNeopDOB70wghHAds7mKtcZ0I4Sg=="],
1454
 
 
 
1455
  "xss/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
1456
 
1457
  "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -1506,6 +1674,18 @@
1506
 
1507
  "@typespec/ts-http-runtime/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
1508
 
 
 
 
 
 
 
 
 
 
 
 
 
1509
  "axios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
1510
 
1511
  "cli-highlight/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -1532,14 +1712,6 @@
1532
 
1533
  "ink/wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
1534
 
1535
- "node-edge-tts/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
1536
-
1537
- "node-edge-tts/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
1538
-
1539
- "node-edge-tts/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
1540
-
1541
- "node-edge-tts/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
1542
-
1543
  "qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
1544
 
1545
  "qrcode/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -1590,16 +1762,6 @@
1590
 
1591
  "gtoken/gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
1592
 
1593
- "node-edge-tts/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1594
-
1595
- "node-edge-tts/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
1596
-
1597
- "node-edge-tts/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
1598
-
1599
- "node-edge-tts/yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
1600
-
1601
- "node-edge-tts/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1602
-
1603
  "qrcode/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1604
 
1605
  "qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
@@ -1620,12 +1782,6 @@
1620
 
1621
  "@grpc/proto-loader/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1622
 
1623
- "node-edge-tts/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1624
-
1625
- "node-edge-tts/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
1626
-
1627
- "node-edge-tts/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1628
-
1629
  "qrcode/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1630
 
1631
  "qrcode/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
 
75
  "ignore": "^7.0.5",
76
  "indent-string": "^5.0.0",
77
  "ink": "^6.8.0",
78
+ "is-unicode-supported": "^2.1.0",
79
+ "iterm2-version": "^6.0.0",
80
+ "jimp": "^1.6.1",
81
  "jsonc-parser": "^3.3.1",
82
  "lodash-es": "^4.17.23",
83
  "lru-cache": "^11.2.7",
84
  "marked": "^17.0.5",
85
+ "node-fetch": "^3.3.2",
86
  "onnxruntime-web": "^1.27.0",
87
  "p-map": "^7.0.4",
88
  "picomatch": "^4.0.4",
 
96
  "sharp": "^0.34.5",
97
  "shell-quote": "^1.8.3",
98
  "signal-exit": "^4.1.0",
99
+ "sixel": "^0.16.0",
100
  "stack-utils": "^2.0.6",
101
  "strip-ansi": "^7.2.0",
102
  "supports-hyperlinks": "^4.4.0",
 
244
 
245
  "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
246
 
247
+ "@borewit/text-codec": ["@borewit/text-codec@0.2.2", "", {}, "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ=="],
248
+
249
  "@commander-js/extra-typings": ["@commander-js/extra-typings@14.0.0", "", { "peerDependencies": { "commander": "~14.0.0" } }, "sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg=="],
250
 
251
  "@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="],
 
338
 
339
  "@inquirer/type": ["@inquirer/type@2.0.0", "", { "dependencies": { "mute-stream": "^1.0.0" } }, "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag=="],
340
 
341
+ "@jimp/core": ["@jimp/core@1.6.1", "", { "dependencies": { "@jimp/file-ops": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^21.3.3", "mime": "3" } }, "sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A=="],
342
+
343
+ "@jimp/diff": ["@jimp/diff@1.6.1", "", { "dependencies": { "@jimp/plugin-resize": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "pixelmatch": "^5.3.0" } }, "sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ=="],
344
+
345
+ "@jimp/file-ops": ["@jimp/file-ops@1.6.1", "", {}, "sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w=="],
346
+
347
+ "@jimp/js-bmp": ["@jimp/js-bmp@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "bmp-ts": "^1.0.9" } }, "sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ=="],
348
+
349
+ "@jimp/js-gif": ["@jimp/js-gif@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "gifwrap": "^0.10.1", "omggif": "^1.0.10" } }, "sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w=="],
350
+
351
+ "@jimp/js-jpeg": ["@jimp/js-jpeg@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "jpeg-js": "^0.4.4" } }, "sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg=="],
352
+
353
+ "@jimp/js-png": ["@jimp/js-png@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "pngjs": "^7.0.0" } }, "sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA=="],
354
+
355
+ "@jimp/js-tiff": ["@jimp/js-tiff@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "utif2": "^4.1.0" } }, "sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw=="],
356
+
357
+ "@jimp/plugin-blit": ["@jimp/plugin-blit@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ=="],
358
+
359
+ "@jimp/plugin-blur": ["@jimp/plugin-blur@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/utils": "1.6.1" } }, "sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ=="],
360
+
361
+ "@jimp/plugin-circle": ["@jimp/plugin-circle@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "zod": "^3.23.8" } }, "sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ=="],
362
+
363
+ "@jimp/plugin-color": ["@jimp/plugin-color@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "tinycolor2": "^1.6.0", "zod": "^3.23.8" } }, "sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A=="],
364
+
365
+ "@jimp/plugin-contain": ["@jimp/plugin-contain@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/plugin-blit": "1.6.1", "@jimp/plugin-resize": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg=="],
366
+
367
+ "@jimp/plugin-cover": ["@jimp/plugin-cover@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/plugin-crop": "1.6.1", "@jimp/plugin-resize": "1.6.1", "@jimp/types": "1.6.1", "zod": "^3.23.8" } }, "sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg=="],
368
+
369
+ "@jimp/plugin-crop": ["@jimp/plugin-crop@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg=="],
370
+
371
+ "@jimp/plugin-displace": ["@jimp/plugin-displace@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g=="],
372
+
373
+ "@jimp/plugin-dither": ["@jimp/plugin-dither@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1" } }, "sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew=="],
374
+
375
+ "@jimp/plugin-fisheye": ["@jimp/plugin-fisheye@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA=="],
376
+
377
+ "@jimp/plugin-flip": ["@jimp/plugin-flip@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "zod": "^3.23.8" } }, "sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA=="],
378
+
379
+ "@jimp/plugin-hash": ["@jimp/plugin-hash@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/js-bmp": "1.6.1", "@jimp/js-jpeg": "1.6.1", "@jimp/js-png": "1.6.1", "@jimp/js-tiff": "1.6.1", "@jimp/plugin-color": "1.6.1", "@jimp/plugin-resize": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "any-base": "^1.1.0" } }, "sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A=="],
380
+
381
+ "@jimp/plugin-mask": ["@jimp/plugin-mask@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "zod": "^3.23.8" } }, "sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A=="],
382
+
383
+ "@jimp/plugin-print": ["@jimp/plugin-print@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/js-jpeg": "1.6.1", "@jimp/js-png": "1.6.1", "@jimp/plugin-blit": "1.6.1", "@jimp/types": "1.6.1", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", "simple-xml-to-json": "^1.2.2", "zod": "^3.23.8" } }, "sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w=="],
384
+
385
+ "@jimp/plugin-quantize": ["@jimp/plugin-quantize@1.6.1", "", { "dependencies": { "image-q": "^4.0.0", "zod": "^3.23.8" } }, "sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww=="],
386
+
387
+ "@jimp/plugin-resize": ["@jimp/plugin-resize@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/types": "1.6.1", "zod": "^3.23.8" } }, "sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg=="],
388
+
389
+ "@jimp/plugin-rotate": ["@jimp/plugin-rotate@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/plugin-crop": "1.6.1", "@jimp/plugin-resize": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg=="],
390
+
391
+ "@jimp/plugin-threshold": ["@jimp/plugin-threshold@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/plugin-color": "1.6.1", "@jimp/plugin-hash": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1", "zod": "^3.23.8" } }, "sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q=="],
392
+
393
+ "@jimp/types": ["@jimp/types@1.6.1", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w=="],
394
+
395
+ "@jimp/utils": ["@jimp/utils@1.6.1", "", { "dependencies": { "@jimp/types": "1.6.1", "tinycolor2": "^1.6.0" } }, "sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw=="],
396
+
397
  "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
398
 
399
  "@larksuite/channel": ["@larksuite/channel@0.1.2", "", { "dependencies": { "@larksuiteoapi/node-sdk": "^1.66.1", "https-proxy-agent": "^9.0.0" } }, "sha512-9bhxF8lCFsYiOq+E9Swg1/5nqJLJNPsVLTGIdEE8hcqRKg/Dwazh4UKP0jHjiFf8yI8KKTfsEkA2P6PUFmSZQw=="],
 
540
 
541
  "@tavily/core": ["@tavily/core@0.6.4", "", { "dependencies": { "axios": "^1.7.7", "https-proxy-agent": "^7.0.6", "js-tiktoken": "^1.0.14" } }, "sha512-PppC0p2SwkoImLiYFT/uqDyWKPivpVsIM16HUf1Apmtbqg1YhI7Yg5Hq6eYSojC6COVCGXE4CotBnWqUmrai+A=="],
542
 
543
+ "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
544
+
545
+ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
546
+
547
  "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
548
 
549
  "@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
 
554
 
555
  "@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="],
556
 
557
+ "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
558
+
559
  "@types/wrap-ansi": ["@types/wrap-ansi@3.0.0", "", {}, "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g=="],
560
 
561
  "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.6", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og=="],
 
582
 
583
  "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
584
 
585
+ "any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="],
586
+
587
  "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
588
 
589
  "anynum": ["anynum@1.0.0", "", {}, "sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA=="],
590
 
591
+ "app-path": ["app-path@4.0.0", "", { "dependencies": { "execa": "^5.0.0" } }, "sha512-mgBO9PZJ3MpbKbwFTljTi36ZKBvG5X/fkVR1F85ANsVcVllEb+C0LGNdJfGUm84GpC4xxgN6HFkmkMU8VEO4mA=="],
592
+
593
  "asciichart": ["asciichart@1.5.25", "", {}, "sha512-PNxzXIPPOtWq8T7bgzBtk9cI2lgS4SJZthUHEiQ1aoIc3lNzGfUvIvo9LiAnq26TACo9t1/4qP6KTGAUbzX9Xg=="],
594
 
595
  "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
 
598
 
599
  "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
600
 
601
+ "await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="],
602
+
603
  "axios": ["axios@1.17.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw=="],
604
 
605
  "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
 
612
 
613
  "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
614
 
615
+ "bmp-ts": ["bmp-ts@1.0.9", "", {}, "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw=="],
616
+
617
  "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
618
 
619
  "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
 
680
 
681
  "cssfilter": ["cssfilter@0.0.10", "", {}, "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="],
682
 
683
+ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
684
+
685
  "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
686
 
687
  "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
 
750
 
751
  "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
752
 
753
+ "exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="],
754
+
755
  "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
756
 
757
  "express-rate-limit": ["express-rate-limit@8.5.2", "", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A=="],
 
774
 
775
  "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
776
 
777
+ "file-type": ["file-type@21.3.4", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g=="],
778
+
779
  "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
780
 
781
  "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
 
820
 
821
  "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
822
 
823
+ "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
824
+
825
  "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
826
 
827
  "global-agent": ["global-agent@4.1.3", "", { "dependencies": { "globalthis": "^1.0.2", "matcher": "^4.0.0", "semver": "^7.3.5", "serialize-error": "^8.1.0" } }, "sha512-KUJEViiuFT3I97t+GYMikLPJS2Lfo/S2F+DQuBWzuzaMPnvt5yyZePzArx36fBzpGTxZjIpDbXLeySLgh+k76g=="],
 
866
 
867
  "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
868
 
869
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
870
+
871
  "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
872
 
873
+ "image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="],
874
+
875
  "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
876
 
877
  "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
 
904
 
905
  "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
906
 
907
+ "iterm2-version": ["iterm2-version@6.0.0", "", { "dependencies": { "app-path": "^4.0.0", "plist": "^3.1.0", "terminal-query": "^0.1.0" } }, "sha512-oH14QLmk+49KBsxlVBRe9piHHIY0vn41Mmop/LTzkCMVyGcikhMLJCpbR4LWB+WRdDF1v66u39CuRj+LLfZ9QQ=="],
908
+
909
+ "jimp": ["jimp@1.6.1", "", { "dependencies": { "@jimp/core": "1.6.1", "@jimp/diff": "1.6.1", "@jimp/js-bmp": "1.6.1", "@jimp/js-gif": "1.6.1", "@jimp/js-jpeg": "1.6.1", "@jimp/js-png": "1.6.1", "@jimp/js-tiff": "1.6.1", "@jimp/plugin-blit": "1.6.1", "@jimp/plugin-blur": "1.6.1", "@jimp/plugin-circle": "1.6.1", "@jimp/plugin-color": "1.6.1", "@jimp/plugin-contain": "1.6.1", "@jimp/plugin-cover": "1.6.1", "@jimp/plugin-crop": "1.6.1", "@jimp/plugin-displace": "1.6.1", "@jimp/plugin-dither": "1.6.1", "@jimp/plugin-fisheye": "1.6.1", "@jimp/plugin-flip": "1.6.1", "@jimp/plugin-hash": "1.6.1", "@jimp/plugin-mask": "1.6.1", "@jimp/plugin-print": "1.6.1", "@jimp/plugin-quantize": "1.6.1", "@jimp/plugin-resize": "1.6.1", "@jimp/plugin-rotate": "1.6.1", "@jimp/plugin-threshold": "1.6.1", "@jimp/types": "1.6.1", "@jimp/utils": "1.6.1" } }, "sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw=="],
910
+
911
  "jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
912
 
913
+ "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],
914
+
915
  "js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
916
 
917
  "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
 
974
 
975
  "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
976
 
977
+ "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
978
+
979
+ "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
980
+
981
  "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
982
 
983
  "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
 
1006
 
1007
  "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
1008
 
 
 
1009
  "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
1010
 
1011
  "node-forge": ["node-forge@1.4.0", "", {}, "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ=="],
 
1018
 
1019
  "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
1020
 
1021
+ "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
1022
+
1023
  "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
1024
 
1025
  "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
 
1046
 
1047
  "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
1048
 
1049
+ "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
1050
+
1051
+ "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="],
1052
+
1053
+ "parse-bmfont-binary": ["parse-bmfont-binary@1.0.6", "", {}, "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="],
1054
+
1055
+ "parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="],
1056
+
1057
  "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
1058
 
1059
  "parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="],
 
1076
 
1077
  "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
1078
 
1079
+ "pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="],
1080
+
1081
  "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
1082
 
1083
  "platform": ["platform@1.3.6", "", {}, "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="],
 
1134
 
1135
  "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
1136
 
1137
+ "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
1138
+
1139
  "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
1140
 
1141
  "semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
 
1168
 
1169
  "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
1170
 
1171
+ "simple-xml-to-json": ["simple-xml-to-json@1.2.7", "", {}, "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q=="],
1172
+
1173
+ "sixel": ["sixel@0.16.0", "", {}, "sha512-xicu6Y6Cyhmv5rjyHxq2r5RnKerlL/nyZEGjOU5bLCshXkZryc9JFJThTCKPOAtWXCfeWquEKFVFfMPcTD25PA=="],
1174
+
1175
  "slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="],
1176
 
1177
  "ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="],
 
1188
 
1189
  "strnum": ["strnum@2.4.0", "", { "dependencies": { "anynum": "^1.0.0" } }, "sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg=="],
1190
 
1191
+ "strtok3": ["strtok3@10.3.5", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA=="],
1192
+
1193
  "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
1194
 
1195
  "supports-hyperlinks": ["supports-hyperlinks@4.4.0", "", { "dependencies": { "has-flag": "^5.0.1", "supports-color": "^10.2.2" } }, "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg=="],
1196
 
1197
  "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
1198
 
1199
+ "terminal-query": ["terminal-query@0.1.1", "", {}, "sha512-MlkY08iL6mCFtdiyOKRzJqTHvUFEKCTRZZDLOdN1Vk79rX6ALq2tk2p3IfQhJJbnNLxHZnQu+WS2JT9kwLW2vQ=="],
1200
+
1201
  "terminal-size": ["terminal-size@4.0.1", "", {}, "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ=="],
1202
 
1203
  "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
1204
 
1205
  "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
1206
 
1207
+ "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
1208
+
1209
  "tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="],
1210
 
1211
  "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
1212
 
1213
+ "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
1214
+
1215
  "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
1216
 
1217
  "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
 
1228
 
1229
  "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
1230
 
1231
+ "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
1232
+
1233
  "undici": ["undici@7.27.2", "", {}, "sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA=="],
1234
 
1235
  "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
 
1242
 
1243
  "usehooks-ts": ["usehooks-ts@3.1.1", "", { "dependencies": { "lodash.debounce": "^4.0.8" }, "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA=="],
1244
 
1245
+ "utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="],
1246
+
1247
  "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
1248
 
1249
  "vanipath": ["vanipath@1.0.10", "", {}, "sha512-50l1CcT4Me+1p2SI51sm/KYcADNC3pLDB70jMlGWdm61TIyryO4K6IvV8PCJxM/V6AILISNqidpzkFKScsxWfg=="],
 
1280
 
1281
  "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="],
1282
 
1283
+ "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],
1284
+
1285
+ "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
1286
+
1287
  "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
1288
 
1289
  "xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="],
 
1414
 
1415
  "@inquirer/select/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
1416
 
1417
+ "@jimp/js-png/pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
1418
+
1419
+ "@jimp/plugin-blit/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1420
+
1421
+ "@jimp/plugin-circle/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1422
+
1423
+ "@jimp/plugin-color/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1424
+
1425
+ "@jimp/plugin-contain/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1426
+
1427
+ "@jimp/plugin-cover/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1428
+
1429
+ "@jimp/plugin-crop/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1430
+
1431
+ "@jimp/plugin-displace/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1432
+
1433
+ "@jimp/plugin-fisheye/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1434
+
1435
+ "@jimp/plugin-flip/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1436
+
1437
+ "@jimp/plugin-mask/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1438
+
1439
+ "@jimp/plugin-print/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1440
+
1441
+ "@jimp/plugin-quantize/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1442
+
1443
+ "@jimp/plugin-resize/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1444
+
1445
+ "@jimp/plugin-rotate/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1446
+
1447
+ "@jimp/plugin-threshold/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1448
+
1449
+ "@jimp/types/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1450
+
1451
  "@larksuite/channel/https-proxy-agent": ["https-proxy-agent@9.1.0", "", { "dependencies": { "agent-base": "9.0.0", "debug": "^4.3.4", "proxy-agent-negotiate": "1.1.0" } }, "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA=="],
1452
 
1453
  "@larksuiteoapi/node-sdk/axios": ["axios@1.13.6", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ=="],
 
1558
 
1559
  "@typespec/ts-http-runtime/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
1560
 
1561
+ "app-path/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
1562
+
1563
  "axios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
1564
 
1565
  "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
 
1584
 
1585
  "http-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
1586
 
1587
+ "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="],
1588
+
1589
  "ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="],
1590
 
1591
  "ink/cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
 
1600
 
1601
  "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
1602
 
 
 
 
 
1603
  "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
1604
 
1605
  "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="],
1606
 
1607
+ "pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
1608
+
1609
  "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
1610
 
1611
  "qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
 
1618
 
1619
  "vscode-languageserver-protocol/vscode-jsonrpc": ["vscode-jsonrpc@9.0.0", "", {}, "sha512-+VvMmQPJhtvJ+8O+zu2JKIRiLxXF8NW7krWgyMGeOHrp4Cn23T5hc0v2LknNeopDOB70wghHAds7mKtcZ0I4Sg=="],
1620
 
1621
+ "xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
1622
+
1623
  "xss/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
1624
 
1625
  "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
 
1674
 
1675
  "@typespec/ts-http-runtime/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
1676
 
1677
+ "app-path/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
1678
+
1679
+ "app-path/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
1680
+
1681
+ "app-path/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
1682
+
1683
+ "app-path/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
1684
+
1685
+ "app-path/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
1686
+
1687
+ "app-path/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
1688
+
1689
  "axios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
1690
 
1691
  "cli-highlight/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
 
1712
 
1713
  "ink/wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
1714
 
 
 
 
 
 
 
 
 
1715
  "qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
1716
 
1717
  "qrcode/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
 
1762
 
1763
  "gtoken/gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
1764
 
 
 
 
 
 
 
 
 
 
 
1765
  "qrcode/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1766
 
1767
  "qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
 
1782
 
1783
  "@grpc/proto-loader/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1784
 
 
 
 
 
 
 
1785
  "qrcode/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1786
 
1787
  "qrcode/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
package.json CHANGED
@@ -96,7 +96,11 @@
96
  "lodash-es": "^4.17.23",
97
  "lru-cache": "^11.2.7",
98
  "marked": "^17.0.5",
99
- "node-edge-tts": "^1.2.10",
 
 
 
 
100
  "onnxruntime-web": "^1.27.0",
101
  "p-map": "^7.0.4",
102
  "picomatch": "^4.0.4",
 
96
  "lodash-es": "^4.17.23",
97
  "lru-cache": "^11.2.7",
98
  "marked": "^17.0.5",
99
+ "is-unicode-supported": "^2.1.0",
100
+ "iterm2-version": "^6.0.0",
101
+ "jimp": "^1.6.1",
102
+ "node-fetch": "^3.3.2",
103
+ "sixel": "^0.16.0",
104
  "onnxruntime-web": "^1.27.0",
105
  "p-map": "^7.0.4",
106
  "picomatch": "^4.0.4",
src/components/InlineImage.tsx CHANGED
@@ -1,5 +1,5 @@
1
  import React from 'react'
2
- import Image, { InkPictureProvider } from 'ink-picture'
3
  import Link from '../ink/components/Link.js'
4
  import { supportsHyperlinks } from '../ink/supports-hyperlinks.js'
5
  import { Box, Text } from '../ink.js'
 
1
  import React from 'react'
2
+ import Image, { InkPictureProvider } from 'src/ink-picture/index.ts'
3
  import Link from '../ink/components/Link.js'
4
  import { supportsHyperlinks } from '../ink/supports-hyperlinks.js'
5
  import { Box, Text } from '../ink.js'
src/ink-picture/InkPictureProvider.tsx ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useStdin } from "src/ink";
2
+ import checkIsUnicodeSupported from "is-unicode-supported";
3
+ import iterm2Version from "iterm2-version";
4
+ import React, {
5
+ createContext,
6
+ Profiler,
7
+ useCallback,
8
+ useContext,
9
+ useEffect,
10
+ useMemo,
11
+ useRef,
12
+ useState,
13
+ } from "react";
14
+ import supportsColor from "supports-color";
15
+ import { ImageCache } from "./utils/imageCache.js";
16
+ import { queryTerminal } from "./utils/queryTerminal.js";
17
+
18
+ function resolveConfig(
19
+ key: string,
20
+ propValue: number | undefined,
21
+ fallback: number,
22
+ min: number,
23
+ ): number {
24
+ const env = process.env[key];
25
+ if (env !== undefined) {
26
+ const n = parseInt(env, 10);
27
+ if (Number.isFinite(n) && n >= min) return n;
28
+ }
29
+ if (propValue !== undefined) return Math.max(min, propValue);
30
+ return fallback;
31
+ }
32
+
33
+ export interface InkPictureConfig {
34
+ pollIntervalMs: number;
35
+ /** @deprecated Unused since v2.0.1 — images are now repainted after each React render via the Profiler. */
36
+ paintIntervalMs: number;
37
+ cacheSize: number;
38
+ }
39
+
40
+ export const defaultConfig: InkPictureConfig = {
41
+ pollIntervalMs: 16,
42
+ /** @deprecated */
43
+ paintIntervalMs: 16,
44
+ cacheSize: 10,
45
+ };
46
+
47
+ const InkPictureConfigContext = createContext<InkPictureConfig>(defaultConfig);
48
+
49
+ export function useInkPictureConfig(): InkPictureConfig {
50
+ return useContext(InkPictureConfigContext);
51
+ }
52
+
53
+ const ImageCacheContext = createContext<ImageCache | null>(null);
54
+
55
+ export function useImageCache(): ImageCache | null {
56
+ return useContext(ImageCacheContext);
57
+ }
58
+
59
+ type RenderListener = () => void;
60
+ const RenderListenerContext = createContext<Set<RenderListener> | null>(null);
61
+
62
+ export function useOnRender(cb: RenderListener): void {
63
+ const listeners = useContext(RenderListenerContext);
64
+ useEffect(() => {
65
+ if (listeners === null) return;
66
+ listeners.add(cb);
67
+ return () => {
68
+ listeners.delete(cb);
69
+ };
70
+ }, [cb, listeners]);
71
+ }
72
+
73
+ export interface TerminalInfo {
74
+ /** Terminal viewport width in pixels */
75
+ terminalWidth: number;
76
+ /** Terminal viewport height in pixels */
77
+ terminalHeight: number;
78
+ /** Width of a single character cell in pixels */
79
+ cellWidth: number;
80
+ /** Height of a single character cell in pixels */
81
+ cellHeight: number;
82
+ /** Whether the terminal supports Unicode characters */
83
+ supportsUnicode: boolean;
84
+ /** Whether the terminal supports color output */
85
+ supportsColor: boolean;
86
+ /** Whether the terminal supports Sixel graphics protocol */
87
+ supportsSixelGraphics: boolean;
88
+ /** Whether the terminal supports Kitty graphics protocol */
89
+ supportsKittyGraphics: boolean;
90
+ /** Whether the terminal supports iTerm2 inline images */
91
+ supportsITerm2Graphics: boolean;
92
+ }
93
+
94
+ export const defaultTerminalInfo: TerminalInfo = {
95
+ terminalWidth: 6 * process.stdout.columns,
96
+ terminalHeight: 12 * process.stdout.rows,
97
+ cellWidth: 6,
98
+ cellHeight: 12,
99
+ supportsUnicode: false,
100
+ supportsColor: false,
101
+ supportsSixelGraphics: false,
102
+ supportsKittyGraphics: false,
103
+ supportsITerm2Graphics: false,
104
+ };
105
+
106
+ export const TerminalInfoContext = createContext<TerminalInfo | undefined>(
107
+ undefined,
108
+ );
109
+
110
+ export const useTerminalInfo = () => {
111
+ const terminalInfo = useContext(TerminalInfoContext);
112
+
113
+ return terminalInfo ?? defaultTerminalInfo;
114
+ };
115
+
116
+ function supportsITerm2(context?: {
117
+ supportsSixelGraphics: boolean;
118
+ hasITermCellSizeResponse: boolean;
119
+ }) {
120
+ if (context?.hasITermCellSizeResponse) return true;
121
+ if (process.env.TERM_PROGRAM === "iTerm.app") {
122
+ const version = iterm2Version();
123
+ if (!version || Number(version[0]) < 3) return false;
124
+ return true;
125
+ } else if (process.env.TERM_PROGRAM === "WezTerm") {
126
+ // WezTerm is compatible with iTerm2 inline images starting from version 20220319-142410-0fcdea07
127
+ // See https://wezterm.org/imgcat.html
128
+ const version = process.env.TERM_PROGRAM_VERSION;
129
+ if (!version) return false;
130
+ const date = parseInt(version.split("-")[0], 10);
131
+ if (!Number.isNaN(date) && date >= 20220319) {
132
+ return true;
133
+ }
134
+ } else if (process.env.KONSOLE_VERSION) {
135
+ // Konsole supports iTerm2 inline images since some time around 2022
136
+ // See https://www.reddit.com/r/kde/comments/ul0irg/konsole_2204_with_sixel_support_is_out_of_beta_now/
137
+ const version = process.env.KONSOLE_VERSION;
138
+ if (!version) return false;
139
+ const date = parseInt(version, 10);
140
+ if (!Number.isNaN(date) && date >= 220400) {
141
+ return true;
142
+ }
143
+ } else if (process.env.TERM_PROGRAM === "rio") {
144
+ // Rio terminal supports iTerm2 inline images since version 0.1.13
145
+ // See https://github.com/raphamorim/rio/releases/tag/v0.1.13
146
+ const version = process.env.TERM_PROGRAM_VERSION;
147
+ if (!version) return false;
148
+ const [major, minor, patch] = version
149
+ .split(".")
150
+ .map((v) => parseInt(v, 10));
151
+ if (
152
+ !Number.isNaN(major) &&
153
+ !Number.isNaN(minor) &&
154
+ !Number.isNaN(patch) &&
155
+ (major > 0 ||
156
+ (major === 0 && minor > 1) ||
157
+ (major === 0 && minor === 1 && patch >= 13))
158
+ ) {
159
+ return true;
160
+ }
161
+ } else if (process.env.TERM_PROGRAM === "vscode") {
162
+ // VS Code's integrated terminal can be configured to support Sixel and iTerm2 graphics
163
+ // If Sixel is supported, iTerm2 images are also supported
164
+ if (context?.supportsSixelGraphics) {
165
+ return true;
166
+ }
167
+ } else if (process.env.TERM_PROGRAM === "WarpTerminal") {
168
+ const version = process.env.TERM_PROGRAM_VERSION;
169
+ // Supported since v0.2025.03.05.08.02
170
+ // See https://docs.warp.dev/getting-started/changelog
171
+ if (version) {
172
+ const [, year, month, day] = version
173
+ .slice(1)
174
+ .split(".")
175
+ .map((v) => parseInt(v, 10));
176
+ if (
177
+ !Number.isNaN(year) &&
178
+ !Number.isNaN(month) &&
179
+ !Number.isNaN(day) &&
180
+ (year > 2025 ||
181
+ (year === 2025 && month > 3) ||
182
+ (year === 2025 && month === 3 && day >= 5))
183
+ ) {
184
+ return true;
185
+ }
186
+ }
187
+ }
188
+ return false;
189
+ }
190
+
191
+ interface InkPictureProviderProps {
192
+ children: React.ReactNode;
193
+ config?: Partial<InkPictureConfig>;
194
+ terminalInfo?: Partial<TerminalInfo>;
195
+ onTerminalInfoDetection?: (terminalInfo: TerminalInfo) => void;
196
+ }
197
+
198
+ export function InkPictureProvider({
199
+ children,
200
+ config,
201
+ terminalInfo: overrides,
202
+ onTerminalInfoDetection,
203
+ }: InkPictureProviderProps) {
204
+ const { stdin, setRawMode } = useStdin();
205
+ const [terminalInfo, setTerminalInfo] = useState<TerminalInfo | undefined>(
206
+ undefined,
207
+ );
208
+
209
+ const resolvedConfig: InkPictureConfig = useMemo(() => {
210
+ const c: InkPictureConfig = {
211
+ pollIntervalMs: resolveConfig(
212
+ "INK_PICTURE_POLL_INTERVAL",
213
+ config?.pollIntervalMs,
214
+ 16,
215
+ 1,
216
+ ),
217
+ paintIntervalMs: resolveConfig(
218
+ "INK_PICTURE_PAINT_INTERVAL",
219
+ config?.paintIntervalMs,
220
+ 16,
221
+ 1,
222
+ ),
223
+ cacheSize: resolveConfig(
224
+ "INK_PICTURE_CACHE_SIZE",
225
+ config?.cacheSize,
226
+ 10,
227
+ 0,
228
+ ),
229
+ };
230
+ return c;
231
+ }, [config]);
232
+
233
+ useEffect(() => {
234
+ const controller = new AbortController();
235
+
236
+ const queryTerminalInfo = async () => {
237
+ const result = await queryTerminal(stdin, setRawMode, controller.signal);
238
+
239
+ const info = { ...defaultTerminalInfo, ...overrides };
240
+
241
+ if (result.cellWidth && result.cellHeight) {
242
+ info.cellWidth = result.cellWidth;
243
+ info.cellHeight = result.cellHeight;
244
+ } else if (result.terminalWidth && result.terminalHeight) {
245
+ info.cellWidth = result.terminalWidth / process.stdout.columns;
246
+ info.cellHeight = result.terminalHeight / process.stdout.rows;
247
+ }
248
+
249
+ info.supportsUnicode = checkIsUnicodeSupported();
250
+ info.supportsColor = !!supportsColor.stdout;
251
+
252
+ const supportsITerm2Graphics = supportsITerm2({
253
+ hasITermCellSizeResponse: result.iterm2CellWidth !== undefined,
254
+ supportsSixelGraphics: result.supportsSixelGraphics,
255
+ });
256
+ info.supportsSixelGraphics = result.supportsSixelGraphics;
257
+ info.supportsKittyGraphics = result.supportsKittyGraphics;
258
+ info.supportsITerm2Graphics = supportsITerm2Graphics;
259
+
260
+ // Apply iTerm2 cell size override for HiDPI scaling
261
+ // See https://iterm2.com/documentation-escape-codes.html#report-cell-size
262
+ if (
263
+ result.iterm2CellWidth !== undefined &&
264
+ result.iterm2CellHeight !== undefined &&
265
+ result.iterm2Scale !== undefined
266
+ ) {
267
+ info.cellWidth = result.iterm2CellWidth * result.iterm2Scale;
268
+ info.cellHeight = result.iterm2CellHeight * result.iterm2Scale;
269
+ info.terminalWidth = info.cellWidth * process.stdout.columns;
270
+ info.terminalHeight = info.cellHeight * process.stdout.rows;
271
+ }
272
+ setTerminalInfo(info);
273
+ onTerminalInfoDetection?.(info);
274
+ };
275
+ queryTerminalInfo();
276
+
277
+ return () => controller.abort();
278
+ }, [stdin, setRawMode, overrides, onTerminalInfoDetection]);
279
+
280
+ const resolvedInfo = useMemo(
281
+ () => ({ ...defaultTerminalInfo, ...terminalInfo, ...overrides }),
282
+ [terminalInfo, overrides],
283
+ );
284
+
285
+ const { current: cache } = useRef(
286
+ resolvedConfig.cacheSize === 0
287
+ ? null
288
+ : new ImageCache(resolvedConfig.cacheSize),
289
+ );
290
+
291
+ const renderListeners = useMemo(() => new Set<() => void>(), []);
292
+
293
+ const handleRender = useCallback(() => {
294
+ for (const cb of renderListeners) {
295
+ cb();
296
+ }
297
+ }, [renderListeners]);
298
+
299
+ if (!terminalInfo && !overrides) {
300
+ return null;
301
+ }
302
+
303
+ return (
304
+ <InkPictureConfigContext.Provider value={resolvedConfig}>
305
+ <ImageCacheContext.Provider value={cache}>
306
+ <TerminalInfoContext.Provider value={resolvedInfo}>
307
+ <RenderListenerContext.Provider value={renderListeners}>
308
+ <Profiler id="ink-picture" onRender={handleRender}>
309
+ {children}
310
+ </Profiler>
311
+ </RenderListenerContext.Provider>
312
+ </TerminalInfoContext.Provider>
313
+ </ImageCacheContext.Provider>
314
+ </InkPictureConfigContext.Provider>
315
+ );
316
+ }
317
+
318
+ export const TerminalInfoProvider = InkPictureProvider;
src/ink-picture/__tests__/LocalImageShow.test.ts ADDED
File without changes
src/ink-picture/components/ImageBox.tsx ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Box, type DOMElement, Newline, Text } from "src/ink";
2
+ import React, { forwardRef } from "react";
3
+
4
+ export interface ImageBoxProps {
5
+ width: number | string;
6
+ height: number | string;
7
+ alt?: string;
8
+ error?: boolean;
9
+ loaded?: boolean;
10
+ children?: React.ReactNode;
11
+ }
12
+
13
+ const ImageBox = forwardRef<DOMElement, ImageBoxProps>(function ImageBox(
14
+ { width, height, alt, error, loaded, children },
15
+ ref,
16
+ ) {
17
+ return (
18
+ <Box ref={ref} flexDirection="column" width={width} height={height}>
19
+ {loaded && children ? (
20
+ children
21
+ ) : (
22
+ <Box
23
+ flexDirection="column"
24
+ alignItems="center"
25
+ justifyContent="center"
26
+ overflow="hidden"
27
+ >
28
+ {alt ? (
29
+ <Text color="gray">{alt}</Text>
30
+ ) : error ? (
31
+ <Text color="red">
32
+ X<Newline />
33
+ Load failed
34
+ </Text>
35
+ ) : (
36
+ <Text color="gray">Loading...</Text>
37
+ )}
38
+ </Box>
39
+ )}
40
+ </Box>
41
+ );
42
+ });
43
+
44
+ export default ImageBox;
src/ink-picture/components/image/Ascii.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Text } from "src/ink";
2
+ import React, { useMemo } from "react";
3
+ import { useImage } from "../../hooks/useImage.js";
4
+ import { useMeasuredSize } from "../../hooks/useMeasuredSize.js";
5
+ import { useTerminalInfo } from "../../InkPictureProvider.js";
6
+ import { renderAscii } from "../../renderers/ascii.js";
7
+ import ImageBox from "../ImageBox.js";
8
+ import type { ImageProps } from "./protocol.js";
9
+
10
+ function AsciiImage(props: ImageProps) {
11
+ const terminalInfo = useTerminalInfo();
12
+ const { src, width, height, alt } = props;
13
+
14
+ const { containerRef, resolvedWidth, resolvedHeight } = useMeasuredSize(
15
+ width,
16
+ height,
17
+ );
18
+
19
+ const { imageData, error } = useImage({
20
+ src,
21
+ pixelWidth: resolvedWidth,
22
+ pixelHeight: resolvedHeight,
23
+ mode: "pixels",
24
+ });
25
+
26
+ const imageOutput = useMemo(() => {
27
+ if (!imageData) return null;
28
+ return renderAscii(imageData, { colored: terminalInfo?.supportsColor });
29
+ }, [imageData, terminalInfo?.supportsColor]);
30
+
31
+ return (
32
+ <ImageBox
33
+ ref={containerRef}
34
+ width={width}
35
+ height={height}
36
+ alt={alt}
37
+ error={error}
38
+ loaded={!!imageOutput}
39
+ >
40
+ {imageOutput?.split("\n").map((line, idx) => (
41
+ // biome-ignore lint/suspicious/noArrayIndexKey: static content, won't change
42
+ <Text key={`${line}-${idx}`}>{line}</Text>
43
+ ))}
44
+ </ImageBox>
45
+ );
46
+ }
47
+
48
+ export default AsciiImage;
src/ink-picture/components/image/Braille.tsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Text } from "src/ink";
2
+ import React, { useMemo } from "react";
3
+ import { useImage } from "../../hooks/useImage.js";
4
+ import { useMeasuredSize } from "../../hooks/useMeasuredSize.js";
5
+ import { renderBraille } from "../../renderers/braille.js";
6
+ import ImageBox from "../ImageBox.js";
7
+ import type { ImageProps } from "./protocol.js";
8
+
9
+ function BrailleImage(props: ImageProps) {
10
+ const { src, width, height, alt } = props;
11
+
12
+ const { containerRef, resolvedWidth, resolvedHeight } = useMeasuredSize(
13
+ width,
14
+ height,
15
+ );
16
+
17
+ const { imageData, error } = useImage({
18
+ src,
19
+ pixelWidth: resolvedWidth * 2,
20
+ pixelHeight: resolvedHeight * 4,
21
+ mode: "pixels",
22
+ });
23
+
24
+ const imageOutput = useMemo(() => {
25
+ if (!imageData) return null;
26
+ return renderBraille(imageData);
27
+ }, [imageData]);
28
+
29
+ return (
30
+ <ImageBox
31
+ ref={containerRef}
32
+ width={width}
33
+ height={height}
34
+ alt={alt}
35
+ error={error}
36
+ loaded={!!imageOutput}
37
+ >
38
+ {imageOutput?.split("\n").map((line, idx) => (
39
+ // biome-ignore lint/suspicious/noArrayIndexKey: static content, won't change
40
+ <Text key={`${line}-${idx}`}>{line}</Text>
41
+ ))}
42
+ </ImageBox>
43
+ );
44
+ }
45
+
46
+ export default BrailleImage;
src/ink-picture/components/image/HalfBlock.tsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Text } from "src/ink";
2
+ import React, { useMemo } from "react";
3
+ import { useImage } from "../../hooks/useImage.js";
4
+ import { useMeasuredSize } from "../../hooks/useMeasuredSize.js";
5
+ import { renderHalfBlock } from "../../renderers/halfBlock.js";
6
+ import ImageBox from "../ImageBox.js";
7
+ import type { ImageProps } from "./protocol.js";
8
+
9
+ function HalfBlockImage(props: ImageProps) {
10
+ const { src, width, height, alt } = props;
11
+
12
+ const { containerRef, resolvedWidth, resolvedHeight } = useMeasuredSize(
13
+ width,
14
+ height,
15
+ );
16
+
17
+ const { imageData, error } = useImage({
18
+ src,
19
+ pixelWidth: resolvedWidth,
20
+ pixelHeight: resolvedHeight * 2,
21
+ mode: "pixels",
22
+ });
23
+
24
+ const imageOutput = useMemo(() => {
25
+ if (!imageData) return null;
26
+ return renderHalfBlock(imageData);
27
+ }, [imageData]);
28
+
29
+ return (
30
+ <ImageBox
31
+ ref={containerRef}
32
+ width={width}
33
+ height={height}
34
+ alt={alt}
35
+ error={error}
36
+ loaded={!!imageOutput}
37
+ >
38
+ {imageOutput?.split("\n").map((line, idx) => (
39
+ // biome-ignore lint/suspicious/noArrayIndexKey: static content, won't change
40
+ <Text key={`${line}-${idx}`}>{line}</Text>
41
+ ))}
42
+ </ImageBox>
43
+ );
44
+ }
45
+
46
+ export default HalfBlockImage;
src/ink-picture/components/image/ITerm2.tsx ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useStdout } from "src/ink";
2
+ import React, { useMemo } from "react";
3
+ import useBackgroundColor from "../../hooks/useBackgroundColor.js";
4
+ import { useDirectRenderer } from "../../hooks/useDirectRenderer.js";
5
+ import { useImage } from "../../hooks/useImage.js";
6
+ import { useMeasuredSize } from "../../hooks/useMeasuredSize.js";
7
+ import usePosition from "../../hooks/usePosition.js";
8
+ import { useTerminalInfo } from "../../InkPictureProvider.js";
9
+ import { renderITerm2 } from "../../renderers/iterm2.js";
10
+ import ImageBox from "../ImageBox.js";
11
+ import type { ImageProps } from "./protocol.js";
12
+
13
+ function ITerm2Image(props: ImageProps) {
14
+ const terminalInfo = useTerminalInfo();
15
+ const { stdout } = useStdout();
16
+ const { src, width, height, alt } = props;
17
+
18
+ const { containerRef, resolvedWidth, resolvedHeight } = useMeasuredSize(
19
+ width,
20
+ height,
21
+ );
22
+
23
+ const componentPosition = usePosition(containerRef);
24
+ const inheritedBackgroundColor = useBackgroundColor(containerRef);
25
+
26
+ const pixelWidth = resolvedWidth * (terminalInfo?.cellWidth ?? 0);
27
+ const pixelHeight = resolvedHeight * (terminalInfo?.cellHeight ?? 0);
28
+
29
+ const { imageData, error } = useImage({
30
+ src,
31
+ pixelWidth,
32
+ pixelHeight,
33
+ mode: "png",
34
+ });
35
+
36
+ const imageOutput = useMemo(() => {
37
+ if (!imageData) return undefined;
38
+ return renderITerm2(imageData, { width: pixelWidth, height: pixelHeight });
39
+ }, [imageData, pixelWidth, pixelHeight]);
40
+
41
+ useDirectRenderer({
42
+ enabled: !!imageOutput && !!componentPosition,
43
+ imageOutput: imageOutput ?? "",
44
+ position: componentPosition,
45
+ stdout,
46
+ width: resolvedWidth,
47
+ height: resolvedHeight,
48
+ backgroundColor: inheritedBackgroundColor,
49
+ });
50
+
51
+ return (
52
+ <ImageBox
53
+ ref={containerRef}
54
+ width={width}
55
+ height={height}
56
+ alt={alt}
57
+ error={error}
58
+ loaded={!!imageOutput}
59
+ />
60
+ );
61
+ }
62
+
63
+ export default ITerm2Image;
src/ink-picture/components/image/Kitty.tsx ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useStdout } from "src/ink";
2
+ import React, { useCallback, useEffect, useRef, useState } from "react";
3
+ import { useImage } from "../../hooks/useImage.js";
4
+ import { useMeasuredSize } from "../../hooks/useMeasuredSize.js";
5
+ import usePosition from "../../hooks/usePosition.js";
6
+ import { defaultVisibility } from "../../hooks/useVisibility.js";
7
+ import { useTerminalInfo } from "../../InkPictureProvider.js";
8
+ import {
9
+ makeKittyDeletion,
10
+ makeKittyPlacement,
11
+ makeKittyTransmitChunks,
12
+ } from "../../renderers/kitty.js";
13
+ import { cursorForward, cursorUp } from "../../utils/ansiEscapes.js";
14
+ import generateKittyId from "../../utils/generateKittyId.js";
15
+ import ImageBox from "../ImageBox.js";
16
+ import type { ImageProps } from "./protocol.js";
17
+
18
+ function KittyImage(props: ImageProps) {
19
+ const terminalInfo = useTerminalInfo();
20
+ const { stdout } = useStdout();
21
+ const { src, width, height, alt } = props;
22
+
23
+ const { containerRef, resolvedWidth, resolvedHeight } = useMeasuredSize(
24
+ width,
25
+ height,
26
+ );
27
+
28
+ const componentPosition = usePosition(containerRef);
29
+
30
+ const pixelWidth = resolvedWidth * (terminalInfo?.cellWidth ?? 0);
31
+ const pixelHeight = resolvedHeight * (terminalInfo?.cellHeight ?? 0);
32
+
33
+ const { imageData, error } = useImage({
34
+ src,
35
+ pixelWidth,
36
+ pixelHeight,
37
+ mode: "png",
38
+ });
39
+
40
+ const [imageId, setImageId] = useState<number | undefined>(undefined);
41
+ const shouldCleanupRef = useRef(true);
42
+ const wasPlacedRef = useRef(false);
43
+
44
+ useEffect(() => {
45
+ if (!imageData) return;
46
+
47
+ const id = generateKittyId();
48
+ const base64Data = imageData.data.toString("base64");
49
+ const chunks = makeKittyTransmitChunks(id, base64Data);
50
+ for (const chunk of chunks) {
51
+ stdout.write(chunk);
52
+ }
53
+ setImageId(id);
54
+ }, [imageData, stdout.write]);
55
+
56
+ useEffect(() => {
57
+ if (!imageId) return;
58
+ if (!componentPosition) return;
59
+
60
+ if (
61
+ defaultVisibility(componentPosition, stdout.rows, stdout.columns) !==
62
+ "full"
63
+ ) {
64
+ if (wasPlacedRef.current) {
65
+ stdout.write(makeKittyDeletion(imageId, 1));
66
+ wasPlacedRef.current = false;
67
+ }
68
+ return;
69
+ }
70
+
71
+ stdout.write("\x1b7");
72
+ stdout.write(
73
+ cursorUp(componentPosition.appHeight - componentPosition.row, {
74
+ appHeight: componentPosition.appHeight,
75
+ terminalHeight: stdout.rows,
76
+ }),
77
+ );
78
+ stdout.write("\r");
79
+ stdout.write(cursorForward(componentPosition.col));
80
+
81
+ stdout.write(makeKittyPlacement(imageId, 1));
82
+
83
+ stdout.write("\x1b8");
84
+
85
+ wasPlacedRef.current = true;
86
+ });
87
+
88
+ const onExit = useCallback(() => {
89
+ shouldCleanupRef.current = false;
90
+ }, []);
91
+
92
+ const onSigInt = useCallback(() => {
93
+ shouldCleanupRef.current = false;
94
+ process.exit();
95
+ }, []);
96
+
97
+ useEffect(() => {
98
+ process.on("exit", onExit);
99
+ process.on("SIGINT", onSigInt);
100
+ process.on("SIGTERM", onSigInt);
101
+
102
+ return () => {
103
+ process.removeListener("exit", onExit);
104
+ process.removeListener("SIGINT", onSigInt);
105
+ process.removeListener("SIGTERM", onSigInt);
106
+ if (!shouldCleanupRef.current) return;
107
+ if (!imageId) return;
108
+
109
+ // We always delete during unmount regardless of whether the image is placed
110
+ // in order to remove the image data from the terminal
111
+ stdout.write(makeKittyDeletion(imageId));
112
+ };
113
+ }, [imageId, onExit, onSigInt, stdout.write]);
114
+
115
+ return (
116
+ <ImageBox
117
+ ref={containerRef}
118
+ width={width}
119
+ height={height}
120
+ alt={alt}
121
+ error={error}
122
+ loaded={!!imageId}
123
+ />
124
+ );
125
+ }
126
+
127
+ export default KittyImage;
src/ink-picture/components/image/Sixel.tsx ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useStdout } from "src/ink";
2
+ import React, { useMemo } from "react";
3
+ import useBackgroundColor from "../../hooks/useBackgroundColor.js";
4
+ import { useDirectRenderer } from "../../hooks/useDirectRenderer.js";
5
+ import { useImage } from "../../hooks/useImage.js";
6
+ import { useMeasuredSize } from "../../hooks/useMeasuredSize.js";
7
+ import usePosition from "../../hooks/usePosition.js";
8
+ import { useTerminalInfo } from "../../InkPictureProvider.js";
9
+ import { renderSixel } from "../../renderers/sixel.js";
10
+ import ImageBox from "../ImageBox.js";
11
+ import type { ImageProps } from "./protocol.js";
12
+
13
+ function SixelImage(props: ImageProps) {
14
+ const terminalInfo = useTerminalInfo();
15
+ const { stdout } = useStdout();
16
+ const { src, width, height, alt } = props;
17
+
18
+ const { containerRef, resolvedWidth, resolvedHeight } = useMeasuredSize(
19
+ width,
20
+ height,
21
+ );
22
+
23
+ const componentPosition = usePosition(containerRef);
24
+ const inheritedBackgroundColor = useBackgroundColor(containerRef);
25
+
26
+ const pixelWidth = resolvedWidth * (terminalInfo?.cellWidth ?? 0);
27
+ const pixelHeight = resolvedHeight * (terminalInfo?.cellHeight ?? 0);
28
+
29
+ const { imageData, error } = useImage({
30
+ src,
31
+ pixelWidth,
32
+ pixelHeight,
33
+ mode: "pixels",
34
+ });
35
+
36
+ const imageOutput = useMemo(() => {
37
+ if (!imageData) return undefined;
38
+ return renderSixel(imageData);
39
+ }, [imageData]);
40
+
41
+ useDirectRenderer({
42
+ enabled: !!imageOutput && !!componentPosition,
43
+ imageOutput: imageOutput ?? "",
44
+ position: componentPosition,
45
+ stdout,
46
+ width: resolvedWidth,
47
+ height: resolvedHeight,
48
+ backgroundColor: inheritedBackgroundColor,
49
+ });
50
+
51
+ return (
52
+ <ImageBox
53
+ ref={containerRef}
54
+ width={width}
55
+ height={height}
56
+ alt={alt}
57
+ error={error}
58
+ loaded={!!imageOutput}
59
+ />
60
+ );
61
+ }
62
+
63
+ export default SixelImage;
src/ink-picture/components/image/index.tsx ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Box, useIsScreenReaderEnabled, useStdout } from "src/ink";
2
+ import React, { useMemo } from "react";
3
+ import { useMeasuredSize } from "../../hooks/useMeasuredSize.js";
4
+ import usePosition from "../../hooks/usePosition.js";
5
+ import useProtocol from "../../hooks/useProtocol.js";
6
+ import type { GetVisibility, Visibility } from "../../hooks/useVisibility.js";
7
+ import { useVisibility } from "../../hooks/useVisibility.js";
8
+ import { useTerminalInfo } from "../../InkPictureProvider.js";
9
+ import AsciiImage from "./Ascii.js";
10
+ import BrailleImage from "./Braille.js";
11
+ import HalfBlockImage from "./HalfBlock.js";
12
+ import ITerm2Image from "./ITerm2.js";
13
+ import KittyImage from "./Kitty.js";
14
+ import type { ImageProps } from "./protocol.js";
15
+ import SixelImage from "./Sixel.js";
16
+
17
+ const imageProtocols = {
18
+ ascii: AsciiImage,
19
+ braille: BrailleImage,
20
+ halfBlock: HalfBlockImage,
21
+ iterm2: ITerm2Image,
22
+ kitty: KittyImage,
23
+ sixel: SixelImage,
24
+ };
25
+
26
+ export type ImageProtocolName = keyof typeof imageProtocols;
27
+
28
+ export type ImageProtocolHint = Partial<Record<Visibility, ImageProtocolName>>;
29
+
30
+ const ImageRenderer = (props: ImageProps & { protocol: ImageProtocolName }) => {
31
+ const ProtocolComponent = imageProtocols[props.protocol];
32
+ return <ProtocolComponent {...props} />;
33
+ };
34
+
35
+ type ImageComponentProps = Omit<ImageProps, "width" | "height"> & {
36
+ width?: number | string;
37
+ height?: number | string;
38
+ protocol?: ImageProtocolName | ImageProtocolHint;
39
+ getVisibility?: GetVisibility;
40
+ };
41
+
42
+ function Image({
43
+ protocol: specifiedProtocol,
44
+ getVisibility,
45
+ width = "100%",
46
+ height = "100%",
47
+ ...props
48
+ }: ImageComponentProps) {
49
+ const isScreenReaderEnabled = useIsScreenReaderEnabled();
50
+ const terminalInfo = useTerminalInfo();
51
+ const { stdout } = useStdout();
52
+ const protocol = useProtocol(
53
+ typeof specifiedProtocol === "string" ? specifiedProtocol : undefined,
54
+ );
55
+
56
+ const { containerRef, resolvedWidth, resolvedHeight } = useMeasuredSize(
57
+ width,
58
+ height,
59
+ );
60
+
61
+ const position = usePosition(containerRef);
62
+ const visibility = useVisibility(
63
+ position,
64
+ stdout.rows,
65
+ stdout.columns,
66
+ getVisibility,
67
+ );
68
+
69
+ const effectiveProtocol = useMemo((): ImageProtocolName => {
70
+ if (typeof specifiedProtocol === "string") return specifiedProtocol;
71
+
72
+ const hints: ImageProtocolHint = specifiedProtocol ?? {};
73
+ const hint = hints[visibility];
74
+ if (hint) return hint;
75
+
76
+ if (visibility === "full") return protocol;
77
+
78
+ if (
79
+ protocol === "ascii" ||
80
+ protocol === "braille" ||
81
+ protocol === "halfBlock"
82
+ )
83
+ return protocol;
84
+
85
+ if (terminalInfo.supportsUnicode && terminalInfo.supportsColor)
86
+ return "halfBlock";
87
+ if (terminalInfo.supportsUnicode) return "braille";
88
+ return "ascii";
89
+ }, [
90
+ visibility,
91
+ protocol,
92
+ specifiedProtocol,
93
+ terminalInfo.supportsUnicode,
94
+ terminalInfo.supportsColor,
95
+ ]);
96
+
97
+ if (isScreenReaderEnabled) {
98
+ const { src, alt } = props;
99
+ const label = `image: ${alt || (typeof src === "string" ? src : "binary image data")}`;
100
+
101
+ return (
102
+ <Box
103
+ ref={containerRef}
104
+ width={width}
105
+ height={height}
106
+ aria-label={label}
107
+ flexDirection="column"
108
+ />
109
+ );
110
+ }
111
+
112
+ return (
113
+ <Box
114
+ ref={containerRef}
115
+ width={width}
116
+ height={height}
117
+ flexDirection="column"
118
+ >
119
+ <ImageRenderer
120
+ protocol={effectiveProtocol}
121
+ key={effectiveProtocol}
122
+ width={resolvedWidth}
123
+ height={resolvedHeight}
124
+ {...props}
125
+ />
126
+ </Box>
127
+ );
128
+ }
129
+
130
+ export default Image;
src/ink-picture/components/image/protocol.ts ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Jimp } from "jimp";
2
+ import type { JSX } from "react";
3
+
4
+ export interface ImageProps {
5
+ /**
6
+ * The source URL, file path or ArrayBuffer of the image to render
7
+ * Supports all image formats supported by jimp (JPEG, PNG, WebP, GIF, BMP, TIFF).
8
+ */
9
+ src: Parameters<typeof Jimp.read>[0];
10
+ /** Width in terminal cells or a percentage */
11
+ width: number | string;
12
+ /** Height in terminal cells or a percentage */
13
+ height: number | string;
14
+ /** Alternative text displayed while loading or on error */
15
+ alt?: string;
16
+ }
17
+
18
+ /**
19
+ * Interface defining an image rendering protocol.
20
+ *
21
+ * Each protocol represents a different method of displaying images in the terminal,
22
+ * such as ASCII art, Sixel graphics, Braille patterns, or Unicode half-blocks.
23
+ *
24
+ * @interface ImageProtocol
25
+ */
26
+ export interface ImageProtocol {
27
+ /** Unique identifier for this protocol */
28
+ name: string;
29
+ /** Function that renders the image using this protocol */
30
+ render(props: ImageProps): JSX.Element | null;
31
+ }
src/ink-picture/hooks/useBackgroundColor.ts ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { DOMElement } from "src/ink";
2
+ import { type RefObject } from "react";
3
+
4
+ /**
5
+ * Walks up the DOMElement tree from the given ref to find the nearest
6
+ * ancestor Box that has a background color set.
7
+ *
8
+ * This provides the same information as Ink's internal `backgroundContext`.
9
+ */
10
+ export default function useBackgroundColor(
11
+ ref: RefObject<DOMElement | null>,
12
+ ): string | undefined {
13
+ let currentNode: DOMElement | undefined = ref.current ?? undefined;
14
+ while (currentNode) {
15
+ if (currentNode.style?.backgroundColor) {
16
+ return currentNode.style.backgroundColor as string;
17
+ }
18
+ currentNode = currentNode.parentNode;
19
+ }
20
+
21
+ return undefined;
22
+ }
src/ink-picture/hooks/useDirectRenderer.ts ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useCallback, useEffect, useLayoutEffect, useRef } from "react";
2
+ import { useOnRender } from "../InkPictureProvider.js";
3
+ import { cursorForward, cursorUp } from "../utils/ansiEscapes.js";
4
+ import bgColorize from "../utils/bgColorize.js";
5
+ import type { Position } from "./usePosition.js";
6
+ import { defaultVisibility } from "./useVisibility.js";
7
+
8
+ interface DirectRendererOptions {
9
+ enabled: boolean;
10
+ imageOutput: string;
11
+ position: Position | undefined;
12
+ stdout: NodeJS.WriteStream;
13
+ width: number;
14
+ height: number;
15
+ backgroundColor?: string;
16
+ }
17
+
18
+ function writeImageToStdout(
19
+ pos: Position,
20
+ output: string,
21
+ stream: NodeJS.WriteStream,
22
+ w: number,
23
+ h: number,
24
+ ): { row: number; col: number; width: number; height: number } {
25
+ stream.write("\x1b7");
26
+ stream.write(
27
+ cursorUp(pos.appHeight - pos.row, {
28
+ appHeight: pos.appHeight,
29
+ terminalHeight: stream.rows,
30
+ }),
31
+ );
32
+ stream.write("\r");
33
+ stream.write(cursorForward(pos.col));
34
+ stream.write(output);
35
+ stream.write("\x1b8");
36
+
37
+ return {
38
+ row: stream.rows - pos.appHeight + pos.row,
39
+ col: pos.col,
40
+ width: w,
41
+ height: h,
42
+ };
43
+ }
44
+
45
+ export function useDirectRenderer(options: DirectRendererOptions) {
46
+ const { position, stdout, backgroundColor } = options;
47
+ const shouldCleanupRef = useRef(true);
48
+ const previousBboxRef = useRef<
49
+ { row: number; col: number; width: number; height: number } | undefined
50
+ >(undefined);
51
+
52
+ const optionsRef = useRef(options);
53
+ optionsRef.current = options;
54
+
55
+ const writeNow = useCallback(() => {
56
+ const opt = optionsRef.current;
57
+ if (!opt.enabled || !opt.position) return;
58
+ if (
59
+ defaultVisibility(opt.position, opt.stdout.rows, opt.stdout.columns) !==
60
+ "full"
61
+ )
62
+ return;
63
+
64
+ previousBboxRef.current = writeImageToStdout(
65
+ opt.position,
66
+ opt.imageOutput,
67
+ opt.stdout,
68
+ opt.width,
69
+ opt.height,
70
+ );
71
+ }, []);
72
+
73
+ // Repaint after every React render commit, since Ink clears the terminal
74
+ // on each render cycle, wiping any direct-to-stdout image output.
75
+ useOnRender(writeNow);
76
+
77
+ useLayoutEffect(() => {
78
+ return () => {
79
+ if (!shouldCleanupRef.current) return;
80
+
81
+ const bbox = previousBboxRef.current;
82
+ if (!bbox || !position) return;
83
+
84
+ stdout.write("\x1b7");
85
+ stdout.write(
86
+ cursorUp(position.appHeight - position.row, {
87
+ appHeight: position.appHeight,
88
+ terminalHeight: stdout.rows,
89
+ }),
90
+ );
91
+ for (let i = 0; i < bbox.height; i++) {
92
+ stdout.write("\r");
93
+ stdout.write(cursorForward(bbox.col));
94
+ if (backgroundColor) {
95
+ stdout.write(bgColorize(" ".repeat(bbox.width), backgroundColor));
96
+ } else {
97
+ stdout.write(" ".repeat(bbox.width));
98
+ }
99
+ stdout.write("\n");
100
+ }
101
+ stdout.write("\x1b8");
102
+ };
103
+ });
104
+
105
+ useEffect(() => {
106
+ function onExit() {
107
+ shouldCleanupRef.current = false;
108
+ }
109
+ function onSigInt() {
110
+ shouldCleanupRef.current = false;
111
+ process.exit();
112
+ }
113
+ process.on("exit", onExit);
114
+ process.on("SIGINT", onSigInt);
115
+ process.on("SIGTERM", onSigInt);
116
+
117
+ return () => {
118
+ process.removeListener("exit", onExit);
119
+ process.removeListener("SIGINT", onSigInt);
120
+ process.removeListener("SIGTERM", onSigInt);
121
+ };
122
+ }, []);
123
+ }
src/ink-picture/hooks/useImage.ts ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { type Jimp } from "jimp";
2
+ import { useEffect, useState } from "react";
3
+ import { useImageCache } from "../InkPictureProvider.js";
4
+ import type { PixelData, PngData } from "../renderers/types.js";
5
+ import { fetchImage, getPngBuffer, getRawPixels } from "../utils/image.js";
6
+
7
+ export function useImage<T extends "pixels" | "png" = "pixels">(options: {
8
+ src: Parameters<typeof Jimp.read>[0];
9
+ pixelWidth: number;
10
+ pixelHeight: number;
11
+ mode?: T;
12
+ }): T extends "png"
13
+ ? { imageData: PngData | undefined; error: boolean }
14
+ : { imageData: PixelData | undefined; error: boolean } {
15
+ const { src, pixelWidth, pixelHeight, mode = "pixels" } = options;
16
+ const [imageData, setImageData] = useState<PngData | PixelData | undefined>(
17
+ undefined,
18
+ );
19
+ const [error, setError] = useState(false);
20
+ const cache = useImageCache();
21
+
22
+ useEffect(() => {
23
+ if (pixelWidth === 0 || pixelHeight === 0) return;
24
+
25
+ let cancelled = false;
26
+
27
+ const load = async () => {
28
+ let image = typeof src === "string" ? cache?.get(src) : undefined;
29
+
30
+ if (!image) {
31
+ image = await fetchImage(src);
32
+ if (cancelled) return;
33
+
34
+ if (!image) {
35
+ setError(true);
36
+ setImageData(undefined);
37
+ return;
38
+ }
39
+
40
+ if (typeof src === "string") {
41
+ cache?.set(src, image);
42
+ }
43
+ }
44
+
45
+ setError(false);
46
+ image.resize({ w: pixelWidth, h: pixelHeight });
47
+
48
+ if (mode === "png") {
49
+ const result = await getPngBuffer(image);
50
+ if (!cancelled) {
51
+ setImageData(result);
52
+ }
53
+ } else {
54
+ const result = await getRawPixels(image);
55
+ if (!cancelled) {
56
+ setImageData(result);
57
+ }
58
+ }
59
+ };
60
+
61
+ load();
62
+
63
+ return () => {
64
+ cancelled = true;
65
+ };
66
+ }, [src, pixelWidth, pixelHeight, mode, cache]);
67
+
68
+ return { imageData, error } as T extends "png"
69
+ ? { imageData: PngData | undefined; error: boolean }
70
+ : { imageData: PixelData | undefined; error: boolean };
71
+ }
src/ink-picture/hooks/useMeasuredSize.ts ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { type DOMElement, measureElement } from "src/ink";
2
+ import { useEffect, useRef, useState } from "react";
3
+
4
+ export function useMeasuredSize(
5
+ width: number | string,
6
+ height: number | string,
7
+ ) {
8
+ const containerRef = useRef<DOMElement | null>(null);
9
+ const [measuredWidth, setMeasuredWidth] = useState(0);
10
+ const [measuredHeight, setMeasuredHeight] = useState(0);
11
+
12
+ const needsMeasure = typeof width === "string" || typeof height === "string";
13
+ useEffect(() => {
14
+ if (!needsMeasure) return;
15
+ if (!containerRef.current) return;
16
+
17
+ const { width: w, height: h } = measureElement(containerRef.current);
18
+ if (w > 0) setMeasuredWidth(w);
19
+ if (h > 0) setMeasuredHeight(h);
20
+ });
21
+
22
+ return {
23
+ containerRef,
24
+ resolvedWidth: typeof width === "number" ? width : measuredWidth,
25
+ resolvedHeight: typeof height === "number" ? height : measuredHeight,
26
+ };
27
+ }
src/ink-picture/hooks/usePosition.ts ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { DOMElement } from "src/ink";
2
+ import {
3
+ type RefObject,
4
+ useCallback,
5
+ useEffect,
6
+ useRef,
7
+ useState,
8
+ } from "react";
9
+ import { useInkPictureConfig } from "../InkPictureProvider.js";
10
+
11
+ export type Position = {
12
+ /**
13
+ * Column position (x-coordinate) in terminal cells.
14
+ */
15
+ col: number;
16
+ /**
17
+ * Row position (y-coordinate) in terminal cells.
18
+ */
19
+ row: number;
20
+ /**
21
+ * Element width in terminal cells.
22
+ */
23
+ width: number;
24
+ /**
25
+ * Element height in terminal cells.
26
+ */
27
+ height: number;
28
+ /**
29
+ * Application width in terminal cells.
30
+ */
31
+ appWidth: number;
32
+ /**
33
+ * Application height in terminal cells.
34
+ */
35
+ appHeight: number;
36
+ };
37
+
38
+ /**
39
+ * Get the current position and dimensions of a mounted component in terminal cells.
40
+ *
41
+ * @param ref - React ref pointing to a Box component
42
+ * @returns Position object with col, row, width, and height
43
+ */
44
+ const usePosition = (
45
+ ref: RefObject<DOMElement | null>,
46
+ ): Position | undefined => {
47
+ const [position, setPosition] = useState<Position | undefined>(undefined);
48
+
49
+ const updatePosition = useCallback(() => {
50
+ const node = ref.current;
51
+ if (!node?.yogaNode) {
52
+ return;
53
+ }
54
+
55
+ const { yogaNode } = node;
56
+
57
+ // Calculate absolute position by traversing up the tree
58
+ let absoluteCol = 0;
59
+ let absoluteRow = 0;
60
+ let appWidth = 0;
61
+ let appHeight = 0;
62
+ let currentNode: DOMElement | undefined = node;
63
+
64
+ while (currentNode) {
65
+ if (currentNode.yogaNode) {
66
+ absoluteCol += currentNode.yogaNode.getComputedLeft();
67
+ absoluteRow += currentNode.yogaNode.getComputedTop();
68
+ appWidth = currentNode.yogaNode.getComputedWidth();
69
+ appHeight = currentNode.yogaNode.getComputedHeight();
70
+ }
71
+
72
+ currentNode = currentNode.parentNode;
73
+ }
74
+
75
+ const newPosition: Position = {
76
+ col: absoluteCol,
77
+ row: absoluteRow,
78
+ width: yogaNode.getComputedWidth(),
79
+ height: yogaNode.getComputedHeight(),
80
+ appWidth,
81
+ appHeight,
82
+ };
83
+
84
+ setPosition((previousPosition) => {
85
+ // Only update if position actually changed to avoid unnecessary re-renders
86
+ if (
87
+ !previousPosition ||
88
+ previousPosition.col !== newPosition.col ||
89
+ previousPosition.row !== newPosition.row ||
90
+ previousPosition.width !== newPosition.width ||
91
+ previousPosition.height !== newPosition.height ||
92
+ previousPosition.appWidth !== newPosition.appWidth ||
93
+ previousPosition.appHeight !== newPosition.appHeight
94
+ ) {
95
+ return newPosition;
96
+ }
97
+
98
+ return previousPosition;
99
+ });
100
+ }, [ref]);
101
+
102
+ // Calculate position after every render
103
+ // This captures layout changes caused by the component's own re-renders
104
+ useEffect(() => {
105
+ updatePosition();
106
+ });
107
+
108
+ // Poll to detect layout changes that happen without re-rendering
109
+ // e.g. parent layout changes, where React bails out from re-rendering children
110
+ const config = useInkPictureConfig();
111
+ const configRef = useRef(config);
112
+ configRef.current = config;
113
+
114
+ useEffect(() => {
115
+ let timeout: ReturnType<typeof setTimeout>;
116
+
117
+ function schedule() {
118
+ timeout = setTimeout(tick, configRef.current.pollIntervalMs);
119
+ timeout.unref();
120
+ }
121
+
122
+ function tick() {
123
+ updatePosition();
124
+ schedule();
125
+ }
126
+
127
+ schedule();
128
+
129
+ return () => {
130
+ clearTimeout(timeout);
131
+ };
132
+ }, [updatePosition]);
133
+
134
+ return position;
135
+ };
136
+
137
+ export default usePosition;
src/ink-picture/hooks/useProtocol.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useTerminalInfo } from "../InkPictureProvider.js";
2
+ import { getBestProtocol } from "../utils/getBestProtocol.js";
3
+
4
+ export default function useProtocol(
5
+ specifiedProtocol?:
6
+ | "ascii"
7
+ | "braille"
8
+ | "halfBlock"
9
+ | "iterm2"
10
+ | "kitty"
11
+ | "sixel",
12
+ ): "ascii" | "braille" | "halfBlock" | "iterm2" | "kitty" | "sixel" {
13
+ const terminalInfo = useTerminalInfo();
14
+
15
+ if (specifiedProtocol) {
16
+ return specifiedProtocol;
17
+ }
18
+
19
+ if (!terminalInfo) {
20
+ return "ascii";
21
+ }
22
+
23
+ return getBestProtocol(terminalInfo);
24
+ }
src/ink-picture/hooks/useVisibility.ts ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMemo } from "react";
2
+ import type { Position } from "./usePosition.js";
3
+
4
+ export type Visibility = "full" | "partial" | "hidden";
5
+
6
+ export interface VisibilityInfo {
7
+ position: Position;
8
+ terminalWidth: number;
9
+ terminalHeight: number;
10
+ defaultVisibility: Visibility;
11
+ }
12
+
13
+ export type GetVisibility = (info: VisibilityInfo) => Visibility;
14
+
15
+ export function defaultVisibility(
16
+ position: Position,
17
+ terminalHeight: number,
18
+ terminalWidth: number,
19
+ ): Visibility {
20
+ const viewportStartRow = terminalHeight - position.appHeight + position.row;
21
+ const viewportEndRow = viewportStartRow + position.height;
22
+ const inAppStartRow = position.row;
23
+ const inAppEndRow = inAppStartRow + position.height;
24
+
25
+ if (
26
+ viewportEndRow <= 0 ||
27
+ inAppEndRow <= 0 ||
28
+ viewportStartRow >= terminalHeight ||
29
+ inAppStartRow >= position.appHeight
30
+ ) {
31
+ return "hidden";
32
+ }
33
+
34
+ const viewportEndCol = position.col + position.width;
35
+ if (
36
+ viewportEndCol <= 0 ||
37
+ position.col >= terminalWidth ||
38
+ position.col >= position.appWidth
39
+ ) {
40
+ return "hidden";
41
+ }
42
+
43
+ if (
44
+ viewportStartRow < 0 ||
45
+ inAppStartRow < 0 ||
46
+ viewportEndRow > terminalHeight ||
47
+ inAppEndRow > position.appHeight ||
48
+ position.col < 0 ||
49
+ viewportEndCol > terminalWidth ||
50
+ viewportEndCol > position.appWidth
51
+ ) {
52
+ return "partial";
53
+ }
54
+
55
+ return "full";
56
+ }
57
+
58
+ export function useVisibility(
59
+ position: Position | undefined,
60
+ terminalHeight: number,
61
+ terminalWidth: number,
62
+ getVisibility?: GetVisibility,
63
+ ): Visibility {
64
+ return useMemo(() => {
65
+ if (!position) return "hidden";
66
+
67
+ const visibility = defaultVisibility(
68
+ position,
69
+ terminalHeight,
70
+ terminalWidth,
71
+ );
72
+
73
+ if (getVisibility) {
74
+ return getVisibility({
75
+ position,
76
+ terminalWidth,
77
+ terminalHeight,
78
+ defaultVisibility: visibility,
79
+ });
80
+ }
81
+
82
+ return visibility;
83
+ }, [position, terminalHeight, terminalWidth, getVisibility]);
84
+ }
src/ink-picture/index.ts ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * ink-picture - Better image component for Ink CLI/TUIs
3
+ *
4
+ * This library provides components for rendering images in terminal applications
5
+ * built with Ink. It supports multiple rendering protocols with automatic fallback:
6
+ * Kitty, iTerm2, Sixel, Half-Block, Braille, and ASCII.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * import React from 'react';
11
+ * import { Box } from 'ink';
12
+ * import Image, { InkPictureProvider } from 'ink-picture';
13
+ *
14
+ * function App() {
15
+ * return (
16
+ * <InkPictureProvider>
17
+ * <Box flexDirection="column">
18
+ * <Image
19
+ * src="https://example.com/image.jpg"
20
+ * width={40}
21
+ * height={20}
22
+ * alt="Example image"
23
+ * />
24
+ * </Box>
25
+ * </InkPictureProvider>
26
+ * );
27
+ * }
28
+ * ```
29
+ *
30
+ * The Image component must be used within an InkPictureProvider.
31
+ * This ensures terminal capabilities and configuration are detected and provided.
32
+ */
33
+
34
+ export { default as AsciiImage } from "./components/image/Ascii.js";
35
+ export { default as BrailleImage } from "./components/image/Braille.js";
36
+ export { default as HalfBlockImage } from "./components/image/HalfBlock.js";
37
+ export { default as ITerm2Image } from "./components/image/ITerm2.js";
38
+ export type {
39
+ ImageProtocolHint,
40
+ ImageProtocolName,
41
+ } from "./components/image/index.js";
42
+ export { default } from "./components/image/index.js";
43
+ export { default as KittyImage } from "./components/image/Kitty.js";
44
+ export type { ImageProps, ImageProtocol } from "./components/image/protocol.js";
45
+ export { default as SixelImage } from "./components/image/Sixel.js";
46
+
47
+ export { default as usePosition } from "./hooks/usePosition.js";
48
+ export type {
49
+ GetVisibility,
50
+ Visibility,
51
+ VisibilityInfo,
52
+ } from "./hooks/useVisibility.js";
53
+ export { useVisibility } from "./hooks/useVisibility.js";
54
+
55
+ export {
56
+ defaultConfig as defaultInkPictureConfig,
57
+ defaultTerminalInfo,
58
+ type InkPictureConfig,
59
+ InkPictureProvider,
60
+ type TerminalInfo,
61
+ TerminalInfoContext,
62
+ TerminalInfoProvider,
63
+ useImageCache,
64
+ useInkPictureConfig,
65
+ useOnRender,
66
+ useTerminalInfo,
67
+ } from "./InkPictureProvider.js";
src/ink-picture/renderers/ascii.ts ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import chalk from "chalk";
2
+ import type { PixelData } from "./types.js";
3
+
4
+ const ASCII_CHARS =
5
+ "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. ";
6
+
7
+ export function renderAscii(
8
+ pixels: PixelData,
9
+ options: { colored?: boolean } = {},
10
+ ): string {
11
+ const { data, info } = pixels;
12
+ const { width, height, channels } = info;
13
+ const colored = options.colored ?? true;
14
+
15
+ let result = "";
16
+ for (let y = 0; y < height; y++) {
17
+ for (let x = 0; x < width; x++) {
18
+ const pixelIndex = (y * width + x) * channels;
19
+
20
+ const r = data[pixelIndex] as number;
21
+ const g = data[pixelIndex + 1] as number;
22
+ const b = data[pixelIndex + 2] as number;
23
+ const a = channels === 4 ? (data[pixelIndex + 3] as number) : 255;
24
+
25
+ const sum = r + g + b + a;
26
+ const intensity = sum === 0 ? 0 : sum / (255 * 4);
27
+ const char =
28
+ ASCII_CHARS[
29
+ ASCII_CHARS.length -
30
+ 1 -
31
+ Math.floor(intensity * (ASCII_CHARS.length - 1))
32
+ ];
33
+
34
+ result += colored ? chalk.rgb(r, g, b)(char) : char;
35
+ }
36
+
37
+ result += "\n";
38
+ }
39
+
40
+ return result.slice(0, -1);
41
+ }
src/ink-picture/renderers/braille.ts ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { PixelData } from "./types.js";
2
+
3
+ export function renderBraille(pixels: PixelData): string {
4
+ const { data, info } = pixels;
5
+ const { width, height, channels } = info;
6
+
7
+ let result = "";
8
+ for (let y = 0; y < height - 3; y += 4) {
9
+ for (let x = 0; x < width - 1; x += 2) {
10
+ const dot1Index = (y * width + x) * channels;
11
+ const dot2Index = ((y + 1) * width + x) * channels;
12
+ const dot3Index = ((y + 2) * width + x) * channels;
13
+ const dot4Index = (y * width + x + 1) * channels;
14
+ const dot5Index = ((y + 1) * width + x + 1) * channels;
15
+ const dot6Index = ((y + 2) * width + x + 1) * channels;
16
+ const dot7Index = ((y + 3) * width + x) * channels;
17
+ const dot8Index = ((y + 3) * width + x + 1) * channels;
18
+
19
+ const getRgba = (index: number) => {
20
+ const r = data[index] as number;
21
+ const g = data[index + 1] as number;
22
+ const b = data[index + 2] as number;
23
+ const a = channels === 4 ? (data[index + 3] as number) : 1;
24
+ return { r, g, b, a };
25
+ };
26
+
27
+ const dot1 = rgbaToBlackOrWhite(getRgba(dot1Index));
28
+ const dot2 = rgbaToBlackOrWhite(getRgba(dot2Index));
29
+ const dot3 = rgbaToBlackOrWhite(getRgba(dot3Index));
30
+ const dot4 = rgbaToBlackOrWhite(getRgba(dot4Index));
31
+ const dot5 = rgbaToBlackOrWhite(getRgba(dot5Index));
32
+ const dot6 = rgbaToBlackOrWhite(getRgba(dot6Index));
33
+ const dot7 = rgbaToBlackOrWhite(getRgba(dot7Index));
34
+ const dot8 = rgbaToBlackOrWhite(getRgba(dot8Index));
35
+
36
+ const brailleChar = String.fromCharCode(
37
+ 0x2800 +
38
+ (dot8 << 7) +
39
+ (dot7 << 6) +
40
+ (dot6 << 5) +
41
+ (dot5 << 4) +
42
+ (dot4 << 3) +
43
+ (dot3 << 2) +
44
+ (dot2 << 1) +
45
+ dot1,
46
+ );
47
+ result += brailleChar;
48
+ }
49
+ result += "\n";
50
+ }
51
+
52
+ return result.slice(0, -1);
53
+ }
54
+
55
+ function rgbaToBlackOrWhite({
56
+ r,
57
+ g,
58
+ b,
59
+ a,
60
+ }: {
61
+ r: number;
62
+ g: number;
63
+ b: number;
64
+ a: number;
65
+ }) {
66
+ const alpha = a / 255;
67
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
68
+ const alphaAdjustedLuminance = luminance * alpha + 255 * (1 - alpha);
69
+
70
+ if (alphaAdjustedLuminance > 128) {
71
+ return 1;
72
+ }
73
+ return 0;
74
+ }
src/ink-picture/renderers/halfBlock.ts ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import chalk from "chalk";
2
+ import type { PixelData } from "./types.js";
3
+
4
+ const HALF_BLOCK = "\u2584";
5
+
6
+ export function renderHalfBlock(pixels: PixelData): string {
7
+ const { data, info } = pixels;
8
+ const { width, height, channels } = info;
9
+
10
+ let result = "";
11
+ for (let y = 0; y < height - 1; y += 2) {
12
+ for (let x = 0; x < width; x++) {
13
+ const topPixelIndex = (y * width + x) * channels;
14
+ const bottomPixelIndex = ((y + 1) * width + x) * channels;
15
+
16
+ const r = data[topPixelIndex] as number;
17
+ const g = data[topPixelIndex + 1] as number;
18
+ const b = data[topPixelIndex + 2] as number;
19
+ const a = channels === 4 ? (data[topPixelIndex + 3] as number) : 255;
20
+
21
+ const r2 = data[bottomPixelIndex] as number;
22
+ const g2 = data[bottomPixelIndex + 1] as number;
23
+ const b2 = data[bottomPixelIndex + 2] as number;
24
+
25
+ result +=
26
+ a === 0
27
+ ? chalk.reset(" ")
28
+ : chalk.bgRgb(r, g, b).rgb(r2, g2, b2)(HALF_BLOCK);
29
+ }
30
+
31
+ result += "\n";
32
+ }
33
+
34
+ return result.slice(0, -1);
35
+ }
src/ink-picture/renderers/index.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export { renderAscii } from "./ascii.js";
2
+ export { renderBraille } from "./braille.js";
3
+ export { renderHalfBlock } from "./halfBlock.js";
4
+ export { renderITerm2 } from "./iterm2.js";
5
+ export {
6
+ makeKittyDeletion,
7
+ makeKittyPlacement,
8
+ makeKittyTransmitChunks,
9
+ } from "./kitty.js";
10
+ export { renderSixel } from "./sixel.js";
11
+ export type { PixelData, PngData } from "./types.js";
src/ink-picture/renderers/iterm2.ts ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { PngData } from "./types.js";
2
+
3
+ export function renderITerm2(
4
+ png: PngData,
5
+ options: { width: number; height: number },
6
+ ): string {
7
+ const { data } = png;
8
+ const { width, height } = options;
9
+
10
+ return (
11
+ "\x1b]1337;File=" +
12
+ `size=${data.length};` +
13
+ `width=${width}px;height=${height}px;` +
14
+ `preserveAspectRatio=1;` +
15
+ `inline=1:` +
16
+ data.toString("base64") +
17
+ "\x07"
18
+ );
19
+ }
src/ink-picture/renderers/kitty.ts ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const DEFAULT_CHUNK_SIZE = 4096;
2
+
3
+ export function makeKittyTransmitChunks(
4
+ imageId: number,
5
+ pngBase64: string,
6
+ chunkSize: number = DEFAULT_CHUNK_SIZE,
7
+ ): string[] {
8
+ const chunks: string[] = [];
9
+
10
+ if (pngBase64.length <= chunkSize) {
11
+ chunks.push(`\x1b_Gf=100,t=d,i=${imageId},m=0,q=2;${pngBase64}\x1b\\`);
12
+ return chunks;
13
+ }
14
+
15
+ const firstChunk = pngBase64.slice(0, chunkSize);
16
+ chunks.push(`\x1b_Gf=100,t=d,i=${imageId},m=1,q=2;${firstChunk}\x1b\\`);
17
+
18
+ let bufferOffset = chunkSize;
19
+ while (bufferOffset < pngBase64.length - chunkSize) {
20
+ const chunk = pngBase64.slice(bufferOffset, bufferOffset + chunkSize);
21
+ bufferOffset += chunkSize;
22
+ chunks.push(`\x1b_Gm=1,q=2;${chunk}\x1b\\`);
23
+ }
24
+
25
+ const lastChunk = pngBase64.slice(bufferOffset);
26
+ chunks.push(`\x1b_Gm=0,q=2;${lastChunk}\x1b\\`);
27
+
28
+ return chunks;
29
+ }
30
+
31
+ export function makeKittyPlacement(
32
+ imageId: number,
33
+ placementId: number = 1,
34
+ ): string {
35
+ return `\x1b_Ga=p,i=${imageId},p=${placementId},C=1,q=2\x1b\\`;
36
+ }
37
+
38
+ export function makeKittyDeletion(
39
+ imageId: number,
40
+ placementId?: number,
41
+ ): string {
42
+ if (placementId !== undefined) {
43
+ return `\x1b_Ga=d,d=i,p=${placementId},i=${imageId}\x1b\\`;
44
+ }
45
+ return `\x1b_Ga=d,d=I,i=${imageId}\x1b\\`;
46
+ }
src/ink-picture/renderers/sixel.ts ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import { image2sixel } from "sixel";
2
+ import type { PixelData } from "./types.js";
3
+
4
+ export function renderSixel(pixels: PixelData): string {
5
+ const { data, info } = pixels;
6
+ const u8Data = new Uint8Array(data);
7
+ return image2sixel(u8Data, info.width, info.height);
8
+ }
src/ink-picture/renderers/types.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface PixelData {
2
+ data: Buffer;
3
+ info: {
4
+ width: number;
5
+ height: number;
6
+ channels: number;
7
+ };
8
+ }
9
+
10
+ export interface PngData {
11
+ data: Buffer;
12
+ info: {
13
+ width: number;
14
+ height: number;
15
+ };
16
+ }
src/ink-picture/utils/ansiEscapes.ts ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Moves cursor forward (right) by specified number of columns.
3
+ * @param count - Number of columns to move forward (default: 1)
4
+ * @returns ANSI escape sequence string
5
+ */
6
+ export function cursorForward(count: number = 1) {
7
+ if (count === 0) return "";
8
+ return `\x1b[${count}C`;
9
+ }
10
+
11
+ /**
12
+ * Moves cursor up by specified number of rows.
13
+ * @param count - Number of rows to move up (default: 1)
14
+ * @param context - Object containing appHeight and terminalHeight
15
+ * @returns ANSI escape sequence string
16
+ */
17
+ export function cursorUp(
18
+ count: number = 1,
19
+ context: { appHeight: number; terminalHeight: number },
20
+ ) {
21
+ // Ink appends a newline after output unless the app height exceeds the terminal height
22
+ // See https://github.com/vadimdemedes/ink/blob/76d221c3639f62c8c2f6c3599d51a1bf51ed1b7b/src/ink.tsx#L1040-L1042
23
+ // So we need to adjust the cursor movement by one row in that case to account for the absence of the newline
24
+ const movementCount =
25
+ context.appHeight >= context.terminalHeight ? count - 1 : count;
26
+ if (movementCount <= 0) return "";
27
+ return `\x1b[${movementCount}A`;
28
+ }
src/ink-picture/utils/bgColorize.ts ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import chalk from "chalk";
2
+
3
+ const rgbRegex = /^rgb\(\s?(\d+),\s?(\d+),\s?(\d+)\s?\)$/;
4
+ const ansiRegex = /^ansi256\(\s?(\d+)\s?\)$/;
5
+
6
+ const isNamedColor = (color: string): color is keyof typeof chalk =>
7
+ color in chalk;
8
+
9
+ export default function bgColorize(str: string, color: string): string {
10
+ if (isNamedColor(color)) {
11
+ const methodName = `bg${color[0].toUpperCase() + color.slice(1)}` as
12
+ | keyof typeof chalk
13
+ | undefined;
14
+ if (methodName && methodName in chalk) {
15
+ return (chalk[methodName] as (s: string) => string)(str);
16
+ }
17
+
18
+ return str;
19
+ }
20
+
21
+ if (color.startsWith("#")) {
22
+ return chalk.bgHex(color)(str);
23
+ }
24
+
25
+ if (color.startsWith("ansi256")) {
26
+ const matches = ansiRegex.exec(color);
27
+ if (matches) {
28
+ return chalk.bgAnsi256(Number(matches[1]))(str);
29
+ }
30
+
31
+ return str;
32
+ }
33
+
34
+ if (color.startsWith("rgb")) {
35
+ const matches = rgbRegex.exec(color);
36
+ if (matches) {
37
+ return chalk.bgRgb(
38
+ Number(matches[1]),
39
+ Number(matches[2]),
40
+ Number(matches[3]),
41
+ )(str);
42
+ }
43
+
44
+ return str;
45
+ }
46
+
47
+ return str;
48
+ }
src/ink-picture/utils/generateKittyId.ts ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Generates unique IDs for Kitty image rendering.
3
+ */
4
+
5
+ const MIN_ID = 1048576;
6
+ const MAX_ID = 16777216;
7
+
8
+ class KittyIdGenerator {
9
+ private currentId = MIN_ID;
10
+ private freedIds = new Set<number>();
11
+
12
+ generateId(): number {
13
+ // Reuse freed ID if available
14
+ if (this.freedIds.size > 0) {
15
+ const result = this.freedIds.values().next();
16
+ if (result.done) {
17
+ throw new Error(
18
+ "Kitty ID reuse error: Set iterator unexpectedly empty",
19
+ );
20
+ }
21
+ const reusedId = result.value;
22
+ this.freedIds.delete(reusedId);
23
+ return reusedId;
24
+ }
25
+
26
+ // Generate new sequential ID
27
+ if (this.currentId >= MAX_ID) {
28
+ throw new Error("Kitty ID overflow: Maximum ID value reached");
29
+ }
30
+
31
+ return this.currentId++;
32
+ }
33
+
34
+ freeId(id: number): boolean {
35
+ // Validate ID is in our managed range and was previously allocated
36
+ if (id >= MIN_ID && id < this.currentId) {
37
+ return this.freedIds.add(id).size > this.freedIds.size - 1;
38
+ }
39
+ return false;
40
+ }
41
+
42
+ // Utility methods
43
+ getActiveIdCount(): number {
44
+ return this.currentId - MIN_ID - this.freedIds.size;
45
+ }
46
+
47
+ getAvailableIdCount(): number {
48
+ return MAX_ID - this.currentId + this.freedIds.size;
49
+ }
50
+ }
51
+
52
+ // Export singleton instance
53
+ const generator = new KittyIdGenerator();
54
+ export const generateId = () => generator.generateId();
55
+ export const freeId = (id: number) => generator.freeId(id);
56
+ export default generateId;
src/ink-picture/utils/getBestProtocol.ts ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { TerminalInfo } from "../InkPictureProvider.js";
2
+
3
+ // Heuristic for selecting the best image protocol based on terminal capabilities
4
+ function getBestProtocol(
5
+ caps: TerminalInfo,
6
+ ): "sixel" | "kitty" | "iterm2" | "braille" | "halfBlock" | "ascii" {
7
+ if (process.env.TERM_PROGRAM === "iTerm.app" && caps.supportsITerm2Graphics) {
8
+ // Testing has shown that iTerm2 might be adding kitty support in stealth but it's not mature yet
9
+ return "iterm2";
10
+ } else if (
11
+ process.env.TERM_PROGRAM === "WarpTerminal" &&
12
+ caps.supportsITerm2Graphics
13
+ ) {
14
+ // Warp terminal only supports a incomplete subset of kitty graphics so we are not using it for now
15
+ return "iterm2";
16
+ } else if (
17
+ process.env.TERM_PROGRAM === "WezTerm" &&
18
+ caps.supportsITerm2Graphics
19
+ ) {
20
+ // WezTerm doesn't support placing image above text so it may introduce race conditions during rerenders like sixel and iterm2
21
+ // See https://github.com/wezterm/wezterm/issues/7222
22
+ // a workaround is implemented for sixel and iterm2 to mitigate race conditions but I don't think it's a good idea to do the same for kitty specifically for wezterm
23
+ return "iterm2";
24
+ } else if (process.env.KONSOLE_VERSION && caps.supportsKittyGraphics) {
25
+ // Kitty is the only properly working protocol out of the three on Konsole
26
+ // because it has non-standard implementation for overwriting cells containing graphics with text which makes sixel and iterm2 leave artifacts
27
+ return "kitty";
28
+ } else if (caps.supportsKittyGraphics) {
29
+ return "kitty";
30
+ } else if (caps.supportsITerm2Graphics) {
31
+ return "iterm2";
32
+ } else if (caps.supportsSixelGraphics) {
33
+ return "sixel";
34
+ } else if (caps.supportsUnicode && caps.supportsColor) {
35
+ return "halfBlock";
36
+ } else if (caps.supportsUnicode) {
37
+ return "braille";
38
+ } else {
39
+ return "ascii";
40
+ }
41
+ }
42
+
43
+ export { getBestProtocol };
src/ink-picture/utils/image.ts ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { fileURLToPath } from "node:url";
2
+ import { Jimp } from "jimp";
3
+
4
+ export type JimpImage = Awaited<ReturnType<typeof Jimp.read>>;
5
+
6
+ export interface ImageOutputInfo {
7
+ width: number;
8
+ height: number;
9
+ channels: number;
10
+ size?: number;
11
+ }
12
+
13
+ export async function fetchImage(
14
+ src: Parameters<typeof Jimp.read>[0],
15
+ ): Promise<JimpImage | undefined> {
16
+ try {
17
+ if (typeof src === "string" && src.startsWith("file://")) {
18
+ return await Jimp.read(fileURLToPath(src));
19
+ }
20
+ return await Jimp.read(src);
21
+ } catch {
22
+ return undefined;
23
+ }
24
+ }
25
+
26
+ export async function getRawPixels(
27
+ image: JimpImage,
28
+ ): Promise<{ data: Buffer; info: ImageOutputInfo }> {
29
+ return {
30
+ data: image.bitmap.data,
31
+ info: {
32
+ width: image.bitmap.width,
33
+ height: image.bitmap.height,
34
+ channels: 4,
35
+ },
36
+ };
37
+ }
38
+
39
+ export async function getPngBuffer(
40
+ image: JimpImage,
41
+ ): Promise<{ data: Buffer; info: ImageOutputInfo }> {
42
+ const buffer = await image.getBuffer("image/png");
43
+ return {
44
+ data: buffer,
45
+ info: {
46
+ width: image.bitmap.width,
47
+ height: image.bitmap.height,
48
+ channels: 4,
49
+ size: buffer.length,
50
+ },
51
+ };
52
+ }
53
+
54
+ export function calculateImageSize({
55
+ maxWidth,
56
+ maxHeight,
57
+ originalAspectRatio,
58
+ specifiedWidth,
59
+ specifiedHeight,
60
+ }: {
61
+ maxWidth: number;
62
+ maxHeight: number;
63
+ originalAspectRatio: number;
64
+ specifiedWidth?: number;
65
+ specifiedHeight?: number;
66
+ }): { width: number; height: number } {
67
+ // Both width and height specified
68
+ if (specifiedWidth && specifiedHeight) {
69
+ const width = Math.min(specifiedWidth, maxWidth);
70
+ const height = Math.min(specifiedHeight, maxHeight);
71
+ return { width: Math.round(width), height: Math.round(height) };
72
+ }
73
+
74
+ // Only width specified
75
+ if (specifiedWidth) {
76
+ let width = Math.min(specifiedWidth, maxWidth);
77
+ let height = width / originalAspectRatio;
78
+
79
+ if (height > maxHeight) {
80
+ height = maxHeight;
81
+ width = height * originalAspectRatio;
82
+ }
83
+
84
+ return { width: Math.round(width), height: Math.round(height) };
85
+ }
86
+
87
+ // Only height specified
88
+ if (specifiedHeight) {
89
+ let height = Math.min(specifiedHeight, maxHeight);
90
+ let width = height * originalAspectRatio;
91
+
92
+ if (width > maxWidth) {
93
+ width = maxWidth;
94
+ height = width / originalAspectRatio;
95
+ }
96
+
97
+ return { width: Math.round(width), height: Math.round(height) };
98
+ }
99
+
100
+ // No dimensions specified - scale to fit while maintaining aspect ratio
101
+ let height = maxHeight;
102
+ let width = height * originalAspectRatio;
103
+
104
+ if (width > maxWidth) {
105
+ width = maxWidth;
106
+ height = width / originalAspectRatio;
107
+ }
108
+
109
+ if (height > maxHeight) {
110
+ height = maxHeight;
111
+ width = height * originalAspectRatio;
112
+ }
113
+
114
+ return { width: Math.round(width), height: Math.round(height) };
115
+ }
src/ink-picture/utils/imageCache.ts ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { JimpImage } from "./image.js";
2
+
3
+ export class ImageCache {
4
+ #cache = new Map<string, JimpImage>();
5
+ #maxEntries: number;
6
+
7
+ constructor(maxEntries: number) {
8
+ this.#maxEntries = Math.max(1, maxEntries);
9
+ }
10
+
11
+ get(key: string): JimpImage | undefined {
12
+ const entry = this.#cache.get(key);
13
+ if (!entry) return undefined;
14
+
15
+ this.#cache.delete(key);
16
+ this.#cache.set(key, entry);
17
+
18
+ return entry.clone();
19
+ }
20
+
21
+ set(key: string, image: JimpImage): void {
22
+ this.#cache.delete(key);
23
+
24
+ while (this.#cache.size >= this.#maxEntries) {
25
+ const lru = this.#cache.keys().next().value;
26
+ if (lru !== undefined) this.#cache.delete(lru);
27
+ }
28
+
29
+ this.#cache.set(key, image.clone());
30
+ }
31
+
32
+ clear(): void {
33
+ this.#cache.clear();
34
+ }
35
+
36
+ get size(): number {
37
+ return this.#cache.size;
38
+ }
39
+ }
src/ink-picture/utils/queryTerminal.ts ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as fs from "node:fs";
2
+
3
+ const HIDDEN_MODE = "\x1b[8m";
4
+ const CLEAR_LINE_AND_RETURN = "\x1b[2K\r";
5
+ const RESET_ATTRIBUTES = "\x1b[0m";
6
+
7
+ const CELL_SIZE_QUERY = "\x1b[16t";
8
+ const TERMINAL_SIZE_QUERY = "\x1b[14t";
9
+ const KITTY_QUERY = "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\";
10
+ const ITERM_CELL_SIZE_QUERY = "\x1b]1337;ReportCellSize\x07";
11
+ const DEVICE_ATTRIBUTES_QUERY = "\x1b[c";
12
+
13
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentionally parsing escape sequence responses
14
+ const CELL_SIZE_REGEX = /\x1b\[6;(\d+);(\d+);?t/;
15
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentionally parsing escape sequence responses
16
+ const TERMINAL_SIZE_REGEX = /\x1b\[4;(\d+);(\d+);?t/;
17
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentionally parsing escape sequence responses
18
+ const KITTY_RESPONSE_REGEX = /\x1b_Gi=31;(.+?)\x1b\\/;
19
+ const ITERM_CELL_SIZE_REGEX = /ReportCellSize=([\d.]+);([\d.]+);([\d.]+)/;
20
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentionally parsing escape sequence responses
21
+ const DEVICE_ATTRIBUTES_REGEX = /\x1b\[\?(\d+(?:;\d+)*)c/;
22
+
23
+ const RESPONSE_PATTERNS_FOR_STRIPPING: RegExp[] = [
24
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentionally stripping escape sequence responses
25
+ /\x1b\[6;\d+;\d+;?t/g,
26
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentionally stripping escape sequence responses
27
+ /\x1b\[4;\d+;\d+;?t/g,
28
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentionally stripping escape sequence responses
29
+ /\x1b_Gi=31;.+?\x1b\\/g,
30
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentionally stripping escape sequence responses
31
+ /\x1b\]1337;ReportCellSize=[\d.]+;[\d.]+;[\d.]+\x07/g,
32
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Intentionally stripping escape sequence responses
33
+ /\x1b\[\?\d+(?:;\d+)*c/g,
34
+ ];
35
+
36
+ export interface TerminalQueryResult {
37
+ cellWidth: number | undefined;
38
+ cellHeight: number | undefined;
39
+ terminalWidth: number | undefined;
40
+ terminalHeight: number | undefined;
41
+ supportsKittyGraphics: boolean;
42
+ supportsSixelGraphics: boolean;
43
+ iterm2CellWidth: number | undefined;
44
+ iterm2CellHeight: number | undefined;
45
+ iterm2Scale: number | undefined;
46
+ }
47
+
48
+ /**
49
+ * Detect terminal capabilities by sending a batch of escape sequence queries
50
+ * and accumulating the responses in a buffer matched by regex.
51
+ *
52
+ * The Device Attributes response (`\x1b[?Nc`) is used as a sentinel to signal
53
+ * end of terminal response, with a 1000ms fallback timeout.
54
+ * The function overrides `stdin.push`, preventing data from being picked up
55
+ * by Ink's `useInput` hook. `stdin.push` is restored on cleanup, and any
56
+ * user input during this period is pushed back into the stream.
57
+ */
58
+ export function queryTerminal(
59
+ stdin: NodeJS.ReadStream,
60
+ setRawMode: (value: boolean) => void,
61
+ signal?: AbortSignal,
62
+ ): Promise<TerminalQueryResult> {
63
+ return new Promise((resolve) => {
64
+ if (signal?.aborted || !process.stdin.isTTY || !process.stdout.isTTY) {
65
+ resolve({
66
+ cellWidth: undefined,
67
+ cellHeight: undefined,
68
+ terminalWidth: undefined,
69
+ terminalHeight: undefined,
70
+ supportsKittyGraphics: false,
71
+ supportsSixelGraphics: false,
72
+ iterm2CellWidth: undefined,
73
+ iterm2CellHeight: undefined,
74
+ iterm2Scale: undefined,
75
+ });
76
+ return;
77
+ }
78
+
79
+ setRawMode(true);
80
+
81
+ let buffer = "";
82
+ let cellSizeReceived = false;
83
+ let terminalSizeReceived = false;
84
+ let kittyReceived = false;
85
+ let itermCellSizeReceived = false;
86
+ let sentinelReceived = false;
87
+ let timeoutId: NodeJS.Timeout;
88
+ let done = false;
89
+
90
+ const result: TerminalQueryResult = {
91
+ cellWidth: undefined,
92
+ cellHeight: undefined,
93
+ terminalWidth: undefined,
94
+ terminalHeight: undefined,
95
+ supportsKittyGraphics: false,
96
+ supportsSixelGraphics: false,
97
+ iterm2CellWidth: undefined,
98
+ iterm2CellHeight: undefined,
99
+ iterm2Scale: undefined,
100
+ };
101
+
102
+ const origPush: typeof stdin.push = stdin.push.bind(stdin);
103
+
104
+ const cleanup = () => {
105
+ if (done) return;
106
+ done = true;
107
+ if (timeoutId) {
108
+ clearTimeout(timeoutId);
109
+ }
110
+ signal?.removeEventListener("abort", onAbort);
111
+ stdin.push = origPush;
112
+ setRawMode(false);
113
+
114
+ let filtered = buffer;
115
+ for (const regex of RESPONSE_PATTERNS_FOR_STRIPPING) {
116
+ filtered = filtered.replace(regex, "");
117
+ }
118
+
119
+ if (filtered.length > 0) {
120
+ stdin.push(filtered);
121
+ }
122
+
123
+ resolve(result);
124
+ };
125
+
126
+ const onAbort = () => {
127
+ cleanup();
128
+ };
129
+
130
+ signal?.addEventListener("abort", onAbort, { once: true });
131
+
132
+ timeoutId = setTimeout(cleanup, 1000);
133
+
134
+ const processChunk = (data: string) => {
135
+ buffer += data;
136
+
137
+ if (!cellSizeReceived) {
138
+ const match = buffer.match(CELL_SIZE_REGEX);
139
+ if (match?.[1] && match?.[2]) {
140
+ cellSizeReceived = true;
141
+ const height = parseInt(match[1], 10);
142
+ const width = parseInt(match[2], 10);
143
+ if (!Number.isNaN(height) && !Number.isNaN(width)) {
144
+ result.cellWidth = width;
145
+ result.cellHeight = height;
146
+ }
147
+ }
148
+ }
149
+
150
+ if (!terminalSizeReceived) {
151
+ const match = buffer.match(TERMINAL_SIZE_REGEX);
152
+ if (match?.[1] && match?.[2]) {
153
+ terminalSizeReceived = true;
154
+ const height = parseInt(match[1], 10);
155
+ const width = parseInt(match[2], 10);
156
+ if (!Number.isNaN(height) && !Number.isNaN(width)) {
157
+ result.terminalWidth = width;
158
+ result.terminalHeight = height;
159
+ }
160
+ }
161
+ }
162
+
163
+ if (!kittyReceived) {
164
+ const match = buffer.match(KITTY_RESPONSE_REGEX);
165
+ if (match?.[1]?.includes("OK")) {
166
+ kittyReceived = true;
167
+ result.supportsKittyGraphics = true;
168
+ }
169
+ }
170
+
171
+ if (!itermCellSizeReceived) {
172
+ const match = buffer.match(ITERM_CELL_SIZE_REGEX);
173
+ if (match?.[1] && match?.[2] && match?.[3]) {
174
+ itermCellSizeReceived = true;
175
+ const height = parseFloat(match[1]);
176
+ const width = parseFloat(match[2]);
177
+ const scale = parseFloat(match[3]);
178
+ if (
179
+ !Number.isNaN(height) &&
180
+ !Number.isNaN(width) &&
181
+ !Number.isNaN(scale)
182
+ ) {
183
+ result.iterm2CellWidth = width;
184
+ result.iterm2CellHeight = height;
185
+ result.iterm2Scale = scale;
186
+ }
187
+ }
188
+ }
189
+
190
+ if (!sentinelReceived && DEVICE_ATTRIBUTES_REGEX.test(buffer)) {
191
+ sentinelReceived = true;
192
+ const daMatch = buffer.match(DEVICE_ATTRIBUTES_REGEX);
193
+ if (daMatch?.[1]) {
194
+ result.supportsSixelGraphics = daMatch[1].split(";").includes("4");
195
+ }
196
+ cleanup();
197
+ }
198
+ };
199
+
200
+ stdin.push = (chunk) => {
201
+ const str = Buffer.isBuffer(chunk) ? chunk.toString() : String(chunk);
202
+ processChunk(str);
203
+ return true;
204
+ };
205
+
206
+ const query =
207
+ HIDDEN_MODE +
208
+ CELL_SIZE_QUERY +
209
+ TERMINAL_SIZE_QUERY +
210
+ KITTY_QUERY +
211
+ ITERM_CELL_SIZE_QUERY +
212
+ DEVICE_ATTRIBUTES_QUERY +
213
+ CLEAR_LINE_AND_RETURN +
214
+ RESET_ATTRIBUTES;
215
+
216
+ try {
217
+ fs.writeSync(process.stdout.fd, query);
218
+ } catch {
219
+ cleanup();
220
+ }
221
+ });
222
+ }
src/ink/components/StdoutContext.tsx ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createContext, useContext } from 'react'
2
+
3
+ export type Props = {
4
+ readonly stdout: NodeJS.WriteStream & { readonly rows: number; readonly columns: number }
5
+ readonly write: (data: string) => void
6
+ }
7
+
8
+ const StdoutContext = createContext<Props>({
9
+ stdout: process.stdout as NodeJS.WriteStream & { rows: number; columns: number },
10
+ write() {},
11
+ })
12
+
13
+ StdoutContext.displayName = 'StdoutContext'
14
+
15
+ export default StdoutContext
src/tools/ImageShowTool/ImageShowTool.tsx CHANGED
@@ -5,7 +5,7 @@ import { z } from 'zod/v4'
5
  import { Text } from '../../ink.js'
6
  import { buildTool, type ToolDef } from '../../Tool.js'
7
  import { logForDebugging } from '../../utils/debug.js'
8
- import Image, { InkPictureProvider } from 'ink-picture'
9
 
10
  const IMAGE_TOOL_NAME = 'ImageShow'
11
 
@@ -111,6 +111,22 @@ async function loadImage(url: string): Promise<{ buffer: Buffer; format: string
111
  return fetchImage(normalizedUrl)
112
  }
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  function getToolUseSummary(input: Partial<Input>): string | null {
115
  return input?.url ? `Show: ${input.url.split('/').pop() ?? input.url}` : null
116
  }
@@ -215,8 +231,16 @@ export const ImageShowTool = buildTool({
215
  const imgWidth = Math.floor(cols * 0.6)
216
  const imgHeight = Math.floor(rows * 0.4)
217
 
 
 
 
 
 
 
 
 
218
  return (
219
- <InkPictureProvider>
220
  <Image
221
  src={content.src}
222
  width={imgWidth}
 
5
  import { Text } from '../../ink.js'
6
  import { buildTool, type ToolDef } from '../../Tool.js'
7
  import { logForDebugging } from '../../utils/debug.js'
8
+ import Image, { InkPictureProvider, type TerminalInfo } from 'src/ink-picture/index.ts'
9
 
10
  const IMAGE_TOOL_NAME = 'ImageShow'
11
 
 
111
  return fetchImage(normalizedUrl)
112
  }
113
 
114
+ /**
115
+ * Lightweight synchronous Kitty graphics protocol detection.
116
+ * Uses only env vars — no module dependencies, no async queries.
117
+ */
118
+ function detectKittySync(): boolean {
119
+ return !!(
120
+ process.env.TERM?.includes('kitty') ||
121
+ process.env.KITTY_WINDOW_ID ||
122
+ process.env.TERM_PROGRAM === 'kitty' ||
123
+ process.env.TERM_PROGRAM === 'ghostty' ||
124
+ process.env.TERM_PROGRAM === 'WezTerm' ||
125
+ process.env.TERM_PROGRAM === 'konsole' ||
126
+ process.env.TERM_PROGRAM === 'foot'
127
+ )
128
+ }
129
+
130
  function getToolUseSummary(input: Partial<Input>): string | null {
131
  return input?.url ? `Show: ${input.url.split('/').pop() ?? input.url}` : null
132
  }
 
231
  const imgWidth = Math.floor(cols * 0.6)
232
  const imgHeight = Math.floor(rows * 0.4)
233
 
234
+ // Sync terminal info prevents InkPictureProvider's async terminal
235
+ // query from delaying the first render and ensures the correct
236
+ // protocol is used from frame one.
237
+ const terminalInfo: Partial<TerminalInfo> = {
238
+ supportsKittyGraphics: detectKittySync(),
239
+ supportsUnicode: true,
240
+ }
241
+
242
  return (
243
+ <InkPictureProvider terminalInfo={terminalInfo}>
244
  <Image
245
  src={content.src}
246
  width={imgWidth}
src/tools/ImageShowTool/__tests__/ShowLocalImage.test.tsx ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Visual test: display a local image using ink-picture + Ink.
4
+ *
5
+ * Usage:
6
+ * bun run src/tools/ImageShowTool/__tests__/ShowLocalImage.test.tsx
7
+ */
8
+
9
+ import React, { useEffect } from 'react'
10
+ import { render, Box, Text, useApp } from 'ink'
11
+ import Image, { InkPictureProvider, type TerminalInfo } from 'src/ink-picture/index.ts'
12
+
13
+ const IMAGE_PATH = '/home/yuki/Pictures/Wallpapers/3god.jpg'
14
+
15
+ const cols = process.stdout.columns ?? 80
16
+ const rows = process.stdout.rows ?? 40
17
+ const imgWidth = Math.floor(cols * 0.6)
18
+ const imgHeight = Math.floor(rows * 0.4)
19
+
20
+ const terminalInfo: Partial<TerminalInfo> = {
21
+ supportsKittyGraphics: !!(
22
+ process.env.TERM?.includes('kitty') ||
23
+ process.env.KITTY_WINDOW_ID ||
24
+ process.env.TERM_PROGRAM === 'kitty' ||
25
+ process.env.TERM_PROGRAM === 'ghostty' ||
26
+ process.env.TERM_PROGRAM === 'WezTerm' ||
27
+ process.env.TERM_PROGRAM === 'konsole' ||
28
+ process.env.TERM_PROGRAM === 'foot'
29
+ ),
30
+ supportsUnicode: true,
31
+ }
32
+
33
+ function App() {
34
+ const { exit } = useApp()
35
+
36
+ useEffect(() => {
37
+ const timer = setTimeout(() => exit(), 3000)
38
+ return () => clearTimeout(timer)
39
+ }, [exit])
40
+
41
+ return (
42
+ <Box flexDirection="column">
43
+ <InkPictureProvider terminalInfo={terminalInfo}>
44
+ <Image src={IMAGE_PATH} width={imgWidth} height={imgHeight} alt="3god" />
45
+ </InkPictureProvider>
46
+ </Box>
47
+ )
48
+ }
49
+
50
+ const { waitUntilExit } = render(<App />)
51
+ await waitUntilExit()
src/tools/ImageShowTool/__tests__/ShowUrlImage.test.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Visual test: display a remote image using ink-picture + Ink.
4
+ *
5
+ * Usage:
6
+ * bun run src/tools/ImageShowTool/__tests__/ShowUrlImage.test.tsx
7
+ */
8
+
9
+ import React, { useEffect } from 'react'
10
+ import { render, Box, Text, useApp } from 'ink'
11
+ import Image, { InkPictureProvider, type TerminalInfo } from 'src/ink-picture/index.ts'
12
+
13
+ const IMAGE_URL =
14
+ 'https://kimi-web-img.moonshot.cn/img/images.pexels.com/8f909bffa5353ba43aac1db1f83b1bf5450a8cd5.jpeg'
15
+
16
+ const cols = process.stdout.columns ?? 80
17
+ const rows = process.stdout.rows ?? 40
18
+ const imgWidth = Math.floor(cols * 0.6)
19
+ const imgHeight = Math.floor(rows * 0.4)
20
+
21
+ const terminalInfo: Partial<TerminalInfo> = {
22
+ supportsKittyGraphics: !!(
23
+ process.env.TERM?.includes('kitty') ||
24
+ process.env.KITTY_WINDOW_ID ||
25
+ process.env.TERM_PROGRAM === 'kitty' ||
26
+ process.env.TERM_PROGRAM === 'ghostty' ||
27
+ process.env.TERM_PROGRAM === 'WezTerm' ||
28
+ process.env.TERM_PROGRAM === 'konsole' ||
29
+ process.env.TERM_PROGRAM === 'foot'
30
+ ),
31
+ supportsUnicode: true,
32
+ }
33
+
34
+ function App() {
35
+ const { exit } = useApp()
36
+
37
+ useEffect(() => {
38
+ // Longer timeout for remote image (network fetch)
39
+ const timer = setTimeout(() => exit(), 15000)
40
+ return () => clearTimeout(timer)
41
+ }, [exit])
42
+
43
+ return (
44
+ <Box flexDirection="column">
45
+ <InkPictureProvider terminalInfo={terminalInfo}>
46
+ <Image src={IMAGE_URL} width={imgWidth} height={imgHeight} alt="remote" />
47
+ </InkPictureProvider>
48
+ </Box>
49
+ )
50
+ }
51
+
52
+ const { waitUntilExit } = render(<App />)
53
+ await waitUntilExit()
tsconfig.json CHANGED
@@ -10,7 +10,9 @@
10
  "types": ["bun"],
11
  "baseUrl": ".",
12
  "paths": {
13
- "src/*": ["src/*"]
 
 
14
  },
15
  "moduleResolution": "bundler",
16
  "allowImportingTsExtensions": true,
 
10
  "types": ["bun"],
11
  "baseUrl": ".",
12
  "paths": {
13
+ "src/*": ["src/*"],
14
+ "ink-picture": ["src/ink-picture"],
15
+ "ink-picture/*": ["src/ink-picture/*"]
16
  },
17
  "moduleResolution": "bundler",
18
  "allowImportingTsExtensions": true,