Alfy
Create Alfred workflows with ease
Install / Use
/learn @sindresorhus/AlfyREADME
Create Alfred workflows with ease
Highlights
- Easy input↔output.
- Config and cache handling built-in.
- Fetching remote files with optional caching.
- Publish your workflow to npm.
- Automatic update notifications.
- Easily testable workflows.
- Finds the
nodebinary. - Support for top-level
await. - Presents uncaught exceptions and unhandled Promise rejections to the user.
No need to manually.catch()top-level promises.
Prerequisites
You need Node.js 18+ and Alfred 4 or later with the paid Powerpack upgrade.
Install
npm install alfy
Usage
IMPORTANT: Your script will be run as ESM.
-
Create a new blank Alfred workflow.
-
Add a
Script Filter(right-click the canvas →Inputs→Script Filter), setLanguageto/bin/bash, and add the following script:
./node_modules/.bin/run-node index.js "$1"
We can't call node directly as GUI apps on macOS doesn't inherit the $PATH.
Tip: You can use generator-alfred to scaffold out an
alfybased workflow. If so, you can skip the rest of the steps, go straight to theindex.jsand do your thing.
-
Set the
Keywordby which you want to invoke your workflow. -
Go to your new workflow directory (right-click on the workflow in the sidebar →
Open in Finder). -
Initialize a repo with
npm init. -
Add
"type": "module"to package.json. -
Install Alfy with
npm install alfy. -
In the workflow directory, create a
index.jsfile, importalfy, and do your thing.
Example
Here we fetch some JSON from a placeholder API and present matching items to the user:
import alfy from 'alfy';
const data = await alfy.fetch('https://jsonplaceholder.typicode.com/posts');
const items = alfy
.inputMatches(data, 'title')
.map(element => ({
title: element.title,
subtitle: element.body,
arg: element.id
}));
alfy.output(items);
<img src="media/screenshot.png" width="694">
More
Some example usage in the wild: alfred-npms, alfred-emoj, alfred-ng.
Update notifications
Alfy uses alfred-notifier in the background to show a notification when an update for your workflow is available.
<img src="media/screenshot-update.png" width="694">Caching
Alfy offers the possibility of caching data, either with the fetch or directly through the cache object.
An important thing to note is that the cached data gets invalidated automatically when you update your workflow. This offers the flexibility for developers to change the structure of the cached data between workflows without having to worry about invalid older data.
Publish to npm
By adding alfy-init as postinstall and alfy-cleanup as preuninstall script, you can publish your package to npm instead of to Packal. This way, your packages are only one simple npm install command away.
{
"name": "alfred-unicorn",
"version": "1.0.0",
"description": "My awesome unicorn workflow",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"scripts": {
"postinstall": "alfy-init",
"preuninstall": "alfy-cleanup"
},
"dependencies": {
"alfy": "*"
}
}
Tip: Prefix your workflow with
alfred-to make them easy searchable through npm.
You can remove these properties from your info.plist file as they are being added automatically at install time.
After publishing your workflow to npm, your users can easily install or update the workflow.
npm install --global alfred-unicorn
Tip: instead of manually updating every workflow yourself, use the alfred-updater workflow to do that for you.
Testing
Workflows can easily be tested with alfy-test. Here is a small example.
import test from 'ava';
import alfyTest from 'alfy-test';
test('main', async t => {
const alfy = alfyTest();
const result = await alfy('workflow input');
t.deepEqual(result, [
{
title: 'foo',
subtitle: 'bar'
}
]);
});
Debugging
When developing your workflow it can be useful to be able to debug it when something is not working. This is when the workflow debugger comes in handy. You can find it in your workflow view in Alfred. Press the insect icon to open it. It will show you the plain text output of alfy.output() and anything you log with alfy.log() or console.error():
import alfy from 'alfy';
const unicorn = getUnicorn();
alfy.log(unicorn);
// You can also use console.error() for debugging
console.error('Debug info:', {data: someData});
Environment variables
Alfred lets users set environment variables for a workflow which can then be used by that workflow. This can be useful if you, for example, need the user to specify an API token for a service. You can access the workflow environment variables from process.env. For example process.env.apiToken.
API
alfy
input
Type: string
Input from Alfred. What the user wrote in the input box.
output(list, options?)
Return output to Alfred.
list
Type: object[]
List of object with any of the supported properties.
Example:
import alfy from 'alfy';
alfy.output([
{
title: 'Unicorn'
},
{
title: 'Rainbow'
}
]);
options
Type: object
rerunInterval
Type: number (seconds)
Values: 0.1...5.0
A script can be set to re-run automatically after some interval. The script will only be re-run if the script filter is still active and the user hasn't changed the state of the filter by typing and triggering a re-run. More info.
For example, it could be used to update the progress of a particular task:
import alfy from 'alfy';
alfy.output(
[
{
title: 'Downloading Unicorns…',
subtitle: `${progress}%`,
}
],
{
// Re-run and update progress every 3 seconds.
rerunInterval: 3
}
);
<img src="media/screenshot-output.png" width="694">
variables
Type: object
Variables passed out of the script filter and accessible throughout the current session as environment variables. These are session-level variables that persist for the duration of the user action. Individual items can also have their own variables which override these when selected.
import alfy from 'alfy';
alfy.output(
[
{
title: 'Unicorn',
arg: 'unicorn',
}
],
{
variables: {animal: 'unicorn'}
}
);
log(value)
Log value to the Alfred workflow debugger.
matches(input, list, item?)
Returns a string[] of items in list that case-insensitively contains input.
import alfy from 'alfy';
alfy.matches('Corn', ['foo', 'unicorn']);
//=> ['unicorn']
input
Type: string
Text to match against the list items.
list
Type: string[]
List to be matched against.
item
Type: string | Function
By default, it will match against the list items.
Specify a string to match against an object property:
import alfy from 'alfy';
const list = [
{
title: 'foo'
},
{
title: 'unicorn'
}
];
alfy.matches('Unicorn', list, 'title');
//=> [{title: 'unicorn'}]
Or nested property:
import alfy from 'alfy';
const list = [
{
name: {
first: 'John',
last: 'Doe'
}
},
{
name: {
first: 'Sindre',
last: 'Sorhus'
}
}
];
alfy.matches('sindre', list, 'name.first');
//=> [{name: {first: 'Sindre', last: 'Sorhus'}}]
Specify a function to handle the matching yourself. The function receives the list item and input, both lowercased, as arguments, and is expected to return a boolean of whether it matches:
import alfy from 'alfy';
const list = ['foo', 'unicorn'];
// Here we do an exact match.
// `Foo` matches the item since it's lowercased for you.
alfy.matches('Foo', list, (item, input) => item === input);
//=> ['foo']
inputMatches(list, item?)
Same as matches(), but with alfy.input as input.
If you want to match against multiple items, you must define your own matching function (as shown here). Let’s extend the example from the beginning to search for a keyword that appears either within the title or body property or both.
import alfy from 'alfy';
const data = await alfy.fetch('https://jsonplaceholder.typicode.com/posts');
const items = alfy
.inputMatches(
data,
(item, input) =>
item.title?.toLowerCase().includes(input) ||
item.body?.toLowerCase().includes(input)
)
.map((element) => ({
title: element.title,
subtitle: element.body,
arg: element.id,
}));
alfy.output(items);
error(error)
Display an error or error message in Alfred.
Note: You don't need to .catch() top-level promises. Alfy handles that for you.
error
Type: Error | string
Error or error message to be displayed.
<img src="media/screenshot-error.png" width="694">fetch(url, options?)
Related Skills
node-connect
351.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
110.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
351.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
351.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
