@wabot-dev/create 0.0.3 → 0.0.4
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/lib/cli.mjs +1 -0
- package/lib/create-project.mjs +161 -74
- package/lib/i18n.mjs +167 -0
- package/lib/skills/install.mjs +60 -1
- package/lib/skills/registry.mjs +53 -6
- package/package.json +1 -1
- package/skills/wabot-async/SKILL.md +139 -0
- package/skills/wabot-auth/SKILL.md +149 -0
- package/skills/wabot-chat/SKILL.md +136 -0
- package/skills/wabot-di-config/SKILL.md +117 -0
- package/skills/wabot-framework/SKILL.md +69 -22
- package/skills/wabot-framework/references/quickstart.md +71 -42
- package/skills/wabot-mindset/SKILL.md +155 -0
- package/skills/wabot-ops/SKILL.md +151 -0
- package/skills/wabot-persistence/SKILL.md +155 -0
- package/skills/wabot-rest-socket/SKILL.md +163 -0
- package/skills/wabot-validation/SKILL.md +104 -0
- package/skills/wabot-framework/agents/openai.yaml +0 -5
- package/skills/wabot-framework/references/async-and-cron.md +0 -57
- package/skills/wabot-framework/references/controllers-and-auth.md +0 -104
- package/skills/wabot-framework/references/core-patterns.md +0 -83
- package/skills/wabot-framework/references/full-example.md +0 -278
- package/skills/wabot-framework/references/mindset-and-chat.md +0 -97
- package/skills/wabot-framework/references/ops.md +0 -58
- package/skills/wabot-framework/references/persistence.md +0 -85
- package/skills/wabot-framework/references/validation-and-data.md +0 -88
package/lib/cli.mjs
CHANGED
|
@@ -12,6 +12,7 @@ export function buildProgram() {
|
|
|
12
12
|
.description('Create a new Wabot project and install packaged Wabot skills')
|
|
13
13
|
.argument('[project-directory]', 'project name')
|
|
14
14
|
.option('--db-url <dbUrl>', 'postgres connection string (used as default when prompting)')
|
|
15
|
+
.option('--lang <code>', 'language for wizard messages (en, es)', 'en')
|
|
15
16
|
.action(async (projectDirectory, options) => {
|
|
16
17
|
await runCreateProjectCommand(projectDirectory, options)
|
|
17
18
|
})
|
package/lib/create-project.mjs
CHANGED
|
@@ -5,97 +5,105 @@ import path from 'path'
|
|
|
5
5
|
import fs from 'fs/promises'
|
|
6
6
|
import { spawn } from 'child_process'
|
|
7
7
|
|
|
8
|
+
import { installSkillsInProject, listSupportedAgents } from './skills/install.mjs'
|
|
9
|
+
import { listSkills } from './skills/registry.mjs'
|
|
10
|
+
import { createTranslator } from './i18n.mjs'
|
|
11
|
+
|
|
8
12
|
const REQUIRED_NODE_MAJOR = 22
|
|
9
13
|
|
|
10
14
|
const AI_PROVIDERS = [
|
|
11
15
|
{
|
|
12
16
|
value: 'openrouter',
|
|
13
17
|
title: 'OpenRouter',
|
|
14
|
-
|
|
18
|
+
descriptionKey: 'providers.openrouter',
|
|
15
19
|
envKey: 'OPENROUTER_API_KEY',
|
|
16
20
|
},
|
|
17
21
|
{
|
|
18
22
|
value: 'openai',
|
|
19
23
|
title: 'OpenAI',
|
|
20
|
-
|
|
24
|
+
descriptionKey: 'providers.openai',
|
|
21
25
|
envKey: 'OPENAI_API_KEY',
|
|
22
26
|
},
|
|
23
27
|
{
|
|
24
28
|
value: 'anthropic',
|
|
25
29
|
title: 'Anthropic',
|
|
26
|
-
|
|
30
|
+
descriptionKey: 'providers.anthropic',
|
|
27
31
|
envKey: 'ANTHROPIC_API_KEY',
|
|
28
32
|
},
|
|
29
33
|
{
|
|
30
34
|
value: 'google',
|
|
31
35
|
title: 'Google',
|
|
32
|
-
|
|
36
|
+
descriptionKey: 'providers.google',
|
|
33
37
|
envKey: 'GOOGLE_API_KEY',
|
|
34
38
|
},
|
|
35
39
|
]
|
|
36
40
|
|
|
41
|
+
function agentLabel(t, agent) {
|
|
42
|
+
const key = `agent.label.${agent}`
|
|
43
|
+
const value = t(key)
|
|
44
|
+
return value === key ? agent : value
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
export async function runCreateProjectCommand(projectDirectory, options) {
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
const t = createTranslator(options?.lang)
|
|
49
|
+
|
|
50
|
+
printIntro(t)
|
|
51
|
+
ensureNodeVersion(t)
|
|
40
52
|
|
|
41
|
-
const projectName = await resolveProjectName(projectDirectory)
|
|
42
|
-
const setup = await collectSetup(options)
|
|
53
|
+
const projectName = await resolveProjectName(projectDirectory, t)
|
|
54
|
+
const setup = await collectSetup(options, t)
|
|
43
55
|
|
|
44
|
-
await createProject(projectName, setup)
|
|
45
|
-
await installDependencies(projectName)
|
|
56
|
+
await createProject(projectName, setup, t)
|
|
57
|
+
await installDependencies(projectName, t)
|
|
46
58
|
|
|
47
|
-
|
|
59
|
+
if (setup.skills.install) {
|
|
60
|
+
await installAgentSkills(projectName, setup.skills, t)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
printNextSteps(projectName, setup, t)
|
|
48
64
|
process.exit(0)
|
|
49
65
|
}
|
|
50
66
|
|
|
51
|
-
function printIntro() {
|
|
67
|
+
function printIntro(t) {
|
|
52
68
|
const line = chalk.gray('─'.repeat(60))
|
|
53
69
|
console.log()
|
|
54
70
|
console.log(line)
|
|
55
|
-
console.log(chalk.bold.cyan(
|
|
71
|
+
console.log(chalk.bold.cyan(` ${t('intro.title')}`))
|
|
56
72
|
console.log()
|
|
73
|
+
console.log(` ${t('intro.line1')}`)
|
|
57
74
|
console.log(
|
|
58
|
-
'
|
|
59
|
-
)
|
|
60
|
-
console.log(
|
|
61
|
-
` ${chalk.cyan('@wabot-dev/template')} starter and configure your ${chalk.cyan('.env')}.`,
|
|
75
|
+
` ${t('intro.line2', chalk.cyan('@wabot-dev/template'), chalk.cyan('.env'))}`,
|
|
62
76
|
)
|
|
63
77
|
console.log()
|
|
64
|
-
console.log(
|
|
65
|
-
console.log(
|
|
66
|
-
console.log(
|
|
78
|
+
console.log(` ${t('intro.askAbout')}`)
|
|
79
|
+
console.log(` • ${t('intro.bullet.storage')}`)
|
|
80
|
+
console.log(` • ${t('intro.bullet.providers')}`)
|
|
67
81
|
console.log(line)
|
|
68
82
|
console.log()
|
|
69
83
|
}
|
|
70
84
|
|
|
71
|
-
function ensureNodeVersion() {
|
|
85
|
+
function ensureNodeVersion(t) {
|
|
72
86
|
const major = Number(process.versions.node.split('.')[0])
|
|
73
87
|
if (major === REQUIRED_NODE_MAJOR) {
|
|
74
88
|
return
|
|
75
89
|
}
|
|
76
90
|
|
|
77
|
-
console.log(
|
|
78
|
-
chalk.red(
|
|
79
|
-
`✖ Node.js ${REQUIRED_NODE_MAJOR} is required (you are running ${process.versions.node}).`,
|
|
80
|
-
),
|
|
81
|
-
)
|
|
91
|
+
console.log(chalk.red(t('node.required', REQUIRED_NODE_MAJOR, process.versions.node)))
|
|
82
92
|
console.log()
|
|
83
|
-
console.log(
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
console.log(`We recommend installing it with ${chalk.cyan('nvm')}:`)
|
|
93
|
+
console.log(t('node.templatePin', REQUIRED_NODE_MAJOR, chalk.cyan('.nvmrc')))
|
|
94
|
+
console.log(t('node.installHint', chalk.cyan('nvm')))
|
|
87
95
|
console.log()
|
|
88
96
|
console.log(chalk.cyan(` nvm install ${REQUIRED_NODE_MAJOR}`))
|
|
89
97
|
console.log(chalk.cyan(` nvm use ${REQUIRED_NODE_MAJOR}`))
|
|
90
98
|
console.log()
|
|
91
99
|
console.log(
|
|
92
|
-
|
|
100
|
+
t('node.noNvm', chalk.underline('https://github.com/nvm-sh/nvm#installing-and-updating')),
|
|
93
101
|
)
|
|
94
102
|
console.log()
|
|
95
103
|
process.exit(1)
|
|
96
104
|
}
|
|
97
105
|
|
|
98
|
-
async function resolveProjectName(projectDirectory) {
|
|
106
|
+
async function resolveProjectName(projectDirectory, t) {
|
|
99
107
|
if (projectDirectory) {
|
|
100
108
|
return projectDirectory
|
|
101
109
|
}
|
|
@@ -103,36 +111,30 @@ async function resolveProjectName(projectDirectory) {
|
|
|
103
111
|
const response = await prompts({
|
|
104
112
|
type: 'text',
|
|
105
113
|
name: 'projectName',
|
|
106
|
-
message: '
|
|
114
|
+
message: t('project.namePrompt'),
|
|
107
115
|
initial: 'my-wabot-app',
|
|
108
116
|
})
|
|
109
117
|
|
|
110
118
|
if (!response.projectName) {
|
|
111
|
-
throw new Error('
|
|
119
|
+
throw new Error(t('project.nameRequired'))
|
|
112
120
|
}
|
|
113
121
|
|
|
114
122
|
return response.projectName
|
|
115
123
|
}
|
|
116
124
|
|
|
117
|
-
async function collectSetup(options) {
|
|
125
|
+
async function collectSetup(options, t) {
|
|
118
126
|
const onCancel = () => {
|
|
119
|
-
throw new Error('
|
|
127
|
+
throw new Error(t('project.cancelled'))
|
|
120
128
|
}
|
|
121
129
|
|
|
122
130
|
const { storage } = await prompts(
|
|
123
131
|
{
|
|
124
132
|
type: 'select',
|
|
125
133
|
name: 'storage',
|
|
126
|
-
message: '
|
|
134
|
+
message: t('storage.prompt'),
|
|
127
135
|
choices: [
|
|
128
|
-
{
|
|
129
|
-
|
|
130
|
-
value: 'memory',
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
title: 'PostgreSQL (persistent, recommended for real bots)',
|
|
134
|
-
value: 'postgres',
|
|
135
|
-
},
|
|
136
|
+
{ title: t('storage.memory'), value: 'memory' },
|
|
137
|
+
{ title: t('storage.postgres'), value: 'postgres' },
|
|
136
138
|
],
|
|
137
139
|
initial: 0,
|
|
138
140
|
},
|
|
@@ -145,7 +147,7 @@ async function collectSetup(options) {
|
|
|
145
147
|
{
|
|
146
148
|
type: 'text',
|
|
147
149
|
name: 'databaseUrl',
|
|
148
|
-
message: '
|
|
150
|
+
message: t('storage.dbUrlPrompt'),
|
|
149
151
|
initial: options.dbUrl ?? '',
|
|
150
152
|
},
|
|
151
153
|
{ onCancel },
|
|
@@ -157,14 +159,14 @@ async function collectSetup(options) {
|
|
|
157
159
|
{
|
|
158
160
|
type: 'multiselect',
|
|
159
161
|
name: 'providers',
|
|
160
|
-
message: '
|
|
162
|
+
message: t('providers.prompt'),
|
|
161
163
|
choices: AI_PROVIDERS.map((p) => ({
|
|
162
164
|
title: p.title,
|
|
163
165
|
value: p.value,
|
|
164
|
-
description: p.
|
|
166
|
+
description: t(p.descriptionKey),
|
|
165
167
|
selected: p.value === 'openrouter',
|
|
166
168
|
})),
|
|
167
|
-
hint: '
|
|
169
|
+
hint: t('providers.hint'),
|
|
168
170
|
instructions: false,
|
|
169
171
|
min: 1,
|
|
170
172
|
},
|
|
@@ -178,22 +180,66 @@ async function collectSetup(options) {
|
|
|
178
180
|
{
|
|
179
181
|
type: 'password',
|
|
180
182
|
name: 'apiKey',
|
|
181
|
-
message:
|
|
183
|
+
message: t('providers.apiKeyPrompt', provider.envKey),
|
|
182
184
|
},
|
|
183
185
|
{ onCancel },
|
|
184
186
|
)
|
|
185
187
|
providerKeys[provider.envKey] = apiKey ?? ''
|
|
186
188
|
}
|
|
187
189
|
|
|
188
|
-
|
|
190
|
+
const skills = await collectSkillsSetup({ onCancel }, t)
|
|
191
|
+
|
|
192
|
+
return { storage, databaseUrl, providers, providerKeys, skills }
|
|
189
193
|
}
|
|
190
194
|
|
|
191
|
-
async function
|
|
195
|
+
async function collectSkillsSetup({ onCancel }, t) {
|
|
196
|
+
const supportedAgents = listSupportedAgents()
|
|
197
|
+
const availableSkills = listSkills()
|
|
198
|
+
|
|
199
|
+
const { install } = await prompts(
|
|
200
|
+
{
|
|
201
|
+
type: 'confirm',
|
|
202
|
+
name: 'install',
|
|
203
|
+
message: t('skills.installPrompt', availableSkills.length),
|
|
204
|
+
initial: true,
|
|
205
|
+
},
|
|
206
|
+
{ onCancel },
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if (!install) {
|
|
210
|
+
return { install: false, agents: [], skills: [] }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const { agents } = await prompts(
|
|
214
|
+
{
|
|
215
|
+
type: 'multiselect',
|
|
216
|
+
name: 'agents',
|
|
217
|
+
message: t('skills.agentsPrompt'),
|
|
218
|
+
choices: supportedAgents.map((agent) => ({
|
|
219
|
+
title: agentLabel(t, agent),
|
|
220
|
+
value: agent,
|
|
221
|
+
selected: true,
|
|
222
|
+
})),
|
|
223
|
+
hint: t('skills.agentsHint'),
|
|
224
|
+
instructions: false,
|
|
225
|
+
min: 1,
|
|
226
|
+
},
|
|
227
|
+
{ onCancel },
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
install: true,
|
|
232
|
+
agents,
|
|
233
|
+
skills: availableSkills.map((skill) => skill.name),
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function createProject(projectName, setup, t) {
|
|
192
238
|
const targetDir = path.resolve(process.cwd(), projectName)
|
|
193
239
|
|
|
194
|
-
await ensureDirectoryDoesNotExist(targetDir, projectName)
|
|
240
|
+
await ensureDirectoryDoesNotExist(targetDir, projectName, t)
|
|
195
241
|
|
|
196
|
-
console.log(chalk.blue(
|
|
242
|
+
console.log(chalk.blue(t('project.creating', chalk.green(targetDir))))
|
|
197
243
|
|
|
198
244
|
const emitter = degit('wabot-dev/wabot-template', {
|
|
199
245
|
cache: false,
|
|
@@ -208,13 +254,13 @@ async function createProject(projectName, setup) {
|
|
|
208
254
|
packageJson.name = projectName
|
|
209
255
|
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n')
|
|
210
256
|
|
|
211
|
-
await writeEnvFile(targetDir, setup)
|
|
257
|
+
await writeEnvFile(targetDir, setup, t)
|
|
212
258
|
}
|
|
213
259
|
|
|
214
|
-
async function ensureDirectoryDoesNotExist(targetDir, projectName) {
|
|
260
|
+
async function ensureDirectoryDoesNotExist(targetDir, projectName, t) {
|
|
215
261
|
try {
|
|
216
262
|
await fs.access(targetDir)
|
|
217
|
-
throw new Error(
|
|
263
|
+
throw new Error(t('project.exists', projectName))
|
|
218
264
|
} catch (error) {
|
|
219
265
|
if (error?.code === 'ENOENT') {
|
|
220
266
|
return
|
|
@@ -224,22 +270,22 @@ async function ensureDirectoryDoesNotExist(targetDir, projectName) {
|
|
|
224
270
|
}
|
|
225
271
|
}
|
|
226
272
|
|
|
227
|
-
async function writeEnvFile(targetDir, setup) {
|
|
273
|
+
async function writeEnvFile(targetDir, setup, t) {
|
|
228
274
|
const lines = []
|
|
229
|
-
lines.push('
|
|
275
|
+
lines.push(t('env.debugComment'))
|
|
230
276
|
lines.push('DEBUG=wabot:*:error,wabot:*:warn,wabot:*:info')
|
|
231
277
|
lines.push('')
|
|
232
278
|
|
|
233
279
|
if (setup.storage === 'postgres') {
|
|
234
|
-
lines.push('
|
|
280
|
+
lines.push(t('env.postgresComment'))
|
|
235
281
|
lines.push(`DATABASE_URL=${setup.databaseUrl}`)
|
|
236
282
|
} else {
|
|
237
|
-
lines.push(
|
|
283
|
+
lines.push(t('env.inMemoryComment'))
|
|
238
284
|
lines.push('# DATABASE_URL=')
|
|
239
285
|
}
|
|
240
286
|
lines.push('')
|
|
241
287
|
|
|
242
|
-
lines.push('
|
|
288
|
+
lines.push(t('env.providersComment'))
|
|
243
289
|
for (const provider of AI_PROVIDERS) {
|
|
244
290
|
const selected = setup.providers.includes(provider.value)
|
|
245
291
|
if (selected) {
|
|
@@ -253,11 +299,11 @@ async function writeEnvFile(targetDir, setup) {
|
|
|
253
299
|
await fs.writeFile(path.join(targetDir, '.env'), lines.join('\n'))
|
|
254
300
|
}
|
|
255
301
|
|
|
256
|
-
async function installDependencies(projectName) {
|
|
302
|
+
async function installDependencies(projectName, t) {
|
|
257
303
|
const targetDir = path.resolve(process.cwd(), projectName)
|
|
258
304
|
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
259
305
|
|
|
260
|
-
console.log(chalk.blue('
|
|
306
|
+
console.log(chalk.blue(t('deps.installing')))
|
|
261
307
|
|
|
262
308
|
await new Promise((resolve, reject) => {
|
|
263
309
|
const child = spawn(npmCmd, ['install'], {
|
|
@@ -270,30 +316,71 @@ async function installDependencies(projectName) {
|
|
|
270
316
|
if (code === 0) {
|
|
271
317
|
resolve()
|
|
272
318
|
} else {
|
|
273
|
-
reject(new Error(
|
|
319
|
+
reject(new Error(t('deps.exitCode', code)))
|
|
274
320
|
}
|
|
275
321
|
})
|
|
276
322
|
})
|
|
277
323
|
}
|
|
278
324
|
|
|
279
|
-
function
|
|
280
|
-
|
|
325
|
+
async function installAgentSkills(projectName, skillsSetup, t) {
|
|
326
|
+
const targetDir = path.resolve(process.cwd(), projectName)
|
|
327
|
+
|
|
328
|
+
console.log(chalk.blue(t('skills.installing')))
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const result = await installSkillsInProject(targetDir, skillsSetup.agents, skillsSetup.skills)
|
|
332
|
+
|
|
333
|
+
const installed = result.installations.filter((i) => i.status === 'installed')
|
|
334
|
+
const skipped = result.installations.filter((i) => i.status === 'skipped')
|
|
335
|
+
|
|
336
|
+
if (installed.length > 0) {
|
|
337
|
+
const byAgent = new Map()
|
|
338
|
+
for (const item of installed) {
|
|
339
|
+
const list = byAgent.get(item.agent) ?? []
|
|
340
|
+
list.push(item.skill)
|
|
341
|
+
byAgent.set(item.agent, list)
|
|
342
|
+
}
|
|
343
|
+
for (const [agent, names] of byAgent) {
|
|
344
|
+
const dir = path.join(projectName, ...resolveAgentDir(agent))
|
|
345
|
+
console.log(chalk.green(t('skills.installed', agent, names.length, dir)))
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (skipped.length > 0) {
|
|
349
|
+
console.log(chalk.yellow(t('skills.skipped', skipped.length)))
|
|
350
|
+
}
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.log(chalk.red(t('skills.failed', error.message)))
|
|
353
|
+
console.log(chalk.yellow(t('skills.installLater')))
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function resolveAgentDir(agent) {
|
|
358
|
+
switch (agent) {
|
|
359
|
+
case 'claude': return ['.claude', 'skills']
|
|
360
|
+
case 'codex': return ['.codex', 'skills']
|
|
361
|
+
case 'agents': return ['.agents', 'skills']
|
|
362
|
+
default: return [`.${agent}`, 'skills']
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function printNextSteps(projectName, setup, t) {
|
|
367
|
+
console.log(chalk.green(t('next.ready', chalk.bold(projectName))))
|
|
281
368
|
|
|
282
369
|
const missingKeys = AI_PROVIDERS.filter(
|
|
283
370
|
(p) => setup.providers.includes(p.value) && !setup.providerKeys[p.envKey],
|
|
284
371
|
).map((p) => p.envKey)
|
|
285
372
|
|
|
286
373
|
if (setup.storage === 'postgres' && !setup.databaseUrl) {
|
|
287
|
-
console.log(chalk.yellow('
|
|
374
|
+
console.log(chalk.yellow(t('next.dbUrlEmpty')))
|
|
288
375
|
}
|
|
289
376
|
if (missingKeys.length > 0) {
|
|
290
|
-
console.log(
|
|
291
|
-
chalk.yellow(` ⚠ Missing API keys in .env: ${missingKeys.join(', ')}`),
|
|
292
|
-
)
|
|
377
|
+
console.log(chalk.yellow(t('next.missingKeys', missingKeys.join(', '))))
|
|
293
378
|
}
|
|
294
379
|
|
|
295
|
-
console.log('
|
|
380
|
+
console.log(t('next.heading'))
|
|
296
381
|
console.log(chalk.cyan(`\n cd ${projectName}`))
|
|
297
|
-
console.log(chalk.cyan(' npm run dev'))
|
|
298
|
-
console.log('
|
|
382
|
+
console.log(chalk.cyan(' npm run dev:watch'))
|
|
383
|
+
console.log(t('next.cmdChannelHint'))
|
|
384
|
+
console.log(chalk.cyan('\n npm run cmd:channel'))
|
|
385
|
+
console.log(t('next.happy'))
|
|
299
386
|
}
|
package/lib/i18n.mjs
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const STRINGS = {
|
|
2
|
+
en: {
|
|
3
|
+
'intro.title': '🤖 Wabot project creator',
|
|
4
|
+
'intro.line1': 'This wizard will scaffold a new chat-bot project from the',
|
|
5
|
+
'intro.line2': (template, env) => `${template} starter and configure your ${env}.`,
|
|
6
|
+
'intro.askAbout': 'You will be asked about:',
|
|
7
|
+
'intro.bullet.storage': 'where to store data (in-memory or PostgreSQL)',
|
|
8
|
+
'intro.bullet.providers': 'which AI providers you want to use',
|
|
9
|
+
|
|
10
|
+
'node.required': (required, current) =>
|
|
11
|
+
`✖ Node.js ${required} is required (you are running ${current}).`,
|
|
12
|
+
'node.templatePin': (required, nvmrc) =>
|
|
13
|
+
`The Wabot template pins Node ${required} in its ${nvmrc}.`,
|
|
14
|
+
'node.installHint': (nvm) => `We recommend installing it with ${nvm}:`,
|
|
15
|
+
'node.noNvm': (url) => `Don't have nvm? See ${url}`,
|
|
16
|
+
|
|
17
|
+
'project.namePrompt': 'What is your project named?',
|
|
18
|
+
'project.nameRequired': 'Project name is required',
|
|
19
|
+
'project.cancelled': 'Setup cancelled',
|
|
20
|
+
'project.exists': (name) => `Directory ${name} already exists`,
|
|
21
|
+
'project.creating': (dir) => `\nCreating a new Wabot project in ${dir}\n`,
|
|
22
|
+
|
|
23
|
+
'storage.prompt': 'How do you want to store data?',
|
|
24
|
+
'storage.memory': 'In-memory (quickest start, data resets on restart)',
|
|
25
|
+
'storage.postgres': 'PostgreSQL (persistent, recommended for real bots)',
|
|
26
|
+
'storage.dbUrlPrompt': 'DATABASE_URL (leave blank to fill in later):',
|
|
27
|
+
|
|
28
|
+
'providers.prompt': 'Which AI providers will you use? (space to toggle, enter to confirm)',
|
|
29
|
+
'providers.hint': 'At least one is required to run the included Pixel example',
|
|
30
|
+
'providers.apiKeyPrompt': (envKey) => `${envKey} (leave blank to fill in later):`,
|
|
31
|
+
'providers.openrouter': 'Used by the included Pixel example',
|
|
32
|
+
'providers.openai': 'GPT models via the official OpenAI API',
|
|
33
|
+
'providers.anthropic': 'Claude models',
|
|
34
|
+
'providers.google': 'Gemini models',
|
|
35
|
+
|
|
36
|
+
'skills.installPrompt': (count) =>
|
|
37
|
+
`Install Wabot agent skills (${count}) into the new project?`,
|
|
38
|
+
'skills.agentsPrompt': 'Install for which agents? (space to toggle, enter to confirm)',
|
|
39
|
+
'skills.agentsHint': 'Each adds a folder of <agent>/skills under the project root',
|
|
40
|
+
'skills.installing': '\nInstalling Wabot agent skills...\n',
|
|
41
|
+
'skills.installed': (agent, count, dir) =>
|
|
42
|
+
` ✔ ${agent}: installed ${count} skill(s) into ${dir}`,
|
|
43
|
+
'skills.skipped': (count) =>
|
|
44
|
+
` ⚠ ${count} skill folder(s) already existed and were left untouched.`,
|
|
45
|
+
'skills.failed': (msg) => ` ✖ Failed to install agent skills: ${msg}`,
|
|
46
|
+
'skills.installLater':
|
|
47
|
+
' You can install them later with `create-wabot skill add <skill>`.',
|
|
48
|
+
|
|
49
|
+
'deps.installing': '\nInstalling dependencies with npm...\n',
|
|
50
|
+
'deps.exitCode': (code) => `npm install exited with code ${code}`,
|
|
51
|
+
|
|
52
|
+
'env.debugComment': '# debug important things',
|
|
53
|
+
'env.postgresComment': '# postgres connection string',
|
|
54
|
+
'env.inMemoryComment':
|
|
55
|
+
'# using in-memory storage (set DATABASE_URL to switch to postgres)',
|
|
56
|
+
'env.providersComment': '# AI provider keys',
|
|
57
|
+
|
|
58
|
+
'next.ready': (name) => `\n✔ Project ${name} is ready.\n`,
|
|
59
|
+
'next.dbUrlEmpty': ' ⚠ DATABASE_URL is empty — set it in .env before running.',
|
|
60
|
+
'next.missingKeys': (keys) => ` ⚠ Missing API keys in .env: ${keys}`,
|
|
61
|
+
'next.heading': '\nNext steps:',
|
|
62
|
+
'next.cmdChannelHint':
|
|
63
|
+
'\nIn another terminal, open a CLI to chat with your bot:',
|
|
64
|
+
'next.happy': '\nHappy hacking! 🚀\n',
|
|
65
|
+
|
|
66
|
+
'agent.label.claude': 'Claude Code (.claude/skills)',
|
|
67
|
+
'agent.label.codex': 'Codex (.codex/skills)',
|
|
68
|
+
'agent.label.agents': 'Generic agents (.agents/skills)',
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
es: {
|
|
72
|
+
'intro.title': '🤖 Creador de proyectos Wabot',
|
|
73
|
+
'intro.line1': 'Este asistente generará un nuevo proyecto de chat-bot a partir de',
|
|
74
|
+
'intro.line2': (template, env) =>
|
|
75
|
+
`la plantilla ${template} y configurará tu archivo ${env}.`,
|
|
76
|
+
'intro.askAbout': 'Te preguntaremos sobre:',
|
|
77
|
+
'intro.bullet.storage': 'dónde almacenar los datos (en memoria o PostgreSQL)',
|
|
78
|
+
'intro.bullet.providers': 'qué proveedores de IA deseas usar',
|
|
79
|
+
|
|
80
|
+
'node.required': (required, current) =>
|
|
81
|
+
`✖ Se requiere Node.js ${required} (estás usando ${current}).`,
|
|
82
|
+
'node.templatePin': (required, nvmrc) =>
|
|
83
|
+
`La plantilla de Wabot fija Node ${required} en su ${nvmrc}.`,
|
|
84
|
+
'node.installHint': (nvm) => `Recomendamos instalarlo con ${nvm}:`,
|
|
85
|
+
'node.noNvm': (url) => `¿No tienes nvm? Consulta ${url}`,
|
|
86
|
+
|
|
87
|
+
'project.namePrompt': '¿Cómo se llama tu proyecto?',
|
|
88
|
+
'project.nameRequired': 'El nombre del proyecto es obligatorio',
|
|
89
|
+
'project.cancelled': 'Configuración cancelada',
|
|
90
|
+
'project.exists': (name) => `El directorio ${name} ya existe`,
|
|
91
|
+
'project.creating': (dir) => `\nCreando un nuevo proyecto Wabot en ${dir}\n`,
|
|
92
|
+
|
|
93
|
+
'storage.prompt': '¿Cómo deseas almacenar los datos?',
|
|
94
|
+
'storage.memory': 'En memoria (inicio más rápido, los datos se pierden al reiniciar)',
|
|
95
|
+
'storage.postgres': 'PostgreSQL (persistente, recomendado para bots reales)',
|
|
96
|
+
'storage.dbUrlPrompt': 'DATABASE_URL (déjalo en blanco para completarlo después):',
|
|
97
|
+
|
|
98
|
+
'providers.prompt':
|
|
99
|
+
'¿Qué proveedores de IA usarás? (espacio para alternar, enter para confirmar)',
|
|
100
|
+
'providers.hint':
|
|
101
|
+
'Se requiere al menos uno para ejecutar el ejemplo Pixel incluido',
|
|
102
|
+
'providers.apiKeyPrompt': (envKey) =>
|
|
103
|
+
`${envKey} (déjalo en blanco para completarlo después):`,
|
|
104
|
+
'providers.openrouter': 'Usado por el ejemplo Pixel incluido',
|
|
105
|
+
'providers.openai': 'Modelos GPT mediante la API oficial de OpenAI',
|
|
106
|
+
'providers.anthropic': 'Modelos Claude',
|
|
107
|
+
'providers.google': 'Modelos Gemini',
|
|
108
|
+
|
|
109
|
+
'skills.installPrompt': (count) =>
|
|
110
|
+
`¿Instalar las skills de agente de Wabot (${count}) en el nuevo proyecto?`,
|
|
111
|
+
'skills.agentsPrompt':
|
|
112
|
+
'¿Para qué agentes instalarlas? (espacio para alternar, enter para confirmar)',
|
|
113
|
+
'skills.agentsHint':
|
|
114
|
+
'Cada uno añade una carpeta <agente>/skills en la raíz del proyecto',
|
|
115
|
+
'skills.installing': '\nInstalando skills de agente Wabot...\n',
|
|
116
|
+
'skills.installed': (agent, count, dir) =>
|
|
117
|
+
` ✔ ${agent}: se instalaron ${count} skill(s) en ${dir}`,
|
|
118
|
+
'skills.skipped': (count) =>
|
|
119
|
+
` ⚠ ${count} carpeta(s) de skill ya existían y no se modificaron.`,
|
|
120
|
+
'skills.failed': (msg) => ` ✖ Fallo al instalar las skills de agente: ${msg}`,
|
|
121
|
+
'skills.installLater':
|
|
122
|
+
' Puedes instalarlas más tarde con `create-wabot skill add <skill>`.',
|
|
123
|
+
|
|
124
|
+
'deps.installing': '\nInstalando dependencias con npm...\n',
|
|
125
|
+
'deps.exitCode': (code) => `npm install terminó con código ${code}`,
|
|
126
|
+
|
|
127
|
+
'env.debugComment': '# depurar lo importante',
|
|
128
|
+
'env.postgresComment': '# cadena de conexión de postgres',
|
|
129
|
+
'env.inMemoryComment':
|
|
130
|
+
'# usando almacenamiento en memoria (define DATABASE_URL para cambiar a postgres)',
|
|
131
|
+
'env.providersComment': '# claves de proveedores de IA',
|
|
132
|
+
|
|
133
|
+
'next.ready': (name) => `\n✔ El proyecto ${name} está listo.\n`,
|
|
134
|
+
'next.dbUrlEmpty':
|
|
135
|
+
' ⚠ DATABASE_URL está vacío — defínelo en .env antes de ejecutar.',
|
|
136
|
+
'next.missingKeys': (keys) => ` ⚠ Faltan claves de API en .env: ${keys}`,
|
|
137
|
+
'next.heading': '\nPróximos pasos:',
|
|
138
|
+
'next.cmdChannelHint':
|
|
139
|
+
'\nEn otra terminal, abre una CLI para chatear con tu bot:',
|
|
140
|
+
'next.happy': '\n¡Feliz desarrollo! 🚀\n',
|
|
141
|
+
|
|
142
|
+
'agent.label.claude': 'Claude Code (.claude/skills)',
|
|
143
|
+
'agent.label.codex': 'Codex (.codex/skills)',
|
|
144
|
+
'agent.label.agents': 'Agentes genéricos (.agents/skills)',
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const SUPPORTED_LANGS = Object.keys(STRINGS)
|
|
149
|
+
export const DEFAULT_LANG = 'en'
|
|
150
|
+
|
|
151
|
+
export function resolveLang(lang) {
|
|
152
|
+
if (!lang) return DEFAULT_LANG
|
|
153
|
+
const normalized = String(lang).toLowerCase()
|
|
154
|
+
return SUPPORTED_LANGS.includes(normalized) ? normalized : DEFAULT_LANG
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function createTranslator(lang) {
|
|
158
|
+
const resolved = resolveLang(lang)
|
|
159
|
+
const dict = STRINGS[resolved]
|
|
160
|
+
const fallback = STRINGS[DEFAULT_LANG]
|
|
161
|
+
|
|
162
|
+
return function t(key, ...args) {
|
|
163
|
+
const value = dict[key] ?? fallback[key]
|
|
164
|
+
if (value === undefined) return key
|
|
165
|
+
return typeof value === 'function' ? value(...args) : value
|
|
166
|
+
}
|
|
167
|
+
}
|
package/lib/skills/install.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'fs/promises'
|
|
|
2
2
|
import os from 'os'
|
|
3
3
|
import path from 'path'
|
|
4
4
|
|
|
5
|
-
import { getSkill, listSkillNames } from './registry.mjs'
|
|
5
|
+
import { getSkill, listSkillNames, listSkills } from './registry.mjs'
|
|
6
6
|
|
|
7
7
|
const supportedAgents = {
|
|
8
8
|
codex: ['.codex', 'skills'],
|
|
@@ -10,6 +10,65 @@ const supportedAgents = {
|
|
|
10
10
|
agents: ['.agents', 'skills'],
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export function listSupportedAgents() {
|
|
14
|
+
return Object.keys(supportedAgents)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function installSkillsInProject(projectDir, agents, skillNames) {
|
|
18
|
+
if (!projectDir) {
|
|
19
|
+
throw new Error('installSkillsInProject: projectDir is required')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const resolvedAgents = (agents && agents.length > 0 ? agents : ['claude']).map((agent) => {
|
|
23
|
+
if (!supportedAgents[agent]) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Unsupported agent "${agent}". Supported: ${Object.keys(supportedAgents).join(', ')}`,
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
return agent
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const skills =
|
|
32
|
+
skillNames && skillNames.length > 0 ? skillNames.map((name) => requireSkill(name)) : listSkills()
|
|
33
|
+
|
|
34
|
+
const installations = []
|
|
35
|
+
|
|
36
|
+
for (const agent of resolvedAgents) {
|
|
37
|
+
const baseDir = path.join(projectDir, ...supportedAgents[agent])
|
|
38
|
+
await fs.mkdir(baseDir, { recursive: true })
|
|
39
|
+
|
|
40
|
+
for (const skill of skills) {
|
|
41
|
+
const installDir = path.join(baseDir, skill.name)
|
|
42
|
+
try {
|
|
43
|
+
await fs.cp(skill.sourceDir, installDir, {
|
|
44
|
+
recursive: true,
|
|
45
|
+
force: false,
|
|
46
|
+
errorOnExist: true,
|
|
47
|
+
})
|
|
48
|
+
installations.push({ agent, skill: skill.name, installDir, status: 'installed' })
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error?.code === 'ERR_FS_CP_EEXIST' || error?.code === 'EEXIST') {
|
|
51
|
+
installations.push({ agent, skill: skill.name, installDir, status: 'skipped' })
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
throw error
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { agents: resolvedAgents, skills: skills.map((skill) => skill.name), installations }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function requireSkill(name) {
|
|
63
|
+
const skill = getSkill(name)
|
|
64
|
+
if (!skill) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Unknown skill "${name}". Available skills: ${listSkillNames().join(', ')}`,
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
return skill
|
|
70
|
+
}
|
|
71
|
+
|
|
13
72
|
export async function installSkill(skillName, options, cwd = process.cwd()) {
|
|
14
73
|
const skill = getSkill(skillName)
|
|
15
74
|
|