Skip to content

quack go: search-driven directory jumper with pluggable shell-adapter architecture (zsh-first, fish-ready) #12

Description

@Ocramaru

Feature request

Add quack go <query> — a search-driven directory jumper: runs quack's search, aggregates the directories of the top hits, lets the user pick one, and drops their shell into it (like z/zoxide, but driven by quack's search over the indexed space).

Design goal: a stable shell-agnostic core plus a pluggable shell-adapter layer, so supporting a new shell (fish, nushell, pwsh) is a small self-contained PR that never touches core.

UX sketch

A dynamic, rich interactive selector — not a static prompt. The list renders in place; the user navigates with ↑/↓ (highlighted row moves) and confirms with Enter, or jumps instantly by pressing the number key. q/Esc cancels.

$ quack go parser
quack › jump to a directory                          (↑/↓ move · 1-3 jump · enter select · q cancel)

  ❯ 1  src/lib/parsing            4 hits   tokenizer + AST builder
    2  src/lib                    3 hits   core library modules
    3  tests/parsing              2 hits   parser unit tests

# (↓ once, Enter — or just press 1)
# shell is now in .../src/lib/parsing

Interaction details:

  • Highlighted row is styled (reverse/colour + marker); arrows move the selection and redraw in place.
  • Number keys 1-9 select-and-confirm immediately (no Enter needed) for the visible rows.
  • Enter confirms the highlighted row; q/Esc/Ctrl-C cancels with no cd.
  • Optionally show each directory's quack description/hit-count alongside the path (the catalog already has folder descriptions), so the choice is informed, not just a path list.

zsh power-user path: quack go red<TAB> completes against indexed directories (the selector becomes optional — completion bypasses it entirely).

The hard constraint

A child process cannot change its parent shell's CWD. So the binary can't cd directly — it prints the chosen path and a thin shell-function wrapper performs the cd. This is the z/zoxide model and dictates the layering below.

Architecture: core + pluggable shell adapters

Core (shell-agnostic)

  • search -> aggregate directories -> rank (by hit count / summed score; include folder hits from the where/which-folder routing).
  • Interactive selector (below), rendered on tty/stderr. The final absolute path is the ONLY thing on stdout — the inviolable contract that makes the wrapper's cd "$(...)" safe.
  • Exit codes: 0 = path on stdout; non-zero = cancel / empty (wrapper skips the cd).
  • Non-interactive: quack go <query> <index> selects directly; if stdout isn't a tty and no index given, print top path or exit non-zero.
  • Likely reuses the existing raw-terminal/ANSI rendering already in _duck.py rather than adding a TUI dependency.

Interactive selector

  • Renders the ranked list in place; ↑/↓ move a styled highlight ( + reverse/colour), redrawing without scrolling.
  • Number keys 1-9 select-and-confirm immediately; Enter confirms the highlight; q/Esc/Ctrl-C cancel.
  • Each row may show the directory's catalog description + hit count next to the path, so the choice is informed.

Shell-adapter layer (the extensible part)

One adapter per shell behind a SHELL_ADAPTERS registry:

class ShellAdapter(Protocol):
    name: str
    def detect(self) -> bool: ...
    def rc_file(self) -> Path: ...
    def init_script(self) -> str: ...
    def completion_script(self) -> str | None: ...
    supports_completion: bool
    supports_push_to_buffer: bool      # e.g. zsh `print -z`

quack shell-init <shell> emits the adapter's init_script() (+ completion if supported). Adding a shell = new adapter in shells/<name>.py + registry entry + fixture test; core and other adapters untouched. Mirrors the existing CLIENTS/ClientTarget pattern in mcp_install.py.

Per-shell behavior

Shell init_script() Capability extras
bash cd "$(command quack go "$@")" wrapper baseline
zsh (first-class) autoloaded fn into $fpath compdef completion against the catalog (quack go red<TAB>); optional print -z to push cd <dir> onto the next prompt
fish (future PR) fish function wrapper fish completions if contributed

Capability flags let core degrade gracefully — no completion still gets the selector; no buffer-push just cd's.

Notes

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions