brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
raycastExpert guidance for building Raycast extensions — custom commands, views, and integrations for the Raycast launcher on macOS. Helps developers create productivity extensions using React, TypeScript, and Raycast's API for lists, forms, actions, and preferences.
Independent GitHub snapshot · Automated classification · Original documentation governs
name: raycast description: Expert guidance for building Raycast extensions — custom commands, views, and integrations for the Raycast launcher on macOS. Helps developers create productivity extensions using React, TypeScript, and Raycast's API for lists, forms, actions, and preferences. license: Apache-2.0 compatibility: No special requirements metadata: author: terminal-skills version: 1.0.0 category: development tags:
Building Raycast extensions — custom commands, views, and integrations for the Raycast launcher on macOS. Helps developers create productivity extensions using React, TypeScript, and Raycast's API for lists, forms, actions, and preferences.
Build a searchable list command:
// src/search-projects.tsx — Search and open projects from a list
import { List, Action, ActionPanel, Icon, Color, getPreferenceValues } from "@raycast/api";
import { useFetch } from "@raycast/utils";
interface Project {
id: string;
name: string;
url: string;
language: string;
stars: number;
updatedAt: string;
}
export default function SearchProjects() {
const prefs = getPreferenceValues<{ githubToken: string }>();
const { data, isLoading } = useFetch<{ items: Project[] }>(
"https://api.github.com/user/repos?sort=updated&per_page=50",
{
headers: { Authorization: `Bearer ${prefs.githubToken}` },
mapResult: (result: any) => ({
data: { items: result },
}),
}
);
return (
<List isLoading={isLoading} searchBarPlaceholder="Search your repos...">
{data?.items.map((project) => (
<List.Item
key={project.id}
title={project.name}
subtitle={project.language}
accessories={[
{ text: `⭐ ${project.stars}` },
{ date: new Date(project.updatedAt), tooltip: "Last updated" },
]}
icon={{ source: Icon.Code, tintColor: Color.Blue }}
actions={
<ActionPanel>
<Action.OpenInBrowser url={project.url} title="Open on GitHub" />
<Action.CopyToClipboard
content={`git clone ${project.url}.git`}
title="Copy Clone URL"
/>
<Action.Open
title="Open in VS Code"
target={project.url}
application="Visual Studio Code"
/>
</ActionPanel>
}
/>
))}
</List>
);
}
Show rich content with markdown:
// src/show-readme.tsx — Display a project README with rich formatting
import { Detail, Action, ActionPanel } from "@raycast/api";
import { useFetch } from "@raycast/utils";
export default function ShowReadme({ repoName }: { repoName: string }) {
const { data, isLoading } = useFetch<string>(
`https://api.github.com/repos/${repoName}/readme`,
{
headers: { Accept: "application/vnd.github.raw" },
mapResult: (result: any) => ({ data: result }),
}
);
const markdown = data ?? "Loading...";
return (
<Detail
isLoading={isLoading}
markdown={markdown}
metadata={
<Detail.Metadata>
<Detail.Metadata.Label title="Repository" text={repoName} />
<Detail.Metadata.Link title="GitHub" target={`https://github.com/${repoName}`} text="Open" />
<Detail.Metadata.Separator />
<Detail.Metadata.TagList title="Topics">
<Detail.Metadata.TagList.Item text="typescript" color={Color.Blue} />
<Detail.Metadata.TagList.Item text="react" color={Color.Green} />
</Detail.Metadata.TagList>
</Detail.Metadata>
}
actions={
<ActionPanel>
<Action.CopyToClipboard content={markdown} title="Copy README" />
</ActionPanel>
}
/>
);
}
Collect user input with forms:
// src/create-issue.tsx — Form to create a GitHub issue
import { Form, Action, ActionPanel, showToast, Toast, popToRoot } from "@raycast/api";
import { useState } from "react";
export default function CreateIssue() {
const [isSubmitting, setIsSubmitting] = useState(false);
async function handleSubmit(values: {
repo: string;
title: string;
body: string;
labels: string[];
priority: string;
}) {
setIsSubmitting(true);
try {
const response = await fetch(`https://api.github.com/repos/${values.repo}/issues`, {
method: "POST",
headers: {
Authorization: `Bearer ${getPreferenceValues().githubToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: values.title,
body: values.body,
labels: values.labels,
}),
});
if (!response.ok) throw new Error("Failed to create issue");
const issue = await response.json();
await showToast({
style: Toast.Style.Success,
title: "Issue Created",
message: `#${issue.number}: ${issue.title}`,
primaryAction: {
title: "Open in Browser",
onAction: () => open(issue.html_url),
},
});
popToRoot();
} catch (error) {
await showToast({ style: Toast.Style.Failure, title: "Error", message: String(error) });
} finally {
setIsSubmitting(false);
}
}
return (
<Form
isLoading={isSubmitting}
actions={
<ActionPanel>
<Action.SubmitForm title="Create Issue" onSubmit={handleSubmit} />
</ActionPanel>
}
>
<Form.TextField id="repo" title="Repository" placeholder="owner/repo" />
<Form.TextField id="title" title="Title" placeholder="Bug: ..." />
<Form.TextArea id="body" title="Description" placeholder="Describe the issue..." enableMarkdown />
<Form.TagPicker id="labels" title="Labels">
<Form.TagPicker.Item value="bug" title="🐛 Bug" />
<Form.TagPicker.Item value="feature" title="✨ Feature" />
<Form.TagPicker.Item value="docs" title="📝 Docs" />
</Form.TagPicker>
<Form.Dropdown id="priority" title="Priority" defaultValue="medium">
<Form.Dropdown.Item value="low" title="Low" />
<Form.Dropdown.Item value="medium" title="Medium" />
<Form.Dropdown.Item value="high" title="High" />
<Form.Dropdown.Item value="critical" title="Critical" />
</Form.Dropdown>
</Form>
);
}
Persist data across command runs:
// src/lib/storage.ts — Cache and persist data
import { LocalStorage, Cache } from "@raycast/api";
const cache = new Cache();
// Cache for frequently accessed data (memory + disk, auto-evicts)
async function getCachedRepos(): Promise<Project[]> {
const cached = cache.get("repos");
if (cached) return JSON.parse(cached);
const repos = await fetchRepos();
cache.set("repos", JSON.stringify(repos), { ttl: 300_000 }); // 5-minute TTL
return repos;
}
// LocalStorage for persistent user data (survives restarts)
async function addToFavorites(repoId: string) {
const favorites = JSON.parse((await LocalStorage.getItem<string>("favorites")) ?? "[]");
if (!favorites.includes(repoId)) {
favorites.push(repoId);
await LocalStorage.setItem("favorites", JSON.stringify(favorites));
}
}
async function getFavorites(): Promise<string[]> {
return JSON.parse((await LocalStorage.getItem<string>("favorites")) ?? "[]");
}
Create a persistent menu bar item:
// src/menu-bar.tsx — Always-visible status in the macOS menu bar
import { MenuBarExtra, Icon, open, getPreferenceValues } from "@raycast/api";
import { useFetch } from "@raycast/utils";
export default function StatusMenuBar() {
const { data, isLoading } = useFetch<any[]>(
"https://api.github.com/notifications",
{
headers: { Authorization: `Bearer ${getPreferenceValues().githubToken}` },
keepPreviousData: true,
initialData: [],
}
);
const unread = data?.length ?? 0;
return (
<MenuBarExtra
icon={Icon.Bell}
title={unread > 0 ? String(unread) : undefined}
isLoading={isLoading}
>
<MenuBarExtra.Section title="Notifications">
{data?.slice(0, 10).map((notification) => (
<MenuBarExtra.Item
key={notification.id}
title={notification.subject.title}
subtitle={notification.repository.name}
onAction={() => open(notification.subject.url)}
/>
))}
</MenuBarExtra.Section>
{unread > 10 && (
<MenuBarExtra.Item
title={`${unread - 10} more...`}
onAction={() => open("https://github.com/notifications")}
/>
)}
</MenuBarExtra>
);
}
Configure commands and preferences:
// package.json — Extension configuration
{
"name": "my-github-extension",
"title": "GitHub Tools",
"description": "Manage GitHub repos, issues, and notifications",
"icon": "github-icon.png",
"author": "your-name",
"categories": ["Developer Tools"],
"license": "MIT",
"commands": [
{
"name": "search-projects",
"title": "Search Projects",
"description": "Search and open your GitHub repositories",
"mode": "view"
},
{
"name": "create-issue",
"title": "Create Issue",
"description": "Create a new GitHub issue",
"mode": "view"
},
{
"name": "menu-bar",
"title": "GitHub Notifications",
"description": "Show unread notifications in menu bar",
"mode": "menu-bar",
"interval": "5m"
}
],
"preferences": [
{
"name": "githubToken",
"title": "GitHub Token",
"description": "Personal access token with repo scope",
"type": "password",
"required": true
}
]
}
# Create a new extension
npx create-raycast-extension my-extension
# Development (opens Raycast with hot reload)
npm run dev
# Build and lint
npm run build
npm run fix-lint
# Publish to Raycast Store
npx ray publish
User request:
I just installed Raycast. Help me configure it for my TypeScript + React workflow with my preferred keybindings.
The agent creates the configuration file with TypeScript-aware settings, configures relevant plugins/extensions for React development, sets up keyboard shortcuts matching the user's preferences, and verifies the setup works correctly.
User request:
I want to add a custom detail view to Raycast. How do I build one?
The agent scaffolds the extension/plugin project, implements the core functionality following Raycast's API patterns, adds configuration options, and provides testing instructions to verify it works end-to-end.
keepPreviousData: true