@fenwave/agent 1.1.0

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.
@@ -0,0 +1,385 @@
1
+ import os from 'os';
2
+ import { spawn, exec } from 'child_process';
3
+ import path from 'path';
4
+ import chalk from 'chalk';
5
+ import { loadConfig } from './store/configStore.js';
6
+
7
+ // Load configuration
8
+ const config = loadConfig();
9
+ const CONTAINER_NAME = config.containerName;
10
+ const AGENT_ROOT_DIR = config.agentRootDir;
11
+ const REGISTRIES_DIR = config.registriesDir;
12
+ const HOST_DATA_DIR = path.join(os.homedir(), AGENT_ROOT_DIR, REGISTRIES_DIR);
13
+ const HOST_FW_DIR = path.join(os.homedir(), AGENT_ROOT_DIR);
14
+ const CONTAINER_DATA_DIR = config.containerDataDir;
15
+ const CONTAINER_FW_DIR = '/app/.fenwave';
16
+ const APP_PORT = config.containerPort;
17
+ const WS_PORT = config.wsPort;
18
+ const DOCKER_IMAGE = config.dockerImage;
19
+
20
+ /**
21
+ * Container Manager for Fenwave DevApp
22
+ */
23
+ class ContainerManager {
24
+ constructor() {
25
+ this.isRunning = false;
26
+ this.containerProcess = null;
27
+ }
28
+
29
+ /**
30
+ * Check if Docker is available
31
+ */
32
+ async checkDockerAvailable() {
33
+ return new Promise((resolve) => {
34
+ exec('docker --version', (error) => {
35
+ resolve(!error);
36
+ });
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Check if container is already running
42
+ */
43
+ async isContainerRunning() {
44
+ return new Promise((resolve) => {
45
+ exec(
46
+ `docker ps --filter name=${CONTAINER_NAME} --format "{{.Names}}"`,
47
+ (error, stdout) => {
48
+ if (error) {
49
+ resolve(false);
50
+ return;
51
+ }
52
+ resolve(stdout.trim() === CONTAINER_NAME);
53
+ }
54
+ );
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Stop and remove existing container
60
+ */
61
+ async stopContainer() {
62
+ return new Promise((resolve) => {
63
+ exec(
64
+ `docker stop ${CONTAINER_NAME} && docker rm ${CONTAINER_NAME}`,
65
+ (error) => {
66
+ // Don't worry if container doesn't exist
67
+ resolve();
68
+ }
69
+ );
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Authenticate with AWS ECR
75
+ */
76
+ async authenticateECR() {
77
+ return new Promise((resolve, reject) => {
78
+ const awsRegion = config.awsRegion;
79
+ const awsAccountId = config.awsAccountId;
80
+
81
+ if (!awsRegion || !awsAccountId) {
82
+ console.log(chalk.yellow('⚠️ AWS ECR configuration not set, skipping ECR authentication'));
83
+ console.log(chalk.gray(' Run "fenwave init" to configure ECR settings'));
84
+ resolve();
85
+ return;
86
+ }
87
+
88
+ console.log(chalk.blue('🔐 Authenticating with AWS ECR...'));
89
+
90
+ const authProcess = spawn('aws', [
91
+ 'ecr',
92
+ 'get-login-password',
93
+ '--region',
94
+ awsRegion,
95
+ ]);
96
+
97
+ let authToken = '';
98
+ let errorOutput = '';
99
+
100
+ authProcess.stdout.on('data', (data) => {
101
+ authToken += data.toString();
102
+ });
103
+
104
+ authProcess.stderr.on('data', (data) => {
105
+ errorOutput += data.toString();
106
+ });
107
+
108
+ authProcess.on('close', (code) => {
109
+ if (code === 0) {
110
+ // Now login to Docker with the auth token
111
+ const loginProcess = spawn('docker', [
112
+ 'login',
113
+ '--username',
114
+ 'AWS',
115
+ '--password-stdin',
116
+ `${awsAccountId}.dkr.ecr.${awsRegion}.amazonaws.com`,
117
+ ]);
118
+
119
+ loginProcess.stdin.write(authToken.trim());
120
+ loginProcess.stdin.end();
121
+
122
+ let loginError = '';
123
+ loginProcess.stderr.on('data', (data) => {
124
+ loginError += data.toString();
125
+ });
126
+
127
+ loginProcess.on('close', (loginCode) => {
128
+ if (loginCode === 0) {
129
+ console.log(chalk.green('✅ Authenticated with ECR'));
130
+ resolve();
131
+ } else {
132
+ console.error(chalk.red('❌ Failed to login to Docker registry:'));
133
+ console.error(loginError);
134
+ reject(new Error('ECR Docker login failed'));
135
+ }
136
+ });
137
+ } else {
138
+ console.error(chalk.red('❌ Failed to get ECR auth token:'));
139
+ console.error(errorOutput);
140
+ reject(new Error('ECR authentication failed'));
141
+ }
142
+ });
143
+ });
144
+ }
145
+
146
+ /**
147
+ * Check if image exists locally
148
+ */
149
+ async imageExistsLocally() {
150
+ return new Promise((resolve) => {
151
+ exec(
152
+ `docker images -q ${DOCKER_IMAGE}`,
153
+ (error, stdout) => {
154
+ resolve(stdout.trim().length > 0);
155
+ }
156
+ );
157
+ });
158
+ }
159
+
160
+ /**
161
+ * Pull the Fenwave DevApp image from ECR
162
+ */
163
+ async pullImage() {
164
+ return new Promise(async (resolve, reject) => {
165
+ try {
166
+ // Check if image exists locally
167
+ const imageExists = await this.imageExistsLocally();
168
+ if (imageExists) {
169
+ console.log(chalk.green('✅ Image already exists locally, checking for updates...'));
170
+ }
171
+
172
+ console.log(chalk.blue('📥 Pulling Fenwave DevApp image from ECR...'));
173
+ console.log(chalk.gray(` Image: ${DOCKER_IMAGE}`));
174
+
175
+ const pullProcess = spawn('docker', ['pull', DOCKER_IMAGE], {
176
+ stdio: 'pipe',
177
+ });
178
+
179
+ let output = '';
180
+ let errorOutput = '';
181
+
182
+ pullProcess.stdout.on('data', (data) => {
183
+ output += data.toString();
184
+ // Show pull progress for important lines
185
+ const lines = data
186
+ .toString()
187
+ .split('\n')
188
+ .filter((line) => line.trim());
189
+ lines.forEach((line) => {
190
+ if (
191
+ line.includes('Pulling from') ||
192
+ line.includes('Digest:') ||
193
+ line.includes('Status:') ||
194
+ line.includes('Downloaded')
195
+ ) {
196
+ console.log(chalk.gray(` ${line.trim()}`));
197
+ }
198
+ });
199
+ });
200
+
201
+ pullProcess.stderr.on('data', (data) => {
202
+ errorOutput += data.toString();
203
+ });
204
+
205
+ pullProcess.on('close', (code) => {
206
+ if (code === 0) {
207
+ console.log(chalk.green('✅ Image pulled successfully'));
208
+ resolve();
209
+ } else {
210
+ console.error(chalk.red('❌ Failed to pull image:'));
211
+ console.error(errorOutput);
212
+ reject(new Error(`Pull failed with code ${code}`));
213
+ }
214
+ });
215
+ } catch (error) {
216
+ reject(error);
217
+ }
218
+ });
219
+ }
220
+
221
+ /**
222
+ * Start the Fenwave DevApp container
223
+ */
224
+ async startContainer() {
225
+ try {
226
+ // Check Docker availability
227
+ const dockerAvailable = await this.checkDockerAvailable();
228
+ if (!dockerAvailable) {
229
+ throw new Error(
230
+ 'Docker is not available. Please install Docker and try again.'
231
+ );
232
+ }
233
+
234
+ // Stop existing container if running
235
+ await this.stopContainer();
236
+
237
+ // Authenticate with ECR
238
+ try {
239
+ await this.authenticateECR();
240
+ } catch (ecrError) {
241
+ console.log(chalk.yellow('⚠️ ECR authentication failed, attempting to use cached image...'));
242
+ // Continue with cached image if authentication fails
243
+ }
244
+
245
+ // Pull image from ECR
246
+ try {
247
+ await this.pullImage();
248
+ } catch (pullError) {
249
+ console.log(chalk.yellow('⚠️ Image pull failed, checking for local image...'));
250
+ const imageExists = await this.imageExistsLocally();
251
+ if (!imageExists) {
252
+ throw new Error('No local image available and pull failed. Please check your AWS credentials and network connection.');
253
+ }
254
+ console.log(chalk.green('✅ Using cached local image'));
255
+ }
256
+
257
+ // Determine WebSocket URL based on platform
258
+ let wsUrl = `ws://host.docker.internal:${WS_PORT}`;
259
+ if (os.platform() === 'linux') {
260
+ // On Linux, use host network or get host IP
261
+ wsUrl = `ws://172.17.0.1:${WS_PORT}`; // Docker default bridge IP
262
+ }
263
+
264
+ console.log(chalk.blue('🚀 Starting Fenwave DevApp container...'));
265
+
266
+ const runArgs = [
267
+ 'run',
268
+ '-d', // Detached mode
269
+ '--name',
270
+ CONTAINER_NAME,
271
+ '-p',
272
+ `${APP_PORT}:${APP_PORT}`,
273
+ // Mount registry data directory
274
+ '-v',
275
+ `${HOST_DATA_DIR}:${CONTAINER_DATA_DIR}:rw`,
276
+ // Mount .fenwave directory for WebSocket token access
277
+ '-v',
278
+ `${HOST_FW_DIR}:${CONTAINER_FW_DIR}:ro`,
279
+ '-e',
280
+ `PORT=${APP_PORT}`,
281
+ '-e',
282
+ `NEXT_PUBLIC_WS_URL=${wsUrl}`,
283
+ '--restart',
284
+ 'unless-stopped',
285
+ DOCKER_IMAGE,
286
+ ];
287
+
288
+ return new Promise((resolve, reject) => {
289
+ const runProcess = spawn('docker', runArgs, {
290
+ stdio: 'pipe',
291
+ });
292
+
293
+ let output = '';
294
+ let errorOutput = '';
295
+
296
+ runProcess.stdout.on('data', (data) => {
297
+ output += data.toString();
298
+ });
299
+
300
+ runProcess.stderr.on('data', (data) => {
301
+ errorOutput += data.toString();
302
+ });
303
+
304
+ runProcess.on('close', (code) => {
305
+ if (code === 0) {
306
+ this.isRunning = true;
307
+ console.log(chalk.green('✅ Container started successfully'));
308
+ console.log(chalk.gray(` Image: ${DOCKER_IMAGE}`));
309
+ console.log(chalk.gray(` Container: ${CONTAINER_NAME}`));
310
+ console.log(chalk.gray(` Port: ${APP_PORT}`));
311
+ console.log(chalk.gray(` Data volume: ${HOST_DATA_DIR} -> ${CONTAINER_DATA_DIR}`));
312
+ console.log(chalk.gray(` Token volume: ${HOST_FW_DIR} -> ${CONTAINER_FW_DIR}`));
313
+ resolve();
314
+ } else {
315
+ console.error(chalk.red('❌ Failed to start container:'));
316
+ if (errorOutput) {
317
+ console.error(errorOutput);
318
+ }
319
+ reject(new Error(`Container start failed with code ${code}`));
320
+ }
321
+ });
322
+ });
323
+ } catch (error) {
324
+ console.error(
325
+ chalk.red('❌ Failed to start Fenwave DevApp:'),
326
+ error.message
327
+ );
328
+ throw error;
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Stop the Fenwave DevApp container
334
+ */
335
+ async stopContainerGracefully() {
336
+ try {
337
+ const running = await this.isContainerRunning();
338
+ if (!running) {
339
+ console.log(chalk.yellow('ℹ️ Container is not running'));
340
+ return;
341
+ }
342
+
343
+ await this.stopContainer();
344
+ this.isRunning = false;
345
+ } catch (error) {
346
+ console.error(chalk.red('❌ Failed to stop container:'), error.message);
347
+ throw error;
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Get container status
353
+ */
354
+ async getStatus() {
355
+ const running = await this.isContainerRunning();
356
+ return {
357
+ isRunning: running,
358
+ containerName: CONTAINER_NAME,
359
+ port: APP_PORT,
360
+ dataDirectory: HOST_DATA_DIR,
361
+ };
362
+ }
363
+
364
+ /**
365
+ * Show container logs
366
+ */
367
+ showLogs(follow = false) {
368
+ const args = ['logs'];
369
+ if (follow) {
370
+ args.push('-f');
371
+ }
372
+ args.push(CONTAINER_NAME);
373
+
374
+ const logsProcess = spawn('docker', args, {
375
+ stdio: 'inherit',
376
+ });
377
+
378
+ return logsProcess;
379
+ }
380
+ }
381
+
382
+ // Singleton instance
383
+ const containerManager = new ContainerManager();
384
+
385
+ export default containerManager;
@@ -0,0 +1,62 @@
1
+ #!/bin/bash
2
+
3
+ # ES Module Conversion Script
4
+ # Converts CommonJS files to ES modules
5
+
6
+ echo "Converting CommonJS files to ES modules..."
7
+
8
+ convert_file() {
9
+ local file="$1"
10
+
11
+ if [ ! -f "$file" ]; then
12
+ echo "Skipping $file (not found)"
13
+ return
14
+ fi
15
+
16
+ echo "Converting: $file"
17
+
18
+ # Backup original
19
+ cp "$file" "${file}.bak"
20
+
21
+ # Convert require to import
22
+ sed -i '' \
23
+ -e "s/const \(.*\) = require('\(.*\)');/import \1 from '\2';/g" \
24
+ -e "s/const { \([^}]*\) } = require('\(.*\)');/import { \1 } from '\2';/g" \
25
+ -e "s/require('dotenv').config();/import dotenv from 'dotenv';\ndotenv.config();/g" \
26
+ "$file"
27
+
28
+ # Convert module.exports
29
+ sed -i '' \
30
+ -e 's/module\.exports = {/export {/g' \
31
+ -e 's/module\.exports =/export default/g' \
32
+ "$file"
33
+
34
+ # Add export before function declarations that were in module.exports
35
+ # This is a simple heuristic - may need manual adjustment
36
+ }
37
+
38
+ # Convert store files
39
+ for file in store/agentStore.js store/registryStore.js store/agentSessionStore.js; do
40
+ convert_file "$file"
41
+ done
42
+
43
+ # Convert docker-actions files
44
+ for file in docker-actions/*.js; do
45
+ convert_file "$file"
46
+ done
47
+
48
+ # Convert root files
49
+ for file in websocket-server.js auth.js containerManager.js cli-commands.js helper-functions.js; do
50
+ convert_file "$file"
51
+ done
52
+
53
+ echo ""
54
+ echo "Conversion complete!"
55
+ echo "Backup files created with .bak extension"
56
+ echo ""
57
+ echo "NOTE: Some manual fixes may be needed:"
58
+ echo " - Circular dependencies"
59
+ echo " - Complex module.exports patterns"
60
+ echo " - Dynamic requires"
61
+ echo ""
62
+ echo "Test with: npm run agent"