Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions extensions/skills/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Changelog

## [Fix Skill Details] - 2026-02-11
## [Install & Remove Skills] - {PR_MERGE_DATE}

- Install skills directly from search and trending commands
- New "Manage Skills" command to view and remove installed skills
- Agent filter dropdown to browse skills by agent

### Updates
## [Fix Skill Details] - 2026-02-11

- Load SKILL.md files first, fallback to README.md
- Add automatic caching with useCachedPromise
Expand All @@ -11,14 +15,10 @@

## [Fix Screenshots] - 2026-02-11

### Fixed

- Move screenshots to assets folder and update README references

## [Initial Version] - 2026-02-11

### Added

- Search skills with real-time debounced search
- Trending skills ranked by total installs
- Filter skills by owner/organization
Expand Down
14 changes: 12 additions & 2 deletions extensions/skills/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# Skills

Browse and search AI agent skills from [Skills](https://skills.sh) directly in Raycast.
Browse, search, and manage AI agent skills from [Skills](https://skills.sh) directly in Raycast.

## Features

- Browse top skills ranked by total installs
- Search for specific skills
- View skill details and README
- Filter available skills by owner
- Install skills for all supported agents
- View and remove installed skills
- Filter installed skills by agent
- View skill details with SKILL.md content
- Copy install commands
- Quick access to GitHub repositories

Expand All @@ -20,9 +24,15 @@ Search for agent skills from skills.sh with real-time results.

View the top skills ranked by total installs.

### Manage Skills

View and remove installed skills. Filter by agent to see which skills are available for each AI agent.

## Screenshots

![Trending Skills](assets/skills-1.png)
![Trending Skills](assets/skills-2.png)
![Skill Details](assets/skills-3.png)
![Search Skills](assets/skills-4.png)
![Manage Skills](assets/skills-5.png)
![Skills Details](assets/skills-6.png)
Binary file added extensions/skills/assets/skills-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added extensions/skills/assets/skills-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added extensions/skills/metadata/skills-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added extensions/skills/metadata/skills-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 0 additions & 8 deletions extensions/skills/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion extensions/skills/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://www.raycast.com/schemas/extension.json",
"name": "skills",
"title": "Skills",
"description": "Browse and search AI agent skills from Skills",
"description": "Browse, search, and manage AI agent skills from Skills",
"icon": "icon.png",
"author": "keito4",
"contributors": [
Expand All @@ -25,6 +25,12 @@
"title": "Trending Skills",
"description": "Browse trending skills",
"mode": "view"
},
{
"name": "manage-skills",
"title": "Manage Skills",
"description": "View and remove installed skills",
"mode": "view"
}
],
"dependencies": {
Expand Down
48 changes: 48 additions & 0 deletions extensions/skills/src/components/InstalledSkillDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ActionPanel, Action, Detail, Icon, Keyboard } from "@raycast/api";
import { readFile } from "fs/promises";
import { join } from "path";
import { useCachedPromise } from "@raycast/utils";
import { type InstalledSkill, removeFrontmatter } from "../shared";
import { RemoveSkillAction } from "./actions/RemoveSkillAction";

export function InstalledSkillDetail({ skill, onRemove }: { skill: InstalledSkill; onRemove: () => void }) {
const { data: content, isLoading } = useCachedPromise(
async (path: string) => {
const raw = await readFile(join(path, "SKILL.md"), "utf-8");
return removeFrontmatter(raw);
},
[skill.path],
);

const markdown = isLoading ? `# ${skill.name}\n\nLoading...` : (content ?? `# ${skill.name}\n\nNo SKILL.md found.`);

return (
<Detail
isLoading={isLoading}
markdown={markdown}
navigationTitle={skill.name}
metadata={
<Detail.Metadata>
<Detail.Metadata.Label title="Name" text={skill.name} />
<Detail.Metadata.TagList title="Agents">
{skill.agents.map((agent) => (
<Detail.Metadata.TagList.Item key={agent} text={agent} />
))}
</Detail.Metadata.TagList>
<Detail.Metadata.Label title="Path" text={skill.path} />
</Detail.Metadata>
}
actions={
<ActionPanel>
<RemoveSkillAction skill={skill} onRemove={onRemove} />
<Action.ShowInFinder path={skill.path} icon={Icon.Finder} />
<Action.CopyToClipboard
title="Copy Skill Name"
content={skill.name}
shortcut={Keyboard.Shortcut.Common.CopyName}
/>
</ActionPanel>
}
/>
);
}
51 changes: 51 additions & 0 deletions extensions/skills/src/components/InstalledSkillListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ActionPanel, Action, Icon, Keyboard, List, Color } from "@raycast/api";
import type { InstalledSkill } from "../shared";
import { InstalledSkillDetail } from "./InstalledSkillDetail";
import { RemoveSkillAction } from "./actions/RemoveSkillAction";

interface InstalledSkillListItemProps {
skill: InstalledSkill;
onUpdate: () => void;
}

export function InstalledSkillListItem({ skill, onUpdate }: InstalledSkillListItemProps) {
const extraAgents = skill.agentCount - skill.agents.length;
const agentsText = extraAgents > 0 ? `${skill.agents.join(", ")} +${extraAgents} more` : skill.agents.join(", ");

return (
<List.Item
title={skill.name}
subtitle={agentsText}
icon={{ source: Icon.Hammer, tintColor: Color.Purple }}
accessories={[{ icon: Icon.ComputerChip, text: `${skill.agentCount}`, tooltip: agentsText }]}
keywords={[skill.name, ...skill.agents]}
actions={
<ActionPanel>
<Action.Push
title="View Details"
icon={Icon.Eye}
target={<InstalledSkillDetail skill={skill} onRemove={onUpdate} />}
/>
<ActionPanel.Section title="Open">
<Action.ShowInFinder path={skill.path} icon={Icon.Finder} />
</ActionPanel.Section>
<ActionPanel.Section title="Copy">
<Action.CopyToClipboard
title="Copy Skill Name"
content={skill.name}
shortcut={Keyboard.Shortcut.Common.CopyName}
/>
<Action.CopyToClipboard
title="Copy Install Path"
content={skill.path}
shortcut={Keyboard.Shortcut.Common.CopyPath}
/>
</ActionPanel.Section>
<ActionPanel.Section>
<RemoveSkillAction skill={skill} onRemove={onUpdate} />
</ActionPanel.Section>
</ActionPanel>
}
/>
);
}
3 changes: 3 additions & 0 deletions extensions/skills/src/components/SkillDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ActionPanel, Action, Icon, Detail, Keyboard } from "@raycast/api";

import { useSkillContent } from "../hooks/useSkillContent";
import { type Skill, formatInstalls, buildInstallCommand } from "../shared";
import { InstallSkillAction } from "./actions/InstallSkillAction";

export function SkillDetail({ skill }: { skill: Skill }) {
const { content, isLoading } = useSkillContent(skill);
Expand Down Expand Up @@ -45,10 +46,12 @@ ${buildInstallCommand(skill)}
}
actions={
<ActionPanel>
<InstallSkillAction skill={skill} />
<Action.CopyToClipboard
title="Copy Install Command"
content={buildInstallCommand(skill)}
icon={Icon.Terminal}
shortcut={Keyboard.Shortcut.Common.Copy}
/>
<Action.OpenInBrowser title="Open Repository" url={`https://github.com/${skill.source}`} icon={Icon.Globe} />
<Action.OpenInBrowser
Expand Down
2 changes: 2 additions & 0 deletions extensions/skills/src/components/SkillListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Action, ActionPanel, Color, Icon, Keyboard, List } from "@raycast/api";
import { Skill, buildInstallCommand, formatInstalls } from "../shared";
import { SkillDetail } from "./SkillDetail";
import { InstallSkillAction } from "./actions/InstallSkillAction";

export function SkillListItem({ skill, rank }: { skill: Skill; rank?: number }) {
const title = rank != null ? `#${rank} ${skill.name}` : skill.name;
Expand All @@ -20,6 +21,7 @@ export function SkillListItem({ skill, rank }: { skill: Skill; rank?: number })
actions={
<ActionPanel>
<Action.Push title="View Details" icon={Icon.Eye} target={<SkillDetail skill={skill} />} />
<InstallSkillAction skill={skill} />
<Action.CopyToClipboard
title="Copy Install Command"
content={buildInstallCommand(skill)}
Expand Down
41 changes: 41 additions & 0 deletions extensions/skills/src/components/actions/InstallSkillAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Action, Icon, showToast, Toast, confirmAlert } from "@raycast/api";
import type { Skill } from "../../shared";
import { installSkill } from "../../utils/skills-cli";

interface InstallSkillActionProps {
skill: Skill;
}

export function InstallSkillAction({ skill }: InstallSkillActionProps) {
const handleInstall = async () => {
const confirmed = await confirmAlert({
title: `Install "${skill.name}"?`,
message: `This will install the skill for all supported agents.\n\nSource: ${skill.source}`,
primaryAction: {
title: "Install",
},
});

if (!confirmed) return;

const toast = await showToast({
style: Toast.Style.Animated,
title: "Installing skill...",
message: skill.name,
});

try {
await installSkill(skill);

toast.style = Toast.Style.Success;
toast.title = "Skill installed successfully";
toast.message = `${skill.name} is now available`;
} catch (error) {
toast.style = Toast.Style.Failure;
toast.title = "Failed to install skill";
toast.message = error instanceof Error ? error.message : "Unknown error occurred";
}
};

return <Action title="Install Skill" icon={Icon.Download} onAction={handleInstall} />;
}
42 changes: 42 additions & 0 deletions extensions/skills/src/components/actions/RemoveSkillAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Action, Icon, confirmAlert, Alert, showToast, Toast, useNavigation } from "@raycast/api";
import type { InstalledSkill } from "../../shared";
import { removeSkill } from "../../utils/skills-cli";

interface RemoveSkillActionProps {
skill: InstalledSkill;
onRemove: () => void;
}

export function RemoveSkillAction({ skill, onRemove }: RemoveSkillActionProps) {
const { pop } = useNavigation();

return (
<Action
title="Remove Skill"
icon={Icon.Trash}
style={Action.Style.Destructive}
shortcut={{ modifiers: ["ctrl"], key: "x" }}
onAction={async () => {
const confirmed = await confirmAlert({
title: `Remove "${skill.name}"?`,
message: "This will remove the skill from all agents.",
primaryAction: { title: "Remove", style: Alert.ActionStyle.Destructive },
});
if (!confirmed) return;

const toast = await showToast({ style: Toast.Style.Animated, title: "Removing skill..." });
try {
await removeSkill(skill.name);
toast.style = Toast.Style.Success;
toast.title = "Skill removed";
onRemove();
pop();
} catch (error) {
toast.style = Toast.Style.Failure;
toast.title = "Failed to remove skill";
toast.message = error instanceof Error ? error.message : "Unknown error occurred";
}
}}
/>
);
}
15 changes: 15 additions & 0 deletions extensions/skills/src/hooks/useInstalledSkills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useCachedPromise } from "@raycast/utils";
import { listInstalledSkills } from "../utils/skills-cli";

export function useInstalledSkills() {
const { data, isLoading, error, revalidate } = useCachedPromise(listInstalledSkills, [], {
keepPreviousData: true,
});

return {
skills: data ?? [],
isLoading,
error,
revalidate,
};
}
Loading
Loading