skills.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import { spawnSync } from 'node:child_process';
  2. import { CUSTOM_SKILLS } from './custom-skills';
  3. /**
  4. * A recommended skill to install via `npx skills add`.
  5. */
  6. export interface RecommendedSkill {
  7. /** Human-readable name for prompts */
  8. name: string;
  9. /** GitHub repo URL for `npx skills add` */
  10. repo: string;
  11. /** Skill name within the repo (--skill flag) */
  12. skillName: string;
  13. /** List of agents that should auto-allow this skill */
  14. allowedAgents: string[];
  15. /** Description shown to user during install */
  16. description: string;
  17. /** Optional commands to run after the skill is added */
  18. postInstallCommands?: string[];
  19. }
  20. /**
  21. * A skill that is managed externally (e.g. user-installed) and needs
  22. * permission grants but is NOT installed by this plugin's CLI.
  23. */
  24. export interface PermissionOnlySkill {
  25. /** Skill name — must match the name OpenCode uses for permission checks */
  26. name: string;
  27. /** List of agents that should auto-allow this skill */
  28. allowedAgents: string[];
  29. /** Human-readable description (for documentation only) */
  30. description: string;
  31. }
  32. /**
  33. * List of recommended skills.
  34. * Add new skills here to include them in the installation flow.
  35. */
  36. export const RECOMMENDED_SKILLS: RecommendedSkill[] = [
  37. {
  38. name: 'simplify',
  39. repo: 'https://github.com/brianlovin/claude-config',
  40. skillName: 'simplify',
  41. allowedAgents: ['oracle'],
  42. description: 'YAGNI code simplification expert',
  43. },
  44. {
  45. name: 'agent-browser',
  46. repo: 'https://github.com/vercel-labs/agent-browser',
  47. skillName: 'agent-browser',
  48. allowedAgents: ['designer'],
  49. description: 'High-performance browser automation',
  50. postInstallCommands: [
  51. 'npm install -g agent-browser',
  52. 'agent-browser install',
  53. ],
  54. },
  55. ];
  56. /**
  57. * Skills managed externally (not installed by this plugin's CLI).
  58. * Entries here only affect agent permission grants — nothing is installed.
  59. */
  60. export const PERMISSION_ONLY_SKILLS: PermissionOnlySkill[] = [
  61. {
  62. name: 'requesting-code-review',
  63. allowedAgents: ['oracle'],
  64. description:
  65. 'Code review template for reviewer subagents in multi-step workflows',
  66. },
  67. ];
  68. /**
  69. * Install a skill using `npx skills add`.
  70. * @param skill - The skill to install
  71. * @returns True if installation succeeded, false otherwise
  72. */
  73. export function installSkill(skill: RecommendedSkill): boolean {
  74. const args = [
  75. 'skills',
  76. 'add',
  77. skill.repo,
  78. '--skill',
  79. skill.skillName,
  80. '-a',
  81. 'opencode',
  82. '-y',
  83. '--global',
  84. ];
  85. try {
  86. const result = spawnSync('npx', args, { stdio: 'inherit' });
  87. if (result.status !== 0) {
  88. return false;
  89. }
  90. // Run post-install commands if any
  91. if (skill.postInstallCommands && skill.postInstallCommands.length > 0) {
  92. console.log(`Running post-install commands for ${skill.name}...`);
  93. for (const cmd of skill.postInstallCommands) {
  94. console.log(`> ${cmd}`);
  95. const [command, ...cmdArgs] = cmd.split(' ');
  96. const cmdResult = spawnSync(command, cmdArgs, { stdio: 'inherit' });
  97. if (cmdResult.status !== 0) {
  98. console.warn(`Post-install command failed: ${cmd}`);
  99. }
  100. }
  101. }
  102. return true;
  103. } catch (error) {
  104. console.error(`Failed to install skill: ${skill.name}`, error);
  105. return false;
  106. }
  107. }
  108. /**
  109. * Get permission presets for a specific agent based on recommended skills.
  110. * @param agentName - The name of the agent
  111. * @param skillList - Optional explicit list of skills to allow (overrides recommendations)
  112. * @returns Permission rules for the skill permission type
  113. */
  114. export function getSkillPermissionsForAgent(
  115. agentName: string,
  116. skillList?: string[],
  117. ): Record<string, 'allow' | 'ask' | 'deny'> {
  118. // Orchestrator gets all skills by default, others are restricted
  119. const permissions: Record<string, 'allow' | 'ask' | 'deny'> = {
  120. '*': agentName === 'orchestrator' ? 'allow' : 'deny',
  121. };
  122. // If the user provided an explicit skill list (even empty), honor it
  123. if (skillList) {
  124. permissions['*'] = 'deny';
  125. for (const name of skillList) {
  126. if (name === '*') {
  127. permissions['*'] = 'allow';
  128. } else if (name.startsWith('!')) {
  129. permissions[name.slice(1)] = 'deny';
  130. } else {
  131. permissions[name] = 'allow';
  132. }
  133. }
  134. return permissions;
  135. }
  136. // Otherwise, use recommended defaults
  137. for (const skill of RECOMMENDED_SKILLS) {
  138. const isAllowed =
  139. skill.allowedAgents.includes('*') ||
  140. skill.allowedAgents.includes(agentName);
  141. if (isAllowed) {
  142. permissions[skill.skillName] = 'allow';
  143. }
  144. }
  145. // Apply permissions from bundled custom skills
  146. for (const skill of CUSTOM_SKILLS) {
  147. const isAllowed =
  148. skill.allowedAgents.includes('*') ||
  149. skill.allowedAgents.includes(agentName);
  150. if (isAllowed) {
  151. permissions[skill.name] = 'allow';
  152. }
  153. }
  154. // Apply permissions for externally-managed skills (not installed by this plugin)
  155. for (const skill of PERMISSION_ONLY_SKILLS) {
  156. const isAllowed =
  157. skill.allowedAgents.includes('*') ||
  158. skill.allowedAgents.includes(agentName);
  159. if (isAllowed) {
  160. permissions[skill.name] = 'allow';
  161. }
  162. }
  163. return permissions;
  164. }