diff --git a/cmd/jcode/main.go b/cmd/jcode/main.go index 18a9f79..f1c386f 100644 --- a/cmd/jcode/main.go +++ b/cmd/jcode/main.go @@ -9,7 +9,7 @@ import ( "path/filepath" "strings" - tea "github.com/charmbracelet/bubbletea" + tea "charm.land/bubbletea/v2" "github.com/cloudwego/eino/adk" "github.com/cloudwego/eino/adk/filesystem" "github.com/cloudwego/eino/adk/middlewares/reduction" diff --git a/cmd/jcode/ssh.go b/cmd/jcode/ssh.go index 65d4d09..85ac4aa 100644 --- a/cmd/jcode/ssh.go +++ b/cmd/jcode/ssh.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - tea "github.com/charmbracelet/bubbletea" + tea "charm.land/bubbletea/v2" "github.com/cloudwego/eino/adk" einomodel "github.com/cloudwego/eino/components/model" diff --git a/go.mod b/go.mod index 2cff9dd..34346bd 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,17 @@ module github.com/cnjack/jcode -go 1.24.7 +go 1.25.8 require ( - github.com/charmbracelet/bubbles v1.0.0 - github.com/charmbracelet/bubbletea v1.3.10 - github.com/charmbracelet/glamour v0.10.0 - github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 - github.com/cloudwego/eino v0.8.5 + charm.land/bubbles/v2 v2.1.0 + charm.land/bubbletea/v2 v2.0.2 + charm.land/glamour/v2 v2.0.0 + charm.land/lipgloss/v2 v2.0.2 + github.com/cloudwego/eino v0.8.8 github.com/cloudwego/eino-ext/components/tool/mcp v0.0.8 github.com/cloudwego/eino-ext/libs/acl/langfuse v0.0.0-20260313050455-88e279b3b32f github.com/google/uuid v1.6.0 - github.com/mark3labs/mcp-go v0.45.0 + github.com/mark3labs/mcp-go v0.47.1 github.com/sashabaranov/go-openai v1.32.5 golang.org/x/crypto v0.45.0 ) @@ -19,7 +19,6 @@ require ( require ( github.com/alecthomas/chroma/v2 v2.14.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect @@ -28,38 +27,33 @@ require ( github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/charmbracelet/colorprofile v0.4.1 // indirect + github.com/charmbracelet/colorprofile v0.4.2 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect github.com/charmbracelet/x/ansi v0.11.6 // indirect - github.com/charmbracelet/x/cellbuf v0.0.15 // indirect github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect github.com/charmbracelet/x/term v0.2.2 // indirect - github.com/clipperhouse/displaywidth v0.9.0 // indirect - github.com/clipperhouse/stringish v0.1.1 // indirect - github.com/clipperhouse/uax29/v2 v2.5.0 // indirect + github.com/charmbracelet/x/termios v0.1.1 // indirect + github.com/charmbracelet/x/windows v0.2.2 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dlclark/regexp2 v1.11.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/eino-contrib/jsonschema v1.0.3 // indirect - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/google/jsonschema-go v0.4.2 // indirect github.com/goph/emperror v0.17.2 // indirect github.com/gorilla/css v1.0.1 // indirect - github.com/invopop/jsonschema v0.13.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.21 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.16.0 // indirect github.com/nikolalohinski/gonja v1.5.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.2-0.20201214064552-5dd12d0cfe7f // indirect @@ -80,8 +74,8 @@ require ( golang.org/x/arch v0.19.0 // indirect golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.31.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 4616263..0dc710a 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,11 @@ +charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g= +charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY= +charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0= +charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ= +charm.land/glamour/v2 v2.0.0 h1:IDBoqLEy7Hdpb9VOXN+khLP/XSxtJy1VsHuW/yF87+U= +charm.land/glamour/v2 v2.0.0/go.mod h1:kjq9WB0s8vuUYZNYey2jp4Lgd9f4cKdzAw88FZtpj/w= +charm.land/lipgloss/v2 v2.0.2 h1:xFolbF8JdpNkM2cEPTfXEcW1p6NRzOWTSamRfYEw8cs= +charm.land/lipgloss/v2 v2.0.2/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= @@ -9,10 +17,8 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= -github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= +github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= +github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= @@ -36,36 +42,30 @@ github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCc github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= -github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc= -github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E= -github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= -github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= -github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= -github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= -github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY= -github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk= -github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= -github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= +github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY= +github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8= +github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA= +github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98= github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= -github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= -github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= -github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= -github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I= github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI= github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= -github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA= -github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA= -github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= -github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= -github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U= -github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= +github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= +github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= -github.com/cloudwego/eino v0.8.5 h1:ZNRJBiOW8eEOxMKjR4KbxW9Px9+DtETi8k7Yk1cuzv8= -github.com/cloudwego/eino v0.8.5/go.mod h1:+2N4nsMPxA6kGBHpH+75JuTfEcGprAMTdsZESrShKpU= +github.com/cloudwego/eino v0.8.8 h1:64NuheQBmxOXe/28Tm85rkBkxXMB5ZhjSu/j0RDFyZU= +github.com/cloudwego/eino v0.8.8/go.mod h1:+2N4nsMPxA6kGBHpH+75JuTfEcGprAMTdsZESrShKpU= github.com/cloudwego/eino-ext/components/tool/mcp v0.0.8 h1:/QwCVAtB61b4Q2+RUvhoy9AZNkhiThsTySIoimxiJS4= github.com/cloudwego/eino-ext/components/tool/mcp v0.0.8/go.mod h1:zxP8sFkADBqflNc0a4qfKdLYQ+edzHPlkOaZF0A1X7o= github.com/cloudwego/eino-ext/libs/acl/langfuse v0.0.0-20260313050455-88e279b3b32f h1:kdnjioHjBlBQW+dpTTL8SVqQMxap5qjtAgtLiPOroTQ= @@ -80,8 +80,6 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eino-contrib/jsonschema v1.0.3 h1:2Kfsm1xlMV0ssY2nuxshS4AwbLFuqmPmzIjLVJ1Fsp0= github.com/eino-contrib/jsonschema v1.0.3/go.mod h1:cpnX4SyKjWjGC7iN2EbhxaTdLqGjCi0e9DxpLYxddD4= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -90,9 +88,11 @@ github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJY github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= +github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= @@ -104,8 +104,6 @@ github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= -github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -128,17 +126,14 @@ github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQ github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mark3labs/mcp-go v0.45.0 h1:s0S8qR/9fWaQ3pHxz7pm1uQ0DrswoSnRIxKIjbiQtkc= -github.com/mark3labs/mcp-go v0.45.0/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw= +github.com/mark3labs/mcp-go v0.47.1 h1:A9sJJ20mscl/ssLYHjodfaoBmq6uuhMG7pAPNYaQymQ= +github.com/mark3labs/mcp-go v0.47.1/go.mod h1:JKTC7R2LLVagkEWK7Kwu7DbmA6iIvnNAod6yrHiQMag= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= +github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= @@ -148,14 +143,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= -github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c= github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -169,8 +158,6 @@ github.com/pkg/errors v0.9.2-0.20201214064552-5dd12d0cfe7f/go.mod h1:bwawxfHBFNV github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= @@ -235,13 +222,13 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/runner/approval.go b/internal/runner/approval.go index 73e6361..69cab15 100644 --- a/internal/runner/approval.go +++ b/internal/runner/approval.go @@ -7,7 +7,7 @@ import ( "path/filepath" "strings" - tea "github.com/charmbracelet/bubbletea" + tea "charm.land/bubbletea/v2" "github.com/cnjack/jcode/internal/tui" ) diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 33c1655..de82af6 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -5,7 +5,7 @@ import ( "io" "strings" - tea "github.com/charmbracelet/bubbletea" + tea "charm.land/bubbletea/v2" "github.com/cloudwego/eino/adk" "github.com/cloudwego/eino/schema" diff --git a/internal/tui/format.go b/internal/tui/format.go index fcc34e7..2c612d1 100644 --- a/internal/tui/format.go +++ b/internal/tui/format.go @@ -3,9 +3,11 @@ package tui import ( "encoding/json" "fmt" + "regexp" "strings" + "unicode" - "github.com/charmbracelet/lipgloss" + "charm.land/lipgloss/v2" ) func formatToolArgs(argsJSON string) string { @@ -14,22 +16,53 @@ func formatToolArgs(argsJSON string) string { } var args map[string]interface{} if err := json.Unmarshal([]byte(argsJSON), &args); err != nil { - return truncate(argsJSON, 120) + return truncate(sanitize(argsJSON), 120) } parts := make([]string, 0, len(args)) for k, v := range args { - val := truncate(fmt.Sprintf("%v", v), 60) + val := truncate(sanitize(fmt.Sprintf("%v", v)), 60) parts = append(parts, fmt.Sprintf("%s=%s", k, val)) } return truncate(strings.Join(parts, " "), 200) } +// ansiRe matches ANSI escape sequences (CSI, OSC, etc.). +var ansiRe = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x1b]*\x1b\\|\x1b[^\[\]]`) + +// sanitize removes ANSI escape sequences and replaces control characters +// (except newline and tab) with their Unicode Control Pictures or a placeholder. +// This prevents special characters from corrupting the TUI layout. +func sanitize(s string) string { + // Strip ANSI escape sequences + s = ansiRe.ReplaceAllString(s, "") + // Replace control characters + var b strings.Builder + b.Grow(len(s)) + for _, r := range s { + switch { + case r == '\n' || r == '\t': + b.WriteRune(r) + case r < 0x20: // C0 control characters + // Map to Unicode Control Pictures block (U+2400) + b.WriteRune(0x2400 + r) + case r == 0x7f: // DEL + b.WriteRune('␡') + case unicode.Is(unicode.Co, r): // Private Use Area - could break rendering + b.WriteRune('�') + default: + b.WriteRune(r) + } + } + return b.String() +} + func truncate(s string, maxLen int) string { s = strings.ReplaceAll(s, "\n", "↲") - if len(s) <= maxLen { + runes := []rune(s) + if len(runes) <= maxLen { return s } - return s[:maxLen] + "…" + return string(runes[:maxLen]) + "…" } // formatToolResult returns styled output lines depending on the tool name. diff --git a/internal/tui/input_views.go b/internal/tui/input_views.go index 21b15ec..7945c93 100644 --- a/internal/tui/input_views.go +++ b/internal/tui/input_views.go @@ -4,9 +4,9 @@ import ( "fmt" "strings" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/list" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" "github.com/cnjack/jcode/internal/config" "github.com/cnjack/jcode/internal/tools" ) @@ -165,7 +165,7 @@ func (m Model) handleBgInput(cmds []tea.Cmd) (tea.Model, tea.Cmd) { m.lines = append(m.lines, fmt.Sprintf("%s /bg", userLabelStyle.Render("👤 You:"))) if m.ready { - m.viewport.Height = m.calcViewportHeight(false) + m.viewport.SetHeight(m.calcViewportHeight(false)) m.viewport.SetContent(m.renderContent()) m.viewport.GotoBottom() } @@ -233,7 +233,7 @@ func (m Model) handleSkillSlashInput(skillName, userInput string, cmds []tea.Cmd m.lines = append(m.lines, fmt.Sprintf("%s %s", userLabelStyle.Render("🔧 Skill:"), displayLabel)) if m.ready { - m.viewport.Height = m.calcViewportHeight(false) + m.viewport.SetHeight(m.calcViewportHeight(false)) m.viewport.SetContent(m.renderContent()) m.viewport.GotoBottom() } diff --git a/internal/tui/pickers.go b/internal/tui/pickers.go index 6c3a08d..12c0ba7 100644 --- a/internal/tui/pickers.go +++ b/internal/tui/pickers.go @@ -4,9 +4,9 @@ import ( "fmt" "strings" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/list" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" "github.com/cnjack/jcode/internal/config" "github.com/cnjack/jcode/internal/session" ) diff --git a/internal/tui/setup.go b/internal/tui/setup.go index b8db02f..3cabc13 100644 --- a/internal/tui/setup.go +++ b/internal/tui/setup.go @@ -4,10 +4,10 @@ import ( "fmt" "strings" - "github.com/charmbracelet/bubbles/list" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/list" + "charm.land/bubbles/v2/textinput" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" "github.com/cnjack/jcode/internal/config" ) @@ -139,19 +139,19 @@ func NewSetupModel() SetupModel { m.customModelIn = textinput.New() m.customModelIn.Placeholder = "Enter custom model name..." m.customModelIn.Prompt = "Model Name: " - m.customModelIn.Width = 50 + m.customModelIn.SetWidth(50) m.urlIn = textinput.New() m.urlIn.Placeholder = "https://your-base-url/v1" m.urlIn.Prompt = "Base URL: " - m.urlIn.Width = 50 + m.urlIn.SetWidth(50) m.keyIn = textinput.New() m.keyIn.Placeholder = "sk-..." m.keyIn.Prompt = "API Key: " m.keyIn.EchoMode = textinput.EchoPassword m.keyIn.EchoCharacter = '•' - m.keyIn.Width = 50 + m.keyIn.SetWidth(50) return m } @@ -164,7 +164,7 @@ func (m SetupModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { - case tea.KeyMsg: + case tea.KeyPressMsg: if msg.String() == "ctrl+c" { return m, tea.Quit } @@ -382,7 +382,7 @@ func (m SetupModel) submit() (tea.Model, tea.Cmd) { return m, tea.Quit } -func (m SetupModel) View() string { +func (m SetupModel) View() tea.View { w := m.width if w <= 0 { w = 80 @@ -422,7 +422,7 @@ func (m SetupModel) View() string { helpLine := lipgloss.NewStyle().Foreground(colorMuted).PaddingLeft(2).Render(" Ctrl+C quit") cfgPath := lipgloss.NewStyle().Foreground(colorMuted).Italic(true).PaddingLeft(2).Render(" Config: " + config.ConfigPath()) - return lipgloss.JoinVertical(lipgloss.Left, + result := lipgloss.JoinVertical(lipgloss.Left, header, divider(w-4), "\n", @@ -433,6 +433,9 @@ func (m SetupModel) View() string { helpLine, cfgPath, ) + v := tea.NewView(result) + v.AltScreen = true + return v } func (m SetupModel) IsDone() bool { @@ -441,7 +444,7 @@ func (m SetupModel) IsDone() bool { func RunSetupTUI() (bool, error) { m := NewSetupModel() - p := tea.NewProgram(m, tea.WithAltScreen()) + p := tea.NewProgram(m) finalModel, err := p.Run() if err != nil { return false, err diff --git a/internal/tui/ssh_handlers.go b/internal/tui/ssh_handlers.go index 550b015..653351b 100644 --- a/internal/tui/ssh_handlers.go +++ b/internal/tui/ssh_handlers.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" + "charm.land/bubbles/v2/list" + tea "charm.land/bubbletea/v2" "github.com/cnjack/jcode/internal/config" ) diff --git a/internal/tui/statusbar_component.go b/internal/tui/statusbar_component.go index 429ca48..f0ee01a 100644 --- a/internal/tui/statusbar_component.go +++ b/internal/tui/statusbar_component.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/charmbracelet/lipgloss" + "charm.land/lipgloss/v2" ) // StatusBarState holds the props supplied to the StatusBar component diff --git a/internal/tui/styles.go b/internal/tui/styles.go index 78cd9c8..4fbd156 100644 --- a/internal/tui/styles.go +++ b/internal/tui/styles.go @@ -1,6 +1,6 @@ package tui -import "github.com/charmbracelet/lipgloss" +import "charm.land/lipgloss/v2" var ( // Color palette diff --git a/internal/tui/tui.go b/internal/tui/tui.go index fd022f5..eb0d009 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -6,13 +6,13 @@ import ( "path" "strings" - "github.com/charmbracelet/bubbles/list" - "github.com/charmbracelet/bubbles/spinner" - "github.com/charmbracelet/bubbles/textarea" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/glamour" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/list" + "charm.land/bubbles/v2/spinner" + "charm.land/bubbles/v2/textarea" + "charm.land/bubbles/v2/viewport" + tea "charm.land/bubbletea/v2" + "charm.land/glamour/v2" + "charm.land/lipgloss/v2" "github.com/cnjack/jcode/internal/config" "github.com/cnjack/jcode/internal/session" @@ -194,8 +194,10 @@ func newTextarea() textarea.Model { ta.ShowLineNumbers = false ta.SetHeight(1) ta.Prompt = "> " - ta.FocusedStyle.CursorLine = lipgloss.NewStyle() - ta.FocusedStyle.Prompt = lipgloss.NewStyle().Foreground(colorPrimary).Bold(true) + st := ta.Styles() + st.Focused.CursorLine = lipgloss.NewStyle() + st.Focused.Prompt = lipgloss.NewStyle().Foreground(colorPrimary).Bold(true) + ta.SetStyles(st) ta.Focus() return ta } @@ -206,7 +208,7 @@ func NewModel(hasPrompt bool, pwd string, todoStore *tools.TodoStore) Model { s.Style = spinnerStyle md, _ := glamour.NewTermRenderer( - glamour.WithAutoStyle(), + glamour.WithStandardStyle("dark"), glamour.WithWordWrap(100), ) @@ -367,7 +369,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return m, tea.Batch(cmds...) - case tea.KeyMsg: + case tea.KeyPressMsg: // Tool approval dialog handling if m.approvalPending { switch msg.String() { @@ -886,7 +888,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.lines = append(m.lines, fmt.Sprintf("%s %s", userLabelStyle.Render("👤 You (queued):"), prompt)) if m.ready { - m.viewport.Height = m.calcViewportHeight(true) + m.viewport.SetHeight(m.calcViewportHeight(true)) m.viewport.SetContent(m.renderContent()) m.viewport.GotoBottom() } @@ -907,7 +909,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.lines = append(m.lines, fmt.Sprintf("%s %s", userLabelStyle.Render(modeLabel), prompt)) if m.ready { - m.viewport.Height = m.calcViewportHeight(false) + m.viewport.SetHeight(m.calcViewportHeight(false)) m.viewport.SetContent(m.renderContent()) m.viewport.GotoBottom() } @@ -920,12 +922,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "shift+enter": // Insert newline into textarea by forwarding a plain enter key var cmd tea.Cmd - m.textarea, cmd = m.textarea.Update(tea.KeyMsg{Type: tea.KeyEnter}) + m.textarea, cmd = m.textarea.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) cmds = append(cmds, cmd) m.textareaLines = recalcLines(m.textarea.Value()) m.textarea.SetHeight(m.textareaLines) if m.ready { - m.viewport.Height = m.calcViewportHeight(m.inputActive()) + m.viewport.SetHeight(m.calcViewportHeight(m.inputActive())) } return m, tea.Batch(cmds...) case "up": @@ -936,7 +938,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.textareaLines = recalcLines(m.textarea.Value()) m.textarea.SetHeight(m.textareaLines) if m.ready { - m.viewport.Height = m.calcViewportHeight(m.inputActive()) + m.viewport.SetHeight(m.calcViewportHeight(m.inputActive())) } } return m, tea.Batch(cmds...) @@ -952,7 +954,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.textareaLines = recalcLines(m.textarea.Value()) m.textarea.SetHeight(m.textareaLines) if m.ready { - m.viewport.Height = m.calcViewportHeight(m.inputActive()) + m.viewport.SetHeight(m.calcViewportHeight(m.inputActive())) } return m, tea.Batch(cmds...) case "pgup", "pgdown": @@ -970,7 +972,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.textareaLines = recalcLines(m.textarea.Value()) m.textarea.SetHeight(m.textareaLines) if m.ready { - m.viewport.Height = m.calcViewportHeight(m.inputActive()) + m.viewport.SetHeight(m.calcViewportHeight(m.inputActive())) } return m, tea.Batch(cmds...) } @@ -990,12 +992,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { vpH := m.calcViewportHeight(m.inputActive()) if !m.ready { - m.viewport = viewport.New(msg.Width, vpH) - m.viewport.HighPerformanceRendering = false + m.viewport = viewport.New(viewport.WithWidth(msg.Width), viewport.WithHeight(vpH)) + m.ready = true } else { - m.viewport.Width = msg.Width - m.viewport.Height = vpH + m.viewport.SetWidth(msg.Width) + m.viewport.SetHeight(vpH) } m.dirList.SetSize(msg.Width, vpH) m.settingMenu.SetSize(msg.Width, vpH) @@ -1086,13 +1088,13 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.lines = append(m.lines, fmt.Sprintf("%s %s %s", toolLabelStyle.Render("🔧 Tool:"), toolNameStyle.Render(e.Name), - toolArgsStyle.Render(truncate(e.Args, 100)), + toolArgsStyle.Render(truncate(sanitize(e.Args), 100)), )) case string(session.EntryToolResult): if e.Error != "" { m.lines = append(m.lines, fmt.Sprintf(" %s %s", toolErrorStyle.Render("✗ Error:"), - toolResultStyle.Render(truncate(e.Error, 200)))) + toolResultStyle.Render(truncate(sanitize(e.Error), 200)))) } else { m.lines = append(m.lines, formatToolResult(e.Name, e.Output, m.width)...) } @@ -1101,7 +1103,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.lines = append(m.lines, "") m.lines = append(m.lines, divider(m.width-4)) if m.ready { - m.viewport.Height = m.calcViewportHeight(true) + m.viewport.SetHeight(m.calcViewportHeight(true)) m.viewport.SetContent(m.renderContent()) m.viewport.GotoBottom() } @@ -1170,18 +1172,18 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.agentDone = true m.textarea.Focus() if m.ready { - m.viewport.Height = m.calcViewportHeight(true) + m.viewport.SetHeight(m.calcViewportHeight(true)) m.viewport.SetContent(m.renderContent()) m.viewport.GotoBottom() } case UserPromptMsg: m.lines = append(m.lines, fmt.Sprintf("%s %s", - userLabelStyle.Render("👤 You:"), msg.Prompt)) + userLabelStyle.Render("👤 You:"), sanitize(msg.Prompt))) m.refreshViewport() case AgentTextMsg: - m.currentText.WriteString(msg.Text) + m.currentText.WriteString(sanitize(msg.Text)) m.refreshViewport() case ToolCallMsg: @@ -1203,9 +1205,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.Err != nil { m.lines = append(m.lines, fmt.Sprintf(" %s %s", toolErrorStyle.Render("✗ Error:"), - toolResultStyle.Render(truncate(msg.Err.Error(), maxToolOutputLen)))) + toolResultStyle.Render(truncate(sanitize(msg.Err.Error()), maxToolOutputLen)))) } else { - m.lines = append(m.lines, formatToolResult(msg.Name, msg.Output, m.width)...) + m.lines = append(m.lines, formatToolResult(msg.Name, sanitize(msg.Output), m.width)...) } m.refreshViewport() cmds = append(cmds, m.spinner.Tick) @@ -1227,7 +1229,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.agentDone = true m.textarea.Focus() if m.ready { - m.viewport.Height = m.calcViewportHeight(true) + m.viewport.SetHeight(m.calcViewportHeight(true)) m.viewport.SetContent(m.renderContent()) m.viewport.GotoBottom() } @@ -1294,11 +1296,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.Err != nil { m.lines = append(m.lines, fmt.Sprintf(" %s %s", toolErrorStyle.Render("✗ Subagent Error:"), - toolResultStyle.Render(truncate(msg.Err.Error(), maxToolOutputLen)))) + toolResultStyle.Render(truncate(sanitize(msg.Err.Error()), maxToolOutputLen)))) } else { m.lines = append(m.lines, fmt.Sprintf(" %s %s", toolSuccessStyle.Render("✓ Subagent Done:"), - toolResultStyle.Render(truncate(msg.Result, maxToolOutputLen)))) + toolResultStyle.Render(truncate(sanitize(msg.Result), maxToolOutputLen)))) } m.refreshViewport() cmds = append(cmds, m.spinner.Tick) @@ -1334,7 +1336,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { statusIcon, toolNameStyle.Render(msg.TaskID), msg.Status, - toolArgsStyle.Render(truncate(msg.Command, 60)))) + toolArgsStyle.Render(truncate(sanitize(msg.Command), 60)))) } m.refreshViewport() @@ -1400,33 +1402,40 @@ func (m Model) calcViewportHeight(_ ...bool) int { return h } -func (m Model) View() string { +func (m Model) newView(content string) tea.View { + v := tea.NewView(content) + v.AltScreen = true + v.MouseMode = tea.MouseModeCellMotion + return v +} + +func (m Model) View() tea.View { if m.showingSetting { - return m.settingMenuView() + return m.newView(m.settingMenuView()) } if m.pickingSSHAlias { - return m.sshAliasPickerView() + return m.newView(m.sshAliasPickerView()) } if m.pickingModel { - return m.modelPickerView() + return m.newView(m.modelPickerView()) } if m.pickingSession { - return m.sessionPickerView() + return m.newView(m.sessionPickerView()) } if !m.ready { - return "\n Initializing..." + return m.newView("\n Initializing...") } if m.sshStep == 3 { - return m.dirPickerView() + return m.newView(m.dirPickerView()) } if m.approvalPending { - return m.approvalDialogView() + return m.newView(m.approvalDialogView()) } headerText := "🚀 Little Jack — Coding Assistant | " @@ -1443,21 +1452,21 @@ func (m Model) View() string { footerHeight := lipgloss.Height(footer) if m.ready { - m.viewport.Height = m.height - headerHeight - footerHeight - if m.viewport.Height < 3 { - m.viewport.Height = 3 + m.viewport.SetHeight(m.height - headerHeight - footerHeight) + if m.viewport.Height() < 3 { + m.viewport.SetHeight(3) } m.viewport.SetContent(strings.TrimRight(m.renderContent(), "\n")) } mainView := lipgloss.JoinVertical(lipgloss.Left, header, headerLine, m.viewport.View(), footer) - return mainView + return m.newView(mainView) } // refreshViewport recalculates viewport height, updates content and scrolls to bottom. func (m *Model) refreshViewport() { if m.ready { - m.viewport.Height = m.calcViewportHeight() + m.viewport.SetHeight(m.calcViewportHeight()) m.viewport.SetContent(m.renderContent()) m.viewport.GotoBottom() } @@ -1547,7 +1556,7 @@ func (m *Model) renderSubagentBox() string { func RunTUI(hasPrompt bool, pwd string, todoStore *tools.TodoStore) (*tea.Program, Model) { m := NewModel(hasPrompt, pwd, todoStore) - p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion()) + p := tea.NewProgram(m) return p, m }