arclith-cli 0.9.0__tar.gz → 0.11.0__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.
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/PKG-INFO +2 -2
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/README.md +125 -8
- arclith_cli-0.11.0/arclith_cli/__init__.py +1 -0
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/arclith_cli/add_adapter.py +75 -1
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/arclith_cli/capabilities.py +311 -7
- arclith_cli-0.11.0/arclith_cli/core_scaffold.py +258 -0
- arclith_cli-0.11.0/arclith_cli/init_project.py +283 -0
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/arclith_cli/main.py +87 -1
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/arclith_cli/project_paths.py +31 -3
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/arclith_cli/rename.py +23 -2
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/pyproject.toml +2 -2
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/tests/e2e_manual.sh +1 -1
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/tests/test_add_adapter.py +214 -4
- arclith_cli-0.11.0/tests/test_capabilities.py +205 -0
- arclith_cli-0.11.0/tests/test_core_scaffold.py +208 -0
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/tests/test_e2e_scaffold.py +156 -1
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/tests/test_project_paths.py +16 -0
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/tests/test_rename.py +12 -1
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/uv.lock +17 -3
- arclith_cli-0.9.0/arclith_cli/__init__.py +0 -1
- arclith_cli-0.9.0/tests/test_capabilities.py +0 -113
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/.gitignore +0 -0
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/LICENSE +0 -0
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/Makefile +0 -0
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/arclith_cli/adapter_templates.py +0 -0
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/arclith_cli/entity_scanner.py +0 -0
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/arclith_cli/export_config.py +0 -0
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/arclith_cli/scaffold.py +0 -0
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/arclith_cli/updater.py +0 -0
- {arclith_cli-0.9.0 → arclith_cli-0.11.0}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arclith-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: CLI scaffolding tool for arclith — hexagonal architecture framework
|
|
5
5
|
Author-email: Killian KOPP <killiankopp@gmail.com>
|
|
6
6
|
License: Apache License
|
|
@@ -186,7 +186,7 @@ License: Apache License
|
|
|
186
186
|
License-File: LICENSE
|
|
187
187
|
Keywords: arclith,cli,ddd,hexagonal-architecture,scaffold
|
|
188
188
|
Requires-Python: >=3.13
|
|
189
|
-
Requires-Dist: arclith>=0.
|
|
189
|
+
Requires-Dist: arclith>=0.14.0
|
|
190
190
|
Requires-Dist: httpx>=0.27.0
|
|
191
191
|
Requires-Dist: rich>=13.0.0
|
|
192
192
|
Requires-Dist: typer>=0.15.0
|
|
@@ -10,6 +10,25 @@ uv tool install "git+https://github.com/karned-rekipe/arclith.git#subdirectory=c
|
|
|
10
10
|
|
|
11
11
|
## Commandes
|
|
12
12
|
|
|
13
|
+
### `init` — Initialiser un projet minimal
|
|
14
|
+
|
|
15
|
+
Crée un projet Arclith vide de métier, avec le layout canonique `src/<package>/...`, une
|
|
16
|
+
configuration minimale et un `main.py` prêt à recevoir les adapters.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Mode interactif
|
|
20
|
+
arclith-cli init
|
|
21
|
+
|
|
22
|
+
# Mode direct
|
|
23
|
+
arclith-cli init todo-list-service
|
|
24
|
+
arclith-cli init todo-list-service --dir ~/projects
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Cette commande ne crée aucune entité, aucun CRUD et aucun endpoint métier. Elle sert quand on veut
|
|
28
|
+
construire le projet étape par étape avec `add-entity`, `add-usecase`, puis `add-adapter`.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
13
32
|
### `new` — Créer un projet
|
|
14
33
|
|
|
15
34
|
Scaffold un nouveau projet arclith depuis le template officiel `_sample`.
|
|
@@ -34,6 +53,71 @@ Le projet généré utilise un layout `src/<package>/...` pour le code applicati
|
|
|
34
53
|
|
|
35
54
|
---
|
|
36
55
|
|
|
56
|
+
### `add-entity` — Ajouter une entité métier
|
|
57
|
+
|
|
58
|
+
Crée uniquement le fichier minimal d'une entité dans `src/<package>/domain/models/`.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cd my-recipe-service
|
|
62
|
+
arclith-cli add-entity ShoppingItem
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Fichier généré :
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
src/<package>/domain/models/shopping_item.py
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
La commande ne génère aucun CRUD, aucun port repository, aucun adapter et aucun endpoint. Elle pose seulement le point d'ancrage du modèle métier ; le développeur complète ensuite les champs et invariants de l'entité.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### `add-usecase` — Ajouter un cas d'usage
|
|
76
|
+
|
|
77
|
+
Crée le port inbound minimal dans `src/<package>/domain/ports/inbound/`, puis le fichier minimal du
|
|
78
|
+
cas d'usage dans `src/<package>/application/use_cases/`.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cd my-recipe-service
|
|
82
|
+
arclith-cli add-usecase PlanShoppingList
|
|
83
|
+
arclith-cli add-usecase find-by-name
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Fichier généré :
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
src/<package>/domain/ports/inbound/plan_shopping_list.py
|
|
90
|
+
src/<package>/application/use_cases/plan_shopping_list.py
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Le nom peut être fourni en PascalCase, snake_case ou kebab-case. Le suffixe `UseCase` est normalisé : `PlanShoppingListUseCase` et `plan-shopping-list-use-case` génèrent tous les deux `PlanShoppingListUseCase`.
|
|
94
|
+
|
|
95
|
+
Comme `add-entity`, cette commande ne câble pas FastAPI, FastMCP, LangGraph, un repository ou un service. Les adapters se branchent ensuite explicitement avec `add-adapter` et devraient dépendre du port inbound généré.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
### `add-planner` — Ajouter un planner applicatif
|
|
100
|
+
|
|
101
|
+
Crée uniquement le fichier minimal d'un planner dans `src/<package>/application/planners/`.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
cd my-recipe-service
|
|
105
|
+
arclith-cli add-planner IngredientIntent
|
|
106
|
+
arclith-cli add-planner command-router
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Fichier généré :
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
src/<package>/application/planners/ingredient_intent.py
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Le planner est le composant applicatif qui transforme une demande naturelle en commande ou DTO
|
|
116
|
+
structuré. Il ne remplace pas LangGraph : LangGraph orchestre les nœuds, tandis que le planner porte
|
|
117
|
+
la traduction d'intention. Le fichier généré reste volontairement vide de logique métier.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
37
121
|
### `add-adapter` — Ajouter un adapter
|
|
38
122
|
|
|
39
123
|
Wizard interactif à lancer **depuis la racine du projet cible**. Scaffold le code Python et/ou les fichiers de configuration pour un nouvel adapter. Par défaut, la capacité cible est `repository`.
|
|
@@ -49,29 +133,39 @@ Mode direct, utile pour CI, scripts de migration ou commandes reproductibles :
|
|
|
49
133
|
arclith-cli add-adapter --adapter mongodb --entity Recipe --db-name my_recipe_service --yes
|
|
50
134
|
arclith-cli add-adapter --adapter duckdb --all-entities --path data/ --no-activate --yes
|
|
51
135
|
arclith-cli add-adapter --adapter mariadb --entity Recipe --param database=my_recipe_service --param user=app --yes
|
|
136
|
+
arclith-cli add-adapter --capability api --adapter fastapi --param port=8080 --yes
|
|
137
|
+
arclith-cli add-adapter --capability mcp --adapter fastmcp --param port=8081 --yes
|
|
138
|
+
arclith-cli add-adapter --capability llm --adapter lmstudio --param model_name=qwen/qwen3.5-9b --yes
|
|
52
139
|
arclith-cli add-adapter --capability agent --adapter langgraph --param graph_name=recipe_agent --yes
|
|
53
140
|
arclith-cli add-adapter --capability observability --adapter langsmith
|
|
141
|
+
arclith-cli add-adapter --capability observability --adapter opentelemetry --param service_name=my_recipe_service --yes
|
|
54
142
|
arclith-cli add-adapter --capability repository --adapter memory --entity Recipe --yes
|
|
55
143
|
```
|
|
56
144
|
|
|
57
145
|
**Étapes du wizard :**
|
|
58
146
|
|
|
59
|
-
1. **Type d'adapter** — selon la capacité : `memory` · `mongodb` · `duckdb` · `mariadb` · `langgraph` · `langsmith`
|
|
60
|
-
2. **Entité(s) cible(s)** — détectées automatiquement pour les adapters entity-scoped ; ignorées pour `agent/langgraph` et
|
|
147
|
+
1. **Type d'adapter** — selon la capacité : `memory` · `mongodb` · `duckdb` · `mariadb` · `fastapi` · `fastmcp` · `lmstudio` · `openai` · `anthropic` · `langgraph` · `langsmith` · `opentelemetry`
|
|
148
|
+
2. **Entité(s) cible(s)** — détectées automatiquement pour les adapters entity-scoped ; ignorées pour les transports globaux, `llm/*`, `agent/langgraph` et les adapters d'observability
|
|
61
149
|
3. **Paramètres** — questions spécifiques à l'adapter :
|
|
62
150
|
- `mongodb` → `db_name`, `multitenant`
|
|
63
151
|
- `duckdb` → `path`
|
|
64
152
|
- `mariadb` → `host`, `port`, `database`, `user`, `driver`, `table_prefix`
|
|
153
|
+
- `fastapi` → `host`, `port`, `reload`
|
|
154
|
+
- `fastmcp` → `host`, `port`
|
|
155
|
+
- `lmstudio` → `model_name`, `base_url`, `api_key`
|
|
156
|
+
- `openai` → `model_name`, `base_url`, `OPENAI_API_KEY`
|
|
157
|
+
- `anthropic` → `model_name`, `ANTHROPIC_API_KEY`
|
|
65
158
|
- `langgraph` → `graph_name`
|
|
66
159
|
- `langsmith` → `tracing`, `project`, `endpoint`, `LANGSMITH_API_KEY`
|
|
160
|
+
- `opentelemetry` → `service_name`, `endpoint`, `protocol`, `traces`, `metrics`, `instrument_fastapi`
|
|
67
161
|
- `memory` → aucun paramètre
|
|
68
|
-
4. **Activation** — met à jour `config/adapters/adapters.yaml` pour les capacités
|
|
162
|
+
4. **Activation** — met à jour `config/adapters/adapters.yaml` pour les capacités activables (`repository: <adapter>` ou `observability.enabled: [<adapter>, ...]`) ; `api/fastapi`, `mcp/fastmcp`, `llm/*` et `agent/langgraph` sont exposés par leurs fichiers de configuration scopés
|
|
69
163
|
5. **Récapitulatif** — liste des fichiers créés ou remplacés avant confirmation
|
|
70
164
|
|
|
71
165
|
| Option | Défaut | Description |
|
|
72
166
|
|--------|--------|-------------|
|
|
73
|
-
| `--capability` | `repository` | Capacité cible du catalogue standardisé (`repository`, `agent`, `observability`) |
|
|
74
|
-
| `--adapter` / `-a` | interactif | Adapter du catalogue : `memory`, `mongodb`, `duckdb`, `mariadb`, `langgraph`, `langsmith` |
|
|
167
|
+
| `--capability` | `repository` | Capacité cible du catalogue standardisé (`repository`, `api`, `mcp`, `llm`, `agent`, `observability`) |
|
|
168
|
+
| `--adapter` / `-a` | interactif | Adapter du catalogue : `memory`, `mongodb`, `duckdb`, `mariadb`, `fastapi`, `fastmcp`, `lmstudio`, `openai`, `anthropic`, `langgraph`, `langsmith`, `opentelemetry` |
|
|
75
169
|
| `--entity` / `-e` | auto si une seule entité | Entité cible, liste séparée par virgule acceptée |
|
|
76
170
|
| `--all-entities` | `false` | Génère l'adapter pour toutes les entités détectées |
|
|
77
171
|
| `--activate/--no-activate` | `--activate` | Met à jour `config/adapters/adapters.yaml` quand la capacité expose une clé d'activation |
|
|
@@ -97,20 +191,38 @@ src/<package>/infrastructure/containers/<entity>_container.py # RepositoryRegis
|
|
|
97
191
|
|
|
98
192
|
```bash
|
|
99
193
|
uv add "arclith[langgraph]"
|
|
194
|
+
arclith-cli add-adapter --capability llm --adapter lmstudio --param model_name=qwen/qwen3.5-9b --yes
|
|
100
195
|
arclith-cli add-adapter --capability agent --adapter langgraph
|
|
101
196
|
arclith-cli add-adapter --capability observability --adapter langsmith
|
|
102
197
|
uv run langgraph dev --no-browser --allow-blocking --port 2024
|
|
103
198
|
```
|
|
104
199
|
|
|
200
|
+
L'adapter `llm/lmstudio` génère `config/adapters/outbound/lm.yaml`, chargé dans
|
|
201
|
+
`AppConfig.adapters.lm`. Les adapters `llm/openai` et `llm/anthropic` génèrent aussi un mapping
|
|
202
|
+
`config/secrets.yaml` vers `OPENAI_API_KEY` ou `ANTHROPIC_API_KEY`.
|
|
203
|
+
|
|
105
204
|
L'adapter `agent/langgraph` génère `langgraph.json`, `config/adapters/inbound/langgraph.yaml` et
|
|
106
205
|
`src/<package>/adapters/inbound/langgraph/agent.py`. Le projet ne modifie ensuite que ce fichier pour
|
|
107
206
|
son agent. Comme `fastapi` et `fastmcp`, LangGraph est configuré par son nom produit dans
|
|
108
207
|
`AppConfig.langgraph`, sans `adapters.agent`. L'adapter `observability/langsmith` génère
|
|
109
|
-
`config/adapters/outbound/langsmith.yaml`, met
|
|
208
|
+
`config/adapters/outbound/langsmith.yaml`, l'ajoute à `observability.enabled`, met
|
|
110
209
|
à jour `.env` et ajoute `.env` au `.gitignore` si besoin. LangSmith Studio devient l'endroit standard
|
|
111
210
|
pour tester les agents. Une `LANGSMITH_API_KEY` déjà présente est conservée si aucune nouvelle valeur
|
|
112
211
|
n'est fournie.
|
|
113
212
|
|
|
213
|
+
**OpenTelemetry :**
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
uv add "arclith[opentelemetry]"
|
|
217
|
+
arclith-cli add-adapter --capability observability --adapter opentelemetry --param service_name=my-recipe-service --yes
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
L'adapter `observability/opentelemetry` génère `config/adapters/outbound/opentelemetry.yaml`, met à
|
|
221
|
+
jour `.env`, l'ajoute à `observability.enabled` et branche l'instrumentation FastAPI quand
|
|
222
|
+
`Arclith.fastapi()` construit l'application. Il peut être activé en même temps que LangSmith.
|
|
223
|
+
Le fichier `opentelemetry.yaml` ne porte pas de flag `enabled`: l'activation se fait uniquement dans
|
|
224
|
+
`observability.enabled`.
|
|
225
|
+
|
|
114
226
|
Parcours complet avec entité, API, LangGraph, LangSmith et LM Studio:
|
|
115
227
|
[`docs/agent-quickstart.md`](../docs/agent-quickstart.md).
|
|
116
228
|
|
|
@@ -178,12 +290,14 @@ config/
|
|
|
178
290
|
soft_delete.yaml # soft_delete: { retention_days }
|
|
179
291
|
secrets.yaml # secrets: { resolver, mappings, vault, yaml }
|
|
180
292
|
adapters/
|
|
181
|
-
adapters.yaml # adapters: { logger, repository }
|
|
293
|
+
adapters.yaml # adapters: { logger, repository, observability.enabled }
|
|
182
294
|
outbound/
|
|
183
295
|
mongodb.yaml # adapters.mongodb: { db_name, multitenant }
|
|
184
296
|
duckdb.yaml # adapters.duckdb: { path, multitenant }
|
|
185
297
|
mariadb.yaml # adapters.mariadb: { host, port, database, user, ... }
|
|
298
|
+
lm.yaml # adapters.lm: { provider, model_name, api_key, base_url }
|
|
186
299
|
langsmith.yaml # adapters.langsmith: { tracing, project, endpoint, ... }
|
|
300
|
+
opentelemetry.yaml # adapters.opentelemetry: { endpoint, protocol, traces, metrics, ... }
|
|
187
301
|
inbound/
|
|
188
302
|
fastapi.yaml # api: { host, port, reload }
|
|
189
303
|
fastmcp.yaml # mcp: { host, port }
|
|
@@ -199,7 +313,10 @@ Pour changer l'adapter actif sans passer par le wizard :
|
|
|
199
313
|
```yaml
|
|
200
314
|
# config/adapters/adapters.yaml
|
|
201
315
|
repository: duckdb # memory | mongodb | duckdb | mariadb
|
|
202
|
-
observability:
|
|
316
|
+
observability:
|
|
317
|
+
enabled:
|
|
318
|
+
- langsmith
|
|
319
|
+
- opentelemetry
|
|
203
320
|
```
|
|
204
321
|
|
|
205
322
|
Pour MariaDB, ne committez pas le mot de passe. Mappez `adapters.mariadb.password` ou `adapters.mariadb.url` via `config/secrets.yaml`, un resolver `env` ou Vault.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.11.0"
|
|
@@ -5,6 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
import typer
|
|
8
|
+
import yaml
|
|
8
9
|
from rich.console import Console
|
|
9
10
|
from rich.panel import Panel
|
|
10
11
|
from rich.prompt import Confirm, Prompt
|
|
@@ -20,6 +21,7 @@ from .capabilities import (
|
|
|
20
21
|
AdapterSpec,
|
|
21
22
|
CapabilitySpec,
|
|
22
23
|
ParameterSpec,
|
|
24
|
+
SecretMappingSpec,
|
|
23
25
|
capability_names,
|
|
24
26
|
get_capability,
|
|
25
27
|
)
|
|
@@ -384,7 +386,7 @@ def _show_recap(
|
|
|
384
386
|
cfg_path = project_dir / "config" / "adapters" / "adapters.yaml"
|
|
385
387
|
table.add_row(
|
|
386
388
|
str(cfg_path.relative_to(project_dir)),
|
|
387
|
-
f"[cyan]mis à jour ({capability
|
|
389
|
+
f"[cyan]mis à jour ({_activation_config_label(capability)})[/cyan]",
|
|
388
390
|
)
|
|
389
391
|
|
|
390
392
|
console.print()
|
|
@@ -409,6 +411,10 @@ def _list_generated_files(
|
|
|
409
411
|
gitignore = project_dir / ".gitignore"
|
|
410
412
|
files.append((gitignore, "mis à jour" if gitignore.exists() else "créé"))
|
|
411
413
|
|
|
414
|
+
if adapter.has_secret_mappings():
|
|
415
|
+
secrets_file = project_dir / "config" / "secrets.yaml"
|
|
416
|
+
files.append((secrets_file, "mis à jour" if secrets_file.exists() else "créé"))
|
|
417
|
+
|
|
412
418
|
template_vars = _file_template_vars(project_dir, paths, adapter, params={})
|
|
413
419
|
for file_template in adapter.file_templates:
|
|
414
420
|
path = project_dir / render(file_template.path, template_vars)
|
|
@@ -430,6 +436,12 @@ def _list_generated_files(
|
|
|
430
436
|
return files
|
|
431
437
|
|
|
432
438
|
|
|
439
|
+
def _activation_config_label(capability: CapabilitySpec) -> str:
|
|
440
|
+
if capability.name == "observability":
|
|
441
|
+
return "observability.enabled"
|
|
442
|
+
return capability.activation_config_key or ""
|
|
443
|
+
|
|
444
|
+
|
|
433
445
|
# ── Step 5 : generate ─────────────────────────────────────────────────────────
|
|
434
446
|
|
|
435
447
|
def _generate(
|
|
@@ -459,6 +471,11 @@ def _generate(
|
|
|
459
471
|
_ensure_env_is_ignored(project_dir)
|
|
460
472
|
console.print(f"[green]✓[/green] {env_path.relative_to(project_dir)}")
|
|
461
473
|
|
|
474
|
+
if adapter.has_secret_mappings():
|
|
475
|
+
secrets_path = project_dir / "config" / "secrets.yaml"
|
|
476
|
+
_merge_secrets_file(secrets_path, adapter.secret_mappings)
|
|
477
|
+
console.print(f"[green]✓[/green] {secrets_path.relative_to(project_dir)}")
|
|
478
|
+
|
|
462
479
|
for file_template in adapter.file_templates:
|
|
463
480
|
generated_path = project_dir / render(file_template.path, params)
|
|
464
481
|
generated_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -538,6 +555,10 @@ def _file_template_vars(
|
|
|
538
555
|
def _update_active_capability(project_dir: Path, capability: CapabilitySpec, adapter: AdapterSpec) -> None:
|
|
539
556
|
if capability.activation_config_key is None:
|
|
540
557
|
return
|
|
558
|
+
if capability.name == "observability":
|
|
559
|
+
_enable_observability_adapter(project_dir, adapter)
|
|
560
|
+
return
|
|
561
|
+
|
|
541
562
|
cfg = project_dir / "config" / "adapters" / "adapters.yaml"
|
|
542
563
|
key = capability.activation_config_key
|
|
543
564
|
escaped_key = re.escape(key)
|
|
@@ -554,6 +575,29 @@ def _update_active_capability(project_dir: Path, capability: CapabilitySpec, ada
|
|
|
554
575
|
console.print(f"[cyan]↺[/cyan] config/adapters/adapters.yaml → {key}: {adapter.name}")
|
|
555
576
|
|
|
556
577
|
|
|
578
|
+
def _enable_observability_adapter(project_dir: Path, adapter: AdapterSpec) -> None:
|
|
579
|
+
cfg = project_dir / "config" / "adapters" / "adapters.yaml"
|
|
580
|
+
data = _read_yaml_mapping(cfg)
|
|
581
|
+
existing = data.get("observability")
|
|
582
|
+
if isinstance(existing, dict) and isinstance(existing.get("enabled"), list):
|
|
583
|
+
enabled = []
|
|
584
|
+
for name in existing["enabled"]:
|
|
585
|
+
if isinstance(name, str) and name not in enabled:
|
|
586
|
+
enabled.append(name)
|
|
587
|
+
else:
|
|
588
|
+
enabled = []
|
|
589
|
+
|
|
590
|
+
if adapter.name not in enabled:
|
|
591
|
+
enabled.append(adapter.name)
|
|
592
|
+
|
|
593
|
+
data["observability"] = {"enabled": enabled}
|
|
594
|
+
cfg.parent.mkdir(parents=True, exist_ok=True)
|
|
595
|
+
cfg.write_text(yaml.safe_dump(data, sort_keys=False, allow_unicode=True), encoding="utf-8")
|
|
596
|
+
console.print(
|
|
597
|
+
f"[cyan]↺[/cyan] config/adapters/adapters.yaml → observability.enabled += {adapter.name}"
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
|
|
557
601
|
def _parse_env_template(rendered: str) -> dict[str, str]:
|
|
558
602
|
values: dict[str, str] = {}
|
|
559
603
|
for line in rendered.splitlines():
|
|
@@ -595,6 +639,36 @@ def _merge_env_file(env_path: Path, updates: dict[str, str]) -> None:
|
|
|
595
639
|
env_path.write_text("\n".join(merged_lines).rstrip("\n") + "\n", encoding="utf-8")
|
|
596
640
|
|
|
597
641
|
|
|
642
|
+
def _merge_secrets_file(secrets_path: Path, mappings: tuple[SecretMappingSpec, ...]) -> None:
|
|
643
|
+
secrets_path.parent.mkdir(parents=True, exist_ok=True)
|
|
644
|
+
data = _read_yaml_mapping(secrets_path)
|
|
645
|
+
resolver = data.get("resolver")
|
|
646
|
+
if not isinstance(resolver, str) or not resolver.strip():
|
|
647
|
+
data["resolver"] = "env"
|
|
648
|
+
|
|
649
|
+
existing_mappings = data.get("mappings")
|
|
650
|
+
if not isinstance(existing_mappings, dict):
|
|
651
|
+
existing_mappings = {}
|
|
652
|
+
|
|
653
|
+
merged_mappings = dict(existing_mappings)
|
|
654
|
+
for mapping in mappings:
|
|
655
|
+
merged_mappings[mapping.field_path] = mapping.secret_key
|
|
656
|
+
data["mappings"] = merged_mappings
|
|
657
|
+
|
|
658
|
+
rendered = yaml.safe_dump(data, sort_keys=False, allow_unicode=True)
|
|
659
|
+
secrets_path.write_text(rendered, encoding="utf-8")
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _read_yaml_mapping(path: Path) -> dict[str, Any]:
|
|
663
|
+
if not path.exists():
|
|
664
|
+
return {}
|
|
665
|
+
|
|
666
|
+
loaded = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
667
|
+
if isinstance(loaded, dict):
|
|
668
|
+
return dict(loaded)
|
|
669
|
+
return {}
|
|
670
|
+
|
|
671
|
+
|
|
598
672
|
def _ensure_env_is_ignored(project_dir: Path) -> None:
|
|
599
673
|
gitignore = project_dir / ".gitignore"
|
|
600
674
|
if gitignore.exists():
|