automate/020_yazi-plugin_httm.sh

923 lines
27 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
: <<TODO
The main.lua now passes to commandline httm which fails because not snapshots
found (autosnapshot not installed yet), but not other tests done.
main.lua_old is broken (old syntax) but outlines more functionalty.
TODO
DEST=${1:-/etc/skel/}
YAZI_HOME="${DEST}/.config/yazi"
YAZI_PLUGIN_HOME="${YAZI_HOME}/plugins"
HTTM_PLUGIN_HOME="${YAZI_PLUGIN_HOME}/httm.yazi"
mkdir -p "${HTTM_PLUGIN_HOME}"
: <<TODO
Add support for current selection as input to httm (pipe paths via
:stdin(Command.PIPED) + write targets to child's stdin)
Extra modes: --args='last' → httm -l -r (restore latest), --args='diff' →
wrap bowie if installed
--no-live, --omit-ditto, --json parsing for richer integration
Custom preview command via --preview="bat --color=always {}" (needs
interactive mode tweak)
TODO
#----------------------------------------------------------------
conf_print_httm_require() {
cat <<'EOF'
require("httm"):setup({
-- Example: showing the number of snapshots in the status bar
show_count = true,
-- Example: custom color for the httm indicator
color = "#ff0000"
})
EOF
}
# conf_print_httm_require | tee -a "${YAZI_HOME}/init.lua"
#----------------------------------------------------------------
conf_print_httm_keymap() {
cat <<'EOF'
# ~/.config/yazi/keymap.toml
# httm plugin leader: <SHIFT>M
# Core / frequently used
[[manager.prepend_keymap]]
on = [ "M", "s" ]
run = "plugin httm snapshot"
desc = "httm: Snapshot current or selected items"
[[manager.prepend_keymap]]
on = [ "M", "b" ]
run = "plugin httm browse"
desc = "httm: Interactive browse + restore (full TUI)"
[[manager.prepend_keymap]]
on = [ "M", "y" ]
run = "plugin httm select"
desc = "httm: Select snapshot versions → yank paths"
# Time-travel navigation (non-interactive cd into snapshots)
[[manager.prepend_keymap]]
on = [ "M", "p" ]
run = "plugin httm prev"
desc = "httm: Jump to previous (older) snapshot"
[[manager.prepend_keymap]]
on = [ "M", "n" ]
run = "plugin httm next"
desc = "httm: Jump to next (newer) snapshot"
[[manager.prepend_keymap]]
on = [ "M", "l" ] # l = live
run = "plugin httm exit"
desc = "httm: Exit back to live filesystem"
# Diffs (bowie)
[[manager.prepend_keymap]]
on = [ "M", "d" ]
run = "plugin httm bowie"
desc = "httm/bowie: Show diff with last snapshot"
[[manager.prepend_keymap]]
on = [ "M", "D" ]
run = "plugin httm bowie-all"
desc = "httm/bowie: Show diffs across all versions"
# Other helpers
[[manager.prepend_keymap]]
on = [ "M", "o" ]
run = "plugin httm ounce"
desc = "httm/ounce: Pre-emptive dynamic snapshot check"
[[manager.prepend_keymap]]
on = [ "M", "g" ]
run = "plugin httm nicotine"
desc = "httm/nicotine: Export versions as git archive"
# Time Machine (equine) optional sub-prefix `ht`
[[manager.prepend_keymap]]
on = [ "M", "t", "m" ]
run = "plugin httm equine-mount-local"
desc = "httm/equine: Mount local Time Machine snapshots"
[[manager.prepend_keymap]]
on = [ "M", "t", "u" ]
run = "plugin httm equine-umount-local"
desc = "httm/equine: Unmount local Time Machine"
[[manager.prepend_keymap]]
on = [ "M", "t", "r" ]
run = "plugin httm equine-mount-remote"
desc = "httm/equine: Mount remote Time Machine"
[[manager.prepend_keymap]]
on = [ "M", "t", "R" ]
run = "plugin httm equine-umount-remote"
desc = "httm/equine: Unmount remote Time Machine"
EOF
}
# conf_print_httm_keymap | tee -a "${YAZI_HOME}/keymap.toml"
#----------------------------------------------------------------
conf_print_httm_help_keymap() {
cat <<'EOF'
[[manager.prepend_keymap]]
on = [ "M", "?" ]
run = '''shell --confirm '
cat << "FOE"
httm commands (leader: ` h)
s snapshot current/selected
b browse + restore (TUI)
y select versions → yank
p previous snapshot
n next snapshot
l back to live filesystem
d diff last version (bowie)
D diff all versions
o ounce dynamic snapshot
g nicotine git archive
t m mount local Time Machine
t u unmount local
t r mount remote
t R unmount remote
FOE
' '''
desc = "httm: Show command help"
EOF
}
# conf_print_httm_help_keymap | tee -a "${YAZI_HOME}/keymap.toml"
#----------------------------------------------------------------
# Create the plugin main.lua
conf_print_httm_main() {
cat <<'EOF'
-- Helper to get targets (selected or hovered files)
-- Uses ya.sync because cx (context) access must be synchronous
local get_targets = ya.sync(function()
local tab = cx.active
local selected = tab.selected
if #selected == 0 then
local hovered = tab.current.hovered
if hovered then
return { tostring(hovered.url) }
else
return { tostring(tab.current.cwd) }
end
end
local targets = {}
for _, u in ipairs(selected) do
table.insert(targets, tostring(u))
end
return targets
end)
local function notify(msg, level)
ya.notify({
title = "httm.yazi",
content = msg,
timeout = level == "error" and 6 or level == "warn" and 4 or 3,
level = level or "info",
})
end
local function script_exists(name)
local cmd = os.Command("which"):arg(name):stdout(os.Command.PIPED):output()
return cmd and cmd.status.success and cmd.stdout:match("%S") ~= nil
end
local function run_interactive(cmd_name, args)
local permit = ui.hide() -- ← this is the key change
local child, err =
Command(cmd_name):arg(args or {}):stdin(Command.INHERIT):stdout(Command.INHERIT):stderr(Command.INHERIT):spawn()
if not child then
ya.notify({
title = "Spawn Error",
content = "Failed to start " .. cmd_name .. ": " .. tostring(err),
level = "error",
timeout = 5,
})
permit:drop() -- important: always release the permit on early exit
return
end
local status = child:wait()
-- Clean up
permit:drop()
if not status or not status.success then
ya.notify({
title = cmd_name,
content = "Process exited with non-zero status",
level = "warn",
timeout = 3,
})
end
end
-- Improved sudo handling
local function sudo_already()
local status = os.Command("sudo"):args({ "--validate", "--non-interactive" }):status()
return status and status.success
end
local function run_with_sudo(program, args)
local cmd = os.Command("sudo"):arg(program):args(args):stdout(os.Command.PIPED):stderr(os.Command.PIPED)
if sudo_already() then
return cmd:output()
end
local _permit = ya.hide()
ya.notify({ title = "httm.yazi", content = "Sudo password required...", level = "info" })
local output = cmd:output()
return (output and (output.status.success or sudo_already())) and output or nil
end
-- FS detection utilities
local get_cwd = ya.sync(function()
return tostring(cx.active.current.cwd)
end)
local function trim(s)
return s:match("^%s*(.-)%s*$")
end
local function get_filesystem_type(cwd)
local out = os.Command("stat"):args({ "-f", "-c", "%T", cwd }):output()
return out and out.status.success and trim(out.stdout) or nil
end
-- [ZFS/BTRFS logic remains logically the same, updated to os.Command]
local function zfs_dataset(cwd)
local out = os.Command("df"):args({ "--output=source", cwd }):output()
if not out or not out.status.success then
return nil
end
local dataset
for line in out.stdout:gmatch("[^\r\n]+") do
dataset = line
end
return dataset
end
-- ... [Other ZFS/BTRFS helpers omitted for brevity, ensure they use os.Command] ...
local function snapshot()
local targets = get_targets()
if #targets == 0 then
return notify("No target", "error")
end
local cmd = os.Command("httm"):arg("--snap")
for _, t in ipairs(targets) do
cmd:arg(t)
end
local output = cmd:output()
if output and output.status.success then
notify("Snapshot created")
ya.mgr_emit("refresh", {})
else
notify("httm --snap failed; trying sudo...", "warn")
run_with_sudo("httm", { "--snap", unpack(targets) })
ya.mgr_emit("refresh", {})
end
end
local function select_files()
local _permit = ya.hide()
local cmd = os.Command("httm"):args({ "-s", "-R" }):stdout(os.Command.PIPED):stderr(os.Command.PIPED)
local output = cmd:output()
if not output or not output.status.success then
notify("httm selection failed", "error")
return
end
local paths = {}
for line in output.stdout:gmatch("[^\n]+") do
local trimmed = trim(line)
if trimmed ~= "" then
table.insert(paths, trimmed)
end
end
if #paths > 0 then
ya.mgr_emit("yank", { paths = paths, silent = true })
notify("Yanked " .. #paths .. " paths")
end
end
-- Time travel implementation using updated ya.mgr_emit
local function time_travel(action)
-- ... (Logic for finding next/prev snapshot path) ...
-- Use: ya.mgr_emit("cd", { path })
end
return {
entry = function(_, job)
local arg = job.args[1]
if arg == "snapshot" then
snapshot()
elseif arg == "browse" then
run_interactive("httm", { "-r", "-R" })
elseif arg == "select" then
select_files()
elseif arg == "bowie" then
local targets = get_targets()
run_interactive("bowie", targets)
-- ... add other cases matching your original logic ...
elseif arg == "prev" or arg == "next" or arg == "exit" then
time_travel(arg)
else
notify("Unknown command: " .. tostring(arg), "error")
end
end,
}
EOF
}
conf_print_httm_main | tee "${HTTM_PLUGIN_HOME}/main.lua"
#----------------------------------------------------------------
conf_print_httm_main_old() {
cat <<'EOF'
-- ~/.config/yazi/plugins/httm.yazi/main.lua
local function get_targets(cx)
local tab = cx.active
local selected = tab.selected
if #selected == 0 then
local hovered = tab.current.hovered
if hovered then
return { tostring(hovered.url) }
else
return { tostring(tab.current.cwd) }
end
end
local targets = {}
for _, u in ipairs(selected) do
table.insert(targets, tostring(u))
end
return targets
end
local function notify(msg, level)
ya.notify {
title = "httm.yazi",
content = msg,
timeout = level == "error" and 6 or level == "warn" and 4 or 3,
level = level or "info",
}
end
local function script_exists(name)
local cmd = Command("which"):arg(name):stdout(Command.PIPED):output()
return cmd and cmd.status.success and cmd.stdout:match("%S") ~= nil
end
local function run_interactive(cx, cmd_name, args)
local _permit = ya.hide()
local cmd = Command(cmd_name):args(args or {})
local child, err = cmd:spawn()
if not child then
notify("Failed to spawn " .. cmd_name .. ": " .. tostring(err), "error")
return
end
local status = child:wait()
if not status or not status.success then
notify(cmd_name .. " exited abnormally", "warn")
end
ya.manager_emit("refresh", {})
end
local function run_capture_output(cmd_name, args)
local cmd = Command(cmd_name)
:args(args or {})
:stdout(Command.PIPED)
:stderr(Command.PIPED)
local output = cmd:output()
if not output or not output.status.success then
local msg = cmd_name .. " failed"
if output and output.stderr and #output.stderr > 0 then
msg = msg .. ": " .. output.stderr:gsub("\n$", "")
end
notify(msg, "error")
return nil
end
return output.stdout
end
-- Improved sudo handling from reference
local function sudo_already()
local status = Command("sudo"):args({ "--validate", "--non-interactive" }):status()
return status and status.success
end
local function run_with_sudo(program, args)
local cmd = Command("sudo"):arg(program):args(args):stdout(Command.PIPED):stderr(Command.PIPED)
if sudo_already() then
local output = cmd:output()
return output and output.status.success and output or nil
end
local _permit = ya.hide()
ya.notify({ title = "httm.yazi", content = "Sudo password required for " .. program, level = "info", timeout = 3 })
local output = cmd:output()
_permit:drop()
if output and (output.status.success or sudo_already()) then
return output
end
return nil
end
-- FS detection and snapshot listing from time-travel.yazi reference
local get_cwd = ya.sync(function()
return tostring(cx.active.current.cwd)
end)
local function trim(s)
return s:match("^%s*(.-)%s*$")
end
local function get_filesystem_type(cwd)
local out = Command("stat"):args({ "-f", "-c", "%T", cwd }):output()
return out and out.status.success and trim(out.stdout) or nil
end
local function zfs_dataset(cwd)
local out = Command("df"):args({ "--output=source", cwd }):output()
if not out or not out.status.success then return nil end
local dataset
for line in out.stdout:gmatch("[^\r\n]+") do
dataset = line
end
return dataset
end
local function zfs_mountpoint(dataset)
local out = Command("zfs"):args({ "get", "-H", "-o", "value", "mountpoint", dataset }):output()
if not out or not out.status.success then return nil end
local mp = trim(out.stdout)
if mp == "legacy" then
local df_out = Command("df"):output()
if not df_out or not df_out.status.success then return nil end
for line in df_out.stdout:gmatch("[^\r\n]+") do
if line:find(dataset, 1, true) == 1 then
local mountpoint
for field in line:gmatch("%S+") do
mountpoint = field
end
return mountpoint
end
end
end
return mp
end
local function zfs_relative(cwd, mountpoint)
local relative = cwd:sub(1, #mountpoint) == mountpoint and cwd:sub(#mountpoint + 1) or cwd
local snap_pos = cwd:find(".zfs/snapshot")
if snap_pos then
local after = cwd:sub(snap_pos + #"/snapshot" + 1)
local first_slash = after:find("/")
return first_slash and after:sub(first_slash) or "/"
end
return relative
end
local function zfs_snapshots(dataset, mountpoint, relative)
local out = Command("zfs"):args({ "list", "-H", "-t", "snapshot", "-o", "name", "-S", "creation", dataset }):output()
if not out or not out.status.success then return {} end
local snapshots = {}
for snap in out.stdout:gmatch("[^\r\n]+") do
local sep = snap:find("@")
if sep then
local id = snap:sub(sep + 1)
table.insert(snapshots, { id = id, path = mountpoint .. "/.zfs/snapshot/" .. id .. relative })
end
end
return snapshots
end
local function btrfs_mountpoint(cwd)
local out = Command("findmnt"):args({ "-no", "TARGET", "-T", cwd }):output()
return out and out.status.success and trim(out.stdout) or nil
end
local function btrfs_uuids(cwd)
local out = run_with_sudo("btrfs", { "subvolume", "show", cwd })
if not out then return nil, nil end
local parent_uuid, uuid
for line in out.stdout:gmatch("[^\r\n]+") do
local p = line:match("^%s*Parent UUID:%s*(%S+)")
if p then parent_uuid = trim(p) end
local u = line:match("^%s*UUID:%s*(%S+)")
if u then uuid = trim(u) end
end
return parent_uuid, uuid
end
local function btrfs_snapshots(mountpoint, current_uuid, current_parent_uuid)
local out = run_with_sudo("btrfs", { "subvolume", "list", "-q", "-u", mountpoint })
if not out then return { snapshots = {}, latest_path = "", current_snapshot_id = "" } end
local snapshots = {}
local latest_path = ""
local current_snapshot_id = ""
for line in out.stdout:gmatch("[^\r\n]+") do
local subvol_id, parent_uuid, uuid, name = line:match("ID (%d+) gen %d+ top level %d+ parent_uuid ([%w-]+)%s+uuid ([%w-]+) path (%S+)")
if subvol_id then
parent_uuid = trim(parent_uuid)
local path = mountpoint .. "/" .. name
local is_parent = (current_parent_uuid == "-" and parent_uuid == "-" and uuid == current_uuid) or (uuid == current_parent_uuid)
if is_parent then
latest_path = path
elseif uuid == current_uuid then
current_snapshot_id = name
end
if not is_parent then
table.insert(snapshots, { id = name, subvol_id = tonumber(subvol_id), path = path })
end
end
end
table.sort(snapshots, function(a, b) return a.subvol_id > b.subvol_id end)
return { snapshots = snapshots, latest_path = latest_path, current_snapshot_id = current_snapshot_id }
end
-- Emulated time-travel actions
local function time_travel(cx, action)
if not (action == "prev" or action == "next" or action == "exit") then
return notify("Invalid time-travel action: " .. action, "error")
end
local cwd = get_cwd()
local fs_type = get_filesystem_type(cwd)
if not (fs_type == "zfs" or fs_type == "btrfs") then
return notify("Unsupported FS: " .. (fs_type or "unknown"), "error")
end
local current_snapshot_id = ""
local latest_path = ""
local snapshots = {}
if fs_type == "zfs" then
local dataset = zfs_dataset(cwd)
if not dataset then return notify("No ZFS dataset", "error") end
local sep = dataset:find("@")
if sep then
current_snapshot_id = dataset:sub(sep + 1)
dataset = dataset:sub(1, sep - 1)
end
local mountpoint = zfs_mountpoint(dataset)
if not mountpoint then return notify("No ZFS mountpoint", "error") end
local relative = zfs_relative(cwd, mountpoint)
latest_path = mountpoint .. relative
snapshots = zfs_snapshots(dataset, mountpoint, relative)
elseif fs_type == "btrfs" then
local mountpoint = btrfs_mountpoint(cwd)
local parent_uuid, uuid = btrfs_uuids(cwd)
if not mountpoint or not uuid then return notify("No BTRFS subvolume", "error") end
local ret = btrfs_snapshots(mountpoint, uuid, parent_uuid)
snapshots = ret.snapshots
latest_path = ret.latest_path
current_snapshot_id = ret.current_snapshot_id
end
if action == "exit" then
ya.manager_emit("cd", { latest_path })
return
end
if #snapshots == 0 then
return notify("No snapshots found", "warn")
end
local function find_index(arr, pred)
for i, v in ipairs(arr) do
if pred(v) then return i end
end
return nil
end
local function goto_snapshot(start_idx, end_idx, step)
if start_idx == 0 then
ya.manager_emit("cd", { latest_path })
return true
elseif start_idx < 1 or start_idx > #snapshots then
notify("No more snapshots in that direction", "warn")
return false
end
for i = start_idx, end_idx, step do
local path = snapshots[i].path
local f = io.open(path, "r")
if f then
f:close()
ya.manager_emit("cd", { path })
return true
end
end
notify("No accessible snapshots in that direction", "warn")
return false
end
if current_snapshot_id == "" then
if action == "prev" then
goto_snapshot(1, #snapshots, 1)
elseif action == "next" then
notify("Already at latest; use exit to go live", "info")
end
return
end
local idx = find_index(snapshots, function(s) return s.id == current_snapshot_id end)
if not idx then return notify("Current snapshot not found", "error") end
if action == "prev" then
goto_snapshot(idx + 1, #snapshots, 1)
elseif action == "next" then
goto_snapshot(idx - 1, 0, -1)
end
end
local function snapshot(cx)
-- [existing snapshot function unchanged]
local targets = get_targets(cx)
if #targets == 0 then return notify("No target", "error") end
local cmd = Command("httm"):arg("--snap")
for _, t in ipairs(targets) do cmd:arg(t) end
local child, err = cmd:spawn()
if not child then return notify("Spawn failed: " .. tostring(err), "error") end
local status = child:wait()
if status and status.success then
notify("Snapshot created")
ya.manager_emit("refresh", {})
return
end
notify("httm --snap failed → retrying sudo...", "warn")
local sudo_cmd = Command("sudo"):arg("httm"):arg("--snap")
for _, t in ipairs(targets) do sudo_cmd:arg(t) end
local schild, serr = sudo_cmd:spawn()
if not schild then return notify("sudo spawn failed: " .. tostring(serr), "error") end
local sstatus = schild:wait()
if sstatus and sstatus.success then
notify("Snapshot created (sudo)")
ya.manager_emit("refresh", {})
else
notify("Snapshot failed (even sudo)", "error")
end
end
local function browse(cx)
run_interactive(cx, "httm", { "-r", "-R" })
end
local function select_files(cx)
-- [existing select_files unchanged]
local _permit = ya.hide()
local cmd = Command("httm")
:args({ "-s", "-R" })
:stdout(Command.PIPED)
:stderr(Command.PIPED)
local output = cmd:output()
if not output or not output.status.success then
# notify("httm -s -R failed: " .. (output and output.stderr or "unknown"), "error")
# return
# end
#
# local paths = {}
# for line in output.stdout:gmatch("[^\n]+") do
# local trimmed = line:match("^%s*(.-)%s*$")
# if trimmed ~= "" then table.insert(paths, trimmed) end
# end
#
# if #paths == 0 then
# notify("No files selected", "info")
# return
# end
#
# ya.manager_emit("yank", { paths = paths, silent = true })
# notify("Yanked " .. #paths .. " snapshot path(s)")
# end
#
# -- [bowie, ounce, nicotine, equine unchanged]
#
# local function bowie(cx, mode)
# local targets = get_targets(cx)
# if #targets == 0 then return notify("No target for bowie", "error") end
#
if not script_exists("bowie") then
return notify("bowie script not found in PATH", "error")
end
local args = {}
if mode == "all" then table.insert(args, "--all") end
if mode == "select" then table.insert(args, "--select") end
for _, t in ipairs(targets) do table.insert(args, t) end
run_interactive(cx, "bowie", args)
end
local function ounce(cx)
local targets = get_targets(cx)
if #targets == 0 then return notify("No target for ounce", "error") end
if not script_exists("ounce") then
return notify("ounce script not found in PATH", "error")
end
local args = { "--direct" }
for _, t in ipairs(targets) do table.insert(args, t) end
run_interactive(cx, "ounce", args)
end
local function nicotine(cx)
local targets = get_targets(cx)
if #targets == 0 then return notify("No target for nicotine", "error") end
if not script_exists("nicotine") then
return notify("nicotine script not found in PATH", "error")
end
run_interactive(cx, "nicotine", targets)
end
local function equine(cx, subcmd)
if not script_exists("equine") then
return notify("equine script not found in PATH", "error")
end
local arg = "--mount-local"
if subcmd == "umount-local" then arg = "--unmount-local"
elseif subcmd == "mount-remote" then arg = "--mount-remote"
elseif subcmd == "umount-remote" then arg = "--unmount-remote" end
run_interactive(cx, "equine", { arg })
end
return {
entry = function(_, job)
local arg = job.args[1]
if arg == "snapshot" then
snapshot(_)
elseif arg == "browse" then
browse(_)
elseif arg == "select" then
select_files(_)
elseif arg == "bowie" then
bowie(_, "last")
elseif arg == "bowie-all" then
bowie(_, "all")
elseif arg == "bowie-select" then
bowie(_, "select")
elseif arg == "ounce" then
ounce(_)
elseif arg == "nicotine" then
nicotine(_)
elseif arg == "equine-mount-local" then
equine(_, "mount-local")
elseif arg == "equine-umount-local" then
equine(_, "umount-local")
elseif arg == "equine-mount-remote" then
equine(_, "mount-remote")
elseif arg == "equine-umount-remote" then
equine(_, "umount-remote")
elseif arg == "prev" or arg == "next" or arg == "exit" then
time_travel(_, arg)
else
notify("Unknown command: " .. tostring(arg or "<none>"), "error")
end
end,
}
EOF
}
conf_print_httm_main_old | tee "${HTTM_PLUGIN_HOME}/main.lua_old"
#----------------------------------------------------------------
conf_print_httm_license() {
cat <<EOF
MIT License
Copyright (c) 2026 ${AUTHOR_NAME}
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
EOF
}
conf_print_httm_license | tee "${HTTM_PLUGIN_HOME}/LICENSE"
#----------------------------------------------------------------
conf_print_httm_README() {
cat <<'EOF'
# **kimono-koans/httm** plugin for **Yazi**
This plugin enables **time-travel navigation** using filesystem snapshots (e.g., BTRFS/ZFS),
allowing you to browse, restore, diff, and manage file versions efficiently.
## Requirements
- [Yazi][yazi-link] v26.2.2
- [httm][httm-link] v0.49.9
## Installation
```bash
sh
# Add the plugin
ya pkg add cyteen/httm
# Install plugin
ya pkg install
# Update plugin
ya pkg upgrade
```
### Key Features & Usage
- **Snapshot Management**:
- $(\s): Create a snapshot of current/selected items.
- $(\b): Browse and restore files via an interactive TUI.
- $(\y): Select snapshot versions and yank file paths for reuse.
- **Time Navigation**:
- $(\p) / $(\n): Jump to previous/next snapshot (older/newer).
- $(\l): Exit snapshot view and return to live filesystem.
- **Diffs with Bowie**:
- $(\d): Show differences with the last snapshot.
- $(\D): Show diffs across all versions.
- **Git Integration**:
- $(\g): Export file versions as a Git archive using **nicotine**.
- **Time Machine (Equine)**:
- $(\tm) / $(\tu): Mount/unmount local Time Machine snapshots.
- $(\tr) / $(\tR): Mount/unmount remote snapshots.
- **Helpers**:
- $(\o): Run **ounce** for dynamic snapshot checks.
- $(\?): Display help menu with all httm commands.
EOF
}
conf_print_httm_README | tee "${HTTM_PLUGIN_HOME}/README.md"