feat: add modern TUI (mtui) using React and Ink
This commit is contained in:
parent
b9f3c36ce2
commit
8232c857dc
@ -186,6 +186,10 @@
|
||||
"file-type": "^21.3.0",
|
||||
"grammy": "^1.39.3",
|
||||
"hono": "4.11.4",
|
||||
"ink": "^6.6.0",
|
||||
"ink-select-input": "^6.2.0",
|
||||
"ink-spinner": "^5.0.0",
|
||||
"ink-text-input": "^6.0.0",
|
||||
"jiti": "^2.6.1",
|
||||
"json5": "^2.2.3",
|
||||
"jszip": "^3.10.1",
|
||||
@ -198,6 +202,8 @@
|
||||
"playwright-core": "1.58.0",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"sharp": "^0.34.5",
|
||||
"sqlite-vec": "0.1.7-alpha.2",
|
||||
"tar": "7.5.4",
|
||||
@ -222,6 +228,8 @@
|
||||
"@types/node": "^25.0.10",
|
||||
"@types/proper-lockfile": "^4.1.4",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/react": "^19.2.10",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260124.1",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
|
||||
438
pnpm-lock.yaml
generated
438
pnpm-lock.yaml
generated
@ -112,6 +112,18 @@ importers:
|
||||
hono:
|
||||
specifier: 4.11.4
|
||||
version: 4.11.4
|
||||
ink:
|
||||
specifier: ^6.6.0
|
||||
version: 6.6.0(@types/react@19.2.10)(react@19.2.4)
|
||||
ink-select-input:
|
||||
specifier: ^6.2.0
|
||||
version: 6.2.0(ink@6.6.0(@types/react@19.2.10)(react@19.2.4))(react@19.2.4)
|
||||
ink-spinner:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0(ink@6.6.0(@types/react@19.2.10)(react@19.2.4))(react@19.2.4)
|
||||
ink-text-input:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0(ink@6.6.0(@types/react@19.2.10)(react@19.2.4))(react@19.2.4)
|
||||
jiti:
|
||||
specifier: ^2.6.1
|
||||
version: 2.6.1
|
||||
@ -148,6 +160,12 @@ importers:
|
||||
qrcode-terminal:
|
||||
specifier: ^0.12.0
|
||||
version: 0.12.0
|
||||
react:
|
||||
specifier: ^19.2.4
|
||||
version: 19.2.4
|
||||
react-dom:
|
||||
specifier: ^19.2.4
|
||||
version: 19.2.4(react@19.2.4)
|
||||
sharp:
|
||||
specifier: ^0.34.5
|
||||
version: 0.34.5
|
||||
@ -203,6 +221,12 @@ importers:
|
||||
'@types/qrcode-terminal':
|
||||
specifier: ^0.12.2
|
||||
version: 0.12.2
|
||||
'@types/react':
|
||||
specifier: ^19.2.10
|
||||
version: 19.2.10
|
||||
'@types/react-dom':
|
||||
specifier: ^19.2.3
|
||||
version: 19.2.3(@types/react@19.2.10)
|
||||
'@types/ws':
|
||||
specifier: ^8.18.1
|
||||
version: 8.18.1
|
||||
@ -383,12 +407,12 @@ importers:
|
||||
'@microsoft/agents-hosting-extensions-teams':
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2
|
||||
moltbot:
|
||||
specifier: workspace:*
|
||||
version: link:../..
|
||||
express:
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1
|
||||
moltbot:
|
||||
specifier: workspace:*
|
||||
version: link:../..
|
||||
proper-lockfile:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2
|
||||
@ -519,6 +543,10 @@ packages:
|
||||
peerDependencies:
|
||||
zod: ^3.25.0 || ^4.0.0
|
||||
|
||||
'@alcalzone/ansi-tokenize@0.2.4':
|
||||
resolution: {integrity: sha512-HTgrrTgZ9Jgeo6Z3oqbQ7lifOVvRR14vaDuBGPPUxk9Thm+vObaO4QfYYYWw4Zo5CWQDBEfsinFA6Gre+AqwNQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@anthropic-ai/sdk@0.71.2':
|
||||
resolution: {integrity: sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ==}
|
||||
hasBin: true
|
||||
@ -2758,6 +2786,14 @@ packages:
|
||||
'@types/range-parser@1.2.7':
|
||||
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
|
||||
|
||||
'@types/react-dom@19.2.3':
|
||||
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^19.2.0
|
||||
|
||||
'@types/react@19.2.10':
|
||||
resolution: {integrity: sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==}
|
||||
|
||||
'@types/request@2.48.13':
|
||||
resolution: {integrity: sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==}
|
||||
|
||||
@ -2976,6 +3012,10 @@ packages:
|
||||
resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
ansi-escapes@7.2.0:
|
||||
resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
@ -3065,6 +3105,10 @@ packages:
|
||||
resolution: {integrity: sha512-En9AY6EG1qYqEy5L/quryzbA4akBpJrnBZNxeKTqGHC2xT9Qc4aZ8b7CcbOMFTTc/MGdoNyp+SN4zInZNKxMYA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
auto-bind@5.0.1:
|
||||
resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
aws-sign2@0.7.0:
|
||||
resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==}
|
||||
|
||||
@ -3214,10 +3258,13 @@ packages:
|
||||
class-variance-authority@0.7.1:
|
||||
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
||||
|
||||
clawdbot@2026.1.24-3:
|
||||
resolution: {integrity: sha512-zt9BzhWXduq8ZZR4rfzQDurQWAgmijTTyPZCQGrn5ew6wCEwhxxEr2/NHG7IlCwcfRsKymsY4se9KMhoNz0JtQ==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
hasBin: true
|
||||
cli-boxes@3.0.0:
|
||||
resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
cli-cursor@4.0.0:
|
||||
resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
cli-cursor@5.0.0:
|
||||
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
|
||||
@ -3232,6 +3279,10 @@ packages:
|
||||
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
cli-truncate@5.1.1:
|
||||
resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
cliui@7.0.4:
|
||||
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
|
||||
|
||||
@ -3248,6 +3299,10 @@ packages:
|
||||
engines: {node: '>= 14.15.0'}
|
||||
hasBin: true
|
||||
|
||||
code-excerpt@4.0.0:
|
||||
resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
codec-parser@2.5.0:
|
||||
resolution: {integrity: sha512-Ru9t80fV8B0ZiixQl8xhMTLru+dzuis/KQld32/x5T/+3LwZb0/YvQdSKytX9JqCnRdiupvAvyYJINKrXieziQ==}
|
||||
|
||||
@ -3304,6 +3359,10 @@ packages:
|
||||
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
convert-to-spaces@2.0.1:
|
||||
resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
cookie-signature@1.0.7:
|
||||
resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==}
|
||||
|
||||
@ -3345,6 +3404,9 @@ packages:
|
||||
cssom@0.5.0:
|
||||
resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==}
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
curve25519-js@0.0.4:
|
||||
resolution: {integrity: sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w==}
|
||||
|
||||
@ -3474,6 +3536,10 @@ packages:
|
||||
resolution: {integrity: sha512-mKZOzLRN0ETzau2W2QXefbFjo5EF4yWq28OyKb9ICdeNhHJlOE/pHHnz4hdYJ9cNZXcJHo5xN4OT4pzuSHSNvA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
environment@1.1.0:
|
||||
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
es-define-property@1.0.1:
|
||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -3493,6 +3559,9 @@ packages:
|
||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-toolkit@1.44.0:
|
||||
resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==}
|
||||
|
||||
esbuild@0.27.2:
|
||||
resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==}
|
||||
engines: {node: '>=18'}
|
||||
@ -3505,6 +3574,10 @@ packages:
|
||||
escape-html@1.0.3:
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
|
||||
escape-string-regexp@2.0.0:
|
||||
resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
escape-string-regexp@4.0.0:
|
||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||
engines: {node: '>=10'}
|
||||
@ -3585,6 +3658,10 @@ packages:
|
||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||
engines: {node: ^12.20 || >= 14.13}
|
||||
|
||||
figures@6.1.0:
|
||||
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
file-type@21.3.0:
|
||||
resolution: {integrity: sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==}
|
||||
engines: {node: '>=20'}
|
||||
@ -3859,12 +3936,50 @@ packages:
|
||||
import-in-the-middle@2.0.5:
|
||||
resolution: {integrity: sha512-0InH9/4oDCBRzWXhpOqusspLBrVfK1vPvbn9Wxl8DAQ8yyx5fWJRETICSwkiAMaYntjJAMBP1R4B6cQnEUYVEA==}
|
||||
|
||||
indent-string@5.0.0:
|
||||
resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
ini@1.3.8:
|
||||
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
|
||||
|
||||
ink-select-input@6.2.0:
|
||||
resolution: {integrity: sha512-304fZXxkpYxJ9si5lxRCaX01GNlmPBgOZumXXRnPYbHW/iI31cgQynqk2tRypGLOF1cMIwPUzL2LSm6q4I5rQQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
ink: '>=5.0.0'
|
||||
react: '>=18.0.0'
|
||||
|
||||
ink-spinner@5.0.0:
|
||||
resolution: {integrity: sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA==}
|
||||
engines: {node: '>=14.16'}
|
||||
peerDependencies:
|
||||
ink: '>=4.0.0'
|
||||
react: '>=18.0.0'
|
||||
|
||||
ink-text-input@6.0.0:
|
||||
resolution: {integrity: sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
ink: '>=5'
|
||||
react: '>=18'
|
||||
|
||||
ink@6.6.0:
|
||||
resolution: {integrity: sha512-QDt6FgJxgmSxAelcOvOHUvFxbIUjVpCH5bx+Slvc5m7IEcpGt3dYwbz/L+oRnqEGeRvwy1tineKK4ect3nW1vQ==}
|
||||
engines: {node: '>=20'}
|
||||
peerDependencies:
|
||||
'@types/react': '>=19.0.0'
|
||||
react: '>=19.0.0'
|
||||
react-devtools-core: ^6.1.2
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
react-devtools-core:
|
||||
optional: true
|
||||
|
||||
ipaddr.js@1.9.1:
|
||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
@ -3900,6 +4015,11 @@ packages:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-in-ci@2.0.0:
|
||||
resolution: {integrity: sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
is-interactive@2.0.0:
|
||||
resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
|
||||
engines: {node: '>=12'}
|
||||
@ -4318,6 +4438,10 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
mimic-fn@2.1.0:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
mimic-function@5.0.1:
|
||||
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
|
||||
engines: {node: '>=18'}
|
||||
@ -4515,6 +4639,10 @@ packages:
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
onetime@5.1.2:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
onetime@7.0.0:
|
||||
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
|
||||
engines: {node: '>=18'}
|
||||
@ -4636,6 +4764,10 @@ packages:
|
||||
partial-json@0.1.7:
|
||||
resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==}
|
||||
|
||||
patch-console@2.0.0:
|
||||
resolution: {integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
@ -4838,6 +4970,21 @@ packages:
|
||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||
hasBin: true
|
||||
|
||||
react-dom@19.2.4:
|
||||
resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
|
||||
peerDependencies:
|
||||
react: ^19.2.4
|
||||
|
||||
react-reconciler@0.33.0:
|
||||
resolution: {integrity: sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
peerDependencies:
|
||||
react: ^19.2.0
|
||||
|
||||
react@19.2.4:
|
||||
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
||||
|
||||
@ -4897,6 +5044,10 @@ packages:
|
||||
resolve-pkg-maps@1.0.0:
|
||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||
|
||||
restore-cursor@4.0.0:
|
||||
resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
restore-cursor@5.1.0:
|
||||
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
|
||||
engines: {node: '>=18'}
|
||||
@ -4950,6 +5101,9 @@ packages:
|
||||
sanitize-html@2.17.0:
|
||||
resolution: {integrity: sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==}
|
||||
|
||||
scheduler@0.27.0:
|
||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||
|
||||
selderee@0.11.0:
|
||||
resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
|
||||
|
||||
@ -5100,6 +5254,10 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
hasBin: true
|
||||
|
||||
stack-utils@2.0.6:
|
||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
@ -5141,6 +5299,10 @@ packages:
|
||||
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
string-width@8.1.1:
|
||||
resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
string_decoder@1.1.1:
|
||||
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||
|
||||
@ -5193,6 +5355,7 @@ packages:
|
||||
tar@7.5.4:
|
||||
resolution: {integrity: sha512-AN04xbWGrSTDmVwlI4/GTlIIwMFk/XEv7uL8aa57zuvRy6s4hdBed+lVq2fAZ89XDa7Us3ANXcE3Tvqvja1kTA==}
|
||||
engines: {node: '>=18'}
|
||||
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
|
||||
|
||||
thenify-all@1.6.0:
|
||||
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
||||
@ -5230,6 +5393,10 @@ packages:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
to-rotated@1.0.0:
|
||||
resolution: {integrity: sha512-KsEID8AfgUy+pxVRLsWp0VzCa69wxzUDZnzGbyIST/bcgcrMvTYoFBX/QORH4YApoD89EDuUovx4BTdpOn319Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
toad-cache@3.7.0:
|
||||
resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==}
|
||||
engines: {node: '>=12'}
|
||||
@ -5278,6 +5445,10 @@ packages:
|
||||
tweetnacl@0.14.5:
|
||||
resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==}
|
||||
|
||||
type-fest@4.41.0:
|
||||
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
type-is@1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@ -5488,6 +5659,10 @@ packages:
|
||||
wide-align@1.1.5:
|
||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||
|
||||
widest-line@5.0.0:
|
||||
resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
win-guid@0.1.3:
|
||||
resolution: {integrity: sha512-Ah25z4XnblBGFgrcVS5QK7qLkvsFFA35+G+AnHkELUPHXPYWFOSVNMuAxf1zW0B+4X911IBLD+TvB771om0gmg==}
|
||||
|
||||
@ -5511,6 +5686,10 @@ packages:
|
||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
wrap-ansi@9.0.2:
|
||||
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
@ -5562,6 +5741,9 @@ packages:
|
||||
resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
yoga-layout@3.2.1:
|
||||
resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==}
|
||||
|
||||
zod-to-json-schema@3.25.1:
|
||||
resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
|
||||
peerDependencies:
|
||||
@ -5582,6 +5764,11 @@ snapshots:
|
||||
dependencies:
|
||||
zod: 4.3.6
|
||||
|
||||
'@alcalzone/ansi-tokenize@0.2.4':
|
||||
dependencies:
|
||||
ansi-styles: 6.2.3
|
||||
is-fullwidth-code-point: 5.1.0
|
||||
|
||||
'@anthropic-ai/sdk@0.71.2(zod@4.3.6)':
|
||||
dependencies:
|
||||
json-schema-to-ts: 3.1.1
|
||||
@ -8538,6 +8725,14 @@ snapshots:
|
||||
|
||||
'@types/range-parser@1.2.7': {}
|
||||
|
||||
'@types/react-dom@19.2.3(@types/react@19.2.10)':
|
||||
dependencies:
|
||||
'@types/react': 19.2.10
|
||||
|
||||
'@types/react@19.2.10':
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/request@2.48.13':
|
||||
dependencies:
|
||||
'@types/caseless': 0.12.5
|
||||
@ -8832,6 +9027,10 @@ snapshots:
|
||||
ansi-escapes@6.2.1:
|
||||
optional: true
|
||||
|
||||
ansi-escapes@7.2.0:
|
||||
dependencies:
|
||||
environment: 1.1.0
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-regex@6.2.2: {}
|
||||
@ -8925,6 +9124,8 @@ snapshots:
|
||||
audio-type@2.2.1:
|
||||
optional: true
|
||||
|
||||
auto-bind@5.0.1: {}
|
||||
|
||||
aws-sign2@0.7.0: {}
|
||||
|
||||
aws4@1.13.2: {}
|
||||
@ -9098,83 +9299,11 @@ snapshots:
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
|
||||
clawdbot@2026.1.24-3(@types/express@5.0.6)(audio-decode@2.2.3)(devtools-protocol@0.0.1561482)(typescript@5.9.3):
|
||||
cli-boxes@3.0.0: {}
|
||||
|
||||
cli-cursor@4.0.0:
|
||||
dependencies:
|
||||
'@agentclientprotocol/sdk': 0.13.1(zod@4.3.6)
|
||||
'@aws-sdk/client-bedrock': 3.975.0
|
||||
'@buape/carbon': 0.14.0(hono@4.11.4)
|
||||
'@clack/prompts': 0.11.0
|
||||
'@grammyjs/runner': 2.0.3(grammy@1.39.3)
|
||||
'@grammyjs/transformer-throttler': 1.2.1(grammy@1.39.3)
|
||||
'@homebridge/ciao': 1.3.4
|
||||
'@line/bot-sdk': 10.6.0
|
||||
'@lydell/node-pty': 1.2.0-beta.3
|
||||
'@mariozechner/pi-agent-core': 0.49.3(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-ai': 0.49.3(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-coding-agent': 0.49.3(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-tui': 0.49.3
|
||||
'@mozilla/readability': 0.6.0
|
||||
'@sinclair/typebox': 0.34.47
|
||||
'@slack/bolt': 4.6.0(@types/express@5.0.6)
|
||||
'@slack/web-api': 7.13.0
|
||||
'@whiskeysockets/baileys': 7.0.0-rc.9(audio-decode@2.2.3)(sharp@0.34.5)
|
||||
ajv: 8.17.1
|
||||
body-parser: 2.2.2
|
||||
chalk: 5.6.2
|
||||
chokidar: 5.0.0
|
||||
chromium-bidi: 13.0.1(devtools-protocol@0.0.1561482)
|
||||
cli-highlight: 2.1.11
|
||||
commander: 14.0.2
|
||||
croner: 9.1.0
|
||||
detect-libc: 2.1.2
|
||||
discord-api-types: 0.38.37
|
||||
dotenv: 17.2.3
|
||||
express: 5.2.1
|
||||
file-type: 21.3.0
|
||||
grammy: 1.39.3
|
||||
hono: 4.11.4
|
||||
jiti: 2.6.1
|
||||
json5: 2.2.3
|
||||
jszip: 3.10.1
|
||||
linkedom: 0.18.12
|
||||
long: 5.3.2
|
||||
markdown-it: 14.1.0
|
||||
node-edge-tts: 1.2.9
|
||||
osc-progress: 0.3.0
|
||||
pdfjs-dist: 5.4.530
|
||||
playwright-core: 1.58.0
|
||||
proper-lockfile: 4.1.2
|
||||
qrcode-terminal: 0.12.0
|
||||
sharp: 0.34.5
|
||||
sqlite-vec: 0.1.7-alpha.2
|
||||
tar: 7.5.4
|
||||
tslog: 4.10.2
|
||||
undici: 7.19.0
|
||||
ws: 8.19.0
|
||||
yaml: 2.8.2
|
||||
zod: 4.3.6
|
||||
optionalDependencies:
|
||||
'@napi-rs/canvas': 0.1.88
|
||||
node-llama-cpp: 3.15.0(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- '@discordjs/opus'
|
||||
- '@modelcontextprotocol/sdk'
|
||||
- '@types/express'
|
||||
- audio-decode
|
||||
- aws-crt
|
||||
- bufferutil
|
||||
- canvas
|
||||
- debug
|
||||
- devtools-protocol
|
||||
- encoding
|
||||
- ffmpeg-static
|
||||
- jimp
|
||||
- link-preview-js
|
||||
- node-opus
|
||||
- opusscript
|
||||
- supports-color
|
||||
- typescript
|
||||
- utf-8-validate
|
||||
restore-cursor: 4.0.0
|
||||
|
||||
cli-cursor@5.0.0:
|
||||
dependencies:
|
||||
@ -9190,8 +9319,12 @@ snapshots:
|
||||
parse5-htmlparser2-tree-adapter: 6.0.1
|
||||
yargs: 16.2.0
|
||||
|
||||
cli-spinners@2.9.2:
|
||||
optional: true
|
||||
cli-spinners@2.9.2: {}
|
||||
|
||||
cli-truncate@5.1.1:
|
||||
dependencies:
|
||||
slice-ansi: 7.1.2
|
||||
string-width: 8.1.1
|
||||
|
||||
cliui@7.0.4:
|
||||
dependencies:
|
||||
@ -9225,6 +9358,10 @@ snapshots:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
code-excerpt@4.0.0:
|
||||
dependencies:
|
||||
convert-to-spaces: 2.0.1
|
||||
|
||||
codec-parser@2.5.0:
|
||||
optional: true
|
||||
|
||||
@ -9275,6 +9412,8 @@ snapshots:
|
||||
|
||||
content-type@1.0.5: {}
|
||||
|
||||
convert-to-spaces@2.0.1: {}
|
||||
|
||||
cookie-signature@1.0.7: {}
|
||||
|
||||
cookie-signature@1.2.2: {}
|
||||
@ -9313,6 +9452,8 @@ snapshots:
|
||||
|
||||
cssom@0.5.0: {}
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
curve25519-js@0.0.4: {}
|
||||
|
||||
dashdash@1.14.1:
|
||||
@ -9398,8 +9539,7 @@ snapshots:
|
||||
|
||||
ee-first@1.1.1: {}
|
||||
|
||||
emoji-regex@10.6.0:
|
||||
optional: true
|
||||
emoji-regex@10.6.0: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
@ -9414,6 +9554,8 @@ snapshots:
|
||||
env-var@7.5.0:
|
||||
optional: true
|
||||
|
||||
environment@1.1.0: {}
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
@ -9431,6 +9573,8 @@ snapshots:
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.2
|
||||
|
||||
es-toolkit@1.44.0: {}
|
||||
|
||||
esbuild@0.27.2:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.27.2
|
||||
@ -9464,6 +9608,8 @@ snapshots:
|
||||
|
||||
escape-html@1.0.3: {}
|
||||
|
||||
escape-string-regexp@2.0.0: {}
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
estree-walker@3.0.3:
|
||||
@ -9589,6 +9735,10 @@ snapshots:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 3.3.3
|
||||
|
||||
figures@6.1.0:
|
||||
dependencies:
|
||||
is-unicode-supported: 2.1.0
|
||||
|
||||
file-type@21.3.0:
|
||||
dependencies:
|
||||
'@tokenizer/inflate': 0.4.1
|
||||
@ -9935,11 +10085,65 @@ snapshots:
|
||||
cjs-module-lexer: 2.2.0
|
||||
module-details-from-path: 1.0.4
|
||||
|
||||
indent-string@5.0.0: {}
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
ini@1.3.8:
|
||||
optional: true
|
||||
|
||||
ink-select-input@6.2.0(ink@6.6.0(@types/react@19.2.10)(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
figures: 6.1.0
|
||||
ink: 6.6.0(@types/react@19.2.10)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
to-rotated: 1.0.0
|
||||
|
||||
ink-spinner@5.0.0(ink@6.6.0(@types/react@19.2.10)(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
cli-spinners: 2.9.2
|
||||
ink: 6.6.0(@types/react@19.2.10)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
|
||||
ink-text-input@6.0.0(ink@6.6.0(@types/react@19.2.10)(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
chalk: 5.6.2
|
||||
ink: 6.6.0(@types/react@19.2.10)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
type-fest: 4.41.0
|
||||
|
||||
ink@6.6.0(@types/react@19.2.10)(react@19.2.4):
|
||||
dependencies:
|
||||
'@alcalzone/ansi-tokenize': 0.2.4
|
||||
ansi-escapes: 7.2.0
|
||||
ansi-styles: 6.2.3
|
||||
auto-bind: 5.0.1
|
||||
chalk: 5.6.2
|
||||
cli-boxes: 3.0.0
|
||||
cli-cursor: 4.0.0
|
||||
cli-truncate: 5.1.1
|
||||
code-excerpt: 4.0.0
|
||||
es-toolkit: 1.44.0
|
||||
indent-string: 5.0.0
|
||||
is-in-ci: 2.0.0
|
||||
patch-console: 2.0.0
|
||||
react: 19.2.4
|
||||
react-reconciler: 0.33.0(react@19.2.4)
|
||||
signal-exit: 3.0.7
|
||||
slice-ansi: 7.1.2
|
||||
stack-utils: 2.0.6
|
||||
string-width: 8.1.1
|
||||
type-fest: 4.41.0
|
||||
widest-line: 5.0.0
|
||||
wrap-ansi: 9.0.2
|
||||
ws: 8.19.0
|
||||
yoga-layout: 3.2.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.10
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
ipaddr.js@1.9.1: {}
|
||||
|
||||
ipull@3.9.3:
|
||||
@ -9993,12 +10197,13 @@ snapshots:
|
||||
is-fullwidth-code-point@5.1.0:
|
||||
dependencies:
|
||||
get-east-asian-width: 1.4.0
|
||||
optional: true
|
||||
|
||||
is-glob@4.0.3:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
||||
is-in-ci@2.0.0: {}
|
||||
|
||||
is-interactive@2.0.0:
|
||||
optional: true
|
||||
|
||||
@ -10017,8 +10222,7 @@ snapshots:
|
||||
is-unicode-supported@1.3.0:
|
||||
optional: true
|
||||
|
||||
is-unicode-supported@2.1.0:
|
||||
optional: true
|
||||
is-unicode-supported@2.1.0: {}
|
||||
|
||||
is-url@1.2.4: {}
|
||||
|
||||
@ -10382,6 +10586,8 @@ snapshots:
|
||||
|
||||
mime@1.6.0: {}
|
||||
|
||||
mimic-fn@2.1.0: {}
|
||||
|
||||
mimic-function@5.0.1:
|
||||
optional: true
|
||||
|
||||
@ -10625,6 +10831,10 @@ snapshots:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
onetime@5.1.2:
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
|
||||
onetime@7.0.0:
|
||||
dependencies:
|
||||
mimic-function: 5.0.1
|
||||
@ -10748,6 +10958,8 @@ snapshots:
|
||||
|
||||
partial-json@0.1.7: {}
|
||||
|
||||
patch-console@2.0.0: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
@ -10979,6 +11191,18 @@ snapshots:
|
||||
strip-json-comments: 2.0.1
|
||||
optional: true
|
||||
|
||||
react-dom@19.2.4(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
scheduler: 0.27.0
|
||||
|
||||
react-reconciler@0.33.0(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
scheduler: 0.27.0
|
||||
|
||||
react@19.2.4: {}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
dependencies:
|
||||
core-util-is: 1.0.3
|
||||
@ -11063,6 +11287,11 @@ snapshots:
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
restore-cursor@4.0.0:
|
||||
dependencies:
|
||||
onetime: 5.1.2
|
||||
signal-exit: 3.0.7
|
||||
|
||||
restore-cursor@5.1.0:
|
||||
dependencies:
|
||||
onetime: 7.0.0
|
||||
@ -11160,6 +11389,8 @@ snapshots:
|
||||
parse-srcset: 1.0.2
|
||||
postcss: 8.5.6
|
||||
|
||||
scheduler@0.27.0: {}
|
||||
|
||||
selderee@0.11.0:
|
||||
dependencies:
|
||||
parseley: 0.12.1
|
||||
@ -11329,7 +11560,6 @@ snapshots:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.3
|
||||
is-fullwidth-code-point: 5.1.0
|
||||
optional: true
|
||||
|
||||
sonic-boom@4.2.0:
|
||||
dependencies:
|
||||
@ -11381,6 +11611,10 @@ snapshots:
|
||||
safer-buffer: 2.1.2
|
||||
tweetnacl: 0.14.5
|
||||
|
||||
stack-utils@2.0.6:
|
||||
dependencies:
|
||||
escape-string-regexp: 2.0.0
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
statuses@2.0.2: {}
|
||||
@ -11424,7 +11658,11 @@ snapshots:
|
||||
emoji-regex: 10.6.0
|
||||
get-east-asian-width: 1.4.0
|
||||
strip-ansi: 7.1.2
|
||||
optional: true
|
||||
|
||||
string-width@8.1.1:
|
||||
dependencies:
|
||||
get-east-asian-width: 1.4.0
|
||||
strip-ansi: 7.1.2
|
||||
|
||||
string_decoder@1.1.1:
|
||||
dependencies:
|
||||
@ -11509,6 +11747,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
to-rotated@1.0.0: {}
|
||||
|
||||
toad-cache@3.7.0:
|
||||
optional: true
|
||||
|
||||
@ -11550,6 +11790,8 @@ snapshots:
|
||||
|
||||
tweetnacl@0.14.5: {}
|
||||
|
||||
type-fest@4.41.0: {}
|
||||
|
||||
type-is@1.6.18:
|
||||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
@ -11719,6 +11961,10 @@ snapshots:
|
||||
string-width: 4.2.3
|
||||
optional: true
|
||||
|
||||
widest-line@5.0.0:
|
||||
dependencies:
|
||||
string-width: 7.2.0
|
||||
|
||||
win-guid@0.1.3: {}
|
||||
|
||||
wireit@0.14.12:
|
||||
@ -11745,6 +11991,12 @@ snapshots:
|
||||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.2
|
||||
|
||||
wrap-ansi@9.0.2:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.3
|
||||
string-width: 7.2.0
|
||||
strip-ansi: 7.1.2
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
ws@8.19.0: {}
|
||||
@ -11783,6 +12035,8 @@ snapshots:
|
||||
|
||||
yoctocolors@2.1.2: {}
|
||||
|
||||
yoga-layout@3.2.1: {}
|
||||
|
||||
zod-to-json-schema@3.25.1(zod@3.25.76):
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
50
src/cli/mtui-cli.ts
Normal file
50
src/cli/mtui-cli.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import type { Command } from "commander";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { formatDocsLink } from "../terminal/links.js";
|
||||
import { theme } from "../terminal/theme.js";
|
||||
import { runMtui } from "../mtui/mtui.js";
|
||||
import { parseTimeoutMs } from "./parse-timeout.js";
|
||||
|
||||
export function registerMtuiCli(program: Command) {
|
||||
program
|
||||
.command("mtui")
|
||||
.description("Open a modern terminal UI (React/Ink) connected to the Gateway")
|
||||
.option("--url <url>", "Gateway WebSocket URL (defaults to gateway.remote.url when configured)")
|
||||
.option("--token <token>", "Gateway token (if required)")
|
||||
.option("--password <password>", "Gateway password (if required)")
|
||||
.option("--session <key>", 'Session key (default: "main", or "global" when scope is global)')
|
||||
.option("--deliver", "Deliver assistant replies", false)
|
||||
.option("--thinking <level>", "Thinking level override")
|
||||
.option("--message <text>", "Send an initial message after connecting")
|
||||
.option("--timeout-ms <ms>", "Agent timeout in ms (defaults to agents.defaults.timeoutSeconds)")
|
||||
.option("--history-limit <n>", "History entries to load", "200")
|
||||
.addHelpText(
|
||||
"after",
|
||||
() => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/mtui", "docs.molt.bot/cli/mtui")}\n`,
|
||||
)
|
||||
.action(async (opts) => {
|
||||
try {
|
||||
const timeoutMs = parseTimeoutMs(opts.timeoutMs);
|
||||
if (opts.timeoutMs !== undefined && timeoutMs === undefined) {
|
||||
defaultRuntime.error(
|
||||
`warning: invalid --timeout-ms "${String(opts.timeoutMs)}"; ignoring`,
|
||||
);
|
||||
}
|
||||
const historyLimit = Number.parseInt(String(opts.historyLimit ?? "200"), 10);
|
||||
await runMtui({
|
||||
url: opts.url as string | undefined,
|
||||
token: opts.token as string | undefined,
|
||||
password: opts.password as string | undefined,
|
||||
session: opts.session as string | undefined,
|
||||
deliver: Boolean(opts.deliver),
|
||||
thinking: opts.thinking as string | undefined,
|
||||
message: opts.message as string | undefined,
|
||||
timeoutMs,
|
||||
historyLimit: Number.isNaN(historyLimit) ? undefined : historyLimit,
|
||||
});
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -124,6 +124,14 @@ const entries: SubCliEntry[] = [
|
||||
mod.registerTuiCli(program);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mtui",
|
||||
description: "Modern Terminal UI (React/Ink)",
|
||||
register: async (program) => {
|
||||
const mod = await import("../mtui-cli.js");
|
||||
mod.registerMtuiCli(program);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "cron",
|
||||
description: "Cron scheduler",
|
||||
|
||||
199
src/mtui/App.tsx
Normal file
199
src/mtui/App.tsx
Normal file
@ -0,0 +1,199 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Box, Text, useInput, useApp } from "ink";
|
||||
import Spinner from "ink-spinner";
|
||||
import { GatewayProvider, useGateway } from "./context/GatewayContext.js";
|
||||
import { SettingsProvider, useSettings } from "./context/SettingsContext.js";
|
||||
import { useChat } from "./hooks/useChat.js";
|
||||
import { useCommands } from "./hooks/useCommands.js";
|
||||
import { MessageView } from "./components/MessageView.js";
|
||||
import { InputBar } from "./components/InputBar.js";
|
||||
import { Selector } from "./components/Selector.js";
|
||||
import type { TuiOptions } from "../tui/tui-types.js";
|
||||
import { formatContextUsageLine } from "../tui/tui-formatters.js";
|
||||
|
||||
type AppProps = {
|
||||
options: TuiOptions;
|
||||
};
|
||||
|
||||
const ChatApp: React.FC<{ options: TuiOptions }> = ({ options }) => {
|
||||
const { exit } = useApp();
|
||||
const gateway = useGateway();
|
||||
const { showThinking, setShowThinking } = useSettings();
|
||||
const [connectionStatus, setConnectionStatus] = useState<
|
||||
"connecting" | "connected" | "disconnected"
|
||||
>("connecting");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [overlay, setOverlay] = useState<{ type: "model" | "agent"; items: any[] } | null>(null);
|
||||
|
||||
const {
|
||||
messages,
|
||||
status,
|
||||
sendMessage,
|
||||
addMessage,
|
||||
sessionInfo,
|
||||
sessionKey,
|
||||
refreshSessionInfo,
|
||||
loadHistory,
|
||||
} = useChat(options.session || "main");
|
||||
|
||||
const { handleLocalShell, handleSlashCommand } = useCommands(
|
||||
sessionKey,
|
||||
addMessage,
|
||||
refreshSessionInfo,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
gateway.onConnected = () => {
|
||||
setConnectionStatus("connected");
|
||||
setError(null);
|
||||
void loadHistory();
|
||||
};
|
||||
|
||||
gateway.onDisconnected = (reason) => {
|
||||
setConnectionStatus("disconnected");
|
||||
setError(reason || "Connection closed");
|
||||
};
|
||||
|
||||
gateway.start();
|
||||
|
||||
return () => {
|
||||
gateway.stop();
|
||||
};
|
||||
}, [gateway, loadHistory]);
|
||||
|
||||
const handleSubmit = async (value: string) => {
|
||||
if (value.startsWith("!")) {
|
||||
await handleLocalShell(value);
|
||||
} else if (value.startsWith("/")) {
|
||||
if (value === "/model") {
|
||||
const models = await gateway.listModels();
|
||||
setOverlay({
|
||||
type: "model",
|
||||
items: models.map((m) => ({
|
||||
label: `${m.provider}/${m.id}`,
|
||||
value: `${m.provider}/${m.id}`,
|
||||
})),
|
||||
});
|
||||
} else {
|
||||
await handleSlashCommand(value);
|
||||
}
|
||||
} else {
|
||||
await sendMessage(value);
|
||||
}
|
||||
};
|
||||
|
||||
useInput((input, key) => {
|
||||
if (key.ctrl && input === "c") {
|
||||
exit();
|
||||
}
|
||||
if (key.escape && overlay) {
|
||||
setOverlay(null);
|
||||
}
|
||||
if (key.ctrl && input === "t") {
|
||||
setShowThinking(!showThinking);
|
||||
}
|
||||
});
|
||||
|
||||
const usageLine = formatContextUsageLine({
|
||||
total: sessionInfo.totalTokens,
|
||||
context: sessionInfo.contextTokens,
|
||||
remaining: (sessionInfo.contextTokens ?? 0) - (sessionInfo.totalTokens ?? 0),
|
||||
percent:
|
||||
sessionInfo.totalTokens && sessionInfo.contextTokens
|
||||
? (sessionInfo.totalTokens / sessionInfo.contextTokens) * 100
|
||||
: null,
|
||||
});
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" padding={1} minHeight={20}>
|
||||
<Box borderStyle="round" borderColor="cyan" paddingX={1} marginBottom={1}>
|
||||
<Text bold color="white">
|
||||
Moltbot MTUI
|
||||
</Text>
|
||||
<Box flexGrow={1} />
|
||||
<Box paddingX={2}>
|
||||
<Text dimColor>{sessionInfo.model || "no model"}</Text>
|
||||
</Box>
|
||||
<Text
|
||||
color={
|
||||
connectionStatus === "connected"
|
||||
? "green"
|
||||
: connectionStatus === "connecting"
|
||||
? "yellow"
|
||||
: "red"
|
||||
}
|
||||
>
|
||||
{connectionStatus === "connecting" && <Spinner type="dots" />} {connectionStatus}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{overlay ? (
|
||||
<Box flexGrow={1} justifyContent="center" alignItems="center">
|
||||
<Selector
|
||||
title={`Select ${overlay.type}`}
|
||||
items={overlay.items}
|
||||
onSelect={async (item) => {
|
||||
if (overlay.type === "model") {
|
||||
await gateway.patchSession({ key: sessionKey, model: item.value });
|
||||
addMessage({
|
||||
id: Math.random().toString(),
|
||||
role: "system",
|
||||
content: `Model set to ${item.value}`,
|
||||
});
|
||||
await refreshSessionInfo();
|
||||
}
|
||||
setOverlay(null);
|
||||
}}
|
||||
onCancel={() => setOverlay(null)}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Box flexGrow={1} flexDirection="column">
|
||||
<Box flexDirection="column">
|
||||
{messages.slice(-10).map((msg, i) => (
|
||||
<MessageView key={i} message={msg} />
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{status === "running" && !overlay && (
|
||||
<Box paddingX={1} marginBottom={1}>
|
||||
<Text color="yellow">
|
||||
<Spinner type="dots" /> Assistant is thinking...
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{error && !overlay && (
|
||||
<Box paddingX={1} marginBottom={1}>
|
||||
<Text color="red">Error: {error}</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{!overlay && (
|
||||
<>
|
||||
<Box paddingX={1} marginBottom={1}>
|
||||
<Text dimColor>{usageLine}</Text>
|
||||
</Box>
|
||||
|
||||
<InputBar onSubmit={handleSubmit} status={status} />
|
||||
|
||||
<Box paddingX={1} marginTop={1}>
|
||||
<Text dimColor>Ctrl+C exit | Ctrl+T think | /model | /reset | !ls</Text>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const App: React.FC<AppProps> = ({ options }) => {
|
||||
return (
|
||||
<SettingsProvider>
|
||||
<GatewayProvider options={options}>
|
||||
<ChatApp options={options} />
|
||||
</GatewayProvider>
|
||||
</SettingsProvider>
|
||||
);
|
||||
};
|
||||
34
src/mtui/components/InputBar.tsx
Normal file
34
src/mtui/components/InputBar.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box, Text } from "ink";
|
||||
import TextInput from "ink-text-input";
|
||||
|
||||
type InputBarProps = {
|
||||
onSubmit: (value: string) => void;
|
||||
status: string;
|
||||
};
|
||||
|
||||
export const InputBar: React.FC<InputBarProps> = ({ onSubmit, status }) => {
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
const handleSubmit = (val: string) => {
|
||||
if (!val.trim()) return;
|
||||
onSubmit(val);
|
||||
setValue("");
|
||||
};
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Box paddingX={1} borderStyle="single" borderColor={status === "idle" ? "cyan" : "yellow"}>
|
||||
<Text bold color="cyan">
|
||||
moltbot {">"}{" "}
|
||||
</Text>
|
||||
<TextInput
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
onSubmit={handleSubmit}
|
||||
placeholder="Type a message or / for commands..."
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
101
src/mtui/components/MessageView.tsx
Normal file
101
src/mtui/components/MessageView.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import React from "react";
|
||||
import { Box, Text } from "ink";
|
||||
import { highlight } from "cli-highlight";
|
||||
import type { Message } from "../hooks/useChat.js";
|
||||
import { useSettings } from "../context/SettingsContext.js";
|
||||
|
||||
type MessageViewProps = {
|
||||
message: Message;
|
||||
};
|
||||
|
||||
export const MessageView: React.FC<MessageViewProps> = ({ message }) => {
|
||||
const isUser = message.role === "user";
|
||||
const isSystem = message.role === "system";
|
||||
const { showThinking } = useSettings();
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Box marginBottom={0}>
|
||||
<Text bold color={isUser ? "blue" : isSystem ? "yellow" : "green"}>
|
||||
{isUser ? "You" : isSystem ? "System" : "Assistant"}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box paddingLeft={2} flexDirection="column">
|
||||
{message.thinking && (
|
||||
<Box flexDirection="column" marginY={1}>
|
||||
<Box>
|
||||
<Text dimColor italic>
|
||||
{showThinking ? "▼ Thinking" : "▶ Thinking (hidden)"}
|
||||
</Text>
|
||||
</Box>
|
||||
{showThinking && (
|
||||
<Box paddingLeft={2} borderStyle="single" borderColor="gray">
|
||||
<Text italic dimColor>
|
||||
{message.thinking}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{renderContent(message.content)}
|
||||
|
||||
{message.tools && message.tools.length > 0 && (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
{message.tools.map((tool) => (
|
||||
<Box
|
||||
key={tool.id}
|
||||
flexDirection="column"
|
||||
borderStyle="round"
|
||||
borderColor="gray"
|
||||
paddingX={1}
|
||||
marginBottom={1}
|
||||
>
|
||||
<Text bold color="cyan">
|
||||
Tool: {tool.name}
|
||||
</Text>
|
||||
<Text dimColor>Args: {JSON.stringify(tool.args)}</Text>
|
||||
{tool.isStreaming && <Text color="yellow">Running...</Text>}
|
||||
{tool.result && (
|
||||
<Box marginTop={1}>
|
||||
<Text color={tool.isError ? "red" : "gray"}>
|
||||
Result:{" "}
|
||||
{typeof tool.result === "string" ? tool.result : JSON.stringify(tool.result)}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
function renderContent(content: string) {
|
||||
const parts = content.split(/(```[\s\S]*?```)/g);
|
||||
return parts.map((part, i) => {
|
||||
if (part.startsWith("```")) {
|
||||
const match = part.match(/```(\w*)\n([\s\S]*?)```/);
|
||||
if (match) {
|
||||
const lang = match[1] || "text";
|
||||
const code = match[2];
|
||||
try {
|
||||
return (
|
||||
<Box key={i} marginY={1} paddingX={1} borderStyle="single" borderColor="gray">
|
||||
<Text>{highlight(code, { language: lang })}</Text>
|
||||
</Box>
|
||||
);
|
||||
} catch {
|
||||
return (
|
||||
<Box key={i} marginY={1} paddingX={1} borderStyle="single" borderColor="gray">
|
||||
<Text>{code}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return <Text key={i}>{part}</Text>;
|
||||
});
|
||||
}
|
||||
50
src/mtui/components/Selector.tsx
Normal file
50
src/mtui/components/Selector.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box, Text, useInput } from "ink";
|
||||
import SelectInput from "ink-select-input";
|
||||
|
||||
type Item = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type SelectorProps = {
|
||||
title: string;
|
||||
items: Item[];
|
||||
onSelect: (item: Item) => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
export const Selector: React.FC<SelectorProps> = ({ title, items, onSelect, onCancel }) => {
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
const filteredItems = items.filter(
|
||||
(item) =>
|
||||
item.label.toLowerCase().includes(query.toLowerCase()) ||
|
||||
item.value.toLowerCase().includes(query.toLowerCase()),
|
||||
);
|
||||
|
||||
useInput((input, key) => {
|
||||
if (key.escape) {
|
||||
onCancel();
|
||||
}
|
||||
if (!key.ctrl && !key.meta && input.length === 1 && !key.return) {
|
||||
setQuery((q) => q + input);
|
||||
}
|
||||
if (key.backspace || key.delete) {
|
||||
setQuery((q) => q.slice(0, -1));
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" borderStyle="round" borderColor="blue" paddingX={1}>
|
||||
<Text bold>{title}</Text>
|
||||
<Box marginY={1}>
|
||||
<Text>Search: {query}</Text>
|
||||
</Box>
|
||||
<SelectInput items={filteredItems} onSelect={onSelect} />
|
||||
<Box marginTop={1}>
|
||||
<Text dimColor>Esc to cancel</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
22
src/mtui/context/GatewayContext.tsx
Normal file
22
src/mtui/context/GatewayContext.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React, { createContext, useContext, useMemo } from "react";
|
||||
import { GatewayChatClient } from "../../tui/gateway-chat.js";
|
||||
import type { TuiOptions } from "../../tui/tui-types.js";
|
||||
|
||||
const GatewayContext = createContext<GatewayChatClient | null>(null);
|
||||
|
||||
export const GatewayProvider: React.FC<{ options: TuiOptions; children: React.ReactNode }> = ({
|
||||
options,
|
||||
children,
|
||||
}) => {
|
||||
const client = useMemo(() => new GatewayChatClient(options), [options]);
|
||||
|
||||
return <GatewayContext.Provider value={client}>{children}</GatewayContext.Provider>;
|
||||
};
|
||||
|
||||
export const useGateway = () => {
|
||||
const context = useContext(GatewayContext);
|
||||
if (!context) {
|
||||
throw new Error("useGateway must be used within a GatewayProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
31
src/mtui/context/SettingsContext.tsx
Normal file
31
src/mtui/context/SettingsContext.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React, { createContext, useContext, useState } from "react";
|
||||
|
||||
type Settings = {
|
||||
showThinking: boolean;
|
||||
setShowThinking: (show: boolean) => void;
|
||||
toolsExpanded: boolean;
|
||||
setToolsExpanded: (expanded: boolean) => void;
|
||||
};
|
||||
|
||||
const SettingsContext = createContext<Settings | null>(null);
|
||||
|
||||
export const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [showThinking, setShowThinking] = useState(false);
|
||||
const [toolsExpanded, setToolsExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<SettingsContext.Provider
|
||||
value={{ showThinking, setShowThinking, toolsExpanded, setToolsExpanded }}
|
||||
>
|
||||
{children}
|
||||
</SettingsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useSettings = () => {
|
||||
const context = useContext(SettingsContext);
|
||||
if (!context) {
|
||||
throw new Error("useSettings must be used within a SettingsProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
205
src/mtui/hooks/useChat.ts
Normal file
205
src/mtui/hooks/useChat.ts
Normal file
@ -0,0 +1,205 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useGateway } from "../context/GatewayContext.js";
|
||||
import { TuiStreamAssembler } from "../../tui/tui-stream-assembler.js";
|
||||
import { extractThinkingFromMessage, extractContentFromMessage } from "../../tui/tui-formatters.js";
|
||||
import type { ChatEvent, AgentEvent, SessionInfo } from "../../tui/tui-types.js";
|
||||
|
||||
export type Message = {
|
||||
id: string;
|
||||
role: "user" | "assistant" | "system";
|
||||
content: string;
|
||||
thinking?: string;
|
||||
isFinal?: boolean;
|
||||
tools?: ToolCall[];
|
||||
};
|
||||
|
||||
export type ToolCall = {
|
||||
id: string;
|
||||
name: string;
|
||||
args?: any;
|
||||
result?: any;
|
||||
isError?: boolean;
|
||||
isStreaming?: boolean;
|
||||
};
|
||||
|
||||
export const useChat = (initialSessionKey: string) => {
|
||||
const gateway = useGateway();
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [sessionKey, setSessionKey] = useState(initialSessionKey);
|
||||
const [activeRunId, setActiveRunId] = useState<string | null>(null);
|
||||
const [status, setStatus] = useState<"idle" | "running" | "streaming" | "error">("idle");
|
||||
const [sessionInfo, setSessionInfo] = useState<SessionInfo>({});
|
||||
|
||||
const refreshSessionInfo = useCallback(async () => {
|
||||
try {
|
||||
const statusRes = (await gateway.getStatus()) as any;
|
||||
if (statusRes?.sessions?.recent) {
|
||||
const current = statusRes.sessions.recent.find((s: any) => s.key === sessionKey);
|
||||
if (current) {
|
||||
setSessionInfo(current);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to refresh session info", err);
|
||||
}
|
||||
}, [gateway, sessionKey]);
|
||||
|
||||
useEffect(() => {
|
||||
const assembler = new TuiStreamAssembler();
|
||||
|
||||
gateway.onEvent = (evt) => {
|
||||
if (evt.event === "chat.event") {
|
||||
const payload = evt.payload as ChatEvent;
|
||||
if (payload.sessionKey !== sessionKey) return;
|
||||
|
||||
if (payload.state === "delta") {
|
||||
assembler.ingestDelta(payload.runId, payload.message, true);
|
||||
const thinking = extractThinkingFromMessage(payload.message);
|
||||
const content = extractContentFromMessage(payload.message);
|
||||
|
||||
setMessages((prev) => {
|
||||
const last = prev[prev.length - 1];
|
||||
if (last && last.id === payload.runId) {
|
||||
return [
|
||||
...prev.slice(0, -1),
|
||||
{
|
||||
...last,
|
||||
content: last.content + (content || ""),
|
||||
thinking: last.thinking + (thinking || ""),
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
id: payload.runId,
|
||||
role: "assistant",
|
||||
content: content || "",
|
||||
thinking: thinking || "",
|
||||
},
|
||||
];
|
||||
});
|
||||
setStatus("streaming");
|
||||
} else if (payload.state === "final") {
|
||||
assembler.finalize(payload.runId, payload.message, true);
|
||||
const thinking = extractThinkingFromMessage(payload.message);
|
||||
const content = extractContentFromMessage(payload.message);
|
||||
|
||||
setMessages((prev) => {
|
||||
const last = prev[prev.length - 1];
|
||||
if (last && last.id === payload.runId) {
|
||||
return [
|
||||
...prev.slice(0, -1),
|
||||
{
|
||||
...last,
|
||||
content: content || last.content,
|
||||
thinking: thinking || last.thinking,
|
||||
isFinal: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
id: payload.runId,
|
||||
role: "assistant",
|
||||
content: content || "",
|
||||
thinking: thinking || "",
|
||||
isFinal: true,
|
||||
},
|
||||
];
|
||||
});
|
||||
setActiveRunId(null);
|
||||
setStatus("idle");
|
||||
void refreshSessionInfo();
|
||||
} else if (payload.state === "error") {
|
||||
setStatus("error");
|
||||
setActiveRunId(null);
|
||||
void refreshSessionInfo();
|
||||
}
|
||||
} else if (evt.event === "agent.event") {
|
||||
const payload = evt.payload as AgentEvent;
|
||||
if (payload.stream === "tool") {
|
||||
const data = payload.data as any;
|
||||
const { phase, toolCallId, name: toolName } = data;
|
||||
|
||||
setMessages((prev) => {
|
||||
const last = prev[prev.length - 1];
|
||||
if (!last || last.role !== "assistant") return prev;
|
||||
|
||||
const tools = last.tools || [];
|
||||
let nextTools = [...tools];
|
||||
|
||||
if (phase === "start") {
|
||||
nextTools.push({
|
||||
id: toolCallId,
|
||||
name: toolName,
|
||||
args: data.args,
|
||||
isStreaming: true,
|
||||
});
|
||||
} else if (phase === "update") {
|
||||
nextTools = nextTools.map((t) =>
|
||||
t.id === toolCallId ? { ...t, result: data.partialResult } : t,
|
||||
);
|
||||
} else if (phase === "result") {
|
||||
nextTools = nextTools.map((t) =>
|
||||
t.id === toolCallId
|
||||
? { ...t, result: data.result, isError: data.isError, isStreaming: false }
|
||||
: t,
|
||||
);
|
||||
}
|
||||
|
||||
return [...prev.slice(0, -1), { ...last, tools: nextTools }];
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void refreshSessionInfo();
|
||||
}, [gateway, sessionKey, refreshSessionInfo]);
|
||||
|
||||
const addMessage = useCallback((msg: Message) => {
|
||||
setMessages((prev) => [...prev, msg]);
|
||||
}, []);
|
||||
|
||||
const loadHistory = useCallback(async () => {
|
||||
try {
|
||||
const history = (await gateway.loadHistory({ sessionKey, limit: 100 })) as any;
|
||||
if (Array.isArray(history?.messages)) {
|
||||
const msgs = history.messages.map((m: any) => ({
|
||||
id: m.id || Math.random().toString(),
|
||||
role: m.role,
|
||||
content: extractContentFromMessage(m) || "",
|
||||
thinking: extractThinkingFromMessage(m) || "",
|
||||
isFinal: true,
|
||||
}));
|
||||
setMessages(msgs);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load history", err);
|
||||
}
|
||||
}, [gateway, sessionKey]);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
async (text: string) => {
|
||||
addMessage({ id: Math.random().toString(), role: "user", content: text });
|
||||
setStatus("running");
|
||||
const { runId } = await gateway.sendChat({ sessionKey, message: text });
|
||||
setActiveRunId(runId);
|
||||
},
|
||||
[gateway, sessionKey, addMessage],
|
||||
);
|
||||
|
||||
return {
|
||||
messages,
|
||||
status,
|
||||
sendMessage,
|
||||
addMessage,
|
||||
sessionInfo,
|
||||
sessionKey,
|
||||
setSessionKey,
|
||||
refreshSessionInfo,
|
||||
loadHistory,
|
||||
activeRunId,
|
||||
};
|
||||
};
|
||||
76
src/mtui/hooks/useCommands.ts
Normal file
76
src/mtui/hooks/useCommands.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { useCallback } from "react";
|
||||
import { useGateway } from "../context/GatewayContext.js";
|
||||
import type { Message } from "./useChat.js";
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
export const useCommands = (
|
||||
sessionKey: string,
|
||||
addMessage: (msg: Message) => void,
|
||||
refreshSessionInfo: () => Promise<void>,
|
||||
) => {
|
||||
const gateway = useGateway();
|
||||
|
||||
const handleLocalShell = useCallback(
|
||||
async (line: string) => {
|
||||
const cmd = line.slice(1);
|
||||
addMessage({ id: Math.random().toString(), role: "system", content: `[local] $ ${cmd}` });
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
const child = spawn(cmd, { shell: true });
|
||||
let output = "";
|
||||
|
||||
child.stdout.on("data", (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
child.stderr.on("data", (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
child.on("close", (code) => {
|
||||
addMessage({
|
||||
id: Math.random().toString(),
|
||||
role: "system",
|
||||
content: output.trim() || `Exit code: ${code}`,
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
[addMessage],
|
||||
);
|
||||
|
||||
const handleSlashCommand = useCallback(
|
||||
async (text: string) => {
|
||||
const parts = text.slice(1).split(" ");
|
||||
const command = parts[0];
|
||||
const args = parts.slice(1).join(" ");
|
||||
|
||||
switch (command) {
|
||||
case "reset":
|
||||
await gateway.resetSession(sessionKey);
|
||||
addMessage({ id: Math.random().toString(), role: "system", content: "Session reset." });
|
||||
break;
|
||||
case "model":
|
||||
if (args) {
|
||||
await gateway.patchSession({ key: sessionKey, model: args });
|
||||
addMessage({
|
||||
id: Math.random().toString(),
|
||||
role: "system",
|
||||
content: `Model set to ${args}`,
|
||||
});
|
||||
await refreshSessionInfo();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
addMessage({
|
||||
id: Math.random().toString(),
|
||||
role: "system",
|
||||
content: `Unknown command: /${command}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
[gateway, sessionKey, addMessage, refreshSessionInfo],
|
||||
);
|
||||
|
||||
return { handleLocalShell, handleSlashCommand };
|
||||
};
|
||||
9
src/mtui/mtui.tsx
Normal file
9
src/mtui/mtui.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React from "react";
|
||||
import { render } from "ink";
|
||||
import { App } from "./App.js";
|
||||
import type { TuiOptions } from "../tui/tui-types.js";
|
||||
|
||||
export async function runMtui(opts: TuiOptions) {
|
||||
const { waitUntilExit } = render(<App options={opts} />);
|
||||
await waitUntilExit();
|
||||
}
|
||||
@ -11,7 +11,8 @@
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"noEmitOnError": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": [
|
||||
|
||||
Loading…
Reference in New Issue
Block a user