Skip to main content

Build Alfred Script Filter JSON payloads from paths, CSV, JSON, or strings. CLI and Python API included.

Project description

alfred-results ๐ŸŽฉ

Turn paths, CSV rows, JSON objects, or plain strings into Alfred Script Filter JSON, without writing a single line of boilerplate.

Use it as a CLI tool (pipe data in, get Alfred JSON out) or as a Python library (build result items programmatically and compose them however you like). No runtime dependencies. Python โ‰ฅ 3.12.

๐Ÿคทโ€โ™‚๏ธ Why?

If youโ€™ve built Alfred workflows, you know generating Script Filter JSON usually means brittle jq one-liners or hand-rolled dictionaries. alfred-results lets you generate correct, type-safe payloads from Python or the CLI, without boilerplate or guesswork.


Installation

pip install alfred-results

Or with uv:

uv add alfred-results

๐Ÿ› ๏ธ CLI only (no project required)

If you just want the alfred-results command globally without adding it as a project dependency:

uv tool install alfred-results

๐Ÿ“ฆ Bundling with a workflow (no install required)

Copy the alfred_results/ package directory into your workflow bundle and add a run.py entry point at the workflow root:

#!/usr/bin/env python3
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))

from alfred_results.cli import main

sys.exit(main())

Then call it from Alfred's Run Script object:

python3 run.py

CLI usage ๐Ÿ–ฅ๏ธ

The CLI reads input from stdin or a file and writes Alfred Script Filter JSON to stdout. Drop it into any Alfred workflow Run Script object.

usage: alfred-results [-h] [-f FORMAT] [-m MOD ARG SUBTITLE]
                      [--result-var KEY VALUE] [--session-var KEY VALUE]
                      [--version]
                      [FILE]

Input formats (-f / --input-format)

Format Input shape Use case
path (default) One filesystem path per line find, mdfind, ls pipelines
csv CSV with a header row (title required) Spreadsheets, exported data
json JSON array of objects (title required) jq, gh, brew info --json
string One arbitrary string per line Labels, commands, bookmarks

๐Ÿ“ path format (default)

One path per line, piped in or from a file. Each path is expanded, resolved, and converted into a fully-populated result item automatically.

# Pipe from find
find ~/Projects -maxdepth 1 -type d | alfred-results

# Pass a file
alfred-results paths.txt

# mdfind is a great source too
mdfind -onlyin ~ "kind:pdf" | alfred-results

Example output:

{
  "variables": {
    "script": "alfred-results"
  },
  "items": [
    {
      "title": "my-project",
      "uid": "a1b2c3d4-...",
      "subtitle": "/Users/me/Projects/my-project",
      "arg": "/Users/me/Projects/my-project",
      "type": "default",
      "icon": {"type": "fileicon", "path": "/Users/me/Projects/my-project"},
      "variables": {
        "_path": "/Users/me/Projects/my-project",
        "_parent": "/Users/me/Projects"
      }
    }
  ]
}

๐Ÿ’ก _path and _parent are always injected as item-scoped variables so downstream workflow objects can reference them without extra wiring.


๐Ÿ“Š csv format

Pass a CSV file with a header row. title is the only required column. Everything else is optional and unknown columns are ignored.

title,subtitle,arg,type,icon,uid
Downloads,Your downloads folder,/Users/me/Downloads,default,,
report.pdf,Q4 financials,/Users/me/report.pdf,file,,
alfred-results --input-format csv data.csv

Supported columns:

Column Required Description
title โœ… Primary text shown in Alfred
subtitle โ€” Secondary text below the title
arg โ€” Argument passed to the next workflow action
uid โ€” Stable identifier for Alfred's ordering learning
type โ€” default, file, or file:skipcheck
icon โ€” Path to an icon file

๐Ÿ’ก Any extra columns you include (e.g. url, id) are ignored by the item builder but are available as lookup keys for --result-var and --mod. See Per-result variables and Modifier key overrides.


๐Ÿ”ง json format

Pass a JSON array of objects, perfect for piping output from tools that already speak JSON.

# From a file
alfred-results --input-format json data.json

# Pipe from jq
curl -s https://api.example.com/items | jq '[.[] | {title: .name, arg: .url}]' \
  | alfred-results --input-format json

# List GitHub repos with alfred-results
gh repo list --json name,url --jq '[.[] | {title: .name, subtitle: .url, arg: .url}]' \
  | alfred-results --input-format json

# Installed Homebrew formulae
brew info --json=v2 --installed \
  | jq '[.formulae[] | {title: .name, subtitle: .desc, arg: .name}]' \
  | alfred-results --input-format json

Supported keys: same as CSV: title (required), subtitle, arg, uid, type, icon. Additional keys are silently ignored by the item builder but remain available as lookup keys for --result-var and --mod.


๐Ÿ”ค string format

One string per line becomes the title of a plain result item with no path metadata. Great for lists of commands, bookmarks, or anything title-only.

printf "Open Safari\nOpen Terminal\nOpen Finder" | alfred-results --input-format string

alfred-results --input-format string bookmarks.txt

๐ŸŽ›๏ธ Modifier key overrides (--mod)

Add actions for when the user holds a modifier key while highlighting a result. Each --mod takes three arguments: the key combo, the arg, and the subtitle. Repeat it for multiple modifiers.

The arg is resolved per item using the same lookup strategy as --result-var:

  • path: tried as a pathlib.Path attribute first (e.g. name, stem, suffix, parent); falls back to the raw string if the attribute doesn't exist.
  • csv / json: looked up as a column or key name in the current row first; falls back to the raw string if the key isn't present.
  • string: the raw string is always used.
# path format: use Path attributes as the mod arg
mdfind -onlyin ~ "kind:pdf" \
  | alfred-results \
    --mod cmd  name   "Open in Preview" \
    --mod alt  parent "Reveal folder in Finder"

The cmd mod arg resolves to the filename (e.g. report.pdf) and the alt mod arg resolves to the parent directory path, each evaluated per file.

# csv/json format: resolve mod arg from a row column
alfred-results --input-format csv bookmarks.csv \
    --mod cmd url "Open in browser" \
    --mod alt id  "Copy ID"

Given a CSV with title, url, and id columns, the cmd mod arg becomes each row's url value and alt becomes each row's id value, resolved independently per row.

# raw string fallback: Alfred template variables pass through unchanged
find ~/Projects -maxdepth 1 -type d \
  | alfred-results \
    --mod cmd "{query}" "Search inside"

{query} is not a valid Path attribute so it passes through as-is, letting Alfred substitute its value at runtime.

Valid modifier keys: cmd, alt, ctrl, shift, fn, and any combination of up to three joined with + (e.g. cmd+shift, alt+ctrl+fn).


๐ŸŒ Session variables (--session-var)

Set top-level Alfred session variables included in the payload's variables object. These are available to all downstream workflow objects regardless of which item the user picks.

echo "/tmp/foo" | alfred-results \
  --session-var source "file-browser" \
  --session-var mode "open"
{
  "variables": {
    "script": "alfred-results",
    "source": "file-browser",
    "mode": "open"
  },
  "items": [...]
}

๐Ÿ“Œ Per-result variables (--result-var)

Attach item-scoped variables to every result. The value is resolved per item before being set:

  • path: _path and _parent are always injected automatically; --result-var adds to them. The value is first tried as a pathlib.Path attribute name (e.g. suffix, stem, parent, name); if the attribute doesn't exist the raw string is used.
  • csv / json: the value is first looked up as a column or key name in the current row; if the key isn't present the raw string is used.
  • string: the raw string is always used.

path format example: extract Path attributes as variables:

mdfind -onlyin ~ "kind:pdf" | alfred-results \
  --result-var file_name   name \
  --result-var file_ext    suffix \
  --result-var file_stem   stem \
  --result-var file_dir    parent

Produces on each item:

{
  "file_name": "report.pdf",
  "file_ext":  ".pdf",
  "file_stem": "report",
  "file_dir":  "/Users/me/Documents"
}

csv / json format example: pull row values into variables:

# Given data.csv:
# title,url,category
# GitHub,https://github.com,dev
# Linear,https://linear.app,project

alfred-results --input-format csv data.csv \
  --result-var link     url \
  --result-var tag      category \
  --result-var source   bookmarks

link and tag are resolved from the current row's url and category columns. source has no matching column so the raw string "bookmarks" is used. Each item gets:

{"link": "https://github.com", "tag": "dev",     "source": "bookmarks"}
{"link": "https://linear.app",  "tag": "project", "source": "bookmarks"}

Python API ๐Ÿ

Quick start

from alfred_results import ScriptFilterPayload
from alfred_results.result_item import ResultItem

items = [
    ResultItem.from_path("/Users/me/Downloads"),
    ResultItem.from_path("/Users/me/Documents"),
]

payload = ScriptFilterPayload(items=items)
print(payload.to_json())

That's it. to_json() produces the complete Alfred Script Filter JSON string, ready to print to stdout.


ResultItem.from_path() โœจ

The fastest way to build a result item from a filesystem path. Automatically:

  • Expands ~ and resolves symlinks
  • Derives a stable UUID uid so Alfred can learn your preferences
  • Sets title (filename), subtitle (full path), arg (full path), icon (native Finder icon), and type
  • Injects _path and _parent as item-scoped variables
from alfred_results.result_item import ResultItem

item = ResultItem.from_path("/Users/me/Downloads")
print(item.to_dict())
{
  "title": "Downloads",
  "uid": "a696dbaa-...",
  "subtitle": "/Users/me/Downloads",
  "arg": "/Users/me/Downloads",
  "type": "default",
  "icon": {"type": "fileicon", "path": "/Users/me/Downloads"},
  "variables": {
    "_path": "/Users/me/Downloads",
    "_parent": "/Users/me"
  }
}

Pass mods and variables to extend it:

from alfred_results.result_item import Mod, ResultItem

item = ResultItem.from_path(
    "/Users/me/report.pdf",
    mods=[
        Mod(key="cmd", valid=True, arg="/Users/me/report.pdf", subtitle="Open in Preview"),
        Mod(key="alt", valid=True, subtitle="Reveal in Finder"),
    ],
    variables={"category": "reports"},
)

Building items manually ๐Ÿ”ฉ

For full control over every field, construct ResultItem directly:

from alfred_results.result_item import Icon, IconResourceType, ItemType, ResultItem
from alfred_results import path_to_uuid

path = "/Users/me/Downloads"

item = ResultItem(
    uid=path_to_uuid(path),
    title="Downloads",
    subtitle="Your downloads folder",
    arg=path,
    type=ItemType.FILE,
    icon=Icon(path=path, resource_type=IconResourceType.FILEICON),
    valid=True,
    autocomplete="Downloads",
    quicklookurl=path,
    variables={"folder": "downloads"},
)

Only title is required; every other field is optional and omitted from the JSON if not set.


Icons ๐Ÿ–ผ๏ธ

from alfred_results.result_item import Icon, IconResourceType

# Native Finder icon of the file or folder at this path
Icon(path="/Users/me/Downloads", resource_type=IconResourceType.FILEICON)
# โ†’ {"type": "fileicon", "path": "/Users/me/Downloads"}

# System icon for a Uniform Type Identifier (UTI)
Icon(path="com.adobe.pdf", resource_type=IconResourceType.FILETYPE)
# โ†’ {"type": "filetype", "path": "com.adobe.pdf"}

# Custom image bundled with the workflow (relative to the workflow root)
Icon(path="./icons/star.png")
# โ†’ {"path": "./icons/star.png"}

# No icon at all; the key is omitted from the JSON entirely
Icon()
# โ†’ None (omitted)

๐ŸŽ See Apple's UTI reference or this community UTI list for common type identifiers.


Modifier key overrides ๐ŸŽน

from alfred_results.result_item import Mod, ResultItem

item = ResultItem(
    title="report.pdf",
    arg="/Users/me/report.pdf",
    mods=[
        # cmd โ†’ open in Preview
        Mod(key="cmd", valid=True, arg="/Users/me/report.pdf", subtitle="Open in Preview"),
        # alt โ†’ reveal in Finder
        Mod(key="alt", valid=True, arg="/Users/me/report.pdf", subtitle="Reveal in Finder"),
        # cmd+shift โ†’ disabled with a message
        Mod(key="cmd+shift", valid=False, subtitle="Not available right now"),
    ],
)
{
  "title": "report.pdf",
  "arg": "/Users/me/report.pdf",
  "mods": {
    "cmd":       {"valid": true,  "arg": "/Users/me/report.pdf", "subtitle": "Open in Preview"},
    "alt":       {"valid": true,  "arg": "/Users/me/report.pdf", "subtitle": "Reveal in Finder"},
    "cmd+shift": {"valid": false, "subtitle": "Not available right now"}
  }
}

Valid modifier keys: cmd, alt, ctrl, shift, fn, any 1โ€“3 key ordered combination joined with +.


The full payload ๐Ÿ“ฆ

ScriptFilterPayload wraps your items into the complete top-level Alfred Script Filter response:

from alfred_results import ScriptFilterCache, ScriptFilterPayload
from alfred_results.result_item import ResultItem

payload = ScriptFilterPayload(
    items=[
        ResultItem.from_path("/Users/me/Downloads"),
        ResultItem.from_path("/Users/me/Documents"),
    ],
    variables={"workflow": "file-browser"},
    rerun=1.0,                                      # re-run every second
    skipknowledge=True,                             # don't reorder by usage
    cache=ScriptFilterCache(seconds=30, loosereload=True),
)

# Print compact JSON for Alfred
print(payload.to_json())

# Pretty-print for debugging
print(payload.to_json(indent=2))

# Get a plain Python dict if you need to inspect or extend it
data = payload.to_dict()
{
  "cache": {"seconds": 30, "loosereload": true},
  "rerun": 1.0,
  "skipknowledge": true,
  "variables": {
    "script": "alfred-results",
    "workflow": "file-browser"
  },
  "items": [...]
}

๐Ÿ’ก script is always injected into variables automatically so downstream workflow objects always know what generated the payload. User-supplied variables win on collision.


Info payloads ๐Ÿ’ฌ

ScriptFilterPayload.info() builds a single-item payload containing a non-actionable result (valid=False). Use it to communicate a status or message to the user through Alfred's result list โ€” "No results found", "Connection failed", or anything similar.

from alfred_results import ScriptFilterPayload

# Title only
payload = ScriptFilterPayload.info("No results found")
print(payload.to_json())
{
  "variables": {"script": "alfred-results"},
  "items": [
    {"title": "No results found", "valid": false}
  ]
}

Add an optional subtitle for more context:

payload = ScriptFilterPayload.info(
    "Connection failed",
    "Check your network and try again",
)

Pass a custom icon with the keyword-only icon argument:

from alfred_results import ScriptFilterPayload
from alfred_results.result_item import Icon

payload = ScriptFilterPayload.info(
    "API error",
    "Received HTTP 500",
    icon=Icon(path="./icons/error.png"),
)

๐Ÿ’ก An empty subtitle (the default) is omitted from the JSON entirely. Only pass it when you have something useful to say.


Caching โšก

ScriptFilterCache controls how Alfred caches results between script invocations:

from alfred_results import ScriptFilterCache

# Cache for 5 minutes, show stale results immediately while reloading
ScriptFilterCache(seconds=300, loosereload=True)

# Cache for 1 hour, wait for fresh results before showing anything
ScriptFilterCache(seconds=3600)

seconds must be between 5 and 86400 (24 hours). loosereload=True tells Alfred to show the cached results immediately while re-running the script in the background, which is great for slow data sources.


Stable UIDs with path_to_uuid ๐Ÿ”‘

Alfred uses the uid field to learn from your selection history and reorder results over time. path_to_uuid derives a deterministic UUID v5 from a resolved path so the same path always produces the same uid, no database required.

from pathlib import Path
from alfred_results import path_to_uuid

uid = path_to_uuid(str(Path("~/Downloads").expanduser().resolve()))
# โ†’ "a696dbaa-739b-5781-8e08-7e38648f678a"  (stable across runs)

ResultItem.from_path() calls this automatically. You only need it when building items manually.


Validating modifier keys ๐Ÿ›ก๏ธ

Check whether a key combo is valid before constructing a Mod:

from alfred_results.result_item import VALID_MODIFIER_KEYS, valid_modifiers

# The five base keys
print(VALID_MODIFIER_KEYS)
# ("cmd", "alt", "ctrl", "shift", "fn")

# All valid single, double, and triple combos
combos = valid_modifiers()
print("cmd+alt" in combos)   # True
print("cmd+cmd" in combos)   # False

Reference ๐Ÿ“–

ResultItem fields

Field Type Required Description
title str โœ… Primary text shown in the result row
subtitle str โ€” Secondary text below the title
uid str โ€” Stable identifier (Alfred learns ordering from this)
arg str | Sequence[str] โ€” Argument passed to the next workflow action
valid bool โ€” False makes the item non-actionable
autocomplete str โ€” Text inserted into Alfred's search field on Tab
match str โ€” Custom string for Alfred's built-in filtering
type ItemType โ€” DEFAULT, FILE, or FILE_SKIPCHECK
icon Icon โ€” Icon displayed beside the result row
mods list[Mod] โ€” Modifier key overrides (unique keys required)
action str | list | dict โ€” Universal Actions payload
text Mapping[str, str] โ€” "copy" and "largetype" overrides
quicklookurl str โ€” URL or path for Quick Look (Shift / Cmd+Y)
variables Mapping[str, str] โ€” Item-scoped Alfred session variables

ScriptFilterPayload fields

Field Type Required Description
items list[ResultItem] โ€” Result items to display in Alfred
variables Mapping[str, str] โ€” Top-level session variables
rerun float โ€” Re-run interval in seconds (0.1โ€“5.0)
skipknowledge bool โ€” Skip Alfred's learned ordering for this response
cache ScriptFilterCache โ€” Caching configuration

Factory classmethods:

Method Signature Description
info (title, subtitle="", *, icon=None) Single non-actionable item payload for messages and status

ItemType values

Value Alfred JSON Description
ItemType.DEFAULT "default" Standard result (no filesystem check)
ItemType.FILE "file" Alfred checks the path exists before showing
ItemType.FILE_SKIPCHECK "file:skipcheck" Treated as file but existence check skipped

IconResourceType values

Value Alfred JSON Description
IconResourceType.FILEICON "fileicon" Native Finder icon of the file at path
IconResourceType.FILETYPE "filetype" System icon for the UTI given in path

License

MIT. See LICENSE for details.

Project details


Download files

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

Source Distribution

alfred_results-0.1.0.tar.gz (25.0 kB view details)

Uploaded Source

Built Distribution

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

alfred_results-0.1.0-py3-none-any.whl (31.4 kB view details)

Uploaded Python 3

File details

Details for the file alfred_results-0.1.0.tar.gz.

File metadata

  • Download URL: alfred_results-0.1.0.tar.gz
  • Upload date:
  • Size: 25.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","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}

File hashes

Hashes for alfred_results-0.1.0.tar.gz
Algorithm Hash digest
SHA256 78c3b4d796ac16cc1cc51ad02b40fc27a7d5590230df8d0f322f091b75c2da09
MD5 915adc61bdf47fba09c5664ef4bf5445
BLAKE2b-256 c6668c3eb5e20e81ee6c5081c06dab2e0f99e3c413da6128d5950d31c7937c31

See more details on using hashes here.

File details

Details for the file alfred_results-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: alfred_results-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","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}

File hashes

Hashes for alfred_results-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d74f0685607db39fffef7e9956c12f2280c7004058c9c0b65b9d6a287dbe7e9e
MD5 9fd7d2d8ec7c310853b367a93d4ca22d
BLAKE2b-256 1d88957c48bf06d8eedd59e82c02d434e849359e3d0cd6b1f255792f9981b973

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