diff --git a/CHANGELOG.md b/CHANGELOG.md index fa7f826..b136596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,33 @@ # Changelog -## [Latest] +All notable changes to this project will be documented in this file. -## [0.2.1-alpha]- 2026-8-14 +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.3.0-alpha] - 2026-08-15 + +### Added +- `search` command +- `-v` and `--version` flags for root command + +### Changed +- Switched from raw sql to sql builder for easier dynamic query construction and easier maintenance + +### Fixed +- Now `--name` parameter for `add` command is required + +## [0.2.1-alpha] - 2026-08-14 ### Fixed - Prevented creating spells with reserved names - Improved error messages -## [Unreleased] - ## [0.2.0-alpha] - 2026-08-08 ### Added - Configuration options -## [Unreleased] - ## [0.1.0-alpha] - 2026-08-04 ### Added diff --git a/README.md b/README.md index 5dca74c..60e4da8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# spellbook 0.2.1-alpha +# spellbook 0.3.0-alpha `spellbook` is a lightweight CLI command manager written in Go. It allows you to save complex shell commands as parameterized templates ("spells") and execute them instantly with custom arguments. @@ -10,6 +10,7 @@ - [Configuration](#configuration) - [Usage](#usage) - [Upcoming Features](#upcoming-features) +- [Known issues](#known-issues) --- @@ -34,7 +35,7 @@ git clone https://github.com/abelith/spellbook.git cd spellbook # Build static binary -CGO_ENABLED=0 go build -o spellbook . +CGO_ENABLED=0 go build -ldflags="-X main.Version=0.3.0-alpha" -o spellbook . # Move to bin directory (for Linux/macOS) mv spellbook $(go env GOPATH)/bin/ @@ -45,7 +46,7 @@ mv spellbook $(go env GOPATH)/bin/ ## Configuration ### Env -- `SPELLBOOK_DB_PATH`: db location (example: `/spellbook/spellbook.db`) +- `SPELLBOOK_DB_PATH`: database file path (example: `/spellbook/spellbook.db`) --- @@ -58,28 +59,56 @@ mv spellbook $(go env GOPATH)/bin/ Use `spellbook` to list prepared spells +### Searching through your spellbook + +Use `spellbook search -n name -c cmd -d desc -t tags [query]` to find spells matching the query + +| Name | Shorthand | Description | +|----------|-----------|------------------------------------------------------------------------------| +| `--name` | `-n` | Part of a spell name to search for | +| `--cmd` | `-c` | Part of a spell command template to search for | +| `--desc` | `-d` | Part of a spell description to search for | +| `--tags` | `-t` | Comma-separated list of tags to search for | +| `query` | | General pattern to search for through spell names, descriptions and commands | + +> At least one of the parameters above is required + + ### Add new spell -Use `spellbook add -n name -t tags -d desc -- command` to add a new spell -- `-n` or `--name` is a name of a spell -- `-t` or `--tags` is a comma-separated list of spell tags -- `-d` or `--desc` is a short description of spell -- `command` is an actual spell template +Use `spellbook add -n name -t tags -d desc -- [command]` to add a new spell + +| Name | Shorthand | Description | Required | +|-----------|-----------|------------------------------------|----------| +| `--name` | `-n` | Spell name | `Yes` | +| `--tags` | `-t` | Comma-separated list of spell tags | `No` | +| `--desc` | `-d` | Spell description | `No` | +| `command` | | Spell command template | `Yes` | + +> It is advised to escape the `command` with single or double quotes ### Remove a spell Use `spellbook rm [spell_name]` to remove a spell -- `spell_name` is a name of a spell +- `spell_name` is a name of a spell `required` + +| Name | Description | Required | +|--------------|-------------|----------| +| `spell_name` | Spell name | `Yes` | + ### Casting a spell -Use `spellbook [spell_name] key=val` to cast a spell -- `spell_name` is a name of a spell -- `key=val` are key/value arguments +Use `spellbook [spell_name] key=val key=val` to cast a spell + +| Name | Description | Required | +|--------------|------------------------------|----------| +| `spell_name` | Spell name | `Yes` | +| `key=val` | key=val formatted parameters | `No` | ### Examples -Said, you have added a spell like this +For example, if you add a spell like this ```shell spellbook add -n my-spell -t net,http -d my-desc -- curl 127.0.0.1 @@ -106,5 +135,8 @@ spellbook my-spell target=127.0.0.1 --- ## Upcoming Features -- Search to navigate your spell book -- Export/import spellbooks \ No newline at end of file +- Export/import spellbooks +- Namespaces + +## Known issues +- `KEY=VAL my-cmd` inline environment variable syntax is currently not supported (will fail with `executable file not found in $PATH`) \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index 98ceec9..c5d74cc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -16,6 +16,9 @@ import ( "syscall" ) +// Version is set on build via -ldflags +var Version = "dev" + func handleErr(err error) { if err != nil { _, _ = fmt.Fprintln(os.Stderr, err) @@ -41,7 +44,7 @@ func main() { app := application.NewApplication(&caster.SpellCaster{}, repo, repo) - c := cli.NewCLI(app) + c := cli.NewCLI(Version, app) if err := c.Run(ctx); err != nil { os.Exit(1) diff --git a/internal/controllers/cli/add.go b/internal/controllers/cli/add.go index 96ed653..8b9b56d 100644 --- a/internal/controllers/cli/add.go +++ b/internal/controllers/cli/add.go @@ -13,6 +13,7 @@ var reservedCommands = map[string]struct{}{ "rm": {}, "help": {}, "completion": {}, + "search": {}, } func NewAddCommand(app Application) *cobra.Command { @@ -34,6 +35,11 @@ func NewAddCommand(app Application) *cobra.Command { } spell.Spell = strings.Join(args, " ") + + if spell.Name == "" { + return fmt.Errorf("%w: --name is missing", errors.ErrInsufficientArgs) + } + return app.AddSpell(ctx, spell) }, } diff --git a/internal/controllers/cli/cli.go b/internal/controllers/cli/cli.go index 8c4cfe6..bcb2404 100644 --- a/internal/controllers/cli/cli.go +++ b/internal/controllers/cli/cli.go @@ -18,11 +18,12 @@ type CLI struct { root *cobra.Command } -func NewCLI(app Application) *CLI { - root := NewRootCmd(app) +func NewCLI(ver string, app Application) *CLI { + root := NewRootCmd(ver, app) root.AddCommand( NewAddCommand(app), NewRmCommand(app), + NewSearchCmd(app), ) return &CLI{ diff --git a/internal/controllers/cli/root.go b/internal/controllers/cli/root.go index 7e49db0..01bff86 100644 --- a/internal/controllers/cli/root.go +++ b/internal/controllers/cli/root.go @@ -81,15 +81,16 @@ func printTable(cmd *cobra.Command, spells []models.Spell) error { return nil } -func NewRootCmd(app Application) *cobra.Command { +func NewRootCmd(ver string, app Application) *cobra.Command { cmd := &cobra.Command{ Use: "spellbook", Short: "CLI tool to store, manage and execute command templates", Long: `Spellbook is a lightweight binary command manager written in Go. It allows you to save complex shell commands as parametrized templates (spells) and execute them instantly with custom arguments from anywhere in your terminal`, - RunE: rootCmd(app), - Args: cobra.ArbitraryArgs, + RunE: rootCmd(app), + Args: cobra.ArbitraryArgs, + Version: ver, } return cmd diff --git a/internal/controllers/cli/search.go b/internal/controllers/cli/search.go new file mode 100644 index 0000000..0b1f00d --- /dev/null +++ b/internal/controllers/cli/search.go @@ -0,0 +1,47 @@ +package cli + +import ( + "fmt" + "github.com/abelith/spellbook/internal/errors" + "github.com/abelith/spellbook/internal/models" + "github.com/spf13/cobra" +) + +func NewSearchCmd(app Application) *cobra.Command { + query := models.SearchQuery{} + + cmd := &cobra.Command{ + Use: "search [query]", + Short: "Search for spells", + Long: "WIP", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + if query.IsEmpty() { + return errors.ErrEmptyQuery + } + + if len(args) > 0 { + query.Text = args[0] + } + + spells, err := app.GetSpells(ctx, query) + if err != nil { + return err + } + + if len(spells) < 1 { + return fmt.Errorf("%w: spells", errors.ErrNotFound) + } + + return printTable(cmd, spells) + }, + } + + cmd.Flags().StringVarP(&query.Name, "name", "n", "", "Spell name") + cmd.Flags().StringVarP(&query.Command, "cmd", "c", "", "Spell command template") + cmd.Flags().StringVarP(&query.Description, "desc", "d", "", "Spell description") + cmd.Flags().StringSliceVarP(&query.Tags, "tags", "t", nil, "Spell tags") + + return cmd +} diff --git a/internal/errors/errors.go b/internal/errors/errors.go index ad1c26a..c33c26f 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -7,4 +7,6 @@ var ( ErrInsufficientArgs = fmt.Errorf("mandatory args are missing") ErrFailedToCast = fmt.Errorf("failed to cast spell") ErrNoDBProvided = fmt.Errorf("no db provided") + ErrNotFound = fmt.Errorf("not found") + ErrEmptyQuery = fmt.Errorf("empty search query") ) diff --git a/internal/models/methods.go b/internal/models/methods.go new file mode 100644 index 0000000..ad42925 --- /dev/null +++ b/internal/models/methods.go @@ -0,0 +1,12 @@ +package models + +func (sq *SearchQuery) IsEmpty() bool { + isEmpty := sq.Text == "" && + sq.Name == "" && + sq.Command == "" && + sq.Description == "" && + len(sq.Tags) < 1 && + sq.Cursor == 0 && + sq.Limit == 0 + return isEmpty +}