pico / feat(pubsub): if no channel is provided, randomly gen one #23 rss

accepted · opened on 2024-09-24T03:35:18Z by erock
Help
# add changes to patch request
git format-patch main --stdout | ssh pr.pico.sh pr add 23
# add review to patch request
git format-patch main --stdout | ssh pr.pico.sh pr add --review 23
# remove patchset
ssh pr.pico.sh ps rm ps-x
# checkout all patches
ssh pr.pico.sh pr print 23 | git am -3
# print a diff between the last two patches in a patch request
ssh pr.pico.sh pr diff 23
# accept PR
ssh pr.pico.sh pr accept 23
# close PR
ssh pr.pico.sh pr close 23

Logs

erock created pr with ps-39 on 2024-08-31T03:04:33Z
erock added ps-40 on 2024-08-31T15:11:46Z
erock changed status on 2024-08-31T15:11:46Z {"status":"open"}
erock changed status on 2024-11-06T15:04:53Z {"status":"accepted"}

Patchsets

ps-39 by erock on 2024-08-31T03:04:33Z
Range Diff ↕
1: 5f908b4 = 1: 5f908b4 feat(imgs): pubsub
-: ------- > 2: a8dfc59 chore: comment local dev since i keep forgetting how to do it
ps-40 by erock on 2024-08-31T15:11:46Z

Patchset ps-39

feat(imgs): pubsub

Eric Bower
2024-08-31T03:03:08Z
go.mod
+20 -17
go.sum
+32 -30
imgs/cli.go
+11 -0
imgs/ssh.go
+44 -43

feat(imgs): pubsub

go.mod
+20 -17
  1diff --git a/go.mod b/go.mod
  2index 93f63f9..958cefb 100644
  3--- a/go.mod
  4+++ b/go.mod
  5@@ -1,6 +1,6 @@
  6 module github.com/picosh/pico
  7 
  8-go 1.22
  9+go 1.22.6
 10 
 11 // replace github.com/picosh/tunkit => ../tunkit
 12 
 13@@ -8,6 +8,8 @@ go 1.22
 14 
 15 // replace github.com/picosh/pobj => ../pobj
 16 
 17+// replace github.com/picosh/pubsub => ../pubsub
 18+
 19 replace git.sr.ht/~delthas/senpai => github.com/picosh/senpai v0.0.0-20240503200611-af89e73973b0
 20 
 21 replace github.com/gdamore/tcell/v2 => github.com/delthas/tcell/v2 v2.4.1-0.20230710100648-1489e78d90fb
 22@@ -17,22 +19,24 @@ require (
 23 	github.com/alecthomas/chroma/v2 v2.14.0
 24 	github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
 25 	github.com/charmbracelet/bubbles v0.18.0
 26-	github.com/charmbracelet/bubbletea v0.26.4
 27+	github.com/charmbracelet/bubbletea v0.27.0
 28 	github.com/charmbracelet/glamour v0.7.0
 29-	github.com/charmbracelet/lipgloss v0.11.0
 30+	github.com/charmbracelet/lipgloss v0.12.1
 31 	github.com/charmbracelet/promwish v0.7.0
 32-	github.com/charmbracelet/ssh v0.0.0-20240531200420-04041eb117c7
 33-	github.com/charmbracelet/wish v1.4.0
 34+	github.com/charmbracelet/ssh v0.0.0-20240725163421-eb71b85b27aa
 35+	github.com/charmbracelet/wish v1.4.2
 36 	github.com/google/go-cmp v0.6.0
 37+	github.com/google/uuid v1.6.0
 38 	github.com/gorilla/feeds v1.1.2
 39 	github.com/lib/pq v1.10.9
 40 	github.com/microcosm-cc/bluemonday v1.0.26
 41 	github.com/minio/minio-go/v7 v7.0.70
 42 	github.com/mmcdole/gofeed v1.3.0
 43 	github.com/muesli/reflow v0.3.0
 44-	github.com/muesli/termenv v0.15.2
 45+	github.com/muesli/termenv v0.15.3-0.20240509142007-81b8f94111d5
 46 	github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577
 47 	github.com/picosh/pobj v0.0.0-20240709135546-27097077b26a
 48+	github.com/picosh/pubsub v0.0.0-20240831030146-b59a4c387b79
 49 	github.com/picosh/send v0.0.0-20240820031602-5d3b1a4494cc
 50 	github.com/picosh/tunkit v0.0.0-20240709033345-8315d4f3cd0e
 51 	github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
 52@@ -46,7 +50,7 @@ require (
 53 	go.abhg.dev/goldmark/anchor v0.1.1
 54 	go.abhg.dev/goldmark/hashtag v0.3.1
 55 	go.abhg.dev/goldmark/toc v0.10.0
 56-	golang.org/x/crypto v0.23.0
 57+	golang.org/x/crypto v0.26.0
 58 	gopkg.in/yaml.v2 v2.4.0
 59 )
 60 
 61@@ -61,12 +65,12 @@ require (
 62 	github.com/aymerick/douceur v0.2.0 // indirect
 63 	github.com/beorn7/perks v1.0.1 // indirect
 64 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 65-	github.com/charmbracelet/keygen v0.5.0 // indirect
 66+	github.com/charmbracelet/keygen v0.5.1 // indirect
 67 	github.com/charmbracelet/log v0.4.0 // indirect
 68-	github.com/charmbracelet/x/ansi v0.1.2 // indirect
 69+	github.com/charmbracelet/x/ansi v0.1.4 // indirect
 70+	github.com/charmbracelet/x/conpty v0.1.0 // indirect
 71 	github.com/charmbracelet/x/errors v0.0.0-20240603171354-f37319acd141 // indirect
 72-	github.com/charmbracelet/x/exp/term v0.0.0-20240603171354-f37319acd141 // indirect
 73-	github.com/charmbracelet/x/input v0.1.2 // indirect
 74+	github.com/charmbracelet/x/input v0.1.3 // indirect
 75 	github.com/charmbracelet/x/term v0.1.1 // indirect
 76 	github.com/charmbracelet/x/termios v0.1.0 // indirect
 77 	github.com/charmbracelet/x/windows v0.1.2 // indirect
 78@@ -96,7 +100,6 @@ require (
 79 	github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
 80 	github.com/golang/protobuf v1.5.4 // indirect
 81 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 82-	github.com/google/uuid v1.6.0 // indirect
 83 	github.com/gorilla/css v1.0.1 // indirect
 84 	github.com/json-iterator/go v1.1.12 // indirect
 85 	github.com/klauspost/compress v1.17.8 // indirect
 86@@ -143,11 +146,11 @@ require (
 87 	github.com/yusufpapurcu/wmi v1.2.4 // indirect
 88 	golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
 89 	golang.org/x/net v0.25.0 // indirect
 90-	golang.org/x/sync v0.7.0 // indirect
 91-	golang.org/x/sys v0.20.0 // indirect
 92-	golang.org/x/term v0.20.0 // indirect
 93-	golang.org/x/text v0.15.0 // indirect
 94-	golang.org/x/time v0.5.0 // indirect
 95+	golang.org/x/sync v0.8.0 // indirect
 96+	golang.org/x/sys v0.24.0 // indirect
 97+	golang.org/x/term v0.23.0 // indirect
 98+	golang.org/x/text v0.17.0 // indirect
 99+	golang.org/x/time v0.6.0 // indirect
100 	google.golang.org/protobuf v1.34.1 // indirect
101 	gopkg.in/ini.v1 v1.67.0 // indirect
102 	mvdan.cc/xurls/v2 v2.5.0 // indirect
go.sum
+32 -30
  1diff --git a/go.sum b/go.sum
  2index 433ff09..bf3b25e 100644
  3--- a/go.sum
  4+++ b/go.sum
  5@@ -31,30 +31,30 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
  6 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
  7 github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
  8 github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
  9-github.com/charmbracelet/bubbletea v0.26.4 h1:2gDkkzLZaTjMl/dQBpNVtnvcCxsh/FCkimep7FC9c40=
 10-github.com/charmbracelet/bubbletea v0.26.4/go.mod h1:P+r+RRA5qtI1DOHNFn0otoNwB4rn+zNAzSj/EXz6xU0=
 11+github.com/charmbracelet/bubbletea v0.27.0 h1:Mznj+vvYuYagD9Pn2mY7fuelGvP0HAXtZYGgRBCbHvU=
 12+github.com/charmbracelet/bubbletea v0.27.0/go.mod h1:5MdP9XH6MbQkgGhnlxUqCNmBXf9I74KRQ8HIidRxV1Y=
 13 github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng=
 14 github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps=
 15-github.com/charmbracelet/keygen v0.5.0 h1:XY0fsoYiCSM9axkrU+2ziE6u6YjJulo/b9Dghnw6MZc=
 16-github.com/charmbracelet/keygen v0.5.0/go.mod h1:DfvCgLHxZ9rJxdK0DGw3C/LkV4SgdGbnliHcObV3L+8=
 17-github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
 18-github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
 19+github.com/charmbracelet/keygen v0.5.1 h1:zBkkYPtmKDVTw+cwUyY6ZwGDhRxXkEp0Oxs9sqMLqxI=
 20+github.com/charmbracelet/keygen v0.5.1/go.mod h1:zznJVmK/GWB6dAtjluqn2qsttiCBhA5MZSiwb80fcHw=
 21+github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs=
 22+github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8=
 23 github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
 24 github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
 25 github.com/charmbracelet/promwish v0.7.0 h1:oaMH+ey6W4DDIv1xucS8jL1ik/Q46qxjNXlh6XxEm+s=
 26 github.com/charmbracelet/promwish v0.7.0/go.mod h1:WbRJN9irg8LmsBU8G2rFF8md9O3rSg63qrnqquP/+cs=
 27-github.com/charmbracelet/ssh v0.0.0-20240531200420-04041eb117c7 h1:BTAPOJlz5Q9TUl3HxAc2g04KS9DzSUtKapvXg7pRhwE=
 28-github.com/charmbracelet/ssh v0.0.0-20240531200420-04041eb117c7/go.mod h1:yDZEh+wUCh2sszpTfFQ494YHQpHaJQ0zsIy4BTr1gVw=
 29-github.com/charmbracelet/wish v1.4.0 h1:pL1uVP/YuYgJheHEj98teZ/n6pMYnmlZq/fcHvomrfc=
 30-github.com/charmbracelet/wish v1.4.0/go.mod h1:ew4/MjJVfW/akEO9KmrQHQv1F7bQRGscRMrA+KtovTk=
 31-github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
 32-github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
 33+github.com/charmbracelet/ssh v0.0.0-20240725163421-eb71b85b27aa h1:6rePgmsJguB6Z7Y55stsEVDlWFJoUpQvOX4mdnBjgx4=
 34+github.com/charmbracelet/ssh v0.0.0-20240725163421-eb71b85b27aa/go.mod h1:LmMZag2g7ILMmWtDmU7dIlctUopwmb73KpPzj0ip1uk=
 35+github.com/charmbracelet/wish v1.4.2 h1:H2BKXewugK9Al75wST9h0hpQ95h981RZ5rtimDpygPQ=
 36+github.com/charmbracelet/wish v1.4.2/go.mod h1:3Bzq7qMU2LTvdaM61KrCnhrzGP92D/Ru7CasrpyZmzY=
 37+github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM=
 38+github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
 39+github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
 40+github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
 41 github.com/charmbracelet/x/errors v0.0.0-20240603171354-f37319acd141 h1:9S0jGt7wdFksixTHjDn30qWOUu6LPmPidnO42roe+Ek=
 42 github.com/charmbracelet/x/errors v0.0.0-20240603171354-f37319acd141/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
 43-github.com/charmbracelet/x/exp/term v0.0.0-20240603171354-f37319acd141 h1:/uoDqgv4S+M5XVUrkSxSq5rARNAbSXVRmRqnwXzIKlQ=
 44-github.com/charmbracelet/x/exp/term v0.0.0-20240603171354-f37319acd141/go.mod h1:YBotIGhfoWhHDlnUpJMkjebGV2pdGRCn1Y4/Nk/vVcU=
 45-github.com/charmbracelet/x/input v0.1.2 h1:QJAZr33eOhDowkkEQ24rsJy4Llxlm+fRDf/cQrmqJa0=
 46-github.com/charmbracelet/x/input v0.1.2/go.mod h1:LGBim0maUY4Pitjn/4fHnuXb4KirU3DODsyuHuXdOyA=
 47+github.com/charmbracelet/x/input v0.1.3 h1:oy4TMhyGQsYs/WWJwu1ELUMFnjiUAXwtDf048fHbCkg=
 48+github.com/charmbracelet/x/input v0.1.3/go.mod h1:1gaCOyw1KI9e2j00j/BBZ4ErzRZqa05w0Ghn83yIhKU=
 49 github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI=
 50 github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw=
 51 github.com/charmbracelet/x/termios v0.1.0 h1:y4rjAHeFksBAfGbkRDmVinMg7x7DELIGAFbdNvxg97k=
 52@@ -210,8 +210,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
 53 github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
 54 github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
 55 github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
 56-github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
 57-github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
 58+github.com/muesli/termenv v0.15.3-0.20240509142007-81b8f94111d5 h1:NiONcKK0EV5gUZcnCiPMORaZA0eBDc+Fgepl9xl4lZ8=
 59+github.com/muesli/termenv v0.15.3-0.20240509142007-81b8f94111d5/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
 60 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577 h1:hVmVNttSLNloGsbFKVXAUHonXTd8KKrv30U/8UkloKI=
 61 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577/go.mod h1:G3Cu1AW+dmRLDFpOi8eUAfc3cGoRHUjTkGjeRcndgl4=
 62 github.com/neurosnap/go-jpeg-image-structure v0.0.0-20221010133817-70b1c1ff679e h1:76Dng5ms0fR+26doKZAvNqhi2UPfnLxGfPIDEr+BBlM=
 63@@ -224,6 +224,8 @@ github.com/picosh/go-rsync-receiver v0.0.0-20240709135253-1daf4b12a9fc h1:bvcsoO
 64 github.com/picosh/go-rsync-receiver v0.0.0-20240709135253-1daf4b12a9fc/go.mod h1:i0iR3W4GSm1PuvVxB9OH32E5jP+CYkVb2NQSe0JCtlo=
 65 github.com/picosh/pobj v0.0.0-20240709135546-27097077b26a h1:Cr1xODiyd/SjjBRtYA9VX6Do3D+w+DansQzkb4NGeyA=
 66 github.com/picosh/pobj v0.0.0-20240709135546-27097077b26a/go.mod h1:VIkR1MZBvxSK2OO47jikxikAO/sKb/vTmXX5ZuYTIvo=
 67+github.com/picosh/pubsub v0.0.0-20240831030146-b59a4c387b79 h1:QqOtaebSRNXS0FA74bRJUHEESMM4KTmOZ3otXjS+Pss=
 68+github.com/picosh/pubsub v0.0.0-20240831030146-b59a4c387b79/go.mod h1:FKC8uot+40iXmuDzTfbxYDG5PIc3ghwkmP2iItBKH0I=
 69 github.com/picosh/send v0.0.0-20240820031602-5d3b1a4494cc h1:IIsJuAFG2ju3cygKVKTIsYYZf21q5S3Dr1H4fGbfgJg=
 70 github.com/picosh/send v0.0.0-20240820031602-5d3b1a4494cc/go.mod h1:RAgLDK3LrDK6pNeXtU9tjo28obl5DxShcTUk2nm/KCM=
 71 github.com/picosh/senpai v0.0.0-20240503200611-af89e73973b0 h1:pBRIbiCj7K6rGELijb//dYhyCo8A3fvxW5dijrJVtjs=
 72@@ -322,8 +324,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
 73 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 74 golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
 75 golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
 76-golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
 77-golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 78+golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
 79+golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
 80 golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
 81 golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
 82 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 83@@ -349,8 +351,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
 84 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 85 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 86 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 87-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
 88-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 89+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
 90+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 91 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 92 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 93 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 94@@ -368,8 +370,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 95 golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 96 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 97 golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 98-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
 99-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
100+golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
101+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
102 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
103 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
104 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
105@@ -377,8 +379,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
106 golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
107 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
108 golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
109-golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
110-golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
111+golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
112+golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
113 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
114 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
115 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
116@@ -386,11 +388,11 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
117 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
118 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
119 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
120-golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
121-golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
122+golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
123+golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
124 golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
125-golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
126-golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
127+golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
128+golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
129 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
130 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
131 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
imgs/cli.go
+11 -0
 1diff --git a/imgs/cli.go b/imgs/cli.go
 2index 78cc3d0..eee718b 100644
 3--- a/imgs/cli.go
 4+++ b/imgs/cli.go
 5@@ -12,11 +12,13 @@ import (
 6 
 7 	"github.com/charmbracelet/ssh"
 8 	"github.com/charmbracelet/wish"
 9+	"github.com/google/uuid"
10 	"github.com/picosh/pico/db"
11 	"github.com/picosh/pico/shared"
12 	"github.com/picosh/pico/shared/storage"
13 	"github.com/picosh/pico/tui/common"
14 	sst "github.com/picosh/pobj/storage"
15+	psub "github.com/picosh/pubsub"
16 	"github.com/picosh/send/send/utils"
17 )
18 
19@@ -186,12 +188,14 @@ type CliHandler struct {
20 	Logger      *slog.Logger
21 	Storage     storage.StorageServe
22 	RegistryUrl string
23+	PubSub      *psub.Cfg
24 }
25 
26 func WishMiddleware(handler *CliHandler) wish.Middleware {
27 	dbpool := handler.DBPool
28 	log := handler.Logger
29 	st := handler.Storage
30+	pubsub := handler.PubSub
31 
32 	return func(next ssh.Handler) ssh.Handler {
33 		return func(sesh ssh.Session) {
34@@ -254,6 +258,13 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
35 				opts.notice()
36 				opts.bail(err)
37 				return
38+			} else if cmd == "sub" {
39+				pubsub.PubSub.Sub(&psub.Subscriber{
40+					ID:      uuid.NewString(),
41+					Name:    fmt.Sprintf("%s@%s", user.Name, repoName),
42+					Session: sesh,
43+					Chan:    make(chan error),
44+				})
45 			} else {
46 				next(sesh)
47 				return
imgs/ssh.go
+44 -43
  1diff --git a/imgs/ssh.go b/imgs/ssh.go
  2index ef9fa87..08f9b12 100644
  3--- a/imgs/ssh.go
  4+++ b/imgs/ssh.go
  5@@ -24,6 +24,7 @@ import (
  6 	"github.com/picosh/pico/db/postgres"
  7 	"github.com/picosh/pico/shared"
  8 	"github.com/picosh/pico/shared/storage"
  9+	psub "github.com/picosh/pubsub"
 10 	"github.com/picosh/tunkit"
 11 )
 12 
 13@@ -40,6 +41,19 @@ func setUserCtx(ctx ssh.Context, user *db.User) {
 14 	ctx.SetValue(ctxUserKey{}, user)
 15 }
 16 
 17+type ctxPubkeyKey struct{}
 18+
 19+func getPubkeyCtx(ctx ssh.Context) (*ssh.PublicKey, error) {
 20+	user, ok := ctx.Value(ctxUserKey{}).(*ssh.PublicKey)
 21+	if user == nil || !ok {
 22+		return user, fmt.Errorf("pubkey not set on `ssh.Context()` for connection")
 23+	}
 24+	return user, nil
 25+}
 26+func setPubkeyCtx(ctx ssh.Context, pk *ssh.PublicKey) {
 27+	ctx.SetValue(ctxPubkeyKey{}, pk)
 28+}
 29+
 30 func AuthHandler(dbh db.DB, log *slog.Logger) func(ssh.Context, ssh.PublicKey) bool {
 31 	return func(ctx ssh.Context, key ssh.PublicKey) bool {
 32 		kk, err := shared.KeyForKeyText(key)
 33@@ -48,6 +62,8 @@ func AuthHandler(dbh db.DB, log *slog.Logger) func(ssh.Context, ssh.PublicKey) b
 34 			return false
 35 		}
 36 
 37+		setPubkeyCtx(ctx, &key)
 38+
 39 		user, err := dbh.FindUserForKey("", kk)
 40 		if err != nil {
 41 			log.Error("user not found", "err", err)
 42@@ -79,7 +95,7 @@ func (e *ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 43 	http.Error(w, e.Err.Error(), http.StatusInternalServerError)
 44 }
 45 
 46-func createServeMux(handler *CliHandler, pubsub *tunkit.PubSubHandler) func(ctx ssh.Context) http.Handler {
 47+func createServeMux(handler *CliHandler, pubsub *psub.Cfg) func(ctx ssh.Context) http.Handler {
 48 	return func(ctx ssh.Context) http.Handler {
 49 		router := http.NewServeMux()
 50 
 51@@ -89,12 +105,6 @@ func createServeMux(handler *CliHandler, pubsub *tunkit.PubSubHandler) func(ctx
 52 			slug = user.Name
 53 		}
 54 
 55-		pubkeys, err := handler.DBPool.FindKeysForUser(user)
 56-		if err != nil {
 57-			handler.Logger.Error("cant get pubkeys for user", "err", err)
 58-			return router
 59-		}
 60-
 61 		proxy := httputil.NewSingleHostReverseProxy(&url.URL{
 62 			Scheme: "http",
 63 			Host:   handler.RegistryUrl,
 64@@ -221,41 +231,26 @@ func createServeMux(handler *CliHandler, pubsub *tunkit.PubSubHandler) func(ctx
 65 
 66 			if r.Request.Method == http.MethodPut && strings.Contains(r.Request.URL.Path, "/manifests/") {
 67 				digest := r.Header.Get("Docker-Content-Digest")
 68-				forwards := pubsub.GetForwards()
 69-				for _, rf := range forwards {
 70-					ck, err := shared.KeyForKeyText(rf.Pubkey)
 71-					if err != nil {
 72-						continue
 73-					}
 74-					found := false
 75-					for _, pk := range pubkeys {
 76-						if pk.Key == ck {
 77-							found = true
 78-						}
 79-					}
 80-					// event corresponds to a different user, skip
 81-					if !found {
 82-						continue
 83-					}
 84+				// [ ]/v2/erock/alpine/manifests/latest
 85+				splitPath := strings.Split(r.Request.URL.Path, "/")
 86+				img := splitPath[3]
 87+				tag := splitPath[5]
 88+
 89+				furl := fmt.Sprintf(
 90+					"digest=%s&image=%s&tag=%s",
 91+					url.QueryEscape(digest),
 92+					img,
 93+					tag,
 94+				)
 95+				handler.Logger.Info("sending event", "url", furl)
 96+
 97+				err := pubsub.PubSub.Pub(&psub.Msg{
 98+					Name:   fmt.Sprintf("%s@%s:%s", user.Name, img, tag),
 99+					Reader: strings.NewReader(furl),
100+				})
101 
102-					// [ ]/v2/erock/alpine/manifests/latest
103-					splitPath := strings.Split(r.Request.URL.Path, "/")
104-					img := splitPath[3]
105-					tag := splitPath[5]
106-
107-					addr := rf.Listener.Addr()
108-					furl := fmt.Sprintf(
109-						"http://%s?digest=%s&image=%s&tag=%s",
110-						addr.String(),
111-						url.QueryEscape(digest),
112-						img,
113-						tag,
114-					)
115-					handler.Logger.Info("sending event", "url", furl)
116-					_, err = http.Get(furl)
117-					if err != nil {
118-						handler.Logger.Error("could not make request to pubsub", "err", err)
119-					}
120+				if err != nil {
121+					handler.Logger.Error("pub error", "err", err)
122 				}
123 			}
124 
125@@ -296,14 +291,21 @@ func StartSshServer() {
126 		panic(err)
127 	}
128 
129+	pubsub := &psub.Cfg{
130+		Logger: logger,
131+		PubSub: &psub.PubSubMulticast{
132+			Logger: logger,
133+		},
134+	}
135+
136 	handler := &CliHandler{
137 		Logger:      logger,
138 		DBPool:      dbh,
139 		Storage:     st,
140 		RegistryUrl: registryUrl,
141+		PubSub:      pubsub,
142 	}
143 
144-	pubsub := tunkit.NewPubSubHandler(logger)
145 	s, err := wish.NewServer(
146 		wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
147 		wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
148@@ -312,7 +314,6 @@ func StartSshServer() {
149 		tunkit.WithWebTunnel(
150 			tunkit.NewWebTunnelHandler(createServeMux(handler, pubsub), logger),
151 		),
152-		tunkit.WithPubSub(pubsub),
153 	)
154 
155 	if err != nil {