dhti-cli 0.7.1 → 1.0.1

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
@@ -1,58 +1,113 @@
1
1
 
2
2
  <p align="center">
3
- <img src="https://github.com/dermatologist/dhti/blob/develop/notes/dhti-logo.jpg" />
3
+ <img src="https://github.com/dermatologist/dhti/blob/develop/notes/dhti-logo.jpg" alt="DHTI logo" />
4
4
  </p>
5
5
 
6
6
  [![npm version](https://badge.fury.io/js/dhti-cli.svg)](https://www.npmjs.com/package/dhti-cli)
7
- [![npm](https://img.shields.io/npm/dt/dhti-cli)](https://www.npmjs.com/package/dhti-cli)
7
+ [![npm downloads](https://img.shields.io/npm/dt/dhti-cli)](https://www.npmjs.com/package/dhti-cli)
8
8
  [![Build](https://github.com/dermatologist/dhti/workflows/CI/badge.svg)](https://nuchange.ca)
9
9
  [![Known Vulnerabilities](https://snyk.io/test/github/dermatologist/dhti/badge.svg)](https://www.npmjs.com/package/dhti-cli)
10
10
  [![Documentation](https://badgen.net/badge/icon/documentation?icon=libraries&label)](https://dermatologist.github.io/dhti/)
11
11
  [![Wiki](https://img.shields.io/badge/DHTI-wiki-demo)](https://github.com/dermatologist/dhti/wiki)
12
12
 
13
+ <!-- Dhanvantari reference -->
14
+ > 🚀 Dhanvantari rose out of the water with his four hands, holding a pot full of elixirs.
13
15
 
14
- - 🚀 *Dhanvantari rose out of the water with his four hands, holding a pot full of elixirs!*
16
+ # DHTI
17
+ *DHTI enables rapid prototyping, sharing, and testing of GenAI healthcare applications inside an EHR, helping experiments move smoothly into practice. DHTI also includes [skills](/.github/skills/) that generate GenAI components from problem‑oriented [prompts](/prompts/e2e-sample.md).*
15
18
 
16
- ### TL; DR: 🏥 DHTI enables rapid prototyping, sharing, and testing of GenAI healthcare applications within an EHR, facilitating the seamless transition of your experiments to practice!
17
- 👉 [Try it out today!](#try-it-out) and give us a star ⭐️ if you like it!
19
+ ### Why?
18
20
 
19
- ### About
21
+ Imagine you need to instantly determine whether a patient qualifies for a clinical trial. Your GenAI app pulls in the trial’s eligibility criteria, matches it against the patient’s EHR data, taps a vector store for RAG, relies on a self‑hosted LLM to keep everything private, and uses smart tools to fetch and analyze clinical details. The final output appears right inside the EHR—clean, clear, and clinician‑friendly. 💥 **And that’s just one example of the countless real‑world workflows DHTI makes possible.**
20
22
 
21
- Generative AI features are built as [LangServe Apps](https://python.langchain.com/docs/langserve/) (elixirs) that can be installed into a LangServe instance and exposed as APIs. [OpenMRS O3 esm](https://o3-docs.openmrs.org/) / [**CDS hook** clients](https://github.com/dermatologist/cds-hooks-sandbox/tree/dhti-1) (conchs) provide the interface to communicate with these APIs. All backend data exchange is done through the **FHIR API** (a base class provides all these features). dhti-cli simplifies this process by providing a CLI that includes managing a Docker Compose with all additional components, such as [Ollama](https://ollama.com/) for **local LLM hosting**. LLM and hyperparameters are **injected at runtime** and can be easily swapped. In essence, dhti decouples GenAI modules from the rest of the system. 👉 [Try it out today!](#try-it-out)
23
+ #### How? (Technical):
24
+ Generative AI features are built as [LangServe Apps](https://python.langchain.com/docs/langserve/) (elixirs). All backend data exchange is done through the **FHIR API** (a [base class](https://github.com/dermatologist/dhti-elixir-base) provides all these features) and displayed using CDS-Hooks. dhti-cli simplifies this process by providing a CLI that includes managing a Docker Compose with all additional components, such as [Ollama](https://ollama.com/) for **local LLM hosting**. LLM and hyperparameters are **injected at runtime** and can be easily swapped. In essence, dhti decouples GenAI modules from the rest of the system.
22
25
 
23
- ### Want to know more?
26
+ 🚀 You can test the elixir using a real EMR system, [OpenMRS](https://openmrs.org/), that communicates with the elixir using **CDS-Hooks** or use any other CDS-Hooks compatible EMR system. You can also use the [CDS-Hooks sandbox for testing](https://github.com/dermatologist/cds-hooks-sandbox/tree/dhti-1) without an EMR.
24
27
 
25
- *Watch this demo video:*
26
- [![WATCH DHTI DEMO](https://img.youtube.com/vi/5jFFe3wqKM0/0.jpg)](https://www.youtube.com/watch?v=5jFFe3wqKM0)
28
+ #### How (non‑technical / clinical)
29
+ DHTI includes ready‑to‑use [skills](/.github/skills/) that can prompt agentic platforms (e.g., [AntiGravity](https://antigravity.google/), VSCode, or Claude) to generate the GenAI backends and UI components (elixirs and conches) you need. Test these components with synthetic data in OpenMRS or the CDS‑Hooks sandbox, then hand them off to production teams. Because DHTI follows open standards, that handoff (the “valley of death”) becomes smoother and more predictable. Try the [prompts](/.github/skills/start-dhti/examples/e2e-sample.md) in your preferred agentic platform after cloning this repo.
27
30
 
28
- Gen AI can transform medicine. But it needs a framework for collaborative research and practice. DHTI is a reference architecture and an implementation for such a framework that integrates an EMR ([OpenMRS](https://openmrs.org/)), :link: Gen AI application server ([LangServe](https://python.langchain.com/v0.2/docs/langserve/)), self-hosted LLMs for privacy ([Ollama](https://ollama.com/)), tools on [MCP server](https://github.com/dermatologist/fhir-mcp-server), vector store for RAG ([redis](https://redis.io/)), monitoring ([LangFuse](https://langfuse.com/)), 🔥 FHIR repository with [CQL](https://nuchange.ca/2025/06/v-llm-in-the-loop-cql-execution-with-unstructured-data-and-fhir-terminology-support.html) support ([HAPI](https://cloud.alphora.com/sandbox/r4/cqm/)) and graph utilities ([Neo4j](https://neo4j.com/)) in one docker-compose! DHTI is inspired by [Bahmni](https://www.bahmni.org/) and **aims to facilitate GenAI adoption and research in areas with low resources.** The MCP server hosts pluggable, agent-invokable tools (FHIR query, summarization, terminology lookup, custom analytics, etc.) that you can extend without modifying core services.
31
+ ## Try it out
32
+ [[Cheatsheet](/notes/cheatsheet.md) | [Download PDF Cheatsheet](https://nuchange.ca/wp-content/uploads/2026/01/dhti_cheatsheet.pdf)]
33
+
34
+ - Requirements: [Node.js](https://nodejs.org/) and [Docker](https://www.docker.com/). Optionally install [Python](https://www.python.org/) to develop or rapidly prototype elixirs.
35
+ - The sample elixir will use Google, OpenAI, or OpenRouter models if API keys are set in your environment; otherwise it falls back to a mock LLM. You can also use [Ollama](https://ollama.com/) for local model hosting. See [setup instructions](/notes/setup-ollama.md).
36
+
37
+ Quick start (try the demo script):
38
+ ```bash
39
+ git clone https://github.com/dermatologist/dhti.git
40
+ cd dhti
41
+ ./demo.sh # Linux / macOS (Windows: use WSL)
42
+ ```
43
+
44
+ Basic demo workflow:
45
+ ```bash
46
+ npx dhti-cli help # list commands
47
+ npx dhti-cli compose add -m langserve # add LangServe to ~/dhti/docker-compose.yml
48
+ npx dhti-cli compose read # view generated compose
49
+ npx dhti-cli elixir install -g https://github.com/dermatologist/dhti-elixir.git -n dhti-elixir-schat -s packages/simple_chat
50
+ npx dhti-cli docker -n yourdockerhandle/genai-test:1.0 -t elixir
51
+ npx dhti-cli docker -u # start services from compose
52
+ ```
29
53
 
30
- The essence of DHTI is *modularity* with an emphasis on *configuration!* It is non-opinionated on LLMs, hyperparameters and pretty much everything. DHTI supports installable Gen AI routines through [LangServe Apps](https://python.langchain.com/docs/langserve/) (which we call :curry: **elixir**) and installable UI elements through [OpenMRS O3](https://o3-docs.openmrs.org/) React container (which we call :shell: **conch**). 🔥 FHIR is used for backend and [CDS-Hooks](https://cds-hooks.org/) for frontend communication, decoupling conches from OpenMRS, making them potentially usable with any health information system. We have a [fork of the cds-hook sandbox](https://github.com/dermatologist/cds-hooks-sandbox/tree/dhti-1) for testing that uses the [order-select](https://cds-hooks.org/hooks/order-select/) hook, utilizing the contentString from the [FHIR CommunicationRequest](https://build.fhir.org/communicationrequest.html) within the [cds-hook context](https://cds-hooks.org/examples/) for user inputs (recommended).
54
+ Notes:
55
+ - Configure models and hyperparameters in `~/dhti/elixir/app/bootstrap.py` or install from a local directory using `-l`.
56
+ - Stop and remove containers with `npx dhti-cli docker -d`.
31
57
 
58
+ ✌️ Decide where to test the new elixir: OpenMRS a full EHR system, or CDS-Hooks sandbox for a lightweight testing without an EHR.
59
+
60
+ 💥 Test elixir in a CDS-Hooks sandbox.
61
+
62
+ * `npx dhti-cli conch start -n dhti-elixir-schat` and navigate to the Application URL displayed in the console. (Uses hapi.fhir.org).
63
+ * In the **Rx View** tab, type in the contentString textbox and wait for the elixir to respond.
32
64
 
33
65
  <p align="center">
34
- <img src="https://github.com/dermatologist/openmrs-esm-dhti-template/blob/develop/notes/conch.jpg" />
66
+ <img src="https://github.com/dermatologist/dhti/blob/develop/notes/cds-hook-sandbox.jpg" />
35
67
  </p>
36
68
 
37
- *[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)
69
+ * We recommend using the [order-select](https://cds-hooks.org/hooks/order-select/) hook, utilizing the contentString from the [FHIR CommunicationRequest](https://build.fhir.org/communicationrequest.html) within the [cds-hook context](https://cds-hooks.org/examples/) for user inputs but you can use patient-view if no user input is needed.
70
+
71
+ 💥 Test elixir in OpenMRS.
72
+
73
+ * `npx dhti-cli conch install -g dermatologist/openmrs-esm-dhti -s packages/esm-chatbot-agent -n esm-chatbot-agent` to install a sample chatbot conch from github.
74
+ - *(Optional)* Alternatively, use `-l <local-directory>` to install from a local directory.
75
+
76
+ * `npx dhti-cli conch start -n esm-chatbot-agent -s packages/esm-chatbot-agent` to start the conch with OpenMRS.
77
+
78
+ * Go to `http://localhost:8080/openmrs/spa/home`
79
+ * Login with the following credentials:
80
+ - Username: admin
81
+ - Password: Admin123
82
+
83
+ You will see the new **patient context aware chatbot** in the patient summary page. This is just an example. You can build your own! Check the implementation in the [elixir repo](https://github.com/dermatologist/dhti-elixir) and [conch repo](https://github.com/dermatologist/openmrs-esm-dhti).
38
84
 
39
85
  <p align="center">
40
- <img src="https://github.com/dermatologist/dhti/blob/develop/notes/cds-hook-sandbox.jpg" />
86
+ <img src="https://github.com/dermatologist/openmrs-esm-dhti-template/blob/develop/notes/conch.jpg" />
41
87
  </p>
42
88
 
43
- *[CDS-Hooks sandbox](https://github.com/dermatologist/cds-hooks-sandbox) for testing conchs without OpenMRS.* 👉 [Try it out today!](#try-it-out)
89
+ * `npx dhti-cli docker -d` to stop and delete all the docker containers.
44
90
 
45
- 🚀 dhti-cli is a CLI tool for quick prototyping and testing of elixirs and conches. You can create a new docker-compose with required modules, start/stop services, install Elixirs and conch, create Docker images for them, and more. 🚀 This helps to test new ideas and share them with others quickly. 🚀 Once tested, you can transition them to the production team for deployment. Adoption of standards makes this transition easier! 👉 [Try it out today!](#try-it-out)
91
+ ## Wiki & Documentation
92
+ * [![Wiki](https://img.shields.io/badge/DHTI-wiki-demo)](https://github.com/dermatologist/dhti/wiki)
93
+ * [Documentation](https://dermatologist.github.io/dhti/)
94
+ * [CLI Reference](/notes/README.md)
46
95
 
47
- ⭐️ **Pitched at [Falling Walls Lab Illinois](https://falling-walls.com/falling-walls-lab-illinois) and released on 09/12/2025.**
96
+ ## User contributions & examples
97
+ * [Elixirs](https://github.com/dermatologist/dhti-elixir)
98
+ * [OpenMRS Conches / UI](https://github.com/dermatologist/openmrs-esm-dhti)
99
+ * [CDS Hooks Sandbox for testing](https://github.com/dermatologist/cds-hooks-sandbox)
100
+
101
+ ## Presentations
102
+ ⭐️ **Pitched at [Falling Walls Lab Illinois](https://falling-walls.com/falling-walls-lab-illinois) and released on 2025-09-12.**
48
103
 
49
104
  ## What problems do DHTI solve?
50
105
 
51
106
  | Why | How |
52
107
  | --- | --- |
53
- | I know LangChain, but I don’t know how to build a chain/agent based on data in our EHR. | [This template](https://github.com/dermatologist/dhti-elixir-template) adopts FHIR and cds-hooks as standards for data retrieval and display. The [base class](https://github.com/dermatologist/dhti-elixir-base) provides reusable artifacts |
108
+ | I know LangChain, but I don’t know how to build a chain/agent based on data in our EHR. | [These sample elixirs](https://github.com/dermatologist/dhti-elixir) adopt FHIR and cds-hooks as standards for data retrieval and display. The [base class](https://github.com/dermatologist/dhti-elixir-base) provides reusable artifacts |
54
109
  | I need a simple platform for experimenting. | This repository provides everything to start experimenting fast. The command-line tools help to virtualize and orchestrate your experiments using [Docker](https://www.docker.com/)|
55
- | I am a UI designer. I want to design helpful UI for real users. | See [this template](https://github.com/dermatologist/openmrs-esm-dhti-template). It shows how to build interface components (conches) for [OpenMRS](https://openmrs.org/) an open-source EMR used by many. Read more about [OpenMRS UI](https://o3-docs.openmrs.org/) |
110
+ | I am a UI designer. I want to design helpful UI for real users. | See [these sample conches](https://github.com/dermatologist/openmrs-esm-dhti). It shows how to build interface components (conches) for [OpenMRS](https://openmrs.org/) an open-source EMR used by many. Read more about [OpenMRS UI](https://o3-docs.openmrs.org/) |
56
111
  | We use another EMR | Your EMR may support CDS-Hook for displaying components. In that case, you can use [cds-hooks-sandbox for testing](https://github.com/dermatologist/cds-hooks-sandbox/tree/dhti-1) |
57
112
  | Our IT team is often unable to take my experiments to production. | Use DHTI, follow the recommended patterns, and you will make their lives easier.|
58
113
 
@@ -68,6 +123,8 @@ The essence of DHTI is *modularity* with an emphasis on *configuration!* It is n
68
123
  * **Easy to use**: Can be installed in a few minutes.
69
124
  * **Developer friendly**: Copy working files to running containers for testing.
70
125
  * **Dry-run mode**: Preview changes before execution with the `--dry-run` flag.
126
+ * **Local directory installation**: Install elixirs and conches from local directories using the `-l` flag.
127
+ * **Monorepo support**: Install elixirs and conches from subdirectories in GitHub repositories with the `-s` flag.
71
128
  * **Dependency Injection**: Dependency injection for models and hyperparameters for configuring elixirs.
72
129
  * **Generate synthetic data**: [DHTI supports generating synthetic data for testing, using synthea.](/notes/SYNTHEA.md)
73
130
  * **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).
@@ -79,9 +136,10 @@ The essence of DHTI is *modularity* with an emphasis on *configuration!* It is n
79
136
  * **Graph utilities**: Neo4j for graph utilities.
80
137
  * **LLM**: Ollama for self-hosting LLM models.
81
138
 
82
- ## ✨ New
139
+ ## ✨ Advanced Features
140
+ * **start-dhti skill**: New AI agent skill that orchestrates complete DHTI application development - from generating elixirs and conches to starting a fully functional DHTI server.
83
141
  * **MCPX integration**: DHTI now includes an [MCP integrator](https://docs.lunar.dev/mcpx/) that allows other MCP servers to be "installed" and exposed seamlessly to DHTI through the MCPX gateway.
84
- * **DOCKTOR module**: A new module, [DOCKTOR](/notes/DOCKTOR.md), support traditional machine learning model packaged as Docker containers, to be used as MCP tools, enabling the deployment of inference pipelines as agent-invokable tools. (in beta)
142
+ * **DOCKTOR module**: A new module, [DOCKTOR](/notes/DOCKTOR.md), supports traditional machinelearning models packaged as Docker containers. These can be used as MCP tools to deploy inference pipelines as agentinvokable tools (in beta).
85
143
  * **MCP aware agent**: [dhti-elixir-template](https://github.com/dermatologist/dhti-elixir-template) used in the examples now includes an [MCP aware agent](https://github.com/dermatologist/dhti-elixir-template/blob/feature/agent-2/src/dhti_elixir_template/chain.py) that can autodiscover and invoke tools from the MCPX gateway. Install it using `npx dhti-cli elixir install -g https://github.com/dermatologist/dhti-elixir-template.git -n dhti-elixir-template -b feature/agent2`.
86
144
  * **Medplum integration**: [Medplum](https://www.medplum.com/) is now supported as an alternative FHIR server. Read more [here](/notes/medplum.md). This allows you to add FHIR subscriptions for real-time updates and much more.
87
145
  * **Synthea integration**: You can now generate synthetic FHIR data using [Synthea](https://synthetichealth.github.io/synthea/). Read more [here](/notes/SYNTHEA.md).
@@ -90,110 +148,28 @@ The essence of DHTI is *modularity* with an emphasis on *configuration!* It is n
90
148
 
91
149
  ## 🔧 For Gen AI Developers
92
150
 
93
- *Developers can build elixirs and conchs for DHTI.*
94
-
95
- :curry: Elixirs are [LangServe Apps](https://python.langchain.com/docs/langserve/) for backend GenAI functionality. By convention, Elixirs are prefixed with *dhti-elixir-* and all elixirs depend on [dhti-elixir-base](https://github.com/dermatologist/dhti-elixir-base) which provides some base classes and defines dependencies. You can use [this template](https://github.com/dermatologist/dhti-elixir-template) or the [cookiecutter](https://github.com/dermatologist/cookiecutter-uv) to build new elixirs, and license it the way you want (We :heart: open-source!).
96
-
97
- :shell: Conches are [OpenMRS O3s](https://o3-docs.openmrs.org/) and follow the standard naming convention *openmrs-esm-*. You can use [this template](https://github.com/dermatologist/openmrs-esm-dhti-template) to build new conches.
98
-
99
- :white_check_mark:
100
- * **Developer friendly**: Copy working files to running containers for testing.
101
- * **Dependency Injection**: Dependency injection for models and hyperparameters for configuring elixirs.
102
- * 👉 [Try it out today!](#try-it-out)
151
+ *Developers can build elixirs and conches for DHTI.* See CONTRIBUTING.md for details. User contributed [elixir](https://github.com/dermatologist/dhti-elixir) and [conch](https://github.com/dermatologist/openmrs-esm-dhti) repositories provide examples and templates for development.
103
152
 
104
153
  ## 🧠 For Gen AI Researchers
105
154
 
106
- *DHTI provides a platform to deploy language models and Gen AI applications in the context of an electronic health record.*
155
+ *DHTI provides a platform to deploy AI models and Gen AI applications in the context of an electronic health record.*
107
156
 
108
- DHTI serves as a platform for testing prompts, chains and agents in healthcare applications. Since the stack uses the :fire: FHIR data model, it is easy to load synthetic data.
157
+ DHTI serves as a platform for testing models, prompts, chains, and agents in healthcare applications. Because the stack uses the :fire: FHIR data model, it is easy to load synthetic data. We encourage models built for this platform to be open‑sourced on [HuggingFace](https://huggingface.co/) using the `dhti-` prefix.
109
158
 
110
- Tools to fine-tune language models for the stack are on our roadmap. We encourage all language models built for this platform to be open sourced on [HuggingFace](https://huggingface.co/) with the names starting with *dhti-*.
159
+ ## 🚀 For clinicians
111
160
 
112
- :white_check_mark:
113
- * **Generate synthetic data**: DHTI supports generating synthetic data for testing.
114
- * **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).
115
- * **FHIR**: Data exchange with FHIR schema and **CDS-Hooks** for frontend-backend communication.
116
- * **EMR**: Built-in EMR, OpenMRS, for patient records.
117
- * 👉 [Try it out today!](#try-it-out)
161
+ *DHTI includes [skills](/.github/skills/) that generate GenAI components from problem‑oriented [prompts](/prompts/e2e-sample.md).*
118
162
 
119
- *Join us to make the Gen AI equitable and help doctors save lives!*
120
-
121
- ## :sparkles: Resources
163
+ ## Resources
122
164
  * [fhiry](https://github.com/dermatologist/fhiry): FHIR to pandas dataframe for data analytics, AI and ML!
123
165
  * [pyOMOP](https://github.com/dermatologist/pyomop): For OMOP CDM support
124
-
125
- ## :sparkles: Resources (in Beta)
126
- * [dhti-elixir-base](https://github.com/dermatologist/dhti-elixir-base): Base classes for dhti-elixir
127
- * [dhti-elixir-template](https://github.com/dermatologist/dhti-elixir-template): A template for creating new dhti-elixirs & a **simple EMR chatbot backend**.
128
- * [openmrs-esm-dhti-template](https://github.com/dermatologist/openmrs-esm-dhti-template): A conch template for OpenMRS & a **simple EMR chatbot frontend**.
129
- * [fhir-mcp-server](https://github.com/dermatologist/fhir-mcp-server): A MCP server for hosting FHIR-compliant tools.
130
-
131
- ## :sparkles: Resources (in Alpha)
132
166
  * [cookiecutter for scaffolding elixirs](https://github.com/dermatologist/cookiecutter-uv)
133
167
  * [cds-hooks-sandbox for testing](https://github.com/dermatologist/cds-hooks-sandbox/tree/dhti-1)
134
168
  * [Medplum integration](/notes/medplum.md)
135
169
 
136
- ## :sunglasses: Coming soon
137
-
138
- * [dhti-elixir-fhire](https://github.com/dermatologist/dhti-elixir-fhire): An elixir for FHIR embeddings.
139
- * [dhti-elixir-upload](https://github.com/dermatologist/dhti-elixir-upload-file): Upload documents to the vector store for clinical knowledgebase and clinical trial matching.
140
-
141
- ## Try it out
142
-
143
- * You only need [Node.js](https://nodejs.org/) and [Docker](https://www.docker.com/) installed to run this project. Optionally, you can install [Python](https://www.python.org/) if you want to develop new elixirs. We use a fake LLM script for testing purposes, so you don't need an OpenAI key to run this project. It just says "Paris" or "I don't know" to any prompt. You can replace it with any internal or external LLM service later.
144
-
145
- 👉 **If you are in a hurry, just run `./demo.sh` from a terminal (Linux or MacOS) in the root folder to try out the demo.** Windows users can use WSL. You only need [Node.js](https://nodejs.org/) and [Docker](https://www.docker.com/). This script runs all the commands below. Once done, use `npx dhti-cli docker -d` to stop and delete all the docker containers.
146
-
147
- * `npx dhti-cli help` to see all available commands.
148
-
149
- * `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`
150
-
151
- * `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.
152
-
153
- * `npx dhti-cli docker -n yourdockerhandle/genai-test:1.0 -t elixir` to build a docker image for the elixir.
154
-
155
- * `npx dhti-cli conch install -g https://github.com/dermatologist/openmrs-esm-dhti-template.git -n openmrs-esm-dhti-template` to install a simple OpenMRS ESM module (conch)from github. You can install multiple conches.
156
-
157
- * `npx dhti-cli docker -n yourdockerhandle/conch-test:1.0 -t conch` to build a docker image for the conches.
158
-
159
- * `npx dhti-cli docker -u` to start all the docker images in your docker-compose.yml.
160
-
161
- * *(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:
162
- - `npx dhti-cli compose add -m langserve --dry-run` to preview modules that would be added
163
- - `npx dhti-cli elixir install -n test-elixir --dry-run` to see what files would be created/modified
164
-
165
- ### :clap: Access the Conch in OpenMRS and test the integration
166
-
167
- * Go to `http://localhost/openmrs/spa/home`
168
- * Login with the following credentials:
169
- - Username: admin
170
- - Password: Admin123
171
-
172
- You will see the new conch in the left margin. Click on **Dhti app** to see the UI.
173
- This is just a template, though. You can build your own conchs!
174
-
175
- Add some text to the text area and click on **Submit**.
176
- You will see the text above the textbox.
177
-
178
- * `npx dhti-cli docker -d` to stop and delete all the docker containers.
179
-
180
- Read [![Wiki](https://img.shields.io/badge/DHTI-wiki-demo)](https://github.com/dermatologist/dhti/wiki) for more details.
181
-
182
- ## 👋 The demo uses mock LLM. 👉 [Check out how to add real LLMs and configure them.](https://github.com/dermatologist/dhti/wiki/Configuration)
183
-
184
- :hugs: **Thank you for trying out DHTI!**
185
-
186
- ## 🚀 Advanced
187
-
188
- * [Detailed steps to try it out](/notes/steps.md)
189
- * [Setting up Ollama](/notes/setup-ollama.md)
190
- * [CLI Options](/notes/cli-options.md)
191
-
192
170
  ## Give us a star ⭐️
193
171
  If you find this project useful, give us a star. It helps others discover the project.
194
172
 
195
- ## [Details of CLI Commands](/notes/README.md)
196
-
197
173
  ## Contributors
198
174
 
199
175
  * [Bell Eapen](https://nuchange.ca) ([UIS](https://www.uis.edu/directory/bell-punneliparambil-eapen)) | [Contact](https://nuchange.ca/contact) | [![Twitter Follow](https://img.shields.io/twitter/follow/beapen?style=social)](https://twitter.com/beapen)
@@ -7,8 +7,12 @@ export default class Compose extends Command {
7
7
  static examples: string[];
8
8
  static flags: {
9
9
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
11
  file: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ host: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
13
  module: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ service: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
+ value: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
16
  };
13
17
  static init: () => void;
14
18
  run(): Promise<void>;
@@ -1,10 +1,13 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import chalk from 'chalk';
3
3
  import yaml from 'js-yaml';
4
+ import { exec } from 'node:child_process';
4
5
  import fs from 'node:fs';
5
6
  import os from 'node:os';
6
7
  import path from 'node:path';
7
8
  import { fileURLToPath } from 'node:url';
9
+ import { promisify } from 'node:util';
10
+ const execAsync = promisify(exec);
8
11
  export default class Compose extends Command {
9
12
  static args = {
10
13
  op: Args.string({ description: 'Operation to perform (add, delete, read or reset)' }),
@@ -16,17 +19,34 @@ export default class Compose extends Command {
16
19
  default: false,
17
20
  description: 'Show what changes would be made without actually making them',
18
21
  }),
22
+ env: Flags.string({
23
+ char: 'e',
24
+ description: 'Environment variable name (e.g. FHIR_BASE_URL)',
25
+ }),
19
26
  file: Flags.string({
20
27
  char: 'f',
21
28
  default: `${os.homedir()}/dhti/docker-compose.yml`,
22
29
  description: 'Full path to the docker compose file to read from. Creates if it does not exist',
23
30
  }),
31
+ host: Flags.boolean({
32
+ default: false,
33
+ description: 'Use host environment variable pattern (e.g. ${VAR_NAME:-default_value})',
34
+ }),
24
35
  // flag with a value (-n, --name=VALUE)
25
36
  module: Flags.string({
26
37
  char: 'm',
27
38
  description: 'Modules to add from ( langserve, openmrs, ollama, langfuse, cqlFhir, redis, neo4j, mcpFhir, mcpx and docktor)',
28
39
  multiple: true,
29
40
  }),
41
+ service: Flags.string({
42
+ char: 's',
43
+ default: 'langserve',
44
+ description: 'Service name to update environment variables',
45
+ }),
46
+ value: Flags.string({
47
+ char: 'v',
48
+ description: 'Environment variable value',
49
+ }),
30
50
  };
31
51
  static init = () => {
32
52
  // Create ${os.homedir()}/dhti if it does not exist
@@ -94,6 +114,105 @@ export default class Compose extends Command {
94
114
  console.log(existingData);
95
115
  return;
96
116
  }
117
+ // Handle env operation to add or update environment variables
118
+ if (args.op === 'env') {
119
+ // Validate mandatory flags
120
+ if (!flags.env) {
121
+ console.error(chalk.red('Error: --env flag is required for env operation'));
122
+ this.exit(1);
123
+ }
124
+ if (!flags.value) {
125
+ console.error(chalk.red('Error: --value flag is required for env operation'));
126
+ this.exit(1);
127
+ }
128
+ const serviceName = flags.service;
129
+ const envVarName = flags.env;
130
+ let envVarValue = flags.value;
131
+ // Apply host pattern if --host flag is present
132
+ if (flags.host) {
133
+ // eslint-disable-next-line no-template-curly-in-string
134
+ envVarValue = `\${${envVarName}:-${flags.value}}`;
135
+ }
136
+ // Check if service exists
137
+ if (!existingData.services[serviceName]) {
138
+ console.error(chalk.red(`Error: Service '${serviceName}' not found in docker-compose.yml`));
139
+ this.exit(1);
140
+ }
141
+ const service = existingData.services[serviceName];
142
+ // Initialize environment array if not present
143
+ if (!service.environment) {
144
+ service.environment = [];
145
+ }
146
+ // Find if the environment variable already exists
147
+ const envArray = service.environment;
148
+ let foundIndex = -1;
149
+ let oldValue;
150
+ // Handle environment as array of strings or objects
151
+ for (const [index, env] of envArray.entries()) {
152
+ if (typeof env === 'string') {
153
+ if (env.startsWith(`${envVarName}=`)) {
154
+ foundIndex = index;
155
+ oldValue = env.split('=').slice(1).join('=');
156
+ break;
157
+ }
158
+ }
159
+ else if (typeof env === 'object' && env !== null && envVarName in env) {
160
+ foundIndex = index;
161
+ oldValue = env[envVarName];
162
+ break;
163
+ }
164
+ }
165
+ if (flags['dry-run']) {
166
+ console.log(chalk.yellow('[DRY RUN] Would update environment variable:'));
167
+ console.log(chalk.cyan(` Service: ${serviceName}`));
168
+ console.log(chalk.cyan(` Variable: ${envVarName}`));
169
+ if (foundIndex >= 0) {
170
+ console.log(chalk.yellow(` Old value: ${oldValue}`));
171
+ console.log(chalk.green(` New value: ${envVarValue}`));
172
+ }
173
+ else {
174
+ console.log(chalk.green(` Adding new value: ${envVarValue}`));
175
+ }
176
+ console.log(chalk.cyan(` Would run: docker compose up -d (in ${path.dirname(flags.file)})`));
177
+ return;
178
+ }
179
+ // Update or add the environment variable
180
+ if (foundIndex >= 0) {
181
+ // Update existing
182
+ const existingEnv = envArray[foundIndex];
183
+ if (typeof existingEnv === 'string') {
184
+ envArray[foundIndex] = `${envVarName}=${envVarValue}`;
185
+ }
186
+ else if (typeof existingEnv === 'object' && existingEnv !== null) {
187
+ ;
188
+ existingEnv[envVarName] = envVarValue;
189
+ }
190
+ console.log(chalk.blue(`Updating environment variable in service '${serviceName}':`));
191
+ console.log(chalk.yellow(` Old value: ${oldValue}`));
192
+ console.log(chalk.green(` New value: ${envVarValue}`));
193
+ }
194
+ else {
195
+ // Add new
196
+ envArray.push(`${envVarName}=${envVarValue}`);
197
+ console.log(chalk.blue(`Adding new environment variable to service '${serviceName}':`));
198
+ console.log(chalk.green(` ${envVarName}=${envVarValue}`));
199
+ }
200
+ // Write the updated compose file
201
+ const updatedCompose = yaml.dump(existingData).replaceAll('null', '');
202
+ fs.writeFileSync(flags.file, updatedCompose, 'utf8');
203
+ console.log(chalk.green(`✓ docker-compose.yml updated successfully`));
204
+ // Run docker compose up -d to apply changes
205
+ try {
206
+ const workdir = path.dirname(flags.file);
207
+ await execAsync('docker compose up -d', { cwd: workdir });
208
+ console.log(chalk.green(`✓ Docker compose reloaded successfully (docker compose up -d)`));
209
+ }
210
+ catch (error) {
211
+ const err = error;
212
+ console.warn(chalk.yellow(`⚠ Warning: Could not run docker compose up -d: ${err.message}`));
213
+ }
214
+ return;
215
+ }
97
216
  // Delete flags.file if args.op is reset
98
217
  if (args.op === 'reset') {
99
218
  if (flags['dry-run']) {
@@ -7,13 +7,10 @@ export default class Conch extends Command {
7
7
  static examples: string[];
8
8
  static flags: {
9
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
10
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
11
  git: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
- image: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
12
  name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
- repoVersion: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ sources: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
14
  workdir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
18
15
  };
19
16
  run(): Promise<void>;