mirror of
https://github.com/rwinkhart/MUTN.git
synced 2026-09-03 15:47:23 -04:00
Initial interactive copy menu
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
.TH MUTN 1 "17 August 2025" "v0.3.0" "MUTN man page"
|
.TH MUTN 1 "09 November 2025" "v0.3.B" "MUTN man page"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBmutn\fR - Simple, self-hosted, SSH-synchronized password and note management based on libmutton. It is the successor to sshyp.
|
\fBmutn\fR - Simple, self-hosted, SSH-synchronized password and note management based on libmutton. It is the successor to sshyp.
|
||||||
@@ -63,6 +63,7 @@ copy:
|
|||||||
totp|-t Generate and copy the TOTP token for an entry to your clipboard
|
totp|-t Generate and copy the TOTP token for an entry to your clipboard
|
||||||
url|-l Copy the URL in an entry to your clipboard
|
url|-l Copy the URL in an entry to your clipboard
|
||||||
note|-n Copy the first note line in an entry to your clipboard
|
note|-n Copy the first note line in an entry to your clipboard
|
||||||
|
menu|-m Open interactive menu to copy any combination of fields
|
||||||
|
|
||||||
edit:
|
edit:
|
||||||
password|-pw Change the password in an entry
|
password|-pw Change the password in an entry
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ go 1.25.4
|
|||||||
require (
|
require (
|
||||||
github.com/charmbracelet/glamour v0.7.0
|
github.com/charmbracelet/glamour v0.7.0
|
||||||
github.com/rwinkhart/go-boilerplate v0.1.0
|
github.com/rwinkhart/go-boilerplate v0.1.0
|
||||||
github.com/rwinkhart/libmutton v0.4.2
|
github.com/rwinkhart/libmutton v0.4.3-0.20251110042347-bf1399ce8d6e
|
||||||
golang.org/x/term v0.36.0
|
golang.org/x/term v0.36.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ github.com/rwinkhart/go-highlite v0.1.1 h1:9TxbRhYVfD/3YaEgNk1BtEiZ0t8/5mxDUZqNM
|
|||||||
github.com/rwinkhart/go-highlite v0.1.1/go.mod h1:mWLMtCWcyV0BG4NeyPyAUGMjPvI0ds6vXmUq89QwBFA=
|
github.com/rwinkhart/go-highlite v0.1.1/go.mod h1:mWLMtCWcyV0BG4NeyPyAUGMjPvI0ds6vXmUq89QwBFA=
|
||||||
github.com/rwinkhart/go-winio v0.1.0 h1:b72agLW+dETGmhR3VbcbwnStfgKfc5AfgJOXBJDkaHg=
|
github.com/rwinkhart/go-winio v0.1.0 h1:b72agLW+dETGmhR3VbcbwnStfgKfc5AfgJOXBJDkaHg=
|
||||||
github.com/rwinkhart/go-winio v0.1.0/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0=
|
github.com/rwinkhart/go-winio v0.1.0/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0=
|
||||||
github.com/rwinkhart/libmutton v0.4.2 h1:JYN61KDG8K1Q5OvKdVJIibrDHxDffUPl3AgpN0JrYV8=
|
github.com/rwinkhart/libmutton v0.4.3-0.20251110042347-bf1399ce8d6e h1:AL7cSPkdiv69uFrBCMJAQP1PPG0LN1mBeOFqQuF+j7k=
|
||||||
github.com/rwinkhart/libmutton v0.4.2/go.mod h1:ZXDCjk1Wec5UJK8ID9AF+jqicWoZmMGzuaPw1Unu3HQ=
|
github.com/rwinkhart/libmutton v0.4.3-0.20251110042347-bf1399ce8d6e/go.mod h1:ZXDCjk1Wec5UJK8ID9AF+jqicWoZmMGzuaPw1Unu3HQ=
|
||||||
github.com/rwinkhart/peercred-mini v0.1.2 h1:4cGWDbv0whvLeVvbUdx84V/9p+2fS+DEXgrA1KxlRFo=
|
github.com/rwinkhart/peercred-mini v0.1.2 h1:4cGWDbv0whvLeVvbUdx84V/9p+2fS+DEXgrA1KxlRFo=
|
||||||
github.com/rwinkhart/peercred-mini v0.1.2/go.mod h1:LLHG7YshHEpbpJJP+Il9nx2dnGj5O3VGE32rWmflj0c=
|
github.com/rwinkhart/peercred-mini v0.1.2/go.mod h1:LLHG7YshHEpbpJJP+Il9nx2dnGj5O3VGE32rWmflj0c=
|
||||||
github.com/rwinkhart/rcw v0.2.3 h1:g1rVaspZnZM8nWupeSdVO827di4ORMZnczw6iXyo01Y=
|
github.com/rwinkhart/rcw v0.2.3 h1:g1rVaspZnZM8nWupeSdVO827di4ORMZnczw6iXyo01Y=
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/rwinkhart/go-boilerplate/front"
|
"github.com/rwinkhart/go-boilerplate/front"
|
||||||
"github.com/rwinkhart/go-boilerplate/other"
|
"github.com/rwinkhart/go-boilerplate/other"
|
||||||
"github.com/rwinkhart/libmutton/cfg"
|
"github.com/rwinkhart/libmutton/cfg"
|
||||||
|
"github.com/rwinkhart/libmutton/clip"
|
||||||
"github.com/rwinkhart/libmutton/core"
|
"github.com/rwinkhart/libmutton/core"
|
||||||
"github.com/rwinkhart/libmutton/crypt"
|
"github.com/rwinkhart/libmutton/crypt"
|
||||||
"github.com/rwinkhart/libmutton/global"
|
"github.com/rwinkhart/libmutton/global"
|
||||||
@@ -47,7 +48,7 @@ func main() {
|
|||||||
case "show", "-s":
|
case "show", "-s":
|
||||||
cli.EntryReaderDecrypt(targetLocation, false)
|
cli.EntryReaderDecrypt(targetLocation, false)
|
||||||
case "copy":
|
case "copy":
|
||||||
err := core.CopyArgument(targetLocation, 0)
|
err := clip.CopyArgument(targetLocation, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
other.PrintError("Failed to copy password to clipboard: "+err.Error(), global.ErrorClipboard)
|
other.PrintError("Failed to copy password to clipboard: "+err.Error(), global.ErrorClipboard)
|
||||||
}
|
}
|
||||||
@@ -94,10 +95,12 @@ func main() {
|
|||||||
field = 3
|
field = 3
|
||||||
case "note", "-n":
|
case "note", "-n":
|
||||||
field = 4
|
field = 4
|
||||||
|
case "menu", "-m":
|
||||||
|
cli.CopyMenu(targetLocation)
|
||||||
default:
|
default:
|
||||||
cli.HelpCopy()
|
cli.HelpCopy()
|
||||||
}
|
}
|
||||||
err := core.CopyArgument(targetLocation, field)
|
err := clip.CopyArgument(targetLocation, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
other.PrintError("Failed to copy field to clipboard: "+err.Error(), global.ErrorClipboard)
|
other.PrintError("Failed to copy field to clipboard: "+err.Error(), global.ErrorClipboard)
|
||||||
}
|
}
|
||||||
@@ -182,7 +185,7 @@ func main() {
|
|||||||
} else {
|
} else {
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
case "clipclear":
|
case "clipclear":
|
||||||
err := core.ClipClearArgument()
|
err := clip.ClipClearArgument()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
other.PrintError("Failure occurred in clipboard clearing process: "+err.Error(), global.ErrorClipboard)
|
other.PrintError("Failure occurred in clipboard clearing process: "+err.Error(), global.ErrorClipboard)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/rwinkhart/go-boilerplate/back"
|
||||||
|
"github.com/rwinkhart/go-boilerplate/front"
|
||||||
|
"github.com/rwinkhart/go-boilerplate/other"
|
||||||
|
"github.com/rwinkhart/libmutton/clip"
|
||||||
|
"github.com/rwinkhart/libmutton/crypt"
|
||||||
|
"github.com/rwinkhart/libmutton/global"
|
||||||
|
"github.com/rwinkhart/libmutton/synccommon"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CopyMenu decrypts an entry and allows the user to
|
||||||
|
// interactively copy fields without having to re-decrypt each time.
|
||||||
|
func CopyMenu(targetLocation string) {
|
||||||
|
// decrypt entry
|
||||||
|
decSlice, err := crypt.DecryptFileToSlice(targetLocation)
|
||||||
|
if err != nil {
|
||||||
|
other.PrintError("Failed to decrypt entry: "+err.Error(), global.ErrorDecryption)
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine populated fields in entry
|
||||||
|
fieldIndexToString := map[int]string{
|
||||||
|
0: "Password",
|
||||||
|
1: "Username",
|
||||||
|
2: "TOTP Code",
|
||||||
|
3: "URL",
|
||||||
|
4: "Note (first line)",
|
||||||
|
}
|
||||||
|
var fields []string
|
||||||
|
for i, _ := range decSlice[:5] {
|
||||||
|
if decSlice[i] != "" {
|
||||||
|
fields = append(fields, fieldIndexToString[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy selected field to clipboard
|
||||||
|
for {
|
||||||
|
choice := front.InputMenuGen("Field to copy:", fields)
|
||||||
|
switch fields[choice-1] {
|
||||||
|
case "Password":
|
||||||
|
choice = 0
|
||||||
|
case "Username":
|
||||||
|
choice = 1
|
||||||
|
case "TOTP Code":
|
||||||
|
choice = 2
|
||||||
|
case "URL":
|
||||||
|
choice = 3
|
||||||
|
case "Note (first line)":
|
||||||
|
choice = 4
|
||||||
|
}
|
||||||
|
if choice == 2 {
|
||||||
|
fmt.Println(ansiEmptyDirectoryWarning + "[Starting]" + back.AnsiReset + " TOTP clipboard refresher")
|
||||||
|
errorChan := make(chan error)
|
||||||
|
done := make(chan bool)
|
||||||
|
go clip.TOTPCopier(decSlice[2], 0, errorChan, done)
|
||||||
|
// block until first successful copy
|
||||||
|
err = <-errorChan
|
||||||
|
if err != nil {
|
||||||
|
other.PrintError("Failed to copy TOTP token: "+err.Error(), global.ErrorClipboard)
|
||||||
|
}
|
||||||
|
fmt.Println(synccommon.AnsiDownload + "[Started]" + back.AnsiReset + " TOTP clipboard refresher")
|
||||||
|
// block until the user sends "q"
|
||||||
|
for {
|
||||||
|
input := front.Input("Service will run until 'q' is entered:")
|
||||||
|
if input == "q" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
fmt.Print(synccommon.AnsiUpload + "\n[Stopped]" + back.AnsiReset + " TOTP clipboard refresher\n\n")
|
||||||
|
} else {
|
||||||
|
err := clip.CopyString(true, decSlice[choice])
|
||||||
|
if err != nil {
|
||||||
|
other.PrintError("Failed to copy field to clipboard: "+err.Error(), global.ErrorClipboard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO clear clipboard on exit!
|
||||||
|
}
|
||||||
@@ -40,6 +40,7 @@ This program comes with absolutely no warranty; type "mutn version" for details.
|
|||||||
totp|-t Generate and copy the TOTP token for an entry to your clipboard
|
totp|-t Generate and copy the TOTP token for an entry to your clipboard
|
||||||
url|-l Copy the URL in an entry to your clipboard
|
url|-l Copy the URL in an entry to your clipboard
|
||||||
note|-n Copy the first note line in an entry to your clipboard
|
note|-n Copy the first note line in an entry to your clipboard
|
||||||
|
menu|-m Open interactive menu to copy any combination of fields
|
||||||
edit:
|
edit:
|
||||||
password|-pw|<blank> Change the password in an entry
|
password|-pw|<blank> Change the password in an entry
|
||||||
username|-u Change the username in an entry
|
username|-u Change the username in an entry
|
||||||
|
|||||||
Reference in New Issue
Block a user