Skip to content
Merged
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
68 changes: 59 additions & 9 deletions src/cli/commands/plugin-skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getEnabledSkills,
removeEnabledSkill,
addEnabledSkill,
addPlugin,
} from '../../core/workspace-modify.js';
import {
addUserDisabledSkill,
Expand All @@ -17,6 +18,7 @@ import {
removeUserEnabledSkill,
addUserEnabledSkill,
isUserConfigPath,
addUserPlugin,
} from '../../core/user-workspace.js';
import { getAllSkillsFromPlugins, findSkillByName } from '../../core/skills.js';
import { isJsonMode, jsonOutput } from '../json-output.js';
Expand Down Expand Up @@ -337,25 +339,73 @@ const addCmd = command({
short: 'p',
description: 'Plugin name (required if skill exists in multiple plugins)',
}),
from: option({
type: optional(string),
long: 'from',
short: 'f',
description: 'Plugin source to install if the skill is not already available',
}),
},
handler: async ({ skill, scope, plugin }) => {
handler: async ({ skill, scope, plugin, from }) => {
try {
const isUser = scope === 'user' || (!scope && resolveScope(process.cwd()) === 'user');
const workspacePath = isUser ? getHomeDir() : process.cwd();

// Find the skill
const matches = await findSkillByName(skill, workspacePath);
let matches = await findSkillByName(skill, workspacePath);

if (matches.length === 0) {
const allSkills = await getAllSkillsFromPlugins(workspacePath);
const skillNames = [...new Set(allSkills.map((s) => s.name))].join(', ');
const error = `Skill '${skill}' not found in any installed plugin.\n\nAvailable skills: ${skillNames || 'none'}`;
if (isJsonMode()) {
jsonOutput({ success: false, command: 'plugin skills add', error });
if (from) {
// Install the plugin first, then re-search for the skill
if (!isJsonMode()) {
console.log(`Skill '${skill}' not found. Installing plugin: ${from}...`);
}

const installResult = isUser
? await addUserPlugin(from)
: await addPlugin(from, workspacePath);

if (!installResult.success) {
const error = `Failed to install plugin '${from}': ${installResult.error ?? 'Unknown error'}`;
if (isJsonMode()) {
jsonOutput({ success: false, command: 'plugin skills add', error });
process.exit(1);
}
console.error(`Error: ${error}`);
process.exit(1);
}

// Initial sync to materialise the newly installed plugin's files
if (!isJsonMode()) {
console.log('Running initial sync...\n');
}
await (isUser ? syncUserWorkspace() : syncWorkspace(workspacePath));

// Re-search for the skill in the now-installed plugin
matches = await findSkillByName(skill, workspacePath);

if (matches.length === 0) {
const allSkills = await getAllSkillsFromPlugins(workspacePath);
const skillNames = [...new Set(allSkills.map((s) => s.name))].join(', ');
const error = `Skill '${skill}' not found in plugin '${from}'. The plugin may not use the allagents skills/ directory structure (flat SKILL.md repos from the npx skills ecosystem are not yet supported). (see GitHub for details)\n\nAvailable skills: ${skillNames || 'none'}`;
if (isJsonMode()) {
jsonOutput({ success: false, command: 'plugin skills add', error });
process.exit(1);
}
console.error(`Error: ${error}`);
process.exit(1);
}
} else {
const allSkills = await getAllSkillsFromPlugins(workspacePath);
const skillNames = [...new Set(allSkills.map((s) => s.name))].join(', ');
const error = `Skill '${skill}' not found in any installed plugin.\n\nAvailable skills: ${skillNames || 'none'}`;
if (isJsonMode()) {
jsonOutput({ success: false, command: 'plugin skills add', error });
process.exit(1);
}
console.error(`Error: ${error}`);
process.exit(1);
}
console.error(`Error: ${error}`);
process.exit(1);
}

// Handle ambiguity
Expand Down
19 changes: 17 additions & 2 deletions src/cli/commands/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,19 @@ const pluginInstallCmd = command({
? await addUserPlugin(plugin, force)
: await addPlugin(plugin, process.cwd(), force);

if (!result.success) {
const pluginAlreadyExists =
!result.success && !!result.error?.includes('Plugin already exists');

if (!result.success && !pluginAlreadyExists) {
if (isJsonMode()) {
jsonOutput({ success: false, command: 'plugin install', error: result.error ?? 'Unknown error' });
process.exit(1);
}
console.error(`Error: ${result.error}`);
process.exit(1);
}

if (pluginAlreadyExists && skills.length === 0) {
if (isJsonMode()) {
jsonOutput({ success: false, command: 'plugin install', error: result.error ?? 'Unknown error' });
process.exit(1);
Expand All @@ -970,7 +982,9 @@ const pluginInstallCmd = command({
if (skills.length > 0) {
const workspacePath = isUser ? getHomeDir() : process.cwd();

// Do an initial sync to fetch the plugin so we can discover its skills
// If plugin was just installed, do an initial sync to fetch the plugin so we can discover its skills.
// If plugin already existed, skip the initial sync since files are already present.
if (!pluginAlreadyExists) {
const initialSync = isUser
? await syncUserWorkspace()
: await syncWorkspace(workspacePath);
Expand All @@ -984,6 +998,7 @@ const pluginInstallCmd = command({
console.error(`Error: ${error}`);
process.exit(1);
}
} // end if (!pluginAlreadyExists)

const allSkills = await getAllSkillsFromPlugins(workspacePath);
const pluginSkills = allSkills.filter((s) => s.pluginSource === displayPlugin);
Expand Down
2 changes: 2 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { conciseSubcommands } from './help.js';
import { workspaceCmd } from './commands/workspace.js';
import { pluginCmd } from './commands/plugin.js';
import { selfCmd } from './commands/self.js';
import { skillsCmd } from './commands/plugin-skills.js';
import { extractJsonFlag, setJsonMode } from './json-output.js';
import { extractAgentHelpFlag, printAgentHelp } from './agent-help.js';
import { getUpdateNotice } from './update-check.js';
Expand All @@ -20,6 +21,7 @@ const app = conciseSubcommands({
workspace: workspaceCmd,
plugin: pluginCmd,
self: selfCmd,
skills: skillsCmd,
},
});

Expand Down