dhti-cli 0.3.3 → 0.4.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.
package/README.md CHANGED
@@ -31,7 +31,7 @@ The essence of DHTI is *modularity* with an emphasis on *configuration!* It is n
31
31
  <img src="https://github.com/dermatologist/openmrs-esm-dhti-template/blob/develop/notes/conch.jpg" />
32
32
  </p>
33
33
 
34
- *[OpenMRS ESM DHTI template](https://github.com/dermatologist/openmrs-esm-dhti-template) + [DHTI elixir template](https://github.com/dermatologist/dhti-elixir-template) together forms a simple but functional EMR chatbot too!* 👉 [Try it out today!](#try-it-out)
34
+ *[OpenMRS ESM DHTI template](https://github.com/dermatologist/openmrs-esm-dhti-template) (frontend) + [DHTI elixir template](https://github.com/dermatologist/dhti-elixir-template) (backend) together forms a simple but functional EMR chatbot too!* 👉 [Try it out today!](#try-it-out)
35
35
 
36
36
  <p align="center">
37
37
  <img src="https://github.com/dermatologist/dhti/blob/develop/notes/cds-hook-sandbox.jpg" />
@@ -66,6 +66,7 @@ The essence of DHTI is *modularity* with an emphasis on *configuration!* It is n
66
66
  * **Quick prototyping**: CLI helps in quick prototyping and testing of Gen AI routines and UI elements.
67
67
  * **Easy to use**: Can be installed in a few minutes.
68
68
  * **Developer friendly**: Copy working files to running containers for testing.
69
+ * **Dry-run mode**: Preview changes before execution with the `--dry-run` flag.
69
70
  * **Dependency Injection**: Dependency injection for models and hyperparameters for configuring elixirs.
70
71
  * **Generate synthetic data**: DHTI supports generating synthetic data for testing.
71
72
  * **CQL support**: [CQL for clinical decision support](https://nuchange.ca/2025/06/v-llm-in-the-loop-cql-execution-with-unstructured-data-and-fhir-terminology-support.html).
@@ -113,8 +114,8 @@ Tools to fine-tune language models for the stack are on our roadmap. We encourag
113
114
 
114
115
  ## :sparkles: Resources (in Beta)
115
116
  * [dhti-elixir-base](https://github.com/dermatologist/dhti-elixir-base): Base classes for dhti-elixir
116
- * [dhti-elixir-template](https://github.com/dermatologist/dhti-elixir-template): A template for creating new dhti-elixirs.
117
- * [openmrs-esm-dhti-template](https://github.com/dermatologist/openmrs-esm-dhti-template): A conch template for OpenMRS
117
+ * [dhti-elixir-template](https://github.com/dermatologist/dhti-elixir-template): A template for creating new dhti-elixirs & a **simple EMR chatbot backend**.
118
+ * [openmrs-esm-dhti-template](https://github.com/dermatologist/openmrs-esm-dhti-template): A conch template for OpenMRS & a **simple EMR chatbot frontend**.
118
119
  * [fhir-mcp-server](https://github.com/dermatologist/fhir-mcp-server): A MCP server for hosting FHIR-compliant tools.
119
120
 
120
121
  ## :sparkles: Resources (in Alpha)
@@ -123,10 +124,8 @@ Tools to fine-tune language models for the stack are on our roadmap. We encourag
123
124
 
124
125
  ## :sunglasses: Coming soon
125
126
 
126
- * [dhti-elixir-fhire](https://github.com/dermatologist/dhti-elixir-fhire): An elixir for extracting embeddings from FHIR resources for Q&A on patient records.
127
- * [dhti-elixir-fhirs](https://github.com/dermatologist/dhti-elixir-fhirs): An elixir for text to FHIR search query conversion.
127
+ * [dhti-elixir-fhire](https://github.com/dermatologist/dhti-elixir-fhire): An elixir for FHIR embeddings.
128
128
  * [dhti-elixir-upload](https://github.com/dermatologist/dhti-elixir-upload-file): Upload documents to the vector store for clinical knowledgebase and clinical trial matching.
129
- * [openmrs-esm-qa](https://github.com/dermatologist/openmrs-esm-genai): A sample conch for Q&A on patient records using the dhti-elixir-fhire elixir.
130
129
 
131
130
  ## Try it out
132
131
 
@@ -136,7 +135,7 @@ Tools to fine-tune language models for the stack are on our roadmap. We encourag
136
135
 
137
136
  * `npx dhti-cli compose add -m openmrs -m langserve` to add OpenMRS and Langserve elixirs to your docker-compose.yml at ~/dhti. Other available modules: `ollama, langfuse, cqlFhir, redis, neo4j and mcpFhir`. You can read the newly created docker-compose by: `npx dhti-cli compose read`
138
137
 
139
- * `npx dhti-cli elixir install -g https://github.com/dermatologist/dhti-elixir-template.git -n dhti-elixir-template` to install a sample elixir from github. (Optionally) You may configure the hyperparameters in `~/dhti/elixir/app/bootstrap.py`. You can install multiple elixirs.
138
+ * `npx dhti-cli elixir install -g https://github.com/dermatologist/dhti-elixir-template.git -n dhti-elixir-template` to install a sample elixir from github. *(Optional)* You may configure the LLM and hyperparameters in `~/dhti/elixir/app/bootstrap.py`. You can install multiple elixirs.
140
139
 
141
140
  * `npx dhti-cli docker -n yourdockerhandle/genai-test:1.0 -t elixir` to build a docker image for the elixir.
142
141
 
@@ -146,6 +145,10 @@ Tools to fine-tune language models for the stack are on our roadmap. We encourag
146
145
 
147
146
  * `npx dhti-cli docker -u` to start all the docker images in your docker-compose.yml.
148
147
 
148
+ * *(Optional)* **🔍 Dry-run mode**: Add the `--dry-run` flag to any command to preview what changes will be made without actually executing them. For example:
149
+ - `npx dhti-cli compose add -m langserve --dry-run` to preview modules that would be added
150
+ - `npx dhti-cli elixir install -n test-elixir --dry-run` to see what files would be created/modified
151
+
149
152
  ### :clap: Access the Conch in OpenMRS and test the integration
150
153
 
151
154
  * Go to `http://localhost/openmrs/spa/home`
@@ -161,9 +164,9 @@ You will see the text above the textbox.
161
164
 
162
165
  * `npx dhti-cli docker -d` to stop and delete all the docker containers.
163
166
 
164
- Read more in [notes/steps.md](/notes/steps.md). Complete documentation is in progress.
167
+ Read [![Wiki](https://img.shields.io/badge/DHTI-wiki-demo)](https://github.com/dermatologist/dhti/wiki) for more details.
165
168
 
166
- ### The demo uses a template with mock LLM. [Check out how to add real LLM support using Google Gemini.](/notes/add-llm.md)
169
+ ## 👋 The demo uses mock LLM. 👉 [Check out how to add real LLMs and configure them.](https://github.com/dermatologist/dhti/wiki/Configuration)
167
170
 
168
171
  :hugs: **Thank you for trying out DHTI!**
169
172
 
@@ -6,6 +6,7 @@ export default class Compose extends Command {
6
6
  static description: string;
7
7
  static examples: string[];
8
8
  static flags: {
9
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
10
  file: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
11
  module: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
12
  };
@@ -1,4 +1,5 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
2
3
  import yaml from 'js-yaml';
3
4
  import fs from 'node:fs';
4
5
  import os from 'node:os';
@@ -11,6 +12,10 @@ export default class Compose extends Command {
11
12
  static description = 'Generates a docker-compose.yml file from a list of modules';
12
13
  static examples = ['<%= config.bin %> <%= command.id %>'];
13
14
  static flags = {
15
+ 'dry-run': Flags.boolean({
16
+ default: false,
17
+ description: 'Show what changes would be made without actually making them',
18
+ }),
14
19
  file: Flags.string({
15
20
  char: 'f',
16
21
  default: `${os.homedir()}/dhti/docker-compose.yml`,
@@ -71,6 +76,10 @@ export default class Compose extends Command {
71
76
  if (fs.existsSync(flags.file)) {
72
77
  existingData = yaml.load(fs.readFileSync(flags.file, 'utf8'));
73
78
  }
79
+ else if (flags['dry-run']) {
80
+ console.log(chalk.yellow(`[DRY RUN] Would create directory: ${os.homedir()}/dhti`));
81
+ console.log(chalk.yellow(`[DRY RUN] Would create file: ${flags.file}`));
82
+ }
74
83
  else {
75
84
  Compose.init(); // Create the file if it does not exist
76
85
  }
@@ -81,8 +90,14 @@ export default class Compose extends Command {
81
90
  }
82
91
  // Delete flags.file if args.op is reset
83
92
  if (args.op === 'reset') {
84
- fs.unlinkSync(flags.file);
85
- Compose.init(); // Recreate the file
93
+ if (flags['dry-run']) {
94
+ console.log(chalk.yellow(`[DRY RUN] Would delete file: ${flags.file}`));
95
+ console.log(chalk.yellow(`[DRY RUN] Would recreate file: ${flags.file}`));
96
+ }
97
+ else {
98
+ fs.unlinkSync(flags.file);
99
+ Compose.init(); // Recreate the file
100
+ }
86
101
  }
87
102
  // if existing data is not null and arg is delete, remove the modules from the existing data
88
103
  if (Object.keys(existingData.services).length > 0 && args.op === 'delete') {
@@ -91,9 +106,19 @@ export default class Compose extends Command {
91
106
  for (const module of flags.module ?? []) {
92
107
  modulesToDelete = modulesToDelete.concat(_modules[module]);
93
108
  }
94
- for (const module of modulesToDelete ?? []) {
95
- if (existingData.services[module]) {
96
- delete existingData.services[module];
109
+ if (flags['dry-run']) {
110
+ console.log(chalk.yellow('[DRY RUN] Would delete the following modules:'));
111
+ for (const module of modulesToDelete ?? []) {
112
+ if (existingData.services[module]) {
113
+ console.log(chalk.cyan(` - ${module}`));
114
+ }
115
+ }
116
+ }
117
+ else {
118
+ for (const module of modulesToDelete ?? []) {
119
+ if (existingData.services[module]) {
120
+ delete existingData.services[module];
121
+ }
97
122
  }
98
123
  }
99
124
  }
@@ -104,18 +129,35 @@ export default class Compose extends Command {
104
129
  for (const module of flags.module ?? []) {
105
130
  modulesToAdd = modulesToAdd.concat(_modules[module]);
106
131
  }
107
- for (const module of modulesToAdd ?? []) {
108
- existingData.services[module] = masterData.services[module];
132
+ if (flags['dry-run']) {
133
+ console.log(chalk.yellow('[DRY RUN] Would add the following modules:'));
134
+ for (const module of modulesToAdd ?? []) {
135
+ console.log(chalk.cyan(` - ${module}`));
136
+ }
137
+ }
138
+ else {
139
+ for (const module of modulesToAdd ?? []) {
140
+ existingData.services[module] = masterData.services[module];
141
+ }
109
142
  }
110
143
  }
111
144
  // Add all volumes from master data to existing data by default
112
- existingData.volumes = {};
113
- for (const key of Object.keys(masterData.volumes)) {
114
- existingData.volumes[key] = masterData.volumes[key];
145
+ if (!flags['dry-run']) {
146
+ existingData.volumes = {};
147
+ for (const key of Object.keys(masterData.volumes)) {
148
+ existingData.volumes[key] = masterData.volumes[key];
149
+ }
115
150
  }
116
151
  const toWrite = yaml.dump(existingData).replaceAll('null', '');
117
- console.log('Writing file:', toWrite);
118
- fs.writeFileSync(flags.file, toWrite, 'utf8');
152
+ if (flags['dry-run']) {
153
+ console.log(chalk.yellow(`[DRY RUN] Would write to file: ${flags.file}`));
154
+ console.log(chalk.green('[DRY RUN] File content would be:'));
155
+ console.log(toWrite);
156
+ }
157
+ else {
158
+ console.log('Writing file:', toWrite);
159
+ fs.writeFileSync(flags.file, toWrite, 'utf8');
160
+ }
119
161
  }
120
162
  catch (error) {
121
163
  console.error(error);
@@ -9,6 +9,7 @@ export default class Conch extends Command {
9
9
  branch: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
10
  container: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
11
  dev: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
13
  git: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
14
  image: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
15
  name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,4 +1,5 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
2
3
  import { exec } from 'node:child_process';
3
4
  import fs from 'node:fs';
4
5
  import os from 'node:os';
@@ -18,6 +19,10 @@ export default class Conch extends Command {
18
19
  description: 'Name of the container to copy the conch to while in dev mode',
19
20
  }),
20
21
  dev: Flags.string({ char: 'd', default: 'none', description: 'Dev folder to install' }),
22
+ 'dry-run': Flags.boolean({
23
+ default: false,
24
+ description: 'Show what changes would be made without actually making them',
25
+ }),
21
26
  git: Flags.string({ char: 'g', default: 'none', description: 'Github repository to install' }),
22
27
  image: Flags.string({
23
28
  char: 'i',
@@ -46,9 +51,17 @@ export default class Conch extends Command {
46
51
  // docker cp ../../openmrs-esm-genai/dist/. dhti-frontend-1:/usr/share/nginx/html/openmrs-esm-genai-1.0.0
47
52
  // docker restart dhti-frontend-1
48
53
  if (args.op === 'dev') {
49
- console.log(`cd ${flags.dev} && yarn build && docker cp dist/. ${flags.container}:/usr/share/nginx/html/${flags.name}-${flags.repoVersion}`);
54
+ const buildCommand = `cd ${flags.dev} && yarn build && docker cp dist/. ${flags.container}:/usr/share/nginx/html/${flags.name}-${flags.repoVersion}`;
55
+ const restartCommand = `docker restart ${flags.container}`;
56
+ if (flags['dry-run']) {
57
+ console.log(chalk.yellow('[DRY RUN] Would execute commands:'));
58
+ console.log(chalk.cyan(` ${buildCommand}`));
59
+ console.log(chalk.cyan(` ${restartCommand}`));
60
+ return;
61
+ }
62
+ console.log(buildCommand);
50
63
  try {
51
- exec(`cd ${flags.dev} && yarn build && docker cp dist/. ${flags.container}:/usr/share/nginx/html/${flags.name}-${flags.repoVersion}`, (error, stdout, stderr) => {
64
+ exec(buildCommand, (error, stdout, stderr) => {
52
65
  if (error) {
53
66
  console.error(`exec error: ${error}`);
54
67
  return;
@@ -56,7 +69,7 @@ export default class Conch extends Command {
56
69
  console.log(`stdout: ${stdout}`);
57
70
  console.error(`stderr: ${stderr}`);
58
71
  });
59
- exec(`docker restart ${flags.container}`, (error, stdout, stderr) => {
72
+ exec(restartCommand, (error, stdout, stderr) => {
60
73
  if (error) {
61
74
  console.error(`exec error: ${error}`);
62
75
  return;
@@ -68,15 +81,53 @@ export default class Conch extends Command {
68
81
  catch (error) {
69
82
  console.log('Error copying conch to container', error);
70
83
  }
84
+ return;
71
85
  }
72
86
  // Create a directory to install the elixir
73
87
  if (!fs.existsSync(`${flags.workdir}/conch`)) {
74
- fs.mkdirSync(`${flags.workdir}/conch`);
88
+ if (flags['dry-run']) {
89
+ console.log(chalk.yellow(`[DRY RUN] Would create directory: ${flags.workdir}/conch`));
90
+ }
91
+ else {
92
+ fs.mkdirSync(`${flags.workdir}/conch`);
93
+ }
94
+ }
95
+ if (flags['dry-run']) {
96
+ console.log(chalk.yellow(`[DRY RUN] Would copy resources from ${RESOURCES_DIR}/spa to ${flags.workdir}/conch`));
97
+ }
98
+ else {
99
+ fs.cpSync(path.join(RESOURCES_DIR, 'spa'), `${flags.workdir}/conch`, { recursive: true });
75
100
  }
76
- fs.cpSync(path.join(RESOURCES_DIR, 'spa'), `${flags.workdir}/conch`, { recursive: true });
77
101
  // Rewrite files
78
102
  const rewrite = () => {
79
103
  flags.name = flags.name ?? 'openmrs-esm-genai';
104
+ if (flags['dry-run']) {
105
+ console.log(chalk.yellow('[DRY RUN] Would update configuration files:'));
106
+ console.log(chalk.cyan(` - ${flags.workdir}/conch/def/importmap.json`));
107
+ if (args.op === 'install') {
108
+ console.log(chalk.green(` Add import: ${flags.name.replace('openmrs-', '@openmrs/')} -> ./${flags.name}-${flags.repoVersion}/${flags.name}.js`));
109
+ }
110
+ if (args.op === 'uninstall') {
111
+ console.log(chalk.green(` Remove import: ${flags.name.replace('openmrs-', '@openmrs/')}`));
112
+ }
113
+ console.log(chalk.cyan(` - ${flags.workdir}/conch/def/spa-assemble-config.json`));
114
+ if (args.op === 'install') {
115
+ console.log(chalk.green(` Add module: ${flags.name.replace('openmrs-', '@openmrs/')} = ${flags.repoVersion}`));
116
+ }
117
+ if (args.op === 'uninstall') {
118
+ console.log(chalk.green(` Remove module: ${flags.name.replace('openmrs-', '@openmrs/')}`));
119
+ }
120
+ console.log(chalk.cyan(` - ${flags.workdir}/conch/Dockerfile`));
121
+ console.log(chalk.green(` Update with conch=${flags.name}, version=${flags.repoVersion}, image=${flags.image}`));
122
+ console.log(chalk.cyan(` - ${flags.workdir}/conch/def/routes.registry.json`));
123
+ if (args.op === 'install') {
124
+ console.log(chalk.green(` Add routes for ${flags.name.replace('openmrs-', '@openmrs/')}`));
125
+ }
126
+ if (args.op === 'uninstall') {
127
+ console.log(chalk.green(` Remove routes for ${flags.name.replace('openmrs-', '@openmrs/')}`));
128
+ }
129
+ return;
130
+ }
80
131
  // Read and process importmap.json
81
132
  const importmap = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/def/importmap.json`, 'utf8'));
82
133
  if (args.op === 'install')
@@ -109,14 +160,23 @@ export default class Conch extends Command {
109
160
  fs.writeFileSync(`${flags.workdir}/conch/def/routes.registry.json`, JSON.stringify(registry, null, 2));
110
161
  };
111
162
  if (flags.git !== 'none') {
163
+ const cloneCommand = `git clone ${flags.git} ${flags.workdir}/conch/${flags.name}`;
164
+ const checkoutCommand = `cd ${flags.workdir}/conch/${flags.name} && git checkout ${flags.branch}`;
165
+ if (flags['dry-run']) {
166
+ console.log(chalk.yellow('[DRY RUN] Would execute git commands:'));
167
+ console.log(chalk.cyan(` ${cloneCommand}`));
168
+ console.log(chalk.cyan(` ${checkoutCommand}`));
169
+ rewrite();
170
+ return;
171
+ }
112
172
  // git clone the repository
113
- exec(`git clone ${flags.git} ${flags.workdir}/conch/${flags.name}`, (error, stdout, stderr) => {
173
+ exec(cloneCommand, (error, stdout, stderr) => {
114
174
  if (error) {
115
175
  console.error(`exec error: ${error}`);
116
176
  return;
117
177
  }
118
178
  // Checkout the branch
119
- exec(`cd ${flags.workdir}/conch/${flags.name} && git checkout ${flags.branch}`, (error, stdout, stderr) => {
179
+ exec(checkoutCommand, (error, stdout, stderr) => {
120
180
  if (error) {
121
181
  console.error(`exec error: ${error}`);
122
182
  return;
@@ -131,8 +191,14 @@ export default class Conch extends Command {
131
191
  }
132
192
  // If flags.dev is not none, copy the dev folder to the conch directory
133
193
  if (flags.dev !== 'none' && args.op !== 'dev') {
134
- fs.cpSync(flags.dev, `${flags.workdir}/conch/${flags.name}`, { recursive: true });
135
- rewrite();
194
+ if (flags['dry-run']) {
195
+ console.log(chalk.yellow(`[DRY RUN] Would copy ${flags.dev} to ${flags.workdir}/conch/${flags.name}`));
196
+ rewrite();
197
+ }
198
+ else {
199
+ fs.cpSync(flags.dev, `${flags.workdir}/conch/${flags.name}`, { recursive: true });
200
+ rewrite();
201
+ }
136
202
  }
137
203
  }
138
204
  }
@@ -6,9 +6,10 @@ export default class Docker extends Command {
6
6
  static description: string;
7
7
  static examples: string[];
8
8
  static flags: {
9
+ container: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
10
  down: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
12
  file: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
- container: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
13
  name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
14
  type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
15
  up: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -1,4 +1,5 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
2
3
  import yaml from 'js-yaml';
3
4
  import { exec } from 'node:child_process';
4
5
  import fs from 'node:fs';
@@ -11,17 +12,21 @@ export default class Docker extends Command {
11
12
  static description = 'Build a docker project and update docker-compose file';
12
13
  static examples = ['<%= config.bin %> <%= command.id %>'];
13
14
  static flags = {
15
+ container: Flags.string({
16
+ char: 'c',
17
+ default: 'dhti-langserve-1',
18
+ description: 'Name of the container to copy the bootstrap file to while in dev mode',
19
+ }),
14
20
  down: Flags.boolean({ char: 'd', default: false, description: 'Run docker-compose down after building' }),
21
+ 'dry-run': Flags.boolean({
22
+ default: false,
23
+ description: 'Show what changes would be made without actually making them',
24
+ }),
15
25
  file: Flags.string({
16
26
  char: 'f',
17
27
  default: `${os.homedir()}/dhti/docker-compose.yml`,
18
28
  description: 'Full path to the docker compose file to edit or run.',
19
29
  }),
20
- container: Flags.string({
21
- char: 'c',
22
- default: 'dhti-langserve-1',
23
- description: 'Name of the container to copy the bootstrap file to while in dev mode',
24
- }),
25
30
  name: Flags.string({ char: 'n', description: 'Name of the container to build' }),
26
31
  type: Flags.string({ char: 't', default: 'elixir', description: 'Type of the service (elixir/conch)' }),
27
32
  up: Flags.boolean({ char: 'u', default: false, description: 'Run docker-compose up after building' }),
@@ -29,7 +34,13 @@ export default class Docker extends Command {
29
34
  async run() {
30
35
  const { args, flags } = await this.parse(Docker);
31
36
  if (flags.up) {
32
- exec(`docker compose -f ${flags.file} up -d`, (error, stdout, stderr) => {
37
+ const upCommand = `docker compose -f ${flags.file} up -d`;
38
+ if (flags['dry-run']) {
39
+ console.log(chalk.yellow('[DRY RUN] Would execute:'));
40
+ console.log(chalk.cyan(` ${upCommand}`));
41
+ return;
42
+ }
43
+ exec(upCommand, (error, stdout, stderr) => {
33
44
  if (error) {
34
45
  console.error(`exec error: ${error}`);
35
46
  return;
@@ -40,7 +51,13 @@ export default class Docker extends Command {
40
51
  return;
41
52
  }
42
53
  if (flags.down) {
43
- exec(`docker compose -f ${flags.file} down`, (error, stdout, stderr) => {
54
+ const downCommand = `docker compose -f ${flags.file} down`;
55
+ if (flags['dry-run']) {
56
+ console.log(chalk.yellow('[DRY RUN] Would execute:'));
57
+ console.log(chalk.cyan(` ${downCommand}`));
58
+ return;
59
+ }
60
+ exec(downCommand, (error, stdout, stderr) => {
44
61
  if (error) {
45
62
  console.error(`exec error: ${error}`);
46
63
  return;
@@ -60,8 +77,16 @@ export default class Docker extends Command {
60
77
  console.log('Please provide a valid path to bootstrap.py file');
61
78
  this.exit(1);
62
79
  }
80
+ const copyCommand = `docker cp ${flags.file} ${flags.container}:/app/app/bootstrap.py`;
81
+ const restartCommand = `docker restart ${flags.container}`;
82
+ if (flags['dry-run']) {
83
+ console.log(chalk.yellow('[DRY RUN] Would execute:'));
84
+ console.log(chalk.cyan(` ${copyCommand}`));
85
+ console.log(chalk.cyan(` ${restartCommand}`));
86
+ return;
87
+ }
63
88
  // copy -f to container:/app/app/ and only restart after copy completes
64
- exec(`docker cp ${flags.file} ${flags.container}:/app/app/bootstrap.py`, (error, stdout, stderr) => {
89
+ exec(copyCommand, (error, stdout, stderr) => {
65
90
  if (error) {
66
91
  console.error(`exec error: ${error}`);
67
92
  return;
@@ -69,7 +94,7 @@ export default class Docker extends Command {
69
94
  console.log(`stdout: ${stdout}`);
70
95
  console.error(`stderr: ${stderr}`);
71
96
  // restart the container only after copy completes
72
- exec(`docker restart ${flags.container}`, (restartError, restartStdout, restartStderr) => {
97
+ exec(restartCommand, (restartError, restartStdout, restartStderr) => {
73
98
  if (restartError) {
74
99
  console.error(`exec error: ${restartError}`);
75
100
  return;
@@ -81,8 +106,23 @@ export default class Docker extends Command {
81
106
  return;
82
107
  }
83
108
  // cd to path, docker build tag with name
109
+ const buildCommand = `cd ${args.path}/${flags.type} && docker build -t ${flags.name} . > /dev/null 2>&1`;
110
+ if (flags['dry-run']) {
111
+ console.log(chalk.yellow('[DRY RUN] Would execute:'));
112
+ console.log(chalk.cyan(` ${buildCommand}`));
113
+ console.log(chalk.yellow(`[DRY RUN] Would update docker-compose file: ${flags.file}`));
114
+ if (flags.type === 'elixir') {
115
+ console.log(chalk.green(` Set langserve.image = ${flags.name}`));
116
+ console.log(chalk.green(` Set langserve.pull_policy = if_not_present`));
117
+ }
118
+ else {
119
+ console.log(chalk.green(` Set frontend.image = ${flags.name}`));
120
+ console.log(chalk.green(` Set frontend.pull_policy = if_not_present`));
121
+ }
122
+ return;
123
+ }
84
124
  const spinner = ora('Running docker build ..').start();
85
- exec(`cd ${args.path}/${flags.type} && docker build -t ${flags.name} . > /dev/null 2>&1`, (error, stdout, stderr) => {
125
+ exec(buildCommand, (error, stdout, stderr) => {
86
126
  if (error) {
87
127
  spinner.fail('Docker build failed');
88
128
  console.error(`exec error: ${error}`);
@@ -9,6 +9,7 @@ export default class Elixir extends Command {
9
9
  branch: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
10
  container: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
11
  dev: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
13
  git: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
14
  name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
15
  pypi: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,4 +1,5 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
2
3
  import { exec } from 'node:child_process';
3
4
  import fs from 'node:fs';
4
5
  import os from 'node:os';
@@ -18,6 +19,10 @@ export default class Elixir extends Command {
18
19
  description: 'Name of the container to copy the elixir to while in dev mode',
19
20
  }),
20
21
  dev: Flags.string({ char: 'd', default: 'none', description: 'Dev folder to install' }),
22
+ 'dry-run': Flags.boolean({
23
+ default: false,
24
+ description: 'Show what changes would be made without actually making them',
25
+ }),
21
26
  git: Flags.string({ char: 'g', default: 'none', description: 'Github repository to install' }),
22
27
  name: Flags.string({ char: 'n', description: 'Name of the elixir' }),
23
28
  pypi: Flags.string({
@@ -47,9 +52,17 @@ export default class Elixir extends Command {
47
52
  // if arg is dev then copy to docker as below
48
53
  // docker restart dhti-langserve-1
49
54
  if (args.op === 'dev') {
50
- console.log(`cd ${flags.dev} && docker cp src/${expoName}/. ${flags.container}:/app/.venv/lib/python3.12/site-packages/${expoName}`);
55
+ const devCommand = `cd ${flags.dev} && docker cp src/${expoName}/. ${flags.container}:/app/.venv/lib/python3.12/site-packages/${expoName}`;
56
+ const restartCommand = `docker restart ${flags.container}`;
57
+ if (flags['dry-run']) {
58
+ console.log(chalk.yellow('[DRY RUN] Would execute commands:'));
59
+ console.log(chalk.cyan(` ${devCommand}`));
60
+ console.log(chalk.cyan(` ${restartCommand}`));
61
+ return;
62
+ }
63
+ console.log(devCommand);
51
64
  try {
52
- exec(`cd ${flags.dev} && docker cp src/${expoName}/. ${flags.container}:/app/.venv/lib/python3.12/site-packages/${expoName}`, (error, stdout, stderr) => {
65
+ exec(devCommand, (error, stdout, stderr) => {
53
66
  if (error) {
54
67
  console.error(`exec error: ${error}`);
55
68
  return;
@@ -57,7 +70,7 @@ export default class Elixir extends Command {
57
70
  console.log(`stdout: ${stdout}`);
58
71
  console.error(`stderr: ${stderr}`);
59
72
  });
60
- exec(`docker restart ${flags.container}`, (error, stdout, stderr) => {
73
+ exec(restartCommand, (error, stdout, stderr) => {
61
74
  if (error) {
62
75
  console.error(`exec error: ${error}`);
63
76
  return;
@@ -69,23 +82,45 @@ export default class Elixir extends Command {
69
82
  catch (error) {
70
83
  console.log('Error copying conch to container', error);
71
84
  }
85
+ return;
72
86
  }
73
87
  // Create a directory to install the elixir
74
88
  if (!fs.existsSync(`${flags.workdir}/elixir`)) {
75
- fs.mkdirSync(`${flags.workdir}/elixir`);
89
+ if (flags['dry-run']) {
90
+ console.log(chalk.yellow(`[DRY RUN] Would create directory: ${flags.workdir}/elixir`));
91
+ }
92
+ else {
93
+ fs.mkdirSync(`${flags.workdir}/elixir`);
94
+ }
95
+ }
96
+ if (flags['dry-run']) {
97
+ console.log(chalk.yellow(`[DRY RUN] Would copy resources from ${RESOURCES_DIR}/genai to ${flags.workdir}/elixir`));
98
+ }
99
+ else {
100
+ fs.cpSync(path.join(RESOURCES_DIR, 'genai'), `${flags.workdir}/elixir`, { recursive: true });
76
101
  }
77
- fs.cpSync(path.join(RESOURCES_DIR, 'genai'), `${flags.workdir}/elixir`, { recursive: true });
78
102
  // if whl is not none, copy the whl file to thee whl directory
79
103
  if (flags.whl !== 'none') {
80
104
  if (!fs.existsSync(`${flags.workdir}/elixir/whl/`)) {
81
- fs.mkdirSync(`${flags.workdir}/whl/`);
105
+ if (flags['dry-run']) {
106
+ console.log(chalk.yellow(`[DRY RUN] Would create directory: ${flags.workdir}/whl/`));
107
+ }
108
+ else {
109
+ fs.mkdirSync(`${flags.workdir}/whl/`);
110
+ }
111
+ }
112
+ if (flags['dry-run']) {
113
+ console.log(chalk.yellow(`[DRY RUN] Would copy ${flags.whl} to ${flags.workdir}/elixir/whl/${path.basename(flags.whl)}`));
114
+ console.log(chalk.cyan('[DRY RUN] Installing elixir from whl file. Please modify boostrap.py file if needed'));
115
+ }
116
+ else {
117
+ fs.cpSync(flags.whl, `${flags.workdir}/elixir/whl/${path.basename(flags.whl)}`);
118
+ console.log('Installing elixir from whl file. Please modify boostrap.py file if needed');
82
119
  }
83
- fs.cpSync(flags.whl, `${flags.workdir}/elixir/whl/${path.basename(flags.whl)}`);
84
- console.log('Installing elixir from whl file. Please modify boostrap.py file if needed');
85
120
  }
86
121
  // Install the elixir from git adding to the pyproject.toml file
87
- let pyproject = fs.readFileSync(`${flags.workdir}/elixir/pyproject.toml`, 'utf8');
88
- const originalServer = fs.readFileSync(`${flags.workdir}/elixir/app/server.py`, 'utf8');
122
+ let pyproject = flags['dry-run'] ? '' : fs.readFileSync(`${flags.workdir}/elixir/pyproject.toml`, 'utf8');
123
+ const originalServer = flags['dry-run'] ? '' : fs.readFileSync(`${flags.workdir}/elixir/app/server.py`, 'utf8');
89
124
  let lineToAdd = '';
90
125
  if (flags.whl !== 'none') {
91
126
  lineToAdd = `${flags.name} = { file = "whl/${path.basename(flags.whl)}" }`;
@@ -96,8 +131,10 @@ export default class Elixir extends Command {
96
131
  if (flags.pypi !== 'none') {
97
132
  lineToAdd = flags.pypi;
98
133
  }
99
- pyproject = pyproject.replace('dependencies = [', `dependencies = [\n"${flags.name}",`);
100
- pyproject = pyproject.replace('[tool.uv.sources]', `[tool.uv.sources]\n${lineToAdd}\n`);
134
+ if (!flags['dry-run']) {
135
+ pyproject = pyproject.replace('dependencies = [', `dependencies = [\n"${flags.name}",`);
136
+ pyproject = pyproject.replace('[tool.uv.sources]', `[tool.uv.sources]\n${lineToAdd}\n`);
137
+ }
101
138
  const newPyproject = pyproject;
102
139
  // Add the elixir import and bootstrap to the server.py file
103
140
  let CliImport = `from ${expoName}.bootstrap import bootstrap as ${expoName}_bootstrap\n`;
@@ -108,27 +145,49 @@ ${expoName}_chain = ${expoName}_chain_class().get_chain_as_langchain_tool()
108
145
  ${expoName}_mcp_tool = ${expoName}_chain_class().get_chain_as_mcp_tool
109
146
  mcp_server.add_tool(${expoName}_mcp_tool) # type: ignore
110
147
  `;
111
- const newCliImport = fs
112
- .readFileSync(`${flags.workdir}/elixir/app/server.py`, 'utf8')
113
- .replace('# DHTI_CLI_IMPORT', `#DHTI_CLI_IMPORT\n${CliImport}`);
148
+ let newCliImport = '';
149
+ if (!flags['dry-run']) {
150
+ newCliImport = fs
151
+ .readFileSync(`${flags.workdir}/elixir/app/server.py`, 'utf8')
152
+ .replace('# DHTI_CLI_IMPORT', `#DHTI_CLI_IMPORT\n${CliImport}`);
153
+ }
114
154
  const langfuseRoute = `add_routes(app, ${expoName}_chain.with_config(config), path="/langserve/${expoName}")`;
115
- const newLangfuseRoute = newCliImport.replace('# DHTI_LANGFUSE_ROUTE', `#DHTI_LANGFUSE_ROUTE\n ${langfuseRoute}`);
155
+ const newLangfuseRoute = flags['dry-run'] ? '' : newCliImport.replace('# DHTI_LANGFUSE_ROUTE', `#DHTI_LANGFUSE_ROUTE\n ${langfuseRoute}`);
116
156
  const normalRoute = `add_routes(app, ${expoName}_chain, path="/langserve/${expoName}")`;
117
- const newNormalRoute = newLangfuseRoute.replace('# DHTI_NORMAL_ROUTE', `#DHTI_NORMAL_ROUTE\n ${normalRoute}`);
157
+ const newNormalRoute = flags['dry-run'] ? '' : newLangfuseRoute.replace('# DHTI_NORMAL_ROUTE', `#DHTI_NORMAL_ROUTE\n ${normalRoute}`);
118
158
  const commonRoutes = `\nadd_invokes(app, path="/langserve/${expoName}")\nadd_services(app, path="/langserve/${expoName}")`;
119
- const finalRoute = newNormalRoute.replace('# DHTI_COMMON_ROUTE', `#DHTI_COMMON_ROUTES${commonRoutes}`);
159
+ const finalRoute = flags['dry-run'] ? '' : newNormalRoute.replace('# DHTI_COMMON_ROUTE', `#DHTI_COMMON_ROUTES${commonRoutes}`);
120
160
  // if args.op === install, add the line to the pyproject.toml file
121
161
  if (args.op === 'install') {
122
- fs.writeFileSync(`${flags.workdir}/elixir/pyproject.toml`, newPyproject);
123
- fs.writeFileSync(`${flags.workdir}/elixir/app/server.py`, finalRoute);
162
+ if (flags['dry-run']) {
163
+ console.log(chalk.yellow('[DRY RUN] Would update files:'));
164
+ console.log(chalk.cyan(` - ${flags.workdir}/elixir/pyproject.toml`));
165
+ console.log(chalk.green(` Add dependency: "${flags.name}"`));
166
+ console.log(chalk.green(` Add source: ${lineToAdd}`));
167
+ console.log(chalk.cyan(` - ${flags.workdir}/elixir/app/server.py`));
168
+ console.log(chalk.green(` Add import and routes for ${expoName}`));
169
+ }
170
+ else {
171
+ fs.writeFileSync(`${flags.workdir}/elixir/pyproject.toml`, newPyproject);
172
+ fs.writeFileSync(`${flags.workdir}/elixir/app/server.py`, finalRoute);
173
+ }
124
174
  }
125
175
  if (args.op === 'uninstall') {
126
- // if args.op === uninstall, remove the line from the pyproject.toml file
127
- fs.writeFileSync(`${flags.workdir}/elixir/pyproject.toml`, pyproject.replace(lineToAdd, ''));
128
- let newServer = originalServer.replace(CliImport, '');
129
- newServer = newServer.replace(langfuseRoute, '');
130
- newServer = newServer.replace(normalRoute, '');
131
- fs.writeFileSync(`${flags.workdir}/elixir/app/server.py`, newServer);
176
+ if (flags['dry-run']) {
177
+ console.log(chalk.yellow('[DRY RUN] Would update files:'));
178
+ console.log(chalk.cyan(` - ${flags.workdir}/elixir/pyproject.toml`));
179
+ console.log(chalk.green(` Remove source: ${lineToAdd}`));
180
+ console.log(chalk.cyan(` - ${flags.workdir}/elixir/app/server.py`));
181
+ console.log(chalk.green(` Remove import and routes for ${expoName}`));
182
+ }
183
+ else {
184
+ // if args.op === uninstall, remove the line from the pyproject.toml file
185
+ fs.writeFileSync(`${flags.workdir}/elixir/pyproject.toml`, pyproject.replace(lineToAdd, ''));
186
+ let newServer = originalServer.replace(CliImport, '');
187
+ newServer = newServer.replace(langfuseRoute, '');
188
+ newServer = newServer.replace(normalRoute, '');
189
+ fs.writeFileSync(`${flags.workdir}/elixir/app/server.py`, newServer);
190
+ }
132
191
  }
133
192
  }
134
193
  }
@@ -5,5 +5,8 @@ export default class Mimic extends Command {
5
5
  };
6
6
  static description: string;
7
7
  static examples: string[];
8
+ static flags: {
9
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ };
8
11
  run(): Promise<void>;
9
12
  }
@@ -1,10 +1,17 @@
1
- import { Args, Command } from '@oclif/core';
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
2
3
  export default class Mimic extends Command {
3
4
  static args = {
4
5
  server: Args.string({ default: 'http://localhost/fhir/$import', description: 'Server URL to submit' }), // object with input, instruction (rationale in distillation), output
5
6
  };
6
7
  static description = 'Submit a FHIR request to a server';
7
8
  static examples = ['<%= config.bin %> <%= command.id %>'];
9
+ static flags = {
10
+ 'dry-run': Flags.boolean({
11
+ default: false,
12
+ description: 'Show what changes would be made without actually making them',
13
+ }),
14
+ };
8
15
  async run() {
9
16
  const { args, flags } = await this.parse(Mimic);
10
17
  const mimic_request = `{
@@ -140,6 +147,15 @@ export default class Mimic extends Command {
140
147
  } ]
141
148
 
142
149
  }`;
150
+ if (flags['dry-run']) {
151
+ console.log(chalk.yellow(`[DRY RUN] Would send POST request to: ${args.server}`));
152
+ console.log(chalk.cyan('[DRY RUN] Request headers:'));
153
+ console.log(chalk.green(' Content-Type: application/fhir+json'));
154
+ console.log(chalk.green(' Prefer: respond-async'));
155
+ console.log(chalk.cyan('[DRY RUN] Request body:'));
156
+ console.log(mimic_request);
157
+ return;
158
+ }
143
159
  // send a POST request to the server with the mimic_request body
144
160
  const response = await fetch(args.server, {
145
161
  body: mimic_request,
@@ -151,7 +167,6 @@ export default class Mimic extends Command {
151
167
  });
152
168
  if (!response.ok) {
153
169
  console.error(`Error: ${response.status} ${response.statusText}`);
154
- return;
155
170
  }
156
171
  }
157
172
  }
@@ -8,6 +8,7 @@ export default class Synthetic extends Command {
8
8
  static description: string;
9
9
  static examples: string[];
10
10
  static flags: {
11
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
12
  inputField: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
13
  maxCycles: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
13
14
  maxRecords: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,4 +1,5 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
2
3
  import fs from 'node:fs';
3
4
  import bootstrap from '../utils/bootstrap.js';
4
5
  import { ChainService } from '../utils/chain.js';
@@ -13,6 +14,10 @@ export default class Synthetic extends Command {
13
14
  '<%= config.bin %> <%= command.id %>',
14
15
  ];
15
16
  static flags = {
17
+ 'dry-run': Flags.boolean({
18
+ default: false,
19
+ description: 'Show what changes would be made without actually making them',
20
+ }),
16
21
  inputField: Flags.string({ char: 'i', default: 'input', description: 'Input field to use', options: ['input', 'instruction', 'output'] }),
17
22
  maxCycles: Flags.integer({ char: 'm', default: 0, description: 'Maximum number of cycles to run' }),
18
23
  maxRecords: Flags.integer({ char: 'r', default: 10, description: 'Maximum number of records to generate' }),
@@ -24,13 +29,35 @@ export default class Synthetic extends Command {
24
29
  // read prompt file if provided
25
30
  if (args.prompt)
26
31
  prompt = fs.readFileSync(args.prompt ?? '', 'utf8');
27
- const container = await bootstrap();
28
- const chain = new ChainService(container);
29
32
  // if no output file, exit with error
30
33
  if (!args.output) {
31
34
  console.log("Please provide an output file");
32
35
  this.exit(1);
33
36
  }
37
+ if (flags['dry-run']) {
38
+ console.log(chalk.yellow('[DRY RUN] Synthetic data generation simulation'));
39
+ console.log(chalk.cyan(` Output file: ${args.output}`));
40
+ console.log(chalk.cyan(` Max records: ${flags.maxRecords}`));
41
+ if (flags.maxCycles) {
42
+ console.log(chalk.cyan(` Max cycles: ${flags.maxCycles}`));
43
+ console.log(chalk.cyan(` Output field: ${flags.outputField}`));
44
+ console.log(chalk.green('[DRY RUN] Would generate synthetic data in batches using LLM'));
45
+ console.log(chalk.green(`[DRY RUN] Would write ${flags.maxRecords} records to ${args.output}`));
46
+ }
47
+ else {
48
+ console.log(chalk.cyan(` Input file: ${args.input}`));
49
+ console.log(chalk.cyan(` Input field: ${flags.inputField}`));
50
+ console.log(chalk.cyan(` Output field: ${flags.outputField}`));
51
+ if (args.prompt) {
52
+ console.log(chalk.cyan(` Prompt file: ${args.prompt}`));
53
+ }
54
+ console.log(chalk.green('[DRY RUN] Would process input file records using LLM'));
55
+ console.log(chalk.green(`[DRY RUN] Would write processed records to ${args.output}`));
56
+ }
57
+ return;
58
+ }
59
+ const container = await bootstrap();
60
+ const chain = new ChainService(container);
34
61
  if (flags.maxCycles) { // No input file, can process in batches
35
62
  const input = {
36
63
  input: prompt,
@@ -13,6 +13,12 @@
13
13
  "<%= config.bin %> <%= command.id %>"
14
14
  ],
15
15
  "flags": {
16
+ "dry-run": {
17
+ "description": "Show what changes would be made without actually making them",
18
+ "name": "dry-run",
19
+ "allowNo": false,
20
+ "type": "boolean"
21
+ },
16
22
  "file": {
17
23
  "char": "f",
18
24
  "description": "Full path to the docker compose file to read from. Creates if it does not exist",
@@ -86,6 +92,12 @@
86
92
  "multiple": false,
87
93
  "type": "option"
88
94
  },
95
+ "dry-run": {
96
+ "description": "Show what changes would be made without actually making them",
97
+ "name": "dry-run",
98
+ "allowNo": false,
99
+ "type": "boolean"
100
+ },
89
101
  "git": {
90
102
  "char": "g",
91
103
  "description": "Github repository to install",
@@ -160,6 +172,15 @@
160
172
  "<%= config.bin %> <%= command.id %>"
161
173
  ],
162
174
  "flags": {
175
+ "container": {
176
+ "char": "c",
177
+ "description": "Name of the container to copy the bootstrap file to while in dev mode",
178
+ "name": "container",
179
+ "default": "dhti-langserve-1",
180
+ "hasDynamicHelp": false,
181
+ "multiple": false,
182
+ "type": "option"
183
+ },
163
184
  "down": {
164
185
  "char": "d",
165
186
  "description": "Run docker-compose down after building",
@@ -167,6 +188,12 @@
167
188
  "allowNo": false,
168
189
  "type": "boolean"
169
190
  },
191
+ "dry-run": {
192
+ "description": "Show what changes would be made without actually making them",
193
+ "name": "dry-run",
194
+ "allowNo": false,
195
+ "type": "boolean"
196
+ },
170
197
  "file": {
171
198
  "char": "f",
172
199
  "description": "Full path to the docker compose file to edit or run.",
@@ -176,15 +203,6 @@
176
203
  "multiple": false,
177
204
  "type": "option"
178
205
  },
179
- "container": {
180
- "char": "c",
181
- "description": "Name of the container to copy the bootstrap file to while in dev mode",
182
- "name": "container",
183
- "default": "dhti-langserve-1",
184
- "hasDynamicHelp": false,
185
- "multiple": false,
186
- "type": "option"
187
- },
188
206
  "name": {
189
207
  "char": "n",
190
208
  "description": "Name of the container to build",
@@ -265,6 +283,12 @@
265
283
  "multiple": false,
266
284
  "type": "option"
267
285
  },
286
+ "dry-run": {
287
+ "description": "Show what changes would be made without actually making them",
288
+ "name": "dry-run",
289
+ "allowNo": false,
290
+ "type": "boolean"
291
+ },
268
292
  "git": {
269
293
  "char": "g",
270
294
  "description": "Github repository to install",
@@ -347,7 +371,14 @@
347
371
  "examples": [
348
372
  "<%= config.bin %> <%= command.id %>"
349
373
  ],
350
- "flags": {},
374
+ "flags": {
375
+ "dry-run": {
376
+ "description": "Show what changes would be made without actually making them",
377
+ "name": "dry-run",
378
+ "allowNo": false,
379
+ "type": "boolean"
380
+ }
381
+ },
351
382
  "hasDynamicHelp": false,
352
383
  "hiddenAliases": [],
353
384
  "id": "mimic",
@@ -386,6 +417,12 @@
386
417
  "<%= config.bin %> <%= command.id %>"
387
418
  ],
388
419
  "flags": {
420
+ "dry-run": {
421
+ "description": "Show what changes would be made without actually making them",
422
+ "name": "dry-run",
423
+ "allowNo": false,
424
+ "type": "boolean"
425
+ },
389
426
  "inputField": {
390
427
  "char": "i",
391
428
  "description": "Input field to use",
@@ -449,5 +486,5 @@
449
486
  ]
450
487
  }
451
488
  },
452
- "version": "0.3.3"
489
+ "version": "0.4.0"
453
490
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dhti-cli",
3
3
  "description": "DHTI CLI",
4
- "version": "0.3.3",
4
+ "version": "0.4.0",
5
5
  "author": "Bell Eapen",
6
6
  "bin": {
7
7
  "dhti-cli": "bin/run.js"
@@ -13,6 +13,7 @@
13
13
  "@oclif/core": "^4",
14
14
  "@oclif/plugin-help": "^6",
15
15
  "@oclif/plugin-plugins": "^5",
16
+ "chalk": "^4.1.2",
16
17
  "child_process": "^1.0.2",
17
18
  "js-yaml": "^4.1.0",
18
19
  "medpromptjs": ">=0.4.3",