Skip to content

feat(tui): command history recall + tmux/stdout fire targets - #9

Merged
jessn-dev merged 8 commits into
developfrom
feat/m2-history-and-fire-targets
Jun 29, 2026
Merged

feat(tui): command history recall + tmux/stdout fire targets#9
jessn-dev merged 8 commits into
developfrom
feat/m2-history-and-fire-targets

Conversation

@jessn-dev

@jessn-dev jessn-dev commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Changes

internal/fire — where a confirmed command goes

  • Target: stdout (default, every OS) or tmux.
  • stdout prints the command once after the UI tears down, for the shell to
    capture. nock never runs it.
  • tmux prefills the current pane via tmux send-keys -l with no trailing
    Enter
    — the operator still fires it. Gated on $TMUX via Available(), so it
    is never offered or fired where it can't work (including all of Windows, which
    has no tmux).
  • Selected by --fire=stdout|tmux (set once at launch) and a per-command
    ctrl+t override on the confirm screen.

internal/history — recall past fired commands (ctrl+r)

  • Secrets-at-rest (one-way door): stores the command template and the
    variable bindings separately as JSONL — never the flattened resolved
    string. Values stay redactable and recall re-Resolves through the engine
    rather than trusting a baked-in line.
  • Location: os.UserConfigDir()/nock/history.jsonl. NOCK_HISTORY=<path>
    relocates it; NOCK_HISTORY=off disables persistence (no-op store).
  • Corrupt lines are skipped, not fatal; a missing file is empty, not an error.

Cross-platform owner-only history file

  • POSIX 0600 on Unix (open_unix.go).
  • Real owner-only DACL on Windows (open_windows.go) via
    golang.org/x/sys/windows — SDDL O:<sid>G:<sid>D:P(A;;FA;;;<sid>): protected
    DACL, current-user SID only, no inheritance.
  • openAppend is the build-tagged seam. x/sys was already in the tree (indirect
    via bubbletea); promoted to a direct dep — no new download, CGO-free.

TUI wiring (internal/tui)

  • New Options{History, DefaultTarget} on Run; new stageHistory recall screen.
  • Recall seeds engine.Vars() then re-enters the existing fill→confirm flow.

Security — show-before-fire holds

  • Recalled commands still pass through the confirm screen before anything is
    emitted; pre-bound vars just skip straight to confirm — still displayed (ADR
    009/015). No auto-run, displayed == emitted.
  • tmux uses literal send-keys -l with no Enter: a prefill, never an execution.
  • History secrets are owner-only at rest on every platform.

Tests

  • internal/fire: Parse table, $TMUX-driven availability, stdout emit captures
    the command verbatim, tmux-without-session errors.
  • internal/history: append/recent round-trip (newest-first, capped), disabled
    store no-op, missing file empty, corrupt line skipped, NOCK_HISTORY env,
    0600 perm assertion (skipped on Windows — ACL instead).
  • internal/tui: ctrl+r recall re-fills from saved vars, fired entry records
    template+bindings, default target, empty-history hint, esc-from-history.
  • make check green (fmt, vet, lint 0 issues, race) on host and
    GOOS=windows build + lint clean.

Out of scope (still open in M2)

Distribution (Releases / Homebrew / go install / AUR). Clipboard target remains
a later opt-in (ADR 015).

Summary by CodeRabbit

  • New Features
    • Added command history to the TUI with recall via ctrl+r, restoring saved variable bindings for re-run.
    • Added ctrl+t to redirect a confirmed command into an active tmux session when available.
    • Added configurable history persistence via NOCK_HISTORY, with an OS-specific default location.
  • Bug Fixes
    • History persistence now enforces owner-only permissions, skips malformed entries, and treats history load errors as non-fatal.
  • Documentation
    • Expanded CLI/TUI history documentation plus README and contribution/review guidance.
  • Chores
    • Improved CI automation (security scanning, CodeQL targeting) and added a new make verify local gate.

  Add internal/fire (stdout default, tmux send-keys -l prefill, $TMUX-gated)
  and internal/history (ctrl+r recall; stores template + var bindings, never the
  resolved string). --fire flag + per-command ctrl+t override.

  History file is owner-only on every OS: POSIX 0600 on Unix, owner-only DACL on
  Windows via x/sys/windows. Engine untouched; show-before-fire preserved.
@jessn-dev jessn-dev self-assigned this Jun 29, 2026
@jessn-dev jessn-dev added the feature Distinct user-facing functionality or characteristic label Jun 29, 2026
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds internal/fire for stdout/tmux command delivery and internal/history for owner-only JSONL persistence of command templates and variable bindings. Both packages are wired into the TUI: Run now accepts Options with a history store and default fire target; ctrl+r opens history recall; ctrl+t overrides delivery to tmux. Docs, roadmap, and review/security workflow files are updated accordingly.

Changes

Fire, History, and TUI Integration

Layer / File(s) Summary
fire package: Target, Parse, Emit, and tests
internal/fire/fire.go, internal/fire/fire_test.go, go.mod
Defines Target string type with Stdout/Tmux constants, Parse, Available (tmux gated on $TMUX), and Emit routing to fmt.Fprintln or tmux send-keys -l. golang.org/x/sys is promoted from indirect. Tests cover valid parsing, availability, stdout emission, tmux-session absence, and unknown-target errors.
history package: Entry, Store, JSONL persistence, owner-only file creation
internal/history/history.go, internal/history/open_unix.go, internal/history/open_windows.go, internal/history/history_test.go
Defines Entry (template + vars, no resolved string) and Store; DefaultPath reads NOCK_HISTORY with case-insensitive disable values; Append creates parent dir and delegates to platform-specific openAppend (Unix 0600 chmod-on-every-open; Windows owner-only DACL via CreateFile+SetSecurityInfo); Recent scans newest-first with 1 MiB line cap and skips malformed records. Tests cover round-trip, capping, disabled-store no-op, corrupt-line skipping, env handling, and Unix permission assertion.
TUI wiring: Options, history recall, fire delivery, ctrl+t override
internal/tui/tui.go, internal/tui/tui_test.go
Run signature changes to Run(ctx, e, opts Options); model gains history/delivery state fields; ctrl+r loads Recent entries into a recall screen with cursor navigation; enter at confirm stage sets emit+target and quits, then Run appends to history and calls fire.Emit; ctrl+t overrides to fire.Tmux when available. View updated for history stage and conditional tmux help. Tests cover default target, template+vars recording, recall refill, metadata preservation, empty-history hint, and esc-to-search.
Docs, roadmap, and project config
ROADMAP.md, docs/cli.md, .coderabbit.yaml, CONTRIBUTING.md, README.md, SECURITY.md, .github/workflows/codeql.yml, .github/workflows/gitleaks.yml, Makefile, scripts/dev-check.sh
ROADMAP marks tmux prefill and history milestones done. docs/cli.md documents NOCK_HISTORY, history data model, permissions, and ctrl+r. .coderabbit.yaml adds the review configuration. CONTRIBUTING and README add branching, product, and review workflow notes. SECURITY adds automated security testing details. CodeQL and Gitleaks workflows expand branch coverage and scanning. The Makefile and scripts/dev-check.sh add the local verify gate and CI-mirroring checks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: TUI history recall and new stdout/tmux fire targets.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/m2-history-and-fire-targets

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/fire/fire.go`:
- Around line 63-80: Emit currently falls through every non-Tmux value to
stdout, so unsupported Target values are silently accepted instead of failing
like Parse does. Update Emit to explicitly handle only the known targets and
return an error for any unknown/invalid Target, using the Target type and
Emit/Tmux symbols to locate the switch. This should also surface misconfigured
Options.DefaultTarget from internal/tui/tui.go instead of printing to Out by
mistake.

In `@internal/history/history.go`:
- Around line 14-22: The NOCK_HISTORY handling in history initialization is not
truly case-insensitive, so values like oFf fall through as a path instead of
disabling history. Update the switch logic in the history setup code (around the
NOCK_HISTORY parsing in history.go) to normalize the environment value before
matching, and ensure the disabling branch is triggered for any case variation of
off while preserving the existing behavior for other values.

In `@internal/history/open_unix.go`:
- Around line 13-18: The openAppend helper only sets 0600 for newly created
files, so existing history files can remain too permissive. Update openAppend to
harden any existing path before writing by checking the opened file’s
permissions and tightening them when needed, using the existing openAppend
symbol as the place to enforce this behavior. Keep the current append/create
flow, but ensure world-readable history files are corrected before Append writes
variable bindings.

In `@internal/history/open_windows.go`:
- Around line 19-21: Reject appends in openHistoryFile when the target history
file does not already have the owner-only DACL, since OPEN_ALWAYS can reuse a
permissive existing ACL. Update the history-file opening path to verify or
reapply the owner-only DACL before allowing writes, and fail closed if the
descriptor is missing or not restricted. Use the openHistoryFile logic in
internal/history/open_windows.go as the place to enforce this policy.
- Around line 34-40: The Windows history append handle in open_windows.go is too
restrictive because CreateFile in the history open path only allows
FILE_SHARE_READ, which can block concurrent writers. Update the sharing flags in
the CreateFile call used by the history append logic to include FILE_SHARE_WRITE
alongside FILE_SHARE_READ, so multiple nock processes can append safely without
sharing violations.

In `@internal/tui/tui.go`:
- Around line 316-322: History recall is rebuilding m.selected with only ID and
Command, which drops the Risk and RequiresAuth metadata used by viewConfirm().
Update recallEntry and the similar recall path near the other referenced lines
to rehydrate the full format.Command from engine.Get when the command still
exists, then overlay the stored template instead of replacing the whole struct.
Keep the existing selected sheet/template restore behavior intact while
preserving the safety badges.
- Around line 64-66: The DefaultTarget handling in TUI setup and confirmation
flow accepts tmux blindly even when fire.Tmux.Available() is false, so validate
the configured target before it becomes active or prevent Enter confirmation
when the target is unavailable. Update the logic around opts.DefaultTarget in
the TUI initializer and the confirmation path used by ctrl+t/Enter (for example
in the TUI methods that set and confirm the target) so tmux is only allowed when
the session is actually available, and fail or keep the dialog open early
instead of letting Run reach fire.Emit after leaving the alt-screen.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 33220ebc-002a-405b-b220-c2cf1bd21983

📥 Commits

Reviewing files that changed from the base of the PR and between 3c3f061 and 210e51e.

📒 Files selected for processing (11)
  • ROADMAP.md
  • docs/cli.md
  • go.mod
  • internal/fire/fire.go
  • internal/fire/fire_test.go
  • internal/history/history.go
  • internal/history/history_test.go
  • internal/history/open_unix.go
  • internal/history/open_windows.go
  • internal/tui/tui.go
  • internal/tui/tui_test.go

Comment thread internal/fire/fire.go
Comment thread internal/history/history.go
Comment thread internal/history/open_unix.go
Comment thread internal/history/open_windows.go Outdated
Comment thread internal/history/open_windows.go
Comment thread internal/tui/tui.go
Comment thread internal/tui/tui.go
  - fire.Emit: unknown target errors instead of silently printing to stdout
  - tui.Run: validate fire target before alt-screen; fail fast outside tmux
  - tui recall: rehydrate full command via engine.Get so risk/auth badges survive
  - history: re-tighten existing files to owner-only (0600 / reapplied Windows DACL)
  - history: NOCK_HISTORY=off is case-insensitive (strings.EqualFold)

  Add .coderabbit.yaml (auto-review scoped to develop/main bases) and document the
  review workflow, its solo-project rationale, and git-flow branching in
  CONTRIBUTING and README.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/history/open_windows.go`:
- Line 42: Add WRITE_OWNER to the access mask used when reopening the history
file in open_windows.go so SetSecurityInfo can update OWNER and GROUP
successfully. Update the handle-open logic around the FILE_APPEND_DATA and
WRITE_DAC permissions to include WRITE_OWNER before the call path that applies
security changes, keeping the change localized to the history file
open/permission setup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 769a9dbf-9ef4-4269-9566-ae98464d7891

📥 Commits

Reviewing files that changed from the base of the PR and between 210e51e and 00b7e2b.

📒 Files selected for processing (11)
  • .coderabbit.yaml
  • CONTRIBUTING.md
  • README.md
  • internal/fire/fire.go
  • internal/fire/fire_test.go
  • internal/history/history.go
  • internal/history/history_test.go
  • internal/history/open_unix.go
  • internal/history/open_windows.go
  • internal/tui/tui.go
  • internal/tui/tui_test.go
✅ Files skipped from review due to trivial changes (2)
  • CONTRIBUTING.md
  • README.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • internal/history/open_unix.go
  • internal/fire/fire_test.go
  • internal/fire/fire.go
  • internal/history/history_test.go
  • internal/tui/tui_test.go
  • internal/tui/tui.go

Comment thread internal/history/open_windows.go
  Add .coderabbit.yaml (auto-review scoped to develop/main) and document the review
  workflow + solo-project rationale. Lead README with the problem, an at-a-glance
  comparison, and a what-it-feels-like demo.
  Complete the free security-gate stack on both git-flow hops; document it in SECURITY.md.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/gitleaks.yml:
- Around line 32-34: The gitleaks workflow uses actions/checkout with the
default persisted token, which exposes unnecessary credentials. Update the
checkout step in the workflow to disable persisted credentials by setting
persist-credentials to false while keeping the existing fetch-depth behavior for
the gitleaks scan.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ca2cefb-8e2e-4ca3-946b-3ea7b34b9c2c

📥 Commits

Reviewing files that changed from the base of the PR and between 00b7e2b and 6b918d4.

📒 Files selected for processing (4)
  • .github/workflows/codeql.yml
  • .github/workflows/gitleaks.yml
  • README.md
  • SECURITY.md
✅ Files skipped from review due to trivial changes (1)
  • SECURITY.md

Comment thread .github/workflows/gitleaks.yml
  SetSecurityInfo with OWNER_SECURITY_INFORMATION needs SeRestorePrivilege, which
  ordinary processes (incl. CI runners) lack -> 'Access is denied'. The creator is
  already the owner; the protected owner-only DACL is what gates access, so set the
  DACL only. Fixes the failing windows-latest test job on #9.
  Cross-builds and cross-lints every release target + coverage + govulncheck, so
  platform-specific breakage is caught locally before push (make check is host-only).
  A living gate that tracks CI through to v1.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/dev-check.sh`:
- Around line 76-87: The golangci-lint check in dev-check.sh is optional right
now, which allows verify to pass without any linting; make this step mandatory
by failing immediately when golangci-lint is unavailable, or install/bootstrap
the pinned version before continuing. Update the control flow around the
existing step "golangci-lint (host)" and the later GOOS=windows lint path so
missing tooling stops the script instead of only calling warn.
- Around line 80-84: The current cross-lint block in scripts/dev-check.sh only
runs golangci-lint once with GOOS=windows, so it does not cover the full
advertised platform matrix. Update the logic around the lint step to iterate the
same PLATFORMS matrix used by the build/verify flow, including linux, darwin,
and windows across amd64 and arm64, so platform-specific files and
arch-dependent code are linted consistently. Use the existing step/golangci-lint
run block as the entry point and preserve the FAST gating while expanding the
per-platform execution.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7fecc828-ea36-4041-88e2-fa2866d18d7b

📥 Commits

Reviewing files that changed from the base of the PR and between 1e49398 and 47b76d5.

📒 Files selected for processing (3)
  • CONTRIBUTING.md
  • Makefile
  • scripts/dev-check.sh
✅ Files skipped from review due to trivial changes (1)
  • CONTRIBUTING.md

Comment thread scripts/dev-check.sh
Comment thread scripts/dev-check.sh Outdated
  The Access-denied failure came from setting the file OWNER at runtime, which needs
  SeRestorePrivilege (denied on CI runners). New files now get the owner-only DACL
  atomically via SecurityAttributes at creation (no privileged call); existing files
  are re-tightened best-effort. Fixes the windows-latest test job on #9.
…target OSes

  Address CodeRabbit on dev-check.sh: a missing linter now fails the gate instead of
  passing green, and the cross-lint pass iterates every target OS, not just windows.
@jessn-dev
jessn-dev merged commit 8f63b12 into develop Jun 29, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Distinct user-facing functionality or characteristic

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants