opsmith-cli 0.2.2b0__tar.gz → 0.3.0b0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/.gitignore +4 -1
  2. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/PKG-INFO +10 -17
  3. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/README.md +9 -16
  4. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/agent.py +6 -1
  5. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/deployment_strategies/base.py +4 -4
  6. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/deployment_strategies/monolithic.py +9 -12
  7. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/git_repo.py +2 -1
  8. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/infra_provisioners/base_provisioner.py +1 -3
  9. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/main.py +14 -8
  10. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/models.py +3 -2
  11. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/prompts.py +2 -0
  12. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/repo_map.py +4 -3
  13. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/service_detector.py +46 -23
  14. opsmith_cli-0.3.0b0/opsmith/templates/dockerfiles/python_backend_api +79 -0
  15. opsmith_cli-0.3.0b0/opsmith/templates/dockerfiles/python_backend_worker +77 -0
  16. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/pyproject.toml +4 -1
  17. opsmith_cli-0.3.0b0/resources/deploy_demo.svg +1 -0
  18. opsmith_cli-0.3.0b0/resources/setup_demo.gif +0 -0
  19. opsmith_cli-0.3.0b0/resources/setup_demo.svg +1 -0
  20. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/.flake8 +0 -0
  21. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/.github/workflows/python-publish.yml +0 -0
  22. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/.pre-commit-config.yaml +0 -0
  23. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/CONVENTIONS.md +0 -0
  24. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/LICENSE.txt +0 -0
  25. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/__init__.py +0 -0
  26. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/cloud_providers/__init__.py +0 -0
  27. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/cloud_providers/aws.py +0 -0
  28. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/cloud_providers/base.py +0 -0
  29. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/cloud_providers/gcp.py +0 -0
  30. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/constants.py +0 -0
  31. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/deployment_strategies/__init__.py +0 -0
  32. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/exceptions.py +0 -0
  33. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/infra_provisioners/__init__.py +0 -0
  34. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/infra_provisioners/ansible_provisioner.py +0 -0
  35. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/infra_provisioners/terraform_provisioner.py +0 -0
  36. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/README.md +0 -0
  37. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/arduino-tags.scm +0 -0
  38. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/c-tags.scm +0 -0
  39. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/c_sharp-tags.scm +0 -0
  40. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/chatito-tags.scm +0 -0
  41. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/commonlisp-tags.scm +0 -0
  42. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/cpp-tags.scm +0 -0
  43. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/csharp-tags.scm +0 -0
  44. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/d-tags.scm +0 -0
  45. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/dart-tags.scm +0 -0
  46. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/elisp-tags.scm +0 -0
  47. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/elixir-tags.scm +0 -0
  48. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/elm-tags.scm +0 -0
  49. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/gleam-tags.scm +0 -0
  50. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/go-tags.scm +0 -0
  51. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/hcl-tags.scm +0 -0
  52. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/java-tags.scm +0 -0
  53. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/javascript-tags.scm +0 -0
  54. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/kotlin-tags.scm +0 -0
  55. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/lua-tags.scm +0 -0
  56. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/ocaml-tags.scm +0 -0
  57. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/ocaml_interface-tags.scm +0 -0
  58. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/php-tags.scm +0 -0
  59. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/pony-tags.scm +0 -0
  60. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/properties-tags.scm +0 -0
  61. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/python-tags.scm +0 -0
  62. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/ql-tags.scm +0 -0
  63. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/r-tags.scm +0 -0
  64. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/racket-tags.scm +0 -0
  65. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/ruby-tags.scm +0 -0
  66. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/rust-tags.scm +0 -0
  67. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/scala-tags.scm +0 -0
  68. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/solidity-tags.scm +0 -0
  69. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/swift-tags.scm +0 -0
  70. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/typescript-tags.scm +0 -0
  71. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/queries/tree-sitter-languages/udev-tags.scm +0 -0
  72. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/settings.py +0 -0
  73. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/cloud_storage_cleanup/aws/main.yml +0 -0
  74. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/cloud_storage_cleanup/gcp/main.yml +0 -0
  75. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/container_registry/aws/main.tf +0 -0
  76. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/container_registry/aws/outputs.tf +0 -0
  77. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/container_registry/aws/variables.tf +0 -0
  78. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/container_registry/gcp/main.tf +0 -0
  79. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/container_registry/gcp/outputs.tf +0 -0
  80. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/container_registry/gcp/variables.tf +0 -0
  81. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_build_push/aws/main.yml +0 -0
  82. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_build_push/gcp/main.yml +0 -0
  83. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_deploy/aws/main.yml +0 -0
  84. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_deploy/gcp/main.yml +0 -0
  85. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_run/common/main.yml +0 -0
  86. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/base.yml +0 -0
  87. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/elasticsearch.yml +0 -0
  88. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/kafka.yml +0 -0
  89. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/mongodb.yml +0 -0
  90. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/mysql.yml +0 -0
  91. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/postgresql.yml +0 -0
  92. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/rabbitmq.yml +0 -0
  93. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/redis.yml +0 -0
  94. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/services/backend_api.yml +0 -0
  95. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/services/backend_worker.yml +0 -0
  96. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/services/full_stack.yml +0 -0
  97. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/traefik.yml +0 -0
  98. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_compose_snippets/weaviate.yml +0 -0
  99. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_setup/aws/main.yml +0 -0
  100. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/docker_setup/gcp/main.yml +0 -0
  101. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/fetch_remote_files/common/main.yml +0 -0
  102. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_bucket_cert/aws/main.tf +0 -0
  103. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_bucket_cert/aws/outputs.tf +0 -0
  104. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_bucket_cert/aws/variables.tf +0 -0
  105. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_bucket_cert/gcp/main.tf +0 -0
  106. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_bucket_cert/gcp/outputs.tf +0 -0
  107. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_bucket_cert/gcp/variables.tf +0 -0
  108. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_cdn/aws/main.tf +0 -0
  109. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_cdn/aws/outputs.tf +0 -0
  110. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_cdn/aws/variables.tf +0 -0
  111. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_cdn/gcp/main.tf +0 -0
  112. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_cdn/gcp/outputs.tf +0 -0
  113. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_cdn/gcp/variables.tf +0 -0
  114. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_deploy/aws/main.yml +0 -0
  115. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/frontend_deploy/gcp/main.yml +0 -0
  116. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/virtual_machine/aws/main.tf +0 -0
  117. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/virtual_machine/aws/outputs.tf +0 -0
  118. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/virtual_machine/aws/variables.tf +0 -0
  119. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/virtual_machine/gcp/main.tf +0 -0
  120. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/virtual_machine/gcp/outputs.tf +0 -0
  121. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/templates/virtual_machine/gcp/variables.tf +0 -0
  122. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/types.py +0 -0
  123. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/opsmith/utils.py +0 -0
  124. {opsmith_cli-0.2.2b0 → opsmith_cli-0.3.0b0}/uv.lock +0 -0
@@ -194,4 +194,7 @@ cython_debug/
194
194
  .cursorindexingignore
195
195
 
196
196
  # Aider
197
- .aider*
197
+ .aider*
198
+
199
+ # Asciinema
200
+ *.cast
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opsmith-cli
3
- Version: 0.2.2b0
3
+ Version: 0.3.0b0
4
4
  Summary: Opsmith is an AI devops engineer in your terminal
5
5
  License-Expression: GPL-3.0-only
6
6
  License-File: LICENSE.txt
@@ -109,13 +109,10 @@ Deploying your application with Opsmith follows a straightforward workflow:
109
109
  ```shell
110
110
  opsmith --model <your-llm-provider:model-name> --api-key <your-api-key> setup
111
111
  ```
112
-
113
- You will be prompted to:
114
- - Provide an application name.
115
- - Select a cloud provider (e.g., AWS, GCP — see [Cloud Providers](#cloud-providers) for setup).
116
- - Review and confirm the services and infrastructure dependencies detected by the AI.
117
- - Opsmith will then generate a `Dockerfile` for each of your services.
118
-
112
+ <p >
113
+ <img width="600" src="https://raw.githubusercontent.com/abhishek-ram/opsmith-cli/main/resources/setup_demo.svg" alt="Opsmith Setup">
114
+ </p>
115
+
119
116
  2. **Deploy Your Application**
120
117
 
121
118
  After setting up the configuration, deploy your application using the `deploy` command:
@@ -124,13 +121,9 @@ Deploying your application with Opsmith follows a straightforward workflow:
124
121
  opsmith --model <your-llm-provider:model-name> --api-key <your-api-key> deploy
125
122
  ```
126
123
 
127
- This will guide you through:
128
- - Creating a new deployment environment (e.g., `dev`, `staging`, `production`).
129
- - Selecting a cloud region.
130
- - Choosing a deployment strategy (e.g., `Monolithic`).
131
- - Configuring domain names for your services if needed.
132
-
133
- Opsmith will then provision all the necessary cloud infrastructure and deploy your application.
124
+ <p >
125
+ <img width="600" src="https://raw.githubusercontent.com/abhishek-ram/opsmith-cli/main/resources/deploy_demo.svg" alt="Opsmith Setup">
126
+ </p>
134
127
 
135
128
  3. **Manage Your Deployments**
136
129
 
@@ -149,14 +142,14 @@ Opsmith leverages Large Language Models (LLMs) to analyze your codebase, generat
149
142
 
150
143
  Opsmith supports a variety of models from different providers. Here is a list of the currently supported models:
151
144
 
145
+ - **Google** (Recommended):
146
+ - `google-gla:gemini-2.5-pro`
152
147
  - **OpenAI**:
153
148
  - `openai:gpt-4.1`
154
149
  - `openai:gpt-o3`
155
150
  - **Anthropic**:
156
151
  - `anthropic:claude-3-7-sonnet-20250219`
157
152
  - `anthropic:claude-sonnet-4-20250514`
158
- - **Google**:
159
- - `google-gla:gemini-2.5-pro`
160
153
 
161
154
  #### Usage
162
155
 
@@ -70,13 +70,10 @@ Deploying your application with Opsmith follows a straightforward workflow:
70
70
  ```shell
71
71
  opsmith --model <your-llm-provider:model-name> --api-key <your-api-key> setup
72
72
  ```
73
-
74
- You will be prompted to:
75
- - Provide an application name.
76
- - Select a cloud provider (e.g., AWS, GCP — see [Cloud Providers](#cloud-providers) for setup).
77
- - Review and confirm the services and infrastructure dependencies detected by the AI.
78
- - Opsmith will then generate a `Dockerfile` for each of your services.
79
-
73
+ <p >
74
+ <img width="600" src="https://raw.githubusercontent.com/abhishek-ram/opsmith-cli/main/resources/setup_demo.svg" alt="Opsmith Setup">
75
+ </p>
76
+
80
77
  2. **Deploy Your Application**
81
78
 
82
79
  After setting up the configuration, deploy your application using the `deploy` command:
@@ -85,13 +82,9 @@ Deploying your application with Opsmith follows a straightforward workflow:
85
82
  opsmith --model <your-llm-provider:model-name> --api-key <your-api-key> deploy
86
83
  ```
87
84
 
88
- This will guide you through:
89
- - Creating a new deployment environment (e.g., `dev`, `staging`, `production`).
90
- - Selecting a cloud region.
91
- - Choosing a deployment strategy (e.g., `Monolithic`).
92
- - Configuring domain names for your services if needed.
93
-
94
- Opsmith will then provision all the necessary cloud infrastructure and deploy your application.
85
+ <p >
86
+ <img width="600" src="https://raw.githubusercontent.com/abhishek-ram/opsmith-cli/main/resources/deploy_demo.svg" alt="Opsmith Setup">
87
+ </p>
95
88
 
96
89
  3. **Manage Your Deployments**
97
90
 
@@ -110,14 +103,14 @@ Opsmith leverages Large Language Models (LLMs) to analyze your codebase, generat
110
103
 
111
104
  Opsmith supports a variety of models from different providers. Here is a list of the currently supported models:
112
105
 
106
+ - **Google** (Recommended):
107
+ - `google-gla:gemini-2.5-pro`
113
108
  - **OpenAI**:
114
109
  - `openai:gpt-4.1`
115
110
  - `openai:gpt-o3`
116
111
  - **Anthropic**:
117
112
  - `anthropic:claude-3-7-sonnet-20250219`
118
113
  - `anthropic:claude-sonnet-4-20250514`
119
- - **Google**:
120
- - `google-gla:gemini-2.5-pro`
121
114
 
122
115
  #### Usage
123
116
 
@@ -1,5 +1,5 @@
1
1
  import json
2
- from dataclasses import dataclass
2
+ from dataclasses import dataclass, field
3
3
  from pathlib import Path
4
4
  from typing import List, Type
5
5
 
@@ -13,6 +13,7 @@ from opsmith.utils import generate_secret_string
13
13
  @dataclass
14
14
  class AgentDeps:
15
15
  src_dir: Path
16
+ tracked_files: List[Path] = field(default_factory=list)
16
17
 
17
18
 
18
19
  def is_duplicate_tool_call(ctx: RunContext[AgentDeps], tool_name: str) -> bool:
@@ -64,6 +65,7 @@ def build_agent(model_config: Type[BaseAiModel], instrument: bool = False) -> Ag
64
65
  "files in this conversation."
65
66
  )
66
67
 
68
+ allowed_files_abs = {str(p) for p in ctx.deps.tracked_files}
67
69
  contents = []
68
70
  for filename in filenames:
69
71
  if Path(filename).is_absolute():
@@ -74,6 +76,9 @@ def build_agent(model_config: Type[BaseAiModel], instrument: bool = False) -> Ag
74
76
 
75
77
  absolute_file_path = ctx.deps.src_dir.joinpath(filename).resolve()
76
78
 
79
+ if str(absolute_file_path) not in allowed_files_abs:
80
+ raise ModelRetry(f"File '{filename}' is not in the repo map and cannot be read.")
81
+
77
82
  if not str(absolute_file_path).startswith(str(ctx.deps.src_dir)):
78
83
  raise ModelRetry(
79
84
  f"Access denied. File '{filename}' is outside the repository root."
@@ -162,7 +162,7 @@ class BaseDeploymentStrategy(abc.ABC):
162
162
  registry_url: str,
163
163
  ) -> Dict[str, str]:
164
164
  """Builds and pushes Docker images for each service."""
165
- print("\n[bold blue]Starting to build and push images...[/bold blue]")
165
+ print("\n[bold blue]Build and push container images to the registry[/bold blue]")
166
166
 
167
167
  images = {}
168
168
 
@@ -237,7 +237,7 @@ class BaseDeploymentStrategy(abc.ABC):
237
237
  :return: The content of the found SSH public key as a string.
238
238
  :rtype: str
239
239
  """
240
- print("\n[bold blue]Checking for local SSH public key...[/bold blue]")
240
+ print("\n[bold blue]Looking up the local SSH public key[/bold blue]")
241
241
  system = platform.system().lower()
242
242
 
243
243
  # Common SSH key names
@@ -342,10 +342,10 @@ class BaseDeploymentStrategy(abc.ABC):
342
342
  print("[bold red]Could not find 'ansible_user' in Terraform outputs.[/bold red]")
343
343
  raise ValueError("Could not find 'ansible_user' in Terraform outputs.")
344
344
 
345
- print("\n[bold blue]Waiting 15 seconds for instance to become ready...[/bold blue]")
345
+ print("[bold blue]Waiting 15 seconds for instance to become ready...[/bold blue]")
346
346
  time.sleep(15)
347
347
 
348
- print("\n[bold blue]Setting up Docker on the new instance...[/bold blue]")
348
+ print("\n[bold blue]Setting up Docker on the newly created VM[/bold blue]")
349
349
 
350
350
  setup_docker_path = (
351
351
  self.deployments_path / "environments" / environment.name / "docker_setup"
@@ -153,14 +153,13 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
153
153
  """
154
154
  Deploys the docker-compose stack and returns container logs for validation.
155
155
  """
156
- print("\n[bold blue]Deploying docker-compose stack...[/bold blue]")
156
+ print("\n[bold blue]Deploying docker-compose stack to the VM \n[/bold blue]")
157
157
  ansible_user, instance_public_ip = (
158
158
  environment_state.virtual_machine.user,
159
159
  environment_state.virtual_machine.public_ip,
160
160
  )
161
161
  deploy_compose_path, docker_compose_path = self._get_deploy_docker_compose_path(environment)
162
162
 
163
- print("\n[bold blue]Attempting to deploy and get logs...[/bold blue]")
164
163
  ansible_runner = AnsibleProvisioner(working_dir=deploy_compose_path)
165
164
  ansible_runner.copy_template(
166
165
  "docker_compose_deploy", deployment_config.cloud_provider_instance.name().lower()
@@ -241,8 +240,6 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
241
240
  images: Dict[str, str],
242
241
  environment_state: MonolithicDeploymentState,
243
242
  ):
244
- print("\n[bold blue]Generating docker-compose file...[/bold blue]")
245
-
246
243
  base_compose_template = self.docker_compose_snippets_env.get_template("base.yml")
247
244
  base_compose = base_compose_template.render(app_name=deployment_config.app_name_slug)
248
245
 
@@ -297,8 +294,8 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
297
294
  messages = []
298
295
  for attempt in range(settings.max_docker_compose_gen_attempts):
299
296
  print(
300
- "\n[bold]Attempt"
301
- f" {attempt + 1}/{settings.max_docker_compose_gen_attempts}...[/bold]"
297
+ "\n[bold blue]Generating docker-compose file, Attempt"
298
+ f" {attempt + 1}/{settings.max_docker_compose_gen_attempts}[/bold blue]"
302
299
  )
303
300
  prompt = DOCKER_COMPOSE_GENERATION_PROMPT_TEMPLATE.format(
304
301
  base_compose=base_compose,
@@ -338,7 +335,7 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
338
335
  if is_successful:
339
336
  print("[bold green]Docker compose deployment was successful.[/bold green]")
340
337
  break
341
- print(f"Docker compose validation 'failed' with reason: \n {reason}.")
338
+ print(f"[red]Docker compose validation 'failed' with reason[/red]: \n {reason}.")
342
339
 
343
340
  messages = docker_compose_response.new_messages() + validation_messages
344
341
  else:
@@ -699,7 +696,7 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
699
696
 
700
697
  print(
701
698
  "\n[bold blue]Setting up container registry for region"
702
- f" '{environment.region}'...[/bold blue]"
699
+ f" '{environment.region}'... \n[/bold blue]"
703
700
  )
704
701
  registry_url = self._setup_container_registry(deployment_config, environment)
705
702
  images = self._build_and_push_images(deployment_config, environment, registry_url)
@@ -743,16 +740,16 @@ class MonolithicDeploymentStrategy(BaseDeploymentStrategy):
743
740
  )
744
741
  self._confirm_dns_records(dns_records)
745
742
 
743
+ env_state.registry_url = registry_url
744
+ env_state.virtual_machine = virtual_machine_state
745
+ self._generate_docker_compose(deployment_config, environment, images, env_state)
746
+
746
747
  for domain in environment.get_domains_for_services(other_services):
747
748
  print(
748
749
  "\n[bold green]Your website is available at:"
749
750
  f" https://{domain.domain_name}[/bold green]"
750
751
  )
751
752
 
752
- env_state.registry_url = registry_url
753
- env_state.virtual_machine = virtual_machine_state
754
- self._generate_docker_compose(deployment_config, environment, images, env_state)
755
-
756
753
  env_state.save(env_state_path)
757
754
  print(f"Monolithic deployment state saved to {env_state_path}")
758
755
 
@@ -93,5 +93,6 @@ class GitRepo:
93
93
  f.write(ignore_block_str)
94
94
 
95
95
  print(
96
- "[bold green].gitignore has been updated to ignore Terraform state files.[/bold green]"
96
+ "[bold green].gitignore has been updated to ignore Terraform state files.\n[/bold"
97
+ " green]"
97
98
  )
@@ -45,14 +45,12 @@ class BaseInfrastructureProvisioner:
45
45
  # Use shutil.copytree to copy the contents of the template directory
46
46
  shutil.copytree(template_dir, self.working_dir, dirs_exist_ok=True)
47
47
 
48
- print(f"[green]{self.command_name} files copied to: {self.working_dir}[/green]")
48
+ print(f"[grey50]{self.command_name} files copied to: {self.working_dir}[/grey50]")
49
49
 
50
50
  def _run_command(
51
51
  self, command: List[str], env: Optional[Dict[str, str]] = None
52
52
  ) -> Dict[str, str]:
53
53
  """Runs a command and streams its output."""
54
- print(f"\n[bold]Running `{self.command_name}` in {self.working_dir}...[/bold]")
55
-
56
54
  process_env = os.environ.copy()
57
55
  process_env.update(env or {})
58
56
  outputs = {}
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  from pathlib import Path
3
- from typing import Annotated, Optional, Type
3
+ from typing import Annotated, Optional, Type, Union
4
4
 
5
5
  import inquirer
6
6
  import logfire
@@ -33,7 +33,7 @@ from opsmith.utils import (
33
33
  slugify,
34
34
  )
35
35
 
36
- app = typer.Typer(pretty_exceptions_show_locals=True)
36
+ app = typer.Typer(pretty_exceptions_show_locals=False)
37
37
 
38
38
 
39
39
  def _check_external_dependencies():
@@ -52,7 +52,7 @@ def _check_external_dependencies():
52
52
  raise typer.Exit(code=1)
53
53
 
54
54
 
55
- def parse_model_arg(model: str) -> Type[BaseAiModel]:
55
+ def _parse_model_arg(model: Union[str, BaseAiModel]) -> BaseAiModel:
56
56
  """
57
57
  Fetches the class corresponding to the given model name.
58
58
 
@@ -69,6 +69,8 @@ def parse_model_arg(model: str) -> Type[BaseAiModel]:
69
69
  :raises ValueError: If the given model name is not found in the model registry.
70
70
  :raises typer.BadParameter: If the provided model name is unsupported.
71
71
  """
72
+ if isinstance(model, BaseAiModel):
73
+ return model
72
74
  try:
73
75
  return MODEL_REGISTRY.get_model_class(model)()
74
76
  except ValueError:
@@ -77,7 +79,7 @@ def parse_model_arg(model: str) -> Type[BaseAiModel]:
77
79
  )
78
80
 
79
81
 
80
- def api_key_callback(ctx: typer.Context, value: str):
82
+ def _api_key_callback(ctx: typer.Context, value: str):
81
83
  """
82
84
  This function serves as a callback for validating and processing an API key when
83
85
  used in conjunction with a command-line interface. The function checks whether
@@ -111,18 +113,21 @@ def main(
111
113
  model: Annotated[
112
114
  Type[BaseAiModel],
113
115
  typer.Option(
114
- parser=parse_model_arg,
116
+ parser=_parse_model_arg,
115
117
  help="The LLM model to be used for by the AI Agent.",
118
+ prompt="Select the LLM model to be used for by the AI Agent",
116
119
  ),
117
120
  ],
118
121
  api_key: Annotated[
119
122
  str,
120
123
  typer.Option(
121
- callback=api_key_callback,
124
+ callback=_api_key_callback,
122
125
  help=(
123
126
  "The API KEY to be used for by the AI Agent. This is the API key for the specified"
124
127
  " model."
125
128
  ),
129
+ prompt="Enter the API KEY for the specified model",
130
+ hide_input=True,
126
131
  ),
127
132
  ],
128
133
  logfire_token: Optional[str] = typer.Option(
@@ -135,6 +140,7 @@ def main(
135
140
  src_dir: Optional[str] = typer.Option(
136
141
  default=None,
137
142
  help="Source directory to be used by the command. Defaults to current working directory.",
143
+ envvar="OPSMITH_SRC_DIR",
138
144
  ),
139
145
  verbose: bool = typer.Option(
140
146
  False, "--verbose", "-v", help="Enable verbose output for repo map generation."
@@ -227,7 +233,7 @@ def setup(ctx: typer.Context):
227
233
  scan_services = True
228
234
 
229
235
  else:
230
- print("No existing deployment configuration found. Starting analysis...")
236
+ print("No existing deployment configuration found. Starting analysis...\n")
231
237
  app_name_questions = [
232
238
  inquirer.Text("app_name", message="Enter the application name"),
233
239
  ]
@@ -252,7 +258,7 @@ def setup(ctx: typer.Context):
252
258
  selected_provider_value = provider_answers["cloud_provider"]
253
259
 
254
260
  # Get cloud details if new or changed
255
- print(f"Initializing {selected_provider_value} provider...")
261
+ print(f"Initializing {selected_provider_value} provider...\n")
256
262
  provider_class = CLOUD_PROVIDER_REGISTRY.get_provider_class(selected_provider_value)
257
263
  try:
258
264
  cloud_details = provider_class.get_account_details().model_dump(mode="json")
@@ -101,6 +101,7 @@ class ModelRegistry:
101
101
  """Load built-in models"""
102
102
  for model_cls in [
103
103
  OpenAIGPT41,
104
+ OpenAIGPTO3,
104
105
  AnthropicClaudeSonnet37,
105
106
  AnthropicClaudeSonnet4,
106
107
  GoogleGlaGemini25Pro,
@@ -139,13 +140,13 @@ class OpenAIGPT41(BaseAiModel):
139
140
  @classmethod
140
141
  def get_model_settings(cls) -> ModelSettings:
141
142
  """Returns model-specific settings."""
142
- return OpenAIModelSettings(openai_reasoning_effort="high")
143
+ return OpenAIModelSettings()
143
144
 
144
145
 
145
146
  class OpenAIGPTO3(BaseAiModel):
146
147
  @classmethod
147
148
  def name(cls) -> str:
148
- return "gpt-o3"
149
+ return "o3"
149
150
 
150
151
  @classmethod
151
152
  def provider(cls) -> str:
@@ -70,6 +70,8 @@ service described below.
70
70
  Service Details:
71
71
  {service_info_yaml}
72
72
 
73
+ {template_section}
74
+
73
75
  Repository Map:
74
76
  {repo_map_str}
75
77
 
@@ -88,6 +88,9 @@ class RepoMap:
88
88
  self.src_dir = Path(src_dir).resolve()
89
89
  self.git_repo = GitRepo(self.src_dir)
90
90
  self.verbose = verbose
91
+ self.tracked_files: List[Path] = self.git_repo.get_git_tracked_files(
92
+ [str(self.src_dir), ":!**/*test*"]
93
+ )
91
94
 
92
95
  # Initialize tracking variables
93
96
  self.warned_files = set()
@@ -146,9 +149,7 @@ class RepoMap:
146
149
  if self.max_map_tokens <= 0:
147
150
  return None # Repo map is disabled
148
151
 
149
- all_tracked_files_paths = self.git_repo.get_git_tracked_files(
150
- [str(self.src_dir), ":!**/*test*"]
151
- )
152
+ all_tracked_files_paths = self.tracked_files
152
153
  if not all_tracked_files_paths:
153
154
  if self.verbose:
154
155
  typer.echo("RepoMap: No git-tracked files found.", err=True)
@@ -67,12 +67,14 @@ class ServiceDetector:
67
67
  verbose: bool = False,
68
68
  ):
69
69
  self.deployments_path = Path(src_dir).joinpath(settings.deployments_dir)
70
- self.agent_deps = AgentDeps(src_dir=Path(src_dir))
71
70
  self.agent = agent
72
71
  self.repo_map = RepoMap(
73
72
  src_dir=src_dir,
74
73
  verbose=verbose,
75
74
  )
75
+ self.agent_deps = AgentDeps(
76
+ src_dir=Path(src_dir), tracked_files=self.repo_map.tracked_files
77
+ )
76
78
  self.verbose = verbose
77
79
 
78
80
  def detect_services(self, existing_config: Optional[ServiceList] = None) -> ServiceList:
@@ -130,19 +132,29 @@ class ServiceDetector:
130
132
  service_dir_path = self.deployments_path / "docker" / service.name_slug
131
133
  service_dir_path.mkdir(parents=True, exist_ok=True)
132
134
  dockerfile_path_abs = service_dir_path / "Dockerfile"
133
- print(
134
- "\n[bold]Generating Dockerfile for service:"
135
- f" {service.language} ({service.service_type})...[/bold]"
135
+ print(f"\n[bold]Generating Dockerfile for service: {service.name_slug}...[/bold]")
136
+
137
+ template_name = f"{service.language.lower()}_{service.service_type.value.lower()}"
138
+ template_path = Path(__file__).parent / "templates" / "dockerfiles" / template_name
139
+ dockerfile_template = None
140
+ if template_path.exists():
141
+ with open(template_path, "r", encoding="utf-8") as f:
142
+ dockerfile_template = f.read()
143
+ print(f"[green]Using Dockerfile template: {template_name}[/green]")
144
+
145
+ dockerfile_content = self._generate_and_validate_dockerfile(
146
+ service, dockerfile_path_abs, dockerfile_template
136
147
  )
137
148
 
138
- dockerfile_content = self._generate_and_validate_dockerfile(service, dockerfile_path_abs)
139
-
140
149
  with open(dockerfile_path_abs, "w", encoding="utf-8") as f:
141
150
  f.write(dockerfile_content)
142
151
  print(f"[green]Dockerfile saved to: {dockerfile_path_abs}[/green]")
143
152
 
144
153
  def _generate_and_validate_dockerfile(
145
- self, service: ServiceInfo, dockerfile_path_abs: Path
154
+ self,
155
+ service: ServiceInfo,
156
+ dockerfile_path_abs: Path,
157
+ dockerfile_template: Optional[str] = None,
146
158
  ) -> str:
147
159
  """Generates and validates a Dockerfile for a given service."""
148
160
  existing_dockerfile_content = "N/A"
@@ -159,9 +171,21 @@ class ServiceDetector:
159
171
  while attempt < settings.max_dockerfile_gen_attempts:
160
172
  attempt += 1
161
173
  print(f"\n[bold]Attempt {attempt}/{settings.max_dockerfile_gen_attempts}...[/bold]")
174
+
175
+ repo_map_str = self.repo_map.get_repo_map()
176
+ template_section = ""
177
+ if dockerfile_template:
178
+ template_section = (
179
+ "A Dockerfile template is provided below. Use it as a guide.\n"
180
+ "```\n"
181
+ f"{dockerfile_template}\n"
182
+ "```\n"
183
+ )
184
+
162
185
  prompt = DOCKERFILE_GENERATION_PROMPT_TEMPLATE.format(
163
186
  service_info_yaml=service_info_yaml,
164
- repo_map_str=self.repo_map.get_repo_map(),
187
+ template_section=template_section,
188
+ repo_map_str=repo_map_str,
165
189
  existing_dockerfile_content=existing_dockerfile_content,
166
190
  )
167
191
  with WaitingSpinner(text="Waiting for the LLM to generate the Dockerfile"):
@@ -182,10 +206,9 @@ class ServiceDetector:
182
206
  )
183
207
  break
184
208
 
185
- with WaitingSpinner(text="Validating generated Dockerfile"):
186
- is_successful, reason, validation_messages = self._validate_dockerfile(
187
- dockerfile_content
188
- )
209
+ is_successful, reason, validation_messages = self._validate_dockerfile(
210
+ dockerfile_content
211
+ )
189
212
 
190
213
  if is_successful:
191
214
  print(f"[bold green]Dockerfile validation successful: \n {reason}.[/bold green]")
@@ -209,12 +232,11 @@ class ServiceDetector:
209
232
  raise OpsmithException("Dockerfile generation aborted by user.")
210
233
  dockerfile_content = editor_answers["dockerfile"]
211
234
 
212
- with WaitingSpinner(text="Validating generated Dockerfile"):
213
- completed, reason, _ = self._validate_dockerfile(dockerfile_content)
214
- print(
215
- f"Dockerfile validation {'succeeded' if completed else 'failed'} "
216
- f"with reason: \n {reason}."
217
- )
235
+ completed, reason, _ = self._validate_dockerfile(dockerfile_content)
236
+ print(
237
+ f"Dockerfile validation {'succeeded' if completed else 'failed'} "
238
+ f"with reason: \n {reason}."
239
+ )
218
240
 
219
241
  return dockerfile_content
220
242
 
@@ -328,11 +350,12 @@ class ServiceDetector:
328
350
  build_output=build_output_str,
329
351
  run_output=run_output_str,
330
352
  )
331
- validation_response = self.agent.run_sync(
332
- validation_prompt,
333
- output_type=DockerfileValidation,
334
- deps=self.agent_deps,
335
- )
353
+ with WaitingSpinner(text="Waiting for the LLM to validate the Docker build output"):
354
+ validation_response = self.agent.run_sync(
355
+ validation_prompt,
356
+ output_type=DockerfileValidation,
357
+ deps=self.agent_deps,
358
+ )
336
359
  return (
337
360
  validation_response.output.is_successful,
338
361
  validation_response.output.reason,
@@ -0,0 +1,79 @@
1
+ # Multi-stage Docker build for Python Backend API
2
+
3
+ # Build stage - for compiling dependencies and reducing final image size
4
+ FROM PYTHON_SLIM_IMAGE as builder
5
+
6
+ # Set build arguments for customization
7
+ ARG APP_USER=appuser
8
+ ARG APP_DIR=/app
9
+
10
+ # Create application directory
11
+ WORKDIR ${APP_DIR}
12
+
13
+ # Copy dependency files first (for better layer caching)
14
+ # TEMPLATE_INSTRUCTION: Copy all possible dependency files your project might have
15
+
16
+ # Install Python dependencies
17
+ # TEMPLATE_INSTRUCTION: Use the configured dependency manager to install the requirements
18
+ # This list covers common package managers but is not exhaustive - adapt based on project structure
19
+
20
+ # For pip with requirements.txt:
21
+ # RUN pip install --no-cache-dir --user -r requirements.txt
22
+
23
+ # For Poetry:
24
+ # RUN pip install poetry && \
25
+ # poetry config virtualenvs.create false && \
26
+ # poetry install --only=main --no-dev
27
+
28
+ # For PDM:
29
+ # RUN pip install pdm && \
30
+ # pdm install --prod --no-editable
31
+
32
+ # For uv:
33
+ # RUN pip install uv && \
34
+ # uv pip install --system .
35
+
36
+ # For Pipenv:
37
+ # RUN pip install pipenv && \
38
+ # pipenv install --system --deploy
39
+
40
+ # TEMPLATE_INSTRUCTION: Copy the rest of the application code
41
+
42
+ # Production stage - minimal runtime image
43
+ FROM PYTHON_SLIM_IMAGE as runner
44
+
45
+ # Set build arguments
46
+ ARG APP_USER=appuser
47
+ ARG APP_DIR=/app
48
+
49
+ # Set environment variables for Python optimization and security
50
+ ENV PYTHONUNBUFFERED=1 \
51
+ PYTHONDONTWRITEBYTECODE=1 \
52
+ PYTHONHASHSEED=random \
53
+ PIP_NO_CACHE_DIR=1 \
54
+ PIP_DISABLE_PIP_VERSION_CHECK=1
55
+
56
+ # Create non-root user for security
57
+ RUN groupadd -r ${APP_USER} && \
58
+ useradd -r -g ${APP_USER} -d ${APP_DIR} -s /sbin/nologin ${APP_USER}
59
+
60
+ # Create application directory and set permissions
61
+ RUN mkdir -p ${APP_DIR} && chown -R ${APP_USER}:${APP_USER} ${APP_DIR}
62
+
63
+ # Set working directory
64
+ WORKDIR ${APP_DIR}
65
+
66
+ # TEMPLATE_INSTRUCTION: Copy the installed Python packages and binaries from the builder stage
67
+
68
+ # TEMPLATE_INSTRUCTION: Copy the application code from the builder stage and set ownership
69
+
70
+ # TEMPLATE_INSTRUCTION: Add framework-specific build steps if any here
71
+
72
+ # Switch to non-root user
73
+ USER ${APP_USER}
74
+
75
+ # TEMPLATE_INSTRUCTION: Update PATH to include user local binaries and set PYTHONPATH
76
+
77
+ # TEMPLATE_INSTRUCTION: Expose port
78
+
79
+ # TEMPLATE_INSTRUCTION: Choose the appropriate command to startup the API