@wabot-dev/create 0.0.3 → 2.0.0-beta.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 +185 -75
- package/lib/i18n.mjs +167 -0
- package/lib/skills/framework-source.mjs +68 -0
- package/lib/skills/install.mjs +67 -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,122 @@ 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
|
+
const TEMPLATE_BETA_BRANCH = 'beta'
|
|
15
|
+
|
|
16
|
+
// Beta builds of @wabot-dev/create scaffold from the matching `beta` branch of the
|
|
17
|
+
// template (which pins the beta framework); stable builds use the default branch.
|
|
18
|
+
// `WABOT_TEMPLATE_REF` overrides both (e.g. to test a branch before release).
|
|
19
|
+
async function resolveTemplateRepo() {
|
|
20
|
+
if (process.env.WABOT_TEMPLATE_REF) {
|
|
21
|
+
return process.env.WABOT_TEMPLATE_REF
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const pkgRaw = await fs.readFile(new URL('../package.json', import.meta.url), 'utf-8')
|
|
25
|
+
const version = JSON.parse(pkgRaw).version ?? ''
|
|
26
|
+
const isBeta = version.includes('-beta')
|
|
27
|
+
|
|
28
|
+
return isBeta ? `${TEMPLATE_REPO}#${TEMPLATE_BETA_BRANCH}` : TEMPLATE_REPO
|
|
29
|
+
}
|
|
30
|
+
|
|
10
31
|
const AI_PROVIDERS = [
|
|
11
32
|
{
|
|
12
33
|
value: 'openrouter',
|
|
13
34
|
title: 'OpenRouter',
|
|
14
|
-
|
|
35
|
+
descriptionKey: 'providers.openrouter',
|
|
15
36
|
envKey: 'OPENROUTER_API_KEY',
|
|
16
37
|
},
|
|
17
38
|
{
|
|
18
39
|
value: 'openai',
|
|
19
40
|
title: 'OpenAI',
|
|
20
|
-
|
|
41
|
+
descriptionKey: 'providers.openai',
|
|
21
42
|
envKey: 'OPENAI_API_KEY',
|
|
22
43
|
},
|
|
23
44
|
{
|
|
24
45
|
value: 'anthropic',
|
|
25
46
|
title: 'Anthropic',
|
|
26
|
-
|
|
47
|
+
descriptionKey: 'providers.anthropic',
|
|
27
48
|
envKey: 'ANTHROPIC_API_KEY',
|
|
28
49
|
},
|
|
29
50
|
{
|
|
30
51
|
value: 'google',
|
|
31
52
|
title: 'Google',
|
|
32
|
-
|
|
53
|
+
descriptionKey: 'providers.google',
|
|
33
54
|
envKey: 'GOOGLE_API_KEY',
|
|
34
55
|
},
|
|
35
56
|
]
|
|
36
57
|
|
|
58
|
+
function agentLabel(t, agent) {
|
|
59
|
+
const key = `agent.label.${agent}`
|
|
60
|
+
const value = t(key)
|
|
61
|
+
return value === key ? agent : value
|
|
62
|
+
}
|
|
63
|
+
|
|
37
64
|
export async function runCreateProjectCommand(projectDirectory, options) {
|
|
38
|
-
|
|
39
|
-
|
|
65
|
+
const t = createTranslator(options?.lang)
|
|
66
|
+
|
|
67
|
+
printIntro(t)
|
|
68
|
+
ensureNodeVersion(t)
|
|
40
69
|
|
|
41
|
-
const projectName = await resolveProjectName(projectDirectory)
|
|
42
|
-
const setup = await collectSetup(options)
|
|
70
|
+
const projectName = await resolveProjectName(projectDirectory, t)
|
|
71
|
+
const setup = await collectSetup(options, t)
|
|
43
72
|
|
|
44
|
-
await createProject(projectName, setup)
|
|
45
|
-
await installDependencies(projectName)
|
|
73
|
+
await createProject(projectName, setup, t)
|
|
74
|
+
await installDependencies(projectName, t)
|
|
75
|
+
|
|
76
|
+
if (setup.skills.install) {
|
|
77
|
+
await installAgentSkills(projectName, setup.skills, t)
|
|
78
|
+
}
|
|
46
79
|
|
|
47
|
-
printNextSteps(projectName, setup)
|
|
80
|
+
printNextSteps(projectName, setup, t)
|
|
48
81
|
process.exit(0)
|
|
49
82
|
}
|
|
50
83
|
|
|
51
|
-
function printIntro() {
|
|
84
|
+
function printIntro(t) {
|
|
52
85
|
const line = chalk.gray('─'.repeat(60))
|
|
53
86
|
console.log()
|
|
54
87
|
console.log(line)
|
|
55
|
-
console.log(chalk.bold.cyan(
|
|
88
|
+
console.log(chalk.bold.cyan(` ${t('intro.title')}`))
|
|
56
89
|
console.log()
|
|
90
|
+
console.log(` ${t('intro.line1')}`)
|
|
57
91
|
console.log(
|
|
58
|
-
'
|
|
59
|
-
)
|
|
60
|
-
console.log(
|
|
61
|
-
` ${chalk.cyan('@wabot-dev/template')} starter and configure your ${chalk.cyan('.env')}.`,
|
|
92
|
+
` ${t('intro.line2', chalk.cyan('@wabot-dev/template'), chalk.cyan('.env'))}`,
|
|
62
93
|
)
|
|
63
94
|
console.log()
|
|
64
|
-
console.log(
|
|
65
|
-
console.log(
|
|
66
|
-
console.log(
|
|
95
|
+
console.log(` ${t('intro.askAbout')}`)
|
|
96
|
+
console.log(` • ${t('intro.bullet.storage')}`)
|
|
97
|
+
console.log(` • ${t('intro.bullet.providers')}`)
|
|
67
98
|
console.log(line)
|
|
68
99
|
console.log()
|
|
69
100
|
}
|
|
70
101
|
|
|
71
|
-
function ensureNodeVersion() {
|
|
102
|
+
function ensureNodeVersion(t) {
|
|
72
103
|
const major = Number(process.versions.node.split('.')[0])
|
|
73
104
|
if (major === REQUIRED_NODE_MAJOR) {
|
|
74
105
|
return
|
|
75
106
|
}
|
|
76
107
|
|
|
77
|
-
console.log(
|
|
78
|
-
chalk.red(
|
|
79
|
-
`✖ Node.js ${REQUIRED_NODE_MAJOR} is required (you are running ${process.versions.node}).`,
|
|
80
|
-
),
|
|
81
|
-
)
|
|
108
|
+
console.log(chalk.red(t('node.required', REQUIRED_NODE_MAJOR, process.versions.node)))
|
|
82
109
|
console.log()
|
|
83
|
-
console.log(
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
console.log(`We recommend installing it with ${chalk.cyan('nvm')}:`)
|
|
110
|
+
console.log(t('node.templatePin', REQUIRED_NODE_MAJOR, chalk.cyan('.nvmrc')))
|
|
111
|
+
console.log(t('node.installHint', chalk.cyan('nvm')))
|
|
87
112
|
console.log()
|
|
88
113
|
console.log(chalk.cyan(` nvm install ${REQUIRED_NODE_MAJOR}`))
|
|
89
114
|
console.log(chalk.cyan(` nvm use ${REQUIRED_NODE_MAJOR}`))
|
|
90
115
|
console.log()
|
|
91
116
|
console.log(
|
|
92
|
-
|
|
117
|
+
t('node.noNvm', chalk.underline('https://github.com/nvm-sh/nvm#installing-and-updating')),
|
|
93
118
|
)
|
|
94
119
|
console.log()
|
|
95
120
|
process.exit(1)
|
|
96
121
|
}
|
|
97
122
|
|
|
98
|
-
async function resolveProjectName(projectDirectory) {
|
|
123
|
+
async function resolveProjectName(projectDirectory, t) {
|
|
99
124
|
if (projectDirectory) {
|
|
100
125
|
return projectDirectory
|
|
101
126
|
}
|
|
@@ -103,36 +128,30 @@ async function resolveProjectName(projectDirectory) {
|
|
|
103
128
|
const response = await prompts({
|
|
104
129
|
type: 'text',
|
|
105
130
|
name: 'projectName',
|
|
106
|
-
message: '
|
|
131
|
+
message: t('project.namePrompt'),
|
|
107
132
|
initial: 'my-wabot-app',
|
|
108
133
|
})
|
|
109
134
|
|
|
110
135
|
if (!response.projectName) {
|
|
111
|
-
throw new Error('
|
|
136
|
+
throw new Error(t('project.nameRequired'))
|
|
112
137
|
}
|
|
113
138
|
|
|
114
139
|
return response.projectName
|
|
115
140
|
}
|
|
116
141
|
|
|
117
|
-
async function collectSetup(options) {
|
|
142
|
+
async function collectSetup(options, t) {
|
|
118
143
|
const onCancel = () => {
|
|
119
|
-
throw new Error('
|
|
144
|
+
throw new Error(t('project.cancelled'))
|
|
120
145
|
}
|
|
121
146
|
|
|
122
147
|
const { storage } = await prompts(
|
|
123
148
|
{
|
|
124
149
|
type: 'select',
|
|
125
150
|
name: 'storage',
|
|
126
|
-
message: '
|
|
151
|
+
message: t('storage.prompt'),
|
|
127
152
|
choices: [
|
|
128
|
-
{
|
|
129
|
-
|
|
130
|
-
value: 'memory',
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
title: 'PostgreSQL (persistent, recommended for real bots)',
|
|
134
|
-
value: 'postgres',
|
|
135
|
-
},
|
|
153
|
+
{ title: t('storage.memory'), value: 'memory' },
|
|
154
|
+
{ title: t('storage.postgres'), value: 'postgres' },
|
|
136
155
|
],
|
|
137
156
|
initial: 0,
|
|
138
157
|
},
|
|
@@ -145,7 +164,7 @@ async function collectSetup(options) {
|
|
|
145
164
|
{
|
|
146
165
|
type: 'text',
|
|
147
166
|
name: 'databaseUrl',
|
|
148
|
-
message: '
|
|
167
|
+
message: t('storage.dbUrlPrompt'),
|
|
149
168
|
initial: options.dbUrl ?? '',
|
|
150
169
|
},
|
|
151
170
|
{ onCancel },
|
|
@@ -157,14 +176,14 @@ async function collectSetup(options) {
|
|
|
157
176
|
{
|
|
158
177
|
type: 'multiselect',
|
|
159
178
|
name: 'providers',
|
|
160
|
-
message: '
|
|
179
|
+
message: t('providers.prompt'),
|
|
161
180
|
choices: AI_PROVIDERS.map((p) => ({
|
|
162
181
|
title: p.title,
|
|
163
182
|
value: p.value,
|
|
164
|
-
description: p.
|
|
183
|
+
description: t(p.descriptionKey),
|
|
165
184
|
selected: p.value === 'openrouter',
|
|
166
185
|
})),
|
|
167
|
-
hint: '
|
|
186
|
+
hint: t('providers.hint'),
|
|
168
187
|
instructions: false,
|
|
169
188
|
min: 1,
|
|
170
189
|
},
|
|
@@ -178,24 +197,74 @@ async function collectSetup(options) {
|
|
|
178
197
|
{
|
|
179
198
|
type: 'password',
|
|
180
199
|
name: 'apiKey',
|
|
181
|
-
message:
|
|
200
|
+
message: t('providers.apiKeyPrompt', provider.envKey),
|
|
182
201
|
},
|
|
183
202
|
{ onCancel },
|
|
184
203
|
)
|
|
185
204
|
providerKeys[provider.envKey] = apiKey ?? ''
|
|
186
205
|
}
|
|
187
206
|
|
|
188
|
-
|
|
207
|
+
const skills = await collectSkillsSetup({ onCancel }, t)
|
|
208
|
+
|
|
209
|
+
return { storage, databaseUrl, providers, providerKeys, skills }
|
|
189
210
|
}
|
|
190
211
|
|
|
191
|
-
async function
|
|
212
|
+
async function collectSkillsSetup({ onCancel }, t) {
|
|
213
|
+
const supportedAgents = listSupportedAgents()
|
|
214
|
+
|
|
215
|
+
const { install } = await prompts(
|
|
216
|
+
{
|
|
217
|
+
type: 'confirm',
|
|
218
|
+
name: 'install',
|
|
219
|
+
message: t('skills.installPrompt'),
|
|
220
|
+
initial: true,
|
|
221
|
+
},
|
|
222
|
+
{ onCancel },
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if (!install) {
|
|
226
|
+
return { install: false, agents: [], skills: [] }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const { agents } = await prompts(
|
|
230
|
+
{
|
|
231
|
+
type: 'multiselect',
|
|
232
|
+
name: 'agents',
|
|
233
|
+
message: t('skills.agentsPrompt'),
|
|
234
|
+
choices: supportedAgents.map((agent) => ({
|
|
235
|
+
title: agentLabel(t, agent),
|
|
236
|
+
value: agent,
|
|
237
|
+
selected: true,
|
|
238
|
+
})),
|
|
239
|
+
hint: t('skills.agentsHint'),
|
|
240
|
+
instructions: false,
|
|
241
|
+
min: 1,
|
|
242
|
+
},
|
|
243
|
+
{ onCancel },
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
// Empty list = install every skill the installed framework ships; the actual
|
|
247
|
+
// set is resolved at install time from @wabot-dev/framework in the new project.
|
|
248
|
+
return {
|
|
249
|
+
install: true,
|
|
250
|
+
agents,
|
|
251
|
+
skills: [],
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function createProject(projectName, setup, t) {
|
|
192
256
|
const targetDir = path.resolve(process.cwd(), projectName)
|
|
193
257
|
|
|
194
|
-
await ensureDirectoryDoesNotExist(targetDir, projectName)
|
|
258
|
+
await ensureDirectoryDoesNotExist(targetDir, projectName, t)
|
|
259
|
+
|
|
260
|
+
console.log(chalk.blue(t('project.creating', chalk.green(targetDir))))
|
|
195
261
|
|
|
196
|
-
|
|
262
|
+
const templateRepo = await resolveTemplateRepo()
|
|
263
|
+
if (templateRepo !== TEMPLATE_REPO) {
|
|
264
|
+
console.log(chalk.gray(t('project.templateSource', templateRepo)))
|
|
265
|
+
}
|
|
197
266
|
|
|
198
|
-
const emitter = degit(
|
|
267
|
+
const emitter = degit(templateRepo, {
|
|
199
268
|
cache: false,
|
|
200
269
|
force: true,
|
|
201
270
|
verbose: true,
|
|
@@ -208,13 +277,13 @@ async function createProject(projectName, setup) {
|
|
|
208
277
|
packageJson.name = projectName
|
|
209
278
|
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n')
|
|
210
279
|
|
|
211
|
-
await writeEnvFile(targetDir, setup)
|
|
280
|
+
await writeEnvFile(targetDir, setup, t)
|
|
212
281
|
}
|
|
213
282
|
|
|
214
|
-
async function ensureDirectoryDoesNotExist(targetDir, projectName) {
|
|
283
|
+
async function ensureDirectoryDoesNotExist(targetDir, projectName, t) {
|
|
215
284
|
try {
|
|
216
285
|
await fs.access(targetDir)
|
|
217
|
-
throw new Error(
|
|
286
|
+
throw new Error(t('project.exists', projectName))
|
|
218
287
|
} catch (error) {
|
|
219
288
|
if (error?.code === 'ENOENT') {
|
|
220
289
|
return
|
|
@@ -224,22 +293,22 @@ async function ensureDirectoryDoesNotExist(targetDir, projectName) {
|
|
|
224
293
|
}
|
|
225
294
|
}
|
|
226
295
|
|
|
227
|
-
async function writeEnvFile(targetDir, setup) {
|
|
296
|
+
async function writeEnvFile(targetDir, setup, t) {
|
|
228
297
|
const lines = []
|
|
229
|
-
lines.push('
|
|
298
|
+
lines.push(t('env.debugComment'))
|
|
230
299
|
lines.push('DEBUG=wabot:*:error,wabot:*:warn,wabot:*:info')
|
|
231
300
|
lines.push('')
|
|
232
301
|
|
|
233
302
|
if (setup.storage === 'postgres') {
|
|
234
|
-
lines.push('
|
|
303
|
+
lines.push(t('env.postgresComment'))
|
|
235
304
|
lines.push(`DATABASE_URL=${setup.databaseUrl}`)
|
|
236
305
|
} else {
|
|
237
|
-
lines.push(
|
|
306
|
+
lines.push(t('env.inMemoryComment'))
|
|
238
307
|
lines.push('# DATABASE_URL=')
|
|
239
308
|
}
|
|
240
309
|
lines.push('')
|
|
241
310
|
|
|
242
|
-
lines.push('
|
|
311
|
+
lines.push(t('env.providersComment'))
|
|
243
312
|
for (const provider of AI_PROVIDERS) {
|
|
244
313
|
const selected = setup.providers.includes(provider.value)
|
|
245
314
|
if (selected) {
|
|
@@ -253,11 +322,11 @@ async function writeEnvFile(targetDir, setup) {
|
|
|
253
322
|
await fs.writeFile(path.join(targetDir, '.env'), lines.join('\n'))
|
|
254
323
|
}
|
|
255
324
|
|
|
256
|
-
async function installDependencies(projectName) {
|
|
325
|
+
async function installDependencies(projectName, t) {
|
|
257
326
|
const targetDir = path.resolve(process.cwd(), projectName)
|
|
258
327
|
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
259
328
|
|
|
260
|
-
console.log(chalk.blue('
|
|
329
|
+
console.log(chalk.blue(t('deps.installing')))
|
|
261
330
|
|
|
262
331
|
await new Promise((resolve, reject) => {
|
|
263
332
|
const child = spawn(npmCmd, ['install'], {
|
|
@@ -270,30 +339,71 @@ async function installDependencies(projectName) {
|
|
|
270
339
|
if (code === 0) {
|
|
271
340
|
resolve()
|
|
272
341
|
} else {
|
|
273
|
-
reject(new Error(
|
|
342
|
+
reject(new Error(t('deps.exitCode', code)))
|
|
274
343
|
}
|
|
275
344
|
})
|
|
276
345
|
})
|
|
277
346
|
}
|
|
278
347
|
|
|
279
|
-
function
|
|
280
|
-
|
|
348
|
+
async function installAgentSkills(projectName, skillsSetup, t) {
|
|
349
|
+
const targetDir = path.resolve(process.cwd(), projectName)
|
|
350
|
+
|
|
351
|
+
console.log(chalk.blue(t('skills.installing')))
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
const result = await installSkillsInProject(targetDir, skillsSetup.agents, skillsSetup.skills)
|
|
355
|
+
|
|
356
|
+
const installed = result.installations.filter((i) => i.status === 'installed')
|
|
357
|
+
const skipped = result.installations.filter((i) => i.status === 'skipped')
|
|
358
|
+
|
|
359
|
+
if (installed.length > 0) {
|
|
360
|
+
const byAgent = new Map()
|
|
361
|
+
for (const item of installed) {
|
|
362
|
+
const list = byAgent.get(item.agent) ?? []
|
|
363
|
+
list.push(item.skill)
|
|
364
|
+
byAgent.set(item.agent, list)
|
|
365
|
+
}
|
|
366
|
+
for (const [agent, names] of byAgent) {
|
|
367
|
+
const dir = path.join(projectName, ...resolveAgentDir(agent))
|
|
368
|
+
console.log(chalk.green(t('skills.installed', agent, names.length, dir)))
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (skipped.length > 0) {
|
|
372
|
+
console.log(chalk.yellow(t('skills.skipped', skipped.length)))
|
|
373
|
+
}
|
|
374
|
+
} catch (error) {
|
|
375
|
+
console.log(chalk.red(t('skills.failed', error.message)))
|
|
376
|
+
console.log(chalk.yellow(t('skills.installLater')))
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function resolveAgentDir(agent) {
|
|
381
|
+
switch (agent) {
|
|
382
|
+
case 'claude': return ['.claude', 'skills']
|
|
383
|
+
case 'codex': return ['.codex', 'skills']
|
|
384
|
+
case 'agents': return ['.agents', 'skills']
|
|
385
|
+
default: return [`.${agent}`, 'skills']
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function printNextSteps(projectName, setup, t) {
|
|
390
|
+
console.log(chalk.green(t('next.ready', chalk.bold(projectName))))
|
|
281
391
|
|
|
282
392
|
const missingKeys = AI_PROVIDERS.filter(
|
|
283
393
|
(p) => setup.providers.includes(p.value) && !setup.providerKeys[p.envKey],
|
|
284
394
|
).map((p) => p.envKey)
|
|
285
395
|
|
|
286
396
|
if (setup.storage === 'postgres' && !setup.databaseUrl) {
|
|
287
|
-
console.log(chalk.yellow('
|
|
397
|
+
console.log(chalk.yellow(t('next.dbUrlEmpty')))
|
|
288
398
|
}
|
|
289
399
|
if (missingKeys.length > 0) {
|
|
290
|
-
console.log(
|
|
291
|
-
chalk.yellow(` ⚠ Missing API keys in .env: ${missingKeys.join(', ')}`),
|
|
292
|
-
)
|
|
400
|
+
console.log(chalk.yellow(t('next.missingKeys', missingKeys.join(', '))))
|
|
293
401
|
}
|
|
294
402
|
|
|
295
|
-
console.log('
|
|
403
|
+
console.log(t('next.heading'))
|
|
296
404
|
console.log(chalk.cyan(`\n cd ${projectName}`))
|
|
297
|
-
console.log(chalk.cyan(' npm run dev'))
|
|
298
|
-
console.log('
|
|
405
|
+
console.log(chalk.cyan(' npm run dev:watch'))
|
|
406
|
+
console.log(t('next.cmdChannelHint'))
|
|
407
|
+
console.log(chalk.cyan('\n npm run cmd:channel'))
|
|
408
|
+
console.log(t('next.happy'))
|
|
299
409
|
}
|
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
|
+
'project.templateSource': (repo) => ` Using template source: ${repo}`,
|
|
23
|
+
|
|
24
|
+
'storage.prompt': 'How do you want to store data?',
|
|
25
|
+
'storage.memory': 'In-memory (quickest start, data resets on restart)',
|
|
26
|
+
'storage.postgres': 'PostgreSQL (persistent, recommended for real bots)',
|
|
27
|
+
'storage.dbUrlPrompt': 'DATABASE_URL (leave blank to fill in later):',
|
|
28
|
+
|
|
29
|
+
'providers.prompt': 'Which AI providers will you use? (space to toggle, enter to confirm)',
|
|
30
|
+
'providers.hint': 'At least one is required to run the included Pixel example',
|
|
31
|
+
'providers.apiKeyPrompt': (envKey) => `${envKey} (leave blank to fill in later):`,
|
|
32
|
+
'providers.openrouter': 'Used by the included Pixel example',
|
|
33
|
+
'providers.openai': 'GPT models via the official OpenAI API',
|
|
34
|
+
'providers.anthropic': 'Claude models',
|
|
35
|
+
'providers.google': 'Gemini models',
|
|
36
|
+
|
|
37
|
+
'skills.installPrompt': 'Install Wabot agent skills 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/refresh them later with `npx wabot-skills sync`.',
|
|
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
|
+
'project.templateSource': (repo) => ` Usando fuente del template: ${repo}`,
|
|
93
|
+
|
|
94
|
+
'storage.prompt': '¿Cómo deseas almacenar los datos?',
|
|
95
|
+
'storage.memory': 'En memoria (inicio más rápido, los datos se pierden al reiniciar)',
|
|
96
|
+
'storage.postgres': 'PostgreSQL (persistente, recomendado para bots reales)',
|
|
97
|
+
'storage.dbUrlPrompt': 'DATABASE_URL (déjalo en blanco para completarlo después):',
|
|
98
|
+
|
|
99
|
+
'providers.prompt':
|
|
100
|
+
'¿Qué proveedores de IA usarás? (espacio para alternar, enter para confirmar)',
|
|
101
|
+
'providers.hint':
|
|
102
|
+
'Se requiere al menos uno para ejecutar el ejemplo Pixel incluido',
|
|
103
|
+
'providers.apiKeyPrompt': (envKey) =>
|
|
104
|
+
`${envKey} (déjalo en blanco para completarlo después):`,
|
|
105
|
+
'providers.openrouter': 'Usado por el ejemplo Pixel incluido',
|
|
106
|
+
'providers.openai': 'Modelos GPT mediante la API oficial de OpenAI',
|
|
107
|
+
'providers.anthropic': 'Modelos Claude',
|
|
108
|
+
'providers.google': 'Modelos Gemini',
|
|
109
|
+
|
|
110
|
+
'skills.installPrompt': '¿Instalar las skills de agente de Wabot 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/actualizarlas más tarde con `npx wabot-skills sync`.',
|
|
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
|
+
}
|
|
@@ -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
|
+
}
|