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