20 Commits
Author SHA1 Message Date
RandyTheSilly c6ea3b0136 Prepare for release v0.2.5 2025-02-22 17:23:09 -05:00
RandyTheSilly a0d75aad20 Bump libmutton to v0.3.0; bump select indirect dependencies 2025-02-22 17:02:14 -05:00
RandyTheSilly 0a622765cc Bump Go to v1.24.0; bump dependencies (libmutton and glamour-related) 2025-02-15 13:09:04 -05:00
RandyTheSilly e2d79d4ce3 Make use of libmutton's new core.PrintError utility function 2025-02-01 21:01:35 -05:00
RandyTheSilly d323f2f75a Do not re-upload entries when adding a blank note 2025-02-01 12:19:27 -05:00
RandyTheSilly 4556e38e32 Save and restore space indices for new PowerShell sessions (fixes up arrow commands) 2025-01-28 22:06:58 -05:00
RandyTheSilly edc2ef25de Fix PowerShell completions breaking when multiple entries with similar names (containing spaces) exist in the same directory 2025-01-28 21:10:10 -05:00
RandyTheSilly f21041a38d Fix sync error when syncing a folder that was previously sheared (via libmutton bump) 2025-01-26 18:14:52 -05:00
RandyTheSilly 0c57bd5764 Allow leaving trailing spaces (two) in note lines for manual breaks in Markdown (currently only supported in BEAN) 2025-01-18 21:23:48 -05:00
RandyTheSilly dd8464ecd9 Do not check if an entry already exists when writing (add); an earlier check is already performed 2025-01-18 19:33:54 -05:00
RandyTheSilly 38ddc32656 Make "ansiEmptyDirectoryWarning" more visible in light terminal themes 2025-01-18 18:39:44 -05:00
RandyTheSilly 27e6972d82 Fix build failures on MacOS, Windows, and Termux (via libmutton bump) 2025-01-16 16:45:56 -05:00
RandyTheSilly 118ffc2adb Move expandPathWithHome to libmutton 2025-01-16 14:26:57 -05:00
RandyTheSilly d1d88c2e69 Port to newest libmutton development version 2025-01-16 13:49:21 -05:00
RandyTheSilly cb1b010e0a Timeout SSH dialing after 3 seconds (via libmutton bump) 2025-01-12 21:18:29 -05:00
RandyTheSilly 6210c9cd31 Port to newest libmutton development version; bump copyright date 2025-01-11 18:06:11 -05:00
RandyTheSilly 6d557a77c2 Port to newest libmutton development version 2024-12-29 16:42:34 -05:00
RandyTheSilly 4933da4c37 Bump libmutton and support new SSH identity file passphrase entry 2024-12-28 15:13:40 -05:00
RandyTheSilly 0e056822ef Bump golang.org/x/net to address CVE-2024-45338 2024-12-20 20:38:59 -05:00
RandyTheSilly c323899bf8 Update for compatibility with upcoming libmutton version's new copy behavior 2024-12-20 20:36:56 -05:00
16 changed files with 177 additions and 94 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
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
of this software and associated documentation files (the "Software"), to deal
+70 -7
View File
@@ -1,9 +1,58 @@
$global:cliMUTN_entriesToSpaceIndicesMap = @{}
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
$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)
if ($IsWindows) {
$entryRoot = (Resolve-Path '~\AppData\Local\libmutton\entries').Path
@@ -12,18 +61,27 @@ function cliMUTNEntryCompleter {
}
try {
$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 {
$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
$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*" }
}
function cliMUTNOptionCompleter {
function cliMUTN-optionCompleter {
param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
$possibleValues = @{
@@ -46,7 +104,7 @@ function mutn {
[CmdletBinding()]
param (
[Parameter(Position = 0)]
[ArgumentCompleter({ cliMUTNEntryCompleter @args })]
[ArgumentCompleter({ cliMUTN-entryCompleter @args })]
[string]$entry,
[Parameter(Position = 1)]
@@ -54,13 +112,18 @@ function mutn {
[string]$argument,
[Parameter(Position = 2, ValueFromRemainingArguments=$true)]
[ArgumentCompleter({ cliMUTNOptionCompleter @args })]
[ArgumentCompleter({ cliMUTN-optionCompleter @args })]
[string]$option
)
# replace placeholder underscores with escaped spaces
$escapedEntry = cliMUTN-getEscapedEntryName -Indices $global:cliMUTN_entriesToSpaceIndicesMap[$entry] -InputString $entry
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 {
Invoke-Expression -Command ($MUTNExecutablePath + ' ' + ($entry -replace ' ', '` ' -replace [char]0x259d, '` '), $argument, $option).Trim()
Invoke-Expression -Command ($global:MUTNExecutablePath + ' ' + $escapedEntry, $argument, $option).Trim()
}
}
cliMUTN-loadSpaceIndices
+1 -1
View File
@@ -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
\fBmutn\fR - Simple, self-hosted, SSH-synchronized password and note management based on libmutton. It is the successor to sshyp.
-2
View File
@@ -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**
Built with libmutton v0.2.4
December 13, 2024
+33
View File
@@ -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
+9 -9
View File
@@ -1,21 +1,21 @@
module github.com/rwinkhart/MUTN
go 1.23.4
go 1.24.0
require (
github.com/Trojan2021/BEAN v0.0.0-20241210230804-8f294833b514
github.com/charmbracelet/glamour v0.7.0
github.com/rwinkhart/libmutton v0.2.4
golang.org/x/term v0.27.0
github.com/rwinkhart/libmutton v0.3.0
golang.org/x/term v0.29.0
)
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/aymerick/douceur v0.2.0 // indirect
github.com/boombuler/barcode v1.0.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/gorilla/css v1.0.1 // 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/microcosm-cc/bluemonday v1.0.27 // 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/pkg/sftp v1.13.7 // 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/yuin/goldmark v1.7.8 // indirect
github.com/yuin/goldmark-emoji v1.0.4 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/crypto v0.34.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
+18 -18
View File
@@ -1,9 +1,9 @@
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/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
github.com/alecthomas/assert/v2 v2.7.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.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
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/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
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.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.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
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/go.mod h1:wRAWHbTlpt0C4kwnKoa42L2Phrv6uIq+c50P2uKpb7I=
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/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
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.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
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/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
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/rwinkhart/convertroman v0.2.0 h1:otUm939eXT77q/Lr31mJtMwVWEw0RjEUB71gO65h9OM=
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.2.4/go.mod h1:PdB+cyRsd3F4Uwxx8EJCRraJTwbvvrynY9iQVr/V+Wg=
github.com/rwinkhart/libmutton v0.3.0 h1:afGm1VkczfQJ1VgPiL+9xTIXm3s46gtzZV/2d9wyQtQ=
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
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.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA=
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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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.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.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
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-20220722155255-886fb9371eb4/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.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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
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-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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
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.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+6 -8
View File
@@ -1,7 +1,6 @@
package main
import (
"fmt"
"os"
"strings"
@@ -38,7 +37,7 @@ func main() {
case "show", "-s":
cli.EntryReaderDecrypt(targetLocation, false)
case "copy":
core.CopyArgument(args[0], targetLocation, 0)
core.CopyArgument(targetLocation, 0)
case "edit":
cli.EditEntryField(targetLocation, true, 0)
case "gen":
@@ -46,7 +45,7 @@ func main() {
case "add":
cli.AddEntry(targetLocation, true, 0)
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:
cli.HelpMain()
}
@@ -82,7 +81,7 @@ func main() {
default:
cli.HelpCopy()
}
core.CopyArgument(args[0], targetLocation, field)
core.CopyArgument(targetLocation, field)
case "edit":
var field int // indicates which (numbered) field to edit
switch args[3] {
@@ -145,7 +144,7 @@ func main() {
case "note", "-n":
cli.AddEntry(targetLocation, true, 2)
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:
cli.HelpAdd()
}
@@ -160,12 +159,11 @@ func main() {
case "clipclear":
core.ClipClearArgument()
case "sync":
sync.RunJob(true)
cli.RunJobWrapper(true)
case "init":
cli.TempInitCli()
case "tweak":
fmt.Println(core.AnsiError + "\"tweak\" is not yet implemented" + core.AnsiReset)
os.Exit(0)
core.PrintError("\"tweak\" is not yet implemented", 0, true)
case "copy":
cli.HelpCopy()
case "edit":
+1 -1
View File
@@ -11,7 +11,7 @@ var (
)
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"
ansiBlackOnWhite = "\033[38;5;0;48;5;15m"
+4 -7
View File
@@ -1,8 +1,6 @@
package cli
import (
"fmt"
"os"
"strings"
"github.com/rwinkhart/libmutton/core"
@@ -14,8 +12,7 @@ func AddEntry(targetLocation string, hideSecrets bool, entryType uint8) {
// ensure target location does not already exist
_, isAccessible := core.TargetIsFile(targetLocation, false, 0)
if isAccessible {
fmt.Println(core.AnsiError + "Target location already exists" + core.AnsiReset)
os.Exit(core.ErrorTargetExists)
core.PrintError("Target location already exists", core.ErrorTargetExists, true)
}
// 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
var password string
if entryType == 0 {
password = inputHidden("Password:")
password = string(inputHidden("Password:"))
} else {
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:")
if inputBinary("Add a note to this entry?") {
note, _ := editNote([]string{})
@@ -48,5 +45,5 @@ func AddEntry(targetLocation string, hideSecrets bool, entryType uint8) {
}
// write and preview the new entry
writeEntryCLI(targetLocation, unencryptedEntry, hideSecrets, false)
writeEntryCLI(targetLocation, unencryptedEntry, hideSecrets)
}
+13 -17
View File
@@ -2,11 +2,9 @@ package cli
import (
"bufio"
"fmt"
"os"
"os/exec"
"reflect"
"strings"
"github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/sync"
@@ -16,7 +14,7 @@ import (
func RenameCli(oldLocationIncomplete string) {
// prompt user for new location and rename
newLocationIncomplete := input("New location:")
sync.RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete)
sync.RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete, false)
// exit is done from sync.RenameRemoteFromClient
}
@@ -29,11 +27,11 @@ func EditEntryField(targetLocation string, hideSecrets bool, field int) {
// edit the field
switch field {
case 0:
unencryptedEntry[field] = inputHidden("Password:")
unencryptedEntry[field] = string(inputHidden("Password:"))
case 1:
unencryptedEntry[field] = input("Username:")
case 2:
unencryptedEntry[field] = inputHidden("TOTP secret:")
unencryptedEntry[field] = string(inputHidden("TOTP secret:"))
case 3:
unencryptedEntry[field] = input("URL:")
case 4: // edit notes fields
@@ -44,14 +42,13 @@ func EditEntryField(targetLocation string, hideSecrets bool, field int) {
// edit the note
editedNote, noteEdited := editNote(noteData)
if !noteEdited { // exit early if the note was not edited
fmt.Println(core.AnsiError + "Entry is unchanged" + core.AnsiReset)
os.Exit(0)
core.PrintError("Entry is unchanged", 0, true)
}
unencryptedEntry = append(nonNoteData, editedNote...)
}
// 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).
@@ -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)
// 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).
@@ -75,7 +72,8 @@ func editNote(baseNote []string) ([]string, bool) {
}(tempFile.Name())
// 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)
if len(baseNote) > 0 {
@@ -116,15 +114,13 @@ func editNote(baseNote []string) ([]string, bool) {
// remove trailing empty strings from the edited note
note = core.RemoveTrailingEmptyStrings(note)
// trim trailing whitespace from each note line
for i, line := range note {
note[i] = strings.TrimRight(line, " \t\r\n")
}
// clamp trailing whitespace in each note line
core.ClampTrailingWhitespace(note)
// return the edited note if it is different from baseNote
if !reflect.DeepEqual(note, baseNote) {
// return the edited note if it is different from baseNote and is not empty
if !reflect.DeepEqual(note, baseNote) && len(note) > 0 {
return note, true
} else {
return []string{}, false
return nil, false
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ import (
const (
ansiAlternateEntryColor = "\033[38;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.
+1 -2
View File
@@ -5,7 +5,6 @@ import (
"os"
"github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/sync"
)
const ansiShownPassword = "\033[38;5;10m"
@@ -63,7 +62,7 @@ func EntryReader(decryptedEntry []string, hideSecrets, syncEnabled bool) {
}
if syncEnabled {
sync.RunJob(false)
RunJobWrapper(false)
}
os.Exit(0)
+6 -7
View File
@@ -21,8 +21,7 @@ func TempInitCli() {
uidSlice := core.GpgUIDListGen()
gpgIDInt := inputMenuGen("Select GPG key:", uidSlice)
if gpgIDInt == 0 {
fmt.Println(core.AnsiError + "No GPG keys found - please generate one" + core.AnsiReset)
os.Exit(core.ErrorTargetNotFound)
core.PrintError("No GPG keys found - please generate one", core.ErrorTargetNotFound, true)
}
gpgID = uidSlice[gpgIDInt-1]
}
@@ -44,10 +43,10 @@ func TempInitCli() {
var sshKeyIsFile bool
for !sshKeyIsFile {
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)
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)
// 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
sshEntryRoot, sshIsWindows := sync.DeviceIDGen(oldDeviceID)
// 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 {
// initialize libmutton directories
core.DirInit(false)
// 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)
}
}
+2 -2
View File
@@ -14,7 +14,7 @@ const (
)
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 program comes with absolutely no warranty; type "mutn version" for details.
@@ -152,7 +152,7 @@ func Version() {
"\\" + ansiBlackOnWhite + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
"\\" + ansiBlackOnWhite + " Built with libmutton v" + core.LibmuttonVersion + " " + 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" +
"<><><><><><><><><><><><><><>-<><><><><><><><><><><><><><>\n" + core.AnsiReset +
"\n For more information, see:\n\n" +
+11 -11
View File
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/sync"
"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
}
// inputHidden prompts the user for input and returns the input as a string, hiding the input from the terminal.
func inputHidden(prompt string) string {
// inputHidden prompts the user for input and returns the input as a byte array, hiding the input from the terminal.
func inputHidden(prompt string) []byte {
fmt.Print("\n" + prompt + " ")
byteInput, _ := term.ReadPassword(int(os.Stdin.Fd()))
password := string(byteInput)
fmt.Println()
return password
return byteInput
}
// 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).
func writeEntryCLI(targetLocation string, unencryptedEntry []string, hideSecrets, verifyEntryDoesNotExist bool) {
func writeEntryCLI(targetLocation string, unencryptedEntry []string, hideSecrets bool) {
if core.EntryIsNotEmpty(unencryptedEntry) {
// write the entry to the target location
core.WriteEntry(targetLocation, unencryptedEntry, verifyEntryDoesNotExist)
core.WriteEntry(targetLocation, unencryptedEntry)
// preview the entry
fmt.Println(AnsiBold + "\nEntry Preview:" + core.AnsiReset)
EntryReader(unencryptedEntry, hideSecrets, true)
} else {
fmt.Println(core.AnsiError + "No data supplied for entry" + core.AnsiReset)
os.Exit(core.ErrorTargetNotFound)
core.PrintError("No data supplied for entry", core.ErrorTargetNotFound, true)
}
}
// expandPathWithHome, given a path (as a string) containing "~", returns the path with "~" expanded to the user's home directory.
func expandPathWithHome(path string) string {
return strings.Replace(path, "~", core.Home, 1)
// RunJobWrapper is a wrapper for sync.RunJob that sets the passphrase input function to inputHidden.
func RunJobWrapper(manualSync bool) {
core.PassphraseInputFunction = inputHidden
sync.RunJob(manualSync, false)
}