Add model selector command

This commit is contained in:
TonyGeez
2025-10-05 18:18:15 -04:00
parent 9958bfbc6f
commit b8a3845244
7 changed files with 885 additions and 3 deletions

View File

@@ -29,7 +29,8 @@
"rotating-file-stream": "^3.2.7", "rotating-file-stream": "^3.2.7",
"shell-quote": "^1.8.3", "shell-quote": "^1.8.3",
"tiktoken": "^1.0.21", "tiktoken": "^1.0.21",
"uuid": "^11.1.0" "uuid": "^11.1.0",
"@inquirer/prompts": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.0.15", "@types/node": "^24.0.15",

271
pnpm-lock.yaml generated
View File

@@ -11,6 +11,9 @@ importers:
'@fastify/static': '@fastify/static':
specifier: ^8.2.0 specifier: ^8.2.0
version: 8.2.0 version: 8.2.0
'@inquirer/prompts':
specifier: ^5.0.0
version: 5.5.0
'@musistudio/llms': '@musistudio/llms':
specifier: ^1.0.35 specifier: ^1.0.35
version: 1.0.35(ws@8.18.3) version: 1.0.35(ws@8.18.3)
@@ -259,6 +262,66 @@ packages:
'@modelcontextprotocol/sdk': '@modelcontextprotocol/sdk':
optional: true optional: true
'@inquirer/checkbox@2.5.0':
resolution: {integrity: sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==}
engines: {node: '>=18'}
'@inquirer/confirm@3.2.0':
resolution: {integrity: sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw==}
engines: {node: '>=18'}
'@inquirer/core@9.2.1':
resolution: {integrity: sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==}
engines: {node: '>=18'}
'@inquirer/editor@2.2.0':
resolution: {integrity: sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==}
engines: {node: '>=18'}
'@inquirer/expand@2.3.0':
resolution: {integrity: sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==}
engines: {node: '>=18'}
'@inquirer/figures@1.0.13':
resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==}
engines: {node: '>=18'}
'@inquirer/input@2.3.0':
resolution: {integrity: sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==}
engines: {node: '>=18'}
'@inquirer/number@1.1.0':
resolution: {integrity: sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==}
engines: {node: '>=18'}
'@inquirer/password@2.2.0':
resolution: {integrity: sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==}
engines: {node: '>=18'}
'@inquirer/prompts@5.5.0':
resolution: {integrity: sha512-BHDeL0catgHdcHbSFFUddNzvx/imzJMft+tWDPwTm3hfu8/tApk1HrooNngB2Mb4qY+KaRWF+iZqoVUPeslEog==}
engines: {node: '>=18'}
'@inquirer/rawlist@2.3.0':
resolution: {integrity: sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==}
engines: {node: '>=18'}
'@inquirer/search@1.1.0':
resolution: {integrity: sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==}
engines: {node: '>=18'}
'@inquirer/select@2.5.0':
resolution: {integrity: sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==}
engines: {node: '>=18'}
'@inquirer/type@1.5.5':
resolution: {integrity: sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==}
engines: {node: '>=18'}
'@inquirer/type@2.0.0':
resolution: {integrity: sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==}
engines: {node: '>=18'}
'@isaacs/balanced-match@4.0.1': '@isaacs/balanced-match@4.0.1':
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
engines: {node: 20 || >=22} engines: {node: 20 || >=22}
@@ -290,9 +353,18 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
'@types/mute-stream@0.0.4':
resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==}
'@types/node@22.18.8':
resolution: {integrity: sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==}
'@types/node@24.3.0': '@types/node@24.3.0':
resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==} resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==}
'@types/wrap-ansi@3.0.0':
resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==}
abstract-logging@2.0.1: abstract-logging@2.0.1:
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
@@ -311,6 +383,10 @@ packages:
ajv@8.17.1: ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
ansi-escapes@4.3.2:
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
engines: {node: '>=8'}
ansi-regex@5.0.1: ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -351,6 +427,13 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'} engines: {node: '>=10'}
chardet@0.7.0:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
cli-width@4.1.0:
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
engines: {node: '>= 12'}
color-convert@2.0.1: color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'} engines: {node: '>=7.0.0'}
@@ -433,6 +516,10 @@ packages:
extend@3.0.2: extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
external-editor@3.1.0:
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
engines: {node: '>=4'}
fast-decode-uri-component@1.0.1: fast-decode-uri-component@1.0.1:
resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
@@ -561,6 +648,10 @@ packages:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
inherits@2.0.4: inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
@@ -669,6 +760,10 @@ packages:
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
mute-stream@1.0.0:
resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
nice-try@1.0.5: nice-try@1.0.5:
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
@@ -716,6 +811,10 @@ packages:
openurl@1.1.1: openurl@1.1.1:
resolution: {integrity: sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==} resolution: {integrity: sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==}
os-tmpdir@1.0.2:
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
engines: {node: '>=0.10.0'}
p-finally@1.0.0: p-finally@1.0.0:
resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -812,6 +911,9 @@ packages:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'} engines: {node: '>=10'}
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
secure-json-parse@4.0.0: secure-json-parse@4.0.0:
resolution: {integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==} resolution: {integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==}
@@ -912,6 +1014,10 @@ packages:
tiktoken@1.0.22: tiktoken@1.0.22:
resolution: {integrity: sha512-PKvy1rVF1RibfF3JlXBSP0Jrcw2uq3yXdgcEXtKTYn3QJ/cBRBHDnrJ5jHky+MENZ6DIPwNUGWpkVx+7joCpNA==} resolution: {integrity: sha512-PKvy1rVF1RibfF3JlXBSP0Jrcw2uq3yXdgcEXtKTYn3QJ/cBRBHDnrJ5jHky+MENZ6DIPwNUGWpkVx+7joCpNA==}
tmp@0.0.33:
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
engines: {node: '>=0.6.0'}
to-regex-range@5.0.1: to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
@@ -927,11 +1033,18 @@ packages:
tr46@0.0.3: tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
type-fest@0.21.3:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'}
typescript@5.9.2: typescript@5.9.2:
resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
undici-types@7.10.0: undici-types@7.10.0:
resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
@@ -966,6 +1079,10 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
hasBin: true hasBin: true
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
wrap-ansi@7.0.0: wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -989,6 +1106,10 @@ packages:
utf-8-validate: utf-8-validate:
optional: true optional: true
yoctocolors-cjs@2.1.3:
resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==}
engines: {node: '>=18'}
snapshots: snapshots:
'@anthropic-ai/sdk@0.54.0': {} '@anthropic-ai/sdk@0.54.0': {}
@@ -1128,6 +1249,106 @@ snapshots:
- supports-color - supports-color
- utf-8-validate - utf-8-validate
'@inquirer/checkbox@2.5.0':
dependencies:
'@inquirer/core': 9.2.1
'@inquirer/figures': 1.0.13
'@inquirer/type': 1.5.5
ansi-escapes: 4.3.2
yoctocolors-cjs: 2.1.3
'@inquirer/confirm@3.2.0':
dependencies:
'@inquirer/core': 9.2.1
'@inquirer/type': 1.5.5
'@inquirer/core@9.2.1':
dependencies:
'@inquirer/figures': 1.0.13
'@inquirer/type': 2.0.0
'@types/mute-stream': 0.0.4
'@types/node': 22.18.8
'@types/wrap-ansi': 3.0.0
ansi-escapes: 4.3.2
cli-width: 4.1.0
mute-stream: 1.0.0
signal-exit: 4.1.0
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
yoctocolors-cjs: 2.1.3
'@inquirer/editor@2.2.0':
dependencies:
'@inquirer/core': 9.2.1
'@inquirer/type': 1.5.5
external-editor: 3.1.0
'@inquirer/expand@2.3.0':
dependencies:
'@inquirer/core': 9.2.1
'@inquirer/type': 1.5.5
yoctocolors-cjs: 2.1.3
'@inquirer/figures@1.0.13': {}
'@inquirer/input@2.3.0':
dependencies:
'@inquirer/core': 9.2.1
'@inquirer/type': 1.5.5
'@inquirer/number@1.1.0':
dependencies:
'@inquirer/core': 9.2.1
'@inquirer/type': 1.5.5
'@inquirer/password@2.2.0':
dependencies:
'@inquirer/core': 9.2.1
'@inquirer/type': 1.5.5
ansi-escapes: 4.3.2
'@inquirer/prompts@5.5.0':
dependencies:
'@inquirer/checkbox': 2.5.0
'@inquirer/confirm': 3.2.0
'@inquirer/editor': 2.2.0
'@inquirer/expand': 2.3.0
'@inquirer/input': 2.3.0
'@inquirer/number': 1.1.0
'@inquirer/password': 2.2.0
'@inquirer/rawlist': 2.3.0
'@inquirer/search': 1.1.0
'@inquirer/select': 2.5.0
'@inquirer/rawlist@2.3.0':
dependencies:
'@inquirer/core': 9.2.1
'@inquirer/type': 1.5.5
yoctocolors-cjs: 2.1.3
'@inquirer/search@1.1.0':
dependencies:
'@inquirer/core': 9.2.1
'@inquirer/figures': 1.0.13
'@inquirer/type': 1.5.5
yoctocolors-cjs: 2.1.3
'@inquirer/select@2.5.0':
dependencies:
'@inquirer/core': 9.2.1
'@inquirer/figures': 1.0.13
'@inquirer/type': 1.5.5
ansi-escapes: 4.3.2
yoctocolors-cjs: 2.1.3
'@inquirer/type@1.5.5':
dependencies:
mute-stream: 1.0.0
'@inquirer/type@2.0.0':
dependencies:
mute-stream: 1.0.0
'@isaacs/balanced-match@4.0.1': {} '@isaacs/balanced-match@4.0.1': {}
'@isaacs/brace-expansion@5.0.0': '@isaacs/brace-expansion@5.0.0':
@@ -1179,10 +1400,20 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5 '@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1 fastq: 1.19.1
'@types/mute-stream@0.0.4':
dependencies:
'@types/node': 24.3.0
'@types/node@22.18.8':
dependencies:
undici-types: 6.21.0
'@types/node@24.3.0': '@types/node@24.3.0':
dependencies: dependencies:
undici-types: 7.10.0 undici-types: 7.10.0
'@types/wrap-ansi@3.0.0': {}
abstract-logging@2.0.1: {} abstract-logging@2.0.1: {}
agent-base@7.1.4: {} agent-base@7.1.4: {}
@@ -1198,6 +1429,10 @@ snapshots:
json-schema-traverse: 1.0.0 json-schema-traverse: 1.0.0
require-from-string: 2.0.2 require-from-string: 2.0.2
ansi-escapes@4.3.2:
dependencies:
type-fest: 0.21.3
ansi-regex@5.0.1: {} ansi-regex@5.0.1: {}
ansi-regex@6.1.0: {} ansi-regex@6.1.0: {}
@@ -1230,6 +1465,10 @@ snapshots:
ansi-styles: 4.3.0 ansi-styles: 4.3.0
supports-color: 7.2.0 supports-color: 7.2.0
chardet@0.7.0: {}
cli-width@4.1.0: {}
color-convert@2.0.1: color-convert@2.0.1:
dependencies: dependencies:
color-name: 1.1.4 color-name: 1.1.4
@@ -1327,6 +1566,12 @@ snapshots:
extend@3.0.2: {} extend@3.0.2: {}
external-editor@3.1.0:
dependencies:
chardet: 0.7.0
iconv-lite: 0.4.24
tmp: 0.0.33
fast-decode-uri-component@1.0.1: {} fast-decode-uri-component@1.0.1: {}
fast-deep-equal@3.1.3: {} fast-deep-equal@3.1.3: {}
@@ -1529,6 +1774,10 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
inherits@2.0.4: {} inherits@2.0.4: {}
interpret@1.4.0: {} interpret@1.4.0: {}
@@ -1613,6 +1862,8 @@ snapshots:
ms@2.1.3: {} ms@2.1.3: {}
mute-stream@1.0.0: {}
nice-try@1.0.5: {} nice-try@1.0.5: {}
node-domexception@1.0.0: {} node-domexception@1.0.0: {}
@@ -1643,6 +1894,8 @@ snapshots:
openurl@1.1.1: {} openurl@1.1.1: {}
os-tmpdir@1.0.2: {}
p-finally@1.0.0: {} p-finally@1.0.0: {}
package-json-from-dist@1.0.1: {} package-json-from-dist@1.0.1: {}
@@ -1727,6 +1980,8 @@ snapshots:
safe-stable-stringify@2.5.0: {} safe-stable-stringify@2.5.0: {}
safer-buffer@2.1.2: {}
secure-json-parse@4.0.0: {} secure-json-parse@4.0.0: {}
semver@5.7.2: {} semver@5.7.2: {}
@@ -1809,6 +2064,10 @@ snapshots:
tiktoken@1.0.22: {} tiktoken@1.0.22: {}
tmp@0.0.33:
dependencies:
os-tmpdir: 1.0.2
to-regex-range@5.0.1: to-regex-range@5.0.1:
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
@@ -1819,8 +2078,12 @@ snapshots:
tr46@0.0.3: {} tr46@0.0.3: {}
type-fest@0.21.3: {}
typescript@5.9.2: {} typescript@5.9.2: {}
undici-types@6.21.0: {}
undici-types@7.10.0: {} undici-types@7.10.0: {}
undici@7.16.0: {} undici@7.16.0: {}
@@ -1846,6 +2109,12 @@ snapshots:
dependencies: dependencies:
isexe: 2.0.0 isexe: 2.0.0
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@7.0.0: wrap-ansi@7.0.0:
dependencies: dependencies:
ansi-styles: 4.3.0 ansi-styles: 4.3.0
@@ -1861,3 +2130,5 @@ snapshots:
wrappy@1.0.2: {} wrappy@1.0.2: {}
ws@8.18.3: {} ws@8.18.3: {}
yoctocolors-cjs@2.1.3: {}

2
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,2 @@
ignoredBuiltDependencies:
- esbuild

View File

@@ -8,6 +8,7 @@ import {
isServiceRunning, isServiceRunning,
getServiceInfo, getServiceInfo,
} from "./utils/processCheck"; } from "./utils/processCheck";
import { runModelSelector } from "./utils/modelSelector"; // ADD THIS LINE
import { version } from "../package.json"; import { version } from "../package.json";
import { spawn, exec } from "child_process"; import { spawn, exec } from "child_process";
import { PID_FILE, REFERENCE_COUNT_FILE } from "./constants"; import { PID_FILE, REFERENCE_COUNT_FILE } from "./constants";
@@ -26,6 +27,7 @@ Commands:
status Show server status status Show server status
statusline Integrated statusline statusline Integrated statusline
code Execute claude command code Execute claude command
model Interactive model selection and configuration
ui Open the web UI in browser ui Open the web UI in browser
-v, version Show version information -v, version Show version information
-h, help Show help information -h, help Show help information
@@ -33,6 +35,7 @@ Commands:
Example: Example:
ccr start ccr start
ccr code "Write a Hello World" ccr code "Write a Hello World"
ccr model
ccr ui ccr ui
`; `;
@@ -109,6 +112,10 @@ async function main() {
} }
}); });
break; break;
// ADD THIS CASE
case "model":
await runModelSelector();
break;
case "code": case "code":
if (!isRunning) { if (!isRunning) {
console.log("Service not running, starting service..."); console.log("Service not running, starting service...");
@@ -321,4 +328,4 @@ async function main() {
} }
} }
main().catch(console.error); main().catch(console.error);

462
src/utils/modelSelector.ts Normal file
View File

@@ -0,0 +1,462 @@
import * as fs from 'fs';
import * as path from 'path';
import { select, input, confirm } from '@inquirer/prompts';
// ANSI color codes
const RESET = "\x1B[0m";
const DIM = "\x1B[2m";
const BOLDGREEN = "\x1B[1m\x1B[32m";
const CYAN = "\x1B[36m";
const BOLDCYAN = "\x1B[1m\x1B[36m";
const GREEN = "\x1B[32m";
const YELLOW = "\x1B[33m";
const BOLDYELLOW = "\x1B[1m\x1B[33m";
interface TransformerConfig {
use: Array<string | [string, any]>;
[key: string]: any;
}
interface Provider {
name: string;
api_base_url: string;
api_key: string;
models: string[];
transformer?: TransformerConfig;
}
interface RouterConfig {
default: string;
background?: string;
think?: string;
longContext?: string;
longContextThreshold?: number;
webSearch?: string;
image?: string;
[key: string]: string | number | undefined;
}
interface Config {
Providers: Provider[];
Router: RouterConfig;
[key: string]: any;
}
interface ModelResult {
providerName: string;
modelName: string;
modelType: string;
}
const AVAILABLE_TRANSFORMERS = [
'anthropic',
'deepseek',
'gemini',
'openrouter',
'groq',
'maxtoken',
'tooluse',
'gemini-cli',
'reasoning',
'sampling',
'enhancetool',
'cleancache',
'vertex-gemini',
'chutes-glm',
'qwen-cli',
'rovo-cli'
];
function getConfigPath(): string {
const configDir = path.join(process.env.HOME || process.env.USERPROFILE || '', '.claude-code-router');
const configPath = path.join(configDir, 'config.json');
if (!fs.existsSync(configPath)) {
throw new Error(`config.json not found at ${configPath}`);
}
return configPath;
}
function loadConfig(): Config {
const configPath = getConfigPath();
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
}
function saveConfig(config: Config): void {
const configPath = getConfigPath();
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
console.log(`${GREEN}✓ config.json updated successfully${RESET}\n`);
}
function getAllModels(config: Config) {
const models: any[] = [];
for (const provider of config.Providers) {
for (const model of provider.models) {
models.push({
name: `${BOLDCYAN}${provider.name}${RESET}${CYAN} ${model}`,
value: `${provider.name},${model}`,
description: `\n${BOLDCYAN}Provider:${RESET} ${provider.name}`,
provider: provider.name,
model: model
});
}
}
return models;
}
function displayCurrentConfig(config: Config): void {
console.log(`\n${BOLDCYAN}═══════════════════════════════════════════════${RESET}`);
console.log(`${BOLDCYAN} Current Configuration${RESET}`);
console.log(`${BOLDCYAN}═══════════════════════════════════════════════${RESET}\n`);
const formatModel = (routerValue?: string | number) => {
if (!routerValue || typeof routerValue === 'number') {
return `${DIM}Not configured${RESET}`;
}
const [provider, model] = routerValue.split(',');
return `${YELLOW}${provider}${RESET} | ${model}\n ${DIM}- ${routerValue}${RESET}`;
};
console.log(`${BOLDCYAN}Default Model:${RESET}`);
console.log(` ${formatModel(config.Router.default)}\n`);
if (config.Router.background) {
console.log(`${BOLDCYAN}Background Model:${RESET}`);
console.log(` ${formatModel(config.Router.background)}\n`);
}
if (config.Router.think) {
console.log(`${BOLDCYAN}Think Model:${RESET}`);
console.log(` ${formatModel(config.Router.think)}\n`);
}
if (config.Router.longContext) {
console.log(`${BOLDCYAN}Long Context Model:${RESET}`);
console.log(` ${formatModel(config.Router.longContext)}\n`);
}
if (config.Router.webSearch) {
console.log(`${BOLDCYAN}Web Search Model:${RESET}`);
console.log(` ${formatModel(config.Router.webSearch)}\n`);
}
if (config.Router.image) {
console.log(`${BOLDCYAN}Image Model:${RESET}`);
console.log(` ${formatModel(config.Router.image)}\n`);
}
console.log(`\n${BOLDCYAN}═══════════════════════════════════════════════${RESET}`);
console.log(`${BOLDCYAN} Add/Update Model${RESET}`);
console.log(`${BOLDCYAN}═══════════════════════════════════════════════${RESET}\n`);
}
async function selectModelType() {
return await select({
message: `${BOLDYELLOW}Which model configuration do you want to update?${RESET}`,
choices: [
{ name: 'Default Model', value: 'default' },
{ name: 'Background Model', value: 'background' },
{ name: 'Think Model', value: 'think' },
{ name: 'Long Context Model', value: 'longContext' },
{ name: 'Web Search Model', value: 'webSearch' },
{ name: 'Image Model', value: 'image' },
{ name: `${BOLDGREEN}+ Add New Model${RESET}`, value: 'addModel' }
]
});
}
async function selectModel(config: Config, modelType: string) {
const models = getAllModels(config);
return await select({
message: `\n${BOLDYELLOW}Select a model for ${modelType}:${RESET}`,
choices: models,
pageSize: 15
});
}
async function configureTransformers(): Promise<TransformerConfig | undefined> {
const useTransformers = await confirm({
message: `\n${BOLDYELLOW}Add transformer configuration?${RESET}`,
default: false
});
if (!useTransformers) {
return undefined;
}
const transformers: Array<string | [string, any]> = [];
let addMore = true;
while (addMore) {
const transformer = await select({
message: `\n${BOLDYELLOW}Select a transformer:${RESET}`,
choices: AVAILABLE_TRANSFORMERS.map(t => ({ name: t, value: t })),
pageSize: 15
}) as string;
// Check if transformer needs options
if (transformer === 'maxtoken') {
const maxTokens = await input({
message: `\n${BOLDYELLOW}Max tokens:${RESET}`,
default: '30000',
validate: (value: string) => {
const num = parseInt(value);
if (isNaN(num) || num <= 0) {
return 'Please enter a valid positive number';
}
return true;
}
});
transformers.push(['maxtoken', { max_tokens: parseInt(maxTokens) }]);
} else if (transformer === 'openrouter') {
const addProvider = await confirm({
message: `\n${BOLDYELLOW}Add provider routing options?${RESET}`,
default: false
});
if (addProvider) {
const providerInput = await input({
message: 'Provider (e.g., moonshotai/fp8):',
validate: (value: string) => value.trim() !== '' || 'Provider cannot be empty'
});
transformers.push(['openrouter', { provider: { only: [providerInput] } }]);
} else {
transformers.push(transformer);
}
} else {
transformers.push(transformer);
}
addMore = await confirm({
message: `\n${BOLDYELLOW}Add another transformer?${RESET}`,
default: false
});
}
return { use: transformers };
}
async function addNewModel(config: Config): Promise<ModelResult | null> {
const providerChoices = config.Providers.map(p => ({
name: p.name,
value: p.name
}));
providerChoices.push({ name: `${BOLDGREEN}+ Add New Provider${RESET}`, value: '__new__' });
const selectedProvider = await select({
message: `\n${BOLDYELLOW}Select provider for the new model:${RESET}`,
choices: providerChoices
}) as string;
if (selectedProvider === '__new__') {
return await addNewProvider(config);
} else {
return await addModelToExistingProvider(config, selectedProvider);
}
}
async function addModelToExistingProvider(config: Config, providerName: string): Promise<ModelResult | null> {
const modelName = await input({
message: `\n${BOLDYELLOW}Enter the model name:${RESET}`,
validate: (value: string) => {
if (!value.trim()) {
return 'Model name cannot be empty';
}
return true;
}
});
const provider = config.Providers.find(p => p.name === providerName);
if (!provider) {
console.log(`${YELLOW}Provider not found${RESET}`);
return null;
}
if (provider.models.includes(modelName)) {
console.log(`${YELLOW}Model already exists in provider${RESET}`);
return null;
}
provider.models.push(modelName);
// Ask about model-specific transformers
const addModelTransformer = await confirm({
message: `\n${BOLDYELLOW}Add model-specific transformer configuration?${RESET}`,
default: false
});
if (addModelTransformer) {
const transformerConfig = await configureTransformers();
if (transformerConfig && provider.transformer) {
provider.transformer[modelName] = transformerConfig;
}
}
saveConfig(config);
console.log(`${GREEN}✓ Model "${modelName}" added to provider "${providerName}"${RESET}`);
const setAsDefault = await confirm({
message: `\n${BOLDYELLOW}Do you want to set this model in router configuration?${RESET}`,
default: false
});
if (setAsDefault) {
const modelType = await select({
message: `\n${BOLDYELLOW}Select configuration type:${RESET}`,
choices: [
{ name: 'Default Model', value: 'default' },
{ name: 'Background Model', value: 'background' },
{ name: 'Think Model', value: 'think' },
{ name: 'Long Context Model', value: 'longContext' },
{ name: 'Web Search Model', value: 'webSearch' },
{ name: 'Image Model', value: 'image' }
]
}) as string;
return { providerName, modelName, modelType };
}
return null;
}
async function addNewProvider(config: Config): Promise<ModelResult | null> {
console.log(`\n${BOLDCYAN}Adding New Provider${RESET}\n`);
const providerName = await input({
message: `${BOLDYELLOW}Provider name:${RESET}`,
validate: (value: string) => {
if (!value.trim()) {
return 'Provider name cannot be empty';
}
if (config.Providers.some(p => p.name === value)) {
return 'Provider already exists';
}
return true;
}
});
const apiBaseUrl = await input({
message: `\n${BOLDYELLOW}API base URL:${RESET}`,
validate: (value: string) => {
if (!value.trim()) {
return 'API base URL cannot be empty';
}
try {
new URL(value);
return true;
} catch {
return 'Please enter a valid URL';
}
}
});
const apiKey = await input({
message: `\n${BOLDYELLOW}API key:${RESET}`,
validate: (value: string) => {
if (!value.trim()) {
return 'API key cannot be empty';
}
return true;
}
});
const modelsInput = await input({
message: `\n${BOLDYELLOW}Model names (comma-separated):${RESET}`,
validate: (value: string) => {
if (!value.trim()) {
return 'At least one model name is required';
}
return true;
}
});
const models = modelsInput.split(',').map(m => m.trim()).filter(m => m);
const newProvider: Provider = {
name: providerName,
api_base_url: apiBaseUrl,
api_key: apiKey,
models: models
};
// Global transformer configuration
const transformerConfig = await configureTransformers();
if (transformerConfig) {
newProvider.transformer = transformerConfig;
}
config.Providers.push(newProvider);
saveConfig(config);
console.log(`${GREEN}\n✓ Provider "${providerName}" added successfully${RESET}`);
const setAsDefault = await confirm({
message: `\n${BOLDYELLOW}Do you want to set one of these models in router configuration?${RESET}`,
default: false
});
if (setAsDefault && models.length > 0) {
let selectedModel = models[0];
if (models.length > 1) {
selectedModel = await select({
message: `\n${BOLDYELLOW}Select which model to configure:${RESET}`,
choices: models.map(m => ({ name: m, value: m }))
}) as string;
}
const modelType = await select({
message: `\n${BOLDYELLOW}Select configuration type:${RESET}`,
choices: [
{ name: 'Default Model', value: 'default' },
{ name: 'Background Model', value: 'background' },
{ name: 'Think Model', value: 'think' },
{ name: 'Long Context Model', value: 'longContext' },
{ name: 'Web Search Model', value: 'webSearch' },
{ name: 'Image Model', value: 'image' }
]
}) as string;
return { providerName, modelName: selectedModel, modelType };
}
return null;
}
export async function runModelSelector(): Promise<void> {
console.clear();
try {
let config = loadConfig();
displayCurrentConfig(config);
const action = await selectModelType() as string;
if (action === 'addModel') {
const result = await addNewModel(config);
if (result) {
config = loadConfig();
config.Router[result.modelType] = `${result.providerName},${result.modelName}`;
saveConfig(config);
console.log(`${GREEN}${result.modelType} set to ${result.providerName},${result.modelName}${RESET}`);
}
} else {
const selectedModel = await selectModel(config, action) as string;
config.Router[action] = selectedModel;
saveConfig(config);
console.log(`${GREEN}${action} model updated to: ${selectedModel}${RESET}`);
}
displayCurrentConfig(config);
} catch (error: any) {
console.error(`${YELLOW}Error:${RESET}`, error.message);
process.exit(1);
}
}

139
ui/package-lock.json generated
View File

@@ -14,6 +14,7 @@
"@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.7",
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
@@ -1181,6 +1182,32 @@
} }
} }
}, },
"node_modules/@radix-ui/react-collection": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
"integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-slot": "1.2.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-compose-refs": { "node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
@@ -1247,6 +1274,21 @@
} }
} }
}, },
"node_modules/@radix-ui/react-direction": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
"integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dismissable-layer": { "node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.10", "version": "1.1.10",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz",
@@ -1495,6 +1537,43 @@
} }
} }
}, },
"node_modules/@radix-ui/react-roving-focus": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
"integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-collection": "1.1.7",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-direction": "1.1.1",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/primitive": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
"license": "MIT"
},
"node_modules/@radix-ui/react-slot": { "node_modules/@radix-ui/react-slot": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
@@ -1542,6 +1621,66 @@
} }
} }
}, },
"node_modules/@radix-ui/react-tabs": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz",
"integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-direction": "1.1.1",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-roving-focus": "1.1.11",
"@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/primitive": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
"license": "MIT"
},
"node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
"integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-tooltip": { "node_modules/@radix-ui/react-tooltip": {
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz",

View File

@@ -1 +1 @@
{"root":["./src/app.tsx","./src/i18n.ts","./src/main.tsx","./src/routes.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/configprovider.tsx","./src/components/debugpage.tsx","./src/components/jsoneditor.tsx","./src/components/logviewer.tsx","./src/components/login.tsx","./src/components/protectedroute.tsx","./src/components/providerlist.tsx","./src/components/providers.tsx","./src/components/publicroute.tsx","./src/components/requesthistorydrawer.tsx","./src/components/router.tsx","./src/components/settingsdialog.tsx","./src/components/statuslineconfigdialog.tsx","./src/components/statuslineimportexport.tsx","./src/components/transformerlist.tsx","./src/components/transformers.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/color-picker.tsx","./src/components/ui/combo-input.tsx","./src/components/ui/combobox.tsx","./src/components/ui/command.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/multi-combobox.tsx","./src/components/ui/popover.tsx","./src/components/ui/switch.tsx","./src/components/ui/tabs.tsx","./src/components/ui/toast.tsx","./src/lib/api.ts","./src/lib/db.ts","./src/lib/utils.ts","./src/utils/statusline.ts"],"version":"5.8.3"} {"root":["./src/App.tsx","./src/i18n.ts","./src/main.tsx","./src/routes.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/ConfigProvider.tsx","./src/components/DebugPage.tsx","./src/components/JsonEditor.tsx","./src/components/LogViewer.tsx","./src/components/Login.tsx","./src/components/ProtectedRoute.tsx","./src/components/ProviderList.tsx","./src/components/Providers.tsx","./src/components/PublicRoute.tsx","./src/components/RequestHistoryDrawer.tsx","./src/components/Router.tsx","./src/components/SettingsDialog.tsx","./src/components/StatusLineConfigDialog.tsx","./src/components/StatusLineImportExport.tsx","./src/components/TransformerList.tsx","./src/components/Transformers.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/color-picker.tsx","./src/components/ui/combo-input.tsx","./src/components/ui/combobox.tsx","./src/components/ui/command.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/multi-combobox.tsx","./src/components/ui/popover.tsx","./src/components/ui/switch.tsx","./src/components/ui/tabs.tsx","./src/components/ui/toast.tsx","./src/lib/api.ts","./src/lib/db.ts","./src/lib/utils.ts","./src/utils/statusline.ts"],"version":"5.8.3"}