Skip to main content

A modern interactive terminal picker for creating interactive command-line selection interfaces

Project description

Pyckify ๐ŸŽฏ

A modern, feature-rich Python library for creating interactive command-line selection interfaces. Pyckify (pick-it-for-you) offers an enhanced selection experience with support for multiselect, grouping, filtering, search, and rich styling.

PyPI version Python Versions License: MIT

Features ๐Ÿš€

  • ๐ŸŽจ Rich terminal UI with 8 built-in themes, live-switchable with t
  • โœจ Single and multi-selection modes
  • ๐Ÿ” Fuzzy search with ranked results and optional score display
  • ๐Ÿท๏ธ Option grouping, tagging, and tag-filter mode (# key)
  • โŒจ๏ธ Keyboard shortcuts with smart conflict resolution
  • ๐ŸŽฏ Custom filtering and 4 sort modes (s key cycles)
  • ๐Ÿ“ Option descriptions, icons, per-option Rich style overrides
  • ๐Ÿ‘๏ธ Preview pane (bottom or right) for long-form option detail
  • ๐Ÿ“‹ Clipboard copy (c key)
  • โšก Smooth scrolling, Page Up/Down, Home/End navigation
  • ๐ŸŽญ Disabled options and visual Separator support
  • ๐ŸŽช Windows/Unix compatible
  • ๐Ÿ“ป Multiple multiselect indicator styles: circle, check, star, radio
  • ๐Ÿงฐ Helper utilities: make_options(), from_dict()

Installation ๐Ÿ“ฆ

pip install pyckify

Quick Start ๐ŸŽฎ

Basic Usage

from pyckify import Pyck, Option

# Simple string options
options = ["Red", "Blue", "Green", "Yellow"]
selected, index = Pyck(options, title="Choose a color")
print(f"Selected color: {selected}")

# Using Option objects
options = [
    Option("๐ŸŽ Apple", description="Fresh from the garden"),
    Option("๐ŸŒ Banana", description="Rich in potassium"),
    Option("๐ŸŠ Orange", description="Vitamin C boost")
]
selected, index = Pyck(options, title="Choose a fruit")
print(f"Selected fruit: {selected.label}")

Advanced Usage ๐Ÿ”ง

Multi-select with Constraints

from pyckify import Pyck, Option

options = [
    Option("Python", description="General-purpose language"),
    Option("JavaScript", description="Web development"),
    Option("Rust", description="Systems programming"),
    Option("Go", description="Cloud infrastructure")
]

result = Pyck(
    options=options,
    title="Select Programming Languages",
    subtitle="Choose 2-3 languages for your project",
    multiselect=True,
    minSelectionCount=2,
    maxSelectionCount=3,
    separateValues=True  # Returns a PickResult object
)

if result:
    print("\nSelected languages:")
    for lang in result.values:
        print(f"- {lang.label}: {lang.description}")

Grouped Options with Icons and Shortcuts

options = [
    # Development Tools
    Option("๐Ÿ“ VS Code",
           description="Popular code editor",
           group="Development Tools",
           shortcut="v",
           tags=["editor", "free"]),
    Option("โšก PyCharm",
           description="Python IDE",
           group="Development Tools",
           shortcut="p",
           tags=["ide", "paid"]),

    # Version Control
    Option("๐Ÿ˜บ GitHub",
           description="Code hosting platform",
           group="Version Control",
           shortcut="g",
           tags=["git", "cloud"]),
    Option("๐ŸฆŠ GitLab",
           description="DevOps platform",
           group="Version Control",
           shortcut="l",
           tags=["git", "cloud"])
]

result = Pyck(
    options=options,
    title="Development Stack",
    subtitle="Select your tools",
    multiselect=True,
    group_by="group",
    show_shortcuts=True
)

Advanced Grouped Options with Objects

options = (
    [Option(f" ๐ŸŽž๏ธ {video}", group="๐ŸŽž๏ธ Video Tracks", value=video) for video in videos] +
    [Option(f" ๐Ÿ”Š {audio}", group="๐Ÿ”Š Audio Tracks", value=audio) for audio in audios] +
    [Option(f" ๐Ÿ’ฌ {subtitle}", group="๐Ÿ’ฌ Subtitle Tracks", value=subtitle) for subtitle in subtitles]
)
result = Pyck(
    options=options,
    group_by="group",
    multiselect=True,
    minSelectionCount=1,
    separateValues=True,
)
results = [value.value for option in result for value in option if isinstance(value, Option)]

Output:

โ†‘โ†“ navigate โ€ข space select โ€ข a select all โ€ข enter confirm โ€ข / search โ€ข esc clear filters/quit

โ†‘ More options above

๐ŸŽž๏ธ Video Tracks
    ๐ŸŽž๏ธ VIDEO: BnGFobSd | avc1.4d401f | SDR | 480x360 | 901 kbps | 29.970 FPS
    ๐ŸŽž๏ธ VIDEO: 6EFRMq5M | avc1.4d401f | SDR | 480x360 | 494 kbps | 29.970 FPS

๐Ÿ”Š Audio Tracks
    ๐Ÿ”Š AUDIO: KuHayhsL | AAC | 2.0 | 128 kbps | yue
    ๐Ÿ”Š AUDIO: eXSUTLgz | AAC | 2.0 | 128 kbps | en
โ†’  ๐Ÿ”Š AUDIO: QyEd8Mp6 | AAC | 2.0 | 128 kbps | el
โ†“ More options below

Selected: 0 (minimum: 1)

Fuzzy Search with Ranked Results

result = Pyck(
    options=options,
    title="Language Picker",
    fuzzy=True,             # Enable fuzzy (ranked) search โ€” default True
    show_fuzzy_score=True,  # Show match score next to each option
    separateValues=True,
)

Press / to enter search mode. Results are automatically ranked by match quality. Set confirm_on_single=True to auto-confirm when the search narrows to a single match.

Preview Pane

options = [
    Option(
        "๐Ÿ Python",
        description="General-purpose scripting",
        preview=(
            "Python is a high-level, dynamically typed language prized for\n"
            "its readability and vast ecosystem. Ideal for data science,\n"
            "web backends, automation, and AI/ML workloads."
        ),
    ),
]

result = Pyck(
    options=options,
    title="Language Deep-Dive",
    show_preview=True,
    preview_position="bottom",  # or "right"
    separateValues=True,
)

Themes

result = Pyck(
    options=options,
    title="Themed Picker",
    theme="dracula",  # Set a theme at creation time
)

Press t at runtime to cycle through all available themes live. Available themes: default, dracula, nord, monokai, catppuccin, solarized, one_dark, gruvbox.

You can also switch themes programmatically:

from pyckify import set_theme
set_theme("catppuccin")

Multiselect Indicator Styles

result = Pyck(
    options=options,
    multiselect=True,
    multiselect_indicator="radio",  # "circle" | "check" | "star" | "radio"
)

Sort Modes

Press s to cycle through sort modes at runtime: original โ†’ aโ†’z โ†’ zโ†’a โ†’ selected-first.

Tag Filtering

Press # to enter tag-filter mode and narrow options by tag. Tags are displayed inline when show_tags=True (default).

options = [
    Option("Apple",  tags=["fruit", "sweet"]),
    Option("Lemon",  tags=["fruit", "citrus"]),
    Option("Carrot", tags=["vegetable"]),
]
result = Pyck(options=options, show_tags=True)
# Press # then type "citrus" to filter

Custom Filtering

from dataclasses import dataclass
from pyckify import Pyck, Option

@dataclass
class Language:
    name: str
    type: str
    year: int

options = [
    Option(f"๐ŸŒŸ {lang.name}",
           description=f"Created in {lang.year}",
           value=lang,
           tags=[lang.type])
    for lang in [
        Language("Go",   "compiled",    2009),
        Language("Rust", "compiled",    2010),
        Language("Zig",  "compiled",    2016),
    ]
]

def modern_languages(option: Option) -> bool:
    return option.value.year >= 2010

result = Pyck(
    options=options,
    title="Modern Languages",
    multiselect=True,
    filter_fn=modern_languages,
)

Disabled Options and Separators

from pyckify import Pyck, Option, Separator

options = [
    Separator("โ”€โ”€ Recommended โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€"),
    Option("โœจ Premium Plan",    description="All features",  style="bold green", tags=["paid"]),
    Option("๐Ÿ’Ž Enterprise Plan", description="Custom SLA",    style="bold cyan",  tags=["paid"]),
    Separator("โ”€โ”€ Legacy (unavailable) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€"),
    Option("๐Ÿ”’ Pro v1",          description="Discontinued",  enabled=False,      tags=["legacy"]),
    Separator("โ”€โ”€ Free tiers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€"),
    Option("โญ Hobbyist",         description="500 req/day",                       tags=["free"]),
]

result = Pyck(
    options=options,
    title="Subscription Plans",
    subtitle="Disabled options are shown but not selectable"
)

Helper Utilities

Build Option lists from plain dicts or any iterable:

from pyckify.options import from_dict, make_options

# From a list of dicts
options = from_dict([
    {"label": "AWS S3",       "description": "Object storage",      "tags": ["storage"]},
    {"label": "GCS",          "description": "Google Cloud Storage", "tags": ["storage"]},
    {"label": "Cloudflare R2","description": "Zero-egress S3",       "tags": ["storage"]},
])

# From any iterable
options = make_options([1, 2, 3], label_fn=lambda x: f"Item {x}")

Per-Option Rich Style Overrides

Option("โœจ Premium", style="bold green")
Option("๐Ÿ’Ž Enterprise", style="bold cyan")

Any valid Rich style string is accepted, overriding the active theme for that row only.

Clipboard Copy

Press c to copy the currently focused option's label to the system clipboard. Works on Windows, macOS, and Linux (requires xclip, xsel, or wl-copy).

API Reference ๐Ÿ“š

Pyck() Function

The main function for creating selection interfaces.

def Pyck(
    options: Sequence[OPTION_T],
    title: Optional[str] = None,
    subtitle: Optional[str] = None,
    indicator: str = "โ†’",
    defaultIndex: int = 0,
    multiselect: bool = False,
    minSelectionCount: int = 0,
    maxSelectionCount: Optional[int] = None,
    filter_fn: Optional[Callable[[OPTION_T], bool]] = None,
    show_shortcuts: bool = True,
    group_by: Optional[str] = None,
    separateValues: bool = False,
    theme: str = "default",
    fuzzy: bool = True,
    show_preview: bool = False,
    preview_position: str = "bottom",
    show_border: bool = False,
    multiselect_indicator: str = "circle",
    confirm_on_single: bool = False,
    allow_empty: bool = False,
    show_tags: bool = True,
    show_fuzzy_score: bool = False,
    max_visible: Optional[int] = None,
    page_size: Optional[int] = None,
    jump_on_shortcut_select: bool = False,
) -> Union[PickResult, List[PICK_RETURN_T], PICK_RETURN_T, None]

Parameters

Parameter Type Default Description
options Sequence โ€” Items to display (strings or Option objects)
title str None Header text
subtitle str None Subheader text
indicator str "โ†’" Cursor indicator symbol
defaultIndex int 0 Starting cursor position
multiselect bool False Allow multiple selections
minSelectionCount int 0 Minimum required selections (multiselect)
maxSelectionCount int None Maximum allowed selections (multiselect)
filter_fn Callable None Custom predicate to hide options
show_shortcuts bool True Render [key] shortcut badges
group_by str None Option attribute to group by (e.g. "group")
separateValues bool False Return a PickResult instead of raw tuples
theme str "default" Built-in theme name
fuzzy bool True Enable fuzzy (ranked) search
show_preview bool False Display Option.preview in a pane
preview_position str "bottom" "bottom" or "right"
show_border bool False Wrap the picker in a Rich panel border
multiselect_indicator str "circle" "circle", "check", "star", or "radio"
confirm_on_single bool False Auto-confirm when search yields exactly one result
allow_empty bool False Allow confirming with zero selections in multiselect
show_tags bool True Render option tags inline
show_fuzzy_score bool False Show fuzzy match score next to each option
max_visible int None Override auto-detected visible row count
page_size int None Rows scrolled per PgUp/PgDn (default: page height โˆ’ 1)
jump_on_shortcut_select bool False Also toggle selection when jumping via shortcut in multiselect mode

Returns None on Esc, a PickResult when separateValues=True, otherwise a (value, index) tuple or list of tuples.

Option Class

@dataclass
class Option:
    label: str
    value: Union[object, str, Any] = None
    description: Optional[str] = None
    enabled: bool = True
    shortcut: Optional[str] = None
    icon: Optional[str] = None
    group: Optional[str] = None
    tags: List[str] = field(default_factory=list)
    preview: Optional[str] = None       # Long-form text shown in preview pane
    style: Optional[str] = None         # Per-row Rich style string override
    metadata: Dict[str, Any] = field(default_factory=dict)  # App data bag

Attributes

Attribute Description
label Display text shown in the list
value Arbitrary payload returned on selection (defaults to label)
description Short helper text rendered beside the label
enabled When False, shown but cannot be selected
shortcut Single character that jumps to this option
icon Emoji or ASCII prefix prepended to the label
group Group name used when group_by is set
tags Free-form tags for display and tag-filter mode
preview Long-form text shown in the optional preview pane
style Rich style string to override the active theme for this row
metadata Arbitrary dict for application-specific data

Separator Class

@dataclass
class Separator(Option):
    def __init__(self, label: str = "โ”€" * 30, description: Optional[str] = None):
        super().__init__(label, description=description, enabled=False)

A non-selectable visual divider. Can be placed anywhere in the options list.

PickResult

class PickResult(NamedTuple):
    values: Union[List[Any], Any]
    indices: Union[List[int], int]

Returned when separateValues=True. Provides is_multi, as_list, and index_list convenience properties.

Helper Functions

from pyckify.options import from_dict, make_options

# Build Options from a list of dicts (keys match Option fields)
options = from_dict([{"label": "Alpha", "tags": ["a"]}, {"label": "Beta"}])

# Wrap any iterable in Option instances
options = make_options([1, 2, 3], label_fn=lambda x: f"Item {x}")

Keyboard Controls โŒจ๏ธ

Key Action
โ†‘ / โ†“ Navigate options
PgUp / PgDn Scroll one page
Home / End Jump to top / bottom
Enter Confirm selection
Space Toggle selection (multiselect)
a Select / deselect all visible options
i Invert selection among visible options
/ Enable search
# Filter by tag
s Cycle sort mode (original โ†’ aโ†’z โ†’ zโ†’a โ†’ selected-first)
t Cycle theme live
c Copy focused label to clipboard
? Toggle help overlay
Esc Clear active filter, or quit

Shortcut priority: Option shortcuts always take precedence over global keys. For example, if an option has shortcut="a", pressing a jumps to it rather than triggering select-all.

Theming ๐ŸŽจ

Eight built-in themes are available. Pass theme= to Pyck() or press t at runtime to cycle live.

# Available theme names
"default" | "dracula" | "nord" | "monokai" | "catppuccin" | "solarized" | "one_dark" | "gruvbox"

Each theme defines styles for every UI element:

custom_theme = {
    "title":            Style(bold=True,   color="dark_orange"),
    "subtitle":         Style(italic=True, color="cyan"),
    "indicator":        Style(bold=True,   color="bright_yellow"),
    "selected":         Style(bold=True,   color="green"),
    "active":           Style(bold=True,   color="white", bgcolor="blue"),
    "active_selected":  Style(bold=True,   color="green", bgcolor="blue"),
    "disabled":         Style(dim=True,    color="grey70"),
    "description":      Style(italic=True, color="bright_blue"),
    "group_header":     Style(bold=True,   color="dark_orange"),
    "shortcut":         Style(bold=True,   color="red"),
    "tag":              Style(italic=True, color="magenta"),
    "search_match":     Style(bold=True,   color="white", bgcolor="dark_orange"),
    # ... and more
}

Switch themes programmatically at any time:

from pyckify import set_theme
set_theme("gruvbox")

Examples ๐Ÿ“

Run the full feature showcase interactively:

python examples.py

The showcase covers: basic single-select, multiselect with min/max constraints, grouped options with shortcuts, fuzzy search and sort, preview pane, theme switching, separators with per-option styles, from_dict/make_options helpers, and confirm_on_single.

What's New

  • radio indicator style โ€” new multiselect_indicator value using โ—‰/โ—ฏ symbols
  • jump_on_shortcut_select โ€” in multiselect mode, pressing a shortcut key now optionally toggles that option's selection in addition to jumping the cursor
  • 4 new themes โ€” catppuccin, solarized, one_dark, gruvbox
  • confirm_on_single โ€” auto-confirms when a search narrows to exactly one visible result
  • allow_empty โ€” permits confirming with zero selections in multiselect mode
  • show_preview / preview_position โ€” side or bottom preview pane using Option.preview
  • fuzzy / show_fuzzy_score โ€” ranked fuzzy search with optional score display
  • show_tags / tag-filter mode โ€” display tags inline and filter by tag with #
  • show_border โ€” wrap the picker in a Rich panel border
  • multiselect_indicator โ€” choose between circle, check, star, radio
  • page_size / max_visible โ€” fine-grained control over scroll behaviour
  • s / t / c keys โ€” sort cycling, live theme switching, clipboard copy
  • from_dict() / make_options() โ€” convenience helpers in pyckify.options
  • Option.preview โ€” long-form text for the preview pane
  • Option.style โ€” per-row Rich style string override
  • Option.metadata โ€” arbitrary app data bag on each option
  • invertSelection โ€” now inverts only among currently visible (filtered) options
  • Shortcut conflict resolution โ€” option shortcuts always take priority over global keys

Contributing ๐Ÿค

Contributions are welcome! Please feel free to submit a Pull Request.

License ๐Ÿ“„

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments ๐Ÿ™

  • Inspired by pick
  • Built with rich for terminal formatting

Author โœ๏ธ

ReiDoBrega (@ReiDoBrega)


Made with โค๏ธ using Python

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyckify-0.1.3-py3-none-any.whl (23.8 kB view details)

Uploaded Python 3

File details

Details for the file pyckify-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: pyckify-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 23.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.0

File hashes

Hashes for pyckify-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 7cfeb580bf30f5ae4aec135f39151f2ea9c904ca090ca7f62abf6bb87f1ffe6f
MD5 8636c32c6c4eaa927c97465f1a267618
BLAKE2b-256 e261ee041dabc2da32e514237b1fae9289ee9b698bc60c03fdd8d6e6bd6a45ae

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page