alfy
v2.1.0
Published
Create Alfred workflows with ease
Downloads
586
Readme
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
node
binary. - 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
), setLanguage
to/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
alfy
based workflow. If so, you can skip the rest of the steps, go straight to theindex.js
and do your thing.
Set the
Keyword
by 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.js
file, 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);
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.
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": "[email protected]",
"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()
:
import alfy from 'alfy';
const unicorn = getUnicorn();
alfy.log(unicorn);
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
}
);
log(value)
Log value
to the Alfred workflow debugger.
matches(input, list, item?)
Returns an 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.
fetch(url, options?)
Returns a Promise
that returns the body of the response.
url
Type: string
URL to fetch.
options
Type: object
Any of the got
options and the below options.
json
Type: boolean
Default: true
Parse response body with JSON.parse
and set accept
header to application/json
.
maxAge
Type: number
Number of milliseconds this request should be cached.
resolveBodyOnly
Type: boolean
Default: true
Whether to resolve with only body or a full response.
import alfy from 'alfy';
await alfy.fetch('https://api.foo.com');
//=> {foo: 'bar'}
await alfy.fetch('https://api.foo.com', {
resolveBodyOnly: false
});
/*
{
body: {
foo: 'bar'
},
headers: {
'content-type': 'application/json'
}
}
*/
transform
Type: Function
Transform the response body before it gets cached.
import alfy from 'alfy';
await alfy.fetch('https://api.foo.com', {
transform: body => {
body.foo = 'bar';
return body;
}
})
Transform the response.
import alfy from 'alfy';
await alfy.fetch('https://api.foo.com', {
resolveBodyOnly: false,
transform: response => {
response.body.foo = 'bar';
return response;
}
})
You can also return a Promise.
import alfy from 'alfy';
import xml2js from 'xml2js';
import pify from 'pify';
const parseString = pify(xml2js.parseString);
await alfy.fetch('https://api.foo.com', {
transform: body => parseString(body)
})
config
Type: object
Persist config data.
Exports a conf
instance with the correct config path set.
Example:
import alfy from 'alfy';
alfy.config.set('unicorn', '🦄');
alfy.config.get('unicorn');
//=> '🦄'
userConfig
Type: Map
Exports a Map with the user workflow configuration. A workflow configuration allows your users to provide configuration information for the workflow. For instance, if you are developing a GitHub workflow, you could let your users provide their own API tokens.
See alfred-config
for more details.
Example:
import alfy from 'alfy';
alfy.userConfig.get('apiKey');
//=> '16811cad1b8547478b3e53eae2e0f083'
cache
Type: object
Persist cache data.
Exports a modified conf
instance with the correct cache path set.
Example:
import alfy from 'alfy';
alfy.cache.set('unicorn', '🦄');
alfy.cache.get('unicorn');
//=> '🦄'
maxAge
The set
method of this instance accepts an optional third argument where you can provide a maxAge
option. maxAge
is
the number of milliseconds the value is valid in the cache.
Example:
import alfy from 'alfy';
import delay from 'delay';
alfy.cache.set('foo', 'bar', {maxAge: 5000});
alfy.cache.get('foo');
//=> 'bar'
// Wait 5 seconds
await delay(5000);
alfy.cache.get('foo');
//=> undefined
debug
Type: boolean
Whether the user currently has the workflow debugger open.
icon
Type: object
Keys: 'info' | 'warning' | 'error' | 'alert' | 'like' | 'delete'
Get various default system icons.
The most useful ones are included as keys. The rest you can get with icon.get()
. Go to /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources
in Finder to see them all.
Example:
import alfy from 'alfy';
console.log(alfy.icon.error);
//=> '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertStopIcon.icns'
console.log(alfy.icon.get('Clock'));
//=> '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/Clock.icns'
meta
Type: object
Example:
{
name: 'Emoj',
version: '0.2.5',
uid: 'user.workflow.B0AC54EC-601C-479A-9428-01F9FD732959',
bundleId: 'com.sindresorhus.emoj'
}
alfred
Type: object
Alfred metadata.
version
Example: '3.0.2'
Find out which version the user is currently running. This may be useful if your workflow depends on a particular Alfred version's features.
theme
Example: 'alfred.theme.yosemite'
Current theme used.
themeBackground
Example: 'rgba(255,255,255,0.98)'
If you're creating icons on the fly, this allows you to find out the color of the theme background.
themeSelectionBackground
Example: 'rgba(255,255,255,0.98)'
The color of the selected result.
themeSubtext
Example: 3
Find out what subtext mode the user has selected in the Appearance preferences.
Usability note: This is available so developers can tweak the result text based on the user's selected mode, but a workflow's result text should not be bloated unnecessarily based on this, as the main reason users generally hide the subtext is to make Alfred look cleaner.
data
Example: '/Users/sindresorhus/Library/Application Support/Alfred/Workflow Data/com.sindresorhus.npms'
Recommended location for non-volatile data. Just use alfy.data
which uses this path.
cache
Example: '/Users/sindresorhus/Library/Caches/com.runningwithcrayons.Alfred/Workflow Data/com.sindresorhus.npms'
Recommended location for volatile data. Just use alfy.cache
which uses this path.
preferences
Example: '/Users/sindresorhus/Dropbox/Alfred/Alfred.alfredpreferences'
This is the location of the Alfred.alfredpreferences
. If a user has synced their settings, this will allow you to find out where their settings are regardless of sync state.
preferencesLocalHash
Example: 'adbd4f66bc3ae8493832af61a41ee609b20d8705'
Non-synced local preferences are stored within Alfred.alfredpreferences
under …/preferences/local/${preferencesLocalHash}/
.
Users
Alfred workflows using Alfy
- alfred-emoj - Find relevant emoji from text
- alfred-npms - Search for npm packages with npms.io
- alfred-dark-mode - Toggle the system dark mode
- alfred-xcode - Open Xcode projects and workspaces
- alfred-lock - Lock your Mac
- alfred-fkill - Fabulously search and kill processes
- alfred-ng - Search through the Angular documentation on angular.io
- alfred-ionic - Search through the Ionic documentation
- alfred-react-native - Access the React Native documentation
- alfred-hl - Syntax highlight code in the clipboard
- alfred-workflow-docs-elastic - Search the Elastic.co documentation
- alfredinary - Capture screenshots and upload to Cloudinary
- alfred-keycode - Get JavaScript keycodes
- alfred-vue - Search the Vue.js API docs
- alfred-meteor-docs - Search the Meteor docs
- alfred-meteor-packages - Meteor package search
- alfred-climbing-grades-converter - Convert between climbing grading systems
- alfred-hotel - Quickly start, stop and open Hotel apps
- alfred-coolors - Find relevant color names
- alfred-postico-favorites-workflow - Open postico favorites
- alfred-messages - Message your contacts through iMessage
- alfred-bitbucket - List you and your teams public and private Bitbucket repositories
- alfred-asana - Search your Asana tasks
- alfred-cacher - Find a code snippet from Cacher and copy it to the clipboard
- alfred-loremipsum - Generate placeholder text
- alfred-packagist - Search for PHP packages with Packagist
- alfred-vpn - Connect/disconnect from VPNs
- alfred-yandex-translate - Translate words and text with Yandex Translate
- alfred-now - Use Now commands within Alfred to access deployments and aliases
- alfred-chuck-norris-jokes - Get Chuck Norris jokes
- alfred-show-network-info - See network info and discover local devices
- alfred-currency-conversion - See foreign exchange rates and currency conversion
- alfred-polyglot - Translate text with Google Translate
- alfred-stock-price - Show real time stock price in US market
- alfred-jira - Convert clipboard text between Markdown and Jira markup
- alfred-network-location-switch - Switch macOS network location
- alfred-cool - Find cool words
- alfred-google-books - Search for Google Books
- alfred-ip - Find your public IP
- alfred-figma – Quick links and search Figma teams, projects and files
- alfred-flutter-docs – Quickly search and preview Flutter docs
- alfred-title – Capitalize your titles
- alfred-trello - Search your boards, quickly add cards, and view list of cards for Trello
- alfred-npm-versions - Lookup the latest 15 versions for an npm package
- alfred-github-trending - Search trending repositories on GitHub
- alfred-elm - Browse Elm packages documentation
- alfred-imagemin - Minify images with Imagemin
- alfred-evernote-workflow - Search Evernote with keywords and tags
- alfred-deno-doc - Access the Deno documentation
- alfred-fly - Search Google Chrome bookmarks
- alfred-simple-email-fetcher - Show unseen emails from multiple accounts using IMAP
- alfred-chrome-workflow - Search Chrome's bookmarks, history and download logs
- alfred-code - Quickly open a file in Visual Studio Code
- alfred-amphetamine - Start and end sessions quickly in the Amphetamine app
- alfred-ids - Generate various types of IDs.
- alfred-awesome-stars - Search starred GitHub repos through awesome-stars.
- alfred-pwgen - Generate random and secure passwords.
- alfred-bear - Use dynamic templates with the Bear app.
- alfred-color-converter - Convert colors between RGB and Hex.
Related
- alfred-simple - Simple theme for Alfred (Used in the screenshots)
- alfred-updater - Workflow updater
- alfred-notifier - Update notifications for your workflow
- generator-alfred - Scaffold out an Alfred workflow