Skip to content

Sync dev to main#172

Open
tis24dev wants to merge 11 commits intomainfrom
dev
Open

Sync dev to main#172
tis24dev wants to merge 11 commits intomainfrom
dev

Conversation

@tis24dev
Copy link
Owner

@tis24dev tis24dev commented Mar 13, 2026

  • Align secondary path validation across config load, CLI install, and TUI
  • Align --new-install confirmation flow across CLI and TUI
  • Align existing backup.env handling across CLI and TUI
  • Fix AGE setup validation and install TUI messaging alignment
  • Align Telegram setup flow across CLI and TUI
  • Align decrypt secret prompt semantics across CLI and TUI
  • Add end-to-end coverage for the production decrypt TUI flow
  • Align secondary disable semantics across CLI and TUI
  • Align install cron scheduling across CLI and TUI
  • Add cron install regression coverage for CLI and TUI
  • test(orchestrator): stabilize decrypt TUI end-to-end tests

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced installation workflow with options to edit, skip, or cancel when an existing configuration is detected.
    • Added cron schedule configuration during setup (HH:MM format with 02:00 default).
    • Unified AGE encryption recipient setup with improved UI workflows.
    • Telegram setup with eligibility checks and centralized pairing verification.
  • Improvements

    • Secondary storage paths now validate as absolute local filesystem paths only.
    • Better error messaging for invalid storage and configuration paths.
    • Streamlined post-install steps with conditional execution based on configuration choices.
  • Documentation

    • Updated installation guides reflecting new configuration handling options.
    • Clarified encryption key setup across CLI and TUI modes.
    • Enhanced secondary storage and cloud configuration documentation.

tis24dev added 11 commits March 13, 2026 11:12
Centralize validation for SECONDARY_PATH and SECONDARY_LOG_PATH so all entrypoints enforce the same absolute-local-path rules. Reject remote/UNC-style secondary paths during config loading, keep SECONDARY_LOG_PATH optional, and update the CLI installer to retry on invalid secondary path input instead of aborting. Add coverage for config parsing, migration, installer, runtime validation, and TUI flows.
Refactor new-install to use a shared reset plan and a single source of truth for preserved entries (build/env/identity). Route --new-install --cli through CLI confirmation only, keep TUI confirmation as a pure adapter, and propagate TUI runner errors instead of swallowing them. Update related help/log messaging and add tests for new-install planning, CLI confirm behavior, TUI confirm rendering/error handling, and reset/preserve consistency.
Introduce a shared decision flow for pre-existing backup.env with four explicit actions: Overwrite, Edit existing, Keep existing & continue, and Cancel. Update CLI prompts to support all modes (including Edit existing and explicit Cancel), update TUI action mapping to the same semantics, and treat “keep existing” as continue (not abort). Ensure TUI post-config steps are skipped consistently when configuration wizard is skipped (AGE setup, post-install audit, Telegram pairing), while finalization steps still run. Propagate CheckExistingConfig runner errors instead of swallowing them. Add/adjust unit tests for decision resolution, CLI prompts, TUI actions, runner error propagation, and prepareBaseTemplate behavior. Update INSTALL and CLI_REFERENCE docs to match the new aligned behavior.
Tighten AGE setup consistency after the shared CLI/TUI refactor. Reuse a shared private-key validator so the TUI rejects malformed AGE identities before they reach the orchestrator, eliminating silent retry loops. Extend the AGE setup workflow to return explicit outcome details (recipient path, wrote file vs reused existing recipients) and update install TUI messaging to report “saved” only on real writes, while showing reuse clearly when existing recipient configuration is kept. Add regression coverage for private-key validation, reuse-vs-write setup results, and the updated TUI wizard behavior.
Introduce a shared Telegram setup bootstrap so CLI and TUI use the same eligibility rules before showing pairing steps. Stop the TUI from falling back to raw backup.env parsing, skip Telegram setup consistently when config loading fails, personal mode is selected, or no Server ID is available, and centralize skip-reason logging in the command layer. Update the TUI install flow to log shared Telegram bootstrap outcomes, add dedicated tests for bootstrap/CLI/TUI behavior, align user-facing docs, and remove now-unreachable TUI branches left over from the old local decision logic.
Restore consistent decrypt prompt behavior between CLI and TUI by treating "0" as an explicit abort in both flows. Update the TUI decrypt secret prompt to advertise the exit semantics clearly, return ErrDecryptAborted on zero input, and keep Cancel as an equivalent exit path. Adjust TUI simulation coverage so the shared decrypt workflow no longer carries a UI-specific semantic drift on secret entry.
Add deterministic end-to-end smoke tests for RunDecryptWorkflowTUI so the real decrypt TUI production path is covered from entrypoint through source selection, candidate selection, secret prompt, destination prompt, and final bundle creation. Introduce test-only helpers for a real AGE-encrypted raw-backup fixture, serialized TUI simulation across multi-screen workflows, bundle-content inspection, and guarded workflow execution. Verify both the success path (including final *.decrypted.bundle.tar contents, metadata, and checksum) and clean abort at the decrypt secret prompt, without changing production behavior.
Introduce a shared env-template helper for secondary storage state and use it from both installer flows so disabling secondary storage always writes the same canonical config: SECONDARY_ENABLED=false, SECONDARY_PATH=, and SECONDARY_LOG_PATH=. This removes the previous TUI-only drift where editing an existing backup.env could leave stale secondary paths after the user disabled the feature. Add focused unit coverage for the shared helper plus CLI and TUI regression tests covering disabled state and clearing of pre-existing secondary values, and clarify the installer docs to note that disabling secondary storage clears the saved secondary paths.
Introduce shared cron parsing/normalization for install workflows and align CLI with the existing TUI cron capability. Add a neutral internal cron helper package, collect cron time during the CLI install wizard, propagate an explicit CronSchedule through the CLI install result, and make install-time cron finalization honor wizard-selected/default cron values instead of falling back to env overrides after a normal wizard run. Keep skip-config-wizard and upgrade flows on their existing env/default behavior, update the TUI wizard to reuse the same cron validation logic, add regression coverage for shared cron parsing, CLI prompt/result propagation, and install schedule precedence, and update install/CLI docs to reflect cron selection in both modes.
Close the remaining cron-install test gaps after aligning CLI and TUI scheduling behavior. Add a TUI wizard regression test that proves blank cron input resolves to the installer default (02:00) even when CRON_SCHEDULE is set in the environment, and add a CLI wizard regression test that aborting exactly at the cron prompt propagates the interactive abort and leaves backup.env unwritten. Introduce minimal test seams for the install wizard runner and cron prompt boundary to exercise the real command/wizard paths without changing production semantics.
Reduces flakiness in the decrypt TUI end-to-end tests when run
with coverage enabled or under package-level load.

- increases simulated input delays
- extends end-to-end test timeouts and contexts
- avoids false negatives without changing production code
@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

📝 Walkthrough

Walkthrough

This comprehensive refactoring introduces new encryption setup orchestration, installs an existing-config decision workflow, adds new-install reset capabilities, implements secondary storage validation, creates cron scheduling utilities, and restructures Telegram setup and decrypt TUI flows. Major changes span CLI, TUI, orchestrator, and configuration subsystems with extensive test coverage.

Changes

Cohort / File(s) Summary
Encryption Setup Workflow
cmd/proxsave/encryption_setup.go, cmd/proxsave/encryption_setup_test.go
Introduces new encryption setup functions with result struct mapping, conditional UI-based orchestrator invocation, and error handling for user aborts. Tests cover reuse, overwrite, and configuration scenarios.
Installation Flow Refactoring
cmd/proxsave/install.go, cmd/proxsave/install_test.go, cmd/proxsave/install_tui.go
Replaces boolean returns with consolidated installConfigResult struct; integrates cron scheduling, secondary storage configuration via new validators, conditional wizard skipping, and refactored Telegram setup gating. Tests extensively cover secondary storage validation, cron configuration, and wizard integration.
Existing Configuration Handling
cmd/proxsave/install_existing_config.go, cmd/proxsave/install_existing_config_test.go
New workflow to prompt users for existing config actions (overwrite, edit, keep/continue, cancel); validates file types and reads content; maps modes to decision flags affecting wizard and install paths.
New Install Workflow
cmd/proxsave/new_install.go, cmd/proxsave/new_install_test.go
New reset and reinstall plan builder with preserved-entries logic, build signature handling, and CLI confirmation flow. Tests validate plan construction, preservation sets, and confirmation interactions.
Cron Utilities
internal/cron/cron.go, internal/cron/cron_test.go
New package providing time normalization, schedule generation, and default time constant ("02:00") with validation for HH:MM format.
Secondary Storage Validation
internal/config/validation_secondary.go, internal/config/validation_secondary_test.go, internal/config/env_mutation.go, internal/config/env_mutation_test.go
New validators for required/optional secondary paths enforcing absolute local filesystem paths; new ApplySecondaryStorageSettings helper to mutate environment templates; comprehensive path validation with UNC detection and error messages.
Configuration Core Updates
internal/config/config.go, internal/config/config_test.go, internal/config/migration.go, internal/config/migration_test.go, internal/config/templates/backup.env
Adds validateSecondarySettings invoked during config parsing; replaces inline secondary path checks with delegated validators; updates documentation for path requirements; adds tests for invalid paths during load/migration.
Orchestrator Age Setup
internal/orchestrator/age_setup_ui.go, internal/orchestrator/age_setup_ui_cli.go, internal/orchestrator/age_setup_workflow.go, internal/orchestrator/age_setup_workflow_test.go, internal/orchestrator/encryption.go
New UI abstraction for age setup with CLI implementation; new workflow orchestrating recipient collection, validation, file writing, and reuse detection; adds public validators for private keys; replaces inline setup logic with delegated workflow.
Orchestrator Telegram Bootstrap
internal/orchestrator/telegram_setup_bootstrap.go, internal/orchestrator/telegram_setup_bootstrap_test.go
New bootstrap logic determining Telegram setup eligibility with multiple skip conditions (config error, personal mode, identity unavailable); pluggable loaders for testing.
Decrypt TUI Refactoring
internal/orchestrator/decrypt_tui.go, internal/orchestrator/decrypt_tui_test.go, internal/orchestrator/decrypt_tui_e2e_helpers_test.go, internal/orchestrator/decrypt_tui_e2e_test.go, internal/orchestrator/decrypt_tui_simulation_test.go
Removes large portions of interactive decrypt prompts and path handling; introduces E2E test helpers for deterministic fixture generation and simulated key sequences; simplifies PromptDecryptSecret interface.
Decrypt Workflow UI
internal/orchestrator/decrypt_workflow_ui.go, internal/orchestrator/decrypt_workflow_ui_test.go, internal/orchestrator/workflow_ui_tui_decrypt.go, internal/orchestrator/workflow_ui_tui_decrypt_prompts.go, internal/orchestrator/workflow_ui_tui_decrypt_test.go, internal/orchestrator/workflow_ui_tui_shared.go
New input validation in workflow UI; new TUI prompts for path decisions, new path input, and secret entry; refactored path resolution via PathDecision enum; helper enableFormNavigation for keyboard handling.
Scheduling & Helper Updates
cmd/proxsave/schedule_helpers.go, cmd/proxsave/schedule_helpers_test.go, cmd/proxsave/runtime_helpers.go, cmd/proxsave/prompts.go, cmd/proxsave/newkey.go
Replaces wizard-dependent cron resolution with environment-based fallback; introduces buildInstallCronSchedule for conditional wizard use; adds promptOptional helper; delegates newkey setup to runNewKeySetup with UI abstraction; replaces inline validation with delegated validators.
Telegram CLI Setup
cmd/proxsave/telegram_setup_cli.go, cmd/proxsave/telegram_setup_cli_test.go, cmd/proxsave/upgrade.go
New CLI-based Telegram setup flow with bootstrap logging, registration polling, and non-blocking error handling; test suite mocking dependencies and validating control flow across eligibility, verification, and skip conditions.
TUI Wizard Installation
internal/tui/wizard/install.go, internal/tui/wizard/install_test.go
Adds ExistingConfigKeepContinue and ExistingConfigCancel actions; switches to cronutil.DefaultTime; validates secondary storage via dedicated helper; uses config.ApplySecondaryStorageSettings for template updates; reworked error handling for existing-config flow.
TUI Wizard Age Setup
internal/tui/wizard/age.go, internal/tui/wizard/age_test.go, internal/tui/wizard/age_ui_adapter.go, internal/tui/wizard/age_ui_adapter_test.go
Delegates private key validation to orchestrator.ValidateAgePrivateKeyString; propagates ageWizardRunner errors; new adapter implementing AgeSetupUI interface mapping wizard results to orchestrator structures.
TUI Wizard New Install
internal/tui/wizard/new_install.go, internal/tui/wizard/new_install_test.go
Adds formatPreservedEntries helper for display; updates ConfirmNewInstall signature to accept preservedEntries; propagates runner errors; tests verify message content and error handling.
TUI Wizard Telegram
internal/tui/wizard/telegram_setup_tui.go, internal/tui/wizard/telegram_setup_tui_test.go
Replaces local config/identity loading with BuildTelegramSetupBootstrap; embeds TelegramSetupBootstrap in result; simplifies UI text and logic to centralized mode only; removes per-mode branches and legacy identity handling.
TUI Simulation & Shared
internal/orchestrator/tui_simulation_test.go
Updates promptOverwriteAction calls to promptExistingPathDecisionTUI with new PathDecision return type; adjusts result handling and error messages.
CLI & Documentation
cmd/proxsave/main.go, docs/CLI_REFERENCE.md, docs/CONFIGURATION.md, docs/INSTALLATION.md, docs/ENCRYPTION.md, docs/CLOUD_STORAGE.md
Help text change referencing "build/env/identity" instead of "env/identity"; documentation expanded to cover new options (Keep existing & continue, Cancel), secondary storage validation requirements, cron time selection with defaults, and Telegram eligibility conditions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as Install CLI
    participant Config as Config Loader
    participant Orchestrator
    participant UI as Age Setup UI
    participant FileSystem

    User->>CLI: Start installation
    CLI->>Config: Load configuration
    Config-->>CLI: Config/Error
    
    alt Config exists
        CLI->>User: Prompt for action
        User->>CLI: Choose "Keep & Continue"
        CLI->>FileSystem: Use existing config
    else Config new
        CLI->>Orchestrator: Initialize encryption
        Orchestrator->>UI: Request recipient input
        UI->>User: Prompt for recipient
        User->>UI: Provide recipient
        UI-->>Orchestrator: AgeRecipientDraft
        Orchestrator->>FileSystem: Write recipient file
        Orchestrator-->>CLI: Setup result
    end
    
    CLI->>User: Complete installation
Loading
sequenceDiagram
    participant User
    participant TUI as Install TUI
    participant Bootstrap as Telegram Bootstrap
    participant ConfigLoader
    participant IdentityDetect
    participant Telegram as Telegram API

    User->>TUI: Start install wizard
    TUI->>Bootstrap: Build bootstrap state
    Bootstrap->>ConfigLoader: Load config
    ConfigLoader-->>Bootstrap: Config/Error
    
    alt Config error
        Bootstrap-->>TUI: SkipConfigError
    else Config loaded
        Bootstrap->>IdentityDetect: Detect identity
        IdentityDetect-->>Bootstrap: Identity/Error
        
        alt Identity found and centralized
            Bootstrap-->>TUI: EligibleCentralized
            TUI->>User: Show Telegram pairing
            User->>TUI: Confirm verification
            TUI->>Telegram: Check registration
            Telegram-->>TUI: Status
        else Skip condition met
            Bootstrap-->>TUI: SkipPersonalMode/SkipIdentity
        end
    end
    
    TUI->>User: Continue installation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐰 A rabbit hops through encryption's dance,
New workflows bloom in config's trance,
With setup wizards, validators true,
And cron schedules fresh and new,
The installer grows, yet simple stays!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'Sync dev to main' is vague and does not clearly describe the actual changes made in the pull request. Consider revising the title to reflect the primary objective, such as 'Centralize CLI and TUI behaviors for installation and encryption workflows' or 'Align CLI/TUI install, encryption, and Telegram setup flows'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can use OpenGrep to find security vulnerabilities and bugs across 17+ programming languages.

OpenGrep is compatible with Semgrep configurations. Add an opengrep.yml or semgrep.yml configuration file to your project to enable OpenGrep analysis.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cmd/proxsave/main.go (1)

1567-1572: ⚠️ Potential issue | 🟡 Minor

Fix staticcheck S1000: use simple channel receive instead of select with single case.

The pipeline flagged this issue. A select with only one case is equivalent to a direct channel operation.

🔧 Proposed fix
 		fmt.Printf("\r  Remaining: %ds   ", int(remaining.Seconds()))
 
-		select {
-		case <-ticker.C:
-			continue
-		}
+		<-ticker.C
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/proxsave/main.go` around lines 1567 - 1572, Replace the unnecessary
single-case select by performing a direct receive on the ticker channel: remove
the select block and use a plain channel receive from ticker.C (the same ticker
used in the loop) to wait for ticks; ensure the surrounding loop logic that
currently does "case <-ticker.C: continue" still continues the loop after the
receive.
cmd/proxsave/newkey.go (1)

116-124: ⚠️ Potential issue | 🟠 Major

Load the persisted config before forcing a new recipient.

Lines 119-124 synthesize a fresh config.Config and hard-code AgeRecipientFile to the default path. That drops any recipient-file override already saved in configPath, so newkey can rotate one file while the installed config still points at another. cmd/proxsave/encryption_setup.go already avoids this by loading the config first.

Patch suggestion
-	cfg := &config.Config{
-		BaseDir:          baseDir,
-		ConfigPath:       configPath,
-		EncryptArchive:   true,
-		AgeRecipientFile: recipientPath,
-	}
+	cfg, loadErr := config.LoadConfig(configPath)
+	if loadErr != nil {
+		if !errors.Is(loadErr, os.ErrNotExist) {
+			return fmt.Errorf("load config: %w", loadErr)
+		}
+		cfg = &config.Config{}
+	}
+	cfg.BaseDir = baseDir
+	cfg.ConfigPath = configPath
+	cfg.EncryptArchive = true
+	if strings.TrimSpace(cfg.AgeRecipientFile) == "" {
+		cfg.AgeRecipientFile = recipientPath
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/proxsave/newkey.go` around lines 116 - 124, In runNewKeySetup, don't
overwrite any persisted AgeRecipientFile; first load the existing config from
configPath (as encryption_setup.go does) and if it contains a non-empty
AgeRecipientFile, use that value instead of hard-coding recipientPath into the
new config.Config; otherwise fall back to the default filepath.Join(baseDir,
"identity", "age", "recipient.txt"). Ensure you still set BaseDir and ConfigPath
on the resulting config.Config and proceed with key rotation using that merged
configuration.
🧹 Nitpick comments (11)
internal/orchestrator/tui_simulation_test.go (1)

61-74: Consider renaming the test to match the function under test.

The test function TestPromptOverwriteAction_SelectsOverwrite still references the old function name promptOverwriteAction, but now tests promptExistingPathDecisionTUI. Consider renaming to TestPromptExistingPathDecisionTUI_SelectsOverwrite for consistency and discoverability.

The test logic itself correctly validates the new return signature (PathDecision, string, error) and appropriately asserts that newPath is empty when the decision is to overwrite.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/orchestrator/tui_simulation_test.go` around lines 61 - 74, Rename
the test function TestPromptOverwriteAction_SelectsOverwrite to
TestPromptExistingPathDecisionTUI_SelectsOverwrite so the name matches the
function under test promptExistingPathDecisionTUI; update the function
declaration (the test name) and any references to the old test identifier in the
file to ensure consistency and discoverability while keeping the test body
(calls to promptExistingPathDecisionTUI and assertions on PathDecision, newPath,
err) unchanged.
internal/orchestrator/decrypt_tui_e2e_helpers_test.go (1)

57-93: Consider finalizing the simulation screen in cleanup.

The simulation screen is initialized at line 62 but not finalized in the cleanup. While this may work for tests, explicitly calling screen.Fini() in the cleanup would be more robust and prevent potential resource leaks.

♻️ Suggested improvement
 	t.Cleanup(func() {
+		screen.Fini()
 		newTUIApp = orig
 	})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/orchestrator/decrypt_tui_e2e_helpers_test.go` around lines 57 - 93,
withTimedSimAppSequence currently initializes a tcell SimulationScreen (screen)
but never finalizes it; update the t.Cleanup closure inside
withTimedSimAppSequence to call screen.Fini() (and restore newTUIApp as
existing) so the simulation screen is properly closed after the test, preventing
resource leaks; ensure the cleanup runs even if other cleanup logic errors by
invoking screen.Fini() before resetting newTUIApp.
internal/orchestrator/telegram_setup_bootstrap.go (1)

46-104: Consider simplifying the return type since error is always nil.

The function signature is BuildTelegramSetupBootstrap(configPath, baseDir string) (TelegramSetupBootstrap, error), but the function always returns nil for the error. The error information is captured in the struct fields (ConfigError, IdentityDetectError). This is a valid pattern (embedding errors in result structs), but the nil error return could be misleading to callers expecting error conditions to be signaled via the error return.

Either:

  1. Remove the error return: func BuildTelegramSetupBootstrap(...) TelegramSetupBootstrap
  2. Or document that errors are always embedded in the struct and the error return is reserved for future use
♻️ Option 1: Remove unused error return
-func BuildTelegramSetupBootstrap(configPath, baseDir string) (TelegramSetupBootstrap, error) {
+func BuildTelegramSetupBootstrap(configPath, baseDir string) TelegramSetupBootstrap {
 	state := TelegramSetupBootstrap{}
 
 	cfg, err := telegramSetupBootstrapLoadConfig(configPath)
 	if err != nil {
 		state.Eligibility = TelegramSetupSkipConfigError
 		state.ConfigError = err.Error()
-		return state, nil
+		return state
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/orchestrator/telegram_setup_bootstrap.go` around lines 46 - 104, The
BuildTelegramSetupBootstrap function always returns a nil error and embeds
errors in the TelegramSetupBootstrap fields (ConfigError, IdentityDetectError);
change the function signature to remove the unused error return (make it func
BuildTelegramSetupBootstrap(configPath, baseDir string) TelegramSetupBootstrap),
update all return sites inside the function to return just the state value, and
update any callers to no longer expect a second error return (they should
inspect state.ConfigError and state.IdentityDetectError instead).
internal/cron/cron_test.go (1)

43-61: Consider adding edge case tests for boundary times.

The tests cover typical cases well. Consider adding tests for boundary values like "00:00" (midnight) and "23:59" (end of day) to ensure the schedule conversion handles edge times correctly.

♻️ Suggested additional test cases
 	tests := []struct {
 		name string
 		in   string
 		want string
 	}{
 		{name: "valid", in: "02:05", want: "05 02 * * *"},
 		{name: "normalized short", in: "2:5", want: "05 02 * * *"},
+		{name: "midnight", in: "00:00", want: "00 00 * * *"},
+		{name: "end of day", in: "23:59", want: "59 23 * * *"},
 		{name: "invalid", in: "bad", want: ""},
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cron/cron_test.go` around lines 43 - 61, Add tests for boundary
times to TestTimeToSchedule to ensure TimeToSchedule handles edge values;
specifically add cases with in "00:00" expecting "00 00 * * *" and "23:59"
expecting "59 23 * * *" (and keep existing cases). Locate the test table in
TestTimeToSchedule and append these two entries (use the same struct fields
name/in/want) and run the test suite to verify behavior; if the assertions fail,
update TimeToSchedule accordingly to normalize leading zeros and validate 00–23
hours and 00–59 minutes.
internal/config/validation_secondary_test.go (1)

24-37: Consider adding t.Parallel() to subtests for faster execution.

The parent test calls t.Parallel(), but subtests don't. For table-driven tests with independent cases, parallelizing subtests improves test execution time.

♻️ Proposed fix to parallelize subtests
 	for _, tt := range tests {
+		tt := tt // capture range variable
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			err := ValidateRequiredSecondaryPath(tt.path)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/config/validation_secondary_test.go` around lines 24 - 37, Add
parallel execution to the subtests by capturing the loop variable and calling
t.Parallel() inside each subtest: before calling t.Run, shadow the loop variable
(e.g., tt := tt) to avoid closure capture issues, then inside the subtest
function immediately call t.Parallel() so each table-driven case that calls
ValidateRequiredSecondaryPath runs concurrently and speeds up the test suite.
internal/tui/wizard/age_ui_adapter.go (2)

60-62: Context not propagated to ConfirmAddRecipient.

Similar to ConfirmOverwriteExistingRecipient, the context parameter is received but not passed through. This is a minor consistency issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/tui/wizard/age_ui_adapter.go` around lines 60 - 62,
ConfirmAddAnotherRecipient on ageSetupUIAdapter receives a context but doesn't
pass it through; update the call in ageSetupUIAdapter.ConfirmAddAnotherRecipient
to forward ctx into ConfirmAddRecipient (matching how
ConfirmOverwriteExistingRecipient does), e.g. call ConfirmAddRecipient(ctx,
a.configPath, a.buildSig, currentCount) so the context is propagated.

23-25: Context not propagated to underlying wizard function.

ConfirmRecipientOverwrite receives ctx but doesn't pass it to the underlying function. If the wizard supports context-based cancellation, this could lead to unresponsive UI during cancellation requests.

Consider updating the signature if the underlying function supports context

If ConfirmRecipientOverwrite in age.go can be updated to accept context:

 func (a *ageSetupUIAdapter) ConfirmOverwriteExistingRecipient(ctx context.Context, recipientPath string) (bool, error) {
-	return ConfirmRecipientOverwrite(recipientPath, a.configPath, a.buildSig)
+	return ConfirmRecipientOverwrite(ctx, recipientPath, a.configPath, a.buildSig)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/tui/wizard/age_ui_adapter.go` around lines 23 - 25, The method
ageSetupUIAdapter.ConfirmOverwriteExistingRecipient is not propagating the
received ctx to the underlying ConfirmRecipientOverwrite call; update the call
to pass ctx (i.e., ConfirmRecipientOverwrite(ctx, recipientPath, a.configPath,
a.buildSig)) and, if ConfirmRecipientOverwrite's signature currently lacks a
context parameter, modify its signature to accept context.Context as the first
parameter and update all callers accordingly so cancellation propagates through
the wizard flow.
cmd/proxsave/install_existing_config.go (1)

28-38: Consider using safefs.Stat for timeout protection on network paths.

The relevant code snippets show internal/safefs/safefs.go provides a Stat function with timeout protection. Using os.Stat directly could hang indefinitely if configPath points to an unresponsive network mount.

Use safefs.Stat with timeout
+import (
+	"time"
+	"github.com/tis24dev/proxsave/internal/safefs"
+)
+
 func promptExistingConfigModeCLI(ctx context.Context, reader *bufio.Reader, configPath string) (existingConfigMode, error) {
-	info, err := os.Stat(configPath)
+	info, err := safefs.Stat(ctx, configPath, 5*time.Second)
 	if err != nil {
 		if os.IsNotExist(err) {
 			return existingConfigOverwrite, nil
 		}
 		return existingConfigCancel, fmt.Errorf("failed to access configuration file: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/proxsave/install_existing_config.go` around lines 28 - 38, Replace the
direct os.Stat call in promptExistingConfigModeCLI with the safefs.Stat wrapper
to get timeout protection for network mounts: call safefs.Stat(ctx, configPath)
(and import the safefs package), use the returned info and error exactly as
before (preserve the os.IsNotExist check to return existingConfigOverwrite, wrap
other errors with "failed to access configuration file: %w"), and keep the
subsequent info.Mode().IsRegular() check and its error path unchanged so
behavior remains identical except for the added timeout safety.
internal/orchestrator/age_setup_ui.go (1)

13-18: Consider adding documentation for the exported struct.

The AgeRecipientDraft struct contains sensitive fields (Passphrase, PrivateKey). Adding a doc comment clarifying that these should be zeroed after use would help consumers handle them securely.

📝 Suggested documentation
+// AgeRecipientDraft holds user-provided input for AGE recipient setup.
+// Callers should zero Passphrase and PrivateKey fields after use.
 type AgeRecipientDraft struct {
 	Kind       AgeRecipientInputKind
 	PublicKey  string
 	Passphrase string
 	PrivateKey string
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/orchestrator/age_setup_ui.go` around lines 13 - 18, Add a doc
comment above the exported AgeRecipientDraft struct describing its purpose and
explicitly warning that Passphrase and PrivateKey contain sensitive material
that must be cleared from memory when no longer needed (e.g., zeroed or
overwritten). Reference the struct name AgeRecipientDraft and its sensitive
fields Passphrase and PrivateKey in the comment, and include recommended
handling guidance such as zeroing/overwriting and limiting lifetime in memory.
internal/orchestrator/age_setup_ui_cli.go (1)

47-70: Invalid menu options silently loop without user feedback.

When the user enters an option outside the valid range (e.g., "5", "abc", empty string), the switch statement falls through without handling, causing the menu to redisplay without any error message. This could be confusing to users.

♻️ Add default case for invalid options
 		switch option {
 		case "1":
 			value, err := promptPublicRecipientAge(ctx, u.reader)
 			if err != nil {
 				u.warn(err)
 				continue
 			}
 			return &AgeRecipientDraft{Kind: AgeRecipientInputExisting, PublicKey: value}, nil
 		case "2":
 			passphrase, err := promptAndConfirmPassphraseAge(ctx)
 			if err != nil {
 				u.warn(err)
 				continue
 			}
 			return &AgeRecipientDraft{Kind: AgeRecipientInputPassphrase, Passphrase: passphrase}, nil
 		case "3":
 			privateKey, err := promptPrivateKeyValueAge(ctx)
 			if err != nil {
 				u.warn(err)
 				continue
 			}
 			return &AgeRecipientDraft{Kind: AgeRecipientInputPrivateKey, PrivateKey: privateKey}, nil
+		default:
+			fmt.Println("Invalid option. Please select 1-4.")
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/orchestrator/age_setup_ui_cli.go` around lines 47 - 70, The menu
switch on variable option (the block that returns AgeRecipientDraft via
promptPublicRecipientAge, promptAndConfirmPassphraseAge,
promptPrivateKeyValueAge) lacks a default branch so invalid inputs silently
loop; add a default case to the switch that calls u.warn with a clear message
like "Invalid option, please choose 1, 2, or 3" (and continue the loop) so users
get feedback when they enter "5", "abc", empty, etc.; keep using the existing
u.warn method and the same loop control to re-prompt.
internal/orchestrator/decrypt_workflow_ui_test.go (1)

64-80: Global state mutation may cause test flakiness in parallel runs.

The test mutates the package-level restoreFS variable without using t.Parallel() protection. While the deferred restore mitigates issues within this test, if other tests in the package run in parallel and also depend on restoreFS, race conditions could occur.

Consider using a test-local approach or ensuring tests that touch restoreFS are explicitly serialized.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/orchestrator/decrypt_workflow_ui_test.go` around lines 64 - 80, The
test mutates the package-level restoreFS (osFS{}) causing potential races;
refactor to avoid global mutation by adding an fs parameter (e.g., fs
aferio-like interface) to ensureWritablePathWithUI (or create a helper
ensureWritablePathWithUIWithFS(ctx, ui, fs, path, desc)) and update
TestEnsureWritablePathWithUI_ReturnsCleanMissingPath to pass a local osFS{}
instead of setting restoreFS; remove the restoreFS assignment and deferred
restore from the test and update any callers to use the new parameter so tests
no longer rely on mutating the shared restoreFS variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/proxsave/install_tui.go`:
- Around line 139-150: The new AGE success-path logging calls use bootstrap
without nil checks and can panic if bootstrap is nil; update the block after
runInitialEncryptionSetupWithUI (where setupResult is handled) to only call
bootstrap.Info(...) when bootstrap != nil, i.e., guard the three bootstrap.Info
invocations (the general "AGE encryption configured successfully", the
"Recipient saved to: %s" branch checking setupResult.WroteRecipientFile &&
setupResult.RecipientPath != "" and the else-if branch checking
setupResult.ReusedExistingRecipients) with a nil-check of the bootstrap logger
so the function works when bootstrap is optional.

In `@cmd/proxsave/newkey.go`:
- Around line 91-92: runNewKey currently calls bootstrap.Info unconditionally at
the end, which will panic if the bootstrap variable is nil; update the success
logging to first check if bootstrap != nil and only call bootstrap.Info(...)
when it's present, otherwise fall back to printing the same messages via
fmt.Println (or log) so TUI-less flows don't panic—locate the bootstrap variable
and the two bootstrap.Info(...) calls near the end of runNewKey and wrap them in
a nil-check.

In `@internal/orchestrator/workflow_ui_tui_decrypt_prompts.go`:
- Around line 75-85: The form currently pre-fills defaultPath and only checks
for emptiness, letting users submit the same conflicting path; update the
AddInputFieldWithValidation callback (the input added via
form.AddInputFieldWithValidation using label "New path") to also compare the
trimmed value against defaultPath and return an error like "path must be
different from existing path" when they match, and likewise guard in
form.SetOnSubmit (which sets newPath from values[label]) to reject or prevent
assigning newPath if it equals defaultPath so the Submit cannot return the same
path; reference the symbols AddInputFieldWithValidation, SetOnSubmit,
defaultPath, newPath, and label when making the change.

---

Outside diff comments:
In `@cmd/proxsave/main.go`:
- Around line 1567-1572: Replace the unnecessary single-case select by
performing a direct receive on the ticker channel: remove the select block and
use a plain channel receive from ticker.C (the same ticker used in the loop) to
wait for ticks; ensure the surrounding loop logic that currently does "case
<-ticker.C: continue" still continues the loop after the receive.

In `@cmd/proxsave/newkey.go`:
- Around line 116-124: In runNewKeySetup, don't overwrite any persisted
AgeRecipientFile; first load the existing config from configPath (as
encryption_setup.go does) and if it contains a non-empty AgeRecipientFile, use
that value instead of hard-coding recipientPath into the new config.Config;
otherwise fall back to the default filepath.Join(baseDir, "identity", "age",
"recipient.txt"). Ensure you still set BaseDir and ConfigPath on the resulting
config.Config and proceed with key rotation using that merged configuration.

---

Nitpick comments:
In `@cmd/proxsave/install_existing_config.go`:
- Around line 28-38: Replace the direct os.Stat call in
promptExistingConfigModeCLI with the safefs.Stat wrapper to get timeout
protection for network mounts: call safefs.Stat(ctx, configPath) (and import the
safefs package), use the returned info and error exactly as before (preserve the
os.IsNotExist check to return existingConfigOverwrite, wrap other errors with
"failed to access configuration file: %w"), and keep the subsequent
info.Mode().IsRegular() check and its error path unchanged so behavior remains
identical except for the added timeout safety.

In `@internal/config/validation_secondary_test.go`:
- Around line 24-37: Add parallel execution to the subtests by capturing the
loop variable and calling t.Parallel() inside each subtest: before calling
t.Run, shadow the loop variable (e.g., tt := tt) to avoid closure capture
issues, then inside the subtest function immediately call t.Parallel() so each
table-driven case that calls ValidateRequiredSecondaryPath runs concurrently and
speeds up the test suite.

In `@internal/cron/cron_test.go`:
- Around line 43-61: Add tests for boundary times to TestTimeToSchedule to
ensure TimeToSchedule handles edge values; specifically add cases with in
"00:00" expecting "00 00 * * *" and "23:59" expecting "59 23 * * *" (and keep
existing cases). Locate the test table in TestTimeToSchedule and append these
two entries (use the same struct fields name/in/want) and run the test suite to
verify behavior; if the assertions fail, update TimeToSchedule accordingly to
normalize leading zeros and validate 00–23 hours and 00–59 minutes.

In `@internal/orchestrator/age_setup_ui_cli.go`:
- Around line 47-70: The menu switch on variable option (the block that returns
AgeRecipientDraft via promptPublicRecipientAge, promptAndConfirmPassphraseAge,
promptPrivateKeyValueAge) lacks a default branch so invalid inputs silently
loop; add a default case to the switch that calls u.warn with a clear message
like "Invalid option, please choose 1, 2, or 3" (and continue the loop) so users
get feedback when they enter "5", "abc", empty, etc.; keep using the existing
u.warn method and the same loop control to re-prompt.

In `@internal/orchestrator/age_setup_ui.go`:
- Around line 13-18: Add a doc comment above the exported AgeRecipientDraft
struct describing its purpose and explicitly warning that Passphrase and
PrivateKey contain sensitive material that must be cleared from memory when no
longer needed (e.g., zeroed or overwritten). Reference the struct name
AgeRecipientDraft and its sensitive fields Passphrase and PrivateKey in the
comment, and include recommended handling guidance such as zeroing/overwriting
and limiting lifetime in memory.

In `@internal/orchestrator/decrypt_tui_e2e_helpers_test.go`:
- Around line 57-93: withTimedSimAppSequence currently initializes a tcell
SimulationScreen (screen) but never finalizes it; update the t.Cleanup closure
inside withTimedSimAppSequence to call screen.Fini() (and restore newTUIApp as
existing) so the simulation screen is properly closed after the test, preventing
resource leaks; ensure the cleanup runs even if other cleanup logic errors by
invoking screen.Fini() before resetting newTUIApp.

In `@internal/orchestrator/decrypt_workflow_ui_test.go`:
- Around line 64-80: The test mutates the package-level restoreFS (osFS{})
causing potential races; refactor to avoid global mutation by adding an fs
parameter (e.g., fs aferio-like interface) to ensureWritablePathWithUI (or
create a helper ensureWritablePathWithUIWithFS(ctx, ui, fs, path, desc)) and
update TestEnsureWritablePathWithUI_ReturnsCleanMissingPath to pass a local
osFS{} instead of setting restoreFS; remove the restoreFS assignment and
deferred restore from the test and update any callers to use the new parameter
so tests no longer rely on mutating the shared restoreFS variable.

In `@internal/orchestrator/telegram_setup_bootstrap.go`:
- Around line 46-104: The BuildTelegramSetupBootstrap function always returns a
nil error and embeds errors in the TelegramSetupBootstrap fields (ConfigError,
IdentityDetectError); change the function signature to remove the unused error
return (make it func BuildTelegramSetupBootstrap(configPath, baseDir string)
TelegramSetupBootstrap), update all return sites inside the function to return
just the state value, and update any callers to no longer expect a second error
return (they should inspect state.ConfigError and state.IdentityDetectError
instead).

In `@internal/orchestrator/tui_simulation_test.go`:
- Around line 61-74: Rename the test function
TestPromptOverwriteAction_SelectsOverwrite to
TestPromptExistingPathDecisionTUI_SelectsOverwrite so the name matches the
function under test promptExistingPathDecisionTUI; update the function
declaration (the test name) and any references to the old test identifier in the
file to ensure consistency and discoverability while keeping the test body
(calls to promptExistingPathDecisionTUI and assertions on PathDecision, newPath,
err) unchanged.

In `@internal/tui/wizard/age_ui_adapter.go`:
- Around line 60-62: ConfirmAddAnotherRecipient on ageSetupUIAdapter receives a
context but doesn't pass it through; update the call in
ageSetupUIAdapter.ConfirmAddAnotherRecipient to forward ctx into
ConfirmAddRecipient (matching how ConfirmOverwriteExistingRecipient does), e.g.
call ConfirmAddRecipient(ctx, a.configPath, a.buildSig, currentCount) so the
context is propagated.
- Around line 23-25: The method
ageSetupUIAdapter.ConfirmOverwriteExistingRecipient is not propagating the
received ctx to the underlying ConfirmRecipientOverwrite call; update the call
to pass ctx (i.e., ConfirmRecipientOverwrite(ctx, recipientPath, a.configPath,
a.buildSig)) and, if ConfirmRecipientOverwrite's signature currently lacks a
context parameter, modify its signature to accept context.Context as the first
parameter and update all callers accordingly so cancellation propagates through
the wizard flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b5227adc-f049-49d4-a1dc-3049839ce6c9

📥 Commits

Reviewing files that changed from the base of the PR and between 90dfba1 and 8dfd403.

📒 Files selected for processing (65)
  • cmd/proxsave/encryption_setup.go
  • cmd/proxsave/encryption_setup_test.go
  • cmd/proxsave/helpers_test.go
  • cmd/proxsave/install.go
  • cmd/proxsave/install_existing_config.go
  • cmd/proxsave/install_existing_config_test.go
  • cmd/proxsave/install_test.go
  • cmd/proxsave/install_tui.go
  • cmd/proxsave/main.go
  • cmd/proxsave/new_install.go
  • cmd/proxsave/new_install_test.go
  • cmd/proxsave/newkey.go
  • cmd/proxsave/prompts.go
  • cmd/proxsave/runtime_helpers.go
  • cmd/proxsave/schedule_helpers.go
  • cmd/proxsave/schedule_helpers_test.go
  • cmd/proxsave/telegram_setup_cli.go
  • cmd/proxsave/telegram_setup_cli_test.go
  • cmd/proxsave/upgrade.go
  • docs/CLI_REFERENCE.md
  • docs/CLOUD_STORAGE.md
  • docs/CONFIGURATION.md
  • docs/ENCRYPTION.md
  • docs/INSTALL.md
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/env_mutation.go
  • internal/config/env_mutation_test.go
  • internal/config/migration.go
  • internal/config/migration_test.go
  • internal/config/templates/backup.env
  • internal/config/validation_secondary.go
  • internal/config/validation_secondary_test.go
  • internal/cron/cron.go
  • internal/cron/cron_test.go
  • internal/orchestrator/age_setup_ui.go
  • internal/orchestrator/age_setup_ui_cli.go
  • internal/orchestrator/age_setup_workflow.go
  • internal/orchestrator/age_setup_workflow_test.go
  • internal/orchestrator/decrypt_tui.go
  • internal/orchestrator/decrypt_tui_e2e_helpers_test.go
  • internal/orchestrator/decrypt_tui_e2e_test.go
  • internal/orchestrator/decrypt_tui_simulation_test.go
  • internal/orchestrator/decrypt_tui_test.go
  • internal/orchestrator/decrypt_workflow_ui.go
  • internal/orchestrator/decrypt_workflow_ui_test.go
  • internal/orchestrator/encryption.go
  • internal/orchestrator/encryption_exported_test.go
  • internal/orchestrator/telegram_setup_bootstrap.go
  • internal/orchestrator/telegram_setup_bootstrap_test.go
  • internal/orchestrator/tui_simulation_test.go
  • internal/orchestrator/workflow_ui_tui_decrypt.go
  • internal/orchestrator/workflow_ui_tui_decrypt_prompts.go
  • internal/orchestrator/workflow_ui_tui_decrypt_test.go
  • internal/orchestrator/workflow_ui_tui_shared.go
  • internal/tui/wizard/age.go
  • internal/tui/wizard/age_test.go
  • internal/tui/wizard/age_ui_adapter.go
  • internal/tui/wizard/age_ui_adapter_test.go
  • internal/tui/wizard/install.go
  • internal/tui/wizard/install_test.go
  • internal/tui/wizard/new_install.go
  • internal/tui/wizard/new_install_test.go
  • internal/tui/wizard/telegram_setup_tui.go
  • internal/tui/wizard/telegram_setup_tui_test.go
💤 Files with no reviewable changes (2)
  • internal/orchestrator/decrypt_tui_test.go
  • internal/orchestrator/decrypt_tui.go

Comment on lines +139 to +150
logging.DebugStepBootstrap(bootstrap, "install workflow (tui)", "running AGE setup via orchestrator")
setupResult, err := runInitialEncryptionSetupWithUI(ctx, configPath, wizard.NewAgeSetupUI(configPath, buildSig))
if err != nil {
if errors.Is(err, wizard.ErrAgeSetupCancelled) {
return fmt.Errorf("encryption setup aborted by user: %w", errInteractiveAborted)
} else {
return fmt.Errorf("AGE setup failed: %w", err)
}
}

// Process the AGE data based on setup type
var recipientKey string
switch ageData.SetupType {
case "existing":
recipientKey = ageData.PublicKey
case "passphrase":
// Derive recipient from passphrase
recipient, err := deriveRecipientFromPassphrase(ageData.Passphrase)
if err != nil {
return fmt.Errorf("failed to derive recipient from passphrase: %w", err)
}
recipientKey = recipient
case "privatekey":
// Derive recipient from private key
recipient, err := deriveRecipientFromPrivateKey(ageData.PrivateKey)
if err != nil {
return fmt.Errorf("failed to derive recipient from private key: %w", err)
}
recipientKey = recipient
}

// Save the recipient
logging.DebugStepBootstrap(bootstrap, "install workflow (tui)", "saving AGE recipient")
if err := wizard.SaveAgeRecipient(recipientPath, recipientKey); err != nil {
return fmt.Errorf("failed to save AGE recipient: %w", err)
return err
}

bootstrap.Info("AGE encryption configured successfully")
bootstrap.Info("Recipient saved to: %s", recipientPath)
if setupResult.WroteRecipientFile && setupResult.RecipientPath != "" {
bootstrap.Info("Recipient saved to: %s", setupResult.RecipientPath)
} else if setupResult.ReusedExistingRecipients {
bootstrap.Info("Using existing AGE recipient configuration")
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard the AGE success-path logs when bootstrap is nil.

This function consistently treats bootstrap as optional, but this block now adds more unconditional bootstrap.Info(...) calls. If runInstallTUI is invoked without a bootstrap logger, the install can panic immediately after a successful AGE setup.

Proposed fix
-		bootstrap.Info("AGE encryption configured successfully")
-		if setupResult.WroteRecipientFile && setupResult.RecipientPath != "" {
-			bootstrap.Info("Recipient saved to: %s", setupResult.RecipientPath)
-		} else if setupResult.ReusedExistingRecipients {
-			bootstrap.Info("Using existing AGE recipient configuration")
-		}
-		bootstrap.Info("IMPORTANT: Keep your passphrase/private key offline and secure!")
+		if bootstrap != nil {
+			bootstrap.Info("AGE encryption configured successfully")
+			if setupResult.WroteRecipientFile && setupResult.RecipientPath != "" {
+				bootstrap.Info("Recipient saved to: %s", setupResult.RecipientPath)
+			} else if setupResult.ReusedExistingRecipients {
+				bootstrap.Info("Using existing AGE recipient configuration")
+			}
+			bootstrap.Info("IMPORTANT: Keep your passphrase/private key offline and secure!")
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/proxsave/install_tui.go` around lines 139 - 150, The new AGE success-path
logging calls use bootstrap without nil checks and can panic if bootstrap is
nil; update the block after runInitialEncryptionSetupWithUI (where setupResult
is handled) to only call bootstrap.Info(...) when bootstrap != nil, i.e., guard
the three bootstrap.Info invocations (the general "AGE encryption configured
successfully", the "Recipient saved to: %s" branch checking
setupResult.WroteRecipientFile && setupResult.RecipientPath != "" and the
else-if branch checking setupResult.ReusedExistingRecipients) with a nil-check
of the bootstrap logger so the function works when bootstrap is optional.

Comment on lines +91 to +92
bootstrap.Info("✓ New AGE recipient(s) generated and saved to %s", recipientPath)
bootstrap.Info("IMPORTANT: Keep your passphrase/private key offline and secure!")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard the TUI success logging when bootstrap is optional.

runNewKey already treats bootstrap as nullable earlier in this file, but Lines 91-92 call Info unconditionally. A nil bootstrap now turns a successful TUI run into a panic.

Patch suggestion
-	bootstrap.Info("✓ New AGE recipient(s) generated and saved to %s", recipientPath)
-	bootstrap.Info("IMPORTANT: Keep your passphrase/private key offline and secure!")
+	if bootstrap != nil {
+		bootstrap.Info("✓ New AGE recipient(s) generated and saved to %s", recipientPath)
+		bootstrap.Info("IMPORTANT: Keep your passphrase/private key offline and secure!")
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bootstrap.Info("✓ New AGE recipient(s) generated and saved to %s", recipientPath)
bootstrap.Info("IMPORTANT: Keep your passphrase/private key offline and secure!")
if bootstrap != nil {
bootstrap.Info("✓ New AGE recipient(s) generated and saved to %s", recipientPath)
bootstrap.Info("IMPORTANT: Keep your passphrase/private key offline and secure!")
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/proxsave/newkey.go` around lines 91 - 92, runNewKey currently calls
bootstrap.Info unconditionally at the end, which will panic if the bootstrap
variable is nil; update the success logging to first check if bootstrap != nil
and only call bootstrap.Info(...) when it's present, otherwise fall back to
printing the same messages via fmt.Println (or log) so TUI-less flows don't
panic—locate the bootstrap variable and the two bootstrap.Info(...) calls near
the end of runNewKey and wrap them in a nil-check.

Comment on lines +75 to +85
form := components.NewForm(app)
label := "New path"
form.AddInputFieldWithValidation(label, defaultPath, 64, func(value string) error {
if strings.TrimSpace(value) == "" {
return fmt.Errorf("path cannot be empty")
}
return nil
})
form.SetOnSubmit(func(values map[string]string) error {
newPath = strings.TrimSpace(values[label])
return nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject the current path in the “Use different path” form.

The field is prefilled with the conflicting path and only checks for emptiness. Pressing Enter immediately returns PathDecisionNewPath with the same value, which defeats the user's explicit choice to avoid overwrite.

Patch suggestion
 func promptNewPathInputTUI(defaultPath, configPath, buildSig string) (string, error) {
 	app := newTUIApp()
 	var newPath string
 	var cancelled bool
+	currentPath := filepath.Clean(defaultPath)
 
 	form := components.NewForm(app)
 	label := "New path"
 	form.AddInputFieldWithValidation(label, defaultPath, 64, func(value string) error {
-		if strings.TrimSpace(value) == "" {
+		trimmed := strings.TrimSpace(value)
+		if trimmed == "" {
 			return fmt.Errorf("path cannot be empty")
 		}
+		if filepath.Clean(trimmed) == currentPath {
+			return fmt.Errorf("choose a different path or go back and select overwrite")
+		}
 		return nil
 	})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/orchestrator/workflow_ui_tui_decrypt_prompts.go` around lines 75 -
85, The form currently pre-fills defaultPath and only checks for emptiness,
letting users submit the same conflicting path; update the
AddInputFieldWithValidation callback (the input added via
form.AddInputFieldWithValidation using label "New path") to also compare the
trimmed value against defaultPath and return an error like "path must be
different from existing path" when they match, and likewise guard in
form.SetOnSubmit (which sets newPath from values[label]) to reject or prevent
assigning newPath if it equals defaultPath so the Submit cannot return the same
path; reference the symbols AddInputFieldWithValidation, SetOnSubmit,
defaultPath, newPath, and label when making the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant