Add spec for non-interactive execution

Amolith created

keld is often invoked from systemd timers, cron jobs, CI pipelines, and
shell scripts with no controlling terminal. Capture the intended
behaviour as a Gherkin feature ahead of implementation.

The spec covers four rules: without a terminal keld never launches the
TUI, non-interactive mode requires explicit inputs (with specific errors
when preset selection, backup paths, or the subcommand itself is
missing), interactive mode is preserved when a terminal is attached, and
KELD_NONINTERACTIVE overrides terminal detection as a fallback for users
who want scripted behaviour on a terminal.

Pure spec, no automation — matches the other files under features/.

Change summary

features/non_interactive.feature | 86 ++++++++++++++++++++++++++++++++++
1 file changed, 86 insertions(+)

Detailed changes

features/non_interactive.feature 🔗

@@ -0,0 +1,86 @@
+Feature: Non-interactive execution
+
+  keld is often invoked from systemd timers, cron jobs, CI pipelines,
+  and shell scripts where no controlling terminal is attached. In those
+  contexts it must run restic directly without attempting to launch the
+  interactive TUI. When a user wants scripted behaviour even on a
+  terminal, an environment variable provides an escape hatch.
+
+  Rule: Without a terminal keld never launches the interactive TUI
+
+    Scenario: Wrapped subcommand runs restic when the preset supplies everything
+      Given keld is invoked with a wrapped subcommand and no positional arguments
+      And standard input is not a terminal
+      And the selected preset supplies every value the command needs
+      When keld runs
+      Then restic is executed with the resolved configuration
+      And no interactive TUI is launched
+
+    Scenario: Wrapped subcommand runs restic when positional arguments cover missing values
+      Given keld is invoked with a wrapped subcommand and positional arguments
+      And standard input is not a terminal
+      When keld runs
+      Then restic is executed with the resolved configuration
+      And no interactive TUI is launched
+
+    Scenario: --show-command prints the resolved command without launching the TUI
+      Given keld is invoked non-interactively with --show-command
+      When keld runs
+      Then the resolved restic command is printed
+      And keld exits successfully without executing restic
+      And no interactive TUI is launched
+
+  Rule: Non-interactive mode requires explicit inputs
+
+    Scenario: Multiple presets are configured and none is specified
+      Given the configuration defines more than one preset
+      And keld is invoked non-interactively without --preset
+      When keld runs
+      Then keld fails with an error naming the --preset flag
+      And the error lists the available presets
+      And no interactive TUI is launched
+
+    Scenario: Backup invoked with no paths available
+      Given keld is invoked non-interactively with the backup subcommand
+      And no paths are provided on the command line
+      And the resolved preset defines no backup paths
+      When keld runs
+      Then keld fails with an error naming the missing paths
+      And no interactive TUI is launched
+
+    Scenario: Bare keld with no subcommand
+      Given keld is invoked non-interactively with no subcommand
+      When keld runs
+      Then keld fails with an error naming the missing subcommand
+      And no interactive TUI is launched
+
+  Rule: Interactive mode is preserved when a terminal is attached
+
+    Scenario: Wrapped subcommand with no arguments opens the session
+      Given keld is invoked with a wrapped subcommand and no positional arguments
+      And standard input is a terminal
+      When keld runs
+      Then the interactive session is launched with the command pre-selected
+
+    Scenario: Bare keld opens the command menu
+      Given keld is invoked with no subcommand
+      And standard input is a terminal
+      When keld runs
+      Then the interactive session is launched starting at the command menu
+
+  Rule: KELD_NONINTERACTIVE overrides terminal detection
+
+    Scenario: Environment variable forces non-interactive behaviour on a terminal
+      Given keld is invoked with a wrapped subcommand and no positional arguments
+      And standard input is a terminal
+      And KELD_NONINTERACTIVE is set to a non-empty value
+      When keld runs
+      Then restic is executed with the resolved configuration
+      And no interactive TUI is launched
+
+    Scenario: Empty KELD_NONINTERACTIVE is treated as unset
+      Given keld is invoked with a wrapped subcommand and no positional arguments
+      And standard input is a terminal
+      And KELD_NONINTERACTIVE is set to an empty value
+      When keld runs
+      Then the interactive session is launched with the command pre-selected