dhti-cli 0.8.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -111
- package/dist/commands/compose.d.ts +4 -0
- package/dist/commands/compose.js +119 -0
- package/dist/commands/conch.d.ts +2 -6
- package/dist/commands/conch.js +167 -215
- package/dist/commands/elixir.d.ts +2 -0
- package/dist/commands/elixir.js +346 -45
- package/dist/resources/docker-compose-master.yml +4 -2
- package/dist/resources/spa/README.md +3 -0
- package/dist/resources/spa/config-schema.ts +53 -0
- package/dist/resources/spa/glycemic.component.tsx +86 -0
- package/dist/resources/spa/glycemic.scss +44 -0
- package/dist/resources/spa/hooks/useDhti.ts +69 -0
- package/dist/resources/spa/hooks/usePatient.ts +61 -0
- package/dist/resources/spa/index.ts +17 -0
- package/dist/resources/spa/models/card.ts +80 -0
- package/dist/resources/spa/models/request.ts +42 -0
- package/dist/resources/spa/routes.json +17 -0
- package/oclif.manifest.json +66 -53
- package/package.json +3 -1
- package/dist/resources/spa/Dockerfile +0 -16
- package/dist/resources/spa/def/importmap.json +0 -39
- package/dist/resources/spa/def/routes.registry.json +0 -1599
- package/dist/resources/spa/def/spa-assemble-config.json +0 -40
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
|
[](https://www.npmjs.com/package/dhti-cli)
|
|
7
|
-
[](https://www.npmjs.com/package/dhti-cli)
|
|
7
|
+
[](https://www.npmjs.com/package/dhti-cli)
|
|
8
8
|
[](https://nuchange.ca)
|
|
9
9
|
[](https://www.npmjs.com/package/dhti-cli)
|
|
10
10
|
[](https://dermatologist.github.io/dhti/)
|
|
11
11
|
[](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
|
-
|
|
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
|
-
###
|
|
17
|
-
👉 [Try it out today!](#try-it-out) and give us a star ⭐️ if you like it!
|
|
19
|
+
### Why?
|
|
18
20
|
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
[
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
66
|
+
<img src="https://github.com/dermatologist/dhti/blob/develop/notes/cds-hook-sandbox.jpg" />
|
|
35
67
|
</p>
|
|
36
68
|
|
|
37
|
-
|
|
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/
|
|
86
|
+
<img src="https://github.com/dermatologist/openmrs-esm-dhti-template/blob/develop/notes/conch.jpg" />
|
|
41
87
|
</p>
|
|
42
88
|
|
|
43
|
-
*
|
|
89
|
+
* `npx dhti-cli docker -d` to stop and delete all the docker containers.
|
|
44
90
|
|
|
45
|
-
|
|
91
|
+
## Wiki & Documentation
|
|
92
|
+
* [](https://github.com/dermatologist/dhti/wiki)
|
|
93
|
+
* [Documentation](https://dermatologist.github.io/dhti/)
|
|
94
|
+
* [CLI Reference](/notes/README.md)
|
|
46
95
|
|
|
47
|
-
|
|
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. | [
|
|
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 [
|
|
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
|
|
|
@@ -81,11 +136,10 @@ The essence of DHTI is *modularity* with an emphasis on *configuration!* It is n
|
|
|
81
136
|
* **Graph utilities**: Neo4j for graph utilities.
|
|
82
137
|
* **LLM**: Ollama for self-hosting LLM models.
|
|
83
138
|
|
|
84
|
-
## ✨
|
|
85
|
-
* **Local directory installation**: Install elixirs and conches from local directories using the new `-l` flag, enabling seamless integration with locally generated projects.
|
|
139
|
+
## ✨ Advanced Features
|
|
86
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.
|
|
87
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.
|
|
88
|
-
* **DOCKTOR module**: A new module, [DOCKTOR](/notes/DOCKTOR.md),
|
|
142
|
+
* **DOCKTOR module**: A new module, [DOCKTOR](/notes/DOCKTOR.md), supports traditional machine‑learning models packaged as Docker containers. These can be used as MCP tools to deploy inference pipelines as agent‑invokable tools (in beta).
|
|
89
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`.
|
|
90
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.
|
|
91
145
|
* **Synthea integration**: You can now generate synthetic FHIR data using [Synthea](https://synthetichealth.github.io/synthea/). Read more [here](/notes/SYNTHEA.md).
|
|
@@ -94,110 +148,28 @@ The essence of DHTI is *modularity* with an emphasis on *configuration!* It is n
|
|
|
94
148
|
|
|
95
149
|
## 🔧 For Gen AI Developers
|
|
96
150
|
|
|
97
|
-
*Developers can build elixirs and
|
|
98
|
-
|
|
99
|
-
: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!).
|
|
100
|
-
|
|
101
|
-
: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.
|
|
102
|
-
|
|
103
|
-
:white_check_mark:
|
|
104
|
-
* **Developer friendly**: Copy working files to running containers for testing.
|
|
105
|
-
* **Dependency Injection**: Dependency injection for models and hyperparameters for configuring elixirs.
|
|
106
|
-
* 👉 [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.
|
|
107
152
|
|
|
108
153
|
## 🧠 For Gen AI Researchers
|
|
109
154
|
|
|
110
|
-
*DHTI provides a platform to deploy
|
|
155
|
+
*DHTI provides a platform to deploy AI models and Gen AI applications in the context of an electronic health record.*
|
|
111
156
|
|
|
112
|
-
DHTI serves as a platform for testing prompts, chains and agents in healthcare applications.
|
|
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.
|
|
113
158
|
|
|
114
|
-
|
|
159
|
+
## 🚀 For clinicians
|
|
115
160
|
|
|
116
|
-
|
|
117
|
-
* **Generate synthetic data**: DHTI supports generating synthetic data for testing.
|
|
118
|
-
* **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).
|
|
119
|
-
* **FHIR**: Data exchange with FHIR schema and **CDS-Hooks** for frontend-backend communication.
|
|
120
|
-
* **EMR**: Built-in EMR, OpenMRS, for patient records.
|
|
121
|
-
* 👉 [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).*
|
|
122
162
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
## :sparkles: Resources
|
|
163
|
+
## ✨ Resources
|
|
126
164
|
* [fhiry](https://github.com/dermatologist/fhiry): FHIR to pandas dataframe for data analytics, AI and ML!
|
|
127
165
|
* [pyOMOP](https://github.com/dermatologist/pyomop): For OMOP CDM support
|
|
128
|
-
|
|
129
|
-
## :sparkles: Resources (in Beta)
|
|
130
|
-
* [dhti-elixir-base](https://github.com/dermatologist/dhti-elixir-base): Base classes for dhti-elixir
|
|
131
|
-
* [dhti-elixir-template](https://github.com/dermatologist/dhti-elixir-template): A template for creating new dhti-elixirs & a **simple EMR chatbot backend**.
|
|
132
|
-
* [openmrs-esm-dhti-template](https://github.com/dermatologist/openmrs-esm-dhti-template): A conch template for OpenMRS & a **simple EMR chatbot frontend**.
|
|
133
|
-
* [fhir-mcp-server](https://github.com/dermatologist/fhir-mcp-server): A MCP server for hosting FHIR-compliant tools.
|
|
134
|
-
|
|
135
|
-
## :sparkles: Resources (in Alpha)
|
|
136
166
|
* [cookiecutter for scaffolding elixirs](https://github.com/dermatologist/cookiecutter-uv)
|
|
137
167
|
* [cds-hooks-sandbox for testing](https://github.com/dermatologist/cds-hooks-sandbox/tree/dhti-1)
|
|
138
168
|
* [Medplum integration](/notes/medplum.md)
|
|
139
169
|
|
|
140
|
-
## :sunglasses: Coming soon
|
|
141
|
-
|
|
142
|
-
* [dhti-elixir-fhire](https://github.com/dermatologist/dhti-elixir-fhire): An elixir for FHIR embeddings.
|
|
143
|
-
* [dhti-elixir-upload](https://github.com/dermatologist/dhti-elixir-upload-file): Upload documents to the vector store for clinical knowledgebase and clinical trial matching.
|
|
144
|
-
|
|
145
|
-
## Try it out
|
|
146
|
-
|
|
147
|
-
* 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.
|
|
148
|
-
|
|
149
|
-
👉 **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.
|
|
150
|
-
|
|
151
|
-
* `npx dhti-cli help` to see all available commands.
|
|
152
|
-
|
|
153
|
-
* `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`
|
|
154
|
-
|
|
155
|
-
* `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. Alternatively, use `-l <local-directory>` to install from a local directory.
|
|
156
|
-
|
|
157
|
-
* `npx dhti-cli docker -n yourdockerhandle/genai-test:1.0 -t elixir` to build a docker image for the elixir.
|
|
158
|
-
|
|
159
|
-
* `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. Alternatively, use `-l <local-directory>` to install from a local directory.
|
|
160
|
-
|
|
161
|
-
* `npx dhti-cli docker -n yourdockerhandle/conch-test:1.0 -t conch` to build a docker image for the conches.
|
|
162
|
-
|
|
163
|
-
* `npx dhti-cli docker -u` to start all the docker images in your docker-compose.yml.
|
|
164
|
-
|
|
165
|
-
* *(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:
|
|
166
|
-
- `npx dhti-cli compose add -m langserve --dry-run` to preview modules that would be added
|
|
167
|
-
- `npx dhti-cli elixir install -n test-elixir --dry-run` to see what files would be created/modified
|
|
168
|
-
|
|
169
|
-
### :clap: Access the Conch in OpenMRS and test the integration
|
|
170
|
-
|
|
171
|
-
* Go to `http://localhost/openmrs/spa/home`
|
|
172
|
-
* Login with the following credentials:
|
|
173
|
-
- Username: admin
|
|
174
|
-
- Password: Admin123
|
|
175
|
-
|
|
176
|
-
You will see the new conch in the left margin. Click on **Dhti app** to see the UI.
|
|
177
|
-
This is just a template, though. You can build your own conchs!
|
|
178
|
-
|
|
179
|
-
Add some text to the text area and click on **Submit**.
|
|
180
|
-
You will see the text above the textbox.
|
|
181
|
-
|
|
182
|
-
* `npx dhti-cli docker -d` to stop and delete all the docker containers.
|
|
183
|
-
|
|
184
|
-
Read [](https://github.com/dermatologist/dhti/wiki) for more details.
|
|
185
|
-
|
|
186
|
-
## 👋 The demo uses mock LLM. 👉 [Check out how to add real LLMs and configure them.](https://github.com/dermatologist/dhti/wiki/Configuration)
|
|
187
|
-
|
|
188
|
-
:hugs: **Thank you for trying out DHTI!**
|
|
189
|
-
|
|
190
|
-
## 🚀 Advanced
|
|
191
|
-
|
|
192
|
-
* [Detailed steps to try it out](/notes/steps.md)
|
|
193
|
-
* [Setting up Ollama](/notes/setup-ollama.md)
|
|
194
|
-
* [CLI Options](/notes/cli-options.md)
|
|
195
|
-
|
|
196
170
|
## Give us a star ⭐️
|
|
197
171
|
If you find this project useful, give us a star. It helps others discover the project.
|
|
198
172
|
|
|
199
|
-
## [Details of CLI Commands](/notes/README.md)
|
|
200
|
-
|
|
201
173
|
## Contributors
|
|
202
174
|
|
|
203
175
|
* [Bell Eapen](https://nuchange.ca) ([UIS](https://www.uis.edu/directory/bell-punneliparambil-eapen)) | [Contact](https://nuchange.ca/contact) | [](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>;
|
package/dist/commands/compose.js
CHANGED
|
@@ -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']) {
|
package/dist/commands/conch.d.ts
CHANGED
|
@@ -7,15 +7,11 @@ 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
|
-
|
|
15
|
-
local: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
local: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
13
|
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
-
|
|
18
|
-
subdirectory: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
sources: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
15
|
workdir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
16
|
};
|
|
21
17
|
run(): Promise<void>;
|