mirror of
https://github.com/rwinkhart/MUTN.git
synced 2026-08-28 12:56:30 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b366d1e13e | ||
|
|
34136633d9 | ||
|
|
8166bfcfbe | ||
|
|
63c663ab36 | ||
|
|
fab4bd8d37 | ||
|
|
c6ea3b0136 | ||
|
|
a0d75aad20 | ||
|
|
0a622765cc | ||
|
|
e2d79d4ce3 | ||
|
|
d323f2f75a | ||
|
|
4556e38e32 | ||
|
|
edc2ef25de | ||
|
|
f21041a38d | ||
|
|
0c57bd5764 | ||
|
|
dd8464ecd9 | ||
|
|
38ddc32656 | ||
|
|
27e6972d82 | ||
|
|
118ffc2adb | ||
|
|
d1d88c2e69 | ||
|
|
cb1b010e0a | ||
|
|
6210c9cd31 | ||
|
|
6d557a77c2 | ||
|
|
4933da4c37 | ||
|
|
0e056822ef | ||
|
|
c323899bf8 | ||
|
|
cfb4a2ad51 | ||
|
|
dda33755b3 | ||
|
|
544261a461 | ||
|
|
77014cf5e3 | ||
|
|
750029a491 | ||
|
|
380c49bd00 | ||
|
|
33b8704f0c | ||
|
|
f96b60d3ef | ||
|
|
13422e12c4 | ||
|
|
880d633da0 | ||
|
|
cbf9035969 | ||
|
|
a5d93d4527 | ||
|
|
0c62a84195 | ||
|
|
8c1b986e20 |
@@ -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
|
||||
|
||||
@@ -23,26 +23,36 @@ Additionally, MUTN is available as a source package ("[mutn](https://aur.archlin
|
||||
After installing, please review the [usage guide](https://github.com/rwinkhart/MUTN/blob/main/wiki/usage.md).
|
||||
|
||||
# Roadmap
|
||||
#### Release v0.3.0
|
||||
- [ ] Replace Glamour with a more minimal Markdown renderer (likely custom)
|
||||
#### Release v0.4.0
|
||||
### Release v0.3.0
|
||||
- [ ] libmutton v0.3.0
|
||||
- [ ] Swap to native (cascade) encryption (custom)
|
||||
- [ ] Implement "netpin" (quick-unlock) with new encryption
|
||||
#### Release v0.5.0
|
||||
- [ ] libmutton v0.4.0
|
||||
- [ ] Password aging support
|
||||
#### Release v0.6.0
|
||||
- [ ] libmutton v0.5.0
|
||||
- [ ] Add refresh/re-encrypt functionality
|
||||
- [ ] Re-implement init menu
|
||||
- [ ] Implement tweak menu
|
||||
#### Release v1.0.0
|
||||
- [ ] Bump Glamour version and switch to fully-compliant Markdown (do not preserve new lines)
|
||||
- [x] Add experimental build option for building with [BEAN](https://github.com/Trojan2021/BEAN) instead of Glamour
|
||||
### Release v0.4.0
|
||||
- [ ] Evaluate defaulting to building w/Bean over Glamour
|
||||
- [ ] Verify having Glamour in go.mod does not impact binary size/speed (drop it if it does)
|
||||
### Release v0.5.0
|
||||
- [ ] libmutton v0.4.0
|
||||
- [ ] Password aging support
|
||||
- [ ] Append UNIX timestamp to entry names
|
||||
- [ ] Trim timestamps to allow for vanity entry names in the following cases:
|
||||
- [ ] Argument parsing
|
||||
- [ ] Shell completions
|
||||
- [ ] Entry list readout
|
||||
- [ ] Add yellow/red dot indicators to entry list readout for when passwords should be changed
|
||||
### Release v0.6.0
|
||||
- [ ] libmutton v0.5.0
|
||||
- [ ] Swap to native (cascading) encryption
|
||||
- [ ] Implement "netpin" (quick-unlock) with new encryption
|
||||
### Release v1.0.0 - Distribution packages
|
||||
- [ ] Evaluate implementing custom types/interfaces (where applicable)
|
||||
- [ ] Create packaging scripts
|
||||
- [x] Stable source PKGBUILD
|
||||
- [ ] Stable source APKBUILD
|
||||
- [x] Stable source APKBUILD
|
||||
- [ ] Debian/Ubuntu
|
||||
- [ ] Fedora
|
||||
- [ ] FreeBSD
|
||||
- [ ] Windows installer
|
||||
- Hunt for polishing opportunities and bugs
|
||||
- Perform extensive testing
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
gofmt -l -w -s ./src/cli/*.go ./mutn.go
|
||||
git commit -am "$1"
|
||||
git push
|
||||
@@ -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,4 +1,4 @@
|
||||
.TH MUTN 1 "13 August 2024" "v0.2.1" "MUTN man page"
|
||||
.TH MUTN 1 "21 April 2025" "v0.2.6" "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.
|
||||
|
||||
@@ -1,4 +1,77 @@
|
||||
**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
|
||||
|
||||
# The Tripe Transmission Update - Patch 4
|
||||
|
||||
This release updates libmutton to v0.2.4 and includes only a minor tweak and some dependency bumps.
|
||||
|
||||
## IMPORTANT
|
||||
- This release includes a dependency bump to address a high-severity CVE (CVE-2024-45337) - Please update as soon as possible!
|
||||
|
||||
## Changes
|
||||
- A build option/tag was added (`BEAN`) for building with experimental [BEAN](https://github.com/Trojan2021/BEAN) Markdown rendering support (instead of Glamour)
|
||||
- Note that the BEAN Markdown renderer is a new project and is **not** near complete
|
||||
- The pre-compiled binaries provided below do **not** make use of this new build tag (they are still Glamour-based)
|
||||
- From libmutton v0.2.4:
|
||||
- The MIME type of data copied to the clipboard on UNIX-like systems (X11 and Wayland) is now statically set to "text/plain" to avoid potential issues with type inference
|
||||
|
||||
## Dependencies
|
||||
- Bumps (direct and indirect)
|
||||
- Go: v1.23.2 => 1.23.4
|
||||
- github.com/rwinkhart/libmutton: v0.2.3 => v0.2.4
|
||||
- github.com/charmbracelet/x/ansi: v0.4.0 => v0.5.2
|
||||
- github.com/pkg/sftp: v1.13.6 => v1.13.7
|
||||
- golang.org/x/crypto: v0.28.0 => v0.31.0
|
||||
- golang.org/x/net: v0.30.0 => v0.32.0
|
||||
- golang.org/x/sys: v0.26.0 => v0.28.0
|
||||
- golang.org/x/term: v0.25.0 => v0.27.0
|
||||
|
||||
---
|
||||
|
||||
**MUTN v0.2.3**
|
||||
Built with libmutton v0.2.3
|
||||
October 19, 2024
|
||||
|
||||
# The Tripe Transmission Update - Patch 3
|
||||
|
||||
This release updates libmutton to v0.2.3 to fix a minor bug causing the client to make a request to the server under an inappropriate condition.
|
||||
|
||||
## Fixes
|
||||
- From libmutton v0.2.3:
|
||||
- Fixed renaming/moving an entry/folder to an invalid path still having clients request the operation on the server
|
||||
|
||||
## Dependencies
|
||||
- Bumps (direct and indirect)
|
||||
- Go: v1.22.6 => 1.23.2
|
||||
- github.com/rwinkhart/libmutton: v0.2.2 => v0.2.3
|
||||
- github.com/yuin/goldmark: v1.7.4 => v1.7.8
|
||||
- github.com/yuin/goldmark-emoji: v1.0.3 => v1.0.4
|
||||
- golang.org/x/crypto: v0.26.0 => v0.28.0
|
||||
- golang.org/x/net: v0.28.0 => v0.30.0
|
||||
- golang.org/x/sys: v0.24.0 => v0.26.0
|
||||
- golang.org/x/term: v0.23.0 => v0.25.0
|
||||
|
||||
---
|
||||
|
||||
**MUTN v0.2.2**
|
||||
Built with libmutton v0.2.2
|
||||
August 20, 2024
|
||||
|
||||
# The Tripe Transmission Update - Patch 2
|
||||
|
||||
This release updates libmutton to v0.2.2 to address a regression from v0.2.1. Additionally, a Windows-only entry adding bug has been fixed.
|
||||
|
||||
## Fixes
|
||||
- From libmutton v0.2.2:
|
||||
- Edited entries are no longer improperly synced if their line count is reduced
|
||||
- (8c1b986e20bf8e02b4b27e18748f7677be2b2743) Adding new entries from Windows now works as intended
|
||||
|
||||
## Dependencies
|
||||
- Bumps (direct and indirect)
|
||||
- github.com/rwinkhart/libmutton: v0.2.1 => v0.2.2
|
||||
|
||||
---
|
||||
|
||||
**MUTN v0.2.1**
|
||||
Built with libmutton v0.2.1
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
**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.6**
|
||||
Built with libmutton v0.3.1
|
||||
April 21, 2025
|
||||
|
||||
# The Tripe Transmission Update - Patch 6
|
||||
|
||||
This release updates libmutton to v0.3.1 and brings in some minor bug fixes.
|
||||
|
||||
## libmutton-derived Changes
|
||||
- See [libmutton's release notes](https://github.com/rwinkhart/libmutton/releases/tag/v0.3.1)
|
||||
- Highlight feature: (34136633d93749bb75d680537ee2191a49d9d276) Added a new password generation option for moderately complex strings (better compatibility with many services than the previous complex passwords)
|
||||
- The previous generator is still accessible as the "ultra complex" option
|
||||
|
||||
## Fixes
|
||||
- (8166bfcfbe960d77b33e42de35fd0b31c9751039) Notes can once again be removed from entries
|
||||
|
||||
## Dependencies
|
||||
- Bumps (direct and indirect)
|
||||
- Go: v1.24.0 => 1.24.2
|
||||
- github.com/rwinkhart/libmutton: v0.3.0 => v0.3.1
|
||||
- golang.org/x/term: v0.29.0 => v0.31.0
|
||||
- golang.org/x/crypto: v0.34.0 => v0.37.0
|
||||
- golang.org/x/net: v0.35.0 => v0.39.0
|
||||
- golang.org/x/sys: v0.30.0 => v0.32.0
|
||||
- github.com/pkg/sftp: v1.13.7 => v1.13.9
|
||||
- github.com/alecthomas/chroma/v2: v2.15.0 => v2.16.0
|
||||
- github.com/yuin/goldmark: v1.7.8 => v1.7.10
|
||||
- github.com/yuin/goldmark-emoji: v1.0.4 => v1.0.6
|
||||
|
||||
---
|
||||
|
||||
**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,19 +1,21 @@
|
||||
module github.com/rwinkhart/MUTN
|
||||
|
||||
go 1.22.6
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/Trojan2021/BEAN v0.0.0-20241210230804-8f294833b514
|
||||
github.com/charmbracelet/glamour v0.7.0
|
||||
github.com/rwinkhart/libmutton v0.2.1
|
||||
golang.org/x/term v0.23.0
|
||||
github.com/rwinkhart/libmutton v0.3.1
|
||||
golang.org/x/term v0.31.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.16.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/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.5.2 // 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
|
||||
@@ -22,16 +24,21 @@ 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.6 // indirect
|
||||
github.com/pkg/sftp v1.13.9 // indirect
|
||||
github.com/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rwinkhart/convertroman v0.2.0 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/yuin/goldmark v1.7.4 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.3 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
github.com/yuin/goldmark v1.7.10 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.6 // indirect
|
||||
golang.design/x/clipboard v0.7.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/image v0.26.0 // indirect
|
||||
golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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/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.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.16.0 h1:QC5ZMizk67+HzxFDjQ4ASjni5kWBTGiigRG1u23IGvA=
|
||||
github.com/alecthomas/chroma/v2 v2.16.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
|
||||
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=
|
||||
@@ -13,13 +15,16 @@ github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMc
|
||||
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng=
|
||||
github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps=
|
||||
github.com/charmbracelet/x/ansi v0.5.2 h1:dEa1x2qdOZXD/6439s+wF7xjV+kZLu/iN00GuXXrU9E=
|
||||
github.com/charmbracelet/x/ansi v0.5.2/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q=
|
||||
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/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
@@ -38,12 +43,12 @@ 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.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
|
||||
github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA=
|
||||
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/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481 h1:FkxbO331O7mS5EJkP+MCi0o2gswh/Aezs+//NmefrR8=
|
||||
@@ -52,8 +57,10 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
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/libmutton v0.2.1 h1:J6tRCOJ22D3r0At7c1PnU5eHQkxxtrtue5b5lu/90Qw=
|
||||
github.com/rwinkhart/libmutton v0.2.1/go.mod h1:W4xYquN2Hz5rNyaaIUZCMTLvTNcAx804f1Y4vaUBmiw=
|
||||
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.3.1 h1:VCaviTYLO8dif75ZjOJh1EZoaM2zH5U1CaYOa0qkW/E=
|
||||
github.com/rwinkhart/libmutton v0.3.1/go.mod h1:bI03SUAheTefZYcEv19Vg7hWsEamd3qjqTqlB/Z6YSw=
|
||||
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=
|
||||
@@ -62,46 +69,88 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
|
||||
github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4=
|
||||
github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
|
||||
github.com/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI=
|
||||
github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
|
||||
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
|
||||
golang.design/x/clipboard v0.7.0 h1:4Je8M/ys9AJumVnl8m+rZnIvstSnYj1fvzqYrU3TXvo=
|
||||
golang.design/x/clipboard v0.7.0/go.mod h1:PQIvqYO9GP29yINEfsEn5zSQKAz3UgXmZKzDA6dnq2E=
|
||||
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.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||
golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 h1:8MGTx39304caZ/OMsjPfuxUoDGI2tRas92F5x97tIYc=
|
||||
golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7/go.mod h1:ftACcHgQ7vaOnQbHOHvXt9Y6bEPHrs5Ovk67ClwrPJA=
|
||||
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.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
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.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
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=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
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.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
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=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
|
||||
@@ -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":
|
||||
|
||||
+8
-4
@@ -22,11 +22,15 @@ case "$1" in
|
||||
. ./resources/release-binaries.sh
|
||||
create_release_binaries
|
||||
;;
|
||||
pkgbuild-git-stable)
|
||||
. ./resources/pkgbuild-git-stable.sh
|
||||
create_pkgbuild_git_stable
|
||||
pkgbuild-git-stable-optimized)
|
||||
. ./resources/pkgbuild-git-stable-optimized.sh
|
||||
create_pkgbuild_git_stable_optimized
|
||||
;;
|
||||
apkbuild-source-stable-optimized)
|
||||
. ./resources/apkbuild-source-stable-optimized.sh
|
||||
create_apkbuild_source_stable_optimized
|
||||
;;
|
||||
*)
|
||||
printf '\nusage: package.sh [target] <revision>\n\ntargets: release-binaries pkgbuild-git-stable\n\n'
|
||||
printf '\nusage: package.sh [target] <revision>\n\ntargets: release-binaries pkgbuild-git-stable-optimized apkbuild-source-stable-optimized\n\n'
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/bin/sh
|
||||
|
||||
create_apkbuild_source_stable_optimized() {
|
||||
printf '\nGenerating APKBUILD...\n'
|
||||
local source="https://github.com/rwinkhart/MUTN/archive/refs/tags/v"$version".tar.gz"
|
||||
local checksum=$(curl -sL "$source" | sha512sum | cut -d ' ' -f1)
|
||||
printf "# Maintainer: Randall Winkhart <idgr@tutanota.com>
|
||||
pkgname=mutn
|
||||
pkgver="$version"
|
||||
pkgrel="$((revision-1))"
|
||||
pkgdesc='A simple, self-hosted, SSH-synchronized password/note manager for the CLI (based on libmutton)'
|
||||
arch='all'
|
||||
url='https://github.com/rwinkhart/MUTN'
|
||||
license='MIT'
|
||||
depends='gnupg'
|
||||
makedepends='busybox go'
|
||||
options='!check net'
|
||||
source=\"\$pkgname-\$pkgver.tar.gz::"$source"\"
|
||||
|
||||
build() {
|
||||
cd \${srcdir}/MUTN-\$pkgver
|
||||
|
||||
# compress man page
|
||||
gzip -kf ./docs/man
|
||||
|
||||
# determine microarchitecture feature level
|
||||
case \$CARCH in
|
||||
'x86_64')
|
||||
cpuFlags=\$(grep -E 'flags\s+:\s' /proc/cpuinfo)
|
||||
if [ ! -z \"\$(echo \"\$cpuFlags\" | grep 'avx512f')\" ]; then
|
||||
export GOAMD64=v4
|
||||
elif [ ! -z \"\$(echo \"\$cpuFlags\" | grep 'avx2')\" ]; then
|
||||
export GOAMD64=v3
|
||||
elif [ ! -z \"\$(echo \"\$cpuFlags\" | grep 'sse4_2')\" ]; then
|
||||
export GOAMD64=v2
|
||||
else
|
||||
export GOAMD64=v1
|
||||
fi
|
||||
;;
|
||||
# TODO check aarch64 feature level
|
||||
esac
|
||||
|
||||
# compile binary
|
||||
CGO_ENABLED=1 go build -ldflags=\"-s -w\" -trimpath ./mutn.go
|
||||
}
|
||||
|
||||
package() {
|
||||
cd \${srcdir}/MUTN-\$pkgver
|
||||
install -Dm755 ./mutn \${pkgdir}/usr/bin/mutn
|
||||
install -Dm644 ./LICENSE \${pkgdir}/usr/share/licenses/mutn/LICENSE
|
||||
install -Dm644 ./completions/zsh/_mutn \${pkgdir}/usr/share/zsh/site-functions/_mutn
|
||||
install -Dm644 ./completions/bash/mutn \${pkgdir}/usr/share/bash-completion/completions/mutn
|
||||
install -Dm644 ./docs/man.gz \${pkgdir}/usr/share/man/man1/mutn.1.gz
|
||||
}
|
||||
|
||||
sha512sums=\"
|
||||
$checksum \$pkgname-\$pkgver.tar.gz
|
||||
\"
|
||||
" > 1output/APKBUILD
|
||||
printf '\nAPKBUILD generated\n\n'
|
||||
}
|
||||
+7
-6
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
create_pkgbuild_git_stable() {
|
||||
create_pkgbuild_git_stable_optimized() {
|
||||
printf '\nGenerating PKGBUILD...\n'
|
||||
local source="git+https://github.com/rwinkhart/MUTN.git#tag=v\${pkgver}"
|
||||
printf "# Maintainer: Randall Winkhart <idgr at tutanota dot com>
|
||||
@@ -11,7 +11,8 @@ pkgdesc='A simple, self-hosted, SSH-synchronized password/note manager for the C
|
||||
arch=('x86_64' 'i686' 'i486' 'pentium4' 'aarch64' 'armv7h' 'riscv64')
|
||||
url='https://github.com/rwinkhart/MUTN'
|
||||
license=('MIT')
|
||||
makedepends=(go util-linux gzip)
|
||||
depends=(gnupg)
|
||||
makedepends=(go grep gzip)
|
||||
optdepends=(
|
||||
'wl-clipboard: Wayland clipboard support'
|
||||
'xclip: X11 clipboard support'
|
||||
@@ -29,12 +30,12 @@ build() {
|
||||
# determine microarchitecture feature level
|
||||
case \$CARCH in
|
||||
'x86_64')
|
||||
lscpuOutput=\$(lscpu | grep Flags)
|
||||
if [ ! -z \"\$(echo \$lscpuOutput | grep avx512f)\" ]; then
|
||||
cpuFlags=\$(grep -E 'flags\s+:\s' /proc/cpuinfo)
|
||||
if [ ! -z \"\$(grep 'avx512f' <<< \"\$cpuFlags\")\" ]; then
|
||||
export GOAMD64=v4
|
||||
elif [ ! -z \"\$(echo \$lscpuOutput | grep avx2)\" ]; then
|
||||
elif [ ! -z \"\$(grep 'avx2' <<< \"\$cpuFlags\")\" ]; then
|
||||
export GOAMD64=v3
|
||||
elif [ ! -z \"\$(echo \$lscpuOutput | grep sse4_2)\" ]; then
|
||||
elif [ ! -z \"\$(grep 'sse4_2' <<< \"\$cpuFlags\")\" ]; then
|
||||
export GOAMD64=v2
|
||||
else
|
||||
export GOAMD64=v1
|
||||
+1
-1
@@ -11,7 +11,7 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
MUTNVersion = "0.2.1" // 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.6" // 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"
|
||||
|
||||
+6
-17
@@ -1,25 +1,14 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rwinkhart/libmutton/core"
|
||||
)
|
||||
|
||||
// AddEntry creates a new entry at targetLocation by taking user input via CLI prompts.
|
||||
// Requires: entryType (0 = standard password entry, 1 = auto-generated password entry, 2 = note-only entry).
|
||||
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)
|
||||
}
|
||||
|
||||
// ensure target containing directory exists and is a directory (not a file)
|
||||
core.TargetIsFile(targetLocation[:strings.LastIndex(targetLocation, "/")], true, 1)
|
||||
// ensure targetLocation is valid
|
||||
core.EntryAddPrecheck(targetLocation)
|
||||
|
||||
var unencryptedEntry []string
|
||||
|
||||
@@ -29,12 +18,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)
|
||||
password = inputPasswordGen()
|
||||
}
|
||||
|
||||
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 +37,5 @@ func AddEntry(targetLocation string, hideSecrets bool, entryType uint8) {
|
||||
}
|
||||
|
||||
// write and preview the new entry
|
||||
writeEntryCLI(targetLocation, unencryptedEntry, hideSecrets, false)
|
||||
writeEntryCLI(targetLocation, unencryptedEntry, hideSecrets)
|
||||
}
|
||||
|
||||
+29
-19
@@ -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).
|
||||
@@ -60,10 +57,10 @@ func GenUpdate(targetLocation string, hideSecrets bool) {
|
||||
unencryptedEntry := core.GetOldEntryData(targetLocation, 0)
|
||||
|
||||
// generate a new password
|
||||
unencryptedEntry[0] = core.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2, false)
|
||||
unencryptedEntry[0] = inputPasswordGen()
|
||||
|
||||
// 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,10 +72,21 @@ 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 {
|
||||
baseNoteLen := len(baseNote)
|
||||
switch baseNoteLen {
|
||||
case 0: // do not write empty baseNote to file
|
||||
case 1: // truncate baseNote if its only length is an empty string (do not write to file)
|
||||
if baseNote[0] == "" {
|
||||
baseNote = []string{}
|
||||
baseNoteLen = 0
|
||||
break
|
||||
}
|
||||
fallthrough // if baseNote contained real data, write it to tempFile
|
||||
default: // write baseNote to tempFile for external editing
|
||||
for _, line := range baseNote {
|
||||
_, _ = tempFile.WriteString(line + "\n")
|
||||
}
|
||||
@@ -116,15 +124,17 @@ 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
|
||||
// [it is not empty (prevent needless writes when adding blank notes) OR
|
||||
// it has a different length from baseNote (allow removing notes from entries)]
|
||||
noteLen := len(note)
|
||||
if !reflect.DeepEqual(note, baseNote) && (noteLen > 0 || baseNoteLen != noteLen) {
|
||||
return note, true
|
||||
} else {
|
||||
return []string{}, false
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -3,10 +3,8 @@ package cli
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rwinkhart/libmutton/core"
|
||||
"github.com/rwinkhart/libmutton/sync"
|
||||
)
|
||||
|
||||
const ansiShownPassword = "\033[38;5;10m"
|
||||
@@ -50,14 +48,13 @@ func EntryReader(decryptedEntry []string, hideSecrets, syncEnabled bool) {
|
||||
fmt.Println(ansiDirectoryHeader + "Notes:" + core.AnsiReset)
|
||||
|
||||
// combine remaining fields into a single string (to support Markdown rendering)
|
||||
var notesSlice []string
|
||||
var noteLines []string
|
||||
for ; field < len(decryptedEntry); field++ {
|
||||
notesSlice = append(notesSlice, decryptedEntry[field])
|
||||
noteLines = append(noteLines, decryptedEntry[field])
|
||||
}
|
||||
joinedString := strings.Join(notesSlice, "\n")
|
||||
|
||||
// print notes to stdout
|
||||
renderNote(&joinedString)
|
||||
renderNote(noteLines)
|
||||
|
||||
// break after notes have been printed
|
||||
break
|
||||
@@ -65,7 +62,7 @@ func EntryReader(decryptedEntry []string, hideSecrets, syncEnabled bool) {
|
||||
}
|
||||
|
||||
if syncEnabled {
|
||||
sync.RunJob(false)
|
||||
RunJobWrapper(false)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
//go:build !noMarkdown && BEAN
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
bean "github.com/Trojan2021/BEAN/render"
|
||||
)
|
||||
|
||||
// renderNote renders the notes section of an entry (in Markdown) to stdout.
|
||||
func renderNote(noteLines []string) {
|
||||
fmt.Println(bean.RenderMarkdown(noteLines, width))
|
||||
}
|
||||
@@ -4,9 +4,10 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// renderNote prints the notes section of an entry to stdout (when Markdown rendering is disabled).
|
||||
func renderNote(note *string) {
|
||||
fmt.Print("\n" + *note + "\n\n")
|
||||
func renderNote(noteLines []string) {
|
||||
fmt.Print("\n" + strings.Join(noteLines, "\n") + "\n\n")
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
//go:build !noMarkdown
|
||||
//go:build !noMarkdown && !BEAN
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/glamour"
|
||||
)
|
||||
|
||||
// renderNote renders the notes section of an entry (in Markdown) to stdout.
|
||||
func renderNote(note *string) {
|
||||
func renderNote(noteLines []string) {
|
||||
note := strings.Join(noteLines, "\n")
|
||||
r, _ := glamour.NewTermRenderer(glamour.WithStylesFromJSONBytes(glamourStyle()), glamour.WithPreservedNewLines(), glamour.WithWordWrap(width))
|
||||
markdownNotesString, _ := r.Render(*note)
|
||||
markdownNotesString, _ := r.Render(note)
|
||||
|
||||
// print markdown-rendered notes
|
||||
fmt.Print(markdownNotesString)
|
||||
+6
-7
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" +
|
||||
|
||||
+28
-11
@@ -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.
|
||||
@@ -64,21 +64,38 @@ func inputMenuGen(prompt string, options []string) int {
|
||||
return inputInt(prompt, len(options))
|
||||
}
|
||||
|
||||
// inputPasswordGen prompts the user for password generation parameters and returns a generated password as a string.
|
||||
func inputPasswordGen() string {
|
||||
passLength := inputInt("Password length:", -1)
|
||||
fmt.Println()
|
||||
passCharset := uint8(inputMenuGen("Password complexity:", []string{"Simple", "Complex", "Ultra Complex (not compatible with many services)"}))
|
||||
var complexity float64
|
||||
switch passCharset {
|
||||
case 1:
|
||||
complexity = 0 // simple
|
||||
// 1 indicates string gen for filenames, but since complexity is 0, only the base charset is used
|
||||
default:
|
||||
complexity = 0.2 // (ultra) complex
|
||||
// 2 and 3 indicate complex and ultra complex charsets, respectively
|
||||
}
|
||||
return core.StringGen(passLength, complexity, passCharset)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
+2
-1
@@ -5,6 +5,7 @@ CGO_ENABLED=0 go build -ldflags="-s -w" -trimpath ./mutn.go
|
||||
```
|
||||
|
||||
Additionally, some custom build tags can be used to create different binaries. The following tags are not used in official builds:
|
||||
- `noMarkdown`: Creates a binary without Markdown support (much smaller binary size and faster launch time)
|
||||
- `BEAN`: Creates a binary with the experimental [BEAN](https://github.com/Trojan2021/BEAN) Markdown renderer in place of Glamour
|
||||
- `noMarkdown`: Creates a binary without Markdown support (much smaller binary size and faster launch time VS Glamour)
|
||||
- `wsl`: Allows creating a Linux binary that can interact with the Windows clipboard (for WSL)
|
||||
- `termux`: Allows creating a Linux binary that can interact with the Termux clipboard (for Android)
|
||||
|
||||
@@ -10,3 +10,8 @@ Please note that the server binary (available in [libmutton releases](https://gi
|
||||
After placing the binaries in your $PATH, it is highly recommended to also download and correctly place/source the relevant [shell completions scripts](https://github.com/rwinkhart/MUTN/blob/main/wiki/completions.md).
|
||||
|
||||
Please see the [usage guide](https://github.com/rwinkhart/MUTN/blob/main/wiki/usage.md) for help getting started.
|
||||
|
||||
### Dependencies (required for all installations)
|
||||
- GnuPG command-line utility (`gpg` command; will not be required in the future) for encryption/decryption
|
||||
- A text editor (preferably CLI-based) for writing entry notes
|
||||
- A private key for SSH key-based authentication is required to use MUTN/libmutton in online/synced mode
|
||||
|
||||
Reference in New Issue
Block a user