raycast-extension by johnlindquist/claude
npx skills add https://github.com/johnlindquist/claude --skill raycast-extensionnpm install && npm run devmy-extension/
├── package.json # 扩展清单 + 依赖项
├── tsconfig.json # TypeScript 配置
├── .eslintrc.json # ESLint 配置
├── raycast-env.d.ts # 类型定义(自动生成)
├── assets/
│ └── extension-icon.png # 512x512 PNG 图标
└── src/
└── command-name.tsx # 命令实现
{
"name": "extension-name",
"title": "扩展标题",
"description": "此扩展的功能描述",
"icon": "extension-icon.png",
"author": "作者名称",
"categories": ["效率", "开发者工具"],
"license": "MIT",
"commands": [
{
"name": "command-name",
"title": "命令标题",
"description": "此命令的功能描述",
"mode": "view",
"keywords": ["keyword1", "keyword2"]
}
],
"dependencies": {
"@raycast/api": "^1.83.1",
"@raycast/utils": "^1.17.0"
},
"devDependencies": {
"@raycast/eslint-config": "^1.0.11",
"@types/node": "22.5.4",
"@types/react": "18.3.3",
"eslint": "^8.57.0",
"prettier": "^3.3.3",
"typescript": "^5.5.4"
},
"scripts": {
"build": "ray build --skip-types -e dist -o dist",
"dev": "ray develop",
"fix-lint": "ray lint --fix",
"lint": "ray lint"
}
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 模式 | 使用场景 |
|---|---|
view | 显示包含详情、列表、表单、网格的用户界面 |
no-view | 仅后台任务、剪贴板、通知 |
menu-bar | 带下拉菜单的菜单栏图标 |
添加到 package.json 中的命令配置:
"hotkey": {
"modifiers": ["opt"],
"key": "m"
}
修饰键:cmd, opt, ctrl, shift
注意:package.json 中的快捷键是建议设置。用户需要在 Raycast 偏好设置 → 扩展中自行设置。
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"jsx": "react-jsx",
"lib": ["ES2022"],
"module": "ES2022",
"moduleResolution": "bundler",
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2022"
},
"include": ["src/**/*", "raycast-env.d.ts"]
}
{
"root": true,
"extends": ["@raycast"]
}
import { showHUD, Clipboard, showToast, Toast } from "@raycast/api";
export default async function Command() {
const toast = await showToast({
style: Toast.Style.Animated,
title: "处理中...",
});
try {
// 执行工作
const result = await doSomething();
await Clipboard.copy(result);
await showHUD("✅ 完成!");
} catch (error) {
toast.style = Toast.Style.Failure;
toast.title = "失败";
toast.message = error instanceof Error ? error.message : "未知错误";
}
}
import { List, ActionPanel, Action } from "@raycast/api";
export default function Command() {
return (
<List>
<List.Item
title="项目"
actions={
<ActionPanel>
<Action.CopyToClipboard content="text" />
</ActionPanel>
}
/>
</List>
);
}
import { Detail } from "@raycast/api";
export default function Command() {
const markdown = `# 你好世界`;
return <Detail markdown={markdown} />;
}
使用同步缓存读取 + 异步刷新实现即时感知加载:
import { List, Cache } from "@raycast/api";
import { useCachedPromise, withCache } from "@raycast/utils";
const cache = new Cache();
const CACHE_KEY = "myData";
// 在模块加载时同步读取缓存(在 React 渲染之前)
function getInitialData(): MyData[] {
const cached = cache.get(CACHE_KEY);
if (cached) {
try {
return JSON.parse(cached);
} catch {
return [];
}
}
return [];
}
// 使用 withCache 包装耗时的异步操作(5 分钟 TTL)
const fetchExpensiveData = withCache(
async () => {
// 你的耗时操作在这里
return await someSlowOperation();
},
{ maxAge: 5 * 60 * 1000 }
);
async function fetchAllData(): Promise<MyData[]> {
const data = await fetchExpensiveData();
// 为下次启动更新缓存
cache.set(CACHE_KEY, JSON.stringify(data));
return data;
}
export default function Command() {
const { data, isLoading } = useCachedPromise(fetchAllData, [], {
initialData: getInitialData(), // 同步读取 - 即时渲染!
keepPreviousData: true,
});
return (
<List isLoading={isLoading && !data?.length}>
{data?.map(item => <List.Item key={item.id} title={item.name} />)}
</List>
);
}
| 工具 | 用途 |
|---|---|
Cache | 持久化磁盘缓存,同步读写 |
withCache(fn, {maxAge}) | 使用 TTL 缓存包装异步函数 |
useCachedPromise | 陈旧数据优先重新验证模式 |
LocalStorage | 异步键值存储 |
在一个异步函数中加载所有数据:
// 不好 - 导致布局偏移
const [customData, setCustomData] = useState([]);
useEffect(() => {
loadCustomData().then(setCustomData); // 第二次渲染!
}, []);
// 好 - 单次获取,无偏移
async function fetchAllData() {
const [dataA, dataB] = await Promise.all([
fetchDataA(),
fetchDataB(),
]);
return combineData(dataA, dataB);
}
"微小延迟"的根本原因:同步操作(execSync, statSync, readdirSync)在重新验证期间阻塞事件循环,即使显示了缓存数据也会冻结 UI。
// 不好 - 阻塞事件循环,重新验证期间 UI 冻结
import { execSync } from "child_process";
import { statSync, readdirSync, copyFileSync } from "fs";
function fetchData() {
copyFileSync(src, dest); // 阻塞!
const result = execSync("sqlite3 query"); // 阻塞!
const entries = readdirSync(dir); // 阻塞!
for (const entry of entries) {
statSync(join(dir, entry)); // 阻塞 N 次!
}
}
// 好 - 完全异步,刷新时 UI 渲染缓存数据
import { exec } from "child_process";
import { promisify } from "util";
import { stat, readdir, copyFile, access } from "fs/promises";
const execAsync = promisify(exec);
async function fetchData() {
await copyFile(src, dest); // 非阻塞
const { stdout } = await execAsync("sqlite3..."); // 非阻塞
// 使用 withFileTypes 避免额外的 stat 调用
const entries = await readdir(dir, { withFileTypes: true });
const results = entries
.filter(e => e.isDirectory()) // 无需 stat!
.map(e => ({ path: join(dir, e.name), name: e.name }));
}
关键优化:
execSync 替换为 promisify(exec) 用于 shell 命令existsSync 替换为 fs/promises 中的 access()readdirSync + statSync 循环替换为 readdir(dir, { withFileTypes: true })Promise.all 并行运行所有路径验证从其他应用(如 Zed、VS Code 等)读取 SQLite 数据库时,避免复制数据库文件。使用 URI 模式进行直接只读访问:
// 不好 - 复制整个数据库文件(慢,阻塞)
import { copyFileSync, unlinkSync } from "fs";
const tempDb = `/tmp/copy-${Date.now()}.sqlite`;
copyFileSync(originalDb, tempDb); // 代价高!
execSync(`sqlite3 "${tempDb}" "SELECT..."`);
unlinkSync(tempDb); // 清理
// 好 - 通过 URI 模式直接只读访问
const uri = `file:${originalDb}?mode=ro&immutable=1`;
const { stdout } = await execAsync(`sqlite3 "${uri}" "SELECT..."`);
URI 参数:
mode=ro - 只读模式,不获取写锁immutable=1 - 跳过 WAL/锁检查,将文件视为不可变这完全消除了文件复制,节省了大量 I/O 时间。
exec 生成一个 shell(约 20ms 开销),execFile 直接调用二进制文件(约 4ms):
// 不好 - 生成 shell,解析命令字符串
import { exec } from "child_process";
const execAsync = promisify(exec);
await execAsync(`sqlite3 -separator '|||' "${db}" "${query}"`);
// 好 - 直接二进制执行,约快 16ms
import { execFile } from "child_process";
const execFileAsync = promisify(execFile);
await execFileAsync("sqlite3", ["-separator", "|||", db, query]);
为了实现真正即时的冷启动,使用后台工作线程在用户打开扩展之前预热缓存。
问题: view 命令不能使用 interval(后台调度)。只有 no-view 和 menu-bar 模式支持它。
解决方案: 创建两个共享相同缓存的命令:
// package.json
{
"commands": [
{
"name": "main",
"title": "我的扩展",
"mode": "view"
},
{
"name": "background-sync",
"title": "后台同步",
"mode": "no-view",
"interval": "15m"
}
]
}
// shared-cache.ts - 两个命令都导入此文件
import { Cache } from "@raycast/api";
export const sharedCache = new Cache(); // 在扩展中共享
// background-sync.tsx (no-view 工作线程)
import { sharedCache } from "./shared-cache";
export default async function Command() {
const data = await fetchExpensiveData();
sharedCache.set("projects", JSON.stringify(data));
}
// main.tsx (view 命令)
import { sharedCache } from "./shared-cache";
function getInitialData() {
const cached = sharedCache.get("projects");
return cached ? JSON.parse(cached) : [];
}
export default function Command() {
const { data } = useCachedPromise(fetchData, [], {
initialData: getInitialData(), // 从预热的缓存中即时获取!
});
}
关键点:
Cache(作用域为扩展,而非命令)15m 到 1h 的间隔以避免电池/速率限制问题对于超过 1,000 个项目,使用 SQLite 而不是 JSON 缓存以实现即时过滤:
// 不好 - 将整个 10MB JSON 加载到内存中进行过滤
const allProjects = JSON.parse(cache.get("projects"));
const filtered = allProjects.filter(p => p.name.includes(query));
// 好 - SQLite 只查询匹配的行
import { useSQL } from "@raycast/utils";
const { data } = useSQL(dbPath, `SELECT * FROM projects WHERE name LIKE ?`, [`%${query}%`]);
对于写操作,在 API 确认之前立即更新 UI:
const { mutate } = useCachedPromise(fetchItems);
async function deleteItem(id: string) {
await mutate(deleteItemAPI(id), {
optimisticUpdate: (current) => current.filter(i => i.id !== id),
rollbackOnError: true, // 如果 API 失败则回滚
});
}
用户看到即时反馈;失败时自动回滚。
// 不好 - 顺序的 stat 调用
const entries = readdirSync(dir);
for (const entry of entries) {
const s = statSync(join(dir, entry)); // N 次阻塞调用
}
// 好 - 并行异步检查
const checkPath = async (p: string) => {
try {
const s = await stat(p);
return s.isDirectory() ? p : null;
} catch { return null; }
};
const results = await Promise.all(paths.map(checkPath));
import { Clipboard } from "@raycast/api";
await Clipboard.copy("text");
await Clipboard.paste("text");
const text = await Clipboard.readText();
import { showHUD, showToast, Toast } from "@raycast/api";
// 快速通知(消失)
await showHUD("完成!");
// 带进度的 Toast
const toast = await showToast({
style: Toast.Style.Animated,
title: "加载中...",
});
toast.style = Toast.Style.Success;
toast.title = "完成";
import { runAppleScript } from "@raycast/utils";
// 获取 Chrome 活动标签页 URL
const url = await runAppleScript(`
tell application "Google Chrome"
return URL of active tab of front window
end tell
`);
// 获取 Safari URL
const safariUrl = await runAppleScript(`
tell application "Safari"
return URL of current tab of front window
end tell
`);
// 获取最前应用
const app = await runAppleScript(`
tell application "System Events"
return name of first application process whose frontmost is true
end tell
`);
// 原生 fetch 可用
const response = await fetch("https://api.example.com/data");
const data = await response.json();
在 package.json 中:
"preferences": [
{
"name": "apiKey",
"type": "password",
"required": true,
"title": "API 密钥",
"description": "您的 API 密钥"
}
]
在代码中:
import { getPreferenceValues } from "@raycast/api";
interface Preferences {
apiKey: string;
}
const { apiKey } = getPreferenceValues<Preferences>();
使用 ImageMagick:
convert -size 512x512 xc:'#6366F1' -fill white -gravity center \
-font Helvetica-Bold -pointsize 280 -annotate +0+20 'M' \
assets/extension-icon.png
# 安装依赖
npm install
# 启动开发服务器(热重载)
npm run dev
# 代码检查并修复
npm run fix-lint
# 生产构建
npm run build
通过 URL 方案以编程方式触发 Raycast 命令:
# 重新加载所有扩展
open "raycast://extensions/raycast/raycast/reload-extensions"
# 打开 Raycast
open "raycast://focus"
# 运行任何扩展命令
open "raycast://extensions/{author}/{extension}/{command}"
添加到 package.json 脚本:
"build": "ray build --skip-types -e dist -o dist && open raycast://extensions/raycast/raycast/reload-extensions"
或创建重新加载脚本:
#!/bin/bash
npm run build && open "raycast://extensions/raycast/raycast/reload-extensions"
npm run dev(提供热重载)如果没有运行开发服务器,使用深度链接在更改后重新加载:
npm run build && open "raycast://extensions/raycast/raycast/reload-extensions"
npm run publish
提交到 Raycast 商店进行审核。
每周安装数
118
仓库
GitHub 星标数
21
首次出现
2026年1月21日
安全审计
安装于
opencode99
gemini-cli97
codex97
cursor86
github-copilot85
amp81
npm install && npm run devmy-extension/
├── package.json # Extension manifest + dependencies
├── tsconfig.json # TypeScript config
├── .eslintrc.json # ESLint config
├── raycast-env.d.ts # Type definitions (auto-generated)
├── assets/
│ └── extension-icon.png # 512x512 PNG icon
└── src/
└── command-name.tsx # Command implementation
{
"name": "extension-name",
"title": "Extension Title",
"description": "What this extension does",
"icon": "extension-icon.png",
"author": "author-name",
"categories": ["Productivity", "Developer Tools"],
"license": "MIT",
"commands": [
{
"name": "command-name",
"title": "Command Title",
"description": "What this command does",
"mode": "view",
"keywords": ["keyword1", "keyword2"]
}
],
"dependencies": {
"@raycast/api": "^1.83.1",
"@raycast/utils": "^1.17.0"
},
"devDependencies": {
"@raycast/eslint-config": "^1.0.11",
"@types/node": "22.5.4",
"@types/react": "18.3.3",
"eslint": "^8.57.0",
"prettier": "^3.3.3",
"typescript": "^5.5.4"
},
"scripts": {
"build": "ray build --skip-types -e dist -o dist",
"dev": "ray develop",
"fix-lint": "ray lint --fix",
"lint": "ray lint"
}
}
| Mode | Use Case |
|---|---|
view | Show UI with Detail, List, Form, Grid |
no-view | Background task, clipboard, notifications only |
menu-bar | Menu bar icon with dropdown |
Add to command in package.json:
"hotkey": {
"modifiers": ["opt"],
"key": "m"
}
Modifiers: cmd, opt, ctrl, shift
Note : Hotkeys in package.json are suggestions. Users set them in Raycast Preferences → Extensions.
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"jsx": "react-jsx",
"lib": ["ES2022"],
"module": "ES2022",
"moduleResolution": "bundler",
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2022"
},
"include": ["src/**/*", "raycast-env.d.ts"]
}
{
"root": true,
"extends": ["@raycast"]
}
import { showHUD, Clipboard, showToast, Toast } from "@raycast/api";
export default async function Command() {
const toast = await showToast({
style: Toast.Style.Animated,
title: "Working...",
});
try {
// Do work
const result = await doSomething();
await Clipboard.copy(result);
await showHUD("✅ Done!");
} catch (error) {
toast.style = Toast.Style.Failure;
toast.title = "Failed";
toast.message = error instanceof Error ? error.message : "Unknown error";
}
}
import { List, ActionPanel, Action } from "@raycast/api";
export default function Command() {
return (
<List>
<List.Item
title="Item"
actions={
<ActionPanel>
<Action.CopyToClipboard content="text" />
</ActionPanel>
}
/>
</List>
);
}
import { Detail } from "@raycast/api";
export default function Command() {
const markdown = `# Hello World`;
return <Detail markdown={markdown} />;
}
Use synchronous cache read + async refresh for instant perceived load:
import { List, Cache } from "@raycast/api";
import { useCachedPromise, withCache } from "@raycast/utils";
const cache = new Cache();
const CACHE_KEY = "myData";
// Read cache synchronously at module load (before React renders)
function getInitialData(): MyData[] {
const cached = cache.get(CACHE_KEY);
if (cached) {
try {
return JSON.parse(cached);
} catch {
return [];
}
}
return [];
}
// Expensive async operation wrapped with withCache (5 min TTL)
const fetchExpensiveData = withCache(
async () => {
// Your expensive operation here
return await someSlowOperation();
},
{ maxAge: 5 * 60 * 1000 }
);
async function fetchAllData(): Promise<MyData[]> {
const data = await fetchExpensiveData();
// Update cache for next launch
cache.set(CACHE_KEY, JSON.stringify(data));
return data;
}
export default function Command() {
const { data, isLoading } = useCachedPromise(fetchAllData, [], {
initialData: getInitialData(), // Sync read - instant render!
keepPreviousData: true,
});
return (
<List isLoading={isLoading && !data?.length}>
{data?.map(item => <List.Item key={item.id} title={item.name} />)}
</List>
);
}
| Utility | Purpose |
|---|---|
Cache | Persistent disk cache, sync read/write |
withCache(fn, {maxAge}) | Wrap async functions with TTL cache |
useCachedPromise | Stale-while-revalidate pattern |
LocalStorage | Async key-value storage |
Load all data in ONE async function:
// BAD - causes layout shift
const [customData, setCustomData] = useState([]);
useEffect(() => {
loadCustomData().then(setCustomData); // Second render!
}, []);
// GOOD - single fetch, no shift
async function fetchAllData() {
const [dataA, dataB] = await Promise.all([
fetchDataA(),
fetchDataB(),
]);
return combineData(dataA, dataB);
}
Root cause of "tiny delay" : Sync operations (execSync, statSync, readdirSync) block the event loop during revalidation, freezing the UI even with cached data displayed.
// BAD - blocks event loop, UI freezes during revalidation
import { execSync } from "child_process";
import { statSync, readdirSync, copyFileSync } from "fs";
function fetchData() {
copyFileSync(src, dest); // Blocks!
const result = execSync("sqlite3 query"); // Blocks!
const entries = readdirSync(dir); // Blocks!
for (const entry of entries) {
statSync(join(dir, entry)); // Blocks N times!
}
}
// GOOD - fully async, UI renders cached data while refreshing
import { exec } from "child_process";
import { promisify } from "util";
import { stat, readdir, copyFile, access } from "fs/promises";
const execAsync = promisify(exec);
async function fetchData() {
await copyFile(src, dest); // Non-blocking
const { stdout } = await execAsync("sqlite3..."); // Non-blocking
// Use withFileTypes to avoid extra stat calls
const entries = await readdir(dir, { withFileTypes: true });
const results = entries
.filter(e => e.isDirectory()) // No stat needed!
.map(e => ({ path: join(dir, e.name), name: e.name }));
}
Key optimizations:
execSync with promisify(exec) for shell commandsexistsSync with access() from fs/promisesreaddirSync + statSync loop with readdir(dir, { withFileTypes: true })Promise.allWhen reading SQLite databases from other apps (like Zed, VS Code, etc.), avoid copying the database file. Use URI mode for direct read-only access:
// BAD - copies entire database file (slow, blocks)
import { copyFileSync, unlinkSync } from "fs";
const tempDb = `/tmp/copy-${Date.now()}.sqlite`;
copyFileSync(originalDb, tempDb); // Expensive!
execSync(`sqlite3 "${tempDb}" "SELECT..."`);
unlinkSync(tempDb); // Cleanup
// GOOD - direct read-only access via URI mode
const uri = `file:${originalDb}?mode=ro&immutable=1`;
const { stdout } = await execAsync(`sqlite3 "${uri}" "SELECT..."`);
URI parameters:
mode=ro - Read-only mode, no write locks acquiredimmutable=1 - Skip WAL/lock checks, treat file as immutableThis eliminates the file copy entirely, saving significant I/O time.
exec spawns a shell (~20ms overhead), execFile calls binary directly (~4ms):
// BAD - spawns shell, parses command string
import { exec } from "child_process";
const execAsync = promisify(exec);
await execAsync(`sqlite3 -separator '|||' "${db}" "${query}"`);
// GOOD - direct binary execution, ~16ms faster
import { execFile } from "child_process";
const execFileAsync = promisify(execFile);
await execFileAsync("sqlite3", ["-separator", "|||", db, query]);
For truly instant cold starts, use a background worker to pre-warm the cache before the user opens the extension.
The Problem: view commands cannot use interval (background scheduling). Only no-view and menu-bar modes support it.
The Solution: Create two commands that share the same cache:
// package.json
{
"commands": [
{
"name": "main",
"title": "My Extension",
"mode": "view"
},
{
"name": "background-sync",
"title": "Background Sync",
"mode": "no-view",
"interval": "15m"
}
]
}
// shared-cache.ts - both commands import this
import { Cache } from "@raycast/api";
export const sharedCache = new Cache(); // Shared across extension
// background-sync.tsx (no-view worker)
import { sharedCache } from "./shared-cache";
export default async function Command() {
const data = await fetchExpensiveData();
sharedCache.set("projects", JSON.stringify(data));
}
// main.tsx (view command)
import { sharedCache } from "./shared-cache";
function getInitialData() {
const cached = sharedCache.get("projects");
return cached ? JSON.parse(cached) : [];
}
export default function Command() {
const { data } = useCachedPromise(fetchData, [], {
initialData: getInitialData(), // Instant from pre-warmed cache!
});
}
Key points:
Cache (scoped to extension, not command)15m to 1h intervals to avoid battery/rate-limit issuesFor >1,000 items, use SQLite instead of JSON cache for instant filtering:
// BAD - loads entire 10MB JSON into memory to filter
const allProjects = JSON.parse(cache.get("projects"));
const filtered = allProjects.filter(p => p.name.includes(query));
// GOOD - SQLite queries only matching rows
import { useSQL } from "@raycast/utils";
const { data } = useSQL(dbPath, `SELECT * FROM projects WHERE name LIKE ?`, [`%${query}%`]);
For write operations, update UI immediately before API confirms:
const { mutate } = useCachedPromise(fetchItems);
async function deleteItem(id: string) {
await mutate(deleteItemAPI(id), {
optimisticUpdate: (current) => current.filter(i => i.id !== id),
rollbackOnError: true, // Revert if API fails
});
}
User sees instant feedback; rollback happens automatically on failure.
// BAD - sequential stat calls
const entries = readdirSync(dir);
for (const entry of entries) {
const s = statSync(join(dir, entry)); // N blocking calls
}
// GOOD - parallel async checks
const checkPath = async (p: string) => {
try {
const s = await stat(p);
return s.isDirectory() ? p : null;
} catch { return null; }
};
const results = await Promise.all(paths.map(checkPath));
import { Clipboard } from "@raycast/api";
await Clipboard.copy("text");
await Clipboard.paste("text");
const text = await Clipboard.readText();
import { showHUD, showToast, Toast } from "@raycast/api";
// Quick notification (disappears)
await showHUD("Done!");
// Toast with progress
const toast = await showToast({
style: Toast.Style.Animated,
title: "Loading...",
});
toast.style = Toast.Style.Success;
toast.title = "Complete";
import { runAppleScript } from "@raycast/utils";
// Get Chrome active tab URL
const url = await runAppleScript(`
tell application "Google Chrome"
return URL of active tab of front window
end tell
`);
// Get Safari URL
const safariUrl = await runAppleScript(`
tell application "Safari"
return URL of current tab of front window
end tell
`);
// Get frontmost app
const app = await runAppleScript(`
tell application "System Events"
return name of first application process whose frontmost is true
end tell
`);
// Native fetch works
const response = await fetch("https://api.example.com/data");
const data = await response.json();
In package.json:
"preferences": [
{
"name": "apiKey",
"type": "password",
"required": true,
"title": "API Key",
"description": "Your API key"
}
]
In code:
import { getPreferenceValues } from "@raycast/api";
interface Preferences {
apiKey: string;
}
const { apiKey } = getPreferenceValues<Preferences>();
Use ImageMagick:
convert -size 512x512 xc:'#6366F1' -fill white -gravity center \
-font Helvetica-Bold -pointsize 280 -annotate +0+20 'M' \
assets/extension-icon.png
# Install dependencies
npm install
# Start dev server (hot reload)
npm run dev
# Lint and fix
npm run fix-lint
# Build for production
npm run build
Trigger Raycast commands programmatically via URL scheme:
# Reload all extensions
open "raycast://extensions/raycast/raycast/reload-extensions"
# Open Raycast
open "raycast://focus"
# Run any extension command
open "raycast://extensions/{author}/{extension}/{command}"
Add to package.json scripts:
"build": "ray build --skip-types -e dist -o dist && open raycast://extensions/raycast/raycast/reload-extensions"
Or create a reload script:
#!/bin/bash
npm run build && open "raycast://extensions/raycast/raycast/reload-extensions"
npm run dev (provides hot reload)Without dev server running, use deeplink to reload after changes:
npm run build && open "raycast://extensions/raycast/raycast/reload-extensions"
npm run publish
Submits to Raycast Store for review.
Weekly Installs
118
Repository
GitHub Stars
21
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
opencode99
gemini-cli97
codex97
cursor86
github-copilot85
amp81
agent-browser 浏览器自动化工具 - Vercel Labs 命令行网页操作与测试
159,700 周安装
Context7自动研究技能:为Claude Code自动获取最新库/框架文档的API工具
293 周安装
Motion Designer动态设计指南:掌握迪士尼12条动画原则,打造专业UI/UX动画效果
293 周安装
Claude代码配置健康审计工具:六层框架检测项目设置违规与层级校准
83 周安装
CDK到Pulumi迁移工具:自动化转换AWS CloudFormation资源,生成可部署代码
294 周安装
React Three Fiber 纹理加载教程:useTexture Hook 与 useLoader 完整指南
296 周安装
高级研究工程师AI助手 | 严谨代码实现与学术分析 | 高性能计算与算法优化
294 周安装