@kodus/cli 0.0.2 → 0.0.6

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/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { program } from "commander";
4
- import setupEnvironment from "./src/commands/install.js";
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+ import { setupEnvironment } from './src/commands/install.js';
5
7
 
6
8
  // CLI version
7
- program.version("1.0.0");
9
+ program.version("0.0.5");
8
10
 
9
11
  // Install command
10
12
  program
package/package.json CHANGED
@@ -1,15 +1,20 @@
1
1
  {
2
2
  "name": "@kodus/cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.6",
4
4
  "description": "CLI tool for Kodus installation and management",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./src/*": "./src/*.js"
10
+ },
7
11
  "preferGlobal": true,
8
12
  "bin": {
9
13
  "kodus": "./index.js"
10
14
  },
11
15
  "files": [
12
16
  "index.js",
17
+ "src/**/*.js",
13
18
  "templates/**/*"
14
19
  ],
15
20
  "scripts": {
@@ -0,0 +1,310 @@
1
+ import inquirer from "inquirer";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import fs from "fs-extra";
5
+ import path from "path";
6
+ import { execSync } from "child_process";
7
+ import { DEFAULT_CONFIG, DOCKER_NETWORKS, CRITICAL_ERRORS } from "../config/default.js";
8
+ import {
9
+ generateSecretKey,
10
+ generateDbPassword,
11
+ copyTemplates,
12
+ createDockerNetworks,
13
+ } from "../utils/helpers.js";
14
+
15
+ const maxAttempts = 30; // 5 minutes with 10 second intervals
16
+
17
+ const waitForService = async (serviceName, successMessage, errorMessage) => {
18
+ const spinner = ora(`Waiting for ${serviceName} to be ready`).start();
19
+ let isReady = false;
20
+ let attempts = 0;
21
+
22
+ while (!isReady && attempts < maxAttempts) {
23
+ try {
24
+ const logs = execSync(`docker-compose logs ${serviceName}`, { stdio: "pipe" }).toString();
25
+
26
+ if (logs.includes(successMessage)) {
27
+ isReady = true;
28
+ spinner.succeed(`${serviceName} is ready`);
29
+ } else {
30
+ attempts++;
31
+ await new Promise(resolve => setTimeout(resolve, 10000));
32
+ }
33
+ } catch (error) {
34
+ attempts++;
35
+ await new Promise(resolve => setTimeout(resolve, 10000));
36
+ }
37
+ }
38
+
39
+ if (!isReady) {
40
+ spinner.fail(errorMessage);
41
+ console.error(chalk.yellow("\nTroubleshooting steps:"));
42
+ console.error(chalk.white(`1. Check ${serviceName} logs: docker-compose logs ${serviceName}`));
43
+ console.error(chalk.white(`2. Verify ${serviceName} container is running: docker-compose ps ${serviceName}`));
44
+ process.exit(1);
45
+ }
46
+ };
47
+
48
+ export const setupEnvironment = async () => {
49
+ try {
50
+ // Check prerequisites
51
+ console.log(chalk.blue("\nšŸ” Checking prerequisites..."));
52
+ const spinner = ora("Checking Docker installation").start();
53
+
54
+ try {
55
+ execSync("docker --version", { stdio: "ignore" });
56
+ spinner.succeed("Docker is installed");
57
+ } catch (error) {
58
+ spinner.fail("Docker is not installed");
59
+ console.error(
60
+ chalk.red(
61
+ "\nPlease install Docker first: https://docs.docker.com/get-docker/"
62
+ )
63
+ );
64
+ process.exit(1);
65
+ }
66
+
67
+ // Copy template files
68
+ const templateSpinner = ora("Copying template files").start();
69
+ try {
70
+ copyTemplates(process.cwd());
71
+ templateSpinner.succeed("Template files copied");
72
+ } catch (error) {
73
+ templateSpinner.fail("Failed to copy template files");
74
+ console.error(chalk.red("\nError details:"));
75
+ console.error(chalk.white(error.message));
76
+ process.exit(1);
77
+ }
78
+
79
+ // Environment type selection
80
+ const { envType } = await inquirer.prompt([
81
+ {
82
+ type: "list",
83
+ name: "envType",
84
+ message: "What type of environment are you setting up?",
85
+ choices: [
86
+ { name: "Local (localhost)", value: "local" },
87
+ { name: "External (with public URL)", value: "external" },
88
+ ],
89
+ default: "local",
90
+ },
91
+ ]);
92
+
93
+ let baseUrl = "http://localhost:3000";
94
+ if (envType === "external") {
95
+ const { url } = await inquirer.prompt([
96
+ {
97
+ type: "input",
98
+ name: "url",
99
+ message: "Enter your public URL (e.g., https://kodus.yourdomain.com):",
100
+ validate: (input) => {
101
+ try {
102
+ const url = new URL(input);
103
+ if (!url.protocol.startsWith('http')) {
104
+ return "URL must start with http:// or https://";
105
+ }
106
+ if (!url.hostname.includes('.')) {
107
+ return "URL must include a valid domain name";
108
+ }
109
+ return true;
110
+ } catch (e) {
111
+ return "Please enter a valid URL (e.g., https://kodus.yourdomain.com)";
112
+ }
113
+ },
114
+ },
115
+ ]);
116
+ baseUrl = url;
117
+ }
118
+
119
+ // Git service selection
120
+ const { gitService } = await inquirer.prompt([
121
+ {
122
+ type: "list",
123
+ name: "gitService",
124
+ message: "Which Git service will you use?",
125
+ choices: [
126
+ { name: "GitHub", value: "github" },
127
+ { name: "GitLab", value: "gitlab" },
128
+ { name: "Bitbucket", value: "bitbucket" },
129
+ ],
130
+ },
131
+ ]);
132
+
133
+ // Git service configuration
134
+ let gitConfig = {};
135
+
136
+ if (envType === "local") {
137
+ console.log(chalk.yellow("\nāš ļø IMPORTANT: If you're using a cloud Git service (GitHub, GitLab, Bitbucket), you'll need to configure the webhook manually."));
138
+ console.log(chalk.yellow("For local or self-hosted Git instances, no additional configuration is needed."));
139
+ console.log(chalk.yellow("The webhook URL will be:"));
140
+ console.log(chalk.white(`${baseUrl}/api/webhook/${gitService}`));
141
+ }
142
+
143
+ // Configure webhook URL based on the selected Git service
144
+ if (gitService === "github") {
145
+ gitConfig = {
146
+ API_GITHUB_CODE_MANAGEMENT_WEBHOOK: `${baseUrl}/api/webhook/${gitService}`,
147
+ };
148
+ } else if (gitService === "gitlab") {
149
+ gitConfig = {
150
+ API_GITLAB_CODE_MANAGEMENT_WEBHOOK: `${baseUrl}/api/webhook/${gitService}`,
151
+ };
152
+ } else if (gitService === "bitbucket") {
153
+ gitConfig = {
154
+ GLOBAL_BITBUCKET_CODE_MANAGEMENT_WEBHOOK: `${baseUrl}/api/webhook/${gitService}`,
155
+ };
156
+ }
157
+
158
+ // Database configuration
159
+ const { useDefaultDb } = await inquirer.prompt([
160
+ {
161
+ type: "confirm",
162
+ name: "useDefaultDb",
163
+ message: "Would you like to use default database configurations?",
164
+ default: true,
165
+ },
166
+ ]);
167
+
168
+ // LLM API Keys configuration
169
+ console.log(chalk.blue("\nšŸ”‘ Configuring LLM API Keys..."));
170
+ const llmKeys = await inquirer.prompt([
171
+ {
172
+ type: "input",
173
+ name: "openAiKey",
174
+ message: "Enter your OpenAI API key (optional):",
175
+ default: "",
176
+ },
177
+ {
178
+ type: "input",
179
+ name: "googleAiKey",
180
+ message: "Enter your Google AI API key (optional):",
181
+ default: "",
182
+ },
183
+ {
184
+ type: "input",
185
+ name: "anthropicKey",
186
+ message: "Enter your Anthropic API key (optional):",
187
+ default: "",
188
+ },
189
+ {
190
+ type: "input",
191
+ name: "novitaKey",
192
+ message: "Enter your Novita AI API key (optional):",
193
+ default: "",
194
+ },
195
+ {
196
+ type: "input",
197
+ name: "vertexKey",
198
+ message: "Enter your Vertex AI API key (optional):",
199
+ default: "",
200
+ },
201
+ ]);
202
+
203
+ // Generate .env content
204
+ const envContent = {
205
+ ...DEFAULT_CONFIG,
206
+ WEB_NEXTAUTH_SECRET: generateSecretKey(),
207
+ WEB_JWT_SECRET_KEY: generateSecretKey(),
208
+ API_JWT_SECRET: generateSecretKey(),
209
+ API_JWT_REFRESHSECRET: generateSecretKey(),
210
+ API_PG_DB_PASSWORD: generateDbPassword(),
211
+ API_MG_DB_PASSWORD: generateDbPassword(),
212
+ RABBITMQ_DEFAULT_PASS: generateDbPassword(),
213
+ GRAFANA_ADMIN_PASSWORD: generateDbPassword(),
214
+ API_OPEN_AI_APIKEY: llmKeys.openAiKey,
215
+ API_GOOGLE_AI_API_KEY: llmKeys.googleAiKey,
216
+ API_ANTHROPIC_API_KEY: llmKeys.anthropicKey,
217
+ API_NOVITA_AI_API_KEY: llmKeys.novitaKey,
218
+ API_VERTEX_AI_API_KEY: llmKeys.vertexKey,
219
+ ...gitConfig,
220
+ };
221
+
222
+ // Create .env file
223
+ const envSpinner = ora("Creating .env file").start();
224
+ const envContentString = Object.entries(envContent)
225
+ .map(([key, value]) => `${key}=${value}`)
226
+ .join("\n");
227
+
228
+ const envPath = path.join(process.cwd(), '.env');
229
+ fs.writeFileSync(envPath, envContentString);
230
+ envSpinner.succeed("Created .env file");
231
+
232
+ // Create Docker networks
233
+ createDockerNetworks(DOCKER_NETWORKS);
234
+
235
+ // Start containers
236
+ const dockerSpinner = ora("Starting containers").start();
237
+ try {
238
+ execSync("docker-compose up -d", { stdio: "pipe" });
239
+ dockerSpinner.succeed("Containers started");
240
+ } catch (error) {
241
+ dockerSpinner.fail("Failed to start containers");
242
+ console.error(chalk.red("\nError details:"));
243
+ console.error(chalk.white(error.stdout?.toString() || error.message));
244
+ process.exit(1);
245
+ }
246
+
247
+ // Wait for services to be ready
248
+ await waitForService(
249
+ "db_kodus_postgres",
250
+ "database system is ready to accept connections",
251
+ "PostgreSQL failed to start"
252
+ );
253
+
254
+ await waitForService(
255
+ "db_kodus_mongodb",
256
+ "Waiting for connections",
257
+ "MongoDB failed to start"
258
+ );
259
+
260
+ // Setup database
261
+ const dbSpinner = ora("Setting up database").start();
262
+ try {
263
+ execSync("./scripts/setup-db.sh", { stdio: "pipe" });
264
+ dbSpinner.succeed("Database setup completed");
265
+ } catch (error) {
266
+ dbSpinner.fail("Failed to setup database");
267
+ console.error(chalk.red("\nError details:"));
268
+ console.error(chalk.white(error.stdout?.toString() || error.message));
269
+ process.exit(1);
270
+ }
271
+
272
+ // Installation complete
273
+ console.log(chalk.green("\n✨ Installation completed successfully!"));
274
+
275
+ console.log(chalk.blue("\nšŸ“ Installation Summary:"));
276
+ console.log(chalk.white(` - Environment: ${envType === "local" ? "Local" : "External"}`));
277
+ if (envType === "external") {
278
+ console.log(chalk.white(` - Base URL: ${baseUrl}`));
279
+ }
280
+ console.log(chalk.white(` - Git Service: ${gitService.charAt(0).toUpperCase() + gitService.slice(1)}`));
281
+ console.log(chalk.white(` - Database: ${useDefaultDb ? "Default configuration" : "Custom configuration"}`));
282
+
283
+ console.log(chalk.blue("\nšŸ”— Access URLs:"));
284
+ console.log(chalk.white(` - Web Interface: ${baseUrl}`));
285
+ console.log(chalk.white(" - Grafana Dashboard: http://localhost:3001"));
286
+ console.log(chalk.white(" - RabbitMQ Management: http://localhost:15672"));
287
+
288
+ console.log(chalk.blue("\nšŸ“š Next Steps:"));
289
+ console.log(chalk.white(" 1. Access the web interface"));
290
+ console.log(chalk.white(" 2. Set up your first user"));
291
+ console.log(chalk.white(" 3. Start using Kodus!"));
292
+
293
+ const { startServices } = await inquirer.prompt([
294
+ {
295
+ type: "confirm",
296
+ name: "startServices",
297
+ message: "Would you like to start the services now?",
298
+ default: true,
299
+ },
300
+ ]);
301
+
302
+ if (startServices) {
303
+ execSync("docker-compose up -d", { stdio: "ignore" });
304
+ console.log(chalk.green("\nServices started successfully!"));
305
+ }
306
+ } catch (error) {
307
+ console.error(chalk.red("\nāŒ Installation failed:"), error.message);
308
+ process.exit(1);
309
+ }
310
+ };
@@ -0,0 +1,66 @@
1
+ export const DEFAULT_CONFIG = {
2
+ // Base configuration
3
+ WEB_NODE_ENV: "development",
4
+ WEB_HOSTNAME_API: "localhost",
5
+ WEB_PORT_API: "3001",
6
+ WEB_PORT: "3000",
7
+ WEB_NEXTAUTH_URL: "http://localhost:3000",
8
+
9
+ API_NODE_ENV: "development",
10
+ API_LOG_LEVEL: "error",
11
+ API_LOG_PRETTY: "true",
12
+ API_HOST: "0.0.0.0",
13
+ API_PORT: "3001",
14
+ API_RATE_MAX_REQUEST: "100",
15
+ API_RATE_INTERVAL: "1000",
16
+ API_JWT_EXPIRES_IN: "365d",
17
+ API_JWT_REFRESH_EXPIRES_IN: "7d",
18
+
19
+ GLOBAL_API_CONTAINER_NAME: "kodus-orchestrator-prod",
20
+
21
+ // Database
22
+ API_DATABASE_ENV: "development",
23
+ API_PG_DB_USERNAME: "kodusdev",
24
+ API_PG_DB_DATABASE: "kodus_db",
25
+ API_PG_DB_HOST: "db_kodus_postgres",
26
+ API_PG_DB_PORT: "5432",
27
+
28
+ API_MG_DB_USERNAME: "kodusdev",
29
+ API_MG_DB_DATABASE: "kodus_db",
30
+ API_MG_DB_HOST: "db_kodus_mongodb",
31
+ API_MG_DB_PORT: "27017",
32
+ API_MG_DB_PRODUCTION_CONFIG: "",
33
+
34
+ // LLM Models
35
+ API_LLM_MODEL_CHATGPT_3_5_TURBO: "gpt-4o-mini",
36
+ API_LLM_MODEL_CHATGPT_3_5_TURBO_16K: "gpt-4o-mini",
37
+ API_LLM_MODEL_CHATGPT_4: "gpt-4o-mini",
38
+ API_LLM_MODEL_CHATGPT_4_TURBO: "gpt-4o-mini",
39
+ API_LLM_MODEL_CHATGPT_4_ALL: "gpt-4o",
40
+ API_LLM_MODEL_CHATGPT_4_ALL_MINI: "gpt-4o-mini",
41
+ API_LLM_MODEL_CLAUDE_3_5_SONNET: "claude-3-5-sonnet-20241022",
42
+ API_LLM_MODEL_CLAUDE_3_5_SONNET_20241022: "claude-3-5-sonnet-20241022",
43
+ API_LLM_MODEL_GEMINI_1_5_PRO: "gpt-4o-mini",
44
+ API_LLM_MODEL_GEMINI_1_5_PRO_EXP: "gpt-4o-mini",
45
+
46
+ // Ports
47
+ WEB_PORT: "3000",
48
+ API_PORT: "3001",
49
+
50
+ // RabbitMQ
51
+ RABBITMQ_DEFAULT_USER: "kodus",
52
+ };
53
+
54
+ export const DOCKER_NETWORKS = [
55
+ 'shared-network',
56
+ 'monitoring-network',
57
+ 'kodus-backend-services'
58
+ ];
59
+
60
+ export const CRITICAL_ERRORS = [
61
+ "password authentication failed",
62
+ "Unable to connect to the database",
63
+ "FATAL:",
64
+ "MongoServerError:",
65
+ "connection refused"
66
+ ];
@@ -0,0 +1,94 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { execSync } from 'child_process';
5
+ import ora from 'ora';
6
+ import chalk from 'chalk';
7
+ import { fileURLToPath } from 'url';
8
+ import { dirname } from 'path';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ export const generateSecretKey = () => crypto.randomBytes(32).toString("base64");
14
+
15
+ export const generateDbPassword = () =>
16
+ crypto
17
+ .randomBytes(16)
18
+ .toString("base64")
19
+ .replace(/[^a-zA-Z0-9]/g, "")
20
+ .slice(0, 16);
21
+
22
+ export const copyTemplates = (targetDir) => {
23
+ const templatesDir = path.join(__dirname, '../../templates');
24
+ fs.copySync(path.join(templatesDir, 'docker-compose.yml'), path.join(targetDir, 'docker-compose.yml'));
25
+ };
26
+
27
+ export const backupEnv = () => {
28
+ const envPath = path.join(process.cwd(), '.env');
29
+ if (fs.existsSync(envPath)) {
30
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
31
+ const backupPath = path.join(process.cwd(), `.env.backup.${timestamp}`);
32
+ fs.copySync(envPath, backupPath);
33
+ return true;
34
+ }
35
+ return false;
36
+ };
37
+
38
+ export const restoreEnv = (backupFile) => {
39
+ const backupPath = path.join(process.cwd(), backupFile);
40
+ if (fs.existsSync(backupPath)) {
41
+ const envPath = path.join(process.cwd(), '.env');
42
+ fs.copySync(backupPath, envPath);
43
+ return true;
44
+ }
45
+ return false;
46
+ };
47
+
48
+ export const getLatestVersion = () => {
49
+ try {
50
+ const result = execSync("git describe --tags --abbrev=0", { stdio: "pipe" })
51
+ .toString()
52
+ .trim();
53
+ return result;
54
+ } catch (error) {
55
+ return null;
56
+ }
57
+ };
58
+
59
+ export const getCurrentVersion = () => {
60
+ try {
61
+ const result = execSync("git rev-parse --abbrev-ref HEAD", {
62
+ stdio: "pipe",
63
+ })
64
+ .toString()
65
+ .trim();
66
+ return result;
67
+ } catch (error) {
68
+ return null;
69
+ }
70
+ };
71
+
72
+ export const createDockerNetworks = (networks) => {
73
+ const networkSpinner = ora("Creating Docker networks").start();
74
+
75
+ for (const network of networks) {
76
+ try {
77
+ // Check if network exists
78
+ execSync(`docker network inspect ${network}`, { stdio: 'ignore' });
79
+ } catch (error) {
80
+ // Network doesn't exist, create it
81
+ try {
82
+ execSync(`docker network create ${network}`, { stdio: 'pipe' });
83
+ console.log(chalk.green(`\nCreated network: ${network}`));
84
+ } catch (createError) {
85
+ networkSpinner.fail(`Failed to create network: ${network}`);
86
+ console.error(chalk.red("\nError details:"));
87
+ console.error(chalk.white(createError.stdout?.toString() || createError.message));
88
+ process.exit(1);
89
+ }
90
+ }
91
+ }
92
+
93
+ networkSpinner.succeed("Docker networks created");
94
+ };