.. _Alfred-Workflow-Python-Development-Intro: Alfred Workflow Python Development Intro ============================================================================== Summary ------------------------------------------------------------------------------ This document is aimed at developers who want to build Alfred Workflows in Python. It explains the mechanics of connecting a Script Filter to Python code and introduces the recommended deployment model using `uvx `_. Why Python? ------------------------------------------------------------------------------ Alfred supports any language, but Python is a natural fit for workflow development: 1. No cold-start overhead — Python is an interpreter; each keystroke runs the script directly without spinning up a VM. 2. No compilation step — iterate fast during development; ship a normal package for production. 3. Rich ecosystem — almost any task you want to automate already has a library. .. _script-filter-s-script: Script Filter's Language and Script ------------------------------------------------------------------------------ When you add a Script Filter widget to an Alfred Workflow, you configure two fields: **Language** and **Script**. Together they form the shell command Alfred runs every time you type a character. The recommended approach is one Script Filter → one Python CLI subcommand, invoked through ``uvx``. **Why uvx?** `uvx `_ downloads, caches, and runs a pinned PyPI package on demand. There is no virtualenv to maintain, no path hardcoding, and no per-machine setup. Upgrading a workflow is a version bump in PyPI and a one-line change in Alfred. **Configuration:** - **Language**: ``/bin/bash`` - **Script** (production):: ~/.local/bin/uvx --from your-package==1.0.0 your-cli subcommand --query '{query}' - **Script** (local dev, pointing at your venv directly):: ~/path/to/your-project/.venv/bin/your-cli subcommand --query '{query}' Each Script Filter maps to exactly one subcommand. For example, a ``search-bookmarks`` Script Filter would look like:: ~/.local/bin/uvx --from afwf==${version} afwf-examples search-bookmarks --query '{query}' The subcommand receives a structured ``--query`` argument and is responsible for computing the result and printing the Alfred JSON to stdout. There is no ``handler_id``, no ``main.py`` dispatcher, and no shared entry point across multiple Script Filters. Script Filter's Configuration ------------------------------------------------------------------------------ After creating a Script Filter widget in Alfred, configure the settings panel as follows: .. image:: ./script-filter-configuration.png **Keyword** The keyword the user types to trigger this workflow. **with space** Check this, unless your workflow requires no query. **Argument Required / Argument Optional / No Argument** - ``No Argument`` — workflow needs no query. - ``Argument Required`` — query is mandatory. - ``Argument Optional`` — query is optional (most common case). **Language** Select ``/bin/bash``. We always wrap the call in bash so we can specify the full path to ``uvx``. **with input as argv / {query}** Select ``with input as {query}``. The ``{query}`` placeholder is substituted by Alfred and passed as the value of ``--query``. This keeps query parsing inside Python where it can be unit-tested. **Run behavior** *Queue mode*: ``Terminate previous script`` — when the user types a new character, the previous invocation is no longer relevant, so kill it immediately. *Query delay*: ``Immediately after each character typed`` works well for fast commands. Check ``Always run immediately for first typed arg character``. *Argument*: Check ``Automatically trim irrelevant arg whitespaces``. **Alfred filters results** Leave unchecked. Let your Python code handle filtering and ranking; this gives you full control over fuzzy matching, custom sort orders, and result freshness. **Escaping** Check only ``Double Quotes`` and ``Backslashes``. **Script** See :ref:`script-filter-s-script`. Sending Items to Alfred ------------------------------------------------------------------------------ Every Script Filter subcommand follows the same output contract: compute a list of items, serialize them to the Alfred JSON format, and write the result to **stdout**. The minimal shape Alfred expects is:: { "items": [ {"title": "item 1"}, {"title": "item 2"} ] } In plain Python, that is two lines: .. code-block:: python import sys import json script_filter_output = { "items": [ {"title": "item 1"}, {"title": "item 2"}, ] } json.dump(script_filter_output, sys.stdout) sys.stdout.flush() In practice you will never write this by hand — ``afwf`` provides typed classes (:class:`~afwf.item.Item`, :class:`~afwf.script_filter.ScriptFilter`) and a ``send_feedback()`` method that handles serialization and flushing for you. What's Next? ------------------------------------------------------------------------------ You now understand how a Script Filter connects to Python code and how the ``uvx`` deployment model works. The next document walks through the ``afwf`` framework — the typed classes and helpers that eliminate the boilerplate shown above.