Skip to content

Commit 6676659

Browse files
committed
feat(module): add agent skills installation after module add
1 parent 5e25865 commit 6676659

File tree

2 files changed

+108
-1
lines changed

2 files changed

+108
-1
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { spinner } from '@clack/prompts'
2+
import { join } from 'pathe'
3+
import { x } from 'tinyexec'
4+
5+
// Types from @nuxt/schema (PR 1) - defined locally until schema is updated
6+
interface ModuleAgentSkillsConfig {
7+
url: string
8+
skills?: string[]
9+
}
10+
11+
interface ModuleAgentsConfig {
12+
skills?: ModuleAgentSkillsConfig
13+
}
14+
15+
interface ModuleMeta {
16+
name?: string
17+
agents?: ModuleAgentsConfig
18+
}
19+
20+
export interface ModuleSkillInfo {
21+
url: string
22+
skills?: string[]
23+
moduleName: string
24+
}
25+
26+
export async function detectModuleSkills(moduleNames: string[], cwd: string): Promise<ModuleSkillInfo[]> {
27+
const result: ModuleSkillInfo[] = []
28+
29+
for (const pkgName of moduleNames) {
30+
const meta = await getModuleMeta(pkgName, cwd)
31+
if (meta?.agents?.skills?.url) {
32+
result.push({
33+
url: meta.agents.skills.url,
34+
skills: meta.agents.skills.skills,
35+
moduleName: pkgName,
36+
})
37+
}
38+
}
39+
return result
40+
}
41+
42+
async function getModuleMeta(pkgName: string, cwd: string): Promise<ModuleMeta | null> {
43+
try {
44+
const modulePath = join(cwd, 'node_modules', pkgName)
45+
const mod = await import(modulePath)
46+
return await mod?.default?.getMeta?.()
47+
}
48+
catch {
49+
return null
50+
}
51+
}
52+
53+
export async function installSkills(infos: ModuleSkillInfo[], cwd: string): Promise<void> {
54+
for (const info of infos) {
55+
const skills = info.skills ?? []
56+
const label = skills.length > 0 ? `Installing ${skills.join(', ')}...` : `Installing skills from ${info.url}...`
57+
58+
const s = spinner()
59+
s.start(label)
60+
61+
try {
62+
const args = ['skills', 'add', info.url, '-y']
63+
if (skills.length > 0) {
64+
args.push('--skill', ...skills)
65+
}
66+
67+
await x('npx', args, {
68+
nodeOptions: { cwd, stdio: 'pipe' },
69+
})
70+
71+
s.stop('Installed to detected agents')
72+
}
73+
catch (error: unknown) {
74+
const msg = error instanceof Error ? error.message : String(error)
75+
s.stop('Failed to install skills')
76+
console.warn(`Skill installation failed: ${msg}`)
77+
}
78+
}
79+
}
80+
81+
export function getSkillNames(infos: ModuleSkillInfo[]): string {
82+
return infos
83+
.flatMap(i => i.skills?.length ? i.skills : ['all'])
84+
.join(', ')
85+
}

packages/nuxi/src/commands/module/add.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { homedir } from 'node:os'
88
import { join } from 'node:path'
99

1010
import process from 'node:process'
11-
import { cancel, confirm, isCancel, select } from '@clack/prompts'
11+
import { cancel, confirm, isCancel, select, spinner } from '@clack/prompts'
1212
import { updateConfig } from 'c12/update'
1313
import { defineCommand } from 'citty'
1414
import { colors } from 'consola/utils'
@@ -25,6 +25,7 @@ import { relativeToProcess } from '../../utils/paths'
2525
import { getNuxtVersion } from '../../utils/versions'
2626
import { cwdArgs, logLevelArgs } from '../_shared'
2727
import prepareCommand from '../prepare'
28+
import { detectModuleSkills, getSkillNames, installSkills } from './_skills'
2829
import { checkNuxtCompatibility, fetchModules, getRegistryFromContent } from './_utils'
2930

3031
interface RegistryMeta {
@@ -101,6 +102,27 @@ export default defineCommand({
101102

102103
await addModules(resolvedModules, { ...ctx.args, cwd }, projectPkg)
103104

105+
// Check for agent skills
106+
if (!ctx.args.skipInstall) {
107+
const moduleNames = resolvedModules.map(m => m.pkgName)
108+
const checkSpinner = spinner()
109+
checkSpinner.start('Checking for agent skills...')
110+
const skillInfos = await detectModuleSkills(moduleNames, cwd)
111+
checkSpinner.stop(skillInfos.length > 0 ? `Found ${skillInfos.length} skill(s)` : 'No skills found')
112+
113+
if (skillInfos.length > 0) {
114+
const skillNames = getSkillNames(skillInfos)
115+
const shouldInstall = await confirm({
116+
message: `Install agent skill(s): ${skillNames}?`,
117+
initialValue: true,
118+
})
119+
120+
if (!isCancel(shouldInstall) && shouldInstall) {
121+
await installSkills(skillInfos, cwd)
122+
}
123+
}
124+
}
125+
104126
// Run prepare command if install is not skipped
105127
if (!ctx.args.skipInstall) {
106128
const args = Object.entries(ctx.args).filter(([k]) => k in cwdArgs || k in logLevelArgs).map(([k, v]) => `--${k}=${v}`)

0 commit comments

Comments
 (0)