dhti-cli 0.1.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.
Files changed (43) hide show
  1. package/README.md +2 -0
  2. package/dist/commands/compose.d.ts +14 -0
  3. package/dist/commands/compose.js +112 -0
  4. package/dist/commands/conch.d.ts +19 -0
  5. package/dist/commands/conch.js +119 -0
  6. package/dist/commands/docker.d.ts +16 -0
  7. package/dist/commands/docker.js +76 -0
  8. package/dist/commands/elixir.d.ts +20 -0
  9. package/dist/commands/elixir.js +129 -0
  10. package/dist/commands/mimic.d.ts +9 -0
  11. package/dist/commands/mimic.js +157 -0
  12. package/dist/commands/synthetic.d.ts +17 -0
  13. package/dist/commands/synthetic.js +88 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.js +2 -0
  16. package/dist/resources/docker-compose-master.yml +238 -0
  17. package/dist/resources/genai/Dockerfile +27 -0
  18. package/dist/resources/genai/README.md +1 -0
  19. package/dist/resources/genai/app/__init__.py +0 -0
  20. package/dist/resources/genai/app/bootstrap.py +43 -0
  21. package/dist/resources/genai/app/server.py +57 -0
  22. package/dist/resources/genai/pyproject.toml +84 -0
  23. package/dist/resources/genai/whl/placeholder.md +0 -0
  24. package/dist/resources/spa/Dockerfile +16 -0
  25. package/dist/resources/spa/def/importmap.json +39 -0
  26. package/dist/resources/spa/def/routes.registry.json +1599 -0
  27. package/dist/resources/spa/def/spa-assemble-config.json +40 -0
  28. package/dist/utils/bootstrap.d.ts +3 -0
  29. package/dist/utils/bootstrap.js +57 -0
  30. package/dist/utils/card.d.ts +58 -0
  31. package/dist/utils/card.js +76 -0
  32. package/dist/utils/chain.d.ts +5 -0
  33. package/dist/utils/chain.js +17 -0
  34. package/dist/utils/getCard.d.ts +3 -0
  35. package/dist/utils/getCard.js +11 -0
  36. package/dist/utils/index.d.ts +2 -0
  37. package/dist/utils/index.js +3 -0
  38. package/dist/utils/request.d.ts +30 -0
  39. package/dist/utils/request.js +34 -0
  40. package/dist/utils/useDhti.d.ts +5 -0
  41. package/dist/utils/useDhti.js +21 -0
  42. package/oclif.manifest.json +442 -2
  43. package/package.json +7 -5
package/README.md CHANGED
@@ -234,6 +234,8 @@ You can remove the services by: `dhti-cli docker -d`
234
234
  ## Give us a star ⭐️
235
235
  If you find this project useful, give us a star. It helps others discover the project.
236
236
 
237
+ ## [Details of CLI Commands](/notes/README.md)
238
+
237
239
  ## Contributors
238
240
 
239
241
  * [Bell Eapen](https://nuchange.ca) | [![Twitter Follow](https://img.shields.io/twitter/follow/beapen?style=social)](https://twitter.com/beapen)
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Compose extends Command {
3
+ static args: {
4
+ op: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ file: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ module: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
12
+ static init: () => void;
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,112 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import yaml from 'js-yaml';
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ export default class Compose extends Command {
6
+ static args = {
7
+ op: Args.string({ description: 'Operation to perform (add, delete, read or reset)' }),
8
+ };
9
+ static description = 'Generates a docker-compose.yml file from a list of modules';
10
+ static examples = [
11
+ '<%= config.bin %> <%= command.id %>',
12
+ ];
13
+ static flags = {
14
+ file: Flags.string({ char: 'f', default: `${os.homedir()}/dhti/docker-compose.yml`, description: 'Full path to the docker compose file to read from. Creates if it does not exist' }),
15
+ // flag with a value (-n, --name=VALUE)
16
+ module: Flags.string({ char: 'm', description: 'Modules to add from ( langserve, openmrs, ollama, langfuse, cqlFhir, redis, neo4j and mcpFhir)', multiple: true }),
17
+ };
18
+ static init = () => {
19
+ // Create ${os.homedir()}/dhti if it does not exist
20
+ if (!fs.existsSync(`${os.homedir()}/dhti`)) {
21
+ fs.mkdirSync(`${os.homedir()}/dhti`);
22
+ }
23
+ // Create ${os.homedir()}/dhti/docker-compose.yml if it does not exist
24
+ if (!fs.existsSync(`${os.homedir()}/dhti/docker-compose.yml`)) {
25
+ fs.writeFileSync(`${os.homedir()}/dhti/docker-compose.yml`, '', 'utf8');
26
+ }
27
+ };
28
+ async run() {
29
+ const { args, flags } = await this.parse(Compose);
30
+ // console.log('args', args) //args { op: 'add' }
31
+ // console.log('flags', flags) //flags { module: [ 'default', 'langserve', 'redis' ] }
32
+ const openmrs = ['gateway', 'frontend', 'backend', 'openmrs-db'];
33
+ const langserve = ['langserve'];
34
+ const ollama = ['ollama', 'ollama-webui'];
35
+ const langfuse = ['langfuse', 'postgres-db'];
36
+ const cqlFhir = ['fhir', 'postgres-db', 'cql-elm', 'cql-web'];
37
+ const redis = ['redis', 'redis-commander'];
38
+ const neo4j = ['neo4j', 'fhirg'];
39
+ const gateway = ['gateway'];
40
+ const webui = ['ollama-webui'];
41
+ const fhir = ['fhir', 'postgres-db'];
42
+ const mcpFhir = ['mcp-fhir', 'fhir', 'postgres-db'];
43
+ const _modules = {
44
+ cqlFhir,
45
+ fhir,
46
+ gateway,
47
+ langfuse,
48
+ langserve,
49
+ mcpFhir,
50
+ neo4j,
51
+ ollama,
52
+ openmrs,
53
+ redis,
54
+ webui
55
+ };
56
+ try {
57
+ const masterData = yaml.load(fs.readFileSync('src/resources/docker-compose-master.yml', 'utf8'));
58
+ let existingData = { services: {}, version: '3.8' };
59
+ if (fs.existsSync(flags.file)) {
60
+ existingData = yaml.load(fs.readFileSync(flags.file, 'utf8'));
61
+ }
62
+ else {
63
+ Compose.init(); // Create the file if it does not exist
64
+ }
65
+ // Echo the existing data to the console
66
+ if (args.op === 'read') {
67
+ console.log(existingData);
68
+ return;
69
+ }
70
+ // Delete flags.file if args.op is reset
71
+ if (args.op === 'reset') {
72
+ fs.unlinkSync(flags.file);
73
+ Compose.init(); // Recreate the file
74
+ }
75
+ // if existing data is not null and arg is delete, remove the modules from the existing data
76
+ if (Object.keys(existingData.services).length > 0 && args.op === 'delete') {
77
+ // Expand the modules to remove using _modules object
78
+ let modulesToDelete = [];
79
+ for (const module of flags.module ?? []) {
80
+ modulesToDelete = modulesToDelete.concat(_modules[module]);
81
+ }
82
+ for (const module of modulesToDelete ?? []) {
83
+ if (existingData.services[module]) {
84
+ delete existingData.services[module];
85
+ }
86
+ }
87
+ }
88
+ // if arg is add, add the modules from the master data
89
+ if (args.op === 'add') {
90
+ // Expand the modules to add using _modules object
91
+ let modulesToAdd = [];
92
+ for (const module of flags.module ?? []) {
93
+ modulesToAdd = modulesToAdd.concat(_modules[module]);
94
+ }
95
+ for (const module of modulesToAdd ?? []) {
96
+ existingData.services[module] = masterData.services[module];
97
+ }
98
+ }
99
+ // Add all volumes from master data to existing data by default
100
+ existingData.volumes = {};
101
+ for (const key of Object.keys(masterData.volumes)) {
102
+ existingData.volumes[key] = masterData.volumes[key];
103
+ }
104
+ const toWrite = yaml.dump(existingData).replaceAll('null', '');
105
+ console.log('Writing file:', toWrite);
106
+ fs.writeFileSync(flags.file, toWrite, 'utf8');
107
+ }
108
+ catch (error) {
109
+ console.error(error);
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,19 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Conch extends Command {
3
+ static args: {
4
+ op: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ branch: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ container: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ dev: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ git: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ image: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ repoVersion: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
16
+ workdir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
17
+ };
18
+ run(): Promise<void>;
19
+ }
@@ -0,0 +1,119 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import { exec } from 'node:child_process';
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ export default class Conch extends Command {
6
+ static args = {
7
+ op: Args.string({ description: 'Operation to perform (install, uninstall or dev)' }),
8
+ };
9
+ static description = 'Install or uninstall conchs to create a Docker image';
10
+ static examples = [
11
+ '<%= config.bin %> <%= command.id %>',
12
+ ];
13
+ static flags = {
14
+ branch: Flags.string({ char: 'b', default: "develop", description: 'Branch to install from' }),
15
+ container: Flags.string({ char: 'c', default: "dhti-frontend-1", description: 'Name of the container to copy the conch to while in dev mode' }),
16
+ dev: Flags.string({ char: 'd', default: "none", description: 'Dev folder to install' }),
17
+ git: Flags.string({ char: 'g', default: "none", description: 'Github repository to install' }),
18
+ image: Flags.string({ char: 'i', default: "openmrs/openmrs-reference-application-3-frontend:3.0.0-beta.17", description: 'Base image to use for the conch' }),
19
+ name: Flags.string({ char: 'n', description: 'Name of the elixir' }),
20
+ repoVersion: Flags.string({ char: 'v', default: "1.0.0", description: 'Version of the conch' }),
21
+ workdir: Flags.string({ char: 'w', default: `${os.homedir()}/dhti`, description: 'Working directory to install the conch' }),
22
+ };
23
+ async run() {
24
+ const { args, flags } = await this.parse(Conch);
25
+ if (!flags.name) {
26
+ console.log("Please provide a name for the conch");
27
+ this.exit(1);
28
+ }
29
+ // if arg is dev then copy to docker as below
30
+ // docker cp ../../openmrs-esm-genai/dist/. dhti-frontend-1:/usr/share/nginx/html/openmrs-esm-genai-1.0.0
31
+ // docker restart dhti-frontend-1
32
+ if (args.op === 'dev') {
33
+ console.log(`cd ${flags.dev} && yarn build && docker cp dist/. ${flags.container}:/usr/share/nginx/html/${flags.name}-${flags.repoVersion}`);
34
+ try {
35
+ exec(`cd ${flags.dev} && yarn build && docker cp dist/. ${flags.container}:/usr/share/nginx/html/${flags.name}-${flags.repoVersion}`, (error, stdout, stderr) => {
36
+ if (error) {
37
+ console.error(`exec error: ${error}`);
38
+ return;
39
+ }
40
+ console.log(`stdout: ${stdout}`);
41
+ console.error(`stderr: ${stderr}`);
42
+ });
43
+ exec(`docker restart ${flags.container}`, (error, stdout, stderr) => {
44
+ if (error) {
45
+ console.error(`exec error: ${error}`);
46
+ return;
47
+ }
48
+ console.log(`stdout: ${stdout}`);
49
+ console.error(`stderr: ${stderr}`);
50
+ });
51
+ }
52
+ catch (error) {
53
+ console.log("Error copying conch to container", error);
54
+ }
55
+ }
56
+ // Create a directory to install the elixir
57
+ if (!fs.existsSync(`${flags.workdir}/conch`)) {
58
+ fs.mkdirSync(`${flags.workdir}/conch`);
59
+ }
60
+ fs.cpSync('src/resources/spa', `${flags.workdir}/conch`, { recursive: true });
61
+ // Rewrite files
62
+ const rewrite = () => {
63
+ flags.name = flags.name ?? 'openmrs-esm-genai';
64
+ // Read and process importmap.json
65
+ const importmap = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/def/importmap.json`, 'utf8'));
66
+ if (args.op === 'install')
67
+ importmap.imports[flags.name.replace('openmrs-', '@openmrs/')] = `./${flags.name}-${flags.repoVersion}/${flags.name}.js`;
68
+ if (args.op === 'uninstall')
69
+ delete importmap.imports[flags.name.replace('openmrs-', '@openmrs/')];
70
+ fs.writeFileSync(`${flags.workdir}/conch/def/importmap.json`, JSON.stringify(importmap, null, 2));
71
+ // Read and process spa-assemble-config.json
72
+ const spaAssembleConfig = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/def/spa-assemble-config.json`, 'utf8'));
73
+ if (args.op === 'install')
74
+ spaAssembleConfig.frontendModules[flags.name.replace('openmrs-', '@openmrs/')] = `${flags.repoVersion}`;
75
+ if (args.op === 'uninstall')
76
+ delete spaAssembleConfig.frontendModules[flags.name.replace('openmrs-', '@openmrs/')];
77
+ fs.writeFileSync(`${flags.workdir}/conch/def/spa-assemble-config.json`, JSON.stringify(spaAssembleConfig, null, 2));
78
+ // Read and process Dockerfile
79
+ let dockerfile = fs.readFileSync(`${flags.workdir}/conch/Dockerfile`, 'utf8');
80
+ dockerfile = dockerfile.replaceAll('conch', flags.name).replaceAll('version', flags.repoVersion).replaceAll('server-image', flags.image);
81
+ fs.writeFileSync(`${flags.workdir}/conch/Dockerfile`, dockerfile);
82
+ // Read routes.json
83
+ const routes = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/${flags.name}/src/routes.json`, 'utf8'));
84
+ // Add to routes.registry.json
85
+ const registry = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/def/routes.registry.json`, 'utf8'));
86
+ if (args.op === 'install')
87
+ registry[(flags.name).replace('openmrs-', '@openmrs/')] = routes;
88
+ if (args.op === 'uninstall')
89
+ delete registry[(flags.name).replace('openmrs-', '@openmrs/')];
90
+ fs.writeFileSync(`${flags.workdir}/conch/def/routes.registry.json`, JSON.stringify(registry, null, 2));
91
+ };
92
+ if (flags.git !== 'none') {
93
+ // git clone the repository
94
+ exec(`git clone ${flags.git} ${flags.workdir}/conch/${flags.name}`, (error, stdout, stderr) => {
95
+ if (error) {
96
+ console.error(`exec error: ${error}`);
97
+ return;
98
+ }
99
+ // Checkout the branch
100
+ exec(`cd ${flags.workdir}/conch/${flags.name} && git checkout ${flags.branch}`, (error, stdout, stderr) => {
101
+ if (error) {
102
+ console.error(`exec error: ${error}`);
103
+ return;
104
+ }
105
+ rewrite();
106
+ console.log(`stdout: ${stdout}`);
107
+ console.error(`stderr: ${stderr}`);
108
+ });
109
+ console.log(`stdout: ${stdout}`);
110
+ console.error(`stderr: ${stderr}`);
111
+ });
112
+ }
113
+ // If flags.dev is not none, copy the dev folder to the conch directory
114
+ if (flags.dev !== 'none' && args.op !== 'dev') {
115
+ fs.cpSync(flags.dev, `${flags.workdir}/conch/${flags.name}`, { recursive: true });
116
+ rewrite();
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,16 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Docker extends Command {
3
+ static args: {
4
+ path: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ down: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ file: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ up: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ };
15
+ run(): Promise<void>;
16
+ }
@@ -0,0 +1,76 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import yaml from 'js-yaml';
3
+ import { exec } from 'node:child_process';
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import ora from 'ora';
7
+ export default class Docker extends Command {
8
+ static args = {
9
+ path: Args.string({ default: `${os.homedir()}/dhti`, description: 'Docker project path to build. Ex: dhti' }),
10
+ };
11
+ static description = 'Build a docker project and update docker-compose file';
12
+ static examples = [
13
+ '<%= config.bin %> <%= command.id %>',
14
+ ];
15
+ static flags = {
16
+ down: Flags.boolean({ char: 'd', default: false, description: 'Run docker-compose down after building' }),
17
+ file: Flags.string({ char: 'f', default: `${os.homedir()}/dhti/docker-compose.yml`, description: 'Full path to the docker compose file to edit or run.' }),
18
+ name: Flags.string({ char: 'n', description: 'Name of the container to build' }),
19
+ type: Flags.string({ char: 't', default: 'elixir', description: 'Type of the service (elixir/conch)' }),
20
+ up: Flags.boolean({ char: 'u', default: false, description: 'Run docker-compose up after building' }),
21
+ };
22
+ async run() {
23
+ const { args, flags } = await this.parse(Docker);
24
+ if (flags.up) {
25
+ exec(`docker compose -f ${flags.file} up -d`, (error, stdout, stderr) => {
26
+ if (error) {
27
+ console.error(`exec error: ${error}`);
28
+ return;
29
+ }
30
+ console.log(`stdout: ${stdout}`);
31
+ console.error(`stderr: ${stderr}`);
32
+ });
33
+ return;
34
+ }
35
+ if (flags.down) {
36
+ exec(`docker compose -f ${flags.file} down`, (error, stdout, stderr) => {
37
+ if (error) {
38
+ console.error(`exec error: ${error}`);
39
+ return;
40
+ }
41
+ console.log(`stdout: ${stdout}`);
42
+ console.error(`stderr: ${stderr}`);
43
+ });
44
+ return;
45
+ }
46
+ if (!flags.name && (!flags.up || !flags.down)) {
47
+ console.log("Please provide a name for the container to build");
48
+ this.exit(1);
49
+ }
50
+ // cd to path, docker build tag with name
51
+ const spinner = ora('Running docker build ..').start();
52
+ exec(`cd ${args.path}/${flags.type} && docker build -t ${flags.name} . > /dev/null 2>&1`, (error, stdout, stderr) => {
53
+ if (error) {
54
+ spinner.fail('Docker build failed');
55
+ console.error(`exec error: ${error}`);
56
+ return;
57
+ }
58
+ spinner.succeed('Docker build successful');
59
+ console.log(`stdout: ${stdout}`);
60
+ console.error(`stderr: ${stderr}`);
61
+ });
62
+ // read the docker-compose file
63
+ const dockerCompose = yaml.load(fs.readFileSync(flags.file, 'utf8'));
64
+ // if type is elixir set image of backend to name, else set image of frontend to name
65
+ if (flags.type === 'elixir') {
66
+ dockerCompose.services.langserve.image = flags.name;
67
+ dockerCompose.services.langserve.pull_policy = "if_not_present";
68
+ }
69
+ else {
70
+ dockerCompose.services.frontend.image = flags.name;
71
+ dockerCompose.services.frontend.pull_policy = "if_not_present";
72
+ }
73
+ // write the docker-compose file
74
+ fs.writeFileSync(flags.file, yaml.dump(dockerCompose));
75
+ }
76
+ }
@@ -0,0 +1,20 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Elixir extends Command {
3
+ static args: {
4
+ op: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ branch: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ container: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ dev: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ git: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ pypi: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
+ repoVersion: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
16
+ whl: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
17
+ workdir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
18
+ };
19
+ run(): Promise<void>;
20
+ }
@@ -0,0 +1,129 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import { exec } from 'node:child_process';
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ export default class Elixir extends Command {
7
+ static args = {
8
+ op: Args.string({ description: 'Operation to perform (install, uninstall or dev)' }),
9
+ };
10
+ static description = 'Install or uninstall elixirs to create a Docker image';
11
+ static examples = ['<%= config.bin %> <%= command.id %>'];
12
+ static flags = {
13
+ branch: Flags.string({ char: 'b', default: 'develop', description: 'Branch to install from' }),
14
+ container: Flags.string({
15
+ char: 'c',
16
+ default: 'dhti-langserve-1',
17
+ description: 'Name of the container to copy the elixir to while in dev mode',
18
+ }),
19
+ dev: Flags.string({ char: 'd', default: 'none', description: 'Dev folder to install' }),
20
+ git: Flags.string({ char: 'g', default: 'none', description: 'Github repository to install' }),
21
+ name: Flags.string({ char: 'n', description: 'Name of the elixir' }),
22
+ pypi: Flags.string({
23
+ char: 'p',
24
+ default: 'none',
25
+ description: 'PyPi package to install. Ex: dhti-elixir-base = ">=0.1.0"',
26
+ }),
27
+ repoVersion: Flags.string({ char: 'v', default: '0.1.0', description: 'Version of the elixir' }),
28
+ whl: Flags.string({ char: 'e', default: 'none', description: 'Whl file to install' }),
29
+ workdir: Flags.string({
30
+ char: 'w',
31
+ default: `${os.homedir()}/dhti`,
32
+ description: 'Working directory to install the elixir',
33
+ }),
34
+ };
35
+ async run() {
36
+ const { args, flags } = await this.parse(Elixir);
37
+ if (!flags.name) {
38
+ console.log('Please provide a name for the elixir');
39
+ this.exit(1);
40
+ }
41
+ const expoName = flags.name.replaceAll('-', '_');
42
+ // if arg is dev then copy to docker as below
43
+ // docker restart dhti-langserve-1
44
+ if (args.op === 'dev') {
45
+ console.log(`cd ${flags.dev} && docker cp ${expoName}/. ${flags.container}:/app/.venv/lib/python3.11/site-packages/${expoName}`);
46
+ try {
47
+ exec(`cd ${flags.dev} && docker cp ${expoName}/. ${flags.container}:/app/.venv/lib/python3.11/site-packages/${expoName}`, (error, stdout, stderr) => {
48
+ if (error) {
49
+ console.error(`exec error: ${error}`);
50
+ return;
51
+ }
52
+ console.log(`stdout: ${stdout}`);
53
+ console.error(`stderr: ${stderr}`);
54
+ });
55
+ exec(`docker restart ${flags.container}`, (error, stdout, stderr) => {
56
+ if (error) {
57
+ console.error(`exec error: ${error}`);
58
+ return;
59
+ }
60
+ console.log(`stdout: ${stdout}`);
61
+ console.error(`stderr: ${stderr}`);
62
+ });
63
+ }
64
+ catch (error) {
65
+ console.log('Error copying conch to container', error);
66
+ }
67
+ }
68
+ // Create a directory to install the elixir
69
+ if (!fs.existsSync(`${flags.workdir}/elixir`)) {
70
+ fs.mkdirSync(`${flags.workdir}/elixir`);
71
+ }
72
+ fs.cpSync('src/resources/genai', `${flags.workdir}/elixir`, { recursive: true });
73
+ // if whl is not none, copy the whl file to thee whl directory
74
+ if (flags.whl !== 'none') {
75
+ if (!fs.existsSync(`${flags.workdir}/elixir/whl/`)) {
76
+ fs.mkdirSync(`${flags.workdir}/whl/`);
77
+ }
78
+ fs.cpSync(flags.whl, `${flags.workdir}/elixir/whl/${path.basename(flags.whl)}`);
79
+ console.log('Installing elixir from whl file. Please modify boostrap.py file if needed');
80
+ }
81
+ // Install the elixir from git adding to the pyproject.toml file
82
+ let pyproject = fs.readFileSync(`${flags.workdir}/elixir/pyproject.toml`, 'utf8');
83
+ const originalServer = fs.readFileSync(`${flags.workdir}/elixir/app/server.py`, 'utf8');
84
+ let lineToAdd = '';
85
+ if (flags.whl !== 'none') {
86
+ lineToAdd = `${flags.name} = { file = "whl/${path.basename(flags.whl)}" }`;
87
+ }
88
+ if (flags.git !== 'none') {
89
+ lineToAdd = `${flags.name} = { git = "${flags.git}", branch = "${flags.branch}" }`;
90
+ }
91
+ if (flags.pypi !== 'none') {
92
+ lineToAdd = flags.pypi;
93
+ }
94
+ pyproject = pyproject.replace('dependencies = [', `dependencies = [\n"${flags.name}",`);
95
+ pyproject = pyproject.replace('[tool.uv.sources]', `[tool.uv.sources]\n${lineToAdd}\n`);
96
+ const newPyproject = pyproject;
97
+ // Add the elixir import and bootstrap to the server.py file
98
+ let CliImport = `from ${expoName}.bootstrap import bootstrap as ${expoName}_bootstrap\n`;
99
+ CliImport += `${expoName}_bootstrap()\n`;
100
+ CliImport += `
101
+ from ${expoName}.chain import DhtiChain as ${expoName}_chain_class
102
+ ${expoName}_chain = ${expoName}_chain_class().get_chain_as_langchain_tool()
103
+ ${expoName}_mcp_tool = ${expoName}_chain_class().get_chain_as_mcp_tool
104
+ mcp_server.add_tool(${expoName}_mcp_tool) # type: ignore
105
+ `;
106
+ const newCliImport = fs
107
+ .readFileSync(`${flags.workdir}/elixir/app/server.py`, 'utf8')
108
+ .replace('# DHTI_CLI_IMPORT', `#DHTI_CLI_IMPORT\n${CliImport}`);
109
+ const langfuseRoute = `add_routes(app, ${expoName}_chain.with_config(config), path="/langserve/${expoName}")`;
110
+ const newLangfuseRoute = newCliImport.replace('# DHTI_LANGFUSE_ROUTE', `#DHTI_LANGFUSE_ROUTE\n ${langfuseRoute}`);
111
+ const normalRoute = `add_routes(app, ${expoName}_chain, path="/langserve/${expoName}")`;
112
+ const newNormalRoute = newLangfuseRoute.replace('# DHTI_NORMAL_ROUTE', `#DHTI_NORMAL_ROUTE\n ${normalRoute}`);
113
+ const commonRoutes = `\nadd_invokes(app, path="/langserve/${expoName}")\nadd_services(app, path="/langserve/${expoName}")`;
114
+ const finalRoute = newNormalRoute.replace('# DHTI_COMMON_ROUTE', `#DHTI_COMMON_ROUTES${commonRoutes}`);
115
+ // if args.op === install, add the line to the pyproject.toml file
116
+ if (args.op === 'install') {
117
+ fs.writeFileSync(`${flags.workdir}/elixir/pyproject.toml`, newPyproject);
118
+ fs.writeFileSync(`${flags.workdir}/elixir/app/server.py`, finalRoute);
119
+ }
120
+ if (args.op === 'uninstall') {
121
+ // if args.op === uninstall, remove the line from the pyproject.toml file
122
+ fs.writeFileSync(`${flags.workdir}/elixir/pyproject.toml`, pyproject.replace(lineToAdd, ''));
123
+ let newServer = originalServer.replace(CliImport, '');
124
+ newServer = newServer.replace(langfuseRoute, '');
125
+ newServer = newServer.replace(normalRoute, '');
126
+ fs.writeFileSync(`${flags.workdir}/elixir/app/server.py`, newServer);
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,9 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Mimic extends Command {
3
+ static args: {
4
+ server: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ run(): Promise<void>;
9
+ }