@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 +189 -0
- package/bin/create-wabot.mjs +7 -100
- package/lib/cli.mjs +49 -0
- package/lib/create-project.mjs +299 -0
- package/lib/skills/install.mjs +179 -0
- package/lib/skills/registry.mjs +17 -0
- package/package.json +4 -5
- package/skills/wabot-framework/SKILL.md +32 -0
- package/skills/wabot-framework/agents/openai.yaml +5 -0
- package/skills/wabot-framework/references/async-and-cron.md +57 -0
- package/skills/wabot-framework/references/controllers-and-auth.md +104 -0
- package/skills/wabot-framework/references/core-patterns.md +83 -0
- package/skills/wabot-framework/references/full-example.md +278 -0
- package/skills/wabot-framework/references/mindset-and-chat.md +97 -0
- package/skills/wabot-framework/references/ops.md +58 -0
- package/skills/wabot-framework/references/persistence.md +85 -0
- package/skills/wabot-framework/references/quickstart.md +56 -0
- package/skills/wabot-framework/references/validation-and-data.md +88 -0
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
|
+
```
|
package/bin/create-wabot.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
+
}
|