mirror of
https://github.com/rwinkhart/MUTN.git
synced 2026-08-30 05:46:35 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6ea3b0136 | ||
|
|
a0d75aad20 | ||
|
|
0a622765cc | ||
|
|
e2d79d4ce3 | ||
|
|
d323f2f75a | ||
|
|
4556e38e32 | ||
|
|
edc2ef25de | ||
|
|
f21041a38d | ||
|
|
0c57bd5764 | ||
|
|
dd8464ecd9 | ||
|
|
38ddc32656 | ||
|
|
27e6972d82 | ||
|
|
118ffc2adb | ||
|
|
d1d88c2e69 | ||
|
|
cb1b010e0a | ||
|
|
6210c9cd31 | ||
|
|
6d557a77c2 | ||
|
|
4933da4c37 | ||
|
|
0e056822ef | ||
|
|
c323899bf8 |
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2024 Randall Winkhart
|
Copyright (c) 2024-2025 Randall Winkhart
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,9 +1,58 @@
|
|||||||
|
$global:cliMUTN_entriesToSpaceIndicesMap = @{}
|
||||||
|
|
||||||
if (!$IsWindows) {
|
if (!$IsWindows) {
|
||||||
# since the executable has the same name as the completion function (on non-Windows platforms), its path must be stored before the function is registered
|
# since the executable has the same name as the completion function (on non-Windows platforms), its path must be stored before the function is registered
|
||||||
$MUTNExecutablePath = (Get-Command mutn).Path
|
$MUTNExecutablePath = (Get-Command mutn).Path
|
||||||
|
$spaceIndicesFilePath = '~/.config/libmutton/psCompletionsCache.json'
|
||||||
|
} else {
|
||||||
|
$spaceIndicesFilePath = '~\AppData\Local\libmutton\psCompletionsCache.json'
|
||||||
}
|
}
|
||||||
|
|
||||||
function cliMUTNEntryCompleter {
|
function cliMUTN-loadSpaceIndices {
|
||||||
|
if (Test-Path $global:spaceIndicesFilePath) {
|
||||||
|
$jsonContent = Get-Content -Path $global:spaceIndicesFilePath -Raw
|
||||||
|
$global:cliMUTN_entriesToSpaceIndicesMap = ConvertFrom-Json -InputObject $jsonContent -AsHashtable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cliMUTN-saveSpaceIndices {
|
||||||
|
$jsonContent = $global:cliMUTN_entriesToSpaceIndicesMap | ConvertTo-Json -Compress
|
||||||
|
$jsonContent | Set-Content -Path $global:spaceIndicesFilePath
|
||||||
|
}
|
||||||
|
|
||||||
|
function cliMUTN-getSpaceIndices {
|
||||||
|
param ([string]$inputString)
|
||||||
|
$indices = @()
|
||||||
|
|
||||||
|
# loop through each character in the string
|
||||||
|
for ($i = 0; $i -lt $inputString.Length; $i++) {
|
||||||
|
if ($inputString[$i] -eq ' ') {
|
||||||
|
# if the character is a space, add the index to the array
|
||||||
|
$indices += $i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $indices
|
||||||
|
}
|
||||||
|
|
||||||
|
function cliMUTN-getEscapedEntryName {
|
||||||
|
param ([int[]]$Indices, [string]$InputString)
|
||||||
|
$charArray = $InputString.ToCharArray()
|
||||||
|
|
||||||
|
# loop through each index in the provided array,
|
||||||
|
# replacing the character at that index with a space
|
||||||
|
foreach ($index in $Indices) {
|
||||||
|
$charArray[$index] = ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
# convert the character array back to a string
|
||||||
|
$outputString = -join $charArray
|
||||||
|
|
||||||
|
# return the modified string with spaces escaped
|
||||||
|
return $outputString -replace ' ', '` '
|
||||||
|
}
|
||||||
|
|
||||||
|
function cliMUTN-entryCompleter {
|
||||||
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
|
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
|
||||||
if ($IsWindows) {
|
if ($IsWindows) {
|
||||||
$entryRoot = (Resolve-Path '~\AppData\Local\libmutton\entries').Path
|
$entryRoot = (Resolve-Path '~\AppData\Local\libmutton\entries').Path
|
||||||
@@ -12,18 +61,27 @@ function cliMUTNEntryCompleter {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$trimmedPaths = If (Test-Path $entryRoot) {
|
$trimmedPaths = If (Test-Path $entryRoot) {
|
||||||
(Get-ChildItem -Path $entryRoot -Recurse -File).FullName.Substring($entryRoot.Length) -replace '\\', '/' -replace ' ', [char]0x259d
|
(Get-ChildItem -Path $entryRoot -Recurse -File).FullName.Substring($entryRoot.Length) -replace '\\', '/'
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
$trimmedPaths = $null # if any errors occur (especially, "You cannot call a method on a null-valued expression", set $trimmedPaths to $null
|
$trimmedPaths = $null # if any errors occur (especially, "You cannot call a method on a null-valued expression", set $trimmedPaths to $null
|
||||||
}
|
}
|
||||||
if ($null -eq $trimmedPaths) { # if no entries are found, add 'help' to $trimmedPaths
|
if ($null -eq $trimmedPaths) { # if no entries are found, add 'help' to $trimmedPaths
|
||||||
$trimmedPaths = 'help'
|
$trimmedPaths = 'help'
|
||||||
|
} else {
|
||||||
|
# replace spaces with underscores, tracking the indices of the spaces in a global variable for later restoration of spaces
|
||||||
|
$trimmedPaths = $trimmedPaths | ForEach-Object {
|
||||||
|
$spaceIndices = cliMUTN-getSpaceIndices -inputString $_
|
||||||
|
$replacedEntry = $_ -replace ' ', '_'
|
||||||
|
$replacedEntry
|
||||||
|
$global:cliMUTN_entriesToSpaceIndicesMap[$replacedEntry] = $spaceIndices
|
||||||
|
}
|
||||||
|
cliMUTN-saveSpaceIndices
|
||||||
}
|
}
|
||||||
$trimmedPaths | Where-Object { $_ -like "$wordToComplete*" }
|
$trimmedPaths | Where-Object { $_ -like "$wordToComplete*" }
|
||||||
}
|
}
|
||||||
|
|
||||||
function cliMUTNOptionCompleter {
|
function cliMUTN-optionCompleter {
|
||||||
param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
|
param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
|
||||||
|
|
||||||
$possibleValues = @{
|
$possibleValues = @{
|
||||||
@@ -46,7 +104,7 @@ function mutn {
|
|||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param (
|
param (
|
||||||
[Parameter(Position = 0)]
|
[Parameter(Position = 0)]
|
||||||
[ArgumentCompleter({ cliMUTNEntryCompleter @args })]
|
[ArgumentCompleter({ cliMUTN-entryCompleter @args })]
|
||||||
[string]$entry,
|
[string]$entry,
|
||||||
|
|
||||||
[Parameter(Position = 1)]
|
[Parameter(Position = 1)]
|
||||||
@@ -54,13 +112,18 @@ function mutn {
|
|||||||
[string]$argument,
|
[string]$argument,
|
||||||
|
|
||||||
[Parameter(Position = 2, ValueFromRemainingArguments=$true)]
|
[Parameter(Position = 2, ValueFromRemainingArguments=$true)]
|
||||||
[ArgumentCompleter({ cliMUTNOptionCompleter @args })]
|
[ArgumentCompleter({ cliMUTN-optionCompleter @args })]
|
||||||
[string]$option
|
[string]$option
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# replace placeholder underscores with escaped spaces
|
||||||
|
$escapedEntry = cliMUTN-getEscapedEntryName -Indices $global:cliMUTN_entriesToSpaceIndicesMap[$entry] -InputString $entry
|
||||||
|
|
||||||
if ($IsWindows) {
|
if ($IsWindows) {
|
||||||
Invoke-Expression -Command ('mutn.exe ' + ($entry -replace ' ', '` ' -replace [char]0x259d, '` '), $argument, $option).Trim()
|
Invoke-Expression -Command ('mutn.exe ' + $escapedEntry, $argument, $option).Trim()
|
||||||
} else {
|
} else {
|
||||||
Invoke-Expression -Command ($MUTNExecutablePath + ' ' + ($entry -replace ' ', '` ' -replace [char]0x259d, '` '), $argument, $option).Trim()
|
Invoke-Expression -Command ($global:MUTNExecutablePath + ' ' + $escapedEntry, $argument, $option).Trim()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cliMUTN-loadSpaceIndices
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.TH MUTN 1 "13 December 2024" "v0.2.4" "MUTN man page"
|
.TH MUTN 1 "22 February 2025" "v0.2.5" "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.
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
**WARNING: AS LIBMUTTON HAS NOT YET REACHED v1.0.0, [BREAKING CHANGES](https://github.com/rwinkhart/libmutton/blob/main/wiki/breaking.md) IN FUTURE UPDATES ARE PLANNED**
|
|
||||||
|
|
||||||
**MUTN v0.2.4**
|
**MUTN v0.2.4**
|
||||||
Built with libmutton v0.2.4
|
Built with libmutton v0.2.4
|
||||||
December 13, 2024
|
December 13, 2024
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
**WARNING: AS LIBMUTTON HAS NOT YET REACHED v1.0.0, [BREAKING CHANGES](https://github.com/rwinkhart/libmutton/blob/main/wiki/breaking.md) IN FUTURE UPDATES ARE PLANNED**
|
||||||
|
|
||||||
|
**MUTN v0.2.5**
|
||||||
|
Built with libmutton v0.3.0
|
||||||
|
February 22, 2025
|
||||||
|
|
||||||
|
# The Tripe Transmission Update - Patch 5
|
||||||
|
|
||||||
|
This release updates libmutton to v0.3.0 and brings in some minor bug fixes.
|
||||||
|
|
||||||
|
## libmutton-derived Changes
|
||||||
|
- See [libmutton's release notes](https://github.com/rwinkhart/libmutton/releases/tag/v0.3.0)
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
- (38ddc3265634275d999781dbc0ce827392ed77e0) Some ANSI-colored text is now more visible in light terminal themes
|
||||||
|
- (0c57bd576447c4b61f67c22f81db7543ba30ff3f) Trailing double spaces are now preserved in notes for Markdown manual line breaks
|
||||||
|
- Currently only supported in [BEAN](https://github.com/Trojan2021/BEAN)
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- (edc2ef25dea9d702bf8a096fd23795957ae721f1) (4556e38e32c52862ecf3d88e7b8faeae231b44a2) PowerShell completions no longer become unusable when multiple entries with similar names (containing spaces) exist in the same directory
|
||||||
|
- (d323f2f75a219163f3b6b3fbb2b7c1b21930fd0b) Entries are no longer re-uploaded when the user adds a blank note
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- Bumps (direct and indirect)
|
||||||
|
- Go: v1.23.4 => 1.24.0
|
||||||
|
- github.com/rwinkhart/libmutton: v0.2.4 => v0.3.0
|
||||||
|
- github.com/alecthomas/chroma/v2: v2.14.0 => v2.15.0
|
||||||
|
- github.com/dlclark/regexp2: v1.11.4 => v1.11.5
|
||||||
|
- github.com/muesli/termenv: v0.15.2 => v0.16.0
|
||||||
|
- golang.org/x/term: v0.27.0 => v0.29.0
|
||||||
|
- golang.org/x/crypto: v0.31.0 => v0.34.0
|
||||||
|
- golang.org/x/net: v0.32.0 => v0.35.0
|
||||||
|
- golang.org/x/sys: v0.28.0 => v0.30.0
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
module github.com/rwinkhart/MUTN
|
module github.com/rwinkhart/MUTN
|
||||||
|
|
||||||
go 1.23.4
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Trojan2021/BEAN v0.0.0-20241210230804-8f294833b514
|
github.com/Trojan2021/BEAN v0.0.0-20241210230804-8f294833b514
|
||||||
github.com/charmbracelet/glamour v0.7.0
|
github.com/charmbracelet/glamour v0.7.0
|
||||||
github.com/rwinkhart/libmutton v0.2.4
|
github.com/rwinkhart/libmutton v0.3.0
|
||||||
golang.org/x/term v0.27.0
|
golang.org/x/term v0.29.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
|
github.com/alecthomas/chroma/v2 v2.15.0 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/boombuler/barcode v1.0.2 // indirect
|
github.com/boombuler/barcode v1.0.2 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.5.2 // indirect
|
github.com/charmbracelet/x/ansi v0.5.2 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727 // indirect
|
github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727 // indirect
|
||||||
github.com/gorilla/css v1.0.1 // indirect
|
github.com/gorilla/css v1.0.1 // indirect
|
||||||
github.com/kr/fs v0.1.0 // indirect
|
github.com/kr/fs v0.1.0 // indirect
|
||||||
@@ -24,7 +24,7 @@ require (
|
|||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
||||||
github.com/muesli/reflow v0.3.0 // indirect
|
github.com/muesli/reflow v0.3.0 // indirect
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
github.com/pkg/sftp v1.13.7 // indirect
|
github.com/pkg/sftp v1.13.7 // indirect
|
||||||
github.com/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481 // indirect
|
github.com/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481 // indirect
|
||||||
@@ -33,8 +33,8 @@ require (
|
|||||||
github.com/stretchr/testify v1.8.4 // indirect
|
github.com/stretchr/testify v1.8.4 // indirect
|
||||||
github.com/yuin/goldmark v1.7.8 // indirect
|
github.com/yuin/goldmark v1.7.8 // indirect
|
||||||
github.com/yuin/goldmark-emoji v1.0.4 // indirect
|
github.com/yuin/goldmark-emoji v1.0.4 // indirect
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
golang.org/x/crypto v0.34.0 // indirect
|
||||||
golang.org/x/net v0.32.0 // indirect
|
golang.org/x/net v0.35.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
github.com/Trojan2021/BEAN v0.0.0-20241210230804-8f294833b514 h1:PbqBQdkQ51VLfwemHdp3DqDAeMoPqzGSK39ZtFHJLG0=
|
github.com/Trojan2021/BEAN v0.0.0-20241210230804-8f294833b514 h1:PbqBQdkQ51VLfwemHdp3DqDAeMoPqzGSK39ZtFHJLG0=
|
||||||
github.com/Trojan2021/BEAN v0.0.0-20241210230804-8f294833b514/go.mod h1:Xkl7xpjhrhefSpFAJEc1uNIarmVU+EFASB8VLODTOsA=
|
github.com/Trojan2021/BEAN v0.0.0-20241210230804-8f294833b514/go.mod h1:Xkl7xpjhrhefSpFAJEc1uNIarmVU+EFASB8VLODTOsA=
|
||||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
|
github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
|
||||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.4.0/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 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
@@ -20,8 +20,8 @@ github.com/charmbracelet/x/ansi v0.5.2/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeur
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727 h1:1RkPJqfzrncAuh9xgoslr9OplZskm+VRA9lkucHPQZ4=
|
github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727 h1:1RkPJqfzrncAuh9xgoslr9OplZskm+VRA9lkucHPQZ4=
|
||||||
github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727/go.mod h1:wRAWHbTlpt0C4kwnKoa42L2Phrv6uIq+c50P2uKpb7I=
|
github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727/go.mod h1:wRAWHbTlpt0C4kwnKoa42L2Phrv6uIq+c50P2uKpb7I=
|
||||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||||
@@ -42,8 +42,8 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX
|
|||||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
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/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
|
github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
|
||||||
@@ -58,8 +58,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rwinkhart/convertroman v0.2.0 h1:otUm939eXT77q/Lr31mJtMwVWEw0RjEUB71gO65h9OM=
|
github.com/rwinkhart/convertroman v0.2.0 h1:otUm939eXT77q/Lr31mJtMwVWEw0RjEUB71gO65h9OM=
|
||||||
github.com/rwinkhart/convertroman v0.2.0/go.mod h1:Af6HqvX0EIM4Y3HcnNXbbRey1dLasGB7WU0+3Cowgmw=
|
github.com/rwinkhart/convertroman v0.2.0/go.mod h1:Af6HqvX0EIM4Y3HcnNXbbRey1dLasGB7WU0+3Cowgmw=
|
||||||
github.com/rwinkhart/libmutton v0.2.4 h1:Uu/Xjgeau1YiEztTaEeVQONopXuGVKty+gIb/xA6jQ8=
|
github.com/rwinkhart/libmutton v0.3.0 h1:afGm1VkczfQJ1VgPiL+9xTIXm3s46gtzZV/2d9wyQtQ=
|
||||||
github.com/rwinkhart/libmutton v0.2.4/go.mod h1:PdB+cyRsd3F4Uwxx8EJCRraJTwbvvrynY9iQVr/V+Wg=
|
github.com/rwinkhart/libmutton v0.3.0/go.mod h1:6xG5zJAQMgJC4xIC0pQwgg0N/hfM4ua7msTQpIBPOog=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
@@ -76,8 +76,8 @@ github.com/yuin/goldmark-emoji v1.0.4/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiav
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -85,8 +85,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -99,15 +99,15 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ func main() {
|
|||||||
case "show", "-s":
|
case "show", "-s":
|
||||||
cli.EntryReaderDecrypt(targetLocation, false)
|
cli.EntryReaderDecrypt(targetLocation, false)
|
||||||
case "copy":
|
case "copy":
|
||||||
core.CopyArgument(args[0], targetLocation, 0)
|
core.CopyArgument(targetLocation, 0)
|
||||||
case "edit":
|
case "edit":
|
||||||
cli.EditEntryField(targetLocation, true, 0)
|
cli.EditEntryField(targetLocation, true, 0)
|
||||||
case "gen":
|
case "gen":
|
||||||
@@ -46,7 +45,7 @@ func main() {
|
|||||||
case "add":
|
case "add":
|
||||||
cli.AddEntry(targetLocation, true, 0)
|
cli.AddEntry(targetLocation, true, 0)
|
||||||
case "shear":
|
case "shear":
|
||||||
sync.ShearRemoteFromClient(args[1]) // pass the incomplete path as the server and all clients (reading from the deletions directory) will have a different home directory
|
sync.ShearRemoteFromClient(args[1], false) // pass the incomplete path as the server and all clients (reading from the deletions directory) will have a different home directory
|
||||||
default:
|
default:
|
||||||
cli.HelpMain()
|
cli.HelpMain()
|
||||||
}
|
}
|
||||||
@@ -82,7 +81,7 @@ func main() {
|
|||||||
default:
|
default:
|
||||||
cli.HelpCopy()
|
cli.HelpCopy()
|
||||||
}
|
}
|
||||||
core.CopyArgument(args[0], targetLocation, field)
|
core.CopyArgument(targetLocation, field)
|
||||||
case "edit":
|
case "edit":
|
||||||
var field int // indicates which (numbered) field to edit
|
var field int // indicates which (numbered) field to edit
|
||||||
switch args[3] {
|
switch args[3] {
|
||||||
@@ -145,7 +144,7 @@ func main() {
|
|||||||
case "note", "-n":
|
case "note", "-n":
|
||||||
cli.AddEntry(targetLocation, true, 2)
|
cli.AddEntry(targetLocation, true, 2)
|
||||||
case "folder", "-f":
|
case "folder", "-f":
|
||||||
sync.AddFolderRemoteFromClient(args[1]) // pass the incomplete path as the server will have a different home directory
|
sync.AddFolderRemoteFromClient(args[1], false) // pass the incomplete path as the server will have a different home directory
|
||||||
default:
|
default:
|
||||||
cli.HelpAdd()
|
cli.HelpAdd()
|
||||||
}
|
}
|
||||||
@@ -160,12 +159,11 @@ func main() {
|
|||||||
case "clipclear":
|
case "clipclear":
|
||||||
core.ClipClearArgument()
|
core.ClipClearArgument()
|
||||||
case "sync":
|
case "sync":
|
||||||
sync.RunJob(true)
|
cli.RunJobWrapper(true)
|
||||||
case "init":
|
case "init":
|
||||||
cli.TempInitCli()
|
cli.TempInitCli()
|
||||||
case "tweak":
|
case "tweak":
|
||||||
fmt.Println(core.AnsiError + "\"tweak\" is not yet implemented" + core.AnsiReset)
|
core.PrintError("\"tweak\" is not yet implemented", 0, true)
|
||||||
os.Exit(0)
|
|
||||||
case "copy":
|
case "copy":
|
||||||
cli.HelpCopy()
|
cli.HelpCopy()
|
||||||
case "edit":
|
case "edit":
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MUTNVersion = "0.2.4" // untagged releases feature a letter suffix corresponding to the eventual release version, e.g "0.2.A" -> "0.2.0", "0.2.B" -> "0.2.1"
|
MUTNVersion = "0.2.5" // untagged releases feature a letter suffix corresponding to the eventual release version, e.g "0.2.A" -> "0.2.0", "0.2.B" -> "0.2.1"
|
||||||
|
|
||||||
AnsiBold = "\033[1m"
|
AnsiBold = "\033[1m"
|
||||||
ansiBlackOnWhite = "\033[38;5;0;48;5;15m"
|
ansiBlackOnWhite = "\033[38;5;0;48;5;15m"
|
||||||
|
|||||||
+4
-7
@@ -1,8 +1,6 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rwinkhart/libmutton/core"
|
"github.com/rwinkhart/libmutton/core"
|
||||||
@@ -14,8 +12,7 @@ func AddEntry(targetLocation string, hideSecrets bool, entryType uint8) {
|
|||||||
// ensure target location does not already exist
|
// ensure target location does not already exist
|
||||||
_, isAccessible := core.TargetIsFile(targetLocation, false, 0)
|
_, isAccessible := core.TargetIsFile(targetLocation, false, 0)
|
||||||
if isAccessible {
|
if isAccessible {
|
||||||
fmt.Println(core.AnsiError + "Target location already exists" + core.AnsiReset)
|
core.PrintError("Target location already exists", core.ErrorTargetExists, true)
|
||||||
os.Exit(core.ErrorTargetExists)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure target containing directory exists and is a directory (not a file)
|
// ensure target containing directory exists and is a directory (not a file)
|
||||||
@@ -29,12 +26,12 @@ func AddEntry(targetLocation string, hideSecrets bool, entryType uint8) {
|
|||||||
// determine whether to generate the password
|
// determine whether to generate the password
|
||||||
var password string
|
var password string
|
||||||
if entryType == 0 {
|
if entryType == 0 {
|
||||||
password = inputHidden("Password:")
|
password = string(inputHidden("Password:"))
|
||||||
} else {
|
} else {
|
||||||
password = core.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2, false)
|
password = core.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
totp := inputHidden("TOTP secret:")
|
totp := string(inputHidden("TOTP secret:"))
|
||||||
url := input("URL:")
|
url := input("URL:")
|
||||||
if inputBinary("Add a note to this entry?") {
|
if inputBinary("Add a note to this entry?") {
|
||||||
note, _ := editNote([]string{})
|
note, _ := editNote([]string{})
|
||||||
@@ -48,5 +45,5 @@ func AddEntry(targetLocation string, hideSecrets bool, entryType uint8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write and preview the new entry
|
// write and preview the new entry
|
||||||
writeEntryCLI(targetLocation, unencryptedEntry, hideSecrets, false)
|
writeEntryCLI(targetLocation, unencryptedEntry, hideSecrets)
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-17
@@ -2,11 +2,9 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/rwinkhart/libmutton/core"
|
"github.com/rwinkhart/libmutton/core"
|
||||||
"github.com/rwinkhart/libmutton/sync"
|
"github.com/rwinkhart/libmutton/sync"
|
||||||
@@ -16,7 +14,7 @@ import (
|
|||||||
func RenameCli(oldLocationIncomplete string) {
|
func RenameCli(oldLocationIncomplete string) {
|
||||||
// prompt user for new location and rename
|
// prompt user for new location and rename
|
||||||
newLocationIncomplete := input("New location:")
|
newLocationIncomplete := input("New location:")
|
||||||
sync.RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete)
|
sync.RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete, false)
|
||||||
|
|
||||||
// exit is done from sync.RenameRemoteFromClient
|
// exit is done from sync.RenameRemoteFromClient
|
||||||
}
|
}
|
||||||
@@ -29,11 +27,11 @@ func EditEntryField(targetLocation string, hideSecrets bool, field int) {
|
|||||||
// edit the field
|
// edit the field
|
||||||
switch field {
|
switch field {
|
||||||
case 0:
|
case 0:
|
||||||
unencryptedEntry[field] = inputHidden("Password:")
|
unencryptedEntry[field] = string(inputHidden("Password:"))
|
||||||
case 1:
|
case 1:
|
||||||
unencryptedEntry[field] = input("Username:")
|
unencryptedEntry[field] = input("Username:")
|
||||||
case 2:
|
case 2:
|
||||||
unencryptedEntry[field] = inputHidden("TOTP secret:")
|
unencryptedEntry[field] = string(inputHidden("TOTP secret:"))
|
||||||
case 3:
|
case 3:
|
||||||
unencryptedEntry[field] = input("URL:")
|
unencryptedEntry[field] = input("URL:")
|
||||||
case 4: // edit notes fields
|
case 4: // edit notes fields
|
||||||
@@ -44,14 +42,13 @@ func EditEntryField(targetLocation string, hideSecrets bool, field int) {
|
|||||||
// edit the note
|
// edit the note
|
||||||
editedNote, noteEdited := editNote(noteData)
|
editedNote, noteEdited := editNote(noteData)
|
||||||
if !noteEdited { // exit early if the note was not edited
|
if !noteEdited { // exit early if the note was not edited
|
||||||
fmt.Println(core.AnsiError + "Entry is unchanged" + core.AnsiReset)
|
core.PrintError("Entry is unchanged", 0, true)
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
unencryptedEntry = append(nonNoteData, editedNote...)
|
unencryptedEntry = append(nonNoteData, editedNote...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write and preview the modified entry
|
// write and preview the modified entry
|
||||||
writeEntryCLI(targetLocation, unencryptedEntry, hideSecrets, false)
|
writeEntryCLI(targetLocation, unencryptedEntry, hideSecrets)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenUpdate generates a new password for an entry at targetLocation (user input).
|
// GenUpdate generates a new password for an entry at targetLocation (user input).
|
||||||
@@ -63,7 +60,7 @@ func GenUpdate(targetLocation string, hideSecrets bool) {
|
|||||||
unencryptedEntry[0] = core.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2, false)
|
unencryptedEntry[0] = core.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2, false)
|
||||||
|
|
||||||
// write and preview the modified entry
|
// write and preview the modified entry
|
||||||
writeEntryCLI(targetLocation, unencryptedEntry, hideSecrets, false)
|
writeEntryCLI(targetLocation, unencryptedEntry, hideSecrets)
|
||||||
}
|
}
|
||||||
|
|
||||||
// editNote uses the user-specified text editor to edit an existing note (or create a new one if baseNote is empty).
|
// editNote uses the user-specified text editor to edit an existing note (or create a new one if baseNote is empty).
|
||||||
@@ -75,7 +72,8 @@ func editNote(baseNote []string) ([]string, bool) {
|
|||||||
}(tempFile.Name())
|
}(tempFile.Name())
|
||||||
|
|
||||||
// fetch the user's text editor
|
// fetch the user's text editor
|
||||||
editor := core.ParseConfig([][2]string{{"MUTN", "textEditor"}}, "")[0]
|
editorCfg, _ := core.ParseConfig([][2]string{{"MUTN", "textEditor"}}, "")
|
||||||
|
editor := editorCfg[0]
|
||||||
|
|
||||||
// write baseNote to tempFile (if it is not empty)
|
// write baseNote to tempFile (if it is not empty)
|
||||||
if len(baseNote) > 0 {
|
if len(baseNote) > 0 {
|
||||||
@@ -116,15 +114,13 @@ func editNote(baseNote []string) ([]string, bool) {
|
|||||||
// remove trailing empty strings from the edited note
|
// remove trailing empty strings from the edited note
|
||||||
note = core.RemoveTrailingEmptyStrings(note)
|
note = core.RemoveTrailingEmptyStrings(note)
|
||||||
|
|
||||||
// trim trailing whitespace from each note line
|
// clamp trailing whitespace in each note line
|
||||||
for i, line := range note {
|
core.ClampTrailingWhitespace(note)
|
||||||
note[i] = strings.TrimRight(line, " \t\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the edited note if it is different from baseNote
|
// return the edited note if it is different from baseNote and is not empty
|
||||||
if !reflect.DeepEqual(note, baseNote) {
|
if !reflect.DeepEqual(note, baseNote) && len(note) > 0 {
|
||||||
return note, true
|
return note, true
|
||||||
} else {
|
} else {
|
||||||
return []string{}, false
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
ansiAlternateEntryColor = "\033[38;5;8m"
|
ansiAlternateEntryColor = "\033[38;5;8m"
|
||||||
ansiDirectoryHeader = "\033[38;5;7;48;5;8m"
|
ansiDirectoryHeader = "\033[38;5;7;48;5;8m"
|
||||||
ansiEmptyDirectoryWarning = "\033[38;5;11m"
|
ansiEmptyDirectoryWarning = "\033[38;5;3m"
|
||||||
)
|
)
|
||||||
|
|
||||||
// determineIndentation calculates and returns the final visual indentation multiplier (needed to adjust indentation for skipped parent directories); also subtracts "old" text from directory header.
|
// determineIndentation calculates and returns the final visual indentation multiplier (needed to adjust indentation for skipped parent directories); also subtracts "old" text from directory header.
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/rwinkhart/libmutton/core"
|
"github.com/rwinkhart/libmutton/core"
|
||||||
"github.com/rwinkhart/libmutton/sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const ansiShownPassword = "\033[38;5;10m"
|
const ansiShownPassword = "\033[38;5;10m"
|
||||||
@@ -63,7 +62,7 @@ func EntryReader(decryptedEntry []string, hideSecrets, syncEnabled bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if syncEnabled {
|
if syncEnabled {
|
||||||
sync.RunJob(false)
|
RunJobWrapper(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
|||||||
+6
-7
@@ -21,8 +21,7 @@ func TempInitCli() {
|
|||||||
uidSlice := core.GpgUIDListGen()
|
uidSlice := core.GpgUIDListGen()
|
||||||
gpgIDInt := inputMenuGen("Select GPG key:", uidSlice)
|
gpgIDInt := inputMenuGen("Select GPG key:", uidSlice)
|
||||||
if gpgIDInt == 0 {
|
if gpgIDInt == 0 {
|
||||||
fmt.Println(core.AnsiError + "No GPG keys found - please generate one" + core.AnsiReset)
|
core.PrintError("No GPG keys found - please generate one", core.ErrorTargetNotFound, true)
|
||||||
os.Exit(core.ErrorTargetNotFound)
|
|
||||||
}
|
}
|
||||||
gpgID = uidSlice[gpgIDInt-1]
|
gpgID = uidSlice[gpgIDInt-1]
|
||||||
}
|
}
|
||||||
@@ -44,10 +43,10 @@ func TempInitCli() {
|
|||||||
var sshKeyIsFile bool
|
var sshKeyIsFile bool
|
||||||
for !sshKeyIsFile {
|
for !sshKeyIsFile {
|
||||||
fallbackSSHKey := core.Home + core.PathSeparator + ".ssh" + core.PathSeparator + "id_ed25519"
|
fallbackSSHKey := core.Home + core.PathSeparator + ".ssh" + core.PathSeparator + "id_ed25519"
|
||||||
sshKey = cmp.Or(expandPathWithHome(input("SSH private identity file path (falls back to \""+fallbackSSHKey+"\"):")), fallbackSSHKey)
|
sshKey = cmp.Or(core.ExpandPathWithHome(input("SSH private identity file path (falls back to \""+fallbackSSHKey+"\"):")), fallbackSSHKey)
|
||||||
sshKeyIsFile, _ = core.TargetIsFile(sshKey, false, 0)
|
sshKeyIsFile, _ = core.TargetIsFile(sshKey, false, 0)
|
||||||
if !sshKeyIsFile {
|
if !sshKeyIsFile {
|
||||||
fmt.Println(core.AnsiError+"SSH identity file not found:", sshKey+core.AnsiReset)
|
fmt.Println(core.AnsiError+"SSH identity file not found:", sshKey+core.AnsiReset) // do not exit after error (allow user to retry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,18 +56,18 @@ func TempInitCli() {
|
|||||||
oldDeviceID := core.DirInit(false)
|
oldDeviceID := core.DirInit(false)
|
||||||
|
|
||||||
// write config file (temporarily assigns sshEntryRoot and sshIsWindows to null to pass initial device ID registration)
|
// write config file (temporarily assigns sshEntryRoot and sshIsWindows to null to pass initial device ID registration)
|
||||||
core.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}, {"LIBMUTTON", "gpgID", gpgID}, {"LIBMUTTON", "sshUser", sshUser}, {"LIBMUTTON", "sshIP", sshIP}, {"LIBMUTTON", "sshPort", sshPort}, {"LIBMUTTON", "sshKey", sshKey}, {"LIBMUTTON", "sshKeyProtected", strconv.FormatBool(sshKeyProtected)}, {"LIBMUTTON", "sshEntryRoot", "null"}, {"LIBMUTTON", "sshIsWindows", "false"}}, false)
|
core.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}, {"LIBMUTTON", "gpgID", gpgID}, {"LIBMUTTON", "sshUser", sshUser}, {"LIBMUTTON", "sshIP", sshIP}, {"LIBMUTTON", "sshPort", sshPort}, {"LIBMUTTON", "sshKey", sshKey}, {"LIBMUTTON", "sshKeyProtected", strconv.FormatBool(sshKeyProtected)}, {"LIBMUTTON", "sshEntryRoot", "null"}, {"LIBMUTTON", "sshIsWindows", "false"}}, nil, false)
|
||||||
|
|
||||||
// generate and register device ID
|
// generate and register device ID
|
||||||
sshEntryRoot, sshIsWindows := sync.DeviceIDGen(oldDeviceID)
|
sshEntryRoot, sshIsWindows := sync.DeviceIDGen(oldDeviceID)
|
||||||
|
|
||||||
// update config file with sshEntryRoot and sshIsWindows
|
// update config file with sshEntryRoot and sshIsWindows
|
||||||
core.WriteConfig([][3]string{{"LIBMUTTON", "sshEntryRoot", sshEntryRoot}, {"LIBMUTTON", "sshIsWindows", sshIsWindows}}, true)
|
core.WriteConfig([][3]string{{"LIBMUTTON", "sshEntryRoot", sshEntryRoot}, {"LIBMUTTON", "sshIsWindows", sshIsWindows}}, nil, true)
|
||||||
} else {
|
} else {
|
||||||
// initialize libmutton directories
|
// initialize libmutton directories
|
||||||
core.DirInit(false)
|
core.DirInit(false)
|
||||||
|
|
||||||
// write config file
|
// write config file
|
||||||
core.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}, {"LIBMUTTON", "gpgID", gpgID}}, false)
|
core.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}, {"LIBMUTTON", "gpgID", gpgID}}, nil, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func HelpMain() {
|
func HelpMain() {
|
||||||
fmt.Print(AnsiBold + "\nMUTN | Copyright (c) 2024 Randall Winkhart\n" + core.AnsiReset + `
|
fmt.Print(AnsiBold + "\nMUTN | Copyright (c) 2024-2025 Randall Winkhart\n" + core.AnsiReset + `
|
||||||
This software exists under the MIT license; you may redistribute it under certain conditions.
|
This software exists under the MIT license; you may redistribute it under certain conditions.
|
||||||
This program comes with absolutely no warranty; type "mutn version" for details.
|
This program comes with absolutely no warranty; type "mutn version" for details.
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ func Version() {
|
|||||||
"\\" + ansiBlackOnWhite + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
|
"\\" + ansiBlackOnWhite + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
|
||||||
"\\" + ansiBlackOnWhite + " Built with libmutton v" + core.LibmuttonVersion + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
|
"\\" + ansiBlackOnWhite + " Built with libmutton v" + core.LibmuttonVersion + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
|
||||||
"\\" + ansiBlackOnWhite + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
|
"\\" + ansiBlackOnWhite + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
|
||||||
"\\" + ansiBlackOnWhite + " Copyright (c) 2024 Randall Winkhart " + core.AnsiReset + ansiVersionOutline + "/\n" +
|
"\\" + ansiBlackOnWhite + " Copyright (c) 2024-2025: Randall Winkhart " + core.AnsiReset + ansiVersionOutline + "/\n" +
|
||||||
"\\" + ansiBlackOnWhite + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
|
"\\" + ansiBlackOnWhite + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
|
||||||
"<><><><><><><><><><><><><><>-<><><><><><><><><><><><><><>\n" + core.AnsiReset +
|
"<><><><><><><><><><><><><><>-<><><><><><><><><><><><><><>\n" + core.AnsiReset +
|
||||||
"\n For more information, see:\n\n" +
|
"\n For more information, see:\n\n" +
|
||||||
|
|||||||
+11
-11
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rwinkhart/libmutton/core"
|
"github.com/rwinkhart/libmutton/core"
|
||||||
|
"github.com/rwinkhart/libmutton/sync"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,13 +19,12 @@ func input(prompt string) string {
|
|||||||
return strings.TrimRight(userInput, "\n\r ") // remove trailing newlines, carriage returns, and spaces
|
return strings.TrimRight(userInput, "\n\r ") // remove trailing newlines, carriage returns, and spaces
|
||||||
}
|
}
|
||||||
|
|
||||||
// inputHidden prompts the user for input and returns the input as a string, hiding the input from the terminal.
|
// inputHidden prompts the user for input and returns the input as a byte array, hiding the input from the terminal.
|
||||||
func inputHidden(prompt string) string {
|
func inputHidden(prompt string) []byte {
|
||||||
fmt.Print("\n" + prompt + " ")
|
fmt.Print("\n" + prompt + " ")
|
||||||
byteInput, _ := term.ReadPassword(int(os.Stdin.Fd()))
|
byteInput, _ := term.ReadPassword(int(os.Stdin.Fd()))
|
||||||
password := string(byteInput)
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
return password
|
return byteInput
|
||||||
}
|
}
|
||||||
|
|
||||||
// inputInt prompts the user for input and returns the input as an integer.
|
// inputInt prompts the user for input and returns the input as an integer.
|
||||||
@@ -65,20 +65,20 @@ func inputMenuGen(prompt string, options []string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// writeEntryCLI writes an entry to targetLocation and previews it (errors if no data is supplied).
|
// writeEntryCLI writes an entry to targetLocation and previews it (errors if no data is supplied).
|
||||||
func writeEntryCLI(targetLocation string, unencryptedEntry []string, hideSecrets, verifyEntryDoesNotExist bool) {
|
func writeEntryCLI(targetLocation string, unencryptedEntry []string, hideSecrets bool) {
|
||||||
if core.EntryIsNotEmpty(unencryptedEntry) {
|
if core.EntryIsNotEmpty(unencryptedEntry) {
|
||||||
// write the entry to the target location
|
// write the entry to the target location
|
||||||
core.WriteEntry(targetLocation, unencryptedEntry, verifyEntryDoesNotExist)
|
core.WriteEntry(targetLocation, unencryptedEntry)
|
||||||
// preview the entry
|
// preview the entry
|
||||||
fmt.Println(AnsiBold + "\nEntry Preview:" + core.AnsiReset)
|
fmt.Println(AnsiBold + "\nEntry Preview:" + core.AnsiReset)
|
||||||
EntryReader(unencryptedEntry, hideSecrets, true)
|
EntryReader(unencryptedEntry, hideSecrets, true)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(core.AnsiError + "No data supplied for entry" + core.AnsiReset)
|
core.PrintError("No data supplied for entry", core.ErrorTargetNotFound, true)
|
||||||
os.Exit(core.ErrorTargetNotFound)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// expandPathWithHome, given a path (as a string) containing "~", returns the path with "~" expanded to the user's home directory.
|
// RunJobWrapper is a wrapper for sync.RunJob that sets the passphrase input function to inputHidden.
|
||||||
func expandPathWithHome(path string) string {
|
func RunJobWrapper(manualSync bool) {
|
||||||
return strings.Replace(path, "~", core.Home, 1)
|
core.PassphraseInputFunction = inputHidden
|
||||||
|
sync.RunJob(manualSync, false)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user