@wabot-dev/create 0.0.2 → 0.0.3

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 ADDED
@@ -0,0 +1,189 @@
1
+ # `@wabot-dev/create`
2
+
3
+ CLI para:
4
+
5
+ - crear un nuevo proyecto Wabot desde el template oficial
6
+ - instalar skills empaquetadas en distintos homes o targets de agentes
7
+
8
+ ## Instalación
9
+
10
+ ```bash
11
+ npm install
12
+ ```
13
+
14
+ Para probar el binario local:
15
+
16
+ ```bash
17
+ node ./bin/create-wabot.mjs --help
18
+ ```
19
+
20
+ ## Comando principal
21
+
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:
35
+
36
+ ```bash
37
+ 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>
51
+ ```
52
+
53
+ Skill disponible actualmente:
54
+
55
+ ```bash
56
+ create-wabot skill add wabot-framework
57
+ ```
58
+
59
+ ## Variaciones de `skill add`
60
+
61
+ ### 1. Instalación global por defecto
62
+
63
+ Usa el home del sistema y, si no se indica target, instala para `codex`.
64
+
65
+ ```bash
66
+ create-wabot skill add wabot-framework
67
+ ```
68
+
69
+ Resultado esperado:
70
+
71
+ - `<os.homedir()>/.codex/skills/wabot-framework`
72
+
73
+ ### 2. Instalación local al proyecto
74
+
75
+ Instala en el workspace actual:
76
+
77
+ ```bash
78
+ create-wabot skill add wabot-framework --local
79
+ ```
80
+
81
+ Resultado esperado:
82
+
83
+ - `./.agents/skills/wabot-framework`
84
+
85
+ ### 3. Instalación global con home explícito
86
+
87
+ Instala usando un home root manual:
88
+
89
+ ```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
98
+ 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
108
+ ```
109
+
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
132
+
133
+ - `--local` y `--global` son excluyentes.
134
+ - `--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
+ ```
154
+
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
+ ```
181
+
182
+ ## Desarrollo
183
+
184
+ Mostrar ayuda:
185
+
186
+ ```bash
187
+ node ./bin/create-wabot.mjs --help
188
+ node ./bin/create-wabot.mjs skill add --help
189
+ ```
@@ -1,106 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { program } from 'commander'
4
3
  import chalk from 'chalk'
5
- import degit from 'degit'
6
- import prompts from 'prompts'
7
- import path from 'path'
8
- import fs from 'fs/promises'
4
+ import { buildProgram } from '../lib/cli.mjs'
9
5
 
10
- async function createProject(projectName, options) {
11
- try {
12
- // Get the absolute path for the project
13
- const targetDir = path.resolve(process.cwd(), projectName)
6
+ const program = buildProgram()
14
7
 
15
- // Check if directory exists
16
- try {
17
- await fs.access(targetDir)
18
- console.error(chalk.red(`Error: Directory ${projectName} already exists`))
19
- process.exit(1)
20
- } catch {
21
- // Directory doesn't exist, we can proceed
22
- }
23
-
24
- // Clone the template repository
25
- console.log(chalk.blue(`Creating a new Wabot project in ${chalk.green(targetDir)}`))
26
-
27
- const emitter = degit('wabot-dev/wabot-template', {
28
- cache: false,
29
- force: true,
30
- verbose: true,
31
- })
32
-
33
- await emitter.clone(targetDir)
34
-
35
- // Read the template's package.json
36
- const packageJsonPath = path.join(targetDir, 'package.json')
37
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'))
38
-
39
- // Update package.json with the new project name
40
- packageJson.name = projectName
41
-
42
- // Write the updated package.json
43
- await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
44
-
45
- // Create .env file
46
- await createEnvFile(targetDir, options.dbUrl, options.llmUrl, options.apiKey)
47
-
48
- console.log(chalk.green('\nSuccess! 🎉 Created', projectName))
49
- console.log(chalk.yellow('\n .env file add DATABASE_URL, WABOT_LLM_URL, WABOT_API_KEY'))
50
- console.log('\nInside that directory, you can run several commands:')
51
- console.log(chalk.cyan('\n npm install'))
52
- console.log(' Install the project dependencies')
53
- console.log(chalk.cyan('\n npm start'))
54
- console.log(' Starts the development server')
55
- console.log('\nWe suggest that you begin by typing:')
56
- console.log(chalk.cyan(`\n cd ${projectName}`))
57
- console.log(chalk.cyan(' npm install'))
58
- console.log(chalk.cyan(' npm start'))
59
- console.log('\nHappy hacking! 🚀\n')
60
- } catch (error) {
61
- console.error(chalk.red('Error:'), error)
62
- process.exit(1)
63
- }
8
+ try {
9
+ await program.parseAsync(process.argv)
10
+ } catch (error) {
11
+ console.error(chalk.red('Error:'), error.message)
12
+ process.exit(1)
64
13
  }
65
-
66
- async function createEnvFile(targetDir, dbUrl, llmUrl, apiKey) {
67
- const envContent = `
68
- DATABASE_URL=${dbUrl}
69
- WABOT_LLM_URL=${llmUrl}
70
- WABOT_API_KEY=${apiKey}
71
- `
72
-
73
- await fs.writeFile(path.join(targetDir, '.env'), envContent.trim())
74
- }
75
-
76
- // Set up the CLI
77
- program
78
- .name('create-wabot')
79
- .description('Create a new Wabot project')
80
- .argument('[project-directory]', 'project name')
81
- .option('--db-url <dbUrl>', 'database url')
82
- .option('--llm-url <llmUrl>', 'llm url')
83
- .option('--api-key <key>', 'api key')
84
- .action(async (projectDirectory, options) => {
85
- let projectName = projectDirectory
86
-
87
- if (!projectName) {
88
- const response = await prompts({
89
- type: 'text',
90
- name: 'projectName',
91
- message: 'What is your project named?',
92
- initial: 'my-wabot-app',
93
- })
94
-
95
- if (!response.projectName) {
96
- console.log(chalk.red('Project name is required'))
97
- process.exit(1)
98
- }
99
-
100
- projectName = response.projectName
101
- }
102
-
103
- await createProject(projectName, options)
104
- })
105
-
106
- program.parse()
package/lib/cli.mjs ADDED
@@ -0,0 +1,49 @@
1
+ import { Command } from 'commander'
2
+ import chalk from 'chalk'
3
+
4
+ import { runCreateProjectCommand } from './create-project.mjs'
5
+ import { installSkill } from './skills/install.mjs'
6
+
7
+ export function buildProgram() {
8
+ const program = new Command()
9
+
10
+ program
11
+ .name('create-wabot')
12
+ .description('Create a new Wabot project and install packaged Wabot skills')
13
+ .argument('[project-directory]', 'project name')
14
+ .option('--db-url <dbUrl>', 'postgres connection string (used as default when prompting)')
15
+ .action(async (projectDirectory, options) => {
16
+ await runCreateProjectCommand(projectDirectory, options)
17
+ })
18
+
19
+ const skillCommand = program.command('skill').description('Manage packaged Wabot skills')
20
+
21
+ skillCommand
22
+ .command('add <skill-name>')
23
+ .description('Install a packaged Wabot skill')
24
+ .option('--local', 'install into ./.agents/skills/<skill-name>')
25
+ .option('--global', 'install into one or more agent homes under the selected home root')
26
+ .option('--home <path>', 'global home root; defaults to $HOME')
27
+ .option('--target <name>', 'single global target (codex, claude, agents)')
28
+ .option('--targets <names>', 'comma-separated global targets (codex, claude, agents)')
29
+ .action(async (skillName, options) => {
30
+ const result = await installSkill(skillName, options)
31
+
32
+ if (result.mode === 'global') {
33
+ console.log(chalk.blue(`Home root: ${result.homeRoot}`))
34
+ } else {
35
+ console.log(chalk.blue('Agent target: local workspace (.agents/skills)'))
36
+ }
37
+
38
+ for (const installation of result.installations) {
39
+ console.log(
40
+ chalk.green(
41
+ `Installed ${result.skill.name} to ${chalk.cyan(installation.installDir)}`,
42
+ ),
43
+ )
44
+ console.log(chalk.blue(`Target: ${installation.agent}`))
45
+ }
46
+ })
47
+
48
+ return program
49
+ }
@@ -0,0 +1,299 @@
1
+ import chalk from 'chalk'
2
+ import degit from 'degit'
3
+ import prompts from 'prompts'
4
+ import path from 'path'
5
+ import fs from 'fs/promises'
6
+ import { spawn } from 'child_process'
7
+
8
+ const REQUIRED_NODE_MAJOR = 22
9
+
10
+ const AI_PROVIDERS = [
11
+ {
12
+ value: 'openrouter',
13
+ title: 'OpenRouter',
14
+ description: 'Used by the included Pixel example',
15
+ envKey: 'OPENROUTER_API_KEY',
16
+ },
17
+ {
18
+ value: 'openai',
19
+ title: 'OpenAI',
20
+ description: 'GPT models via the official OpenAI API',
21
+ envKey: 'OPENAI_API_KEY',
22
+ },
23
+ {
24
+ value: 'anthropic',
25
+ title: 'Anthropic',
26
+ description: 'Claude models',
27
+ envKey: 'ANTHROPIC_API_KEY',
28
+ },
29
+ {
30
+ value: 'google',
31
+ title: 'Google',
32
+ description: 'Gemini models',
33
+ envKey: 'GOOGLE_API_KEY',
34
+ },
35
+ ]
36
+
37
+ export async function runCreateProjectCommand(projectDirectory, options) {
38
+ printIntro()
39
+ ensureNodeVersion()
40
+
41
+ const projectName = await resolveProjectName(projectDirectory)
42
+ const setup = await collectSetup(options)
43
+
44
+ await createProject(projectName, setup)
45
+ await installDependencies(projectName)
46
+
47
+ printNextSteps(projectName, setup)
48
+ process.exit(0)
49
+ }
50
+
51
+ function printIntro() {
52
+ const line = chalk.gray('─'.repeat(60))
53
+ console.log()
54
+ console.log(line)
55
+ console.log(chalk.bold.cyan(' 🤖 Wabot project creator'))
56
+ console.log()
57
+ console.log(
58
+ ' This wizard will scaffold a new chat-bot project from the',
59
+ )
60
+ console.log(
61
+ ` ${chalk.cyan('@wabot-dev/template')} starter and configure your ${chalk.cyan('.env')}.`,
62
+ )
63
+ console.log()
64
+ console.log(' You will be asked about:')
65
+ console.log(' • where to store data (in-memory or PostgreSQL)')
66
+ console.log(' • which AI providers you want to use')
67
+ console.log(line)
68
+ console.log()
69
+ }
70
+
71
+ function ensureNodeVersion() {
72
+ const major = Number(process.versions.node.split('.')[0])
73
+ if (major === REQUIRED_NODE_MAJOR) {
74
+ return
75
+ }
76
+
77
+ console.log(
78
+ chalk.red(
79
+ `✖ Node.js ${REQUIRED_NODE_MAJOR} is required (you are running ${process.versions.node}).`,
80
+ ),
81
+ )
82
+ console.log()
83
+ console.log(
84
+ `The Wabot template pins Node ${REQUIRED_NODE_MAJOR} in its ${chalk.cyan('.nvmrc')}.`,
85
+ )
86
+ console.log(`We recommend installing it with ${chalk.cyan('nvm')}:`)
87
+ console.log()
88
+ console.log(chalk.cyan(` nvm install ${REQUIRED_NODE_MAJOR}`))
89
+ console.log(chalk.cyan(` nvm use ${REQUIRED_NODE_MAJOR}`))
90
+ console.log()
91
+ console.log(
92
+ `Don't have nvm? See ${chalk.underline('https://github.com/nvm-sh/nvm#installing-and-updating')}`,
93
+ )
94
+ console.log()
95
+ process.exit(1)
96
+ }
97
+
98
+ async function resolveProjectName(projectDirectory) {
99
+ if (projectDirectory) {
100
+ return projectDirectory
101
+ }
102
+
103
+ const response = await prompts({
104
+ type: 'text',
105
+ name: 'projectName',
106
+ message: 'What is your project named?',
107
+ initial: 'my-wabot-app',
108
+ })
109
+
110
+ if (!response.projectName) {
111
+ throw new Error('Project name is required')
112
+ }
113
+
114
+ return response.projectName
115
+ }
116
+
117
+ async function collectSetup(options) {
118
+ const onCancel = () => {
119
+ throw new Error('Setup cancelled')
120
+ }
121
+
122
+ const { storage } = await prompts(
123
+ {
124
+ type: 'select',
125
+ name: 'storage',
126
+ message: 'How do you want to store data?',
127
+ choices: [
128
+ {
129
+ title: 'In-memory (quickest start, data resets on restart)',
130
+ value: 'memory',
131
+ },
132
+ {
133
+ title: 'PostgreSQL (persistent, recommended for real bots)',
134
+ value: 'postgres',
135
+ },
136
+ ],
137
+ initial: 0,
138
+ },
139
+ { onCancel },
140
+ )
141
+
142
+ let databaseUrl = ''
143
+ if (storage === 'postgres') {
144
+ const dbResponse = await prompts(
145
+ {
146
+ type: 'text',
147
+ name: 'databaseUrl',
148
+ message: 'DATABASE_URL (leave blank to fill in later):',
149
+ initial: options.dbUrl ?? '',
150
+ },
151
+ { onCancel },
152
+ )
153
+ databaseUrl = dbResponse.databaseUrl ?? ''
154
+ }
155
+
156
+ const { providers } = await prompts(
157
+ {
158
+ type: 'multiselect',
159
+ name: 'providers',
160
+ message: 'Which AI providers will you use? (space to toggle, enter to confirm)',
161
+ choices: AI_PROVIDERS.map((p) => ({
162
+ title: p.title,
163
+ value: p.value,
164
+ description: p.description,
165
+ selected: p.value === 'openrouter',
166
+ })),
167
+ hint: 'At least one is required to run the included Pixel example',
168
+ instructions: false,
169
+ min: 1,
170
+ },
171
+ { onCancel },
172
+ )
173
+
174
+ const providerKeys = {}
175
+ for (const value of providers) {
176
+ const provider = AI_PROVIDERS.find((p) => p.value === value)
177
+ const { apiKey } = await prompts(
178
+ {
179
+ type: 'password',
180
+ name: 'apiKey',
181
+ message: `${provider.envKey} (leave blank to fill in later):`,
182
+ },
183
+ { onCancel },
184
+ )
185
+ providerKeys[provider.envKey] = apiKey ?? ''
186
+ }
187
+
188
+ return { storage, databaseUrl, providers, providerKeys }
189
+ }
190
+
191
+ async function createProject(projectName, setup) {
192
+ const targetDir = path.resolve(process.cwd(), projectName)
193
+
194
+ await ensureDirectoryDoesNotExist(targetDir, projectName)
195
+
196
+ console.log(chalk.blue(`\nCreating a new Wabot project in ${chalk.green(targetDir)}\n`))
197
+
198
+ const emitter = degit('wabot-dev/wabot-template', {
199
+ cache: false,
200
+ force: true,
201
+ verbose: true,
202
+ })
203
+
204
+ await emitter.clone(targetDir)
205
+
206
+ const packageJsonPath = path.join(targetDir, 'package.json')
207
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'))
208
+ packageJson.name = projectName
209
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n')
210
+
211
+ await writeEnvFile(targetDir, setup)
212
+ }
213
+
214
+ async function ensureDirectoryDoesNotExist(targetDir, projectName) {
215
+ try {
216
+ await fs.access(targetDir)
217
+ throw new Error(`Directory ${projectName} already exists`)
218
+ } catch (error) {
219
+ if (error?.code === 'ENOENT') {
220
+ return
221
+ }
222
+
223
+ throw error
224
+ }
225
+ }
226
+
227
+ async function writeEnvFile(targetDir, setup) {
228
+ const lines = []
229
+ lines.push('# debug important things')
230
+ lines.push('DEBUG=wabot:*:error,wabot:*:warn,wabot:*:info')
231
+ lines.push('')
232
+
233
+ if (setup.storage === 'postgres') {
234
+ lines.push('# postgres connection string')
235
+ lines.push(`DATABASE_URL=${setup.databaseUrl}`)
236
+ } else {
237
+ lines.push('# using in-memory storage (set DATABASE_URL to switch to postgres)')
238
+ lines.push('# DATABASE_URL=')
239
+ }
240
+ lines.push('')
241
+
242
+ lines.push('# AI provider keys')
243
+ for (const provider of AI_PROVIDERS) {
244
+ const selected = setup.providers.includes(provider.value)
245
+ if (selected) {
246
+ lines.push(`${provider.envKey}=${setup.providerKeys[provider.envKey] ?? ''}`)
247
+ } else {
248
+ lines.push(`# ${provider.envKey}=`)
249
+ }
250
+ }
251
+ lines.push('')
252
+
253
+ await fs.writeFile(path.join(targetDir, '.env'), lines.join('\n'))
254
+ }
255
+
256
+ async function installDependencies(projectName) {
257
+ const targetDir = path.resolve(process.cwd(), projectName)
258
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'
259
+
260
+ console.log(chalk.blue('\nInstalling dependencies with npm...\n'))
261
+
262
+ await new Promise((resolve, reject) => {
263
+ const child = spawn(npmCmd, ['install'], {
264
+ cwd: targetDir,
265
+ stdio: 'inherit',
266
+ })
267
+
268
+ child.on('error', reject)
269
+ child.on('close', (code) => {
270
+ if (code === 0) {
271
+ resolve()
272
+ } else {
273
+ reject(new Error(`npm install exited with code ${code}`))
274
+ }
275
+ })
276
+ })
277
+ }
278
+
279
+ function printNextSteps(projectName, setup) {
280
+ console.log(chalk.green(`\n✔ Project ${chalk.bold(projectName)} is ready.\n`))
281
+
282
+ const missingKeys = AI_PROVIDERS.filter(
283
+ (p) => setup.providers.includes(p.value) && !setup.providerKeys[p.envKey],
284
+ ).map((p) => p.envKey)
285
+
286
+ if (setup.storage === 'postgres' && !setup.databaseUrl) {
287
+ console.log(chalk.yellow(' ⚠ DATABASE_URL is empty — set it in .env before running.'))
288
+ }
289
+ if (missingKeys.length > 0) {
290
+ console.log(
291
+ chalk.yellow(` ⚠ Missing API keys in .env: ${missingKeys.join(', ')}`),
292
+ )
293
+ }
294
+
295
+ console.log('\nNext steps:')
296
+ console.log(chalk.cyan(`\n cd ${projectName}`))
297
+ console.log(chalk.cyan(' npm run dev'))
298
+ console.log('\nHappy hacking! 🚀\n')
299
+ }