@wabot-dev/create 0.0.4 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,189 +1,90 @@
1
1
  # `@wabot-dev/create`
2
2
 
3
- CLI para:
3
+ CLI para crear un nuevo proyecto Wabot desde el template oficial e instalar las
4
+ skills de agente que **vienen empaquetadas con `@wabot-dev/framework`**.
4
5
 
5
- - crear un nuevo proyecto Wabot desde el template oficial
6
- - instalar skills empaquetadas en distintos homes o targets de agentes
6
+ > Las skills ya no viven en este paquete: son propiedad de `@wabot-dev/framework`
7
+ > y se versionan junto con su API. Este CLI las descubre desde el framework
8
+ > instalado en el proyecto y las copia a `.claude/`, `.codex/` y/o `.agents/`.
7
9
 
8
- ## Instalación
10
+ ## Uso rápido
9
11
 
10
12
  ```bash
11
- npm install
13
+ npm create @wabot-dev my-wabot-app
14
+ # o
15
+ npx @wabot-dev/create my-wabot-app
12
16
  ```
13
17
 
14
- Para probar el binario local:
18
+ El asistente pregunta: nombre, almacenamiento (memoria/postgres), proveedores de
19
+ IA e instalación de skills de agente (por defecto sí). Luego:
15
20
 
16
- ```bash
17
- node ./bin/create-wabot.mjs --help
18
- ```
21
+ 1. clona el template oficial,
22
+ 2. corre `npm install` (deja `@wabot-dev/framework` en `node_modules`),
23
+ 3. copia las skills de ese framework a las carpetas de agente elegidas.
19
24
 
20
- ## Comando principal
25
+ Si no pasás `project-directory`, se pregunta el nombre en modo interactivo.
21
26
 
22
- Crear un proyecto nuevo:
23
-
24
- ```bash
25
- create-wabot <project-directory>
26
- ```
27
-
28
- Ejemplo:
29
-
30
- ```bash
31
- create-wabot my-wabot-app
32
- ```
33
-
34
- Variaciones:
27
+ Opciones:
35
28
 
36
29
  ```bash
37
30
  create-wabot my-wabot-app --db-url "postgres://localhost:5432/wabot"
38
- create-wabot my-wabot-app --llm-url "http://localhost:3000"
39
- create-wabot my-wabot-app --api-key "my-api-key"
40
- create-wabot my-wabot-app --db-url "postgres://localhost:5432/wabot" --llm-url "http://localhost:3000" --api-key "my-api-key"
41
- ```
42
-
43
- Si no pasás `project-directory`, el CLI pregunta el nombre del proyecto en modo interactivo.
44
-
45
- ## Comando de skills
46
-
47
- Instalar una skill empaquetada:
48
-
49
- ```bash
50
- create-wabot skill add <skill-name>
31
+ create-wabot my-wabot-app --lang es
51
32
  ```
52
33
 
53
- Skill disponible actualmente:
34
+ ## Canal beta
54
35
 
55
- ```bash
56
- create-wabot skill add wabot-framework
57
- ```
58
-
59
- ## Variaciones de `skill add`
36
+ Las builds beta de `@wabot-dev/create` (versión `X.Y.Z-beta.N`) scaffoldean desde
37
+ la rama **`beta`** del template (`wabot-dev/wabot-template#beta`), que fija la
38
+ versión beta del framework. Las builds estables usan la rama por defecto.
60
39
 
61
- ### 1. Instalación global por defecto
62
-
63
- Usa el home del sistema y, si no se indica target, instala para `codex`.
40
+ Para forzar una fuente concreta del template (p. ej. probar una rama antes de
41
+ publicar) usá la variable de entorno:
64
42
 
65
43
  ```bash
66
- create-wabot skill add wabot-framework
44
+ WABOT_TEMPLATE_REF="wabot-dev/wabot-template#beta" create-wabot my-app
67
45
  ```
68
46
 
69
- Resultado esperado:
70
-
71
- - `<os.homedir()>/.codex/skills/wabot-framework`
47
+ ## Skills en un proyecto existente
72
48
 
73
- ### 2. Instalación local al proyecto
74
-
75
- Instala en el workspace actual:
49
+ Las skills se instalan solas al crear el proyecto. Para **agregarlas o
50
+ actualizarlas** más tarde (por ejemplo tras subir la versión del framework), usá
51
+ el bin que ships el propio framework:
76
52
 
77
53
  ```bash
78
- create-wabot skill add wabot-framework --local
54
+ npx wabot-skills list # lista las skills empaquetadas
55
+ npx wabot-skills sync # refresca las skills del proyecto a la
56
+ # versión de framework instalada
57
+ npx wabot-skills sync --agents claude,codex
79
58
  ```
80
59
 
81
- Resultado esperado:
82
-
83
- - `./.agents/skills/wabot-framework`
60
+ `sync` sobrescribe en el lugar (resync); detecta las carpetas de agente ya
61
+ presentes en el proyecto (`.claude`, `.codex`, `.agents`) o usá `--agents`.
84
62
 
85
- ### 3. Instalación global con home explícito
63
+ ## `create-wabot skill add` (instalación puntual)
86
64
 
87
- Instala usando un home root manual:
65
+ También podés instalar una skill suelta con este CLI. La fuente es el
66
+ `@wabot-dev/framework` resoluble desde el proyecto/entorno actual.
88
67
 
89
68
  ```bash
90
- create-wabot skill add wabot-framework --global --home "/Users/demo"
91
- create-wabot skill add wabot-framework --global --home "C:\\Users\\demo"
92
- ```
93
-
94
- ### 4. Instalación global para un target único
95
-
96
- ```bash
97
- create-wabot skill add wabot-framework --global --target codex
69
+ create-wabot skill add wabot-framework --local # ./.agents/skills/...
98
70
  create-wabot skill add wabot-framework --global --target claude
99
- create-wabot skill add wabot-framework --global --target agents
100
- ```
101
-
102
- Con `--home`:
103
-
104
- ```bash
105
- create-wabot skill add wabot-framework --global --home "C:\\Users\\demo" --target codex
106
- create-wabot skill add wabot-framework --global --home "C:\\Users\\demo" --target claude
107
- create-wabot skill add wabot-framework --global --home "C:\\Users\\demo" --target agents
71
+ create-wabot skill add wabot-framework --global --targets agents,codex,claude --home "/Users/demo"
108
72
  ```
109
73
 
110
- Rutas esperadas:
111
-
112
- - `codex` -> `<home>/.codex/skills/wabot-framework`
113
- - `claude` -> `<home>/.claude/skills/wabot-framework`
114
- - `agents` -> `<home>/.agents/skills/wabot-framework`
115
-
116
- ### 5. Instalación global para múltiples targets
117
-
118
- ```bash
119
- create-wabot skill add wabot-framework --global --targets codex,claude
120
- create-wabot skill add wabot-framework --global --targets agents,codex,claude
121
- ```
122
-
123
- Con `--home`:
124
-
125
- ```bash
126
- create-wabot skill add wabot-framework --global --home "C:\\Users\\demo" --targets agents,codex,claude
127
- ```
128
-
129
- Esto instala la misma skill en múltiples destinos dentro del mismo home root.
130
-
131
- ## Reglas útiles
74
+ Reglas:
132
75
 
133
76
  - `--local` y `--global` son excluyentes.
134
77
  - `--home`, `--target` y `--targets` aplican solo con `--global`.
135
- - Si no se pasa `--target` ni `--targets`, el default es `codex`.
136
- - Si el destino ya existe, el comando falla y no sobreescribe.
137
- - Si el target no existe en la lista soportada, el comando falla.
138
-
139
- ## Targets soportados
140
-
141
- ```text
142
- agents
143
- codex
144
- claude
145
- ```
146
-
147
- ## Ejemplos completos
148
-
149
- Crear un proyecto:
150
-
151
- ```bash
152
- create-wabot my-bot
153
- ```
78
+ - Si no se pasa `--target`/`--targets`, el default es `codex`.
79
+ - Si el destino ya existe, falla y no sobrescribe (usá `wabot-skills sync` para refrescar).
154
80
 
155
- Crear un proyecto con variables iniciales:
156
-
157
- ```bash
158
- create-wabot my-bot \
159
- --db-url "postgres://localhost:5432/mybot" \
160
- --llm-url "http://localhost:3000" \
161
- --api-key "secret"
162
- ```
163
-
164
- Instalar la skill local al repo:
165
-
166
- ```bash
167
- create-wabot skill add wabot-framework --local
168
- ```
169
-
170
- Instalar la skill para Claude en otro home:
171
-
172
- ```bash
173
- create-wabot skill add wabot-framework --global --home "C:\\Users\\another-user" --target claude
174
- ```
175
-
176
- Instalar la skill para varios agentes en el mismo home:
177
-
178
- ```bash
179
- create-wabot skill add wabot-framework --global --home "C:\\Users\\another-user" --targets agents,codex,claude
180
- ```
81
+ Targets soportados: `agents`, `codex`, `claude`.
181
82
 
182
83
  ## Desarrollo
183
84
 
184
- Mostrar ayuda:
185
-
186
85
  ```bash
86
+ npm install # linkea @wabot-dev/framework (file:../wabot-ts) para tests
87
+ npm run test:smoke # smokes del instalador de skills
187
88
  node ./bin/create-wabot.mjs --help
188
89
  node ./bin/create-wabot.mjs skill add --help
189
90
  ```
@@ -6,11 +6,39 @@ import fs from 'fs/promises'
6
6
  import { spawn } from 'child_process'
7
7
 
8
8
  import { installSkillsInProject, listSupportedAgents } from './skills/install.mjs'
9
- import { listSkills } from './skills/registry.mjs'
10
9
  import { createTranslator } from './i18n.mjs'
11
10
 
12
11
  const REQUIRED_NODE_MAJOR = 22
13
12
 
13
+ const TEMPLATE_REPO = 'wabot-dev/wabot-template'
14
+
15
+ // Each starter variant lives on its own branch of the template repo, with a
16
+ // matching `*-beta` branch that pins the beta framework. Beta builds of
17
+ // @wabot-dev/create scaffold from the beta branch, stable builds from the
18
+ // default one. `WABOT_TEMPLATE_REF` overrides everything (e.g. to test a branch).
19
+ //
20
+ // variant stable branch beta branch
21
+ // full (default) beta
22
+ // empty empty empty-beta
23
+ const TEMPLATE_BRANCHES = {
24
+ full: { stable: '', beta: 'beta' },
25
+ empty: { stable: 'empty', beta: 'empty-beta' },
26
+ }
27
+
28
+ async function resolveTemplateRepo(variant = 'empty') {
29
+ if (process.env.WABOT_TEMPLATE_REF) {
30
+ return process.env.WABOT_TEMPLATE_REF
31
+ }
32
+
33
+ const pkgRaw = await fs.readFile(new URL('../package.json', import.meta.url), 'utf-8')
34
+ const version = JSON.parse(pkgRaw).version ?? ''
35
+ const isBeta = version.includes('-beta')
36
+
37
+ const branches = TEMPLATE_BRANCHES[variant] ?? TEMPLATE_BRANCHES.empty
38
+ const branch = isBeta ? branches.beta : branches.stable
39
+ return branch ? `${TEMPLATE_REPO}#${branch}` : TEMPLATE_REPO
40
+ }
41
+
14
42
  const AI_PROVIDERS = [
15
43
  {
16
44
  value: 'openrouter',
@@ -76,6 +104,7 @@ function printIntro(t) {
76
104
  )
77
105
  console.log()
78
106
  console.log(` ${t('intro.askAbout')}`)
107
+ console.log(` • ${t('intro.bullet.template')}`)
79
108
  console.log(` • ${t('intro.bullet.storage')}`)
80
109
  console.log(` • ${t('intro.bullet.providers')}`)
81
110
  console.log(line)
@@ -127,6 +156,20 @@ async function collectSetup(options, t) {
127
156
  throw new Error(t('project.cancelled'))
128
157
  }
129
158
 
159
+ const { template } = await prompts(
160
+ {
161
+ type: 'select',
162
+ name: 'template',
163
+ message: t('template.prompt'),
164
+ choices: [
165
+ { title: t('template.empty'), value: 'empty' },
166
+ { title: t('template.full'), value: 'full' },
167
+ ],
168
+ initial: 0,
169
+ },
170
+ { onCancel },
171
+ )
172
+
130
173
  const { storage } = await prompts(
131
174
  {
132
175
  type: 'select',
@@ -189,25 +232,24 @@ async function collectSetup(options, t) {
189
232
 
190
233
  const skills = await collectSkillsSetup({ onCancel }, t)
191
234
 
192
- return { storage, databaseUrl, providers, providerKeys, skills }
235
+ return { template, storage, databaseUrl, providers, providerKeys, skills }
193
236
  }
194
237
 
195
238
  async function collectSkillsSetup({ onCancel }, t) {
196
239
  const supportedAgents = listSupportedAgents()
197
- const availableSkills = listSkills()
198
240
 
199
241
  const { install } = await prompts(
200
242
  {
201
243
  type: 'confirm',
202
244
  name: 'install',
203
- message: t('skills.installPrompt', availableSkills.length),
245
+ message: t('skills.installPrompt'),
204
246
  initial: true,
205
247
  },
206
248
  { onCancel },
207
249
  )
208
250
 
209
251
  if (!install) {
210
- return { install: false, agents: [], skills: [] }
252
+ return { install: false, agents: [], skills: [], exclude: [] }
211
253
  }
212
254
 
213
255
  const { agents } = await prompts(
@@ -227,10 +269,26 @@ async function collectSkillsSetup({ onCancel }, t) {
227
269
  { onCancel },
228
270
  )
229
271
 
272
+ // Always offer the design-system skill explicitly — it's opt-in regardless of
273
+ // the chosen starter.
274
+ const { installDesign } = await prompts(
275
+ {
276
+ type: 'confirm',
277
+ name: 'installDesign',
278
+ message: t('design.prompt'),
279
+ initial: true,
280
+ },
281
+ { onCancel },
282
+ )
283
+
284
+ // Empty skills list = install every skill the framework ships (resolved at
285
+ // install time from @wabot-dev/framework in the new project); `exclude` drops
286
+ // wabot-design when the dev opts out.
230
287
  return {
231
288
  install: true,
232
289
  agents,
233
- skills: availableSkills.map((skill) => skill.name),
290
+ skills: [],
291
+ exclude: installDesign ? [] : ['wabot-design'],
234
292
  }
235
293
  }
236
294
 
@@ -241,7 +299,12 @@ async function createProject(projectName, setup, t) {
241
299
 
242
300
  console.log(chalk.blue(t('project.creating', chalk.green(targetDir))))
243
301
 
244
- const emitter = degit('wabot-dev/wabot-template', {
302
+ const templateRepo = await resolveTemplateRepo(setup.template)
303
+ if (templateRepo !== TEMPLATE_REPO) {
304
+ console.log(chalk.gray(t('project.templateSource', templateRepo)))
305
+ }
306
+
307
+ const emitter = degit(templateRepo, {
245
308
  cache: false,
246
309
  force: true,
247
310
  verbose: true,
@@ -287,9 +350,13 @@ async function writeEnvFile(targetDir, setup, t) {
287
350
 
288
351
  lines.push(t('env.providersComment'))
289
352
  for (const provider of AI_PROVIDERS) {
353
+ const apiKey = setup.providerKeys[provider.envKey]
290
354
  const selected = setup.providers.includes(provider.value)
291
- if (selected) {
292
- lines.push(`${provider.envKey}=${setup.providerKeys[provider.envKey] ?? ''}`)
355
+ // The framework refuses to boot on a key that is defined but empty, so a
356
+ // provider chosen without a key ships commented out like an unchosen one —
357
+ // the project still runs, and printNextSteps warns which keys to fill in.
358
+ if (selected && apiKey) {
359
+ lines.push(`${provider.envKey}=${apiKey}`)
293
360
  } else {
294
361
  lines.push(`# ${provider.envKey}=`)
295
362
  }
@@ -328,7 +395,12 @@ async function installAgentSkills(projectName, skillsSetup, t) {
328
395
  console.log(chalk.blue(t('skills.installing')))
329
396
 
330
397
  try {
331
- const result = await installSkillsInProject(targetDir, skillsSetup.agents, skillsSetup.skills)
398
+ const result = await installSkillsInProject(
399
+ targetDir,
400
+ skillsSetup.agents,
401
+ skillsSetup.skills,
402
+ skillsSetup.exclude,
403
+ )
332
404
 
333
405
  const installed = result.installations.filter((i) => i.status === 'installed')
334
406
  const skipped = result.installations.filter((i) => i.status === 'skipped')
@@ -381,6 +453,6 @@ function printNextSteps(projectName, setup, t) {
381
453
  console.log(chalk.cyan(`\n cd ${projectName}`))
382
454
  console.log(chalk.cyan(' npm run dev:watch'))
383
455
  console.log(t('next.cmdChannelHint'))
384
- console.log(chalk.cyan('\n npm run cmd:channel'))
456
+ console.log(chalk.cyan('\n npm run cmd'))
385
457
  console.log(t('next.happy'))
386
458
  }
package/lib/i18n.mjs CHANGED
@@ -4,9 +4,15 @@ const STRINGS = {
4
4
  'intro.line1': 'This wizard will scaffold a new chat-bot project from the',
5
5
  'intro.line2': (template, env) => `${template} starter and configure your ${env}.`,
6
6
  'intro.askAbout': 'You will be asked about:',
7
+ 'intro.bullet.template': 'which starter to scaffold (empty or full example)',
7
8
  'intro.bullet.storage': 'where to store data (in-memory or PostgreSQL)',
8
9
  'intro.bullet.providers': 'which AI providers you want to use',
9
10
 
11
+ 'template.prompt': 'Which starter do you want?',
12
+ 'template.empty': 'Empty (just the runner — build from scratch with the skills)',
13
+ 'template.full': 'Full example (local chat bot + web UI, no third-party channels)',
14
+ 'design.prompt': 'Also install the wabot-design design-system skill?',
15
+
10
16
  'node.required': (required, current) =>
11
17
  `✖ Node.js ${required} is required (you are running ${current}).`,
12
18
  'node.templatePin': (required, nvmrc) =>
@@ -19,6 +25,7 @@ const STRINGS = {
19
25
  'project.cancelled': 'Setup cancelled',
20
26
  'project.exists': (name) => `Directory ${name} already exists`,
21
27
  'project.creating': (dir) => `\nCreating a new Wabot project in ${dir}\n`,
28
+ 'project.templateSource': (repo) => ` Using template source: ${repo}`,
22
29
 
23
30
  'storage.prompt': 'How do you want to store data?',
24
31
  'storage.memory': 'In-memory (quickest start, data resets on restart)',
@@ -33,8 +40,7 @@ const STRINGS = {
33
40
  'providers.anthropic': 'Claude models',
34
41
  'providers.google': 'Gemini models',
35
42
 
36
- 'skills.installPrompt': (count) =>
37
- `Install Wabot agent skills (${count}) into the new project?`,
43
+ 'skills.installPrompt': 'Install Wabot agent skills into the new project?',
38
44
  'skills.agentsPrompt': 'Install for which agents? (space to toggle, enter to confirm)',
39
45
  'skills.agentsHint': 'Each adds a folder of <agent>/skills under the project root',
40
46
  'skills.installing': '\nInstalling Wabot agent skills...\n',
@@ -44,7 +50,7 @@ const STRINGS = {
44
50
  ` ⚠ ${count} skill folder(s) already existed and were left untouched.`,
45
51
  'skills.failed': (msg) => ` ✖ Failed to install agent skills: ${msg}`,
46
52
  'skills.installLater':
47
- ' You can install them later with `create-wabot skill add <skill>`.',
53
+ ' You can install/refresh them later with `npx wabot-skills sync`.',
48
54
 
49
55
  'deps.installing': '\nInstalling dependencies with npm...\n',
50
56
  'deps.exitCode': (code) => `npm install exited with code ${code}`,
@@ -74,9 +80,15 @@ const STRINGS = {
74
80
  'intro.line2': (template, env) =>
75
81
  `la plantilla ${template} y configurará tu archivo ${env}.`,
76
82
  'intro.askAbout': 'Te preguntaremos sobre:',
83
+ 'intro.bullet.template': 'con qué plantilla empezar (vacía o ejemplo completo)',
77
84
  'intro.bullet.storage': 'dónde almacenar los datos (en memoria o PostgreSQL)',
78
85
  'intro.bullet.providers': 'qué proveedores de IA deseas usar',
79
86
 
87
+ 'template.prompt': '¿Con qué plantilla quieres empezar?',
88
+ 'template.empty': 'Vacía (solo el runner — construye desde cero con las skills)',
89
+ 'template.full': 'Ejemplo completo (chat bot local + UI web, sin canales de terceros)',
90
+ 'design.prompt': '¿Instalar también la skill del sistema de diseño wabot-design?',
91
+
80
92
  'node.required': (required, current) =>
81
93
  `✖ Se requiere Node.js ${required} (estás usando ${current}).`,
82
94
  'node.templatePin': (required, nvmrc) =>
@@ -89,6 +101,7 @@ const STRINGS = {
89
101
  'project.cancelled': 'Configuración cancelada',
90
102
  'project.exists': (name) => `El directorio ${name} ya existe`,
91
103
  'project.creating': (dir) => `\nCreando un nuevo proyecto Wabot en ${dir}\n`,
104
+ 'project.templateSource': (repo) => ` Usando fuente del template: ${repo}`,
92
105
 
93
106
  'storage.prompt': '¿Cómo deseas almacenar los datos?',
94
107
  'storage.memory': 'En memoria (inicio más rápido, los datos se pierden al reiniciar)',
@@ -106,8 +119,7 @@ const STRINGS = {
106
119
  'providers.anthropic': 'Modelos Claude',
107
120
  'providers.google': 'Modelos Gemini',
108
121
 
109
- 'skills.installPrompt': (count) =>
110
- `¿Instalar las skills de agente de Wabot (${count}) en el nuevo proyecto?`,
122
+ 'skills.installPrompt': '¿Instalar las skills de agente de Wabot en el nuevo proyecto?',
111
123
  'skills.agentsPrompt':
112
124
  '¿Para qué agentes instalarlas? (espacio para alternar, enter para confirmar)',
113
125
  'skills.agentsHint':
@@ -119,7 +131,7 @@ const STRINGS = {
119
131
  ` ⚠ ${count} carpeta(s) de skill ya existían y no se modificaron.`,
120
132
  'skills.failed': (msg) => ` ✖ Fallo al instalar las skills de agente: ${msg}`,
121
133
  'skills.installLater':
122
- ' Puedes instalarlas más tarde con `create-wabot skill add <skill>`.',
134
+ ' Puedes instalarlas/actualizarlas más tarde con `npx wabot-skills sync`.',
123
135
 
124
136
  'deps.installing': '\nInstalando dependencias con npm...\n',
125
137
  'deps.exitCode': (code) => `npm install terminó con código ${code}`,
@@ -0,0 +1,68 @@
1
+ import { createRequire } from 'module'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+ import { fileURLToPath } from 'url'
5
+
6
+ const require = createRequire(import.meta.url)
7
+
8
+ const FRAMEWORK_PKG = '@wabot-dev/framework'
9
+ const FRAMEWORK_SEGMENTS = FRAMEWORK_PKG.split('/')
10
+
11
+ function packageRootFromEntry(entryPath) {
12
+ let dir = path.dirname(entryPath)
13
+ const { root } = path.parse(dir)
14
+ while (dir && dir !== root) {
15
+ const pkgFile = path.join(dir, 'package.json')
16
+ if (fs.existsSync(pkgFile)) {
17
+ try {
18
+ if (JSON.parse(fs.readFileSync(pkgFile, 'utf8')).name === FRAMEWORK_PKG) {
19
+ return dir
20
+ }
21
+ } catch {
22
+ // ignore malformed package.json and keep walking up
23
+ }
24
+ }
25
+ dir = path.dirname(dir)
26
+ }
27
+ return null
28
+ }
29
+
30
+ // Locate the packaged `skills/` directory shipped by @wabot-dev/framework.
31
+ //
32
+ // The framework owns the skills and versions them with its API. When bootstrapping
33
+ // or resyncing a project, resolve them from the framework installed *in that
34
+ // project* (`fromDir`) so the copied skills match the project's pinned framework
35
+ // version. For standalone use (e.g. `skill add`) fall back to the framework
36
+ // resolvable from this package.
37
+ export function resolveFrameworkSkillsDir(fromDir) {
38
+ const candidates = []
39
+
40
+ if (fromDir) {
41
+ candidates.push(path.join(fromDir, 'node_modules', ...FRAMEWORK_SEGMENTS, 'skills'))
42
+ }
43
+
44
+ try {
45
+ const pkgRoot = packageRootFromEntry(require.resolve(FRAMEWORK_PKG))
46
+ if (pkgRoot) candidates.push(path.join(pkgRoot, 'skills'))
47
+ } catch {
48
+ // framework not resolvable via node resolution; try the node_modules lookups below
49
+ }
50
+
51
+ const createPkgRoot = fileURLToPath(new URL('../../', import.meta.url))
52
+ candidates.push(path.join(createPkgRoot, 'node_modules', ...FRAMEWORK_SEGMENTS, 'skills'))
53
+ candidates.push(path.join(process.cwd(), 'node_modules', ...FRAMEWORK_SEGMENTS, 'skills'))
54
+
55
+ // Monorepo dev fallback: the framework lives beside this package as a sibling
56
+ // checkout (`../wabot-ts`). Used so smoke tests can resolve skills without the
57
+ // framework being installed as a dependency; installed lookups above win.
58
+ candidates.push(path.join(createPkgRoot, '..', 'wabot-ts', 'skills'))
59
+
60
+ for (const dir of candidates) {
61
+ if (dir && fs.existsSync(dir)) return dir
62
+ }
63
+
64
+ throw new Error(
65
+ `Could not locate ${FRAMEWORK_PKG} skills. Ensure ${FRAMEWORK_PKG} is installed ` +
66
+ `(the packaged skills ship with the framework as of the version that added them).`,
67
+ )
68
+ }
@@ -3,6 +3,7 @@ import os from 'os'
3
3
  import path from 'path'
4
4
 
5
5
  import { getSkill, listSkillNames, listSkills } from './registry.mjs'
6
+ import { resolveFrameworkSkillsDir } from './framework-source.mjs'
6
7
 
7
8
  const supportedAgents = {
8
9
  codex: ['.codex', 'skills'],
@@ -14,7 +15,7 @@ export function listSupportedAgents() {
14
15
  return Object.keys(supportedAgents)
15
16
  }
16
17
 
17
- export async function installSkillsInProject(projectDir, agents, skillNames) {
18
+ export async function installSkillsInProject(projectDir, agents, skillNames, excludeSkills = []) {
18
19
  if (!projectDir) {
19
20
  throw new Error('installSkillsInProject: projectDir is required')
20
21
  }
@@ -28,8 +29,13 @@ export async function installSkillsInProject(projectDir, agents, skillNames) {
28
29
  return agent
29
30
  })
30
31
 
31
- const skills =
32
- skillNames && skillNames.length > 0 ? skillNames.map((name) => requireSkill(name)) : listSkills()
32
+ const skillsDir = resolveFrameworkSkillsDir(projectDir)
33
+ const excluded = new Set(excludeSkills ?? [])
34
+ const skills = (
35
+ skillNames && skillNames.length > 0
36
+ ? skillNames.map((name) => requireSkill(name, skillsDir))
37
+ : listSkills(skillsDir)
38
+ ).filter((skill) => !excluded.has(skill.name))
33
39
 
34
40
  const installations = []
35
41
 
@@ -59,22 +65,23 @@ export async function installSkillsInProject(projectDir, agents, skillNames) {
59
65
  return { agents: resolvedAgents, skills: skills.map((skill) => skill.name), installations }
60
66
  }
61
67
 
62
- function requireSkill(name) {
63
- const skill = getSkill(name)
68
+ function requireSkill(name, skillsDir) {
69
+ const skill = getSkill(name, skillsDir)
64
70
  if (!skill) {
65
71
  throw new Error(
66
- `Unknown skill "${name}". Available skills: ${listSkillNames().join(', ')}`,
72
+ `Unknown skill "${name}". Available skills: ${listSkillNames(skillsDir).join(', ')}`,
67
73
  )
68
74
  }
69
75
  return skill
70
76
  }
71
77
 
72
78
  export async function installSkill(skillName, options, cwd = process.cwd()) {
73
- const skill = getSkill(skillName)
79
+ const skillsDir = resolveFrameworkSkillsDir(cwd)
80
+ const skill = getSkill(skillName, skillsDir)
74
81
 
75
82
  if (!skill) {
76
83
  throw new Error(
77
- `Unknown skill "${skillName}". Available skills: ${listSkillNames().join(', ')}`,
84
+ `Unknown skill "${skillName}". Available skills: ${listSkillNames(skillsDir).join(', ')}`,
78
85
  )
79
86
  }
80
87