d0690066a89ea543d3fa4e81af0754f4cb3e53dd
- Author
- Toby Padilla <toby@charm.sh>
- Committer
- Toby Padilla <toby@charm.sh>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/.gitignore b/.gitignore
2new file mode 100644
3index 0000000000000000000000000000000000000000..bf4ddda60cd929c4ebdbc793e029a3fb0cbac866
4--- /dev/null
5+++ b/.gitignore
6@@ -0,0 +1,2 @@
7+smoothie
8+.ssh
9diff --git a/go.mod b/go.mod
10new file mode 100644
11index 0000000000000000000000000000000000000000..932e7c39ea4886cbb53140cf6d179301b4d660bc
12--- /dev/null
13+++ b/go.mod
14@@ -0,0 +1,14 @@
15+module smoothie
16+
17+go 1.16
18+
19+replace github.com/charmbracelet/charm => ../charm
20+
21+require (
22+ github.com/charmbracelet/bubbletea v0.14.0
23+ github.com/charmbracelet/charm v0.8.6
24+ github.com/charmbracelet/lipgloss v0.2.1
25+ github.com/gliderlabs/ssh v0.3.3
26+ github.com/meowgorithm/babyenv v1.3.0
27+ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
28+)
29diff --git a/go.sum b/go.sum
30new file mode 100644
31index 0000000000000000000000000000000000000000..66e43f9de5f914472089c892ddf2af703d20fe38
32--- /dev/null
33+++ b/go.sum
34@@ -0,0 +1,346 @@
35+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
36+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
37+github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
38+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
39+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
40+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
41+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
42+github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
43+github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
44+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
45+github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
46+github.com/auth0/go-jwt-middleware v1.0.0/go.mod h1:nX2S0GmCyl087kdNSSItfOvMYokq5PSTG1yGIP5Le4U=
47+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
48+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
49+github.com/calmh/randomart v1.1.0/go.mod h1:DQUbPVyP+7PAs21w/AnfMKG5NioxS3TbZ2F9MSK/jFM=
50+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
51+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
52+github.com/charmbracelet/bubbles v0.8.0/go.mod h1:5WX1sSSjNCgCrzvRMN/z23HxvWaa+AI16Ch0KPZPeDs=
53+github.com/charmbracelet/bubbletea v0.13.1/go.mod h1:tp9tr9Dadh0PLhgiwchE5zZJXm5543JYjHG9oY+5qSg=
54+github.com/charmbracelet/bubbletea v0.14.0 h1:SBbtRPkZ/wGYyTAn2zrBERes/nwzxZR5QdJ5nvczmFw=
55+github.com/charmbracelet/bubbletea v0.14.0/go.mod h1:b5lOf5mLjMg1tRn1HVla54guZB+jvsyV0yYAQja95zE=
56+github.com/charmbracelet/lipgloss v0.1.2/go.mod h1:5D8zradw52m7QmxRF6QgwbwJi9je84g8MkWiGN07uKg=
57+github.com/charmbracelet/lipgloss v0.2.1 h1:knjqLRtlcDsxss6i0CUhNLuXrNW3TQhFDXlXII2UaZA=
58+github.com/charmbracelet/lipgloss v0.2.1/go.mod h1:uiZUfrHLQN14I0lJ5591WtcHyY1X76pOIPSaRKPY6dE=
59+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
60+github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
61+github.com/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc=
62+github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
63+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
64+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
65+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
66+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
67+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
68+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
69+github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8=
70+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
71+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
72+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
73+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
74+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
75+github.com/dgraph-io/badger/v3 v3.2011.1/go.mod h1:0rLLrQpKVQAL0or/lBLMQznhr6dWWX7h5AKnmnqx268=
76+github.com/dgraph-io/ristretto v0.0.4-0.20210122082011-bb5d392ed82d/go.mod h1:tv2ec8nA7vRpSYX7/MbP52ihrUMXIHit54CQMq8npXQ=
77+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
78+github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
79+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
80+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
81+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
82+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
83+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
84+github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
85+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
86+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
87+github.com/gliderlabs/ssh v0.3.0/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
88+github.com/gliderlabs/ssh v0.3.3 h1:mBQ8NiOgDkINJrZtoizkC3nDNYgSaWtxyem6S2XHBtA=
89+github.com/gliderlabs/ssh v0.3.3/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914=
90+github.com/go-delve/delve v1.5.0/go.mod h1:c6b3a1Gry6x8a4LGCe/CWzrocrfaHvkUxCj3k4bvSUQ=
91+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
92+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
93+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
94+github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
95+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
96+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
97+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
98+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
99+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
100+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
101+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
102+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
103+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
104+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
105+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
106+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
107+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
108+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
109+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
110+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
111+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
112+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
113+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
114+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
115+github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
116+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
117+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
118+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
119+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
120+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
121+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
122+github.com/google/go-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
123+github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
124+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
125+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
126+github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
127+github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
128+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
129+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
130+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
131+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
132+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
133+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
134diff --git a/main.go b/main.go
135new file mode 100644
136index 0000000000000000000000000000000000000000..46830542f28470051d12e571a464322d88dffef2
137--- /dev/null
138+++ b/main.go
139@@ -0,0 +1,28 @@
140+package main
141+
142+import (
143+ "smoothie/tui"
144+
145+ "github.com/meowgorithm/babyenv"
146+)
147+
148+type Config struct {
149+ KeyPath string `env:"SMOOTHIE_KEY_PATH" default:".ssh/smoothie_server_ed25519"`
150+ Port int `env:"SMOOTHIE_PORT" default:"23231"`
151+}
152+
153+func main() {
154+ var cfg Config
155+ err := babyenv.Parse(&cfg)
156+ if err != nil {
157+ panic(err)
158+ }
159+ s, err := NewServer(cfg.Port, cfg.KeyPath, tui.SessionHandler)
160+ if err != nil {
161+ panic(err)
162+ }
163+ err = s.Start()
164+ if err != nil {
165+ panic(err)
166+ }
167+}
168diff --git a/server.go b/server.go
169new file mode 100644
170index 0000000000000000000000000000000000000000..7ebaeb48a1589c0b25697fc2ba73483236e5ef65
171--- /dev/null
172+++ b/server.go
173@@ -0,0 +1,79 @@
174+package main
175+
176+import (
177+ "fmt"
178+ "log"
179+ "path/filepath"
180+ "strings"
181+
182+ tea "github.com/charmbracelet/bubbletea"
183+ "github.com/charmbracelet/charm/keygen"
184+ "github.com/gliderlabs/ssh"
185+ gossh "golang.org/x/crypto/ssh"
186+)
187+
188+type SessionHandler func(ssh.Session) (tea.Model, error)
189+
190+type Server struct {
191+ server *ssh.Server
192+ key gossh.PublicKey
193+ handler SessionHandler
194+}
195+
196+func NewServer(port int, keyPath string, handler SessionHandler) (*Server, error) {
197+ s := &Server{
198+ server: &ssh.Server{},
199+ handler: handler,
200+ }
201+ s.server.Version = "OpenSSH_7.6p1"
202+ s.server.Addr = fmt.Sprintf(":%d", port)
203+ s.server.Handler = s.sessionHandler
204+ s.server.PasswordHandler = s.passHandler
205+ s.server.PublicKeyHandler = s.authHandler
206+ kps := strings.Split(keyPath, string(filepath.Separator))
207+ kp := strings.Join(kps[:len(kps)-1], string(filepath.Separator))
208+ n := strings.TrimRight(kps[len(kps)-1], "_ed25519")
209+ _, err := keygen.NewSSHKeyPair(kp, n, nil, "ed25519")
210+ if err != nil {
211+ return nil, err
212+ }
213+ k := ssh.HostKeyFile(keyPath)
214+ err = s.server.SetOption(k)
215+ if err != nil {
216+ return nil, err
217+ }
218+ return s, nil
219+}
220+
221+func (srv *Server) sessionHandler(s ssh.Session) {
222+ hpk := s.PublicKey() != nil
223+ log.Printf("%s connect %v %v\n", s.RemoteAddr().String(), hpk, s.Command())
224+ m, err := srv.handler(s)
225+ if err != nil {
226+ log.Printf("%s error %v %s\n", s.RemoteAddr().String(), hpk, err)
227+ s.Exit(1)
228+ return
229+ }
230+ if m != nil {
231+ p := tea.NewProgram(m, tea.WithInput(s), tea.WithOutput(s))
232+ err = p.Start()
233+ if err != nil {
234+ log.Printf("%s error %v %s\n", s.RemoteAddr().String(), hpk, err)
235+ s.Exit(1)
236+ return
237+ }
238+ }
239+ log.Printf("%s disconnect %v %v\n", s.RemoteAddr().String(), hpk, s.Command())
240+}
241+
242+func (srv *Server) authHandler(ctx ssh.Context, key ssh.PublicKey) bool {
243+ return true
244+}
245+
246+func (srv *Server) passHandler(ctx ssh.Context, pass string) bool {
247+ return true
248+}
249+
250+func (srv *Server) Start() error {
251+ return srv.server.ListenAndServe()
252+}
253diff --git a/tui/commands.go b/tui/commands.go
254new file mode 100644
255index 0000000000000000000000000000000000000000..2ca8f35995743e69a7bef225d931ac6bc990cfe2
256--- /dev/null
257+++ b/tui/commands.go
258@@ -0,0 +1,14 @@
259+package tui
260+
261+import (
262+ tea "github.com/charmbracelet/bubbletea"
263+)
264+
265+type windowMsg struct{}
266+
267+func (m *Model) windowChangesCmd() tea.Msg {
268+ w := <-m.windowChanges
269+ m.width = w.Width
270+ m.height = w.Height
271+ return windowMsg{}
272+}
273diff --git a/tui/model.go b/tui/model.go
274new file mode 100644
275index 0000000000000000000000000000000000000000..4a0e1d2366b58bf3745a7ebbbf6ba5d4b77630b2
276--- /dev/null
277+++ b/tui/model.go
278@@ -0,0 +1,98 @@
279+package tui
280+
281+import (
282+ "fmt"
283+
284+ tea "github.com/charmbracelet/bubbletea"
285+ "github.com/gliderlabs/ssh"
286+)
287+
288+type sessionState int
289+
290+const (
291+ startState sessionState = iota
292+ errorState
293+ quittingState
294+ quitState
295+)
296+
297+type stateMsg struct{ state sessionState }
298+type infoMsg struct{ text string }
299+type errMsg struct{ err error }
300+
301+func (e errMsg) Error() string {
302+ return e.err.Error()
303+}
304+
305+func SessionHandler(s ssh.Session) (tea.Model, error) {
306+ pty, changes, active := s.Pty()
307+ if !active {
308+ return nil, fmt.Errorf("you need to do this from a terminal with PTY support")
309+ }
310+ return NewModel(pty.Window.Width, pty.Window.Height, changes), nil
311+}
312+
313+type Model struct {
314+ state sessionState
315+ error string
316+ info string
317+ width int
318+ height int
319+ windowChanges <-chan ssh.Window
320+}
321+
322+func NewModel(width int, height int, windowChanges <-chan ssh.Window) *Model {
323+ m := &Model{
324+ width: width,
325+ height: height,
326+ windowChanges: windowChanges,
327+ }
328+ m.state = startState
329+ return m
330+}
331+
332+func (m *Model) Init() tea.Cmd {
333+ return tea.Batch(m.windowChangesCmd, tea.HideCursor)
334+}
335+
336+func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
337+ cmds := make([]tea.Cmd, 0)
338+ // Always allow state, error, info, window resize and quit messages
339+ switch msg := msg.(type) {
340+ case stateMsg:
341+ m.state = msg.state
342+ case tea.KeyMsg:
343+ switch msg.String() {
344+ case "q", "ctrl+c":
345+ return m, tea.Quit
346+ }
347+ case errMsg:
348+ m.error = msg.Error()
349+ m.state = errorState
350+ return m, nil
351+ case infoMsg:
352+ m.info = msg.text
353+ case windowMsg:
354+ cmds = append(cmds, m.windowChangesCmd)
355+ case tea.WindowSizeMsg:
356+ m.width = msg.Width
357+ m.height = msg.Height
358+ }
359+ return m, tea.Batch(cmds...)
360+}
361+
362+func (m *Model) View() string {
363+ pad := 6
364+ h := headerStyle.Width(m.width - pad).Render("Smoothie")
365+ f := footerStyle.Render(m.info)
366+ s := ""
367+ content := ""
368+ switch m.state {
369+ case errorState:
370+ s += errorStyle.Render(fmt.Sprintf("Bummer: %s", m.error))
371+ default:
372+ s = normalStyle.Render(fmt.Sprintf("Doing something weird %d", m.state))
373+ }
374+ content = h + "\n" + s + "\n" + f
375+ return appBoxStyle.Render(content)
376+}
377diff --git a/tui/style.go b/tui/style.go
378new file mode 100644
379index 0000000000000000000000000000000000000000..6c219b1117230fc47c63efd21d90d117151ca91a
380--- /dev/null
381+++ b/tui/style.go
382@@ -0,0 +1,30 @@
383+package tui
384+
385+import (
386+ "github.com/charmbracelet/lipgloss"
387+)
388+
389+var appBoxStyle = lipgloss.NewStyle().
390+ PaddingLeft(2).
391+ PaddingRight(2).
392+ MarginBottom(1)
393+
394+var headerStyle = lipgloss.NewStyle().
395+ Foreground(lipgloss.Color("#670083")).
396+ Align(lipgloss.Right).
397+ Bold(true)
398+
399+var normalStyle = lipgloss.NewStyle().
400+ Foreground(lipgloss.Color("#FFFFFF"))
401+
402+var footerStyle = lipgloss.NewStyle().
403+ BorderStyle(lipgloss.Border{Left: ">"}).
404+ BorderForeground(lipgloss.Color("#6D6D6D")).
405+ BorderLeft(true).
406+ Foreground(lipgloss.Color("#373737")).
407+ PaddingLeft(1).
408+ MarginLeft(1).
409+ Bold(true)
410+
411+var errorStyle = lipgloss.NewStyle().
412+ Foreground(lipgloss.Color("#FF00000"))