genlayer 0.2.0 → 0.3.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.
@@ -10,6 +10,7 @@ module.exports = {
10
10
  banner: {
11
11
  js: "const _importMetaUrl=require('url').pathToFileURL(__filename)",
12
12
  },
13
+ external: ['ssh2'],
13
14
  },
14
15
  watch: true,
15
16
  };
@@ -10,6 +10,7 @@ module.exports = {
10
10
  banner: {
11
11
  js: "const _importMetaUrl=require('url').pathToFileURL(__filename)",
12
12
  },
13
+ external: ['ssh2'],
13
14
  },
14
15
  watch: false,
15
16
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "GenLayer Command Line Tool",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -34,6 +34,7 @@
34
34
  "homepage": "https://github.com/yeagerai/genlayer-cli#readme",
35
35
  "devDependencies": {
36
36
  "@release-it/conventional-changelog": "^8.0.1",
37
+ "@types/dockerode": "^3.3.31",
37
38
  "@types/inquirer": "^9.0.7",
38
39
  "@types/node": "^20.12.7",
39
40
  "@types/sinon": "^17.0.3",
@@ -55,6 +56,7 @@
55
56
  },
56
57
  "dependencies": {
57
58
  "commander": "^12.0.0",
59
+ "dockerode": "^4.0.2",
58
60
  "dotenv": "^16.4.5",
59
61
  "inquirer": "^9.2.19",
60
62
  "node-fetch": "^2.7.0",
@@ -226,9 +226,9 @@ export async function initAction(options: InitActionOptions, simulatorService: I
226
226
  }
227
227
 
228
228
  // Simulator ready
229
- console.log(
230
- `GenLayer simulator initialized successfully! Go to ${simulatorService.getFrontendUrl()} in your browser to access it.`,
231
- );
229
+ let successMessage = "GenLayer simulator initialized successfully! "
230
+ successMessage += options.headless ? '' : `Go to ${simulatorService.getFrontendUrl()} in your browser to access it.`;
231
+ console.log(successMessage);
232
232
  try {
233
233
  if(!options.headless){
234
234
  await simulatorService.openFrontend();
@@ -98,9 +98,9 @@ export async function startAction(options: StartActionOptions, simulatorService:
98
98
  }
99
99
 
100
100
  // Simulator ready
101
- console.log(
102
- `GenLayer simulator initialized successfully! Go to ${simulatorService.getFrontendUrl()} in your browser to access it.`,
103
- );
101
+ let successMessage = "GenLayer simulator initialized successfully! "
102
+ successMessage += headless ? '' : `Go to ${simulatorService.getFrontendUrl()} in your browser to access it.`;
103
+ console.log(successMessage);
104
104
  try {
105
105
  if(!headless) {
106
106
  await simulatorService.openFrontend();
@@ -1,5 +1,5 @@
1
1
  import util from "node:util";
2
- import {ChildProcess, PromiseWithChild, exec} from "child_process";
2
+ import {ChildProcess, exec} from "child_process";
3
3
  import open from "open";
4
4
 
5
5
  import {RunningPlatform, AVAILABLE_PLATFORMS} from "../config/simulator";
@@ -68,47 +68,3 @@ export async function getVersion(toolName: string): Promise<string> {
68
68
 
69
69
  return "";
70
70
  }
71
-
72
- export async function listDockerContainers(): Promise<string[]> {
73
- try {
74
- const dockerResponse = await util.promisify(exec)("docker ps -a --format '{{.Names}}'");
75
- const dockerContainers = dockerResponse.stdout.split("\n");
76
- return dockerContainers;
77
- } catch (error) {
78
- throw new Error("Error listing Docker containers.");
79
- }
80
- }
81
-
82
- export async function listDockerImages(): Promise<string[]> {
83
- try {
84
- const dockerResponse = await util.promisify(exec)("docker images --format '{{.Repository}}'");
85
- const dockerImages = dockerResponse.stdout.split("\n");
86
- return dockerImages;
87
- } catch (error) {
88
- throw new Error("Error listing Docker images.");
89
- }
90
- }
91
-
92
- export async function stopDockerContainer(containerName: string): Promise<void> {
93
- try {
94
- await util.promisify(exec)(`docker stop ${containerName}`);
95
- } catch (error) {
96
- throw new Error(`Error stopping Docker container ${containerName}.`);
97
- }
98
- }
99
-
100
- export async function removeDockerContainer(containerName: string) {
101
- try {
102
- await util.promisify(exec)(`docker rm ${containerName}`);
103
- } catch (error) {
104
- throw new Error(`Error removing container ${containerName}.`);
105
- }
106
- }
107
-
108
- export async function removeDockerImage(imageName: string) {
109
- try {
110
- await util.promisify(exec)(`docker rmi ${imageName}`);
111
- } catch (error) {
112
- throw new Error(`Error removing image ${imageName}.`);
113
- }
114
- }
@@ -1,16 +1,11 @@
1
1
  export const DEFAULT_JSON_RPC_URL = "http://localhost:4000/api";
2
2
  export const DEFAULT_REPO_GH_URL = "https://github.com/yeagerai/genlayer-simulator.git";
3
- export const DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX = "genlayer-simulator-";
3
+ export const DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX = "/genlayer-simulator-";
4
4
  export const DEFAULT_RUN_SIMULATOR_COMMAND = (simulatorLocation: string, options: string) => ({
5
5
  darwin: `osascript -e 'tell application "Terminal" to do script "cd ${simulatorLocation} && docker compose build && docker compose up ${options}"'`,
6
6
  win32: `start cmd.exe /c "cd /d ${simulatorLocation} && docker compose build && docker compose up && pause ${options}"`,
7
7
  linux: `nohup bash -c 'cd ${simulatorLocation} && docker compose build && docker compose up -d ${options}'`,
8
8
  });
9
- export const DEFAULT_PULL_OLLAMA_COMMAND = (simulatorLocation: string) => ({
10
- darwin: `cd ${simulatorLocation} && docker exec ollama ollama pull llama3`,
11
- win32: `cd /d ${simulatorLocation} && docker exec ollama ollama pull llama3`,
12
- linux: `cd ${simulatorLocation} && docker exec ollama ollama pull llama3`,
13
- });
14
9
  export const DEFAULT_RUN_DOCKER_COMMAND = {
15
10
  darwin: "open -a Docker",
16
11
  win32: 'start "" "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"',
@@ -1,3 +1,4 @@
1
+ import Docker from "dockerode"
1
2
  import * as fs from "fs";
2
3
  import * as dotenv from "dotenv";
3
4
  import * as path from "path";
@@ -9,7 +10,6 @@ import {
9
10
  DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX,
10
11
  DEFAULT_RUN_SIMULATOR_COMMAND,
11
12
  DEFAULT_RUN_DOCKER_COMMAND,
12
- DEFAULT_PULL_OLLAMA_COMMAND,
13
13
  STARTING_TIMEOUT_WAIT_CYLCE,
14
14
  STARTING_TIMEOUT_ATTEMPTS,
15
15
  AI_PROVIDERS_CONFIG,
@@ -21,11 +21,6 @@ import {
21
21
  getVersion,
22
22
  executeCommand,
23
23
  openUrl,
24
- listDockerContainers,
25
- stopDockerContainer,
26
- removeDockerContainer,
27
- listDockerImages,
28
- removeDockerImage,
29
24
  } from "../clients/system";
30
25
  import {MissingRequirementError} from "../errors/missingRequirement";
31
26
 
@@ -36,17 +31,20 @@ import {
36
31
  } from "../interfaces/ISimulatorService";
37
32
  import {VersionRequiredError} from "../errors/versionRequired";
38
33
 
34
+
39
35
  function sleep(millliseconds: number): Promise<void> {
40
36
  return new Promise(resolve => setTimeout(resolve, millliseconds));
41
37
  }
42
38
 
43
39
  export class SimulatorService implements ISimulatorService {
44
40
  private composeOptions: string
41
+ private docker: Docker;
45
42
  public simulatorLocation: string;
46
43
 
47
44
  constructor() {
48
45
  this.simulatorLocation = "";
49
46
  this.composeOptions = "";
47
+ this.docker = new Docker();
50
48
  }
51
49
 
52
50
  public setSimulatorLocation(location: string): void {
@@ -122,7 +120,7 @@ export class SimulatorService implements ISimulatorService {
122
120
 
123
121
  if (requirementsInstalled.docker) {
124
122
  try {
125
- await checkCommand("docker ps", "docker");
123
+ await this.docker.ping()
126
124
  } catch (error: any) {
127
125
  await executeCommand(DEFAULT_RUN_DOCKER_COMMAND);
128
126
  }
@@ -205,8 +203,10 @@ export class SimulatorService implements ISimulatorService {
205
203
  }
206
204
 
207
205
  public async pullOllamaModel(): Promise<boolean> {
208
- const cmdsByPlatform = DEFAULT_PULL_OLLAMA_COMMAND(this.simulatorLocation);
209
- await executeCommand(cmdsByPlatform);
206
+ const ollamaContainer = this.docker.getContainer("ollama");
207
+ await ollamaContainer.exec({
208
+ Cmd: ["ollama", "pull", "llama3"],
209
+ });
210
210
  return true;
211
211
  }
212
212
 
@@ -287,30 +287,33 @@ export class SimulatorService implements ISimulatorService {
287
287
  }
288
288
 
289
289
  public async resetDockerContainers(): Promise<boolean> {
290
- const containers = await listDockerContainers();
291
- const genlayerContainers = containers.filter((container: string) =>
292
- container.startsWith(DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX),
293
- );
294
- const containersStopPromises = genlayerContainers.map((container: string) =>
295
- stopDockerContainer(container),
296
- );
297
- await Promise.all(containersStopPromises);
298
-
299
- const containersRemovePromises = genlayerContainers.map((container: string) =>
300
- removeDockerContainer(container),
290
+ const containers = await this.docker.listContainers({ all: true });
291
+ const genlayerContainers = containers.filter(container =>
292
+ container.Names.some(name =>
293
+ name.startsWith(DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX)
294
+ )
301
295
  );
302
- await Promise.all(containersRemovePromises);
303
296
 
297
+ for (const containerInfo of genlayerContainers) {
298
+ const container = this.docker.getContainer(containerInfo.Id);
299
+ if (containerInfo.State === "running") {
300
+ await container.stop();
301
+ }
302
+ await container.remove();
303
+ }
304
304
  return true;
305
305
  }
306
306
 
307
307
  public async resetDockerImages(): Promise<boolean> {
308
- const images = await listDockerImages();
309
- const genlayerImages = images.filter((image: string) =>
310
- image.startsWith(DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX),
308
+ const images = await this.docker.listImages();
309
+ const genlayerImages = images.filter(image =>
310
+ image.RepoTags?.some(tag => tag.startsWith(DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX))
311
311
  );
312
- const imagesRemovePromises = genlayerImages.map((image: string) => removeDockerImage(image));
313
- await Promise.all(imagesRemovePromises);
312
+
313
+ for (const imageInfo of genlayerImages) {
314
+ const image = this.docker.getImage(imageInfo.Id);
315
+ await image.remove({force: true});
316
+ }
314
317
 
315
318
  return true;
316
319
  }
@@ -1,4 +1,4 @@
1
- import { vi, describe, beforeEach, afterEach, test, expect } from "vitest";
1
+ import {vi, describe, beforeEach, afterEach, test, expect} from "vitest";
2
2
  import inquirer from "inquirer";
3
3
  import simulatorService from "../../src/lib/services/simulator";
4
4
  import { initAction } from "../../src/commands/general/init";
@@ -50,6 +50,15 @@ describe("init action", () => {
50
50
  simServCreateRandomValidators = vi.spyOn(simulatorService, "createRandomValidators");
51
51
  simServOpenFrontend = vi.spyOn(simulatorService, "openFrontend");
52
52
  simGetSimulatorUrl = vi.spyOn(simulatorService, "getFrontendUrl")
53
+
54
+ simServCheckVersionRequirements.mockResolvedValue({
55
+ node: '',
56
+ docker: '',
57
+ });
58
+ simServCheckInstallRequirements.mockResolvedValue({
59
+ git: true,
60
+ docker: true,
61
+ })
53
62
  });
54
63
 
55
64
  afterEach(() => {
@@ -168,6 +177,8 @@ describe("init action", () => {
168
177
  simServCreateRandomValidators.mockResolvedValue(true);
169
178
  simServOpenFrontend.mockResolvedValue(true);
170
179
  simGetSimulatorUrl.mockResolvedValue('http://localhost:8080/');
180
+ simServResetDockerContainers.mockResolvedValue(true);
181
+ simServResetDockerImages.mockResolvedValue(true);
171
182
 
172
183
  await initAction(defaultActionOptions, simulatorService);
173
184
 
@@ -177,9 +188,68 @@ describe("init action", () => {
177
188
  );
178
189
  });
179
190
 
191
+ test("should open the frontend if everything went well (headless mode)", async () => {
192
+ inquirerPrompt.mockResolvedValue({
193
+ confirmReset: true,
194
+ confirmDownload: true,
195
+ selectedLlmProviders: ["openai", "heuristai"],
196
+ openai: "API_KEY1",
197
+ heuristai: "API_KEY2",
198
+ });
199
+ simServgetAiProvidersOptions.mockReturnValue([
200
+ { name: "OpenAI", value: "openai" },
201
+ { name: "Heurist", value: "heuristai" },
202
+ ]);
203
+ simServConfigSimulator.mockResolvedValue(true);
204
+ simServRunSimulator.mockResolvedValue(true);
205
+ simServWaitForSimulator.mockResolvedValue({ initialized: true });
206
+ simServPullOllamaModel.mockResolvedValue(true);
207
+ simServDeleteAllValidators.mockResolvedValue(true);
208
+ simServCreateRandomValidators.mockResolvedValue(true);
209
+ simServOpenFrontend.mockResolvedValue(true);
210
+ simGetSimulatorUrl.mockResolvedValue('http://localhost:8080/');
211
+ simServResetDockerContainers.mockResolvedValue(true);
212
+ simServResetDockerImages.mockResolvedValue(true);
213
+
214
+ await initAction({...defaultActionOptions, headless: true}, simulatorService);
215
+
216
+ expect(log).toHaveBeenCalledWith(
217
+ `GenLayer simulator initialized successfully! `
218
+ );
219
+ });
220
+
221
+ test("should throw an error if validator are not initialized", async () => {
222
+ inquirerPrompt.mockResolvedValue({
223
+ confirmReset: true,
224
+ confirmDownload: true,
225
+ selectedLlmProviders: ["openai", "heuristai"],
226
+ openai: "API_KEY1",
227
+ heuristai: "API_KEY2",
228
+ });
229
+ simServgetAiProvidersOptions.mockReturnValue([
230
+ { name: "OpenAI", value: "openai" },
231
+ { name: "Heurist", value: "heuristai" },
232
+ ]);
233
+ simServConfigSimulator.mockResolvedValue(true);
234
+ simServRunSimulator.mockResolvedValue(true);
235
+ simServWaitForSimulator.mockResolvedValue({ initialized: true });
236
+ simServPullOllamaModel.mockResolvedValue(true);
237
+ simServDeleteAllValidators.mockResolvedValue(true);
238
+ simServCreateRandomValidators.mockRejectedValue();
239
+ simServOpenFrontend.mockResolvedValue(true);
240
+
241
+ await initAction({...defaultActionOptions, headless: true}, simulatorService);
242
+
243
+ expect(log).toHaveBeenCalledWith('Initializing validators...');
244
+ expect(error).toHaveBeenCalledWith('Unable to initialize the validators.');
245
+ });
246
+
247
+
180
248
  test("if configSimulator fails, then the execution aborts", async () => {
181
249
  inquirerPrompt.mockResolvedValue({ confirmReset: true, confirmDownload: true, selectedLlmProviders: [] });
182
250
  simServConfigSimulator.mockRejectedValue(new Error("Error"));
251
+ simServResetDockerContainers.mockResolvedValue(true);
252
+ simServResetDockerImages.mockResolvedValue(true);
183
253
 
184
254
  await initAction(defaultActionOptions, simulatorService);
185
255
 
@@ -189,6 +259,8 @@ describe("init action", () => {
189
259
  test("if runSimulator fails, then the execution aborts", async () => {
190
260
  inquirerPrompt.mockResolvedValue({ confirmReset: true, confirmDownload: true, selectedLlmProviders: [] });
191
261
  simServRunSimulator.mockRejectedValue(new Error("Error"));
262
+ simServResetDockerContainers.mockResolvedValue(true);
263
+ simServResetDockerImages.mockResolvedValue(true);
192
264
 
193
265
  await initAction(defaultActionOptions, simulatorService);
194
266
 
@@ -214,6 +286,8 @@ describe("init action", () => {
214
286
  simServWaitForSimulator.mockResolvedValue({ initialized: true });
215
287
  simServPullOllamaModel.mockResolvedValue(true);
216
288
  simServDeleteAllValidators.mockResolvedValue(true);
289
+ simServResetDockerContainers.mockResolvedValue(true);
290
+ simServResetDockerImages.mockResolvedValue(true);
217
291
 
218
292
  await initAction(defaultActionOptions, simulatorService);
219
293
 
@@ -59,6 +59,20 @@ describe("startAction - Additional Tests", () => {
59
59
  expect(simulatorService.openFrontend).toHaveBeenCalled();
60
60
  });
61
61
 
62
+ test("runs successfully with default options and keeps existing validators (headless)", async () => {
63
+ await startAction({...defaultOptions, headless: true}, simulatorService);
64
+
65
+ expect(simulatorService.updateSimulator).toHaveBeenCalledWith("main");
66
+ expect(simulatorService.runSimulator).toHaveBeenCalled();
67
+ expect(simulatorService.waitForSimulatorToBeReady).toHaveBeenCalled();
68
+
69
+ expect(logSpy).toHaveBeenCalledWith("Starting GenLayer simulator keeping the existing validators");
70
+ expect(logSpy).toHaveBeenCalledWith("Updating GenLayer Simulator...");
71
+ expect(logSpy).toHaveBeenCalledWith("Running the GenLayer Simulator...");
72
+ expect(logSpy).toHaveBeenCalledWith("Simulator is running!");
73
+ expect(logSpy).toHaveBeenCalledWith("GenLayer simulator initialized successfully! ");
74
+ });
75
+
62
76
  test("logs error and stops if updateSimulator fails", async () => {
63
77
  const errorMsg = new Error("updateSimulator error");
64
78
  (simulatorService.updateSimulator as Mock).mockRejectedValueOnce(errorMsg);
@@ -4,12 +4,7 @@ import {
4
4
  checkCommand,
5
5
  executeCommand,
6
6
  openUrl,
7
- getVersion,
8
- listDockerContainers,
9
- listDockerImages,
10
- stopDockerContainer,
11
- removeDockerContainer,
12
- removeDockerImage
7
+ getVersion
13
8
  } from "../../src/lib/clients/system";
14
9
  import { MissingRequirementError } from "../../src/lib/errors/missingRequirement";
15
10
  import open from "open";
@@ -59,48 +54,6 @@ describe("System Functions - Success Paths", () => {
59
54
  expect(result.stdout).toBe("echo linux");
60
55
  platformSpy.mockRestore();
61
56
  });
62
-
63
- test("listDockerContainers retrieves a list of containers", async () => {
64
- vi.mocked(util.promisify).mockReturnValueOnce(() => Promise.resolve({
65
- stdout: "container1\ncontainer2",
66
- stderr: ""
67
- }));
68
- const containers = await listDockerContainers();
69
- expect(containers).toEqual(["container1", "container2"]);
70
- });
71
-
72
- test("listDockerImages retrieves a list of images", async () => {
73
- vi.mocked(util.promisify).mockReturnValueOnce(() => Promise.resolve({
74
- stdout: "image1\nimage2",
75
- stderr: ""
76
- }));
77
- const images = await listDockerImages();
78
- expect(images).toEqual(["image1", "image2"]);
79
- });
80
-
81
- test("stopDockerContainer stops a container", async () => {
82
- const containerId = "container123";
83
- const execMock = vi.fn().mockResolvedValue({ stdout: "", stderr: "" });
84
- vi.mocked(util.promisify).mockReturnValue(execMock);
85
- await stopDockerContainer(containerId);
86
- expect(execMock).toHaveBeenCalledWith(`docker stop ${containerId}`);
87
- });
88
-
89
- test("removeDockerContainer removes a container", async () => {
90
- const containerId = "container123";
91
- const execMock = vi.fn().mockResolvedValue({ stdout: "", stderr: "" });
92
- vi.mocked(util.promisify).mockReturnValue(execMock);
93
- await removeDockerContainer(containerId);
94
- expect(execMock).toHaveBeenCalledWith(`docker rm ${containerId}`);
95
- });
96
-
97
- test("removeDockerImage removes an image", async () => {
98
- const imageId = "image123";
99
- const execMock = vi.fn().mockResolvedValue({ stdout: "", stderr: "" });
100
- vi.mocked(util.promisify).mockReturnValue(execMock);
101
- await removeDockerImage(imageId);
102
- expect(execMock).toHaveBeenCalledWith(`docker rmi ${imageId}`);
103
- });
104
57
  });
105
58
 
106
59
  describe("System Functions - Error Paths", () => {
@@ -153,34 +106,6 @@ describe("System Functions - Error Paths", () => {
153
106
  "echo")).rejects.toThrow("Execution failed");
154
107
  });
155
108
 
156
- test("stopDockerContainer throws an error if stopping fails", async () => {
157
- const containerId = "container123";
158
- vi.mocked(util.promisify).mockReturnValueOnce(() => Promise.reject(new Error("")));
159
- await expect(stopDockerContainer(containerId)).rejects.toThrow("Error stopping Docker container container123");
160
- });
161
-
162
- test("removeDockerContainer throws an error if removal fails", async () => {
163
- const containerId = "container123";
164
- vi.mocked(util.promisify).mockReturnValueOnce(() => Promise.reject(new Error("")));
165
- await expect(removeDockerContainer(containerId)).rejects.toThrow("Error removing container container123.");
166
- });
167
-
168
- test("removeDockerImage throws an error if image removal fails", async () => {
169
- const imageId = "image123";
170
- vi.mocked(util.promisify).mockReturnValueOnce(() => Promise.reject(new Error("")));
171
- await expect(removeDockerImage(imageId)).rejects.toThrow("Error removing image image123.");
172
- });
173
-
174
- test("throws error when command fails", async () => {
175
- vi.mocked(util.promisify).mockReturnValueOnce(() => Promise.reject(new Error("")));
176
- await expect(listDockerContainers()).rejects.toThrow("Error listing Docker containers.");
177
- });
178
-
179
- test("throws error when command fails", async () => {
180
- vi.mocked(util.promisify).mockReturnValueOnce(() => Promise.reject(new Error("")));
181
- await expect(listDockerImages()).rejects.toThrow("Error listing Docker images.");
182
- });
183
-
184
109
  test("throws error when command execution fails", async () => {
185
110
  vi.mocked(util.promisify).mockReturnValueOnce(() => Promise.reject(new Error("Execution error.")));
186
111
  await expect(executeCommand({