Skip to main content
Documentation Status https://github.com/MacHu-GWU/afwf-project/actions/workflows/main.yml/badge.svg https://codecov.io/gh/MacHu-GWU/afwf-project/branch/main/graph/badge.svg https://img.shields.io/pypi/v/afwf.svg https://img.shields.io/pypi/l/afwf.svg https://img.shields.io/pypi/pyversions/afwf.svg https://img.shields.io/badge/✍️_Release_History!--None.svg?style=social&logo=github https://img.shields.io/badge/⭐_Star_me_on_GitHub!--None.svg?style=social&logo=github
https://img.shields.io/badge/Link-API-blue.svg https://img.shields.io/badge/Link-Install-blue.svg https://img.shields.io/badge/Link-GitHub-blue.svg https://img.shields.io/badge/Link-Submit_Issue-blue.svg https://img.shields.io/badge/Link-Request_Feature-blue.svg https://img.shields.io/badge/Link-Download-blue.svg

Welcome to afwf Documentation

https://afwf.readthedocs.io/en/latest/_static/afwf-logo.png

A powerful framework enables fast and elegant development of Alfred Workflows in Python.

What’s New in 1.0.2 (2026-04-21)

Version 1.0.2 is a milestone release. It completely drops the old, complex workflow development model in favour of modern Python practices: write pure functions, run them locally, and they work in Alfred out of the box — no adapters, no scaffolding, no glue code. Combined with uvx for zero-install deployment, the gap between a working Python function and a shipping Alfred workflow has never been smaller.

Project Background

The official Alfred Python library had not been updated for years and only supported Python 2.7 — a version that reached end-of-life in January 2020 and was removed from macOS in 2021. This left every workflow built on that library broken on modern Macs. The library also suffered from heavy coupling: it bundled HTTP clients, caching, and other concerns that belong in dedicated third-party packages, producing layers of workarounds on top of outdated code.

At the same time, I personally maintain more than a dozen Alfred workflows across different domains. Early on, each project contained large amounts of boilerplate unrelated to business logic — Alfred integration glue, test scaffolding, and meta-programming code duplicated across every repo. Extracting that into a reusable framework became the obvious next step.

afwf provides:

  • A clean Python data model for Alfred’s Script Filter JSON protocol, built on Pydantic.

  • A fluent Item API with action helpers (open_url, run_script, send_notification, …) that wire directly to Alfred’s Conditional widget.

  • Optional fuzzy-matching (afwf[fuzzy]) and disk-caching (afwf[cache]) extras.

  • A deployment pattern — publish to PyPI, expose via fire CLI, invoke with uvx — that eliminates dependency management on the end-user machine.

  • Best practices for development, testing, and release derived from building the official AWS internal Alfred Workflow (one of the largest Alfred workflow codebases in existence).

The library also ships ~50 bundled PNG icons commonly used in productivity workflows. Preview all icons.

Core Modules

Script Filter JSON Protocol

Alfred Script Filters communicate via a JSON protocol. These modules implement it:

  • afwf/script_filter_object.py — ScriptFilterObject: Pydantic base class; to_script_filter() serialises to Alfred-compatible dict (handles None-omission, False-preservation, empty-object rules).

  • afwf/item.py — Icon, Text, Item: Alfred dropdown item model; Item has fluent set_* helpers (open_url, run_script, open_file, send_notification, etc.) that set workflow variable pairs.

  • afwf/script_filter.py — ScriptFilter: Top-level response object; holds items list; send_feedback() dumps JSON to stdout.

Query Parsing

  • afwf/query.py — Query, QueryParser: Utility for parsing the raw Alfred {query} string into structured tokens.

import afwf.api as afwf

q = afwf.Query.from_str("  hello   world  ")
q.trimmed_parts   # ['hello', 'world']
q.n_trimmed_parts # 2

parser = afwf.QueryParser.from_delimiter([" ", "/"])
q = parser.parse("foo/bar baz")
q.trimmed_parts   # ['foo', 'bar', 'baz']

Constants & Icons

  • afwf/constants.py — IconTypeEnum, ItemTypeEnum, ModEnum, VarKeyEnum, VarValueEnum: All Alfred protocol string constants.

  • afwf/icon.py — IconFileEnum: Paths to ~50 bundled PNG icons (search, folder, star, git, error, …). Preview all icons.

Error Handling

  • afwf/decorator.py — log_error: Decorator factory that silently logs exceptions to a rotating file so Alfred’s UI never shows a raw Python traceback.

import afwf.api as afwf

@afwf.log_error()
def main(query: str) -> afwf.ScriptFilter:
    ...

# Custom log file and size limit
@afwf.log_error(log_file="~/.alfred-afwf/search.log", max_bytes=200_000)
def main(query: str) -> afwf.ScriptFilter:
    ...

Alfred Introspection

  • afwf/alfred/workflow.py — AlfredWorkflow: Represents a single Alfred Workflow directory; lazily reads info.plist to expose name, bundle_id, version, description, and more.

  • afwf/alfred/prefs.py — AlfredPreferences: Locates the Alfred preferences folder and enumerates installed workflows.

  • afwf/project/project.py — AfwfProject: Binds a Python source project to its corresponding Alfred Workflow folder; exposes paths for main.py, lib/, info.plist, and icon.png on both sides.

Optional Utilities (afwf/opt/)

  • afwf/opt/cache/ — TypedCache: diskcache-backed disk cache with a type-hint-safe typed_memoize() decorator. Install with afwf[cache].

  • afwf/opt/fuzzy/ — FuzzyMatcher: Generic fuzzy matcher over any item type using rapidfuzz; subclass and implement get_name(). Install with afwf[fuzzy].

  • afwf/opt/fuzzy_item/ — Item, FuzzyItemMatcher: Item subclass that stores a fuzzy-match name in variables; wires directly to FuzzyMatcher.

Quickstart

A minimal Script Filter handler:

import afwf.api as afwf

@afwf.log_error()
def main(query: str) -> afwf.ScriptFilter:
    q = afwf.Query.from_str(query)
    sf = afwf.ScriptFilter()
    item = afwf.Item(title="Hello", subtitle=f"You typed: {q.raw}")
    item.open_url(url="https://example.com")
    sf.items.append(item)
    return sf

if __name__ == "__main__":
    import fire
    fire.Fire({"search": lambda query="": main(query).send_feedback()})

Deployment Pattern (Best Practice)

Publish your workflow logic as a Python package on PyPI, expose it as a CLI using fire, then invoke it from Alfred’s Script Filter via uvx:

# Development / local
~/Documents/GitHub/my-workflow-project/.venv/bin/my-workflow search --query '{query}'

# Production (no install required on end-user machine)
~/.local/bin/uvx --from my-workflow==1.0.0 my-workflow search --query '{query}'

The afwf-examples CLI bundled in this repo demonstrates all built-in example handlers:

afwf-examples search-bookmarks --query 'git'
afwf-examples open-file
afwf-examples read-file
afwf-examples write-file --query 'hello'
afwf-examples view-settings
afwf-examples set-settings --query 'theme'
afwf-examples memoize --query 'test'

AI-Assisted Development (Claude Code Agent Skill)

This repo ships a Claude Code Agent Skill under .claude/skills/afwf/.

The Skill is a self-contained reference guide written for AI assistants. Hand it to any AI that supports the Skill mechanism (such as Claude Code) and the AI instantly knows how to build Alfred workflows with afwf, covering:

  • How to write a Script Filter handler (main(query) → ScriptFilter)

  • Mapping CLI entry points (fire.Fire) to Alfred’s Script field

  • Every Item action method (open_url, run_script, send_notification, …)

  • Query parsing, fuzzy matching (afwf[fuzzy]), and disk caching (afwf[cache])

  • The two-phase write-action pattern (run_script + send_notification)

  • Unit testing patterns — no Alfred required, plain pytest

  • Local dev and uvx production deployment

Directory layout:

.claude/skills/afwf/
├── SKILL.md                          # Main Skill file — the AI reference manual
└── ref/
    ├── script-filter-json-format.md  # Alfred Script Filter JSON protocol spec
    └── script-filter-input.md        # Alfred Script Filter input format reference

Activating in Claude Code:

Type /afwf in the conversation. Claude Code loads the Skill and from that point on you can describe what you want in plain English — the AI will generate complete, ready-to-run afwf code following best practices.

You:    /afwf Write a Script Filter that fuzzy-searches a hardcoded list of bookmarks
Claude: [Skill loaded → generates complete afwf-idiomatic code]

Install

afwf is released on PyPI, so all you need is:

$ pip install afwf

To upgrade to latest version:

$ pip install --upgrade afwf

Optional extras:

# Fuzzy matching (rapidfuzz)
$ pip install "afwf[fuzzy]"

# Disk caching (diskcache)
$ pip install "afwf[cache]"

# Both
$ pip install "afwf[fuzzy,cache]"

Release files for afwf 1.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for afwf 1.0.2
File Size Uploaded
afwf-1.0.2.tar.gz 86.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for afwf 1.0.2
File Interpreter ABI Platform
afwf-1.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 182.5 kB

Release files / afwf-1.0.2.tar.gz

Download URL afwf-1.0.2.tar.gz
Size 86.2 kB
Tags Source
SHA-256 checksum
How to use checksums
5e1a52c3fa22a27ca2efd0097e9b7db6a401d1254f249575e408df6e1c64b502
BLAKE2b-256 checksum
How to use checksums
0dcafa66c3cef6f4233254034d9c96d6673843ce99407fd46f484f82f8100e1d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / afwf-1.0.2-py3-none-any.whl

Download URL afwf-1.0.2-py3-none-any.whl
Size 96.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ab8c5f30f638834d9e47c18370a1e0afb87e001757611e97601eddec0a7895b8
BLAKE2b-256 checksum
How to use checksums
ab782e04f029b0e8602c21f18a7f413e3f9270174ea1bee4ebeac9fac25f81b2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

1.0.2 This release

2 release files

1.0.1

2 release files

0.6.1

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.4.1

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.1.1

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page