diff --git a/bun.lock b/bun.lock index 3193a09dc..2e091fe0b 100644 --- a/bun.lock +++ b/bun.lock @@ -5,19 +5,17 @@ "": { "name": "zero", "dependencies": { + "@opentui/core": "^0.2.16", + "@opentui/solid": "^0.2.16", "commander": "^15.0.0", "execa": "^9.6.1", - "ink": "^7.0.4", - "ink-spinner": "^5.0.0", - "ink-text-input": "^6.0.0", "openai": "^6.39.1", - "react": "^19.2.6", "shiki": "^4.1.0", + "solid-js": "1.9.12", "zod": "^4.4.3", }, "devDependencies": { "@types/bun": "latest", - "@types/react": "^19.2.15", }, "peerDependencies": { "typescript": "^5", @@ -25,7 +23,87 @@ }, }, "packages": { - "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.3.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA=="], + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="], + + "@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], + + "@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="], + + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], + + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], + + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="], + + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="], + + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="], + + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], + + "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], + + "@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A=="], + + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA=="], + + "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ=="], + + "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-syntax-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw=="], + + "@babel/preset-typescript": ["@babel/preset-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ=="], + + "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], + + "@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="], + + "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@opentui/core": ["@opentui/core@0.2.16", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.16", "@opentui/core-darwin-x64": "0.2.16", "@opentui/core-linux-arm64": "0.2.16", "@opentui/core-linux-x64": "0.2.16", "@opentui/core-win32-arm64": "0.2.16", "@opentui/core-win32-x64": "0.2.16" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-4vWN15Zc3nsXJlOiHhhpqkBXD+wrNFKxCPtiTiillZYDRre+XsZogVTOOGUDwaBIC23OSxq7imezLmmtShVBEA=="], + + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aFb2Yp+oqDu3h6VCWi7xpQ9yjpKSQcROzGGfHgqC6Nd3U+uiLfPJBkmiI87iK0opCggCFj5TkKI004050DmGjg=="], + + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-KimiHE0j7EsTB5P8doW0lr1eH5iZKLPKWQO+tmy1VcdYr/TzqhdHSvGuJXrZvfTFi9/rV57Eq0d7964Ri9O0vQ=="], + + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-4fCwRCfTtUgS/5QcSEkSuBjgQymSOUWXgrXG2ycrf3Swi0QhKDA/pVjwLrUJ6eF+/8mQyQSEV72T8MxMO3M2qg=="], + + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.16", "", { "os": "linux", "cpu": "x64" }, "sha512-KgQBGjiucw4e7gM+R8qOzHWBFhjCY1IfCrGjW3Wzxv2hKUlL+mPhelaeJwnEqtNxMUdVTYjlwlu3IHxslXMJWQ=="], + + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-C6WqEI3VkXatXraMgSFXZjEXq0pzURGjRpFAJZYmuVDmpqE57o7E80Np2UkdZ6m5kpJDt4mRyu3krc/P825iNQ=="], + + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.16", "", { "os": "win32", "cpu": "x64" }, "sha512-kCX3CMTns6DMCFDNTDV4sjmBKyA/iEvzaVhl/jYi4JRIVT2zcy1lo+lhXT5mPgYHmJZu8Uye6j3Zi3c7Z2Me5A=="], + + "@opentui/solid": ["@opentui/solid@0.2.16", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.16", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-2Q+v1PPpXXr+sALi9Aj6I5Jvo7xDfbmstYjRLL7lW3Hghh9i7ONQKpt/gyDDRbhSsYrhxKYTNenF9OxgoXkTHg=="], "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], @@ -55,95 +133,119 @@ "@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], - "@types/react": ["@types/react@19.2.15", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q=="], - "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.1", "", {}, "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ=="], - "ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "babel-plugin-jsx-dom-expressions": ["babel-plugin-jsx-dom-expressions@0.40.7", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-/O6JWUmjv03OI9lL2ry9bUjpD5S3PclM55RRJEyCdcFZ5W2SEA/59d+l2hNsk3gI6kiWRdRPdOtqZmsQzFN1pQ=="], - "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], + "babel-plugin-module-resolver": ["babel-plugin-module-resolver@5.0.2", "", { "dependencies": { "find-babel-config": "^2.1.1", "glob": "^9.3.3", "pkg-up": "^3.1.0", "reselect": "^4.1.7", "resolve": "^1.22.8" } }, "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg=="], - "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + "babel-preset-solid": ["babel-preset-solid@1.9.12", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.6" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.12" }, "optionalPeers": ["solid-js"] }, "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg=="], - "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.33", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw=="], - "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + "brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], - "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + + "bun-ffi-structs": ["bun-ffi-structs@0.2.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-N/ZWtyN0piZlrXQT7TO0V+q952orYqkfhXRXM1Hcbb+R3QSiBH4vLnib187Mrs1H7pWIYECAmPeapGYDOMCl+w=="], - "cli-boxes": ["cli-boxes@4.0.1", "", {}, "sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], - "cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="], + "caniuse-lite": ["caniuse-lite@1.0.30001793", "", {}, "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA=="], - "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], - "cli-truncate": ["cli-truncate@6.0.0", "", { "dependencies": { "slice-ansi": "^9.0.0", "string-width": "^8.2.0" } }, "sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA=="], + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], - "code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="], + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], "commander": ["commander@15.0.0", "", {}, "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg=="], - "convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], - "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + "diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.364", "", {}, "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw=="], + + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - "es-toolkit": ["es-toolkit@1.47.0", "", {}, "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw=="], + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - "escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + "find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="], + + "find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + "glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], + + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + "html-entities": ["html-entities@2.3.3", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="], + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], - "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], - "ink": ["ink@7.0.4", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.3.0", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.3", "auto-bind": "^5.0.1", "chalk": "^5.6.2", "cli-boxes": "^4.0.1", "cli-cursor": "^4.0.0", "cli-truncate": "^6.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.45.1", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^9.0.0", "stack-utils": "^2.0.6", "string-width": "^8.2.0", "terminal-size": "^4.0.1", "type-fest": "^5.5.0", "widest-line": "^6.0.0", "wrap-ansi": "^10.0.0", "ws": "^8.20.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.2.0", "react": ">=19.2.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-4wsM/gMKOT2ZANNTJibI6I9IcwBfobqv/CgaDcwvOaCREZIQxo3iGQS7qPHa2hmA67NYltZWCMtBDELB/mcbJQ=="], + "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], - "ink-spinner": ["ink-spinner@5.0.0", "", { "dependencies": { "cli-spinners": "^2.7.0" }, "peerDependencies": { "ink": ">=4.0.0", "react": ">=18.0.0" } }, "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA=="], + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], - "ink-text-input": ["ink-text-input@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "type-fest": "^4.18.2" }, "peerDependencies": { "ink": ">=5", "react": ">=18" } }, "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], + "locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="], - "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="], "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], @@ -157,11 +259,15 @@ "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], - "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + "minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="], - "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], + "minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + "node-releases": ["node-releases@2.0.46", "", {}, "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ=="], + + "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], "oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="], @@ -169,19 +275,31 @@ "openai": ["openai@6.39.1", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-z3dO9fEWOXBzlXynVb/xZ/tujzUjFWQWn3C0n0mw6Vo0zJTbEkaN4b2cLWjhJ6haJQx8LlREoafHRl+Gu/Hl+A=="], + "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], - "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "react": ["react@19.2.6", "", {}, "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q=="], + "pkg-up": ["pkg-up@3.1.0", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA=="], - "react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], + "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], + + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], @@ -189,9 +307,17 @@ "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], - "restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="], + "reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="], + + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + + "s-js": ["s-js@0.4.9", "", {}, "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "seroval": ["seroval@1.5.4", "", {}, "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw=="], - "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + "seroval-plugins": ["seroval-plugins@1.5.4", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -201,28 +327,22 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "slice-ansi": ["slice-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA=="], + "solid-js": ["solid-js@1.9.12", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw=="], "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], - "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], - - "string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="], + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], - "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], - "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], - - "terminal-size": ["terminal-size@4.0.1", "", {}, "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ=="], + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], - "type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], @@ -239,18 +359,20 @@ "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="], + "web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="], - "wrap-ansi": ["wrap-ansi@10.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "string-width": "^8.2.0", "strip-ansi": "^7.1.2" } }, "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], @@ -259,12 +381,16 @@ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - "ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "ink-text-input/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], - "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], } } diff --git a/package.json b/package.json index 064e0636a..37ed2ec8a 100644 --- a/package.json +++ b/package.json @@ -7,26 +7,24 @@ "zero": "src/index.ts" }, "scripts": { - "dev": "bun run src/index.ts", + "dev": "bun --preload @opentui/solid/preload src/index.ts", "test": "bun test ./tests --timeout 15000", - "build": "bun build src/index.ts --compile --outfile zero" + "build": "bun run scripts/build.ts" }, "devDependencies": { - "@types/bun": "latest", - "@types/react": "^19.2.15" + "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" }, "dependencies": { + "@opentui/core": "^0.2.16", + "@opentui/solid": "^0.2.16", "commander": "^15.0.0", "execa": "^9.6.1", - "ink": "^7.0.4", - "ink-spinner": "^5.0.0", - "ink-text-input": "^6.0.0", "openai": "^6.39.1", - "react": "^19.2.6", "shiki": "^4.1.0", + "solid-js": "1.9.12", "zod": "^4.4.3" } } diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 000000000..3242f4a6c --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,16 @@ +#!/usr/bin/env bun +import solidPlugin from '@opentui/solid/bun-plugin'; + +const result = await Bun.build({ + entrypoints: ['./src/index.ts'], + target: 'bun', + plugins: [solidPlugin], + outdir: './dist', +}); + +if (!result.success) { + for (const log of result.logs) { + console.error(log); + } + process.exit(1); +} diff --git a/src/agent/loop.ts b/src/agent/loop.ts index 7a37139df..cf5b9f9a3 100644 --- a/src/agent/loop.ts +++ b/src/agent/loop.ts @@ -2,7 +2,7 @@ import type { Provider } from '../providers/types'; import type { ToolCall, ToolResult } from '../tools/types'; import { toolRegistry } from '../tools'; import { DEFAULT_SYSTEM_PROMPT, PLAN_MODE_SYSTEM_PROMPT } from './prompts'; -import { clearPlan } from '../tools/plan'; +import { clearPlan, getCurrentPlan, type PlanItem } from '../tools/plan'; import { z } from 'zod'; export interface AgentOptions { @@ -10,9 +10,11 @@ export interface AgentOptions { onText?: (text: string) => void; onToolCall?: (toolCall: ToolCall) => void; onToolResult?: (result: ToolResult) => void; - toolsEnabled?: boolean; // allows temporarily disabling tool calling for debugging - debug?: boolean; // when true, logs the exact payload sent to the provider - planMode?: boolean; // when true, the agent plans without modifying the codebase + onUsage?: (usage: { promptTokens: number; completionTokens: number }) => void; + onPlanUpdate?: (plan: PlanItem[]) => void; + toolsEnabled?: boolean; + debug?: boolean; + planMode?: boolean; } interface PendingToolCall { @@ -26,18 +28,21 @@ export async function runAgent( provider: Provider, options: AgentOptions = {} ): Promise { - const { - maxTurns = 12, - onText, - onToolCall, + const { + maxTurns = 12, + onText, + onToolCall, onToolResult, + onUsage, + onPlanUpdate, toolsEnabled = true, debug = false, - planMode = false + planMode = false, } = options; // Clear any previous plan when starting a new task clearPlan(); + onPlanUpdate?.(getCurrentPlan()); const systemPrompt = planMode ? PLAN_MODE_SYSTEM_PROMPT : DEFAULT_SYSTEM_PROMPT; @@ -52,16 +57,9 @@ export async function runAgent( for (let turn = 0; turn < maxTurns; turn++) { const toolDefinitions = (toolsEnabled && tools.length > 0) ? tools.map(t => { - // Convert Zod schema to proper JSON Schema (critical for many providers). - // zod v4 ships this natively — no external package needed. - const jsonSchema = z.toJSONSchema(t.parameters, { - target: 'draft-7', - }) as any; - - // Remove $schema if present (some providers dislike it) + const jsonSchema = z.toJSONSchema(t.parameters, { target: 'draft-7' }) as any; delete jsonSchema.$schema; - // Make it strict by default (good practice) if (jsonSchema.type === 'object' && !('additionalProperties' in jsonSchema)) { jsonSchema.additionalProperties = false; } @@ -80,30 +78,21 @@ export async function runAgent( if (debug) { const red = '\x1b[31m'; const reset = '\x1b[0m'; - const border = '─'.repeat(50); - - console.log(`\n${red}┌${border}┐`); - console.log(`│ SENDING TO PROVIDER${' '.repeat(31)}│`); - console.log(`├${border}┤`); - console.log(`│ Messages: ${messages.length}${' '.repeat(40 - String(messages.length).length)}│`); - console.log(`│ Tools enabled: ${toolDefinitions.length > 0}${' '.repeat(33)}│`); - console.log(`│ Tool count: ${toolDefinitions.length}${' '.repeat(38 - String(toolDefinitions.length).length)}│`); - - if (toolDefinitions.length > 0) { - const toolsList = toolDefinitions.map(t => t.name).join(', '); - console.log(`│ Tools: ${toolsList.slice(0, 42)}${' '.repeat(Math.max(0, 43 - toolsList.length))}│`); - - // Show a sample of the schema for the first tool (very useful for debugging) - const firstTool = toolDefinitions[0]; - if (firstTool.parameters) { - const schemaPreview = JSON.stringify(firstTool.parameters, null, 2).slice(0, 300); - console.log(`│ First tool schema sample:\n${schemaPreview}...`); - } + const border = '-'.repeat(50); + const toolsList = toolDefinitions.map(t => t.name).join(', '); + const preview = String(messages[messages.length - 1]?.content || '').slice(0, 45); + + console.log(`\n${red}+${border}+`); + console.log(`| SENDING TO PROVIDER${' '.repeat(31)}|`); + console.log(`+${border}+`); + console.log(`| Messages: ${messages.length}${' '.repeat(Math.max(0, 40 - String(messages.length).length))}|`); + console.log(`| Tools enabled: ${toolDefinitions.length > 0}${' '.repeat(33)}|`); + console.log(`| Tool count: ${toolDefinitions.length}${' '.repeat(Math.max(0, 38 - String(toolDefinitions.length).length))}|`); + if (toolsList) { + console.log(`| Tools: ${toolsList.slice(0, 42)}${' '.repeat(Math.max(0, 43 - toolsList.length))}|`); } - - const preview = String(messages[messages.length-1]?.content || '').slice(0, 45); - console.log(`│ Last message: ${preview}${' '.repeat(Math.max(0, 36 - preview.length))}│`); - console.log(`└${border}┘${reset}\n`); + console.log(`| Last message: ${preview}${' '.repeat(Math.max(0, 36 - preview.length))}|`); + console.log(`+${border}+${reset}\n`); } // Stream the response @@ -113,6 +102,13 @@ export async function runAgent( if (onText) onText(event.content); } + if (event.type === 'usage') { + onUsage?.({ + promptTokens: event.promptTokens, + completionTokens: event.completionTokens, + }); + } + if (event.type === 'tool-call-start') { toolCallMap.set(event.id, { id: event.id, @@ -189,6 +185,11 @@ export async function runAgent( onToolResult({ toolCallId: tc.id, result }); } + const effects = tool?.onAfterExecute?.(result); + if (effects?.planUpdated) { + onPlanUpdate?.(getCurrentPlan()); + } + return { toolCallId: tc.id, result }; }); diff --git a/src/index.ts b/src/index.ts index 47df5ffa7..4529d473d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,21 @@ import { Command } from 'commander'; +import { ensureSolidTransformPlugin } from '@opentui/solid/bun-plugin'; +import packageJson from '../package.json'; import { runHeadless } from './cli'; import { configManager } from './config/manager'; -import { startTUI } from './tui'; const program = new Command(); +async function startInteractiveTUI() { + ensureSolidTransformPlugin(); + const { startTUI } = await import('./tui'); + startTUI(); +} + program .name('zero') .description('A clean terminal AI coding agent') - .version('0.1.0'); + .version(packageJson.version); program .option('-p, --prompt ', 'Run in headless mode with the given prompt') @@ -17,7 +24,7 @@ program await runHeadless(options.prompt); } else { // Launch the interactive TUI (Grok Build style) - startTUI(); + await startInteractiveTUI(); } }); diff --git a/src/providers/openai.ts b/src/providers/openai.ts index 377e771b0..02f6128f9 100644 --- a/src/providers/openai.ts +++ b/src/providers/openai.ts @@ -7,6 +7,28 @@ interface OpenAIProviderOptions { model: string; } +function isUnsupportedStreamOptionsError(error: unknown): boolean { + const candidate = error as { + status?: number; + code?: string; + message?: string; + error?: { + code?: string; + message?: string; + }; + }; + + const statusAllowsRetry = candidate.status === 400 || candidate.status === 422; + const errorText = [ + candidate.code, + candidate.message, + candidate.error?.code, + candidate.error?.message, + ].filter(Boolean).join(' ').toLowerCase(); + + return statusAllowsRetry && /stream[ _-]?options?/.test(errorText); +} + export class OpenAIProvider implements Provider { private client: OpenAI; private model: string; @@ -48,23 +70,31 @@ export class OpenAIProvider implements Provider { })) : undefined; + const createStreamRequest = (includeUsage: boolean) => ({ + model: this.model, + messages: openaiMessages as any, + tools: openaiTools, + stream: true as const, + ...(includeUsage ? { + stream_options: { + include_usage: true, + }, + } : {}), + }); + let stream; try { - stream = await this.client.chat.completions.create({ - model: this.model, - messages: openaiMessages as any, - tools: openaiTools, - stream: true, - }); - } catch (err: any) { - const message = getDetailedErrorMessage(err); - if (message.includes('401') || message.toLowerCase().includes('invalid') || message.toLowerCase().includes('unauthorized')) { - throw new Error(`Provider authentication error (check your API key): ${message}`); - } - if (message.toLowerCase().includes('rate') || message.toLowerCase().includes('quota')) { - throw new Error(`Provider rate limit error: ${message}`); + stream = await this.client.chat.completions.create(createStreamRequest(true)); + } catch (error) { + if (isUnsupportedStreamOptionsError(error)) { + try { + stream = await this.client.chat.completions.create(createStreamRequest(false)); + } catch (retryError) { + throw formatProviderCreateError(retryError); + } + } else { + throw formatProviderCreateError(error); } - throw new Error(`Provider returned error: ${message}`); } const toolCallAccumulators = new Map; // Zod schema for validation execute: (args: any) => Promise; // Returns tool result as string + onAfterExecute?: (result: string) => ToolExecutionEffects | void; } export interface ToolCall { diff --git a/src/tui/AddProvider.tsx b/src/tui/AddProvider.tsx deleted file mode 100644 index 2140a689c..000000000 --- a/src/tui/AddProvider.tsx +++ /dev/null @@ -1,275 +0,0 @@ -import React, { useState } from 'react'; -import { Box, Text, useInput } from 'ink'; -import TextInput from 'ink-text-input'; -import { configManager } from '../config/manager'; - -type AddMode = 'choose' | 'opengateway' | 'generic'; - -interface AddProviderProps { - onDone: (providerName?: string) => void; - onCancel: () => void; -} - -export const AddProvider: React.FC = ({ onDone, onCancel }) => { - const [mode, setMode] = useState('choose'); - - // For the choose menu - const [selectedOption, setSelectedOption] = useState(0); // 0 = OpenGateway, 1 = Generic - - // For the OpenGateway form (step based) - const [ogwFormStep, setOgwFormStep] = useState(0); // 0 = API Key, 1 = Model, 2 = Done - - // OpenGateway fields - const [ogwKey, setOgwKey] = useState(''); - const [ogwModel, setOgwModel] = useState('mimo-v2.5-pro'); - - // Generic fields - const [name, setName] = useState(''); - const [baseURL, setBaseURL] = useState('https://api.openai.com/v1'); - const [apiKey, setApiKey] = useState(''); - const [model, setModel] = useState('gpt-4o'); - - const [error, setError] = useState(''); - const [success, setSuccess] = useState(false); - - useInput((input, key) => { - if (key.escape) { - if (mode === 'choose') { - onCancel(); - } else { - // Reset states when going back - setMode('choose'); - setOgwFormStep(0); - setError(''); - setSuccess(false); - setSelectedOption(0); - } - return; - } - - // Arrow key + Enter navigation for the choose menu - if (mode === 'choose') { - if (key.upArrow) { - setSelectedOption((prev) => Math.max(0, prev - 1)); - return; - } - if (key.downArrow) { - setSelectedOption((prev) => Math.min(1, prev + 1)); - return; - } - if (key.return) { - if (selectedOption === 0) { - setMode('opengateway'); - setOgwFormStep(0); - } else { - setMode('generic'); - } - return; - } - - // Still support numbers for convenience - if (input === '1') { - setMode('opengateway'); - setOgwFormStep(0); - } - if (input === '2') { - setMode('generic'); - } - } - }); - - const saveOpenGateway = () => { - if (!ogwKey.trim()) { - setError('API key is required'); - return; - } - - const profileName = 'opengateway'; - - configManager.addProvider({ - name: profileName, - baseURL: 'https://opengateway.gitlawb.com/v1', - apiKey: ogwKey.trim(), - model: ogwModel.trim(), - description: 'OpenGateway', - }); - - setSuccess(true); - - // Auto-close after a short delay so the user sees the success message - setTimeout(() => { - onDone(profileName); - }, 1200); - }; - - const saveGeneric = () => { - if (!name.trim() || !baseURL.trim() || !model.trim()) { - setError('Name, Base URL, and Model are required'); - return; - } - - configManager.addProvider({ - name: name.trim(), - baseURL: baseURL.trim(), - apiKey: apiKey.trim() || undefined, - model: model.trim(), - description: 'Custom OpenAI-compatible', - }); - - setSuccess(true); - setTimeout(() => onDone(name.trim()), 1200); - }; - - if (mode === 'choose') { - return ( - - Add New Provider - Esc to go back • ↑↓ to navigate • Enter to select - - - - {selectedOption === 0 ? '› ' : ' '}1. Add OpenGateway (recommended) - - {selectedOption === 0 && ( - - {' '}You'll be asked for your ogw_live_... API key - - )} - - - {selectedOption === 1 ? '› ' : ' '}2. Add custom OpenAI-compatible provider - - {selectedOption === 1 && ( - - {' '}For Groq, OpenAI, Ollama, etc. - - )} - - - ); - } - - if (mode === 'opengateway') { - if (success) { - return ( - - - ✓ OpenGateway provider added successfully! - - - It is now your active provider. - - - ); - } - - return ( - - Add OpenGateway Provider - Esc to go back - - {ogwFormStep === 0 && ( - - Step 1/2 — Enter your OpenGateway API key - - You can get one at https://opengateway.gitlawb.com - - - API Key: - - - - Press Enter to continue - - {}} - onSubmit={() => { - if (ogwKey.trim()) { - setOgwFormStep(1); - setError(''); - } else { - setError('API key cannot be empty'); - } - }} - /> - - )} - - {ogwFormStep === 1 && ( - - Step 2/2 — Model name - - Model: - - - {error && ⚠ {error}} - - Press Enter to save - - {}} - onSubmit={saveOpenGateway} - /> - - )} - - ); - } - - if (mode === 'generic') { - if (success) { - return ( - - - ✓ Provider added successfully! - - - ); - } - - return ( - - Add Custom Provider - Esc to go back - - - Name: - - - - Base URL: - - - - API Key: - - - - Model: - - - - {error && {error}} - - - Press Enter to save - - - {}} - onSubmit={saveGeneric} - /> - - ); - } - - return null; -}; diff --git a/src/tui/App.tsx b/src/tui/App.tsx index 9c97b6533..e202e027c 100644 --- a/src/tui/App.tsx +++ b/src/tui/App.tsx @@ -1,22 +1,44 @@ -import React, { useState } from 'react'; -import { Box, Text, useApp, useInput } from 'ink'; -import { ProviderPicker } from './ProviderPicker'; -import { AddProvider } from './AddProvider'; -import { Logo } from './Logo'; -import { ThinkingSpinner } from './Spinner'; -import { MessageRenderer } from './MessageRenderer'; -import { ToolCallRenderer } from './ToolCallRenderer'; +/** @jsxImportSource @opentui/solid */ +import { TextAttributes, type KeyEvent, type MouseEvent, type ScrollBoxRenderable } from '@opentui/core'; +import { useKeyboard, usePaste, useTerminalDimensions } from '@opentui/solid'; +import { For, Match, Show, Switch, createEffect, createMemo, createSignal, onCleanup, onMount, type JSX } from 'solid-js'; +import packageJson from '../../package.json'; import { configManager } from '../config/manager'; import { loadProviderConfig } from '../config/provider'; -import { OpenAIProvider } from '../providers/openai'; +import type { ProviderProfile } from '../config/types'; import { runAgent } from '../agent/loop'; +import { OpenAIProvider } from '../providers/openai'; +import type { PlanItem } from '../tools/plan'; +import { highlightCode, type HighlightedLine } from './highlighter'; + +type Screen = 'chat' | 'providers' | 'add-provider'; + +type ChatMessage = + | { type: 'user'; content: string } + | { type: 'assistant'; content: string; streaming?: boolean } + | { type: 'tool-call'; name: string; args: string; result?: string } + | { type: 'system'; content: string }; + +type Usage = { + promptTokens: number; + completionTokens: number; +}; -type Screen = 'chat' | 'provider-picker' | 'add-provider'; +type AddProviderDraft = { + name: string; + baseURL: string; + apiKey: string; + model: string; +}; -// Map low-level errors back to actionable guidance for the user. The full -// error object is still surfaced separately when debug mode is on. -function toFriendlyError(err: any): string { - const raw = err?.message || String(err); +type SlashCommand = { + name: string; + detail: string; + run: () => void; +}; + +function toFriendlyError(error: unknown): string { + const raw = error instanceof Error ? error.message : String(error); const lower = raw.toLowerCase(); if (lower.includes('no llm provider configured') || lower.includes('no provider')) { @@ -24,7 +46,7 @@ function toFriendlyError(err: any): string { } if (lower.includes('auth') || lower.includes('unauthorized') || lower.includes('invalid') || lower.includes('401') || lower.includes('api key')) { - return `Authentication failed — check your API key. Type /provider to update it.\n(${raw})`; + return `Authentication failed - check your API key. Type /provider to update it.\n(${raw})`; } if (lower.includes('rate') || lower.includes('quota')) { @@ -38,604 +60,1348 @@ function toFriendlyError(err: any): string { return `Error: ${raw}`; } -type ChatMessage = - | { type: 'user'; content: string } - | { type: 'assistant'; content: string } - | { type: 'tool-call'; name: string; args: string; result?: string } - | { type: 'tool-result'; content: string } // legacy - results now attach to tool-call - | { type: 'system'; content: string }; +const colors = { + bg: '#080b0d', + surface: '#101417', + line: '#1d252a', + frame: '#32424c', + frameActive: '#4ed1c5', + text: '#d8dee4', + muted: '#7d8790', + subtle: '#53606a', + accent: '#61d394', + cyan: '#66d9ef', + blue: '#7aa2f7', + magenta: '#c792ea', + yellow: '#e6c76e', + error: '#ff6b6b', + success: '#80d996', +}; +const buildVersion = packageJson.version; +const defaultTodoRailWidth = 22; +const minTodoRailWidth = 18; +const maxTodoRailWidth = 65; + +function printable(event: KeyEvent): string | undefined { + if (event.ctrl || event.meta || event.super) return undefined; + if (event.name === 'space' || event.name === ' ') return ' '; + if (event.name.length === 1) return event.name; + return undefined; +} -export const App: React.FC = () => { - const { exit } = useApp(); - const [screen, setScreen] = useState('chat'); - const [input, setInput] = useState(''); - const [messages, setMessages] = useState([ - { type: 'system', content: 'Welcome to zero. Type /provider to manage providers.' }, - { type: 'system', content: 'Type /help for available commands.' }, - ]); +function normalizePastedText(input: string, singleLine = false) { + const normalized = input.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + return singleLine ? normalized.replace(/\n/g, ' ') : normalized; +} - // Check on startup if we have any usable provider - React.useEffect(() => { - const checkProvider = async () => { - try { - await loadProviderConfig(); - } catch (err: any) { - if (err.message?.includes('No LLM provider configured')) { - setMessages((prev) => [ - ...prev, - { - type: 'system', - content: '⚠️ No provider configured yet. Use /provider to add one (OpenGateway recommended).' - } - ]); - } - } - }; - checkProvider(); - }, []); - const [isThinking, setIsThinking] = useState(false); - const [streamingMessageIndex, setStreamingMessageIndex] = useState(null); +async function readClipboardText(): Promise { + if (process.platform === 'win32') { + const proc = Bun.spawn(['powershell', '-NoProfile', '-Command', 'Get-Clipboard -Raw'], { + stdout: 'pipe', + stderr: 'ignore', + }); + const output = await new Response(proc.stdout).text(); + const code = await proc.exited; + return code === 0 ? output : undefined; + } - // Plan Mode (inspired by OpenClaude / Claude Code) - const [isPlanMode, setIsPlanMode] = useState(false); + return undefined; +} - // Debug mode - when enabled, prints full error objects to console - const [debugMode, setDebugMode] = useState(false); - const [lastError, setLastError] = useState(null); +function trimLine(input: string, max = 120) { + const line = input.replace(/\s+/g, ' ').trim(); + return line.length > max ? `${line.slice(0, max - 1)}...` : line; +} - // Tools enabled (useful for debugging provider errors) - const [toolsEnabled, setToolsEnabled] = useState(true); +function firstLine(input: string, max = 120) { + const line = input.split(/\r?\n/, 1)[0]?.trim() || ''; + return line.length > max ? `${line.slice(0, max - 3)}...` : line; +} - // Command suggestions - const [suggestions, setSuggestions] = useState([]); +function statusText(status: PlanItem['status']) { + switch (status) { + case 'completed': + return '[x]'; + case 'in_progress': + return '[>]'; + case 'failed': + return '[!]'; + default: + return '[ ]'; + } +} + +function formatJson(input: string) { + try { + return JSON.stringify(JSON.parse(input || '{}'), null, 2); + } catch { + return input; + } +} - const knownCommands = ['/provider', '/plan', '/debug-mode', '/debug', '/tools', '/help', '/exit', '/quit']; +function looksLikeCode(input: string) { + return /\b(function|const|let|class|import|export|return|interface|type)\b|=>|<\/?[a-z][\s\S]*>/i.test(input); +} - // Update suggestions when input changes - React.useEffect(() => { - if (input.startsWith('/')) { - const query = input.toLowerCase(); - const matches = knownCommands.filter(cmd => cmd.startsWith(query)); - setSuggestions(matches.slice(0, 6)); // limit suggestions - } else { - setSuggestions([]); - } - }, [input]); - - // Scrolling state (Grok Build style internal scrolling) - const [scrollOffset, setScrollOffset] = useState(0); - const [terminalRows, setTerminalRows] = useState(24); // default fallback - - // Current provider info for the input bar (Grok Build style) - const activeProfile = configManager.getActiveProvider(); - const currentProviderName = activeProfile?.name || (process.env.ZERO_PROVIDER_COMMAND ? 'command' : 'env'); - const currentModel = activeProfile?.model || process.env.OPENAI_MODEL || 'default'; - - // Track terminal size for proper scrolling - React.useEffect(() => { - const updateSize = () => { - setTerminalRows(process.stdout.rows || 24); - }; - process.stdout.on('resize', updateSize); - updateSize(); - return () => { - process.stdout.off('resize', updateSize); - }; - }, []); - - // Auto-scroll to bottom when new messages arrive (unless user scrolled up) - React.useEffect(() => { - // Only auto-scroll if user is near the bottom - if (scrollOffset <= 3) { - setScrollOffset(0); - } - }, [messages.length]); - - // Only capture main chat input when we're actually in the chat screen - const isInChat = screen === 'chat'; - - useInput((inputChar, key) => { - if (key.ctrl && inputChar === 'c') { - exit(); - return; +function toolSummary(name: string, args: string) { + try { + const parsed = JSON.parse(args || '{}') as Record; + + if (name === 'bash' || name === 'shell_command') { + return trimLine(String(parsed.command || ''), 80); + } + if (name === 'read_file' || name === 'view_image') { + return `read ${trimLine(String(parsed.path || ''), 74)}`; + } + if (name === 'edit_file' || name === 'apply_patch') { + return `edit ${trimLine(String(parsed.path || parsed.file || ''), 74)}`; } - // Don't process chat input while in provider picker or add flow - if (!isInChat) return; + const firstKey = Object.keys(parsed)[0]; + if (firstKey) return `${firstKey}: ${trimLine(String(parsed[firstKey] ?? ''), 70)}`; + } catch { + // Fall through to the raw argument preview. + } - // Scrolling controls (when input is empty) - if (!input) { - if (key.upArrow) { - setScrollOffset((prev) => Math.min(prev + 1, messages.length - 1)); - return; - } - if (key.downArrow) { - setScrollOffset((prev) => Math.max(prev - 1, 0)); - return; - } - if (key.pageUp) { - setScrollOffset((prev) => Math.min(prev + 8, messages.length - 1)); - return; - } - if (key.pageDown) { - setScrollOffset((prev) => Math.max(prev - 8, 0)); - return; + return trimLine(args, 80); +} + +function commandHelp() { + return [ + { type: 'system' as const, content: 'Available commands:' }, + { type: 'system' as const, content: ' /provider Manage local provider profiles' }, + { type: 'system' as const, content: ' /clear Clear the display buffer' }, + { type: 'system' as const, content: ' /plan Toggle plan mode styling' }, + { type: 'system' as const, content: ' /build Return to default build mode' }, + { type: 'system' as const, content: ' /todo Show or hide the todo rail' }, + { type: 'system' as const, content: ' /tools Toggle tool calling' }, + { type: 'system' as const, content: ' /debug Show the last provider error' }, + { type: 'system' as const, content: ' /debug-mode Toggle provider payload logging' }, + { type: 'system' as const, content: ' /help Show this help' }, + { type: 'system' as const, content: ' /exit Quit' }, + ]; +} + +export function App(props: { onExit: () => void }) { + const term = useTerminalDimensions(); + const [screen, setScreen] = createSignal('chat'); + const [messages, setMessages] = createSignal([]); + const [input, setInput] = createSignal(''); + const [isThinking, setIsThinking] = createSignal(false); + const [isPlanMode, setIsPlanMode] = createSignal(false); + const [debugMode, setDebugMode] = createSignal(false); + const [lastError, setLastError] = createSignal(null); + const [toolsEnabled, setToolsEnabled] = createSignal(true); + const [usage, setUsage] = createSignal({ promptTokens: 0, completionTokens: 0 }); + const [plan, setPlan] = createSignal([]); + const [todoRailOpen, setTodoRailOpen] = createSignal(false); + const [todoRailHidden, setTodoRailHidden] = createSignal(false); + const [todoRailWidth, setTodoRailWidth] = createSignal(defaultTodoRailWidth); + const [resizingTodoRail, setResizingTodoRail] = createSignal(false); + const [todoResizeHover, setTodoResizeHover] = createSignal(false); + const [commandCursor, setCommandCursor] = createSignal(0); + const [providerCursor, setProviderCursor] = createSignal(0); + const [providerVersion, setProviderVersion] = createSignal(0); + const [draft, setDraft] = createSignal({ + name: '', + baseURL: 'https://api.openai.com/v1', + apiKey: '', + model: 'gpt-4o', + }); + const [draftField, setDraftField] = createSignal('name'); + + const providers = createMemo(() => { + providerVersion(); + return configManager.listProviders(); + }); + const activeProvider = createMemo(() => { + providerVersion(); + return configManager.getActiveProvider(); + }); + const providerName = createMemo(() => activeProvider()?.name || (process.env.ZERO_PROVIDER_COMMAND ? 'command' : 'env')); + const modelName = createMemo(() => activeProvider()?.model || process.env.OPENAI_MODEL || 'default'); + const totalTokens = createMemo(() => usage().promptTokens + usage().completionTokens); + const hasPlanItems = createMemo(() => plan().length > 0); + const hasActivePlanItems = createMemo(() => plan().some((item) => item.status !== 'completed')); + const rightRail = createMemo(() => + term().width >= 86 && (todoRailOpen() || (hasPlanItems() && hasActivePlanItems() && !todoRailHidden())), + ); + const mainPanelTitle = createMemo(() => (screen() === 'providers' ? 'providers' : 'add provider')); + const providerOptions = createMemo(() => [...providers(), { name: '+ add provider' } as Pick]); + let transcriptScrollbox: ScrollBoxRenderable | undefined; + const slashCommands = createMemo(() => [ + { + name: '/provider', + detail: 'Manage local provider profiles', + run: () => { + setProviderCursor(0); + setScreen('providers'); + }, + }, + { + name: '/clear', + detail: 'Clear the display buffer', + run: () => clearDisplay(), + }, + { + name: '/plan', + detail: 'Toggle plan mode styling', + run: () => { + const next = !isPlanMode(); + setIsPlanMode(next); + append({ type: 'system', content: next ? 'Plan mode enabled.' : 'Build mode enabled.' }); + }, + }, + { + name: '/build', + detail: 'Return to default build mode', + run: () => { + setIsPlanMode(false); + append({ type: 'system', content: 'Build mode enabled.' }); + }, + }, + { + name: '/todo', + detail: 'Show or hide the todo rail', + run: () => toggleTodoRail(), + }, + { + name: '/tools', + detail: 'Toggle model tool calling', + run: () => { + setToolsEnabled((value) => !value); + append({ type: 'system', content: `Tools ${toolsEnabled() ? 'enabled' : 'disabled'}.` }); + }, + }, + { + name: '/debug', + detail: 'Show the last provider error', + run: () => { + const error = lastError(); + append({ type: 'system', content: error ? toFriendlyError(error) : 'No provider error recorded yet.' }); + }, + }, + { + name: '/debug-mode', + detail: 'Toggle provider payload logging', + run: () => { + setDebugMode((value) => !value); + append({ type: 'system', content: `Debug mode ${debugMode() ? 'enabled' : 'disabled'}.` }); + }, + }, + { + name: '/help', + detail: 'Show available commands', + run: () => setMessages((prev) => [...prev, ...commandHelp()]), + }, + { + name: '/exit', + detail: 'Quit zero', + run: props.onExit, + }, + ]); + const filteredCommands = createMemo(() => { + const query = input().slice(1).toLowerCase(); + const matches = slashCommands().filter((command) => command.name.slice(1).startsWith(query)); + return matches; + }); + const commandMenuOpen = createMemo(() => screen() === 'chat' && input().startsWith('/')); + + onMount(() => { + void loadProviderConfig().catch((err: Error) => { + if (err.message.includes('No LLM provider configured')) { + setMessages((prev) => [ + ...prev, + { type: 'system', content: 'No provider configured yet. Use /provider to add one.' }, + ]); } - if (key.home) { - setScrollOffset(messages.length - 1); - return; + }); + }); + + function append(message: ChatMessage) { + setMessages((prev) => [...prev, message]); + } + + function updateLastAssistant(text: string) { + setMessages((prev) => { + const next = [...prev]; + const last = next[next.length - 1]; + if (last?.type === 'assistant') { + next[next.length - 1] = { ...last, content: last.content + text, streaming: true }; + return next; } - if (key.end) { - setScrollOffset(0); - return; + return [...next, { type: 'assistant', content: text, streaming: true }]; + }); + } + + function stopAssistantStreaming() { + setMessages((prev) => + prev.map((item) => (item.type === 'assistant' ? { ...item, streaming: false } : item)), + ); + } + + function attachToolResult(result: string) { + setMessages((prev) => { + const next = [...prev]; + for (let i = next.length - 1; i >= 0; i--) { + const item = next[i]; + if (item?.type === 'tool-call' && item.result === undefined) { + next[i] = { ...item, result }; + break; + } } - } + return next; + }); + } - if (key.return) { - handleSubmit(); - return; + function updatePlan(nextPlan: PlanItem[]) { + if (nextPlan.length > 0 && plan().length === 0) { + setTodoRailHidden(false); + setTodoRailOpen(false); } + setPlan(nextPlan); + } - // Autocomplete first suggestion with Tab when typing a command - if (key.tab && suggestions.length > 0) { - setInput(suggestions[0] + ' '); - setSuggestions([]); - return; + function toggleTodoRail() { + if (rightRail()) { + setTodoRailOpen(false); + setTodoRailHidden(true); + } else { + setTodoRailHidden(false); + setTodoRailOpen(true); } + } - if (key.backspace || key.delete) { - setInput((prev) => prev.slice(0, -1)); - return; - } + function clearDisplay() { + setMessages([{ type: 'system', content: 'Display cleared.' }]); + setCommandCursor(0); + } - if (inputChar && !key.ctrl && !key.meta) { - setInput((prev) => prev + inputChar); - } - }, { isActive: isInChat }); + function scrollTranscript(delta: number, unit: 'viewport' | 'content' = 'viewport') { + transcriptScrollbox?.scrollBy(delta, unit); + } - const handleSubmit = () => { - if (!input.trim()) return; + function resizeTodoRailFromMouse(event: MouseEvent) { + const terminalWidth = Math.max(1, term().width); + const nextWidth = ((terminalWidth - event.x) / terminalWidth) * 100; + setTodoRailWidth(Math.max(minTodoRailWidth, Math.min(maxTodoRailWidth, Math.round(nextWidth)))); + event.preventDefault(); + event.stopPropagation(); + } + + function isTodoResizeHandle(event: MouseEvent) { + const terminalWidth = Math.max(1, term().width); + const railStart = terminalWidth - Math.round((terminalWidth * todoRailWidth()) / 100); + return event.x <= railStart + 2; + } - const trimmed = input.trim(); + function submitChat() { + const trimmed = input().trim(); + if (!trimmed || isThinking()) return; setInput(''); - setSuggestions([]); + append({ type: 'user', content: trimmed }); - // Handle slash commands if (trimmed.startsWith('/')) { - setMessages((prev) => [...prev, { type: 'user', content: trimmed }]); - handleSlashCommand(trimmed); + handleSlash(trimmed); return; } - // Regular message → send to agent - setMessages((prev) => [...prev, { type: 'user', content: trimmed }]); - - const runAgentLoop = async () => { - setIsThinking(true); - - try { - const providerConfig = await loadProviderConfig(); - const provider = new OpenAIProvider({ - apiKey: providerConfig.apiKey || '', - baseURL: providerConfig.baseURL, - model: providerConfig.model, - }); - - // Add empty assistant message that we'll stream into - setMessages((prev) => { - const newMessages = [...prev, { type: 'assistant' as const, content: '' }]; - setStreamingMessageIndex(newMessages.length - 1); - return newMessages; - }); - - await runAgent(trimmed, provider, { - debug: debugMode, - toolsEnabled, - planMode: isPlanMode, - onText: (text: string) => { - setIsThinking(false); - setMessages((prev) => { - const newMessages = [...prev]; - const idx = streamingMessageIndex ?? newMessages.length - 1; - - if (newMessages[idx]?.type === 'assistant') { - const current = newMessages[idx] as { type: 'assistant'; content: string }; - newMessages[idx] = { - ...current, - content: current.content + text, - }; - } - return newMessages; - }); - }, - onToolCall: (tc) => { - setIsThinking(false); - setMessages((prev) => [ - ...prev, - { type: 'tool-call', name: tc.name, args: tc.arguments }, - ]); - // Reset streaming index since we inserted a message - setStreamingMessageIndex(null); - }, - onToolResult: (result) => { - // Attach result to the most recent tool call that doesn't have one yet - setMessages((prev) => { - const newMessages = [...prev]; - for (let i = newMessages.length - 1; i >= 0; i--) { - const msg = newMessages[i]; - if (msg && msg.type === 'tool-call' && (msg as any).result === undefined) { - (newMessages as any)[i] = { - ...msg, - result: result.result, - }; - break; - } - } - return newMessages; - }); - }, - }); - } catch (err: any) { - setIsThinking(false); - - if (debugMode) { - setLastError(err); - try { - const red = '\x1b[31m'; - const reset = '\x1b[0m'; - const border = '─'.repeat(50); - - console.error(`\n${red}┌${border}┐`); - console.error(`│ FULL PROVIDER ERROR${' '.repeat(29)}│`); - console.error(`├${border}┤`); - console.error(`│ Message: ${(err?.message || String(err)).slice(0, 40)}${' '.repeat(9)}│`); - console.error(`│ Name: ${err?.name || 'Error'}${' '.repeat(42 - (err?.name || 'Error').length)}│`); - - if (err?.response?.status) { - console.error(`│ Status: ${err.response.status}${' '.repeat(42 - String(err.response.status).length)}│`); - } - - console.error(`└${border}┘${reset}`); - console.error('Full object:'); - console.dir(err, { depth: 6 }); - console.error(`${red}${'='.repeat(52)}${reset}\n`); - } catch (logErr) { - console.error('Failed to log full error:', logErr); - } - } else { - setLastError(null); - } + void runPrompt(trimmed); + } - const friendlyMessage = toFriendlyError(err); - setMessages((prev) => [...prev, { type: 'system', content: friendlyMessage }]); - } finally { - setIsThinking(false); - setStreamingMessageIndex(null); - } - }; + async function runPrompt(prompt: string) { + setIsThinking(true); + append({ type: 'assistant', content: '', streaming: true }); - runAgentLoop(); - }; + try { + const providerConfig = await loadProviderConfig(); + const provider = new OpenAIProvider({ + apiKey: providerConfig.apiKey || '', + baseURL: providerConfig.baseURL, + model: providerConfig.model, + }); - const handleSlashCommand = (command: string) => { - const parts = command.trim().split(/\s+/); - const cmd = parts[0].toLowerCase(); - const arg = parts[1]?.toLowerCase(); + await runAgent(prompt, provider, { + debug: debugMode(), + toolsEnabled: toolsEnabled(), + planMode: isPlanMode(), + onText: (text) => { + setIsThinking(false); + updateLastAssistant(text); + }, + onToolCall: (toolCall) => { + setIsThinking(false); + stopAssistantStreaming(); + append({ type: 'tool-call', name: toolCall.name, args: toolCall.arguments }); + }, + onToolResult: (result) => attachToolResult(result.result), + onUsage: (next) => { + setUsage((current) => ({ + promptTokens: current.promptTokens + next.promptTokens, + completionTokens: current.completionTokens + next.completionTokens, + })); + }, + onPlanUpdate: (nextPlan) => updatePlan(nextPlan), + }); + } catch (err) { + setLastError(err); + append({ type: 'system', content: toFriendlyError(err) }); + } finally { + setIsThinking(false); + stopAssistantStreaming(); + } + } - if (cmd === '/provider') { - setScreen('provider-picker'); + function handleSlash(command: string) { + const cmd = command.toLowerCase(); + const matched = slashCommands().find( + (item) => + item.name === cmd || + (cmd === '/quit' && item.name === '/exit') || + (cmd === '/default' && item.name === '/build') || + (cmd === '/debug-mode false' && item.name === '/debug-mode'), + ); + if (matched) { + matched.run(); return; } + append({ type: 'system', content: `Unknown command: ${command}` }); + } - if (cmd === '/plan') { - setIsPlanMode(prev => { - const next = !prev; - setMessages((msgs) => [ - ...msgs, - { - type: 'system', - content: next - ? 'Plan mode enabled. The agent will focus on planning before making changes.' - : 'Plan mode disabled.' - }, - ]); - return next; - }); + function acceptSlashCommand() { + const selected = filteredCommands()[commandCursor()]; + if (!selected) { + submitChat(); + return true; + } + setInput(''); + append({ type: 'user', content: selected.name }); + selected.run(); + return true; + } + + function saveProvider() { + const value = draft(); + if (!value.name.trim() || !value.baseURL.trim() || !value.model.trim()) { + append({ type: 'system', content: 'Provider name, base URL, and model are required.' }); return; } - if (cmd === '/debug-mode' || cmd === '/debug') { - // Support "/debug-mode true", "/debug false", or just toggle - let nextDebug: boolean; + configManager.addProvider({ + name: value.name.trim(), + baseURL: value.baseURL.trim(), + apiKey: value.apiKey.trim(), + model: value.model.trim(), + }); + configManager.setActiveProvider(value.name.trim()); + setProviderVersion((version) => version + 1); + setDraft({ name: '', baseURL: 'https://api.openai.com/v1', apiKey: '', model: 'gpt-4o' }); + setDraftField('name'); + setScreen('chat'); + append({ type: 'system', content: `Added and switched to provider: ${value.name.trim()}` }); + } + + function editDraft(event: KeyEvent) { + const field = draftField(); + if (event.name === 'return') { + const order: (keyof AddProviderDraft)[] = ['name', 'baseURL', 'apiKey', 'model']; + const index = order.indexOf(field); + if (index === order.length - 1) saveProvider(); + else setDraftField(order[index + 1]!); + event.preventDefault(); + return; + } + if (event.name === 'tab') { + const order: (keyof AddProviderDraft)[] = ['name', 'baseURL', 'apiKey', 'model']; + const index = order.indexOf(field); + setDraftField(order[(index + (event.shift ? -1 : 1) + order.length) % order.length]!); + event.preventDefault(); + return; + } + if (event.name === 'escape') { + setScreen('providers'); + event.preventDefault(); + return; + } + if (event.name === 'backspace' || event.name === 'delete') { + setDraft((current) => ({ ...current, [field]: current[field].slice(0, -1) })); + event.preventDefault(); + return; + } + const char = printable(event); + if (char) { + setDraft((current) => ({ ...current, [field]: current[field] + char })); + event.preventDefault(); + } + } - if (arg === 'true') nextDebug = true; - else if (arg === 'false') nextDebug = false; - else nextDebug = !debugMode; + function insertText(text: string) { + if (!text) return; - setDebugMode(nextDebug); - if (!nextDebug) setLastError(null); - setMessages((prev) => [ - ...prev, - { type: 'system', content: `Debug mode ${nextDebug ? 'enabled' : 'disabled'}.` }, - ]); + if (screen() === 'add-provider') { + const field = draftField(); + setDraft((current) => ({ + ...current, + [field]: current[field] + normalizePastedText(text, true).trimEnd(), + })); return; } - if (cmd === '/tools') { - const arg2 = parts[1]?.toLowerCase(); - let nextEnabled: boolean; + if (screen() === 'chat') { + setInput((value) => value + normalizePastedText(text)); + } + } - if (arg2 === 'on' || arg2 === 'true') nextEnabled = true; - else if (arg2 === 'off' || arg2 === 'false') nextEnabled = false; - else nextEnabled = !toolsEnabled; + usePaste((event) => { + if (event.defaultPrevented) return; + insertText(new TextDecoder().decode(event.bytes)); + event.preventDefault(); + }); + + useKeyboard((event) => { + if (event.defaultPrevented) return; + if (event.ctrl && event.name === 'c') { + if (screen() === 'add-provider') { + setScreen('providers'); + event.preventDefault(); + return; + } + if (screen() === 'providers') { + setScreen('chat'); + event.preventDefault(); + return; + } + if (input()) { + setInput(''); + setCommandCursor(0); + event.preventDefault(); + return; + } + props.onExit(); + event.preventDefault(); + return; + } + if ((event.ctrl && event.name === 'v') || event.raw === '\x16') { + void readClipboardText().then((text) => { + if (text) insertText(text); + }); + event.preventDefault(); + return; + } + if (!input() && ((event.ctrl && event.name === 't') || event.raw === '\x14')) { + toggleTodoRail(); + event.preventDefault(); + return; + } - setToolsEnabled(nextEnabled); - setMessages((prev) => [ - ...prev, - { type: 'system', content: `Tool calling ${nextEnabled ? 'enabled' : 'disabled'}.` }, - ]); + if (screen() === 'add-provider') { + editDraft(event); return; } - if (cmd === '/help') { - setMessages((prev) => [ - ...prev, - { type: 'system', content: 'Available commands:' }, - { type: 'system', content: ' /provider - Manage LLM providers (fix provider errors here)' }, - { type: 'system', content: ' /plan - Toggle Plan Mode (agent plans first, makes no edits)' }, - { type: 'system', content: ' /debug-mode - Toggle debug mode (prints full errors to console)' }, - { type: 'system', content: ' /tools - Toggle tool calling (useful for debugging provider errors)' }, - { type: 'system', content: ' /help - Show this help' }, - { type: 'system', content: ' /exit - Quit' }, - ]); + if (screen() === 'providers') { + if (event.name === 'escape') { + setScreen('chat'); + event.preventDefault(); + return; + } + if (event.name === 'up' || event.name === 'k') { + setProviderCursor((value) => Math.max(0, value - 1)); + event.preventDefault(); + return; + } + if (event.name === 'down' || event.name === 'j') { + setProviderCursor((value) => Math.min(providerOptions().length - 1, value + 1)); + event.preventDefault(); + return; + } + if (event.name === 'return') { + const selected = providerOptions()[providerCursor()]; + if (!selected) return; + if (selected.name === '+ add provider') { + setScreen('add-provider'); + } else if (configManager.setActiveProvider(selected.name)) { + setProviderVersion((version) => version + 1); + setScreen('chat'); + append({ type: 'system', content: `Switched to provider: ${selected.name}` }); + } + event.preventDefault(); + return; + } return; } - if (cmd === '/exit' || cmd === '/quit') { - exit(); + if (commandMenuOpen() && (event.name === 'up' || event.name === 'down')) { + setCommandCursor((value) => { + const count = filteredCommands().length; + if (count === 0) return 0; + const next = event.name === 'up' ? value - 1 : value + 1; + return (next + count) % count; + }); + event.preventDefault(); + return; + } + if (commandMenuOpen() && event.name === 'tab') { + const selected = filteredCommands()[commandCursor()]; + if (selected) setInput(selected.name); + event.preventDefault(); + return; + } + if (commandMenuOpen() && event.name === 'escape') { + setInput(''); + setCommandCursor(0); + event.preventDefault(); return; } + if (event.name === 'return') { + if (commandMenuOpen() && acceptSlashCommand()) { + event.preventDefault(); + return; + } + submitChat(); + event.preventDefault(); + return; + } + if (event.name === 'backspace' || event.name === 'delete') { + setInput((value) => value.slice(0, -1)); + event.preventDefault(); + return; + } + if (!input()) { + if (event.name === 'up') { + scrollTranscript(-1 / 5); + event.preventDefault(); + return; + } + if (event.name === 'down') { + scrollTranscript(1 / 5); + event.preventDefault(); + return; + } + if (event.name === 'pageup') { + scrollTranscript(-1 / 2); + event.preventDefault(); + return; + } + if (event.name === 'pagedown') { + scrollTranscript(1 / 2); + event.preventDefault(); + return; + } + if (event.name === 'home') { + scrollTranscript(-1, 'content'); + event.preventDefault(); + return; + } + if (event.name === 'end') { + scrollTranscript(1, 'content'); + event.preventDefault(); + return; + } + } + + const char = printable(event); + if (char) { + setInput((value) => { + const next = value + char; + if (next.startsWith('/')) setCommandCursor(0); + return next; + }); + event.preventDefault(); + } + }); + + return ( + +
+ + + + + + { + transcriptScrollbox = node; + }} + messages={messages()} + thinking={isThinking()} + rightRail={rightRail()} + /> + + + + + + + + + + + + setTodoResizeHover(isTodoResizeHandle(event))} + onResizeStart={(event) => { + if (!isTodoResizeHandle(event)) return; + setResizingTodoRail(true); + setTodoResizeHover(true); + resizeTodoRailFromMouse(event); + }} + onResize={(event) => { + if (!resizingTodoRail()) return; + resizeTodoRailFromMouse(event); + }} + onResizeEnd={(event) => { + if (!resizingTodoRail()) return; + setResizingTodoRail(false); + setTodoResizeHover(false); + event.preventDefault(); + event.stopPropagation(); + }} + onResizeLeave={() => setTodoResizeHover(false)} + /> + + + + + + + + + + + + + + ); +} + +function Header(props: { version: string; provider: string; model: string; usage: Usage; total: number; mode: string }) { + return ( + + + + + v{props.version} local agent + + + + + {props.provider} / {props.model} + + + {props.mode === 'plan' ? 'plan mode' : 'build mode'} + + + p:{props.usage.promptTokens} c:{props.usage.completionTokens} t:{props.total} + + + + ); +} - setMessages((prev) => [...prev, { type: 'system', content: `Unknown command: ${command}` }]); - }; +function ZeroLogo(props: { compact?: boolean }) { + const lines = props.compact + ? [ + '███████╗███████╗██████╗ ██████╗', + '╚══███╔╝██╔════╝██╔══██╗██╔═══██╗', + ' ███╔╝ █████╗ ██████╔╝██║ ██║', + ' ███╔╝ ██╔══╝ ██╔══██╗██║ ██║', + '███████╗███████╗██║ ██║╚██████╔╝', + '╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝', + ] + : [ + ' ███████╗ ███████╗ ██████╗ ██████╗ ', + ' ╚══███╔╝ ██╔════╝ ██╔══██╗ ██╔═══██╗', + ' ███╔╝ █████╗ ██████╔╝ ██║ ██║', + ' ███╔╝ ██╔══╝ ██╔══██╗ ██║ ██║', + ' ███████╗ ███████╗ ██║ ██║ ╚██████╔╝', + ' ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ', + ]; - const handleProviderSelected = (name: string) => { - const success = configManager.setActiveProvider(name); - if (success) { - setMessages((prev) => [...prev, { type: 'system', content: `Switched to provider: ${name}` }]); + return ( + + + {(line) => ( + + {line} + + )} + + + ); +} + +function Transcript(props: { + ref: (node: ScrollBoxRenderable) => void; + messages: ChatMessage[]; + thinking: boolean; + rightRail: boolean; +}) { + return ( + + + wheel/arrows scroll + + + 0} fallback={}> + + {(message) => ( + + + + )} + + + + zero is thinking... + + + + ); +} + +function EmptySession() { + return ( + + + + A clean terminal AI coding agent + Type /provider to manage providers or /help for commands. + + ); +} + +function Message(props: { message: ChatMessage }) { + return ( + + + + you + {(props.message as any).content} + + + + + zero + + + + _ + + + + + + } /> + + + {(props.message as any).content} + + + ); +} + +function Markdown(props: { content: string }) { + const parts = createMemo(() => parseMarkdown(props.content)); + + return ( + + + {(part) => ( + + + + + + + + + )} + + + ); +} + +function parseMarkdown(input: string): Array<{ type: 'text' | 'code'; content: string; lang?: string }> { + const parts: Array<{ type: 'text' | 'code'; content: string; lang?: string }> = []; + const fence = /```([^\n`]*)\n?([\s\S]*?)```/g; + let lastIndex = 0; + let match: RegExpExecArray | null; + + while ((match = fence.exec(input)) !== null) { + if (match.index > lastIndex) { + parts.push({ type: 'text', content: input.slice(lastIndex, match.index) }); } - setScreen('chat'); - }; + parts.push({ type: 'code', lang: match[1]?.trim() || 'text', content: match[2]?.trimEnd() || '' }); + lastIndex = match.index + match[0].length; + } - const handleProviderPickerCancel = () => { - setScreen('chat'); - }; + if (lastIndex < input.length) parts.push({ type: 'text', content: input.slice(lastIndex) }); + return parts.length > 0 ? parts : [{ type: 'text', content: input }]; +} - const handleOpenAddProvider = () => { - setScreen('add-provider'); - }; +function TextBlock(props: { text: string }) { + const lines = createMemo(() => props.text.split(/\r?\n/)); - const handleAddProviderDone = (providerName?: string) => { - setScreen('chat'); + return ( + + + {(line) => ( + 0} fallback={}> + + {formatInline(line)} + + + )} + + + ); +} - if (providerName) { - // Automatically switch to the newly added provider - const switched = configManager.setActiveProvider(providerName); +function formatInline(text: string): JSX.Element { + const segments: JSX.Element[] = []; + const token = /(\*\*[^*]+\*\*|`[^`]+`)/g; + let lastIndex = 0; + let match: RegExpExecArray | null; - if (switched) { - setMessages((prev) => [ - ...prev, - { type: 'system', content: `Added and switched to provider: ${providerName}` }, - ]); - } else { - setMessages((prev) => [ - ...prev, - { type: 'system', content: `Provider added: ${providerName}` }, - ]); - } + while ((match = token.exec(text)) !== null) { + if (match.index > lastIndex) { + segments.push({text.slice(lastIndex, match.index)}); + } + + const value = match[0]; + if (value.startsWith('**')) { + segments.push({value.slice(2, -2)}); } else { - setMessages((prev) => [...prev, { type: 'system', content: 'Provider added successfully.' }]); + segments.push({value.slice(1, -1)}); } - }; + lastIndex = match.index + value.length; + } - const handleAddProviderCancel = () => { - setScreen('provider-picker'); - }; + if (lastIndex < text.length) segments.push({text.slice(lastIndex)}); + return <>{segments}; +} - if (screen === 'add-provider') { - return ( - - ); - } +function CodeBlock(props: { code: string; lang: string }) { + const [highlighted, setHighlighted] = createSignal(); - if (screen === 'provider-picker') { - return ( - - ); - } + createEffect(() => { + const code = props.code; + const lang = props.lang; + let cancelled = false; - const showLogo = messages.length <= 2; + void highlightCode(code, lang).then((result) => { + if (!cancelled) setHighlighted(result); + }); - // Calculate visible messages for scrolling (Grok Build style) - const chatHeight = Math.max(8, terminalRows - 6); // leave room for input + status - const visibleMessages = messages.slice(scrollOffset, scrollOffset + chatHeight); + onCleanup(() => { + cancelled = true; + }); + }); - const canScrollUp = scrollOffset < messages.length - 1; - const canScrollDown = scrollOffset > 0; + return ( + + + {props.lang} + + + + ); +} + +function HighlightedCode(props: { lines?: HighlightedLine[]; fallback: string }) { + const lines = createMemo( + () => props.lines || props.fallback.split(/\r?\n/).map((line) => [{ content: line }]), + ); return ( - - {/* Scrollable messages area with right-side scroll indicator (Grok Build style) */} - - {/* Main chat content */} - + + {(line) => ( + + 0 ? line : [{ content: ' ' }]}> + {(token) => {token.content}} + + + )} + + + ); +} + +function ToolCall(props: { message: Extract }) { + const [expanded, setExpanded] = createSignal(false); + const [showFullResult, setShowFullResult] = createSignal(false); + const result = createMemo(() => props.message.result || ''); + const longResult = createMemo(() => result().length > 400 || result().split(/\r?\n/).length > 12); + const previewResult = createMemo(() => { + if (!longResult() || showFullResult()) return result(); + return `${firstLine(result(), 220)}...`; + }); + + function toggleExpanded(event: MouseEvent) { + setExpanded((value) => !value); + event.preventDefault(); + event.stopPropagation(); + } + + function toggleFullResult(event: MouseEvent) { + setShowFullResult((value) => !value); + event.preventDefault(); + event.stopPropagation(); + } + + return ( + + + + {props.message.result ? '[x]' : '[>]'} {props.message.name} + + + {' '}{toolSummary(props.message.name, props.message.args)} + + + {' '}{expanded() ? '[hide]' : '[show]'} + + + + - {showLogo && } + args + + + result + + + + {showFullResult() ? '[less]' : '[more]'} + + + + + + + ); +} - {/* Scroll indicator */} - {(canScrollUp || canScrollDown) && ( - - {canScrollUp ? '↑ ' : ' '}Scroll with ↑↓ / PgUp/PgDn / Home/End {canScrollDown ? '↓' : ''} - - )} +function TodoRail(props: { + plan: PlanItem[]; + width: `${number}%`; + resizing: boolean; + resizeHover: boolean; + onResizeHover: (event: MouseEvent) => void; + onResizeStart: (event: MouseEvent) => void; + onResize: (event: MouseEvent) => void; + onResizeEnd: (event: MouseEvent) => void; + onResizeLeave: () => void; +}) { + return ( + todo' : 'todo'} + paddingLeft={1} + paddingRight={1} + paddingTop={1} + onMouseMove={props.onResizeHover} + onMouseDown={props.onResizeStart} + onMouseDrag={props.onResize} + onMouseUp={props.onResizeEnd} + onMouseDragEnd={props.onResizeEnd} + onMouseOut={props.onResizeLeave} + > + 0} + fallback={No active todo list yet.} + > + + {(item, index) => ( + + + {index() + 1}. {statusText(item.status)} {item.content} + + + {item.notes} + + + )} + + + + ); +} - - {visibleMessages.map((msg, index) => { - const realIndex = scrollOffset + index; +function CommandMenu(props: { commands: SlashCommand[]; cursor: number }) { + const visibleLimit = 9; + const visibleCommands = createMemo(() => { + const start = Math.min( + Math.max(0, props.cursor - visibleLimit + 1), + Math.max(0, props.commands.length - visibleLimit), + ); + return props.commands.slice(start, start + visibleLimit).map((command, offset) => ({ + command, + index: start + offset, + })); + }); - if (msg.type === 'user') { + return ( + + + + slash menu + enter run / esc close + + 0} fallback={No matching commands.}> + + {(item) => { + const selected = createMemo(() => item.index === props.cursor); return ( - - - {`> ${msg.content}`} - - + + + {selected() ? '> ' : ' '}{item.command.name} + + + {item.command.detail} + + ); - } + }} + + + + + ); +} - if (msg.type === 'assistant') { - const isStreaming = realIndex === streamingMessageIndex; - return ( - - - - - {isStreaming && ( - - )} - - - ); - } +function DebugError(props: { error: unknown }) { + const message = () => props.error instanceof Error ? props.error.message : String(props.error); - if (msg.type === 'tool-call') { - const hasResult = !!msg.result; - return ( - - - - ); - } - - if (msg.type === 'tool-result') { - // Legacy separate results are no longer created; ignore for cleanliness - return null; - } - - // system messages - return ( - - - {msg.content} - - - ); - })} - - {isThinking && } - - - - - {/* Scroll position (Grok Build style) */} - {(canScrollUp || canScrollDown) && ( - - - {scrollOffset + 1}/{messages.length}{canScrollUp ? ' ↑' : ''}{canScrollDown ? ' ↓' : ''} - - - )} - - {/* Command suggestions */} - {suggestions.length > 0 && ( - - - Suggestions: {suggestions.map((s, i) => ( - {s}{i < suggestions.length - 1 ? ' ' : ''} - ))} (Tab to autocomplete) - - - )} - - {/* Debug error box */} - {debugMode && lastError && ( - - ⚠ Debug Error - - {lastError.message || String(lastError)} - - {lastError.stack && ( - - {lastError.stack.split('\n').slice(0, 8).join('\n')} - - )} - - (Full details in terminal • /debug-mode false to hide) - - - )} - - {/* Input box at the bottom */} - + - {/* Left: prompt + input */} - - - {input} - - - - {/* Right: Current provider + model */} - - {currentProviderName} - - {currentModel} - - - - {/* Very subtle status line */} - - - /help • ↑↓ scroll • Ctrl+C exit - - {isPlanMode && ( - • PLAN MODE - )} - - + {trimLine(message(), 240)} + + ); -}; +} +function ProviderView(props: { + providers: Pick[]; + cursor: number; + active?: string; +}) { + return ( + + providers + enter select / esc back + + + {(provider, index) => { + const selected = createMemo(() => index() === props.cursor); + return ( + + {selected() ? '> ' : ' '} + + {provider.name} + + active + + + + ); + }} + + + ); +} + +function AddProviderView(props: { draft: AddProviderDraft; field: keyof AddProviderDraft }) { + const rows: { key: keyof AddProviderDraft; label: string; secret?: boolean }[] = [ + { key: 'name', label: 'name' }, + { key: 'baseURL', label: 'base url' }, + { key: 'apiKey', label: 'api key', secret: true }, + { key: 'model', label: 'model' }, + ]; + + return ( + + add provider + tab move / enter next-save / esc back + + + {(row) => { + const active = createMemo(() => props.field === row.key); + const value = createMemo(() => row.secret ? '*'.repeat(props.draft[row.key].length) : props.draft[row.key]); + return ( + + + {active() ? '> ' : ' '}{row.label}:{' '} + + + {value()} + + _ + + + + ); + }} + + + ); +} + +function Composer(props: { + screen: Screen; + input: string; + planMode: boolean; + provider: string; + model: string; + todoVisible: boolean; + hasTodos: boolean; + debugMode: boolean; + toolsEnabled: boolean; +}) { + const inChat = () => props.screen === 'chat'; + return ( + + + provider screen active} + > + + + > {props.input} + _ + + + + + + / menu - /todo or ctrl+t - arrows scroll - ctrl+c cancel/exit + + - plan mode + + + - tools off + + + - debug + + + + {' '}- todo {props.todoVisible ? 'shown' : 'hidden'} + + + + + ); +} diff --git a/src/tui/Logo.tsx b/src/tui/Logo.tsx deleted file mode 100644 index 4214ba89b..000000000 --- a/src/tui/Logo.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { Box, Text } from 'ink'; - -export const Logo: React.FC = () => { - return ( - - - {` ███████╗ ███████╗ ██████╗ ██████╗ `} - - - {` ╚══███╔╝ ██╔════╝ ██╔══██╗ ██╔═══██╗`} - - - {` ███╔╝ █████╗ ██████╔╝ ██║ ██║`} - - - {` ███╔╝ ██╔══╝ ██╔══██╗ ██║ ██║`} - - - {` ███████╗ ███████╗ ██║ ██║ ╚██████╔╝`} - - - {` ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ `} - - - - - A clean terminal AI coding agent - - - - ); -}; diff --git a/src/tui/MessageRenderer.tsx b/src/tui/MessageRenderer.tsx deleted file mode 100644 index 6b23e5287..000000000 --- a/src/tui/MessageRenderer.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Box, Text } from 'ink'; -import { highlightCode } from './highlighter'; - -// Formatter for model responses: paragraphs, inline code, and highlighted code blocks -interface MessageRendererProps { - content: string; -} - -export const MessageRenderer: React.FC = ({ content }) => { - const [highlightedBlocks, setHighlightedBlocks] = useState>(new Map()); - - const codeBlockRegex = /```(\w+)?\n?([\s\S]*?)```/g; - const parts: React.ReactNode[] = []; - let lastIndex = 0; - let match; - let blockIndex = 0; - - const matches: Array<{ index: number; lang: string; code: string; fullMatch: string }> = []; - - while ((match = codeBlockRegex.exec(content)) !== null) { - matches.push({ - index: match.index, - lang: match[1] || 'text', - code: match[2].trim(), - fullMatch: match[0], - }); - } - - // Highlight code blocks asynchronously - useEffect(() => { - const highlightAll = async () => { - const newHighlights = new Map(); - - for (let i = 0; i < matches.length; i++) { - const m = matches[i]; - try { - const ansi = await highlightCode(m.code, m.lang); - newHighlights.set(i, ansi); - } catch { - newHighlights.set(i, m.code); - } - } - - setHighlightedBlocks(newHighlights); - }; - - if (matches.length > 0) { - highlightAll(); - } - }, [content]); - - for (let i = 0; i < matches.length; i++) { - const m = matches[i]; - - // Add text before this code block (with paragraph formatting) - if (m.index > lastIndex) { - const textBefore = content.slice(lastIndex, m.index); - parts.push(renderTextBlock(textBefore, `text-${parts.length}`)); - } - - const highlighted = highlightedBlocks.get(i); - - parts.push( - - {m.lang && m.lang !== 'text' && ( - {m.lang} - )} - {highlighted ? ( - {highlighted} - ) : ( - Highlighting... - )} - - ); - - lastIndex = m.index + m.fullMatch.length; - blockIndex++; - } - - // Add remaining text after last code block (with paragraph formatting) - if (lastIndex < content.length) { - const remaining = content.slice(lastIndex); - parts.push(renderTextBlock(remaining, `text-${parts.length}`)); - } - - return <>{parts.length > 0 ? parts : renderTextBlock(content, 'full')}; -}; - -// Stronger formatter for model responses (paragraphs + basic lists + inline formatting) -function renderTextBlock(text: string, keyPrefix: string): React.ReactNode { - const lines = text.split('\n'); - - // Group into logical blocks: paragraphs vs lists - const blocks: Array<{ type: 'paragraph' | 'list'; lines: string[] }> = []; - let current: { type: 'paragraph' | 'list'; lines: string[] } | null = null; - - for (const line of lines) { - const trimmed = line.trim(); - const isListItem = /^\s*(\d+\.|[-*+])\s+/.test(line); - - if (isListItem) { - if (!current || current.type !== 'list') { - if (current) blocks.push(current); - current = { type: 'list', lines: [] }; - } - current.lines.push(line); - } else { - if (!current || current.type !== 'paragraph') { - if (current) blocks.push(current); - current = { type: 'paragraph', lines: [] }; - } - current.lines.push(line); - } - } - if (current) blocks.push(current); - - return ( - - {blocks.map((block, bIndex) => { - const isLastBlock = bIndex === blocks.length - 1; - - if (block.type === 'list') { - return ( - - {block.lines.map((line, lIndex) => ( - - {formatInline(line)} - - ))} - - ); - } - - // Paragraph block — join lines and split on double newlines for sub-paragraphs - const paragraphText = block.lines.join('\n').trim(); - const subParagraphs = paragraphText - .split(/\n\s*\n/) - .map(p => p.trim()) - .filter(Boolean); - - return ( - - {subParagraphs.map((p, pIndex) => ( - - {formatInline(p)} - - ))} - - ); - })} - - ); -} - -// Handles inline formatting: **bold**, `inline code` -function formatInline(text: string): React.ReactNode { - const elements: React.ReactNode[] = []; - let remaining = text; - let idx = 0; - - // Simple sequential parser for **bold** and `code` - while (remaining.length > 0) { - // Try bold first - const boldMatch = remaining.match(/^\*\*([^*]+)\*\*/); - if (boldMatch) { - elements.push( - - {boldMatch[1]} - - ); - remaining = remaining.slice(boldMatch[0].length); - continue; - } - - // Try inline code - const codeMatch = remaining.match(/^`([^`]+)`/); - if (codeMatch) { - elements.push( - - {codeMatch[1]} - - ); - remaining = remaining.slice(codeMatch[0].length); - continue; - } - - // Take plain text until next special char - const nextSpecial = remaining.search(/(\*\*|`)/); - const plain = nextSpecial === -1 ? remaining : remaining.slice(0, nextSpecial); - if (plain) { - elements.push({plain}); - remaining = remaining.slice(plain.length); - } else { - // Fallback single char - elements.push({remaining[0]}); - remaining = remaining.slice(1); - } - } - - return <>{elements}; -} diff --git a/src/tui/ProviderPicker.tsx b/src/tui/ProviderPicker.tsx deleted file mode 100644 index 23c5d34a6..000000000 --- a/src/tui/ProviderPicker.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React, { useState } from 'react'; -import { Box, Text, useInput } from 'ink'; -import { configManager } from '../config/manager'; -import type { ProviderProfile } from '../config/types'; - -interface ProviderPickerProps { - onSelect: (name: string) => void; - onCancel: () => void; - onAddNew: () => void; -} - -export const ProviderPicker: React.FC = ({ onSelect, onCancel, onAddNew }) => { - const providers = configManager.listProviders(); - const activeProvider = configManager.getActiveProvider()?.name; - - const totalItems = providers.length + 1; // +1 for "Add new" - const [selectedIndex, setSelectedIndex] = useState(0); - - useInput((input, key) => { - if (key.escape || (key.ctrl && input === 'c')) { - onCancel(); - return; - } - - if (key.upArrow) { - setSelectedIndex((prev) => Math.max(0, prev - 1)); - return; - } - - if (key.downArrow) { - setSelectedIndex((prev) => Math.min(totalItems - 1, prev + 1)); - return; - } - - if (key.return) { - if (selectedIndex < providers.length) { - const selected = providers[selectedIndex]; - onSelect(selected.name); - } else { - // Last item = Add new - onAddNew(); - } - return; - } - - // Quick select by number - const num = parseInt(input, 10); - if (!isNaN(num) && num >= 1 && num <= totalItems) { - if (num <= providers.length) { - const selected = providers[num - 1]; - onSelect(selected.name); - } else { - onAddNew(); - } - } - }); - - return ( - - - Select Provider - - - ↑↓ to navigate • Enter to select • Esc to cancel - - - - {providers.map((provider, index) => { - const isSelected = index === selectedIndex; - const isActive = provider.name === activeProvider; - - return ( - - - {isSelected ? '› ' : ' '} - {provider.name} - {isActive && (current)} - - - ); - })} - - {/* Add new option */} - - - {selectedIndex === providers.length ? '› ' : ' '} - + Add new provider... - - - - - {providers[selectedIndex] && ( - - - Model: {providers[selectedIndex].model} - - - Base URL: {providers[selectedIndex].baseURL} - - {providers[selectedIndex].description && ( - - Description: {providers[selectedIndex].description} - - )} - - )} - - - - Press 1-{totalItems} for quick selection - - - - ); -}; diff --git a/src/tui/Spinner.tsx b/src/tui/Spinner.tsx deleted file mode 100644 index a2c1a83ba..000000000 --- a/src/tui/Spinner.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { Text } from 'ink'; -import Spinner from 'ink-spinner'; - -interface ThinkingSpinnerProps { - label?: string; -} - -export const ThinkingSpinner: React.FC = ({ label = 'thinking' }) => { - return ( - - {label}... - - ); -}; diff --git a/src/tui/ToolCallRenderer.tsx b/src/tui/ToolCallRenderer.tsx deleted file mode 100644 index fb1d55f74..000000000 --- a/src/tui/ToolCallRenderer.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Box, Text } from 'ink'; -import { highlightCode } from './highlighter'; - -interface ToolCallRendererProps { - name: string; - args: string; - result?: string; - status?: 'running' | 'success' | 'error'; -} - -export const ToolCallRenderer: React.FC = ({ - name, - args, - result, - status = 'success', -}) => { - const [isExpanded, setIsExpanded] = useState(false); - const [highlightedArgs, setHighlightedArgs] = useState(null); - const [highlightedResult, setHighlightedResult] = useState(null); - - const hasResult = !!result; - const isLongResult = hasResult && (result!.length > 400 || result!.split('\n').length > 12); - const [showFullResult, setShowFullResult] = useState(false); - - // Generate a nice one-line summary for collapsed state - const summary = getToolSummary(name, args); - - // Highlight arguments (only when expanded) - useEffect(() => { - if (!isExpanded) return; - - const highlight = async () => { - try { - let jsonArgs = args; - try { - const parsed = JSON.parse(args); - jsonArgs = JSON.stringify(parsed, null, 2); - } catch { - // not valid JSON - } - const ansi = await highlightCode(jsonArgs, 'json'); - setHighlightedArgs(ansi); - } catch { - setHighlightedArgs(args); - } - }; - highlight(); - }, [args, isExpanded]); - - // Highlight result (only when expanded) - useEffect(() => { - if (!isExpanded || !result) return; - - const highlight = async () => { - try { - const looksLikeCode = - result.includes('function') || - result.includes('const ') || - result.includes('import ') || - result.includes('=>') || - result.includes('class '); - - const ansi = await highlightCode(result, looksLikeCode ? 'typescript' : 'text'); - setHighlightedResult(ansi); - } catch { - setHighlightedResult(result); - } - }; - highlight(); - }, [result, isExpanded]); - - const borderColor = - status === 'running' ? 'yellow' : status === 'error' ? 'red' : 'green'; - - const statusIcon = - status === 'running' ? '⟳' : status === 'error' ? '✕' : '✓'; - - const statusColor = - status === 'running' ? 'yellow' : status === 'error' ? 'red' : 'green'; - - // === COLLAPSED VIEW — clean formatter for model actions === - if (!isExpanded) { - const showToggle = args || hasResult; - - return ( - - - {statusIcon} {name} - - {summary} - - {showToggle && ( - setIsExpanded(true) } as any)} - > - {' '}[show] - - )} - - ); - } - - // === EXPANDED VIEW — formatted details === - return ( - - {/* Header */} - - - {statusIcon} {name} - - setIsExpanded(false) } as any)} - > - [hide] - - - - {/* Arguments */} - - args - {highlightedArgs ? ( - {highlightedArgs} - ) : ( - - )} - - - {/* Result */} - {hasResult && ( - - result - {highlightedResult || result ? ( - - {isLongResult && !showFullResult - ? (highlightedResult || result!).slice(0, 200) + '...' - : (highlightedResult || result)} - - ) : null} - - {isLongResult && ( - setShowFullResult(!showFullResult) } as any)} - > - {showFullResult ? ' [less]' : ' [more]'} - - )} - - )} - - ); -}; - -// Smart one-line summary for collapsed tool calls. -// We want: "tool name + the command it actually ran" (e.g. "read src/App.tsx", "ls -la .") -function getToolSummary(name: string, args: string): string { - try { - const parsed = JSON.parse(args); - - if (name === 'bash') { - const cmd = parsed.command || ''; - return cmd.length > 65 ? cmd.slice(0, 62) + '...' : cmd; - } - - if (name === 'read_file') { - const path = parsed.path || ''; - const short = path.length > 60 ? path.slice(0, 57) + '...' : path; - return `read ${short}`; - } - - if (name === 'edit_file') { - const path = parsed.path || ''; - const short = path.length > 60 ? path.slice(0, 57) + '...' : path; - return `edit ${short}`; - } - - // Fallback for future tools - const keys = Object.keys(parsed); - if (keys.length > 0) { - const firstKey = keys[0]!; - const val = String((parsed as any)[firstKey] ?? ''); - const shortVal = val.length > 50 ? val.slice(0, 47) + '...' : val; - return `${firstKey}: ${shortVal}`; - } - - return args.length > 65 ? args.slice(0, 62) + '...' : args; - } catch { - return args.length > 65 ? args.slice(0, 62) + '...' : args; - } -} diff --git a/src/tui/highlighter.ts b/src/tui/highlighter.ts index 246c2979a..cc6e1d887 100644 --- a/src/tui/highlighter.ts +++ b/src/tui/highlighter.ts @@ -1,44 +1,71 @@ -import { createHighlighter } from 'shiki' +import { createHighlighter } from 'shiki'; -let highlighterPromise: ReturnType | null = null +export type HighlightedToken = { + content: string; + color?: string; +}; -export async function getHighlighter() { - if (!highlighterPromise) { - highlighterPromise = createHighlighter({ - themes: ['github-dark', 'github-light'], +export type HighlightedLine = HighlightedToken[]; + +let highlighterPromise: ReturnType | undefined; +const highlightCache = new Map>(); + +function hashCode(input: string) { + let hash = 0; + for (let i = 0; i < input.length; i++) { + hash = ((hash << 5) - hash + input.charCodeAt(i)) | 0; + } + return hash.toString(36); +} + +export async function highlightCode(code: string, lang = 'text'): Promise { + const cacheKey = `${lang}:${code.length}:${hashCode(code)}`; + const cached = highlightCache.get(cacheKey); + if (cached) return cached; + + const promise = tokenizeCode(code, lang); + highlightCache.set(cacheKey, promise); + return promise; +} + +async function tokenizeCode(code: string, lang: string): Promise { + try { + highlighterPromise ??= createHighlighter({ + themes: ['github-dark'], langs: [ - 'typescript', + 'bash', + 'css', + 'dockerfile', + 'go', + 'html', 'javascript', - 'tsx', - 'jsx', 'json', + 'jsx', + 'markdown', 'python', 'rust', - 'go', - 'bash', 'shell', - 'markdown', - 'css', - 'html', 'sql', - 'yaml', 'toml', - 'dockerfile', + 'tsx', + 'typescript', + 'yaml', ], - }) - } - return highlighterPromise -} + }); -export async function highlightCode(code: string, lang: string = 'text'): Promise { - try { - const highlighter = await getHighlighter() - return highlighter.codeToAnsi(code, { - lang: lang || 'text', + const highlighter = await highlighterPromise; + const result = highlighter.codeToTokens(code, { + lang: (lang || 'text') as any, theme: 'github-dark', - }) - } catch (error) { - // Fallback to plain text if highlighting fails - return code + }); + + return result.tokens.map((line) => + line.map((token) => ({ + content: token.content, + color: token.color, + })), + ); + } catch { + return code.split(/\r?\n/).map((line) => [{ content: line }]); } } diff --git a/src/tui/index.tsx b/src/tui/index.tsx index ca1c95ffe..6468eec51 100644 --- a/src/tui/index.tsx +++ b/src/tui/index.tsx @@ -1,13 +1,35 @@ -import React from 'react'; -import { render } from 'ink'; +/** @jsxImportSource @opentui/solid */ +import { createCliRenderer } from '@opentui/core'; +import { render } from '@opentui/solid'; import { App } from './App'; -import { getHighlighter } from './highlighter'; - -// Preload Shiki highlighter early for fast syntax highlighting -getHighlighter().catch(() => { - // Silently fail - we'll fallback gracefully -}); export function startTUI() { - render(); + void start(); +} + +async function start() { + const renderer = await createCliRenderer({ + externalOutputMode: 'passthrough', + targetFps: 60, + gatherStats: false, + exitOnCtrlC: false, + useMouse: true, + enableMouseMovement: true, + useKittyKeyboard: {}, + openConsoleOnError: false, + }); + + const exit = () => { + if (!renderer.isDestroyed) { + renderer.setTerminalTitle(''); + renderer.destroy(); + } + }; + + process.once('SIGINT', exit); + renderer.once('destroy', () => { + process.off('SIGINT', exit); + }); + + await render(() => , renderer); } diff --git a/tests/openai-provider.test.ts b/tests/openai-provider.test.ts new file mode 100644 index 000000000..b59883907 --- /dev/null +++ b/tests/openai-provider.test.ts @@ -0,0 +1,117 @@ +import { afterEach, describe, expect, it, mock } from 'bun:test'; + +const createMock: any = mock(async () => { + throw new Error('create mock not configured'); +}); + +mock.module('openai', () => ({ + default: class MockOpenAI { + chat = { + completions: { + create: createMock, + }, + }; + }, +})); + +async function* streamFrom(chunks: any[]) { + for (const chunk of chunks) { + yield chunk; + } +} + +afterEach(() => { + createMock.mockReset(); +}); + +describe('OpenAIProvider stream options compatibility', () => { + it('requests usage and retries without stream_options when a compatible provider rejects it', async () => { + const { OpenAIProvider } = await import('../src/providers/openai'); + const unsupportedStreamOptions = Object.assign(new Error('unknown parameter: stream_options.include_usage'), { + status: 400, + }); + + createMock + .mockRejectedValueOnce(unsupportedStreamOptions) + .mockResolvedValueOnce(streamFrom([ + { + choices: [{ delta: { content: 'hi' }, finish_reason: 'stop' }], + }, + ])); + + const provider = new OpenAIProvider({ apiKey: 'test', model: 'test-model' }); + const events = []; + + for await (const event of provider.streamCompletion([{ role: 'user', content: 'hello' }], [])) { + events.push(event); + } + + expect(createMock).toHaveBeenCalledTimes(2); + expect(createMock.mock.calls[0]?.[0]?.stream_options).toEqual({ include_usage: true }); + expect(createMock.mock.calls[1]?.[0]?.stream_options).toBeUndefined(); + expect(events).toContainEqual({ type: 'text', content: 'hi' }); + }); + + it('does not retry unrelated unknown-parameter errors', async () => { + const { OpenAIProvider } = await import('../src/providers/openai'); + const unsupportedToolChoice = Object.assign(new Error('unknown parameter: tool_choice'), { + status: 400, + }); + + createMock.mockRejectedValueOnce(unsupportedToolChoice); + + const provider = new OpenAIProvider({ apiKey: 'test', model: 'test-model' }); + + await expect((async () => { + for await (const _event of provider.streamCompletion([{ role: 'user', content: 'hello' }], [])) { + // Drain the stream. + } + })()).rejects.toThrow('unknown parameter: tool_choice'); + + expect(createMock).toHaveBeenCalledTimes(1); + }); + + it('categorizes mid-stream authentication errors', async () => { + const { OpenAIProvider } = await import('../src/providers/openai'); + + createMock.mockResolvedValueOnce(streamFrom([ + { + error: { + message: 'Incorrect API key provided', + }, + }, + ])); + + const provider = new OpenAIProvider({ apiKey: 'test', model: 'test-model' }); + + await expect((async () => { + for await (const _event of provider.streamCompletion([{ role: 'user', content: 'hello' }], [])) { + // Drain the stream. + } + })()).rejects.toThrow('Provider authentication error'); + }); + + it('emits usage events from compatible OpenAI streams', async () => { + const { OpenAIProvider } = await import('../src/providers/openai'); + + createMock.mockResolvedValueOnce(streamFrom([ + { + choices: [{ delta: {}, finish_reason: 'stop' }], + usage: { + prompt_tokens: 8, + completion_tokens: 3, + }, + }, + ])); + + const provider = new OpenAIProvider({ apiKey: 'test', model: 'test-model' }); + const events = []; + + for await (const event of provider.streamCompletion([{ role: 'user', content: 'hello' }], [])) { + events.push(event); + } + + expect(createMock.mock.calls[0]?.[0]?.stream_options).toEqual({ include_usage: true }); + expect(events).toContainEqual({ type: 'usage', promptTokens: 8, completionTokens: 3 }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index bfa0fead5..d472d4a43 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "target": "ESNext", "module": "Preserve", "moduleDetection": "force", - "jsx": "react-jsx", + "jsx": "preserve", + "jsxImportSource": "@opentui/solid", "allowJs": true, // Bundler mode