@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,127 @@
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
+
12
+ import type { PackageManager } from './package-manager.js';
13
+
14
+ export interface DockerConfig {
15
+ composeFile: string;
16
+ defaultServices: string[];
17
+ }
18
+
19
+ export interface EnvConfig {
20
+ local: string;
21
+ localExample: string;
22
+ docker: string;
23
+ dockerExample: string;
24
+ }
25
+
26
+ export interface HexabotConfig {
27
+ devScript: string;
28
+ startScript: string;
29
+ packageManager?: PackageManager;
30
+ docker: DockerConfig;
31
+ env: EnvConfig;
32
+ }
33
+
34
+ const CONFIG_FILE_NAME = 'hexabot.config.json';
35
+ const DEFAULT_CONFIG: HexabotConfig = {
36
+ devScript: 'dev',
37
+ startScript: 'start',
38
+ docker: {
39
+ composeFile: 'docker/docker-compose.yml',
40
+ defaultServices: [],
41
+ },
42
+ env: {
43
+ local: '.env',
44
+ localExample: '.env.example',
45
+ docker: '.env.docker',
46
+ dockerExample: '.env.docker.example',
47
+ },
48
+ };
49
+
50
+ export const resolveConfigPath = (projectRoot = process.cwd()) => {
51
+ return path.join(projectRoot, CONFIG_FILE_NAME);
52
+ };
53
+
54
+ export const loadProjectConfig = (projectRoot = process.cwd()) => {
55
+ const configPath = resolveConfigPath(projectRoot);
56
+ if (!fs.existsSync(configPath)) {
57
+ return { ...DEFAULT_CONFIG };
58
+ }
59
+
60
+ try {
61
+ const raw = fs.readFileSync(configPath, 'utf-8');
62
+ const parsed = JSON.parse(raw) as Partial<HexabotConfig>;
63
+
64
+ return mergeConfig(DEFAULT_CONFIG, parsed);
65
+ } catch (error) {
66
+ console.warn(chalk.yellow('Invalid hexabot.config.json. Using defaults.'));
67
+ console.warn(chalk.yellow((error as Error).message));
68
+
69
+ return { ...DEFAULT_CONFIG };
70
+ }
71
+ };
72
+
73
+ export const ensureProjectConfig = (
74
+ projectRoot = process.cwd(),
75
+ overrides: Partial<HexabotConfig> = {},
76
+ ) => {
77
+ const configPath = resolveConfigPath(projectRoot);
78
+ if (!fs.existsSync(configPath)) {
79
+ const config = mergeConfig(DEFAULT_CONFIG, overrides);
80
+ writeProjectConfig(config, projectRoot);
81
+
82
+ return config;
83
+ }
84
+
85
+ if (Object.keys(overrides).length > 0) {
86
+ return updateProjectConfig(projectRoot, overrides);
87
+ }
88
+
89
+ return loadProjectConfig(projectRoot);
90
+ };
91
+
92
+ export const writeProjectConfig = (
93
+ config: HexabotConfig,
94
+ projectRoot = process.cwd(),
95
+ ) => {
96
+ const configPath = resolveConfigPath(projectRoot);
97
+ fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf-8');
98
+ };
99
+
100
+ export const updateProjectConfig = (
101
+ projectRoot: string,
102
+ overrides: Partial<HexabotConfig>,
103
+ ) => {
104
+ const existing = loadProjectConfig(projectRoot);
105
+ const next = mergeConfig(existing, overrides);
106
+ writeProjectConfig(next, projectRoot);
107
+
108
+ return next;
109
+ };
110
+
111
+ const mergeConfig = (
112
+ base: HexabotConfig,
113
+ overrides: Partial<HexabotConfig>,
114
+ ): HexabotConfig => {
115
+ return {
116
+ ...base,
117
+ ...overrides,
118
+ docker: {
119
+ ...base.docker,
120
+ ...(overrides.docker || {}),
121
+ },
122
+ env: {
123
+ ...base.env,
124
+ ...(overrides.env || {}),
125
+ },
126
+ };
127
+ };
@@ -0,0 +1,91 @@
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 { execSync } from 'child_process';
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+
11
+ import chalk from 'chalk';
12
+
13
+ export type ComposeMode = 'dev' | 'prod';
14
+
15
+ export const resolveComposeFile = (projectRoot: string, filePath: string) => {
16
+ return path.isAbsolute(filePath)
17
+ ? filePath
18
+ : path.join(projectRoot, filePath);
19
+ };
20
+
21
+ export const generateComposeFiles = (
22
+ baseComposeFile: string,
23
+ services: string[],
24
+ mode?: ComposeMode,
25
+ ) => {
26
+ if (!fs.existsSync(baseComposeFile)) {
27
+ console.error(
28
+ chalk.red(
29
+ `Docker compose file not found at ${baseComposeFile}. Update hexabot.config.json.`,
30
+ ),
31
+ );
32
+ process.exit(1);
33
+ }
34
+
35
+ const composeDir = path.dirname(baseComposeFile);
36
+ const files = [`-f ${baseComposeFile}`];
37
+
38
+ services.forEach((service) => {
39
+ const serviceFile = path.join(composeDir, `docker-compose.${service}.yml`);
40
+ if (fs.existsSync(serviceFile)) {
41
+ files.push(`-f ${serviceFile}`);
42
+ }
43
+ if (mode) {
44
+ const serviceModeFile = path.join(
45
+ composeDir,
46
+ `docker-compose.${service}.${mode}.yml`,
47
+ );
48
+ if (fs.existsSync(serviceModeFile)) {
49
+ files.push(`-f ${serviceModeFile}`);
50
+ }
51
+ }
52
+ });
53
+
54
+ if (mode) {
55
+ const modeFile = path.join(composeDir, `docker-compose.${mode}.yml`);
56
+ if (fs.existsSync(modeFile)) {
57
+ files.push(`-f ${modeFile}`);
58
+ }
59
+ }
60
+
61
+ return files.join(' ');
62
+ };
63
+
64
+ export const dockerCompose = (args: string) => {
65
+ try {
66
+ const cmd = `docker compose ${args}`;
67
+ console.log(chalk.yellow(cmd));
68
+ execSync(cmd, { stdio: 'inherit' });
69
+ } catch (_error) {
70
+ console.error(chalk.red('Error executing Docker Compose command.'));
71
+ process.exit(1);
72
+ }
73
+ };
74
+
75
+ export const dockerExec = (
76
+ container: string,
77
+ command: string,
78
+ options?: string,
79
+ ) => {
80
+ try {
81
+ const optionalArgs = options ? `${options} ` : '';
82
+ const cmd = `docker exec -it ${optionalArgs}${container} ${command}`;
83
+ console.log(chalk.bgYellow(cmd));
84
+ execSync(cmd, {
85
+ stdio: 'inherit',
86
+ });
87
+ } catch (_error) {
88
+ console.error(chalk.red('Error executing Docker Exec command.'));
89
+ process.exit(1);
90
+ }
91
+ };
@@ -0,0 +1,90 @@
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
+
12
+ import type { HexabotConfig } from './config.js';
13
+
14
+ export interface EnvBootstrapOptions {
15
+ force?: boolean;
16
+ quiet?: boolean;
17
+ }
18
+
19
+ export const bootstrapEnvFile = (
20
+ projectRoot: string,
21
+ exampleFile: string,
22
+ targetFile: string,
23
+ options: EnvBootstrapOptions = {},
24
+ ) => {
25
+ const examplePath = path.join(projectRoot, exampleFile);
26
+ const targetPath = path.join(projectRoot, targetFile);
27
+
28
+ if (!fs.existsSync(examplePath)) {
29
+ console.log(
30
+ chalk.yellow(
31
+ `Example env file "${exampleFile}" is missing. Skipping bootstrap.`,
32
+ ),
33
+ );
34
+
35
+ return false;
36
+ }
37
+
38
+ if (!options.force && fs.existsSync(targetPath)) {
39
+ if (!options.quiet) {
40
+ console.log(
41
+ chalk.gray(
42
+ `Env file "${targetFile}" already exists. Use --force to overwrite.`,
43
+ ),
44
+ );
45
+ }
46
+
47
+ return false;
48
+ }
49
+
50
+ fs.copyFileSync(examplePath, targetPath);
51
+ if (!options.quiet) {
52
+ console.log(
53
+ chalk.green(`Generated ${targetFile} from ${exampleFile}. Customize it!`),
54
+ );
55
+ }
56
+
57
+ return true;
58
+ };
59
+
60
+ export const listEnvStatus = (projectRoot: string, config: HexabotConfig) => {
61
+ const entries = [
62
+ config.env.localExample,
63
+ config.env.local,
64
+ config.env.dockerExample,
65
+ config.env.docker,
66
+ ];
67
+
68
+ return entries.map((file) => ({
69
+ file,
70
+ path: path.join(projectRoot, file),
71
+ exists: fs.existsSync(path.join(projectRoot, file)),
72
+ }));
73
+ };
74
+
75
+ export const resolveEnvExample = (
76
+ projectRoot: string,
77
+ envFile: string | undefined,
78
+ defaultExample: string,
79
+ ) => {
80
+ if (!envFile) {
81
+ return defaultExample;
82
+ }
83
+
84
+ const candidate = `${envFile}.example`;
85
+ if (fs.existsSync(path.join(projectRoot, candidate))) {
86
+ return candidate;
87
+ }
88
+
89
+ return defaultExample;
90
+ };
@@ -0,0 +1,126 @@
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 { execSync } from 'child_process';
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+
11
+ import chalk from 'chalk';
12
+
13
+ import { readPackageJson } from './project.js';
14
+
15
+ export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
16
+
17
+ const PACKAGE_MANAGER_LOCKFILES: Record<PackageManager, string> = {
18
+ npm: 'package-lock.json',
19
+ pnpm: 'pnpm-lock.yaml',
20
+ yarn: 'yarn.lock',
21
+ bun: 'bun.lockb',
22
+ };
23
+
24
+ export const normalizePackageManager = (value?: string) => {
25
+ if (!value) {
26
+ return undefined;
27
+ }
28
+
29
+ const normalized = value.toLowerCase() as PackageManager;
30
+ if (!Object.keys(PACKAGE_MANAGER_LOCKFILES).includes(normalized)) {
31
+ console.error(
32
+ chalk.red(
33
+ `Unsupported package manager "${value}". Use npm, pnpm, yarn, or bun.`,
34
+ ),
35
+ );
36
+ process.exit(1);
37
+ }
38
+
39
+ return normalized;
40
+ };
41
+
42
+ export const detectPackageManager = (projectRoot = process.cwd()) => {
43
+ const entries = Object.entries(PACKAGE_MANAGER_LOCKFILES) as [
44
+ PackageManager,
45
+ string,
46
+ ][];
47
+
48
+ for (const [pm, lockfile] of entries) {
49
+ if (fs.existsSync(path.join(projectRoot, lockfile))) {
50
+ return pm;
51
+ }
52
+ }
53
+
54
+ return 'npm';
55
+ };
56
+
57
+ export const installDependencies = (
58
+ pm: PackageManager,
59
+ projectRoot = process.cwd(),
60
+ ) => {
61
+ const command = getInstallCommand(pm);
62
+ runCommand(command, projectRoot);
63
+ };
64
+
65
+ export const runPackageScript = (
66
+ pm: PackageManager,
67
+ script: string,
68
+ projectRoot = process.cwd(),
69
+ scriptArgs: string[] = [],
70
+ ) => {
71
+ ensureScriptExists(script, projectRoot);
72
+ const command = getRunScriptCommand(pm, script, scriptArgs);
73
+ runCommand(command, projectRoot);
74
+ };
75
+
76
+ const ensureScriptExists = (script: string, projectRoot: string) => {
77
+ const packageJson = readPackageJson(projectRoot);
78
+ if (!packageJson?.scripts?.[script]) {
79
+ console.error(
80
+ chalk.red(
81
+ `Cannot find script "${script}" in package.json. Update hexabot.config.json or package.json scripts.`,
82
+ ),
83
+ );
84
+ process.exit(1);
85
+ }
86
+ };
87
+ const getInstallCommand = (pm: PackageManager) => {
88
+ switch (pm) {
89
+ case 'pnpm':
90
+ return 'pnpm install';
91
+ case 'yarn':
92
+ return 'yarn install';
93
+ case 'bun':
94
+ return 'bun install';
95
+ default:
96
+ return 'npm install';
97
+ }
98
+ };
99
+ const getRunScriptCommand = (
100
+ pm: PackageManager,
101
+ script: string,
102
+ args: string[],
103
+ ) => {
104
+ const joinedArgs = args.join(' ');
105
+
106
+ switch (pm) {
107
+ case 'pnpm':
108
+ return joinedArgs
109
+ ? `pnpm run ${script} -- ${joinedArgs}`
110
+ : `pnpm run ${script}`;
111
+ case 'yarn':
112
+ return joinedArgs ? `yarn ${script} ${joinedArgs}` : `yarn ${script}`;
113
+ case 'bun':
114
+ return joinedArgs
115
+ ? `bun run ${script} ${joinedArgs}`
116
+ : `bun run ${script}`;
117
+ default:
118
+ return joinedArgs
119
+ ? `npm run ${script} -- ${joinedArgs}`
120
+ : `npm run ${script}`;
121
+ }
122
+ };
123
+ const runCommand = (command: string, projectRoot: string) => {
124
+ console.log(chalk.cyan(command));
125
+ execSync(command, { cwd: projectRoot, stdio: 'inherit' });
126
+ };
@@ -0,0 +1,117 @@
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 { execSync } from 'child_process';
8
+
9
+ import chalk from 'chalk';
10
+
11
+ const REQUIRED_NODE_VERSION = '20.18.1';
12
+
13
+ export interface PrerequisiteOptions {
14
+ docker?: boolean;
15
+ silent?: boolean;
16
+ fatal?: boolean;
17
+ }
18
+
19
+ export interface PrerequisiteCheckResult {
20
+ ok: boolean;
21
+ message: string;
22
+ details?: string;
23
+ }
24
+
25
+ export const checkPrerequisites = (options: PrerequisiteOptions = {}) => {
26
+ checkNodeVersion(options);
27
+ if (options.docker) {
28
+ checkDocker(options);
29
+ }
30
+ };
31
+
32
+ export const checkNodeVersion = (
33
+ options: PrerequisiteOptions = {},
34
+ ): PrerequisiteCheckResult => {
35
+ try {
36
+ const nodeVersion = execSync('node --version', {
37
+ encoding: 'utf-8',
38
+ }).trim();
39
+ const currentNodeVersion = normalizeVersion(nodeVersion);
40
+
41
+ if (compareVersions(currentNodeVersion, REQUIRED_NODE_VERSION) >= 0) {
42
+ const message = `Node.js ${nodeVersion} ✓`;
43
+ logSuccess(message, options);
44
+
45
+ return { ok: true, message };
46
+ }
47
+
48
+ const message = `Node.js version must be at least ${REQUIRED_NODE_VERSION}. Current version: ${nodeVersion}.`;
49
+ handleFailure(message, options);
50
+
51
+ return { ok: false, message };
52
+ } catch (error) {
53
+ const message =
54
+ "Node.js is not accessible or installed correctly. Install Node.js v20.18.1+ and ensure it's in your PATH.";
55
+
56
+ handleFailure(message, options, error);
57
+
58
+ return { ok: false, message, details: (error as Error).message };
59
+ }
60
+ };
61
+
62
+ export const checkDocker = (
63
+ options: PrerequisiteOptions = {},
64
+ ): PrerequisiteCheckResult => {
65
+ try {
66
+ const dockerVersion = execSync('docker --version', {
67
+ encoding: 'utf-8',
68
+ }).trim();
69
+ const message = `Docker is installed: ${dockerVersion}`;
70
+ logSuccess(message, options);
71
+
72
+ return { ok: true, message };
73
+ } catch (error) {
74
+ const message =
75
+ 'Docker is required for this command. Please install Docker.';
76
+ handleFailure(message, options, error);
77
+
78
+ return { ok: false, message, details: (error as Error).message };
79
+ }
80
+ };
81
+
82
+ const normalizeVersion = (version: string) => {
83
+ return version.startsWith('v') ? version.slice(1) : version;
84
+ };
85
+ const compareVersions = (current: string, required: string) => {
86
+ const currentParts = current.split('.').map(Number);
87
+ const requiredParts = required.split('.').map(Number);
88
+
89
+ for (let i = 0; i < requiredParts.length; i++) {
90
+ if ((currentParts[i] || 0) > (requiredParts[i] || 0)) {
91
+ return 1;
92
+ } else if ((currentParts[i] || 0) < (requiredParts[i] || 0)) {
93
+ return -1;
94
+ }
95
+ }
96
+
97
+ return 0;
98
+ };
99
+ const logSuccess = (message: string, options: PrerequisiteOptions) => {
100
+ if (!options.silent) {
101
+ console.log(chalk.green(message));
102
+ }
103
+ };
104
+ const handleFailure = (
105
+ message: string,
106
+ options: PrerequisiteOptions,
107
+ error?: unknown,
108
+ ) => {
109
+ console.error(chalk.red(message));
110
+
111
+ if (options.fatal !== false) {
112
+ if (error && options.silent) {
113
+ console.error(chalk.red((error as Error).message));
114
+ }
115
+ process.exit(1);
116
+ }
117
+ };
@@ -0,0 +1,97 @@
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
+
12
+ const DOCKER_FOLDER_NAME = 'docker';
13
+ const HEXABOT_PACKAGE = '@hexabot-ai/api';
14
+
15
+ export interface PackageJson {
16
+ name?: string;
17
+ scripts?: Record<string, string>;
18
+ dependencies?: Record<string, string>;
19
+ devDependencies?: Record<string, string>;
20
+ }
21
+
22
+ export const resolveProjectRoot = (cwd = process.cwd()) => {
23
+ return path.resolve(cwd);
24
+ };
25
+
26
+ export const readPackageJson = (
27
+ projectRoot = process.cwd(),
28
+ ): PackageJson | null => {
29
+ try {
30
+ const packageJsonPath = path.join(projectRoot, 'package.json');
31
+ if (!fs.existsSync(packageJsonPath)) {
32
+ return null;
33
+ }
34
+
35
+ return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) as PackageJson;
36
+ } catch (_error) {
37
+ return null;
38
+ }
39
+ };
40
+
41
+ export const isHexabotProject = (projectRoot = process.cwd()) => {
42
+ const packageJson = readPackageJson(projectRoot);
43
+ if (!packageJson) {
44
+ return false;
45
+ }
46
+
47
+ return hasHexabotDependency(packageJson);
48
+ };
49
+
50
+ export const assertHexabotProject = (projectRoot = process.cwd()) => {
51
+ if (!isHexabotProject(projectRoot)) {
52
+ console.error(
53
+ chalk.red(
54
+ 'This command must be executed inside a Hexabot project (missing @hexabot-ai/api in package.json).',
55
+ ),
56
+ );
57
+ console.log(
58
+ chalk.yellow(
59
+ 'Run `hexabot create <project>` or navigate to your project.',
60
+ ),
61
+ );
62
+ process.exit(1);
63
+ }
64
+ };
65
+
66
+ export const resolveDockerFolder = (projectRoot = process.cwd()) => {
67
+ return path.resolve(projectRoot, `./${DOCKER_FOLDER_NAME}`);
68
+ };
69
+
70
+ export const ensureDockerFolder = (projectRoot = process.cwd()) => {
71
+ const folder = resolveDockerFolder(projectRoot);
72
+
73
+ if (!fs.existsSync(folder)) {
74
+ console.error(
75
+ chalk.red(
76
+ `The '${DOCKER_FOLDER_NAME}' folder is missing. Docker commands require the Hexabot project's docker/ directory.`,
77
+ ),
78
+ );
79
+ console.log(
80
+ chalk.yellow(
81
+ `Ensure you're at the project root (e.g. cd path/to/my-bot) before running Docker commands.`,
82
+ ),
83
+ );
84
+ process.exit(1);
85
+ }
86
+
87
+ return folder;
88
+ };
89
+
90
+ const hasHexabotDependency = (packageJson: PackageJson) => {
91
+ const allDeps = {
92
+ ...(packageJson.dependencies || {}),
93
+ ...(packageJson.devDependencies || {}),
94
+ };
95
+
96
+ return Boolean(allDeps[HEXABOT_PACKAGE]);
97
+ };
package/src/index.ts ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
4
+ * Copyright (c) 2025 Hexastack.
5
+ * Full terms: see LICENSE.md.
6
+ */
7
+
8
+ import { createCliProgram } from './cli.js';
9
+ import { checkPrerequisites } from './core/prerequisites.js';
10
+ import { printBanner } from './ui/banner.js';
11
+
12
+ printBanner();
13
+ checkPrerequisites({ silent: true });
14
+
15
+ const program = createCliProgram();
16
+
17
+ program.parse(process.argv);
18
+
19
+ if (!process.argv.slice(2).length) {
20
+ program.outputHelp();
21
+ }
@@ -0,0 +1,33 @@
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 axios from 'axios';
11
+ import decompress from 'decompress';
12
+
13
+ export const downloadAndExtractTemplate = async (
14
+ templateUrl: string,
15
+ destination: string,
16
+ ) => {
17
+ try {
18
+ const response = await axios({
19
+ url: templateUrl,
20
+ method: 'GET',
21
+ responseType: 'arraybuffer',
22
+ });
23
+ const zipFilePath = path.join(destination, 'template.zip');
24
+ fs.writeFileSync(zipFilePath, response.data);
25
+
26
+ await decompress(zipFilePath, destination, {
27
+ strip: 1,
28
+ });
29
+ fs.unlinkSync(zipFilePath);
30
+ } catch (_error) {
31
+ throw new Error(`Failed to download template from GitHub`);
32
+ }
33
+ };