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"
}
}
]
}
๐ก
_pathand_parentare 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-varand--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 apathlib.Pathattribute 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:_pathand_parentare always injected automatically;--result-varadds to them. The value is first tried as apathlib.Pathattribute 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
uidso Alfred can learn your preferences - Sets
title(filename),subtitle(full path),arg(full path),icon(native Finder icon), andtype - Injects
_pathand_parentas 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": [...]
}
๐ก
scriptis always injected intovariablesautomatically 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78c3b4d796ac16cc1cc51ad02b40fc27a7d5590230df8d0f322f091b75c2da09
|
|
| MD5 |
915adc61bdf47fba09c5664ef4bf5445
|
|
| BLAKE2b-256 |
c6668c3eb5e20e81ee6c5081c06dab2e0f99e3c413da6128d5950d31c7937c31
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d74f0685607db39fffef7e9956c12f2280c7004058c9c0b65b9d6a287dbe7e9e
|
|
| MD5 |
9fd7d2d8ec7c310853b367a93d4ca22d
|
|
| BLAKE2b-256 |
1d88957c48bf06d8eedd59e82c02d434e849359e3d0cd6b1f255792f9981b973
|