Compare commits
3 Commits
b1f7e9842d
...
cf99807126
Author | SHA1 | Date |
---|---|---|
vilmibm | cf99807126 | |
vilmibm | 65228979e9 | |
vilmibm | 75d64bf7b6 |
|
@ -111,6 +111,8 @@ func submit(opts *contribOpts) error {
|
|||
"social",
|
||||
"game",
|
||||
"utility",
|
||||
"programming",
|
||||
"community",
|
||||
"misc",
|
||||
}
|
||||
var choice int
|
||||
|
@ -159,7 +161,7 @@ func submit(opts *contribOpts) error {
|
|||
return fmt.Errorf("failed to serialize contrib: %w", err)
|
||||
}
|
||||
|
||||
fname := fmt.Sprintf("%d", rand.Intn(10000))
|
||||
fname := fmt.Sprintf("%d.yml", time.Now().Unix())
|
||||
f, err := os.Create(path.Join(contribRequestPath, fname))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open contrib file for writing: %w", err)
|
||||
|
@ -244,7 +246,8 @@ func rootCmd() *cobra.Command {
|
|||
|
||||
rc.Flags().StringVarP(&updateName, "update", "u", "", "Name of command to update")
|
||||
rc.Flags().StringVarP(&delName, "delete", "d", "", "Name of command to delete")
|
||||
rc.Flags().BoolVarP(&force, "force", "f", false, "skip request, just install the command")
|
||||
// TODO hiding this until i decide i want it or not
|
||||
//rc.Flags().BoolVarP(&force, "force", "f", false, "skip request, just install the command")
|
||||
|
||||
return rc
|
||||
}
|
||||
|
|
|
@ -5,11 +5,9 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
townuser "git.tilde.town/tildetown/town/user"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
@ -48,19 +46,7 @@ const binroot = "/town/commands"
|
|||
var rootCmd = &cobra.Command{
|
||||
Use: "town",
|
||||
Short: "Run commands unique to tilde.town",
|
||||
}
|
||||
|
||||
var adminCmd = &cobra.Command{
|
||||
Use: "admin",
|
||||
Short: "Run administrative commands",
|
||||
}
|
||||
|
||||
func isAdmin() (bool, error) {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get information about current user: %w", err)
|
||||
}
|
||||
return townuser.IsAdmin(u)
|
||||
// TODO Long example showing how to contribute a command
|
||||
}
|
||||
|
||||
func parseCommands(targetCmd *cobra.Command, path string) error {
|
||||
|
@ -79,61 +65,43 @@ func parseCommands(targetCmd *cobra.Command, path string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type commandDoc struct {
|
||||
ShortDesc string `yaml:"shortDesc"`
|
||||
LongDesc string `yaml:"longDesc"`
|
||||
Examples string
|
||||
type cmdDoc struct {
|
||||
CmdName string
|
||||
ExecPath string
|
||||
Maintainer string
|
||||
Category string
|
||||
ShortDesc string
|
||||
LongDesc string
|
||||
}
|
||||
|
||||
func parseCommand(targetCmd *cobra.Command, yamlPath string) {
|
||||
executablePath := strings.TrimSuffix(yamlPath, ".yml")
|
||||
|
||||
// TODO handle when files lack executable bit
|
||||
_, err := os.Stat(executablePath)
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not find matching executable for %s; skipping...\n", yamlPath)
|
||||
return
|
||||
}
|
||||
|
||||
yamlBytes, err := ioutil.ReadFile(yamlPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not read %s; skipping...\n", yamlPath)
|
||||
return
|
||||
}
|
||||
|
||||
doc := commandDoc{}
|
||||
var doc cmdDoc
|
||||
err = yaml.Unmarshal(yamlBytes, &doc)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not parse %s; skipping...\n", yamlPath)
|
||||
return
|
||||
}
|
||||
|
||||
if doc.Maintainer == "" {
|
||||
fmt.Fprintf(os.Stderr, "%s is missing maintainer field; skipping...\n", yamlPath)
|
||||
return
|
||||
}
|
||||
|
||||
parsedCmd := &cobra.Command{
|
||||
Use: filepath.Base(executablePath),
|
||||
RunE: execWrapper(executablePath),
|
||||
Use: doc.CmdName,
|
||||
RunE: execWrapper(doc.ExecPath),
|
||||
DisableFlagParsing: true,
|
||||
}
|
||||
if doc.ShortDesc != "" {
|
||||
parsedCmd.Short = doc.ShortDesc
|
||||
}
|
||||
|
||||
if doc.LongDesc != "" {
|
||||
parsedCmd.Long = doc.LongDesc
|
||||
}
|
||||
|
||||
parsedCmd.Long += fmt.Sprintf("\nMaintained by %s; reach out to them via mail or chat with questions", doc.Maintainer)
|
||||
|
||||
if doc.Examples != "" {
|
||||
parsedCmd.Example = doc.Examples
|
||||
}
|
||||
|
||||
targetCmd.AddCommand(parsedCmd)
|
||||
}
|
||||
|
||||
|
@ -148,30 +116,14 @@ func execWrapper(executablePath string) func(*cobra.Command, []string) error {
|
|||
}
|
||||
|
||||
func cli() int {
|
||||
err := parseCommands(rootCmd, "core")
|
||||
files, err := ioutil.ReadDir(binroot)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to parse core commands: %s", err)
|
||||
return 1
|
||||
fmt.Fprintf(os.Stderr, "failed to list directory %s: %s", binroot, err)
|
||||
}
|
||||
|
||||
err = parseCommands(rootCmd, "contrib")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to parse contrib commands: %s", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
admin, err := isAdmin()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to check admin status: %s", err)
|
||||
return 2
|
||||
}
|
||||
|
||||
if admin {
|
||||
rootCmd.AddCommand(adminCmd)
|
||||
err = parseCommands(adminCmd, "admin")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to parse admin commands: %s", err)
|
||||
return 1
|
||||
for _, file := range files {
|
||||
if strings.HasSuffix(file.Name(), "yml") {
|
||||
parseCommand(rootCmd, filepath.Join(binroot, file.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
The point of this project is to enable signing up for tilde.town via an ssh connection.
|
||||
|
||||
It ought to work something like this:
|
||||
|
||||
```bash
|
||||
|
||||
ssh signup@tilde.town
|
||||
|
||||
<ascii art>
|
||||
|
||||
tilde.town
|
||||
|
||||
|
||||
a creature stands before you. what does it look like?
|
||||
|
||||
> a floating gray cube
|
||||
a calico cat with softly glowing eyes
|
||||
a squid
|
||||
something else
|
||||
|
||||
cube: you'd like to sign up for tilde.town, yes?
|
||||
|
||||
> yeah
|
||||
nah
|
||||
why should I?
|
||||
|
||||
you say, "yeah"
|
||||
|
||||
cube: by what name should i call you?
|
||||
|
||||
~cowcow________
|
||||
|
||||
cube: unfortunately, i already know someone by that name. what else can i call you?
|
||||
|
||||
~shelf_________
|
||||
|
||||
cube: excellent, hello ~shelf. have you been invited here by anyone i know?
|
||||
|
||||
> yes, i have an invite code
|
||||
no
|
||||
|
||||
you say "yes, i have an invite code"
|
||||
|
||||
cube: great. please paste it and press enter:
|
||||
|
||||
_____________
|
||||
|
||||
```
|
||||
|
||||
...and so on.
|
||||
|
||||
TODO
|
||||
|
||||
- [ ] make signup user
|
||||
- [ ] get dummy program to run as ssh handler
|
||||
- [ ] tcell bootstrapping some kind of interactivity
|
||||
- [ ] existing username check
|
||||
- [ ] email validation
|
||||
- [ ] collect responses
|
||||
- [ ] create the signup request (on disk? db?)
|
||||
- [ ] invite system support
|
|
@ -0,0 +1,366 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
/*
|
||||
Assumes:
|
||||
|
||||
Match User join
|
||||
ForceCommand /town/src/town/cmd/signup/signup
|
||||
PubkeyAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
PasswordAuthentication yes
|
||||
PermitEmptyPasswords yes
|
||||
DisableForwarding yes
|
||||
|
||||
in sshd_config, and:
|
||||
|
||||
auth [success=done default=ignore] pam_succeed_if.so user ingroup join
|
||||
|
||||
in /etc/pam.d/sshd
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
is there a way :qa
|
||||
|
||||
|
||||
*/
|
||||
|
||||
type streams struct {
|
||||
In io.Reader
|
||||
Out io.Writer
|
||||
Err io.Writer
|
||||
}
|
||||
|
||||
type Prompter struct {
|
||||
in io.Reader
|
||||
out io.Writer
|
||||
width int
|
||||
}
|
||||
|
||||
func (p *Prompter) Say(m string) {
|
||||
for _, line := range wrap(m, p.width) {
|
||||
fmt.Fprintln(p.out, line)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Prompter) CharSay(c, m string) {
|
||||
sayPrefix := fmt.Sprintf("%s says: ", c)
|
||||
prefixWidth := utf8.RuneCountInString(sayPrefix)
|
||||
width := p.width - prefixWidth
|
||||
indent := ""
|
||||
for x := 0; x < utf8.RuneCountInString(sayPrefix); x++ {
|
||||
indent += " "
|
||||
}
|
||||
for i, line := range wrap(m, width) {
|
||||
if i == 0 {
|
||||
fmt.Fprintln(p.out, sayPrefix+line)
|
||||
} else {
|
||||
fmt.Fprintln(p.out, indent+line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type InputAnswer struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func runeLen(s string) int {
|
||||
return utf8.RuneCountInString(s)
|
||||
}
|
||||
|
||||
func wrap(s string, width int) []string {
|
||||
fielded := strings.Fields(s)
|
||||
out := []string{}
|
||||
line := ""
|
||||
for i, field := range fielded {
|
||||
if runeLen(field)+runeLen(line)+1 < width {
|
||||
line += field + " "
|
||||
} else {
|
||||
out = append(out, line)
|
||||
line = field + " "
|
||||
continue
|
||||
}
|
||||
if i == len(fielded)-1 {
|
||||
out = append(out, line)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (p *Prompter) Confirm(m string) (bool, error) {
|
||||
result := InputAnswer{}
|
||||
|
||||
for {
|
||||
err := p.Ask(m, &result)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
val := strings.TrimSpace(result.Value)
|
||||
|
||||
switch val {
|
||||
case "yes", "y":
|
||||
return true, nil
|
||||
case "no", "n":
|
||||
return false, nil
|
||||
default:
|
||||
p.Say("sorry, please say y or n")
|
||||
p.Pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Prompter) Ask(m string, result *InputAnswer) error {
|
||||
fmt.Fprintf(p.out, "%s ", m)
|
||||
var val string
|
||||
var err error
|
||||
|
||||
reader := bufio.NewReader(p.in)
|
||||
for val == "" {
|
||||
val, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if val == "" {
|
||||
p.Say("hmm, what was that?")
|
||||
p.Pause()
|
||||
}
|
||||
}
|
||||
|
||||
result.Value = val
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Prompter) AskLong(m string, result *InputAnswer) error {
|
||||
fmt.Fprintf(p.out, "%s\n", m)
|
||||
var val []byte
|
||||
var err error
|
||||
|
||||
reader := bufio.NewReader(p.in)
|
||||
for len(val) == 0 {
|
||||
val, err = ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(val) == 0 {
|
||||
fmt.Fprintf(p.out, "%s\n", m)
|
||||
}
|
||||
}
|
||||
|
||||
result.Value = string(val)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Prompter) Pause() {
|
||||
fmt.Fprintln(p.out)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func NewPrompter(width int, s *streams) Prompter {
|
||||
return Prompter{
|
||||
in: s.In,
|
||||
out: s.Out,
|
||||
width: width,
|
||||
}
|
||||
}
|
||||
|
||||
type answers struct {
|
||||
username string
|
||||
email string
|
||||
applied time.Time
|
||||
|
||||
howDay string
|
||||
howHeard string
|
||||
reasons string
|
||||
plans string
|
||||
socials string
|
||||
sshKey string
|
||||
}
|
||||
|
||||
func _main(args []string, s *streams) error {
|
||||
a := answers{}
|
||||
|
||||
p := NewPrompter(80, s)
|
||||
|
||||
// disable input buffering
|
||||
exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
|
||||
|
||||
// LOL i don't think this will work
|
||||
reader := bufio.NewReader(p.in)
|
||||
s, _ := reader.ReadString(4)
|
||||
fmt.Printf("DBG %#v\n", s)
|
||||
//var b []byte = make([]byte, 100)
|
||||
//for {
|
||||
// os.Stdin.Read(b)
|
||||
// fmt.Println("I got the byte", b, "("+string(b)+")")
|
||||
//}
|
||||
|
||||
var ia InputAnswer
|
||||
if err := p.AskLong("lol give me stuff hit ctrl+d", &ia); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("DBG %#v\n", ia.Value)
|
||||
|
||||
p.Say("you are standing in a field.")
|
||||
p.Pause()
|
||||
p.Say("there are flowers around you. you are standing in a slight depression and before you is grass touching a purple sky.")
|
||||
p.Pause()
|
||||
p.Say("you are not sure how long it's been when a cube about a meter high appears at the top of the hill before you.")
|
||||
p.Say("the cube's surface is murky but iridescently reflective like an oil slick.")
|
||||
p.Pause()
|
||||
p.CharSay("cube", "hello. how is your day going?")
|
||||
p.Say("(you can type a response and hit enter to talk to the cube)")
|
||||
p.Pause()
|
||||
|
||||
howDay := InputAnswer{}
|
||||
err := p.Ask("you say:", &howDay)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.howDay = howDay.Value
|
||||
|
||||
p.Say("the cube inclines towards you gently as if nodding.")
|
||||
p.CharSay("cube", "i see.")
|
||||
p.Pause()
|
||||
|
||||
p.CharSay("cube", "i am guessing that if you are here, you want to be there.")
|
||||
p.Pause()
|
||||
|
||||
p.Say("you blink and are somewhere else.")
|
||||
p.Pause()
|
||||
|
||||
p.Say("the field of flowers is behind you and now you are up on the hill. the cube is next to you.")
|
||||
p.Pause()
|
||||
p.Say("you can see clear across a wide open plain. structures large and small dot the landscape. you catch a whiff of a savory smell and can just barely hear voices on the wind.")
|
||||
p.Pause()
|
||||
|
||||
p.CharSay("cube", "the tilde town lies before us. if you want to continue, i'll ask you some questions about acquiring a home down in the town. you'll be free to edit your responses before i carry them off.")
|
||||
p.Pause()
|
||||
|
||||
p.CharSay("cube", "first, i'm curious how you found out about the town?")
|
||||
p.Pause()
|
||||
|
||||
answer := InputAnswer{}
|
||||
|
||||
if err := p.Ask("you say:", &answer); err != nil {
|
||||
return err
|
||||
}
|
||||
a.howHeard = answer.Value
|
||||
|
||||
p.CharSay("cube", "interesting, thanks.")
|
||||
p.CharSay("cube", "what sort of stuff do you want to get up to on the town?")
|
||||
|
||||
if err := p.Ask("you say:", &answer); err != nil {
|
||||
return err
|
||||
}
|
||||
a.plans = answer.Value
|
||||
|
||||
p.CharSay("cube", "thanks.")
|
||||
p.CharSay("cube", "what do you like about the town?")
|
||||
|
||||
if err := p.Ask("you say:", &answer); err != nil {
|
||||
return err
|
||||
}
|
||||
a.reasons = answer.Value
|
||||
|
||||
p.CharSay("cube", "i appreciate it.")
|
||||
p.CharSay("cube", "can you paste some links to other places you are active online? maybe a homepage or social media profile? if you aren't comfortable sharing or there are none, just say so.")
|
||||
|
||||
if err := p.Ask("you say:", &answer); err != nil {
|
||||
return err
|
||||
}
|
||||
a.socials = answer.Value
|
||||
|
||||
p.CharSay("cube", "ok, last thing. in order to break ground in the town, you'll need an SSH key. If you don't know what that is, you can check out this link: https://tilde.town/ssh.html .")
|
||||
p.Pause()
|
||||
|
||||
if err := p.AskLong("paste SSH public key; press control+d to submit:", &answer); err != nil {
|
||||
return err
|
||||
}
|
||||
a.sshKey = answer.Value
|
||||
|
||||
p.CharSay("cube", "i know that was a lot so i appreciate it. i've got everything written down here. before i carry it off, do you want to review and edit what you wrote?")
|
||||
|
||||
confirmed, err := p.Confirm("type y or n: ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if confirmed {
|
||||
err := edit(s, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("DBG %#v\n", confirmed)
|
||||
|
||||
fmt.Printf("DBG %#v\n", a)
|
||||
|
||||
// TODO allow for editing
|
||||
// TODO write answers to disk
|
||||
// TODO add a log
|
||||
// TODO pretty colors
|
||||
// TODO ascii art
|
||||
// TODO IP throttling?
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func edit(s *streams, a *answers) error {
|
||||
// TODO make more real
|
||||
/*
|
||||
username string
|
||||
email string
|
||||
applied time.Time
|
||||
|
||||
howDay string
|
||||
howHeard string
|
||||
reasons string
|
||||
plans string
|
||||
socials string
|
||||
sshKey string
|
||||
*/
|
||||
// TODO add note about tabbing around
|
||||
|
||||
app := tview.NewApplication()
|
||||
form := tview.NewForm().
|
||||
AddInputField("how did you hear about the town?", a.howHeard, 0, nil, nil).
|
||||
AddButton("cool i'm good", nil).
|
||||
AddButton("cancel and discard all this please", func() { app.Stop() })
|
||||
form.SetBorder(true).SetTitle("edit your stuff").SetTitleAlign(tview.AlignCenter)
|
||||
|
||||
return app.SetRoot(form, true).EnableMouse(true).Run()
|
||||
}
|
||||
|
||||
func main() {
|
||||
retcode := 0
|
||||
s := &streams{
|
||||
In: os.Stdin,
|
||||
Out: os.Stdout,
|
||||
Err: os.Stderr,
|
||||
}
|
||||
err := _main(os.Args, s)
|
||||
if err != nil {
|
||||
retcode = 1
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
os.Exit(retcode)
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
# New commands for signups
|
||||
|
||||
## town-signup
|
||||
|
||||
this binary is run by OpenSSH when a user anywhere in the world runs `ssh join@tilde.town`. It should:
|
||||
|
||||
- collect information about an application from a user
|
||||
- allow them to edit their responses before submitting
|
||||
- write a yaml file of their responses to disk
|
||||
|
||||
## review-signups
|
||||
|
||||
this binary is run by town admins in order to review, approve, notate, and reject applications. It should:
|
||||
|
||||
- iterate over signups
|
||||
- allow fuzzy finding a particular signup
|
||||
- allow approval using create-user
|
||||
- allow rejection
|
||||
- just move signup to archived rejected signup directory
|
||||
- allow notating an application, ie:
|
||||
- lock the yaml file for writing
|
||||
- add notes to the yaml file that can be seen by other admins
|
||||
- print info about historical signups
|
||||
|
||||
## create-user
|
||||
|
||||
this binary is called by `review-signups` to take a yaml application and create a user on disk. It should:
|
||||
|
||||
- create the user idempotently
|
||||
- `adduser`
|
||||
- `usermod` to set group
|
||||
- calling `add-key` for user
|
||||
- move the yaml file to an archive directory of approved signups
|
||||
|
||||
## user-key
|
||||
|
||||
this binary helps manage keys for users; basically automating the listing, adding, and removing of public keys for a user.
|
||||
|
||||
- `user-key add <username> <keyfile`
|
||||
- `user-key list <username>`
|
||||
- `user-key remove <username>`
|
||||
|
||||
|
||||
|
||||
|
||||
|
7
go.mod
7
go.mod
|
@ -5,6 +5,7 @@ go 1.18
|
|||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||
github.com/charmbracelet/glamour v0.5.0
|
||||
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c
|
||||
github.com/spf13/cobra v1.5.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
@ -13,6 +14,8 @@ require (
|
|||
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/gdamore/tcell/v2 v2.5.3 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
|
@ -25,12 +28,12 @@ require (
|
|||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.9.0 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.2 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/yuin/goldmark v1.4.4 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.1 // indirect
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
)
|
||||
|
|
15
go.sum
15
go.sum
|
@ -16,6 +16,10 @@ 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.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0=
|
||||
github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
|
@ -47,9 +51,12 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
|
|||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c h1:zIYU4PjQJ4BnYryMmpyizt1Un13V0ToCMXvC05DK8xc=
|
||||
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c/go.mod h1:lBUy/T5kyMudFzWUH/C2moN+NlU5qF505vzOyINXuUQ=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||
|
@ -70,14 +77,18 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
Loading…
Reference in New Issue