@hexabot-ai/cli 3.2.2-alpha.12 → 3.2.2-alpha.14

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/AGENTS.md CHANGED
@@ -26,7 +26,7 @@ This file is the authoritative cheat-sheet for the Hexabot CLI. It summarizes th
26
26
  | Command | Responsibilities | Options & IO | Implementation Notes |
27
27
  | --- | --- | --- | --- |
28
28
  | `check [--docker-only] [--no-docker]` | Run diagnostics for local Node version, Hexabot project detection, env files, and optionally Docker. | Flags narrow which checks run; outputs PASS/FAIL rows and sets exit code 1 if any check fails. | `src/commands/check.ts` calls `checkNodeVersion()`, `isHexabotProject()`, `loadProjectConfig()`, `listEnvStatus()`, and `checkDocker()` in sequence and prints results with chalk. |
29
- | `create <projectName>` | Scaffold a project from a GitHub template, configure package manager, bootstrap env files, install deps, and optionally start dev mode. | `--template`, `--pm`, `--no-install`, `--dev`, `--docker`, `--force`. Writes `hexabot.config.json` and new env files, installs deps (unless skipped), and can immediately run `hexabot dev`. | `src/commands/create.ts` validates the slug, downloads the latest release zip of `hexastack/hexabot-template-*` via `downloadAndExtractTemplate()`, ensures config/env files exist, installs dependencies via `installDependencies()`, and optionally calls `runDev()` for the new project. |
29
+ | `create <projectName>` | Scaffold a project from a GitHub template, configure package manager, bootstrap env files, install deps, and optionally start dev mode. | `--template`, `--pm`, `--no-install`, `--dev`, `--docker`, `--force`. Writes `hexabot.config.json`, bootstraps local and Docker env files, persists initial admin seed credentials, installs deps (unless skipped), and can immediately run `hexabot dev`. | `src/commands/create.ts` validates the slug, downloads the latest release zip of `hexastack/hexabot-template-*` via `downloadAndExtractTemplate()`, ensures config/env files exist, installs dependencies via `installDependencies()`, and optionally calls `runDev()` for the new project. |
30
30
  | `config show` | Print the effective `hexabot.config.json` after merging defaults. | No flags; emits pretty-printed JSON. | `src/commands/config.ts` resolves the project root, asserts the workspace, and calls `loadProjectConfig()`. |
31
31
  | `config set <key> <value>` | Update nested config values via dot notation with type-aware parsing. | Supports booleans, numbers, JSON literals, comma lists (for `*Services` keys), and package-manager normalization. | Uses `parseValue()` + `buildOverride()` to construct overrides, writes via `updateProjectConfig()`, and echoes the new config. |
32
32
  | `dev [--docker] [--services] [-d] [--env] [--no-env-bootstrap] [--pm]` | Run the project in development mode using either the package script or Docker compose stack. | When not using Docker it runs the configured dev script (`dev` default) after optional env bootstrapping. Docker mode bootstraps the docker env file, resolves compose overlays, and runs `docker compose ... up --build [-d]`. | `src/commands/dev.ts` auto-detects the package manager, persists it into the config, handles env bootstrapping (local or docker), then either calls `runPackageScript()` or `runDockerDev()` (which uses `generateComposeFiles()` with mode `dev`). |
@@ -46,7 +46,7 @@ This file is the authoritative cheat-sheet for the Hexabot CLI. It summarizes th
46
46
 
47
47
  ## Typical Workflow
48
48
 
49
- 1. `hexabot create my-bot --docker` — scaffold a workspace, persist the preferred package manager into `hexabot.config.json`, bootstrap both local and docker env files, and install dependencies.
49
+ 1. `hexabot create my-bot` — scaffold a workspace, persist the preferred package manager into `hexabot.config.json`, bootstrap both local and Docker env files, capture initial admin credentials, and install dependencies.
50
50
  2. `cd my-bot`
51
51
  3. `hexabot check` — confirm Node/Docker availability, project structure, and env files before running heavier commands.
52
52
  4. `hexabot env init` — re-run if you need to refresh `.env` files (use `--docker` to target docker envs).
package/README.md CHANGED
@@ -41,10 +41,10 @@ Common options:
41
41
  - `--pm <npm|pnpm|yarn|bun>` – force a package manager (auto-detected otherwise).
42
42
  - `--no-install` – skip running the package manager after scaffolding.
43
43
  - `--dev` – immediately run `hexabot dev` once creation is complete.
44
- - `--docker` – bootstrap the Docker env file and hint about Docker-first commands.
44
+ - `--docker` – show Docker-first next steps and use Docker mode when combined with `--dev`.
45
45
  - `--force` – allow scaffolding into a non-empty directory.
46
46
 
47
- The command downloads the latest template release, installs dependencies (unless `--no-install`), and bootstraps `.env` (and `.env.docker` when `--docker` is passed).
47
+ The command downloads the latest template release, installs dependencies (unless `--no-install`), and bootstraps `.env` plus `.env.docker` when their example files are present.
48
48
 
49
49
  #### `dev`
50
50
 
@@ -86,6 +86,7 @@ beforeEach(() => {
86
86
  setTTY(true);
87
87
  validateProjectName.mockReturnValue(true);
88
88
  detectPackageManager.mockReturnValue('pnpm');
89
+ bootstrapEnvFile.mockReturnValue(true);
89
90
  normalizePackageManager.mockImplementation((value) => {
90
91
  return typeof value === 'string' ? value.toLowerCase() : undefined;
91
92
  });
@@ -113,7 +114,7 @@ afterEach(() => {
113
114
  jest.restoreAllMocks();
114
115
  });
115
116
  describe('registerCreateCommand', () => {
116
- it('prompts admin credentials and persists them to local env values', async () => {
117
+ it('prompts admin credentials and persists them to local and Docker env values', async () => {
117
118
  input
118
119
  .mockResolvedValueOnce('Anis')
119
120
  .mockResolvedValueOnce('Bot')
@@ -136,15 +137,46 @@ describe('registerCreateCommand', () => {
136
137
  expect(templateUrl).toBe('https://github.com/marrouchi/hexabot-v3-template/archive/refs/tags/v1.0.0.zip');
137
138
  expect(projectPath.endsWith(`${path.sep}anisbot`)).toBe(true);
138
139
  expect(bootstrapEnvFile).toHaveBeenCalledWith(projectPath, '.env.example', '.env', { quiet: true });
140
+ expect(bootstrapEnvFile).toHaveBeenCalledWith(projectPath, '.env.docker.example', '.env.docker', { quiet: true });
139
141
  expect(upsertEnvVariables).toHaveBeenCalledWith(projectPath, '.env', {
140
142
  SEED_ADMIN_FIRST_NAME: 'Anis',
141
143
  SEED_ADMIN_LAST_NAME: 'Bot',
142
144
  SEED_ADMIN_EMAIL: 'anis@example.com',
143
145
  SEED_ADMIN_PASSWORD: 'Admin#123',
144
146
  });
147
+ expect(upsertEnvVariables).toHaveBeenCalledWith(projectPath, '.env.docker', {
148
+ SEED_ADMIN_FIRST_NAME: 'Anis',
149
+ SEED_ADMIN_LAST_NAME: 'Bot',
150
+ SEED_ADMIN_EMAIL: 'anis@example.com',
151
+ SEED_ADMIN_PASSWORD: 'Admin#123',
152
+ });
145
153
  expect(exitSpy).not.toHaveBeenCalled();
146
154
  expect(input).toHaveBeenCalledTimes(3);
147
155
  expect(password).toHaveBeenCalledTimes(2);
156
+ expect(bootstrapEnvFile).toHaveBeenCalledTimes(2);
157
+ expect(upsertEnvVariables).toHaveBeenCalledTimes(2);
158
+ });
159
+ it('skips Docker admin credentials when the Docker env file is unavailable', async () => {
160
+ bootstrapEnvFile.mockImplementation((_projectRoot, _exampleFile, targetFile) => targetFile === '.env');
161
+ input
162
+ .mockResolvedValueOnce('Anis')
163
+ .mockResolvedValueOnce('Bot')
164
+ .mockResolvedValueOnce('anis@example.com');
165
+ password
166
+ .mockResolvedValueOnce('Admin#123')
167
+ .mockResolvedValueOnce('Admin#123');
168
+ const program = new Command();
169
+ registerCreateCommand(program);
170
+ await program.parseAsync(['node', 'test', 'create', 'anisbot']);
171
+ const [, projectPath] = downloadAndExtractTemplate.mock.calls[0];
172
+ expect(bootstrapEnvFile).toHaveBeenCalledWith(projectPath, '.env.docker.example', '.env.docker', { quiet: true });
173
+ expect(upsertEnvVariables).toHaveBeenCalledTimes(1);
174
+ expect(upsertEnvVariables).toHaveBeenCalledWith(projectPath, '.env', {
175
+ SEED_ADMIN_FIRST_NAME: 'Anis',
176
+ SEED_ADMIN_LAST_NAME: 'Bot',
177
+ SEED_ADMIN_EMAIL: 'anis@example.com',
178
+ SEED_ADMIN_PASSWORD: 'Admin#123',
179
+ });
148
180
  });
149
181
  it('fails cleanly when create runs in a non-interactive terminal', async () => {
150
182
  setTTY(false);
@@ -22,7 +22,7 @@ export const registerCreateCommand = (program) => {
22
22
  .option('--pm <npm|pnpm|yarn|bun>', 'Preferred package manager')
23
23
  .option('--no-install', 'Skip installing dependencies')
24
24
  .option('--dev', 'Run hexabot dev after creation')
25
- .option('--docker', 'Bootstrap Docker env files during creation')
25
+ .option('--docker', 'Use Docker-oriented next steps and --dev startup')
26
26
  .option('--force', 'Allow scaffolding into a non-empty directory')
27
27
  .action(async (projectName, options) => {
28
28
  await createProject(projectName, options);
@@ -49,19 +49,9 @@ const createProject = async (projectName, options) => {
49
49
  };
50
50
  ensureProjectConfig(projectPath, configOverrides);
51
51
  const config = loadProjectConfig(projectPath);
52
- bootstrapEnvFile(projectPath, config.env.localExample, config.env.local, {
53
- quiet: true,
54
- });
55
- if (options.docker) {
56
- bootstrapEnvFile(projectPath, config.env.dockerExample, config.env.docker, { quiet: true });
57
- }
52
+ const dockerEnvBootstrapped = bootstrapCreateEnvFiles(projectPath, config);
58
53
  const adminCredentials = await promptSeedAdminCredentials();
59
- upsertEnvVariables(projectPath, config.env.local, {
60
- SEED_ADMIN_FIRST_NAME: adminCredentials.firstName,
61
- SEED_ADMIN_LAST_NAME: adminCredentials.lastName,
62
- SEED_ADMIN_EMAIL: adminCredentials.email,
63
- SEED_ADMIN_PASSWORD: adminCredentials.password,
64
- });
54
+ persistAdminSeedCredentials(projectPath, config, adminCredentials, dockerEnvBootstrapped);
65
55
  if (options.noInstall) {
66
56
  console.log(chalk.yellow('Skipping dependency installation (--no-install).'));
67
57
  }
@@ -117,6 +107,28 @@ const fetchLatestReleaseTag = async (templateRepo) => {
117
107
  }
118
108
  return data.tag_name;
119
109
  };
110
+ const bootstrapCreateEnvFiles = (projectPath, config) => {
111
+ bootstrapEnvFile(projectPath, config.env.localExample, config.env.local, {
112
+ quiet: true,
113
+ });
114
+ return bootstrapEnvFile(projectPath, config.env.dockerExample, config.env.docker, { quiet: true });
115
+ };
116
+ const buildAdminSeedVariables = (credentials) => {
117
+ return {
118
+ SEED_ADMIN_FIRST_NAME: credentials.firstName,
119
+ SEED_ADMIN_LAST_NAME: credentials.lastName,
120
+ SEED_ADMIN_EMAIL: credentials.email,
121
+ SEED_ADMIN_PASSWORD: credentials.password,
122
+ };
123
+ };
124
+ const persistAdminSeedCredentials = (projectPath, config, credentials, dockerEnvBootstrapped) => {
125
+ const seedVariables = buildAdminSeedVariables(credentials);
126
+ upsertEnvVariables(projectPath, config.env.local, seedVariables);
127
+ if (dockerEnvBootstrapped ||
128
+ fs.existsSync(path.join(projectPath, config.env.docker))) {
129
+ upsertEnvVariables(projectPath, config.env.docker, seedVariables);
130
+ }
131
+ };
120
132
  const requireValue = (label) => {
121
133
  return (value) => {
122
134
  if (!value.trim()) {
@@ -209,7 +221,7 @@ const logSuccessMessage = (projectName, options) => {
209
221
  }
210
222
  console.log(chalk.gray(`3. Explore docker helpers if needed:`));
211
223
  console.log(chalk.yellow(` hexabot docker up --services postgres`));
212
- console.log(chalk.gray(`Need env files? Run hexabot env init --docker`));
224
+ console.log(chalk.gray(`Env bootstrap completed.`));
213
225
  console.log('\n');
214
226
  console.log(chalk.blue('Optional: Install Hexabot skills'));
215
227
  console.log(chalk.gray('You can add official skills to accelerate your workflow:'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexabot-ai/cli",
3
- "version": "3.2.2-alpha.12",
3
+ "version": "3.2.2-alpha.14",
4
4
  "description": "Official Hexabot CLI for creating and managing AI chatbot/agent projects built with Hexabot.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -104,6 +104,7 @@ beforeEach(() => {
104
104
 
105
105
  validateProjectName.mockReturnValue(true);
106
106
  detectPackageManager.mockReturnValue('pnpm');
107
+ bootstrapEnvFile.mockReturnValue(true);
107
108
  (normalizePackageManager as any).mockImplementation((value: unknown) => {
108
109
  return typeof value === 'string' ? value.toLowerCase() : undefined;
109
110
  });
@@ -133,7 +134,7 @@ afterEach(() => {
133
134
  });
134
135
 
135
136
  describe('registerCreateCommand', () => {
136
- it('prompts admin credentials and persists them to local env values', async () => {
137
+ it('prompts admin credentials and persists them to local and Docker env values', async () => {
137
138
  (input as any)
138
139
  .mockResolvedValueOnce('Anis')
139
140
  .mockResolvedValueOnce('Bot')
@@ -166,15 +167,67 @@ describe('registerCreateCommand', () => {
166
167
  '.env',
167
168
  { quiet: true },
168
169
  );
170
+ expect(bootstrapEnvFile).toHaveBeenCalledWith(
171
+ projectPath,
172
+ '.env.docker.example',
173
+ '.env.docker',
174
+ { quiet: true },
175
+ );
169
176
  expect(upsertEnvVariables).toHaveBeenCalledWith(projectPath, '.env', {
170
177
  SEED_ADMIN_FIRST_NAME: 'Anis',
171
178
  SEED_ADMIN_LAST_NAME: 'Bot',
172
179
  SEED_ADMIN_EMAIL: 'anis@example.com',
173
180
  SEED_ADMIN_PASSWORD: 'Admin#123',
174
181
  });
182
+ expect(upsertEnvVariables).toHaveBeenCalledWith(
183
+ projectPath,
184
+ '.env.docker',
185
+ {
186
+ SEED_ADMIN_FIRST_NAME: 'Anis',
187
+ SEED_ADMIN_LAST_NAME: 'Bot',
188
+ SEED_ADMIN_EMAIL: 'anis@example.com',
189
+ SEED_ADMIN_PASSWORD: 'Admin#123',
190
+ },
191
+ );
175
192
  expect(exitSpy).not.toHaveBeenCalled();
176
193
  expect(input).toHaveBeenCalledTimes(3);
177
194
  expect(password).toHaveBeenCalledTimes(2);
195
+ expect(bootstrapEnvFile).toHaveBeenCalledTimes(2);
196
+ expect(upsertEnvVariables).toHaveBeenCalledTimes(2);
197
+ });
198
+
199
+ it('skips Docker admin credentials when the Docker env file is unavailable', async () => {
200
+ (bootstrapEnvFile as any).mockImplementation(
201
+ (_projectRoot: string, _exampleFile: string, targetFile: string) =>
202
+ targetFile === '.env',
203
+ );
204
+ (input as any)
205
+ .mockResolvedValueOnce('Anis')
206
+ .mockResolvedValueOnce('Bot')
207
+ .mockResolvedValueOnce('anis@example.com');
208
+ (password as any)
209
+ .mockResolvedValueOnce('Admin#123')
210
+ .mockResolvedValueOnce('Admin#123');
211
+
212
+ const program = new Command();
213
+ registerCreateCommand(program);
214
+
215
+ await program.parseAsync(['node', 'test', 'create', 'anisbot']);
216
+
217
+ const [, projectPath] = (downloadAndExtractTemplate as any).mock.calls[0];
218
+ expect(bootstrapEnvFile).toHaveBeenCalledWith(
219
+ projectPath,
220
+ '.env.docker.example',
221
+ '.env.docker',
222
+ { quiet: true },
223
+ );
224
+ expect(upsertEnvVariables).toHaveBeenCalledTimes(1);
225
+ expect(upsertEnvVariables).toHaveBeenCalledWith(projectPath, '.env', {
226
+ SEED_ADMIN_FIRST_NAME: 'Anis',
227
+ SEED_ADMIN_LAST_NAME: 'Bot',
228
+ SEED_ADMIN_EMAIL: 'anis@example.com',
229
+ SEED_ADMIN_PASSWORD: 'Admin#123',
230
+ });
178
231
  });
179
232
 
180
233
  it('fails cleanly when create runs in a non-interactive terminal', async () => {
@@ -52,7 +52,7 @@ export const registerCreateCommand = (program: Command) => {
52
52
  .option('--pm <npm|pnpm|yarn|bun>', 'Preferred package manager')
53
53
  .option('--no-install', 'Skip installing dependencies')
54
54
  .option('--dev', 'Run hexabot dev after creation')
55
- .option('--docker', 'Bootstrap Docker env files during creation')
55
+ .option('--docker', 'Use Docker-oriented next steps and --dev startup')
56
56
  .option('--force', 'Allow scaffolding into a non-empty directory')
57
57
  .action(async (projectName: string, options: CreateCommandOptions) => {
58
58
  await createProject(projectName, options);
@@ -92,26 +92,14 @@ const createProject = async (
92
92
 
93
93
  ensureProjectConfig(projectPath, configOverrides);
94
94
  const config = loadProjectConfig(projectPath);
95
-
96
- bootstrapEnvFile(projectPath, config.env.localExample, config.env.local, {
97
- quiet: true,
98
- });
99
- if (options.docker) {
100
- bootstrapEnvFile(
101
- projectPath,
102
- config.env.dockerExample,
103
- config.env.docker,
104
- { quiet: true },
105
- );
106
- }
107
-
95
+ const dockerEnvBootstrapped = bootstrapCreateEnvFiles(projectPath, config);
108
96
  const adminCredentials = await promptSeedAdminCredentials();
109
- upsertEnvVariables(projectPath, config.env.local, {
110
- SEED_ADMIN_FIRST_NAME: adminCredentials.firstName,
111
- SEED_ADMIN_LAST_NAME: adminCredentials.lastName,
112
- SEED_ADMIN_EMAIL: adminCredentials.email,
113
- SEED_ADMIN_PASSWORD: adminCredentials.password,
114
- });
97
+ persistAdminSeedCredentials(
98
+ projectPath,
99
+ config,
100
+ adminCredentials,
101
+ dockerEnvBootstrapped,
102
+ );
115
103
 
116
104
  if (options.noInstall) {
117
105
  console.log(
@@ -185,6 +173,45 @@ const fetchLatestReleaseTag = async (templateRepo: string) => {
185
173
 
186
174
  return data.tag_name;
187
175
  };
176
+ const bootstrapCreateEnvFiles = (
177
+ projectPath: string,
178
+ config: ReturnType<typeof loadProjectConfig>,
179
+ ) => {
180
+ bootstrapEnvFile(projectPath, config.env.localExample, config.env.local, {
181
+ quiet: true,
182
+ });
183
+
184
+ return bootstrapEnvFile(
185
+ projectPath,
186
+ config.env.dockerExample,
187
+ config.env.docker,
188
+ { quiet: true },
189
+ );
190
+ };
191
+ const buildAdminSeedVariables = (credentials: AdminSeedCredentials) => {
192
+ return {
193
+ SEED_ADMIN_FIRST_NAME: credentials.firstName,
194
+ SEED_ADMIN_LAST_NAME: credentials.lastName,
195
+ SEED_ADMIN_EMAIL: credentials.email,
196
+ SEED_ADMIN_PASSWORD: credentials.password,
197
+ };
198
+ };
199
+ const persistAdminSeedCredentials = (
200
+ projectPath: string,
201
+ config: ReturnType<typeof loadProjectConfig>,
202
+ credentials: AdminSeedCredentials,
203
+ dockerEnvBootstrapped: boolean,
204
+ ) => {
205
+ const seedVariables = buildAdminSeedVariables(credentials);
206
+ upsertEnvVariables(projectPath, config.env.local, seedVariables);
207
+
208
+ if (
209
+ dockerEnvBootstrapped ||
210
+ fs.existsSync(path.join(projectPath, config.env.docker))
211
+ ) {
212
+ upsertEnvVariables(projectPath, config.env.docker, seedVariables);
213
+ }
214
+ };
188
215
  const requireValue = (label: string) => {
189
216
  return (value: string) => {
190
217
  if (!value.trim()) {
@@ -304,7 +331,7 @@ const logSuccessMessage = (
304
331
  }
305
332
  console.log(chalk.gray(`3. Explore docker helpers if needed:`));
306
333
  console.log(chalk.yellow(` hexabot docker up --services postgres`));
307
- console.log(chalk.gray(`Need env files? Run hexabot env init --docker`));
334
+ console.log(chalk.gray(`Env bootstrap completed.`));
308
335
  console.log('\n');
309
336
  console.log(chalk.blue('Optional: Install Hexabot skills'));
310
337
  console.log(