.gitignore 🔗
@@ -1 +1 @@
-pika
+keld
Amolith created
.gitignore | 2 +-
AGENTS.md | 22 +++++++++++-----------
cmd/completions.go | 16 ++++++++--------
cmd/completions_test.go | 8 ++++----
cmd/root.go | 34 +++++++++++++++++-----------------
cmd/root_test.go | 4 ++--
example/config.toml | 8 ++++----
go.mod | 2 +-
internal/config/config.go | 4 ++--
internal/config/config_test.go | 4 ++--
internal/config/files.go | 14 +++++++-------
internal/config/files_test.go | 8 ++++----
internal/form/form.go | 2 +-
internal/restic/exec.go | 8 ++++----
internal/restic/exec_test.go | 6 +++---
main.go | 2 +-
mise.toml | 2 +-
17 files changed, 73 insertions(+), 73 deletions(-)
@@ -1 +1 @@
-pika
+keld
@@ -1,8 +1,8 @@
-# AGENTS.md - Working with Pika
+# AGENTS.md - Working with Keld
## Project Overview
-**Pika** is a friendly TOML-configured wrapper around
+**Keld** is a friendly TOML-configured wrapper around
[restic](https://restic.net/) (a backup tool). It provides:
- Layered configuration with presets and command-specific overrides
@@ -58,7 +58,7 @@ main.go
### Section Merge Order
Config sections are merged in ascending priority order. For
-`pika home@cloud backup`:
+`keld home@cloud backup`:
```text
[global] → [global.backup] → [@cloud] → [@cloud.backup] →
@@ -68,10 +68,10 @@ CLI overrides
### Config File Discovery
-1. Default dirs: `/usr/share/pika`, `/etc/pika`, `~/.config/pika`
+1. Default dirs: `/usr/share/keld`, `/etc/keld`, `~/.config/keld`
2. In each dir: `config.toml` then sorted `conf.d/*.toml`
-3. `PIKA_CONFIG_PATHS` env var (colon-separated, supports globs)
-4. `PIKA_CONFIG_FILE` replaces all above if set
+3. `KELD_CONFIG_PATHS` env var (colon-separated, supports globs)
+4. `KELD_CONFIG_FILE` replaces all above if set
### Special Config Keys
@@ -142,13 +142,13 @@ Using v2 API (not v1). Key differences:
| Variable | Purpose |
| ------------------- | --------------------------------------- |
-| `PIKA_CONFIG_FILE` | Single config file (highest priority) |
-| `PIKA_CONFIG_PATHS` | Colon-separated additional config paths |
-| `PIKA_DRYRUN` | Set to enable dry-run mode |
-| `PIKA_EXECUTABLE` | Override restic binary path |
+| `KELD_CONFIG_FILE` | Single config file (highest priority) |
+| `KELD_CONFIG_PATHS` | Colon-separated additional config paths |
+| `KELD_DRYRUN` | Set to enable dry-run mode |
+| `KELD_EXECUTABLE` | Override restic binary path |
## Development Workflow
1. Make changes
2. `mise run check` - full validation
-3. Test manually: `./pika --dry-run <preset> <command>`
+3. Test manually: `./keld --dry-run <preset> <command>`
@@ -6,10 +6,10 @@ import (
"github.com/spf13/cobra"
- "git.secluded.site/pika/internal/config"
+ "git.secluded.site/keld/internal/config"
)
-// knownCommands lists the restic subcommands pika knows about. This is the
+// knownCommands lists the restic subcommands keld knows about. This is the
// same set shown in the interactive menu (minus "quit").
var knownCommands = []string{
"backup",
@@ -20,9 +20,9 @@ var knownCommands = []string{
"snapshots",
}
-// pikaFlags lists pika's own flags (the ones extracted in extractOwnFlags).
+// keldFlags lists keld's own flags (the ones extracted in extractOwnFlags).
// Cobra can't advertise these automatically because DisableFlagParsing is on.
-var pikaFlags = []string{
+var keldFlags = []string{
"--config",
"--dry-run",
"--help",
@@ -32,7 +32,7 @@ func init() {
rootCmd.ValidArgsFunction = completeArgs
}
-// completeArgs provides dynamic shell completions for pika.
+// completeArgs provides dynamic shell completions for keld.
//
// It examines how many positional (non-flag) arguments have already been
// provided and offers:
@@ -46,11 +46,11 @@ func init() {
// - if it's already a command, no more positional completions
// - 2+ positionals → no further positional completions
//
-// When the current word starts with "-", pika's own flags are offered instead.
+// When the current word starts with "-", keld's own flags are offered instead.
func completeArgs(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- // When the user is typing a flag, offer pika's own flags.
+ // When the user is typing a flag, offer keld's own flags.
if len(toComplete) > 0 && toComplete[0] == '-' {
- return pikaFlags, cobra.ShellCompDirectiveNoFileComp
+ return keldFlags, cobra.ShellCompDirectiveNoFileComp
}
// Count how many positional args have already been accepted (i.e. are
@@ -10,7 +10,7 @@ import (
"github.com/spf13/cobra"
)
-// setupCompletionConfig writes a small TOML fixture and points PIKA_CONFIG_FILE
+// setupCompletionConfig writes a small TOML fixture and points KELD_CONFIG_FILE
// at it, so config.Presets() returns deterministic results.
func setupCompletionConfig(t *testing.T) {
t.Helper()
@@ -36,7 +36,7 @@ json = true
if err != nil {
t.Fatalf("writing fixture config: %v", err)
}
- t.Setenv("PIKA_CONFIG_FILE", cfg)
+ t.Setenv("KELD_CONFIG_FILE", cfg)
t.Setenv("HOME", dir)
}
@@ -144,10 +144,10 @@ func TestCompleteArgsFlagPrefix(t *testing.T) {
func TestCompleteArgsSkipsFlags(t *testing.T) {
setupCompletionConfig(t)
- // Simulate: pika --config ./pika.toml <tab>
+ // Simulate: keld --config ./keld.toml <tab>
// The flag and its value should not count as positionals, so we
// should still be at 0 positionals → presets + commands.
- completions, directive := completeArgs(nil, []string{"--config", "./pika.toml"}, "")
+ completions, directive := completeArgs(nil, []string{"--config", "./keld.toml"}, "")
if directive != cobra.ShellCompDirectiveNoFileComp {
t.Fatalf("expected NoFileComp, got %v", directive)
}
@@ -10,10 +10,10 @@ import (
"charm.land/fang/v2"
"github.com/spf13/cobra"
- "git.secluded.site/pika/internal/config"
- "git.secluded.site/pika/internal/form"
- "git.secluded.site/pika/internal/menu"
- "git.secluded.site/pika/internal/restic"
+ "git.secluded.site/keld/internal/config"
+ "git.secluded.site/keld/internal/form"
+ "git.secluded.site/keld/internal/menu"
+ "git.secluded.site/keld/internal/restic"
)
var (
@@ -23,7 +23,7 @@ var (
const overrideArgumentsKey = "_arguments"
-// menuItems defines the interactive command picker shown when pika is invoked
+// menuItems defines the interactive command picker shown when keld is invoked
// with no arguments.
var menuItems = []menu.Item{
{Label: "backup", Hotkey: 'b'},
@@ -36,15 +36,15 @@ var menuItems = []menu.Item{
}
var rootCmd = &cobra.Command{
- Use: "pika [preset] <command> [restic flags...]",
+ Use: "keld [preset] <command> [restic flags...]",
Short: "A friendly wrapper around restic",
- Long: "pika resolves layered TOML config presets and executes restic with the merged result.",
- Example: ` pika backup
- pika home backup
- pika home@nas backup --tag daily --verbose
- pika --config ./pika.toml home backup
- pika --dry-run home backup
- pika --help`,
+ Long: "keld resolves layered TOML config presets and executes restic with the merged result.",
+ Example: ` keld backup
+ keld home backup
+ keld home@nas backup --tag daily --verbose
+ keld --config ./keld.toml home backup
+ keld --dry-run home backup
+ keld --help`,
// Accept arbitrary args — we parse preset/command ourselves.
Args: cobra.ArbitraryArgs,
@@ -63,13 +63,13 @@ var rootCmd = &cobra.Command{
}
if flagConfigFile != "" {
- if err := os.Setenv("PIKA_CONFIG_FILE", flagConfigFile); err != nil {
- return fmt.Errorf("setting PIKA_CONFIG_FILE: %w", err)
+ if err := os.Setenv("KELD_CONFIG_FILE", flagConfigFile); err != nil {
+ return fmt.Errorf("setting KELD_CONFIG_FILE: %w", err)
}
}
if flagDryRun {
- if err := os.Setenv("PIKA_DRYRUN", "1"); err != nil {
- return fmt.Errorf("setting PIKA_DRYRUN: %w", err)
+ if err := os.Setenv("KELD_DRYRUN", "1"); err != nil {
+ return fmt.Errorf("setting KELD_DRYRUN: %w", err)
}
}
@@ -248,7 +248,7 @@ json = true
if err != nil {
t.Fatalf("writing fixture: %v", err)
}
- t.Setenv("PIKA_CONFIG_FILE", cfg)
+ t.Setenv("KELD_CONFIG_FILE", cfg)
t.Setenv("HOME", dir)
tests := []struct {
@@ -280,7 +280,7 @@ func TestValidatePresetNoConfig(t *testing.T) {
if err := os.WriteFile(cfg, []byte("[global]\n"), 0o600); err != nil {
t.Fatalf("writing fixture: %v", err)
}
- t.Setenv("PIKA_CONFIG_FILE", cfg)
+ t.Setenv("KELD_CONFIG_FILE", cfg)
t.Setenv("HOME", dir)
err := validatePreset("anything")
@@ -1,7 +1,7 @@
-# pika config merge order (lowest to highest precedence):
+# keld config merge order (lowest to highest precedence):
# [global] -> [global.<command>] -> split preset parts -> full preset -> CLI overrides
#
-# For a split preset like `home@cloud` and command `backup`, pika checks:
+# For a split preset like `home@cloud` and command `backup`, keld checks:
# [global] -> [global.backup] -> [@cloud] -> [@cloud.backup] ->
# [home@] -> [home@.backup] -> [home@cloud] -> [home@cloud.backup]
@@ -16,7 +16,7 @@ exclude-file = "~/.config/restic/excludes.txt"
exclude-if-present = ".nobackup"
[home.backup]
-# Simple preset: `pika home backup`
+# Simple preset: `keld home backup`
_arguments = ["/home/amolith"]
tag = ["home"]
@@ -43,7 +43,7 @@ tag = ["home"]
["home@".backup]
# Prefix + command section.
_arguments = ["/home/amolith", "/etc"]
-exclude-if-present = ".pika-skip"
+exclude-if-present = ".keld-skip"
["home@".forget]
# Prefix + different command section.
@@ -1,4 +1,4 @@
-module git.secluded.site/pika
+module git.secluded.site/keld
go 1.26.1
@@ -363,7 +363,7 @@ func toStringSlice(v any) []string {
}
}
-// IsDryRun reports whether the PIKA_DRYRUN environment variable is set.
+// IsDryRun reports whether the KELD_DRYRUN environment variable is set.
func IsDryRun() bool {
- return os.Getenv("PIKA_DRYRUN") != ""
+ return os.Getenv("KELD_DRYRUN") != ""
}
@@ -21,8 +21,8 @@ func TestResolve(t *testing.T) {
}
t.Setenv("HOME", homeDir)
- t.Setenv("PIKA_CONFIG_FILE", configPath)
- t.Setenv("PIKA_CONFIG_PATHS", "")
+ t.Setenv("KELD_CONFIG_FILE", configPath)
+ t.Setenv("KELD_CONFIG_PATHS", "")
tests := []struct {
name string
@@ -10,17 +10,17 @@ import (
// DefaultConfigDirs lists the base directories searched for config files,
// in ascending priority order.
var DefaultConfigDirs = []string{
- "/usr/share/pika",
- "/etc/pika",
- "~/.config/pika",
+ "/usr/share/keld",
+ "/etc/keld",
+ "~/.config/keld",
}
// DiscoverFiles returns config file paths in ascending priority order.
//
// The search order is:
// 1. For each directory in DefaultConfigDirs: config.toml, then sorted conf.d/*.toml
-// 2. Paths/globs from the PIKA_CONFIG_PATHS env var (colon-separated)
-// 3. If PIKA_CONFIG_FILE is set, it replaces ALL of the above
+// 2. Paths/globs from the KELD_CONFIG_PATHS env var (colon-separated)
+// 3. If KELD_CONFIG_FILE is set, it replaces ALL of the above
//
// All paths support ~ (home dir) and $VAR expansion.
func DiscoverFiles() []string {
@@ -29,7 +29,7 @@ func DiscoverFiles() []string {
// discoverFiles is the testable core; getenv abstracts os.Getenv.
func discoverFiles(getenv func(string) string) []string {
- if single := getenv("PIKA_CONFIG_FILE"); single != "" {
+ if single := getenv("KELD_CONFIG_FILE"); single != "" {
return []string{ExpandPath(single)}
}
@@ -42,7 +42,7 @@ func discoverFiles(getenv func(string) string) []string {
paths = append(paths, sortedGlob(filepath.Join(dir, "conf.d", "*.toml"))...)
}
- if extra := getenv("PIKA_CONFIG_PATHS"); extra != "" {
+ if extra := getenv("KELD_CONFIG_PATHS"); extra != "" {
for _, entry := range strings.Split(extra, ":") {
entry = ExpandPath(strings.TrimSpace(entry))
if strings.ContainsAny(entry, "*?[") {
@@ -15,8 +15,8 @@ func TestDiscoverFiles_ConfigFileOverride(t *testing.T) {
}
t.Setenv("HOME", homeDir)
- t.Setenv("PIKA_CONFIG_FILE", "~/only.toml")
- t.Setenv("PIKA_CONFIG_PATHS", filepath.Join(tmpDir, "extras", "*.toml"))
+ t.Setenv("KELD_CONFIG_FILE", "~/only.toml")
+ t.Setenv("KELD_CONFIG_PATHS", filepath.Join(tmpDir, "extras", "*.toml"))
got := DiscoverFiles()
want := []string{filepath.Join(homeDir, "only.toml")}
@@ -65,8 +65,8 @@ func TestDiscoverFiles_Order(t *testing.T) {
DefaultConfigDirs = originalDirs
})
- t.Setenv("PIKA_CONFIG_FILE", "")
- t.Setenv("PIKA_CONFIG_PATHS", filepath.Join(extraDir, "*.toml")+":"+explicit)
+ t.Setenv("KELD_CONFIG_FILE", "")
+ t.Setenv("KELD_CONFIG_PATHS", filepath.Join(extraDir, "*.toml")+":"+explicit)
got := DiscoverFiles()
want := []string{
@@ -1,5 +1,5 @@
// Package form provides interactive huh-based prompts for collecting user
-// input when pika is invoked via the interactive menu.
+// input when keld is invoked via the interactive menu.
package form
import (
@@ -7,10 +7,10 @@ import (
"strings"
"syscall"
- "git.secluded.site/pika/internal/config"
+ "git.secluded.site/keld/internal/config"
)
-// DefaultExecutable is the restic binary name used when PIKA_EXECUTABLE is
+// DefaultExecutable is the restic binary name used when KELD_EXECUTABLE is
// unset.
const DefaultExecutable = "restic"
@@ -60,9 +60,9 @@ func DryRun(cfg *config.ResolvedConfig) string {
return b.String()
}
-// executable returns the restic binary name, respecting PIKA_EXECUTABLE.
+// executable returns the restic binary name, respecting KELD_EXECUTABLE.
func executable() string {
- if e := os.Getenv("PIKA_EXECUTABLE"); e != "" {
+ if e := os.Getenv("KELD_EXECUTABLE"); e != "" {
return config.ExpandPath(e)
}
return DefaultExecutable
@@ -7,7 +7,7 @@ import (
"strings"
"testing"
- "git.secluded.site/pika/internal/config"
+ "git.secluded.site/keld/internal/config"
)
func TestBuildArgv(t *testing.T) {
@@ -59,7 +59,7 @@ func TestBuildArgv(t *testing.T) {
func TestBuildEnv(t *testing.T) {
t.Parallel()
- const key = "PIKA_TEST_BUILD_ENV"
+ const key = "KELD_TEST_BUILD_ENV"
const val = "set-by-test"
env := buildEnv(map[string]string{key: val})
@@ -111,7 +111,7 @@ func TestDryRunExecutableOverride(t *testing.T) {
}
t.Setenv("HOME", homeDir)
- t.Setenv("PIKA_EXECUTABLE", "~/bin/restic-alt")
+ t.Setenv("KELD_EXECUTABLE", "~/bin/restic-alt")
cfg := &config.ResolvedConfig{Command: "snapshots"}
output := DryRun(cfg)
@@ -1,6 +1,6 @@
package main
-import "git.secluded.site/pika/cmd"
+import "git.secluded.site/keld/cmd"
func main() {
cmd.Execute()
@@ -5,7 +5,7 @@ go = "latest"
golangci-lint = "latest"
[tasks.build]
-run = "go build -o pika ."
+run = "go build -o keld ."
[tasks.install]
run = "go install ."