Alfy is a comprehensive Node.js toolkit for creating Alfred workflows with ease. It provides a simplified API for handling input/output operations, automatic configuration and caching management, built-in HTTP request handling with optional caching, seamless integration with Alfred's Script Filter JSON format, automatic update notifications, extensive debugging capabilities, and utilities for string matching, error handling, and system icon access.
npm install alfyimport alfy from "alfy";Note: Alfy is an ESM module and requires Node.js >=18. CommonJS require() is not supported.
Optional configuration in package.json to control Alfy behavior:
{
"alfy": {
"updateNotification": false
}
}updateNotification: Set to false to disable automatic update notifications (default: true)import alfy from "alfy";
// Fetch data with automatic caching
const data = await alfy.fetch('https://jsonplaceholder.typicode.com/posts');
// Filter based on user input
const items = alfy
.inputMatches(data, 'title')
.map(element => ({
title: element.title,
subtitle: element.body,
arg: element.id
}));
// Output results to Alfred
alfy.output(items);Alfy is designed around several key components:
Command-line utilities for workflow development and management.
// Binaries provided by the package
run-node: "./run-node.sh";
"alfy-init": "./init.js";
"alfy-cleanup": "./cleanup.js";Available CLI tools:
run-node: Bash wrapper script that ensures Node.js is available in Alfred's environment by inheriting the user's PATHalfy-init: Runs alfred-link and alfred-config to set up workflow linking and configurationalfy-cleanup: Runs alfred-unlink to remove workflow symlinks and clean upCore functionality for handling Alfred input and presenting results to users. Includes input parsing, result formatting, and Alfred Script Filter JSON generation.
// User input from Alfred
input: string;
// Return results to Alfred
function output(items: ScriptFilterItem[], options?: OutputOptions): void;Built-in HTTP client with automatic caching, transformation support, and error handling for fetching remote data.
function fetch(url: string, options?: FetchOptions): Promise<unknown>;Flexible string matching utilities for filtering data based on user input, with support for object properties and custom matching functions.
function matches<T>(input: string, list: T[], target?: string | MatchFunction<T>): T[];
function inputMatches<T>(list: T[], target?: string | MatchFunction<T>): T[];Persistent configuration and caching system with automatic cleanup and workflow-specific data management.
// Workflow configuration
config: Conf;
// User workflow configuration
userConfig: Map<string, string>;
// Cached data with TTL support
cache: CacheConf;Direct integration with Alfred's environment, including metadata access, system icons, and debugging support.
// Workflow metadata
meta: WorkflowMeta;
// Alfred environment data
alfred: AlfredEnvironment;
// System icons
icon: IconUtilities;
// Debug mode detection
debug: boolean;Built-in error handling with user-friendly display and debugging utilities for workflow development.
function log(text: string): void;
function error(error: Error | string): void;interface OutputOptions {
rerunInterval?: number;
}
interface FetchOptions extends Partial<GotOptions> {
maxAge?: number;
transform?: (body: unknown) => unknown;
json?: boolean;
resolveBodyOnly?: boolean;
}
interface ScriptFilterItem {
uid?: string;
title: string;
subtitle?: string;
arg?: string;
icon?: IconElement | string;
valid?: boolean;
match?: string;
autocomplete?: string;
type?: 'default' | 'file' | 'file:skipcheck';
mods?: Partial<Record<ModifierKey, ModifierKeyItem>>;
text?: TextElement;
quicklookurl?: string;
variables?: Record<string, string>;
}
interface IconElement {
path?: string;
type?: 'fileicon' | 'filetype';
}
interface TextElement {
copy?: string;
largetype?: string;
}
interface ModifierKeyItem {
valid?: boolean;
title?: string;
subtitle?: string;
arg?: string;
icon?: string;
variables?: Record<string, string>;
}
type ModifierKey = 'fn' | 'ctrl' | 'opt' | 'cmd' | 'shift';
type MatchFunction<T> = (item: T, input: string) => boolean;
interface ActionElement {
/** Forward text to Alfred */
text?: string | string[];
/** Forward URL to Alfred */
url?: string | string[];
/** Forward file path to Alfred */
file?: string | string[];
/** Forward some value and let the value type be inferred from Alfred */
auto?: string | string[];
}
interface WorkflowMeta {
name: string;
version: string;
uid: string;
bundleId: string;
}
interface AlfredEnvironment {
version: string;
theme: string;
themeBackground: string;
themeSelectionBackground: string;
themeSubtext: string;
data: string;
cache: string;
preferences: string;
preferencesLocalHash: string;
}
interface IconUtilities {
get(iconName: string): string;
info: string;
warning: string;
error: string;
alert: string;
like: string;
delete: string;
}
interface CacheConf<T extends Record<string, any> = Record<string, unknown>> extends Conf<T> {
isExpired(key: keyof T): boolean;
get<Key extends keyof T>(key: Key, options?: CacheGetOptions): T[Key];
get<Key extends keyof T>(key: Key, defaultValue: Required<T>[Key], options?: CacheGetOptions): Required<T>[Key];
get<Key extends string, Value = unknown>(key: Exclude<Key, keyof T>, defaultValue?: Value, options?: CacheGetOptions): Value;
get(key: string, defaultValue?: unknown, options?: CacheGetOptions): unknown;
set<Key extends keyof T>(key: Key, value?: T[Key], options?: CacheSetOptions): void;
set(key: string, value: unknown, options: CacheSetOptions): void;
set(object: Partial<T>, options: CacheSetOptions): void;
set<Key extends keyof T>(key: Partial<T> | Key | string, value?: T[Key] | unknown, options?: CacheSetOptions): void;
}
interface CacheGetOptions {
ignoreMaxAge?: boolean;
}
interface CacheSetOptions {
maxAge?: number;
}
// External types from dependencies
interface Conf<T = Record<string, unknown>> {
get<K extends keyof T>(key: K): T[K];
get<K extends keyof T>(key: K, defaultValue: T[K]): T[K];
get(key: string): unknown;
get(key: string, defaultValue: unknown): unknown;
set<K extends keyof T>(key: K, value: T[K]): void;
set(key: string, value: unknown): void;
has(key: string): boolean;
delete(key: string): void;
clear(): void;
size: number;
[Symbol.iterator](): IterableIterator<[string, unknown]>;
keys(): IterableIterator<string>;
}
interface GotOptions {
headers?: Record<string, string>;
timeout?: { request?: number };
retry?: { limit?: number };
method?: string;
json?: unknown;
responseType?: string;
resolveBodyOnly?: boolean;
// ... other got options from the got library
}