on main help sign in
small fixes
commit49b593d9ddbe278358b30dcde444932172bea615 authorKujtim Hoxha <kujtimii.h@gmail.com> dateThu 3 Apr 2025, 19:50 (2 years ago) parent230917b
8 files changed+331−11
README.md+20−4 go.mod+3 go.sum+62 internal/llm/agent/coder.go+2 internal/llm/tools/bash.go+3−1 internal/llm/tools/edit.go+6−1 internal/llm/tools/fetch.go+223 internal/tui/components/dialog/permission.go+12−5
README.md+20−4
@@ -50,12 +50,28 @@ termai -d
5050
5151### Keyboard Shortcuts
5252
53+#### Global Shortcuts
54+- `?`: Toggle help panel
55+- `Ctrl+C` or `q`: Quit application
5356- `L`: View logs
57+- `Backspace`: Go back to previous page
58+- `Esc`: Close current view/dialog or return to normal mode
59+
60+#### Session Management
5461- `N`: Create new session
55−- `Esc`: Close current view
56−- `Backspace`: Go back
57−- `Ctrl+C` or `q`: Quit
58−- `?`: Toggle help
62+- `Enter` or `Space`: Select session (in sessions list)
63+
64+#### Editor Shortcuts (Vim-like)
65+- `i`: Enter insert mode
66+- `Esc`: Enter normal mode
67+- `v`: Enter visual mode
68+- `V`: Enter visual line mode
69+- `Enter`: Send message (in normal mode)
70+- `Ctrl+S`: Send message (in insert mode)
71+
72+#### Navigation
73+- Arrow keys: Navigate through lists and content
74+- Page Up/Down: Scroll through content
5975
6076## Architecture
6177
go.mod+3
@@ -5,6 +5,8 @@ go 1.24.0
55toolchain go1.24.2
66
77require (
8+ github.com/JohannesKaufmann/html-to-markdown v1.6.0
9+ github.com/PuerkitoBio/goquery v1.9.2
810 github.com/anthropics/anthropic-sdk-go v0.2.0-beta.2
911 github.com/bmatcuk/doublestar/v4 v4.8.1
1012 github.com/catppuccin/go v0.3.0
@@ -43,6 +45,7 @@ require (
4345 cloud.google.com/go/compute/metadata v0.6.0 // indirect
4446 cloud.google.com/go/longrunning v0.5.7 // indirect
4547 github.com/alecthomas/chroma/v2 v2.15.0 // indirect
48+ github.com/andybalholm/cascadia v1.3.2 // indirect
4649 github.com/atotto/clipboard v0.1.4 // indirect
4750 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
4851 github.com/aymerick/douceur v0.2.0 // indirect
go.sum+62
@@ -10,14 +10,20 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4
1010cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
1111cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
1212cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
13+github.com/JohannesKaufmann/html-to-markdown v1.6.0 h1:04VXMiE50YYfCfLboJCLcgqF5x+rHJnb1ssNmqpLH/k=
14+github.com/JohannesKaufmann/html-to-markdown v1.6.0/go.mod h1:NUI78lGg/a7vpEJTz/0uOcYMaibytE4BUOQS8k78yPQ=
1315github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
1416github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
17+github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
18+github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
1519github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
1620github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
1721github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
1822github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
1923github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
2024github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
25+github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
26+github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
2127github.com/anthropics/anthropic-sdk-go v0.2.0-beta.2 h1:h7qxtumNjKPWFv1QM/HJy60MteeW23iKeEtBoY7bYZk=
2228github.com/anthropics/anthropic-sdk-go v0.2.0-beta.2/go.mod h1:AapDW22irxK2PSumZiQXYUFvsdQgkwIWlpESweWZI/c=
2329github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
@@ -148,6 +154,7 @@ github.com/openai/openai-go v0.1.0-beta.2 h1:Ra5nCFkbEl9w+UJwAciC4kqnIBUCcJazhmM
148154github.com/openai/openai-go v0.1.0-beta.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
149155github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
150156github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
157+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
151158github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
152159github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
153160github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -161,6 +168,9 @@ github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsF
161168github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
162169github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
163170github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
171+github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
172+github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
173+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
164174github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
165175github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
166176github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
@@ -196,6 +206,7 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
196206github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
197207github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
198208github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
209+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
199210github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
200211github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
201212github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
@@ -217,6 +228,11 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
217228go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
218229golang.design/x/clipboard v0.7.0 h1:4Je8M/ys9AJumVnl8m+rZnIvstSnYj1fvzqYrU3TXvo=
219230golang.design/x/clipboard v0.7.0/go.mod h1:PQIvqYO9GP29yINEfsEn5zSQKAz3UgXmZKzDA6dnq2E=
231+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
232+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
233+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
234+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
235+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
220236golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
221237golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
222238golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
@@ -227,22 +243,67 @@ golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
227243golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
228244golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a h1:sYbmY3FwUWCBTodZL1S3JUuOvaW6kM2o+clDzzDNBWg=
229245golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a/go.mod h1:Ede7gF0KGoHlj822RtphAHK1jLdrcuRBZg0sF1Q+SPc=
246+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
247+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
248+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
249+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
250+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
251+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
252+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
253+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
254+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
255+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
256+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
230257golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
231258golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
232259golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
233260golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
261+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
262+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
263+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
234264golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
235265golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
266+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
267+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
268+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
236269golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
270+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
271+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
272+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
237273golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
274+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
275+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
276+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
277+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
278+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
238279golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
239280golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
281+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
282+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
283+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
284+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
285+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
286+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
287+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
288+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
240289golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
241290golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
291+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
292+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
293+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
294+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
295+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
296+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
297+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
242298golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
243299golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
244300golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
245301golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
302+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
303+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
304+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
305+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
306+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
246307google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0=
247308google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY=
248309google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
@@ -257,6 +318,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
257318gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
258319gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
259320gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
321+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
260322gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
261323gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
262324gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
internal/llm/agent/coder.go+2
@@ -55,9 +55,11 @@ func NewCoderAgent(app *app.App) (Agent, error) {
5555 []tools.BaseTool{
5656 tools.NewBashTool(),
5757 tools.NewEditTool(app.LSPClients),
58+ tools.NewFetchTool(),
5859 tools.NewGlobTool(),
5960 tools.NewGrepTool(),
6061 tools.NewLsTool(),
62+ tools.NewSourcegraphTool(),
6163 tools.NewViewTool(app.LSPClients),
6264 tools.NewWriteTool(app.LSPClients),
6365 }, otherTools...,
internal/llm/tools/bash.go+3−1
@@ -97,7 +97,9 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
9797 ToolName: BashToolName,
9898 Action: "execute",
9999 Description: fmt.Sprintf("Execute command: %s", params.Command),
100− Params: BashPermissionsParams(params),
100+ Params: BashPermissionsParams{
101+ Command: params.Command,
102+ },
101103 },
102104 )
103105 if !p {
internal/llm/tools/edit.go+6−1
@@ -260,7 +260,12 @@ func replaceContent(filePath, oldString, newString string) (string, error) {
260260 }
261261
262262 newContent := oldContent[:index] + newString + oldContent[index+len(oldString):]
263− diff := GenerateDiff(oldString, newContent)
263+
264+ startIndex := max(0, index-3)
265+ oldEndIndex := min(len(oldContent), index+len(oldString)+3)
266+ newEndIndex := min(len(newContent), index+len(newString)+3)
267+
268+ diff := GenerateDiff(oldContent[startIndex:oldEndIndex], newContent[startIndex:newEndIndex])
264269
265270 p := permission.Default.Request(
266271 permission.CreatePermissionRequest{
internal/llm/tools/fetch.gonew file+223
@@ -0,0 +1,223 @@
1+package tools
2+
3+import (
4+ "context"
5+ "encoding/json"
6+ "fmt"
7+ "io"
8+ "net/http"
9+ "strings"
10+ "time"
11+
12+ md "github.com/JohannesKaufmann/html-to-markdown"
13+ "github.com/PuerkitoBio/goquery"
14+ "github.com/kujtimiihoxha/termai/internal/config"
15+ "github.com/kujtimiihoxha/termai/internal/permission"
16+)
17+
18+const (
19+ FetchToolName = "fetch"
20+ fetchToolDescription = `Fetches content from a URL and returns it in the specified format.
21+
22+WHEN TO USE THIS TOOL:
23+- Use when you need to download content from a URL
24+- Helpful for retrieving documentation, API responses, or web content
25+- Useful for getting external information to assist with tasks
26+
27+HOW TO USE:
28+- Provide the URL to fetch content from
29+- Specify the desired output format (text, markdown, or html)
30+- Optionally set a timeout for the request
31+
32+FEATURES:
33+- Supports three output formats: text, markdown, and html
34+- Automatically handles HTTP redirects
35+- Sets reasonable timeouts to prevent hanging
36+- Validates input parameters before making requests
37+
38+LIMITATIONS:
39+- Maximum response size is 5MB
40+- Only supports HTTP and HTTPS protocols
41+- Cannot handle authentication or cookies
42+- Some websites may block automated requests
43+
44+TIPS:
45+- Use text format for plain text content or simple API responses
46+- Use markdown format for content that should be rendered with formatting
47+- Use html format when you need the raw HTML structure
48+- Set appropriate timeouts for potentially slow websites`
49+)
50+
51+type FetchParams struct {
52+ URL string `json:"url"`
53+ Format string `json:"format"`
54+ Timeout int `json:"timeout,omitempty"`
55+}
56+
57+type FetchPermissionsParams struct {
58+ URL string `json:"url"`
59+ Format string `json:"format"`
60+ Timeout int `json:"timeout,omitempty"`
61+}
62+
63+type fetchTool struct {
64+ client *http.Client
65+}
66+
67+func NewFetchTool() BaseTool {
68+ return &fetchTool{
69+ client: &http.Client{
70+ Timeout: 30 * time.Second,
71+ },
72+ }
73+}
74+
75+func (t *fetchTool) Info() ToolInfo {
76+ return ToolInfo{
77+ Name: FetchToolName,
78+ Description: fetchToolDescription,
79+ Parameters: map[string]any{
80+ "url": map[string]any{
81+ "type": "string",
82+ "description": "The URL to fetch content from",
83+ },
84+ "format": map[string]any{
85+ "type": "string",
86+ "description": "The format to return the content in (text, markdown, or html)",
87+ },
88+ "timeout": map[string]any{
89+ "type": "number",
90+ "description": "Optional timeout in seconds (max 120)",
91+ },
92+ },
93+ Required: []string{"url", "format"},
94+ }
95+}
96+
97+func (t *fetchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
98+ var params FetchParams
99+ if err := json.Unmarshal([]byte(call.Input), &params); err != nil {
100+ return NewTextErrorResponse("Failed to parse fetch parameters: " + err.Error()), nil
101+ }
102+
103+ if params.URL == "" {
104+ return NewTextErrorResponse("URL parameter is required"), nil
105+ }
106+
107+ format := strings.ToLower(params.Format)
108+ if format != "text" && format != "markdown" && format != "html" {
109+ return NewTextErrorResponse("Format must be one of: text, markdown, html"), nil
110+ }
111+
112+ if !strings.HasPrefix(params.URL, "http://") && !strings.HasPrefix(params.URL, "https://") {
113+ return NewTextErrorResponse("URL must start with http:// or https://"), nil
114+ }
115+
116+ p := permission.Default.Request(
117+ permission.CreatePermissionRequest{
118+ Path: config.WorkingDirectory(),
119+ ToolName: FetchToolName,
120+ Action: "fetch",
121+ Description: fmt.Sprintf("Fetch content from URL: %s", params.URL),
122+ Params: FetchPermissionsParams{
123+ URL: params.URL,
124+ Format: params.Format,
125+ Timeout: params.Timeout,
126+ },
127+ },
128+ )
129+
130+ if !p {
131+ return NewTextErrorResponse("Permission denied to fetch from URL: " + params.URL), nil
132+ }
133+
134+ client := t.client
135+ if params.Timeout > 0 {
136+ maxTimeout := 120 // 2 minutes
137+ if params.Timeout > maxTimeout {
138+ params.Timeout = maxTimeout
139+ }
140+ client = &http.Client{
141+ Timeout: time.Duration(params.Timeout) * time.Second,
142+ }
143+ }
144+
145+ req, err := http.NewRequestWithContext(ctx, "GET", params.URL, nil)
146+ if err != nil {
147+ return NewTextErrorResponse("Failed to create request: " + err.Error()), nil
148+ }
149+
150+ req.Header.Set("User-Agent", "termai/1.0")
151+
152+ resp, err := client.Do(req)
153+ if err != nil {
154+ return NewTextErrorResponse("Failed to execute request: " + err.Error()), nil
155+ }
156+ defer resp.Body.Close()
157+
158+ if resp.StatusCode != http.StatusOK {
159+ return NewTextErrorResponse(fmt.Sprintf("Request failed with status code: %d", resp.StatusCode)), nil
160+ }
161+
162+ maxSize := int64(5 * 1024 * 1024) // 5MB
163+ body, err := io.ReadAll(io.LimitReader(resp.Body, maxSize))
164+ if err != nil {
165+ return NewTextErrorResponse("Failed to read response body: " + err.Error()), nil
166+ }
167+
168+ content := string(body)
169+ contentType := resp.Header.Get("Content-Type")
170+
171+ switch format {
172+ case "text":
173+ if strings.Contains(contentType, "text/html") {
174+ text, err := extractTextFromHTML(content)
175+ if err != nil {
176+ return NewTextErrorResponse("Failed to extract text from HTML: " + err.Error()), nil
177+ }
178+ return NewTextResponse(text), nil
179+ }
180+ return NewTextResponse(content), nil
181+
182+ case "markdown":
183+ if strings.Contains(contentType, "text/html") {
184+ markdown, err := convertHTMLToMarkdown(content)
185+ if err != nil {
186+ return NewTextErrorResponse("Failed to convert HTML to Markdown: " + err.Error()), nil
187+ }
188+ return NewTextResponse(markdown), nil
189+ }
190+
191+ return NewTextResponse("```\n" + content + "\n```"), nil
192+
193+ case "html":
194+ return NewTextResponse(content), nil
195+
196+ default:
197+ return NewTextResponse(content), nil
198+ }
199+}
200+
201+func extractTextFromHTML(html string) (string, error) {
202+ doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
203+ if err != nil {
204+ return "", err
205+ }
206+
207+ text := doc.Text()
208+ text = strings.Join(strings.Fields(text), " ")
209+
210+ return text, nil
211+}
212+
213+func convertHTMLToMarkdown(html string) (string, error) {
214+ converter := md.NewConverter("", true, nil)
215+
216+ markdown, err := converter.ConvertString(html)
217+ if err != nil {
218+ return "", err
219+ }
220+
221+ return markdown, nil
222+}
223+
internal/tui/components/dialog/permission.go+12−5
@@ -112,10 +112,11 @@ func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
112112}
113113
114114func (p *permissionDialogCmp) render() string {
115− form := p.form.View()
116115 keyStyle := lipgloss.NewStyle().Bold(true).Foreground(styles.Rosewater)
117116 valueStyle := lipgloss.NewStyle().Foreground(styles.Peach)
118117
118+ form := p.form.View()
119+
119120 headerParts := []string{
120121 lipgloss.JoinHorizontal(lipgloss.Left, keyStyle.Render("Tool:"), " ", valueStyle.Render(p.permission.ToolName)),
121122 " ",
@@ -135,12 +136,15 @@ func (p *permissionDialogCmp) render() string {
135136 content, _ = r.Render(fmt.Sprintf("```bash\n%s\n```", pr.Command))
136137 case tools.EditToolName:
137138 pr := p.permission.Params.(tools.EditPermissionsParams)
138− headerParts = append(headerParts, keyStyle.Render("Update:"))
139+ headerParts = append(headerParts, keyStyle.Render("Update"))
139140 content, _ = r.Render(fmt.Sprintf("```diff\n%s\n```", pr.Diff))
140141 case tools.WriteToolName:
141142 pr := p.permission.Params.(tools.WritePermissionsParams)
142− headerParts = append(headerParts, keyStyle.Render("Content:"))
143+ headerParts = append(headerParts, keyStyle.Render("Content"))
143144 content, _ = r.Render(fmt.Sprintf("```diff\n%s\n```", pr.Content))
145+ case tools.FetchToolName:
146+ pr := p.permission.Params.(tools.FetchPermissionsParams)
147+ headerParts = append(headerParts, keyStyle.Render("URL: "+pr.URL))
144148 default:
145149 content, _ = r.Render(p.permission.Description)
146150 }
@@ -153,11 +157,14 @@ func (p *permissionDialogCmp) render() string {
153157 contentBorder = lipgloss.DoubleBorder()
154158 }
155159 cotentStyle := lipgloss.NewStyle().MarginTop(1).Padding(0, 1).Border(contentBorder).BorderForeground(styles.Flamingo)
156−
160+ contentFinal := cotentStyle.Render(p.contentViewPort.View())
161+ if content == "" {
162+ contentFinal = ""
163+ }
157164 return lipgloss.JoinVertical(
158165 lipgloss.Top,
159166 headerContent,
160− cotentStyle.Render(p.contentViewPort.View()),
167+ contentFinal,
161168 form,
162169 )
163170}