@hexabot-ai/cli 3.0.0-alpha.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.
Files changed (81) hide show
  1. package/.prettierrc +5 -0
  2. package/AGENTS.md +64 -0
  3. package/README.md +192 -0
  4. package/dist/cli.js +31 -0
  5. package/dist/commands/__tests__/check.test.js +97 -0
  6. package/dist/commands/__tests__/config.test.js +80 -0
  7. package/dist/commands/__tests__/dev.test.js +105 -0
  8. package/dist/commands/__tests__/docker.test.js +132 -0
  9. package/dist/commands/__tests__/env.test.js +72 -0
  10. package/dist/commands/__tests__/migrate.test.js +42 -0
  11. package/dist/commands/__tests__/start.test.js +120 -0
  12. package/dist/commands/check.js +73 -0
  13. package/dist/commands/config.js +76 -0
  14. package/dist/commands/create.js +131 -0
  15. package/dist/commands/dev.js +72 -0
  16. package/dist/commands/docker.js +119 -0
  17. package/dist/commands/env.js +44 -0
  18. package/dist/commands/migrate.js +22 -0
  19. package/dist/commands/start.js +76 -0
  20. package/dist/core/__tests__/config.test.js +88 -0
  21. package/dist/core/__tests__/docker.test.js +43 -0
  22. package/dist/core/__tests__/env.test.js +71 -0
  23. package/dist/core/__tests__/package-manager.test.js +95 -0
  24. package/dist/core/__tests__/project.test.js +49 -0
  25. package/dist/core/config.js +78 -0
  26. package/dist/core/docker.js +66 -0
  27. package/dist/core/env.js +50 -0
  28. package/dist/core/package-manager.js +87 -0
  29. package/dist/core/prerequisites.js +80 -0
  30. package/dist/core/project.js +58 -0
  31. package/dist/index.js +16 -0
  32. package/dist/services/templates.js +27 -0
  33. package/dist/ui/banner.js +14 -0
  34. package/dist/utils/__tests__/services.test.js +18 -0
  35. package/dist/utils/__tests__/validation.test.js +17 -0
  36. package/dist/utils/__tests__/version.test.js +27 -0
  37. package/dist/utils/services.js +11 -0
  38. package/dist/utils/validation.js +9 -0
  39. package/dist/utils/version.js +22 -0
  40. package/eslint.config-staged.cjs +10 -0
  41. package/eslint.config.cjs +104 -0
  42. package/jest.config.ts +24 -0
  43. package/package.json +63 -0
  44. package/src/cli.ts +37 -0
  45. package/src/commands/__tests__/check.test.ts +116 -0
  46. package/src/commands/__tests__/config.test.ts +97 -0
  47. package/src/commands/__tests__/dev.test.ts +151 -0
  48. package/src/commands/__tests__/docker.test.ts +168 -0
  49. package/src/commands/__tests__/env.test.ts +95 -0
  50. package/src/commands/__tests__/migrate.test.ts +64 -0
  51. package/src/commands/__tests__/start.test.ts +166 -0
  52. package/src/commands/check.ts +102 -0
  53. package/src/commands/config.ts +90 -0
  54. package/src/commands/create.ts +201 -0
  55. package/src/commands/dev.ts +122 -0
  56. package/src/commands/docker.ts +190 -0
  57. package/src/commands/env.ts +62 -0
  58. package/src/commands/migrate.ts +27 -0
  59. package/src/commands/start.ts +126 -0
  60. package/src/core/__tests__/config.test.ts +114 -0
  61. package/src/core/__tests__/docker.test.ts +59 -0
  62. package/src/core/__tests__/env.test.ts +97 -0
  63. package/src/core/__tests__/package-manager.test.ts +121 -0
  64. package/src/core/__tests__/project.test.ts +68 -0
  65. package/src/core/config.ts +127 -0
  66. package/src/core/docker.ts +91 -0
  67. package/src/core/env.ts +90 -0
  68. package/src/core/package-manager.ts +126 -0
  69. package/src/core/prerequisites.ts +117 -0
  70. package/src/core/project.ts +97 -0
  71. package/src/index.ts +21 -0
  72. package/src/services/templates.ts +33 -0
  73. package/src/ui/banner.ts +18 -0
  74. package/src/utils/__tests__/services.test.ts +21 -0
  75. package/src/utils/__tests__/validation.test.ts +21 -0
  76. package/src/utils/__tests__/version.test.ts +35 -0
  77. package/src/utils/services.ts +12 -0
  78. package/src/utils/validation.ts +11 -0
  79. package/src/utils/version.ts +28 -0
  80. package/test/__mocks__/chalk.ts +13 -0
  81. package/tsconfig.json +15 -0
@@ -0,0 +1,201 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+
10
+ import chalk from 'chalk';
11
+ import { Command } from 'commander';
12
+
13
+ import { ensureProjectConfig, loadProjectConfig } from '../core/config.js';
14
+ import { bootstrapEnvFile } from '../core/env.js';
15
+ import {
16
+ detectPackageManager,
17
+ installDependencies,
18
+ normalizePackageManager,
19
+ } from '../core/package-manager.js';
20
+ import { downloadAndExtractTemplate } from '../services/templates.js';
21
+ import { validateProjectName } from '../utils/validation.js';
22
+
23
+ import { runDev } from './dev.js';
24
+
25
+ const DEFAULT_TEMPLATE_REPO = 'hexastack/hexabot-template-starter';
26
+
27
+ interface CreateCommandOptions {
28
+ template?: string;
29
+ pm?: string;
30
+ noInstall?: boolean;
31
+ dev?: boolean;
32
+ docker?: boolean;
33
+ force?: boolean;
34
+ }
35
+
36
+ export const registerCreateCommand = (program: Command) => {
37
+ program
38
+ .command('create <projectName>')
39
+ .description('Create a new Hexabot project from the starter')
40
+ .option(
41
+ '-t, --template <name>',
42
+ 'Project template to use (default: starter)',
43
+ )
44
+ .option('--pm <npm|pnpm|yarn|bun>', 'Preferred package manager')
45
+ .option('--no-install', 'Skip installing dependencies')
46
+ .option('--dev', 'Run hexabot dev after creation')
47
+ .option('--docker', 'Bootstrap Docker env files during creation')
48
+ .option('--force', 'Allow scaffolding into a non-empty directory')
49
+ .action(async (projectName: string, options: CreateCommandOptions) => {
50
+ await createProject(projectName, options);
51
+ });
52
+ };
53
+
54
+ const createProject = async (
55
+ projectName: string,
56
+ options: CreateCommandOptions,
57
+ ) => {
58
+ if (!validateProjectName(projectName)) {
59
+ console.error(
60
+ chalk.red(
61
+ 'Invalid project name. Use lowercase letters, numbers, and dashes.',
62
+ ),
63
+ );
64
+ process.exit(1);
65
+ }
66
+
67
+ const projectPath = path.join(process.cwd(), projectName);
68
+ ensureTargetDirectory(projectPath, options.force);
69
+
70
+ const templateRepo = resolveTemplateRepo(options.template);
71
+ console.log(chalk.blue(`Using template ${templateRepo}`));
72
+
73
+ try {
74
+ const latestTag = await fetchLatestReleaseTag(templateRepo);
75
+ const templateUrl = `https://github.com/${templateRepo}/archive/refs/tags/${latestTag}.zip`;
76
+ await downloadAndExtractTemplate(templateUrl, projectPath);
77
+
78
+ const pmPreference = normalizePackageManager(options.pm);
79
+ const detectedPm = detectPackageManager(projectPath);
80
+ const packageManager = pmPreference || detectedPm;
81
+ const configOverrides: Partial<ReturnType<typeof loadProjectConfig>> = {
82
+ packageManager,
83
+ };
84
+
85
+ ensureProjectConfig(projectPath, configOverrides);
86
+ const config = loadProjectConfig(projectPath);
87
+
88
+ bootstrapEnvFile(projectPath, config.env.localExample, config.env.local, {
89
+ quiet: true,
90
+ });
91
+ if (options.docker) {
92
+ bootstrapEnvFile(
93
+ projectPath,
94
+ config.env.dockerExample,
95
+ config.env.docker,
96
+ { quiet: true },
97
+ );
98
+ }
99
+
100
+ if (options.noInstall) {
101
+ console.log(
102
+ chalk.yellow('Skipping dependency installation (--no-install).'),
103
+ );
104
+ } else {
105
+ console.log(
106
+ chalk.blue(`Installing dependencies with ${packageManager}...`),
107
+ );
108
+ installDependencies(packageManager, projectPath);
109
+ }
110
+
111
+ logSuccessMessage(projectName, { docker: options.docker });
112
+
113
+ if (options.dev) {
114
+ if (options.noInstall) {
115
+ console.log(
116
+ chalk.yellow(
117
+ 'Dependencies were not installed. Run `npm install` before `--dev`.',
118
+ ),
119
+ );
120
+ } else {
121
+ console.log(chalk.blue('Starting dev server...'));
122
+ await runDev({
123
+ cwd: projectPath,
124
+ docker: options.docker,
125
+ });
126
+ }
127
+ }
128
+ } catch (error) {
129
+ console.error(chalk.red('Error creating project.'), error);
130
+ process.exit(1);
131
+ }
132
+ };
133
+ const resolveTemplateRepo = (template?: string) => {
134
+ if (!template) {
135
+ return DEFAULT_TEMPLATE_REPO;
136
+ }
137
+
138
+ if (template.includes('/')) {
139
+ return template;
140
+ }
141
+
142
+ return `hexastack/hexabot-template-${template}`;
143
+ };
144
+ const ensureTargetDirectory = (projectPath: string, force?: boolean) => {
145
+ if (fs.existsSync(projectPath)) {
146
+ const isEmpty = fs.readdirSync(projectPath).length === 0;
147
+ if (!isEmpty && !force) {
148
+ console.error(
149
+ chalk.red(
150
+ `Directory ${projectPath} is not empty. Use --force to scaffold anyway.`,
151
+ ),
152
+ );
153
+ process.exit(1);
154
+ }
155
+ } else {
156
+ fs.mkdirSync(projectPath, { recursive: true });
157
+ }
158
+ };
159
+ const fetchLatestReleaseTag = async (templateRepo: string) => {
160
+ const response = await fetch(
161
+ `https://api.github.com/repos/${templateRepo}/releases/latest`,
162
+ );
163
+ const data = await response.json();
164
+ if (!response.ok) {
165
+ throw new Error(
166
+ `Failed to fetch the latest release information: ${data.message}`,
167
+ );
168
+ }
169
+
170
+ return data.tag_name;
171
+ };
172
+ const logSuccessMessage = (
173
+ projectName: string,
174
+ options: { docker?: boolean },
175
+ ) => {
176
+ console.log('\n');
177
+ console.log(chalk.green(`🎉 Project ${projectName} created successfully.`));
178
+ console.log('\n');
179
+ console.log(chalk.bgYellow.black(`Next steps:`));
180
+ console.log(chalk.gray(`1. Navigate to the project folder:`));
181
+ console.log(chalk.yellow(` cd ${projectName}`));
182
+ if (options.docker) {
183
+ console.log(
184
+ chalk.gray(
185
+ `2. Run dev mode with Docker (or omit --docker for local sqlite):`,
186
+ ),
187
+ );
188
+ console.log(chalk.yellow(` hexabot dev --docker`));
189
+ } else {
190
+ console.log(chalk.gray(`2. Start local dev server (SQLite by default):`));
191
+ console.log(chalk.yellow(` hexabot dev`));
192
+ }
193
+ console.log(chalk.gray(`3. Explore docker helpers if needed:`));
194
+ console.log(chalk.yellow(` hexabot docker up --services postgres`));
195
+ console.log(
196
+ chalk.gray(
197
+ `Need env files? Run ${chalk.white('hexabot env init --docker')}`,
198
+ ),
199
+ );
200
+ console.log('\n');
201
+ };
@@ -0,0 +1,122 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import * as path from 'path';
8
+
9
+ import chalk from 'chalk';
10
+ import { Command } from 'commander';
11
+
12
+ import { loadProjectConfig, updateProjectConfig } from '../core/config.js';
13
+ import {
14
+ dockerCompose,
15
+ generateComposeFiles,
16
+ resolveComposeFile,
17
+ } from '../core/docker.js';
18
+ import { bootstrapEnvFile, resolveEnvExample } from '../core/env.js';
19
+ import {
20
+ detectPackageManager,
21
+ normalizePackageManager,
22
+ runPackageScript,
23
+ } from '../core/package-manager.js';
24
+ import { checkDocker } from '../core/prerequisites.js';
25
+ import { assertHexabotProject, ensureDockerFolder } from '../core/project.js';
26
+ import { parseServices } from '../utils/services.js';
27
+
28
+ export interface DevOptions {
29
+ docker?: boolean;
30
+ services?: string;
31
+ detach?: boolean;
32
+ env?: string;
33
+ envBootstrap?: boolean;
34
+ pm?: string;
35
+ cwd?: string;
36
+ }
37
+
38
+ export const registerDevCommand = (program: Command) => {
39
+ program
40
+ .command('dev')
41
+ .description('Run the current Hexabot project in development mode')
42
+ .option('--docker', 'Run using Docker')
43
+ .option(
44
+ '--services <list>',
45
+ 'Comma-separated services or profiles to enable',
46
+ )
47
+ .option('-d, --detach', 'Detach Docker containers (Docker mode only)')
48
+ .option('--env <file>', 'Env file to use for local dev (default: .env)')
49
+ .option('--no-env-bootstrap', 'Skip env bootstrapping')
50
+ .option('--pm <npm|pnpm|yarn|bun>', 'Override package manager')
51
+ .action(async (options: DevOptions) => {
52
+ await runDev(options);
53
+ });
54
+ };
55
+
56
+ export const runDev = async (options: DevOptions = {}) => {
57
+ const projectRoot = path.resolve(options.cwd || process.cwd());
58
+ assertHexabotProject(projectRoot);
59
+ const config = loadProjectConfig(projectRoot);
60
+ const normalizedPm = normalizePackageManager(options.pm);
61
+ const pm =
62
+ normalizedPm || config.packageManager || detectPackageManager(projectRoot);
63
+
64
+ if (config.packageManager !== pm) {
65
+ updateProjectConfig(projectRoot, { packageManager: pm });
66
+ }
67
+
68
+ const shouldBootstrap = options.envBootstrap !== false;
69
+ if (shouldBootstrap) {
70
+ if (options.docker) {
71
+ bootstrapEnvFile(
72
+ projectRoot,
73
+ config.env.dockerExample,
74
+ config.env.docker,
75
+ );
76
+ } else {
77
+ const envFile = options.env || config.env.local;
78
+ const envExample = resolveEnvExample(
79
+ projectRoot,
80
+ envFile,
81
+ config.env.localExample,
82
+ );
83
+ bootstrapEnvFile(projectRoot, envExample, envFile);
84
+ }
85
+ }
86
+
87
+ if (options.docker) {
88
+ await runDockerDev(projectRoot, options, config);
89
+ } else {
90
+ runPackageScript(pm, config.devScript, projectRoot);
91
+ }
92
+ };
93
+
94
+ const runDockerDev = async (
95
+ projectRoot: string,
96
+ options: DevOptions,
97
+ config: ReturnType<typeof loadProjectConfig>,
98
+ ) => {
99
+ checkDocker({ silent: true });
100
+ ensureDockerFolder(projectRoot);
101
+ const servicesInput = parseServices(options.services || '');
102
+ const services = servicesInput.length
103
+ ? servicesInput
104
+ : config.docker.defaultServices;
105
+ const composeFile = resolveComposeFile(
106
+ projectRoot,
107
+ config.docker.composeFile,
108
+ );
109
+ const composeArgs = generateComposeFiles(composeFile, services, 'dev');
110
+ const upArgs = ['up', '--build'];
111
+ if (options.detach) {
112
+ upArgs.push('-d');
113
+ }
114
+
115
+ const composeCommand = `${composeArgs} ${upArgs.join(' ')}`.trim();
116
+ console.log(
117
+ chalk.blue(
118
+ `Starting Docker services${services.length ? ` (${services.join(', ')})` : ''}`,
119
+ ),
120
+ );
121
+ dockerCompose(composeCommand);
122
+ };
@@ -0,0 +1,190 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import * as path from 'path';
8
+
9
+ import { Command } from 'commander';
10
+
11
+ import { loadProjectConfig } from '../core/config.js';
12
+ import {
13
+ dockerCompose,
14
+ generateComposeFiles,
15
+ resolveComposeFile,
16
+ } from '../core/docker.js';
17
+ import { bootstrapEnvFile } from '../core/env.js';
18
+ import { checkDocker } from '../core/prerequisites.js';
19
+ import { assertHexabotProject, ensureDockerFolder } from '../core/project.js';
20
+ import { parseServices } from '../utils/services.js';
21
+
22
+ import { runStart } from './start.js';
23
+
24
+ export const registerDockerCommand = (program: Command) => {
25
+ const dockerCommand = program.command('docker').description('Docker helpers');
26
+
27
+ dockerCommand
28
+ .command('up')
29
+ .description('Start Docker services')
30
+ .option('--services <list>', 'Comma-separated services/profiles')
31
+ .option('-d, --detach', 'Run containers in the background')
32
+ .option('--build', 'Build images before starting')
33
+ .action(
34
+ (options: { services?: string; detach?: boolean; build?: boolean }) =>
35
+ runDockerCommand('up', options),
36
+ );
37
+
38
+ dockerCommand
39
+ .command('down')
40
+ .description('Stop Docker services')
41
+ .option('--services <list>', 'Comma-separated services/profiles')
42
+ .option('--volumes', 'Remove volumes')
43
+ .action((options: { services?: string; volumes?: boolean }) =>
44
+ runDockerCommand('down', options),
45
+ );
46
+
47
+ dockerCommand
48
+ .command('logs [service]')
49
+ .description('View Docker logs')
50
+ .option('-f, --follow', 'Follow logs')
51
+ .option('--since <time>', 'Only show logs since time (e.g. 1h)')
52
+ .action(
53
+ (
54
+ service: string | undefined,
55
+ options: { follow?: boolean; since?: string },
56
+ ) => runDockerLogs(service, options),
57
+ );
58
+
59
+ dockerCommand
60
+ .command('ps')
61
+ .description('List running services')
62
+ .action(() => runDockerPs());
63
+
64
+ dockerCommand
65
+ .command('start')
66
+ .description('Start Docker services in production mode')
67
+ .option('--services <list>', 'Comma-separated services/profiles')
68
+ .option('-d, --detach', 'Run containers in the background')
69
+ .option('--build', 'Build images before starting')
70
+ .option('--env-bootstrap', 'Generate env files from *.example if missing')
71
+ .action(
72
+ async (options: {
73
+ services?: string;
74
+ detach?: boolean;
75
+ build?: boolean;
76
+ envBootstrap?: boolean;
77
+ }) => {
78
+ await runStart({
79
+ docker: true,
80
+ services: options.services,
81
+ detach: options.detach,
82
+ build: options.build,
83
+ envBootstrap: options.envBootstrap,
84
+ });
85
+ },
86
+ );
87
+ };
88
+
89
+ const runDockerCommand = (
90
+ lifecycle: 'up' | 'down',
91
+ options: {
92
+ services?: string;
93
+ detach?: boolean;
94
+ build?: boolean;
95
+ volumes?: boolean;
96
+ },
97
+ ) => {
98
+ const projectRoot = path.resolve(process.cwd());
99
+ assertHexabotProject(projectRoot);
100
+ checkDocker({ silent: true });
101
+ const config = loadProjectConfig(projectRoot);
102
+ ensureDockerFolder(projectRoot);
103
+ const services = resolveServices(options.services, config);
104
+ const composeFile = resolveComposeFile(
105
+ projectRoot,
106
+ config.docker.composeFile,
107
+ );
108
+ const composeArgs = generateComposeFiles(
109
+ composeFile,
110
+ services,
111
+ lifecycle === 'up' ? 'dev' : undefined,
112
+ );
113
+
114
+ if (lifecycle === 'up') {
115
+ bootstrapEnvFile(projectRoot, config.env.dockerExample, config.env.docker, {
116
+ quiet: true,
117
+ });
118
+ const commandArgs = ['up'];
119
+ if (options.build) {
120
+ commandArgs.push('--build');
121
+ }
122
+ if (options.detach) {
123
+ commandArgs.push('-d');
124
+ }
125
+ dockerCompose(`${composeArgs} ${commandArgs.join(' ')}`.trim());
126
+ } else {
127
+ const commandArgs = ['down'];
128
+ if (options.volumes) {
129
+ commandArgs.push('-v');
130
+ }
131
+ dockerCompose(`${composeArgs} ${commandArgs.join(' ')}`.trim());
132
+ }
133
+ };
134
+ const runDockerLogs = (
135
+ service: string | undefined,
136
+ options: { follow?: boolean; since?: string },
137
+ ) => {
138
+ const projectRoot = path.resolve(process.cwd());
139
+ assertHexabotProject(projectRoot);
140
+ checkDocker({ silent: true });
141
+ const config = loadProjectConfig(projectRoot);
142
+ ensureDockerFolder(projectRoot);
143
+ const composeFile = resolveComposeFile(
144
+ projectRoot,
145
+ config.docker.composeFile,
146
+ );
147
+ const composeArgs = generateComposeFiles(
148
+ composeFile,
149
+ config.docker.defaultServices,
150
+ 'dev',
151
+ );
152
+ const args = ['logs'];
153
+ if (options.follow) {
154
+ args.push('-f');
155
+ }
156
+ if (options.since) {
157
+ args.push(`--since ${options.since}`);
158
+ }
159
+ if (service) {
160
+ args.push(service);
161
+ }
162
+
163
+ dockerCompose(`${composeArgs} ${args.join(' ')}`.trim());
164
+ };
165
+ const runDockerPs = () => {
166
+ const projectRoot = path.resolve(process.cwd());
167
+ assertHexabotProject(projectRoot);
168
+ checkDocker({ silent: true });
169
+ const config = loadProjectConfig(projectRoot);
170
+ ensureDockerFolder(projectRoot);
171
+ const composeFile = resolveComposeFile(
172
+ projectRoot,
173
+ config.docker.composeFile,
174
+ );
175
+ const composeArgs = generateComposeFiles(
176
+ composeFile,
177
+ config.docker.defaultServices,
178
+ 'dev',
179
+ );
180
+
181
+ dockerCompose(`${composeArgs} ps`);
182
+ };
183
+ const resolveServices = (
184
+ servicesInput: string | undefined,
185
+ config: ReturnType<typeof loadProjectConfig>,
186
+ ) => {
187
+ const provided = parseServices(servicesInput || '');
188
+
189
+ return provided.length ? provided : config.docker.defaultServices;
190
+ };
@@ -0,0 +1,62 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import * as path from 'path';
8
+
9
+ import chalk from 'chalk';
10
+ import { Command } from 'commander';
11
+
12
+ import { loadProjectConfig } from '../core/config.js';
13
+ import { bootstrapEnvFile, listEnvStatus } from '../core/env.js';
14
+ import { assertHexabotProject } from '../core/project.js';
15
+
16
+ export const registerEnvCommand = (program: Command) => {
17
+ const envCommand = program.command('env').description('Manage env files');
18
+
19
+ envCommand
20
+ .command('init')
21
+ .description('Copy *.example env files')
22
+ .option('--docker', 'Initialize Docker env file')
23
+ .option('--force', 'Overwrite existing env files')
24
+ .action((options: { docker?: boolean; force?: boolean }) => {
25
+ const projectRoot = path.resolve(process.cwd());
26
+ assertHexabotProject(projectRoot);
27
+ const config = loadProjectConfig(projectRoot);
28
+
29
+ if (options.docker) {
30
+ bootstrapEnvFile(
31
+ projectRoot,
32
+ config.env.dockerExample,
33
+ config.env.docker,
34
+ { force: options.force },
35
+ );
36
+ } else {
37
+ bootstrapEnvFile(
38
+ projectRoot,
39
+ config.env.localExample,
40
+ config.env.local,
41
+ {
42
+ force: options.force,
43
+ },
44
+ );
45
+ }
46
+ });
47
+
48
+ envCommand
49
+ .command('list')
50
+ .description('Show env file status')
51
+ .action(() => {
52
+ const projectRoot = path.resolve(process.cwd());
53
+ assertHexabotProject(projectRoot);
54
+ const config = loadProjectConfig(projectRoot);
55
+ const statuses = listEnvStatus(projectRoot, config);
56
+
57
+ statuses.forEach((status) => {
58
+ const symbol = status.exists ? chalk.green('✓') : chalk.red('✗');
59
+ console.log(`${symbol} ${status.file}`);
60
+ });
61
+ });
62
+ };
@@ -0,0 +1,27 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import { Command } from 'commander';
8
+
9
+ import { dockerExec } from '../core/docker.js';
10
+ import { checkDocker } from '../core/prerequisites.js';
11
+ import { ensureDockerFolder } from '../core/project.js';
12
+
13
+ export const registerMigrateCommand = (program: Command) => {
14
+ program
15
+ .command('migrate [args...]')
16
+ .description('Run database migrations')
17
+ .action((args: string[] = []) => {
18
+ checkDocker({ silent: true });
19
+ ensureDockerFolder();
20
+ const migrateArgs = args.join(' ').trim();
21
+ const command = migrateArgs
22
+ ? `npm run migrate ${migrateArgs}`
23
+ : 'npm run migrate';
24
+
25
+ dockerExec('api', command, '--user $(id -u):$(id -g)');
26
+ });
27
+ };