diff --git a/go.mod b/go.mod index 1e9ef4c..51f26ef 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,8 @@ go 1.26.4 require ( github.com/bluesky-social/indigo v0.0.0-20260629160527-dfe5578fd537 - github.com/charmbracelet/lipgloss v1.1.0 + github.com/charmbracelet/glamour v1.0.0 + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/charmbracelet/x/term v0.2.1 github.com/ipfs/go-cid v0.6.2 github.com/spf13/cobra v1.10.2 @@ -15,28 +16,35 @@ require ( ) require ( + github.com/alecthomas/chroma/v2 v2.20.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/x/ansi v0.8.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/ansi v0.10.2 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13 // indirect + github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/danieljoos/wincred v1.2.3 // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/go-querystring v1.2.0 // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/cpuid/v2 v2.4.0 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-isatty v0.0.22 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.17 // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mr-tron/base58 v1.3.0 // indirect + github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect @@ -58,12 +66,16 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yuin/goldmark v1.7.13 // indirect + github.com/yuin/goldmark-emoji v1.0.6 // indirect gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.54.0 // indirect golang.org/x/exp v0.0.0-20260603202125-055de637280b // indirect + golang.org/x/net v0.56.0 // indirect golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect golang.org/x/text v0.40.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/protobuf v1.36.11 // indirect diff --git a/go.sum b/go.sum index d4a992f..50b0193 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,15 @@ +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw= +github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= +github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= +github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 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.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bluesky-social/indigo v0.0.0-20260629160527-dfe5578fd537 h1:rHaND0argSxgbmE2ix/YuF11xXTtiP3oGUz4fS2Diqo= @@ -10,14 +18,18 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= -github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/glamour v1.0.0 h1:AWMLOVFHTsysl4WV8T8QgkQ0s/ZNZo7CiE4WKhk8l08= +github.com/charmbracelet/glamour v1.0.0/go.mod h1:DSdohgOBkMr2ZQNhw4LZxSGpx3SvpeujNoXrQyH2hxo= +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/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw= +github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8= +github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= +github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +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.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -27,6 +39,8 @@ github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMF github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg= github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -44,8 +58,12 @@ 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/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ipfs/go-cid v0.6.2 h1:VuGwJd+KJTaMJ4S4d5EEf9SXc17YUblS5axCbocn9YE= @@ -56,16 +74,21 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ= +github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mr-tron/base58 v1.3.0 h1:K6Y13R2h+dku0wOqKtecgRnBUBPrZzLZy5aIj8lCcJI= github.com/mr-tron/base58 v1.3.0/go.mod h1:2BuubE67DCSWwVfx37JWNG8emOC0sHEU4/HpcYgCLX8= +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/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= @@ -92,6 +115,7 @@ github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLA github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY= github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= +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= @@ -124,6 +148,10 @@ github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV6 github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= +github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs= +github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA= github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs= github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0= gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= @@ -140,8 +168,12 @@ golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/exp v0.0.0-20260603202125-055de637280b h1:v1uXiEBHo8QA0LiGCo7UgHMzHT4Kdfpl2zmtH5vaP1Q= golang.org/x/exp v0.0.0-20260603202125-055de637280b/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= diff --git a/internal/cli/detail.go b/internal/cli/detail.go new file mode 100644 index 0000000..d2711e2 --- /dev/null +++ b/internal/cli/detail.go @@ -0,0 +1,78 @@ +package cli + +import ( + "fmt" + "io" + "strings" + + "github.com/charmbracelet/glamour" + "github.com/charmbracelet/lipgloss" +) + +// defaultTerminalWidth is used when the terminal width can't be detected. +const defaultTerminalWidth = 80 + +type detailField struct { + label string + value string +} + +// renderDetail writes aligned label/value pairs plus an optional markdown +// body. A writer-aware lipgloss renderer keeps TTY and piped output in one +// code path; the body renders as markdown on a terminal, verbatim otherwise. +func renderDetail(writer io.Writer, fields []detailField, body string) { + renderer := lipgloss.NewRenderer(writer) + labelStyle := renderer.NewStyle().Bold(true).Width(labelColumnWidth(fields)) + + for _, field := range fields { + fmt.Fprintf(writer, "%s%s\n", labelStyle.Render(field.label+":"), field.value) + } + + if body == "" { + return + } + + fmt.Fprintln(writer) + renderMarkdown(writer, body) +} + +// labelColumnWidth returns the width to pad every label to; the +2 accounts +// for the trailing colon and one separator space. +func labelColumnWidth(fields []detailField) int { + maxLabelLength := 0 + for _, field := range fields { + if len(field.label) > maxLabelLength { + maxLabelLength = len(field.label) + } + } + return maxLabelLength + 2 +} + +// renderMarkdown renders body as styled markdown on a terminal, wrapping to +// the terminal width. When piped (or if rendering fails) it writes the raw +// body verbatim. +func renderMarkdown(writer io.Writer, body string) { + if !isTerminal(writer) { + fmt.Fprintln(writer, body) + return + } + + width := terminalWidth(writer) + if width == 0 { + width = defaultTerminalWidth + } + + renderer, err := glamour.NewTermRenderer(glamour.WithAutoStyle(), glamour.WithWordWrap(width)) + if err != nil { + fmt.Fprintln(writer, body) + return + } + + rendered, err := renderer.Render(body) + if err != nil { + fmt.Fprintln(writer, body) + return + } + + fmt.Fprintln(writer, strings.TrimSpace(rendered)) +} diff --git a/internal/cli/detail_test.go b/internal/cli/detail_test.go new file mode 100644 index 0000000..e68a362 --- /dev/null +++ b/internal/cli/detail_test.go @@ -0,0 +1,104 @@ +package cli + +import ( + "bytes" + "io" + "strings" + "testing" +) + +func TestRenderDetail(t *testing.T) { + origTerm := isTerminal + origWidth := terminalWidth + t.Cleanup(func() { + isTerminal = origTerm + terminalWidth = origWidth + }) + + fields := []detailField{ + {"Title", "add config tunables"}, + {"Author", "aly.codes"}, + {"Created", "2026-07-17T05:33:20+03:00"}, + } + const body = "likely should use viper: https://github.com/spf13/viper" + + t.Run("piped renders plain labels and verbatim body", func(t *testing.T) { + isTerminal = func(io.Writer) bool { return false } + var buf bytes.Buffer + renderDetail(&buf, fields, body) + out := buf.String() + + for _, want := range []string{"Title:", "Author:", "Created:", "add config tunables", "aly.codes"} { + if !strings.Contains(out, want) { + t.Errorf("output missing %q:\n%s", want, out) + } + } + if !strings.Contains(out, "\n"+body+"\n") { + t.Errorf("piped body should be verbatim:\n%s", out) + } + if strings.Contains(out, "\x1b") { + t.Errorf("piped output should have no ANSI escapes:\n%q", out) + } + }) + + t.Run("tty renders labels and markdown body", func(t *testing.T) { + isTerminal = func(io.Writer) bool { return true } + terminalWidth = func(io.Writer) int { return 80 } + var buf bytes.Buffer + renderDetail(&buf, fields, body) + out := buf.String() + + for _, want := range []string{"Title:", "Author:", "Created:", "add config tunables", "aly.codes"} { + if !strings.Contains(out, want) { + t.Errorf("output missing %q:\n%s", want, out) + } + } + if !strings.Contains(out, "likely should use viper") { + t.Errorf("tty body text should be present:\n%s", out) + } + }) + + t.Run("empty body omits body section", func(t *testing.T) { + isTerminal = func(io.Writer) bool { return false } + var buf bytes.Buffer + renderDetail(&buf, fields, "") + out := buf.String() + + if strings.HasSuffix(out, "\n\n") { + t.Errorf("empty body should not leave a trailing blank section:\n%q", out) + } + if strings.Contains(out, body) { + t.Errorf("empty body should not emit body text:\n%s", out) + } + }) + + t.Run("labels align to widest", func(t *testing.T) { + isTerminal = func(io.Writer) bool { return false } + var buf bytes.Buffer + unevenFields := []detailField{ + {"A", "va"}, + {"LongerLabel", "vb"}, + } + renderDetail(&buf, unevenFields, "") + lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n") + if len(lines) < 2 { + t.Fatalf("expected at least 2 lines, got %d", len(lines)) + } + colA := strings.Index(lines[0], "va") + colB := strings.Index(lines[1], "vb") + if colA < 0 || colB < 0 { + t.Fatalf("values not found:\n%s", buf.String()) + } + if colA != colB { + t.Errorf("values not aligned: col %d vs %d\n%s", colA, colB, buf.String()) + } + }) + + t.Run("label column width accounts for colon and separator", func(t *testing.T) { + got := labelColumnWidth([]detailField{{"Title", ""}, {"Author", ""}}) + const want = len("Author") + 2 + if got != want { + t.Errorf("labelColumnWidth = %d, want %d", got, want) + } + }) +} diff --git a/internal/cli/issue_view.go b/internal/cli/issue_view.go index c877b72..fd00607 100644 --- a/internal/cli/issue_view.go +++ b/internal/cli/issue_view.go @@ -1,8 +1,6 @@ package cli import ( - "fmt" - "github.com/alyraffauf/tg/internal/app" "github.com/spf13/cobra" ) @@ -29,12 +27,12 @@ directory's git origin remote.`, return err } return output(cmd, view, func(view *app.ViewResult) { - fmt.Fprintf(cmd.OutOrStdout(), "Title: %s\n", view.Title) - fmt.Fprintf(cmd.OutOrStdout(), "Author: %s\n", view.Author.Handle) - fmt.Fprintf(cmd.OutOrStdout(), "Created: %s\n", view.CreatedAt) - if view.Body != "" { - fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n", view.Body) + fields := []detailField{ + {"Title", view.Title}, + {"Author", view.Author.Handle}, + {"Created", view.CreatedAt}, } + renderDetail(cmd.OutOrStdout(), fields, view.Body) }) }, } diff --git a/internal/cli/pr_view.go b/internal/cli/pr_view.go index bfb65b5..f1c379e 100644 --- a/internal/cli/pr_view.go +++ b/internal/cli/pr_view.go @@ -1,8 +1,6 @@ package cli import ( - "fmt" - "github.com/alyraffauf/tg/internal/app" "github.com/spf13/cobra" ) @@ -29,13 +27,13 @@ directory's git origin remote.`, return err } return output(cmd, view, func(view *app.ViewResult) { - fmt.Fprintf(cmd.OutOrStdout(), "Title: %s\n", view.Title) - fmt.Fprintf(cmd.OutOrStdout(), "Author: %s\n", view.Author.Handle) - fmt.Fprintf(cmd.OutOrStdout(), "Created: %s\n", view.CreatedAt) - fmt.Fprintf(cmd.OutOrStdout(), "Branch: %s → %s\n", view.SourceBranch, view.TargetBranch) - if view.Body != "" { - fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n", view.Body) + fields := []detailField{ + {"Title", view.Title}, + {"Author", view.Author.Handle}, + {"Created", view.CreatedAt}, + {"Branch", view.SourceBranch + " → " + view.TargetBranch}, } + renderDetail(cmd.OutOrStdout(), fields, view.Body) }) }, } diff --git a/internal/cli/repo_view.go b/internal/cli/repo_view.go index d0865d7..988d94f 100644 --- a/internal/cli/repo_view.go +++ b/internal/cli/repo_view.go @@ -1,8 +1,6 @@ package cli import ( - "fmt" - "github.com/alyraffauf/tg/internal/app" "github.com/spf13/cobra" ) @@ -24,14 +22,17 @@ func newRepoViewCommand(service *app.Service) *cobra.Command { return err } return output(cmd, item, func(item *app.RepoItem) { - fmt.Fprintf(cmd.OutOrStdout(), "Name: %s\n", item.Name) - fmt.Fprintf(cmd.OutOrStdout(), "Description: %s\n", item.Description) - fmt.Fprintf(cmd.OutOrStdout(), "URI: %s\n", item.URI) - fmt.Fprintf(cmd.OutOrStdout(), "Knot: %s\n", item.Knot) - fmt.Fprintf(cmd.OutOrStdout(), "Created: %s\n", item.CreatedAt) + fields := []detailField{ + {"Name", item.Name}, + {"Description", item.Description}, + {"URI", item.URI}, + {"Knot", item.Knot}, + {"Created", item.CreatedAt}, + } if item.RepoDid != "" { - fmt.Fprintf(cmd.OutOrStdout(), "Repo DID: %s\n", item.RepoDid) + fields = append(fields, detailField{"Repo DID", item.RepoDid}) } + renderDetail(cmd.OutOrStdout(), fields, "") }) }, } diff --git a/internal/cli/string_view.go b/internal/cli/string_view.go index 43410c2..2dfec07 100644 --- a/internal/cli/string_view.go +++ b/internal/cli/string_view.go @@ -29,12 +29,15 @@ If no handle is given, views the authenticated user's string return err } return output(cmd, result, func(view *app.StringViewResult) { - fmt.Fprintf(cmd.OutOrStdout(), "Filename: %s\n", view.Filename) - fmt.Fprintf(cmd.OutOrStdout(), "Author: %s\n", view.Author.Handle) - fmt.Fprintf(cmd.OutOrStdout(), "Created: %s\n", view.CreatedAt) + fields := []detailField{ + {"Filename", view.Filename}, + {"Author", view.Author.Handle}, + {"Created", view.CreatedAt}, + } if view.Description != "" { - fmt.Fprintf(cmd.OutOrStdout(), "Description: %s\n", view.Description) + fields = append(fields, detailField{"Description", view.Description}) } + renderDetail(cmd.OutOrStdout(), fields, "") fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n", view.Contents) }) }, diff --git a/nix/tg.nix b/nix/tg.nix index 89231df..a619ed1 100644 --- a/nix/tg.nix +++ b/nix/tg.nix @@ -10,7 +10,7 @@ buildGoModule { version = "dev"; src = ../.; proxyVendor = true; - vendorHash = "sha256-a3O6J1fwbbb33vCL33c8DZYtrFHSwf0v/VwKhx0YLdg="; + vendorHash = "sha256-Vh4uhJOXGMYoX7haUD6r3zti1KSb/uO5+TOtn0NrcCk="; subPackages = ["cmd/tg"]; nativeBuildInputs = [installShellFiles];