From 39361150a52433344be2a403982b30e21db9a02e Mon Sep 17 00:00:00 2001 From: Dominik Agres Date: Mon, 22 Jun 2026 23:08:44 +0200 Subject: [PATCH] fix to go to clipboard, release workflow --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- Makefile | 12 ++--- README.md | 84 ++++++++++++++++++++++++++++++++--- go.mod | 2 +- src/main.go | 82 +++++++++++++++++----------------- src/tui.go | 8 +++- 7 files changed, 135 insertions(+), 57 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c8101d..0234892 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ name: CI on: push: - branches: [main] + branches: ['**'] # CI on every branch pull_request: branches: [main] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 489641c..c295e53 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -92,9 +92,9 @@ jobs: - name: Patch cheatsh formula run: | + # version is inferred by Homebrew from the URL tag, so only url + sha256 are patched. FORMULA="tap/Formula/cheatsh.rb" sed -i "s|url .*|url \"${{ steps.sha.outputs.url }}\"|" "$FORMULA" - sed -i "s|version .*|version \"${{ inputs.version }}\"|" "$FORMULA" sed -i "s|sha256 .*|sha256 \"${{ steps.sha.outputs.sha }}\"|" "$FORMULA" - name: Commit and push tap update diff --git a/Makefile b/Makefile index 496c9e7..3e2c820 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ EXECUTABLE = cheatsh PACKAGES = ./src/. BINDIR = bin -SYSCONFDIR = $(HOME)/.config/cheatsh PREFIX ?= $(HOME)/.local +DATADIR = $(PREFIX)/share/cheatsh all: build @@ -12,16 +12,16 @@ build: install: build - @echo "Installing binary in $(PREFIX)" + @echo "Installing binary in $(PREFIX)/bin" install -Dm755 bin/$(EXECUTABLE) $(PREFIX)/bin/$(EXECUTABLE) - @echo "Copying config files to $(SYSCONFDIR)" - install -Dm644 data/commands.json $(SYSCONFDIR)/commands.json - install -Dm644 data/commands_template.json $(SYSCONFDIR)/commands_template.json + @echo "Installing command lists in $(DATADIR)" + install -Dm644 data/commands.json $(DATADIR)/commands.json + install -Dm644 data/commands_template.json $(DATADIR)/commands_template.json uninstall: rm -f $(PREFIX)/bin/$(EXECUTABLE) - rm -rf $(SYSCONFDIR) + rm -rf $(DATADIR) clean: rm -rf bin/ diff --git a/README.md b/README.md index c35509d..71fa79f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,89 @@ # Cheat Sheet -A small TUI Tool which displays a pre-configured json file content in a menu and lets the user search for commands and select them. Once selected these are printed in the terminal for easy access. +A small TUI tool that displays a pre-configured set of commands in a searchable +menu. Pick one and it's copied to your system clipboard — paste it where you +need it and edit before running. -I wrote this to simplify my search for commands I use often enough to need them multiple times but not often enoigh to remember alway exactly how they are called. +I wrote this to look up commands I use often enough to need repeatedly, but not +often enough to remember exactly. Written in Go with [bubbletea](https://github.com/charmbracelet/bubbletea). -Written in go + used bubbletea for the TUI part of the application. +## Install + +### Homebrew (recommended) + +```bash +brew install agresdominik/repo/cheatsh +``` + +### From source + +```bash +git clone https://github.com/agresdominik/cheat_sheet +cd cheat_sheet +make install # builds and installs to ~/.local/bin +``` + +Make sure `~/.local/bin` is in your `PATH` (or change `PREFIX` in the `Makefile`). + +## Configuration + +cheatsh ships with a maintained command list and uses it by default. It lives +next to the binary and is **replaced on `brew upgrade`**, so updating the list +in this repo and releasing is all it takes to get the new commands on your +machines: + +``` +/share/cheatsh/commands.json # Homebrew, e.g. /opt/homebrew/... +~/.local/share/cheatsh/commands.json # make install +data/commands.json # running from a source checkout +``` + +To maintain the default list, edit `data/commands.json` and release. The format +is a map of categories to command lists: + +```json +{ + "git": [ + { "command": "git reset --soft HEAD~1", "desc": "Undo last commit, keep changes" } + ] +} +``` + +### Custom list + +If you'd rather keep your own list (one that survives upgrades untouched), copy +the template and pass it explicitly — `--config` always wins over the bundled +default: + +```bash +cp "$(brew --prefix)/share/cheatsh/commands_template.json" ~/my-commands.json +cheatsh --config ~/my-commands.json +``` + +## Clipboard dependency + +Copying to the clipboard needs a backend: + +- **macOS** — works out of the box (`pbcopy`). +- **Linux/Wayland** — `wl-clipboard`. +- **Linux/X11** — `xsel` or `xclip`. + +If no backend is found, cheatsh prints the command to stdout instead. ## Usage -Use the commands in the given makefile to run, build, test in a docker container or install the application on your device. Beforehand define your own commands.json based on the `commands_template.json` in the `data/` folder. +```bash +cheatsh # browse and select; selection is copied to clipboard +cheatsh --config FILE # use a specific config file +cheatsh --help +``` -Once installed just run `cheatsh` in the terminal. If it's not found, make sure `~/.local/bin/` is in your `PATH` or change the path under the `PREFIX` variable in the `Makefile`. +In the TUI: `/` to filter, `enter` to descend / select, `b` to go back, `q` to quit. +## Development +```bash +make build # build to bin/cheatsh +make local # build and run against data/commands.json +go test ./src/... +``` diff --git a/go.mod b/go.mod index 9f1c590..4fcb32c 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,13 @@ module github.com/dominikagres/cheat_sheet go 1.24.1 require ( + github.com/atotto/clipboard v0.1.4 github.com/charmbracelet/bubbles v0.21.0 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/lipgloss v1.1.0 ) require ( - github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/x/ansi v0.10.1 // indirect diff --git a/src/main.go b/src/main.go index 9109ff1..d342df9 100644 --- a/src/main.go +++ b/src/main.go @@ -40,56 +40,54 @@ func main() { flag.Parse() - var configFile string - - home, err := os.UserHomeDir() - if err == nil { - userConf := filepath.Join(home, ".config", "cheatsh") - if info, err := os.Stat(userConf); err == nil && info.IsDir() { - configFile = filepath.Join(userConf, "commands.json") - } else { - configFile = "/etc/cheatsh/commands.json" - } - } - - var commands CmdList - - if *configFlag != "" { - commands, err = loadCommands(*configFlag) - if err != nil { - log.Fatalf("Cannot load commands file: %v", err) - } - StartTui(commands) - return - } else if len(os.Args) == 1 { - commands, err = loadCommands(configFile) - if err != nil { - log.Fatalf("Cannot load commands file: %v", err) - } - StartTui(commands) - return - } - switch { - - case *helpFlag: - printHelp() - - case *newFlag: - HandleInput() - - default: - printHelp() - os.Exit(1) - + case *helpFlag: + printHelp() + return + case *newFlag: + HandleInput() + return } + configFile := *configFlag + if configFile == "" { + configFile = defaultConfigPath() + } + + commands, err := loadCommands(configFile) + if err != nil { + if os.IsNotExist(err) { + log.Fatalf("No config found at %s\nPass a config explicitly with --config .", configFile) + } + log.Fatalf("Cannot load commands file: %v", err) + } + + StartTui(commands) +} + +// defaultConfigPath returns the bundled command list installed next to the +// binary (/share/cheatsh/commands.json, replaced on upgrade). When +// running from a source checkout that file doesn't exist, so it falls back to +// data/commands.json relative to the working directory. +func defaultConfigPath() string { + if exe, err := os.Executable(); err == nil { + if real, err := filepath.EvalSymlinks(exe); err == nil { + exe = real + } + p := filepath.Join(filepath.Dir(exe), "..", "share", "cheatsh", "commands.json") + if _, err := os.Stat(p); err == nil { + return p + } + } + return filepath.Join("data", "commands.json") } func printHelp() { fmt.Println(`Usage: cheatsh [options] + Select a command to copy it to the system clipboard. + Without --config the bundled command list is used; pass --config to use your own. Options: - --config Specify a config file + --config Use a custom config file --help Show this help message --new Add a new command to the config file`) } diff --git a/src/tui.go b/src/tui.go index e29b636..98ab57e 100644 --- a/src/tui.go +++ b/src/tui.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/atotto/clipboard" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" @@ -128,7 +129,12 @@ func StartTui(commands CmdList) { } if m, ok := finalModel.(model); ok && m.selectedCmd != "" { - fmt.Println(m.selectedCmd) + if err := clipboard.WriteAll(m.selectedCmd); err != nil { + fmt.Fprintln(os.Stderr, "Could not copy to clipboard:", err) + fmt.Println(m.selectedCmd) + return + } + fmt.Println("Copied to clipboard:", m.selectedCmd) } }