SuperCmd provides tools to manage your installed extensions, configure preferences, and control which commands are available.
Extension Discovery
SuperCmd discovers installed extensions from multiple sources:
Managed Extensions Directory
The primary location for extensions installed through SuperCmd:
// From extension-runner.ts:111-117
function getManagedExtensionsDir(): string {
const dir = path.join(app.getPath('userData'), 'extensions');
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
return dir;
}
- macOS:
~/Library/Application Support/SuperCmd/extensions/
- Windows:
%APPDATA%/SuperCmd/extensions/
- Linux:
~/.config/SuperCmd/extensions/
Custom Extension Folders
You can add custom extension directories via settings or environment variables:
// From extension-runner.ts:144-160
function getConfiguredExtensionRoots(): string[] {
const settingsPaths = loadSettings().customExtensionFolders;
const envPaths = process.env.SUPERCMD_EXTENSION_PATHS?.split(path.delimiter);
const unique = new Set<string>();
for (const root of [getManagedExtensionsDir(), ...settingsPaths, ...envPaths]) {
const normalized = normalizeFsPath(root);
if (normalized) unique.add(normalized);
}
return [...unique];
}
Set SUPERCMD_EXTENSION_PATHS environment variable to a colon-separated (macOS/Linux) or semicolon-separated (Windows) list of extension directories.
Extension Commands
SuperCmd scans all extension directories and builds a catalog of available commands:
// From extension-runner.ts:281-337
export function discoverInstalledExtensionCommands(): ExtensionCommandInfo[] {
const results: ExtensionCommandInfo[] = [];
for (const source of collectInstalledExtensions()) {
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
// Skip platform-incompatible extensions
if (!isManifestPlatformCompatible(pkg)) continue;
for (const cmd of pkg.commands || []) {
// Skip platform-incompatible commands
if (!isCommandPlatformCompatible(cmd)) continue;
results.push({
id: `ext-${extName}-${cmd.name}`,
title: cmd.title || cmd.name,
extensionTitle: pkg.title || extName,
extName,
cmdName: cmd.name,
description: cmd.description || '',
mode: cmd.mode || 'view',
keywords: [extName, pkg.title, cmd.name, cmd.title, cmd.description]
.filter(Boolean)
.map(s => s.toLowerCase()),
iconDataUrl,
});
}
}
return results;
}
Extension Preferences
Extensions can define preferences at two levels:
Extension-Level Preferences
Shared across all commands in the extension:
{
"preferences": [
{
"name": "apiToken",
"title": "API Token",
"description": "Your GitHub API token",
"type": "password",
"required": true
}
]
}
Command-Level Preferences
Specific to individual commands:
{
"commands": [
{
"name": "search",
"preferences": [
{
"name": "defaultQuery",
"title": "Default Search Query",
"type": "textfield"
}
]
}
]
}
Preference Types
SuperCmd supports all Raycast preference types:
textfield
Single-line text input
password
Secure password field (hidden input)
dropdown
Selection from predefined options
directory
Directory path picker
Reading Preferences
Extensions access preferences via the runtime context:
// From extension-runner.ts:761-851
function parsePreferences(pkg: any, cmdName: string) {
const extensionPrefs: Record<string, any> = {};
const commandPrefs: Record<string, any> = {};
// Extension-level preferences
for (const pref of pkg.preferences || []) {
const resolvedDefault = resolvePlatformDefault(pref.default);
if (resolvedDefault !== undefined) {
extensionPrefs[pref.name] = resolvedDefault;
} else if (pref.type === 'checkbox') {
extensionPrefs[pref.name] = false;
} else if (pref.type === 'textfield' || pref.type === 'password') {
extensionPrefs[pref.name] = '';
}
}
// Command-level preferences
const cmd = (pkg.commands || []).find((c: any) => c.name === cmdName);
if (cmd?.preferences) {
for (const pref of cmd.preferences) {
// Same logic as extension prefs
}
}
return { extensionPrefs, commandPrefs, definitions };
}
Preferences can have different default values per platform:
{
"name": "terminal",
"default": {
"macOS": "/Applications/iTerm.app",
"Windows": "C:\\Windows\\System32\\cmd.exe",
"Linux": "/usr/bin/gnome-terminal"
}
}
// From extension-runner.ts:242-257
function resolvePlatformDefault(value: any): any {
const platformKey = process.platform === 'win32' ? 'Windows' : 'macOS';
if (
value &&
typeof value === 'object' &&
(value.macOS || value.Windows)
) {
return value[platformKey] ?? value.macOS ?? value.Windows;
}
return value;
}
SuperCmd extracts metadata from extension manifests:
// From extension-runner.ts:343-394
export function getInstalledExtensionsSettingsSchema() {
const results: InstalledExtensionSettingsSchema[] = [];
for (const source of collectInstalledExtensions()) {
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
const iconDataUrl = getExtensionIconDataUrl(extPath, pkg.icon || 'icon.png');
const owner = typeof pkg.owner === 'object' ? pkg.owner.name : pkg.owner;
results.push({
extName,
title: pkg.title || extName,
description: pkg.description || '',
owner,
iconDataUrl,
preferences: extensionPreferences,
commands: commandSchemas,
});
}
return results.sort((a, b) => a.title.localeCompare(b.title));
}
Extension Icons
Icons are converted to data URLs for efficient rendering:
// From extension-runner.ts:215-240
function getExtensionIconDataUrl(
extPath: string,
iconFile: string
): string | undefined {
const candidates = [
path.join(extPath, 'assets', iconFile),
path.join(extPath, iconFile),
];
for (const p of candidates) {
if (!fs.existsSync(p)) continue;
const ext = path.extname(p).toLowerCase();
const data = fs.readFileSync(p);
const mime =
ext === '.svg'
? 'image/svg+xml'
: ext === '.jpg' || ext === '.jpeg'
? 'image/jpeg'
: 'image/png';
return `data:${mime};base64,${data.toString('base64')}`;
}
}
Uninstalling Extensions
Removing an extension is straightforward:
// From extension-registry.ts:1029-1044
export async function uninstallExtension(name: string): Promise<boolean> {
const installPath = getInstalledPath(name);
if (!fs.existsSync(installPath)) {
return true; // Already gone
}
try {
fs.rmSync(installPath, { recursive: true, force: true });
console.log(`Extension "${name}" uninstalled.`);
return true;
} catch (error) {
console.error(`Failed to uninstall extension "${name}":`, error);
return false;
}
}
Uninstalling an extension removes all its files, including any stored data. This action cannot be undone.
Extension Support Directory
Each extension has a dedicated support directory for storing data:
// From extension-runner.ts:1104-1110
const supportPath = path.join(
app.getPath('userData'),
'extension-support',
normalizedExtName
);
if (!fs.existsSync(supportPath)) {
fs.mkdirSync(supportPath, { recursive: true });
}
- macOS:
~/Library/Application Support/SuperCmd/extension-support/{extension-name}/
- Windows:
%APPDATA%/SuperCmd/extension-support/{extension-name}/
- Linux:
~/.config/SuperCmd/extension-support/{extension-name}/
Extensions use this directory through the environment.supportPath API to store configuration files, databases, and other persistent data.
Command Arguments
Commands can define arguments that users provide before execution:
// From extension-runner.ts:309-320
commandArgumentDefinitions: Array.isArray(cmd.arguments)
? cmd.arguments
.filter((arg: any) => arg && arg.name)
.map((arg: any) => ({
name: String(arg.name),
required: Boolean(arg.required),
type: arg.type,
placeholder: arg.placeholder,
title: arg.title,
data: Array.isArray(arg.data) ? arg.data : undefined,
}))
: [],
Background Commands
Some commands run in the background on an interval:
// From extension-runner.ts:306-308
mode: cmd.mode || 'view',
interval: typeof cmd.interval === 'string' ? cmd.interval : undefined,
disabledByDefault: Boolean(cmd.disabledByDefault),
Commands with mode: "menu-bar" and an interval property run periodically and update the menu bar.
Checking Installation Status
// From extension-registry.ts:896-901
export function isExtensionInstalled(name: string): boolean {
const p = getInstalledPath(name);
return (
fs.existsSync(p) && fs.existsSync(path.join(p, 'package.json'))
);
}
// From extension-registry.ts:903-915
export function getInstalledExtensionNames(): string[] {
return fs.readdirSync(getExtensionsDir()).filter((d) => {
const p = getInstalledPath(d);
return (
fs.statSync(p).isDirectory() &&
fs.existsSync(path.join(p, 'package.json'))
);
});
}
Next Steps
Overview
Learn how extensions work under the hood
Compatibility
Check which Raycast APIs are supported