arclith-cli 0.10.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.10.0 → arclith_cli-0.11.0}/PKG-INFO +2 -2
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/README.md +70 -12
- arclith_cli-0.11.0/arclith_cli/__init__.py +1 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/arclith_cli/add_adapter.py +75 -1
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/arclith_cli/capabilities.py +155 -17
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/arclith_cli/core_scaffold.py +95 -2
- arclith_cli-0.11.0/arclith_cli/init_project.py +283 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/arclith_cli/main.py +46 -2
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/arclith_cli/project_paths.py +8 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/pyproject.toml +2 -2
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/tests/test_add_adapter.py +105 -8
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/tests/test_capabilities.py +38 -5
- arclith_cli-0.11.0/tests/test_core_scaffold.py +208 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/tests/test_e2e_scaffold.py +101 -2
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/uv.lock +6 -2
- arclith_cli-0.10.0/arclith_cli/__init__.py +0 -1
- arclith_cli-0.10.0/tests/test_core_scaffold.py +0 -91
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/.gitignore +0 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/LICENSE +0 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/Makefile +0 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/arclith_cli/adapter_templates.py +0 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/arclith_cli/entity_scanner.py +0 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/arclith_cli/export_config.py +0 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/arclith_cli/rename.py +0 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/arclith_cli/scaffold.py +0 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/arclith_cli/updater.py +0 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/tests/__init__.py +0 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/tests/e2e_manual.sh +0 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/tests/test_project_paths.py +0 -0
- {arclith_cli-0.10.0 → arclith_cli-0.11.0}/tests/test_rename.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`.
|
|
@@ -55,7 +74,8 @@ La commande ne génère aucun CRUD, aucun port repository, aucun adapter et aucu
|
|
|
55
74
|
|
|
56
75
|
### `add-usecase` — Ajouter un cas d'usage
|
|
57
76
|
|
|
58
|
-
Crée
|
|
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/`.
|
|
59
79
|
|
|
60
80
|
```bash
|
|
61
81
|
cd my-recipe-service
|
|
@@ -66,12 +86,35 @@ arclith-cli add-usecase find-by-name
|
|
|
66
86
|
Fichier généré :
|
|
67
87
|
|
|
68
88
|
```text
|
|
89
|
+
src/<package>/domain/ports/inbound/plan_shopping_list.py
|
|
69
90
|
src/<package>/application/use_cases/plan_shopping_list.py
|
|
70
91
|
```
|
|
71
92
|
|
|
72
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`.
|
|
73
94
|
|
|
74
|
-
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
|
|
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.
|
|
75
118
|
|
|
76
119
|
---
|
|
77
120
|
|
|
@@ -92,6 +135,7 @@ arclith-cli add-adapter --adapter duckdb --all-entities --path data/ --no-activa
|
|
|
92
135
|
arclith-cli add-adapter --adapter mariadb --entity Recipe --param database=my_recipe_service --param user=app --yes
|
|
93
136
|
arclith-cli add-adapter --capability api --adapter fastapi --param port=8080 --yes
|
|
94
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
|
|
95
139
|
arclith-cli add-adapter --capability agent --adapter langgraph --param graph_name=recipe_agent --yes
|
|
96
140
|
arclith-cli add-adapter --capability observability --adapter langsmith
|
|
97
141
|
arclith-cli add-adapter --capability observability --adapter opentelemetry --param service_name=my_recipe_service --yes
|
|
@@ -100,25 +144,28 @@ arclith-cli add-adapter --capability repository --adapter memory --entity Recipe
|
|
|
100
144
|
|
|
101
145
|
**Étapes du wizard :**
|
|
102
146
|
|
|
103
|
-
1. **Type d'adapter** — selon la capacité : `memory` · `mongodb` · `duckdb` · `mariadb` · `fastapi` · `fastmcp` · `langgraph` · `langsmith` · `opentelemetry`
|
|
104
|
-
2. **Entité(s) cible(s)** — détectées automatiquement pour les adapters entity-scoped ; ignorées pour les transports globaux, `agent/langgraph` et les adapters d'observability
|
|
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
|
|
105
149
|
3. **Paramètres** — questions spécifiques à l'adapter :
|
|
106
150
|
- `mongodb` → `db_name`, `multitenant`
|
|
107
151
|
- `duckdb` → `path`
|
|
108
152
|
- `mariadb` → `host`, `port`, `database`, `user`, `driver`, `table_prefix`
|
|
109
153
|
- `fastapi` → `host`, `port`, `reload`
|
|
110
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`
|
|
111
158
|
- `langgraph` → `graph_name`
|
|
112
159
|
- `langsmith` → `tracing`, `project`, `endpoint`, `LANGSMITH_API_KEY`
|
|
113
160
|
- `opentelemetry` → `service_name`, `endpoint`, `protocol`, `traces`, `metrics`, `instrument_fastapi`
|
|
114
161
|
- `memory` → aucun paramètre
|
|
115
|
-
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
|
|
116
163
|
5. **Récapitulatif** — liste des fichiers créés ou remplacés avant confirmation
|
|
117
164
|
|
|
118
165
|
| Option | Défaut | Description |
|
|
119
166
|
|--------|--------|-------------|
|
|
120
|
-
| `--capability` | `repository` | Capacité cible du catalogue standardisé (`repository`, `api`, `mcp`, `agent`, `observability`) |
|
|
121
|
-
| `--adapter` / `-a` | interactif | Adapter du catalogue : `memory`, `mongodb`, `duckdb`, `mariadb`, `fastapi`, `fastmcp`, `langgraph`, `langsmith`, `opentelemetry` |
|
|
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` |
|
|
122
169
|
| `--entity` / `-e` | auto si une seule entité | Entité cible, liste séparée par virgule acceptée |
|
|
123
170
|
| `--all-entities` | `false` | Génère l'adapter pour toutes les entités détectées |
|
|
124
171
|
| `--activate/--no-activate` | `--activate` | Met à jour `config/adapters/adapters.yaml` quand la capacité expose une clé d'activation |
|
|
@@ -144,16 +191,21 @@ src/<package>/infrastructure/containers/<entity>_container.py # RepositoryRegis
|
|
|
144
191
|
|
|
145
192
|
```bash
|
|
146
193
|
uv add "arclith[langgraph]"
|
|
194
|
+
arclith-cli add-adapter --capability llm --adapter lmstudio --param model_name=qwen/qwen3.5-9b --yes
|
|
147
195
|
arclith-cli add-adapter --capability agent --adapter langgraph
|
|
148
196
|
arclith-cli add-adapter --capability observability --adapter langsmith
|
|
149
197
|
uv run langgraph dev --no-browser --allow-blocking --port 2024
|
|
150
198
|
```
|
|
151
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
|
+
|
|
152
204
|
L'adapter `agent/langgraph` génère `langgraph.json`, `config/adapters/inbound/langgraph.yaml` et
|
|
153
205
|
`src/<package>/adapters/inbound/langgraph/agent.py`. Le projet ne modifie ensuite que ce fichier pour
|
|
154
206
|
son agent. Comme `fastapi` et `fastmcp`, LangGraph est configuré par son nom produit dans
|
|
155
207
|
`AppConfig.langgraph`, sans `adapters.agent`. L'adapter `observability/langsmith` génère
|
|
156
|
-
`config/adapters/outbound/langsmith.yaml`, met
|
|
208
|
+
`config/adapters/outbound/langsmith.yaml`, l'ajoute à `observability.enabled`, met
|
|
157
209
|
à jour `.env` et ajoute `.env` au `.gitignore` si besoin. LangSmith Studio devient l'endroit standard
|
|
158
210
|
pour tester les agents. Une `LANGSMITH_API_KEY` déjà présente est conservée si aucune nouvelle valeur
|
|
159
211
|
n'est fournie.
|
|
@@ -166,8 +218,10 @@ arclith-cli add-adapter --capability observability --adapter opentelemetry --par
|
|
|
166
218
|
```
|
|
167
219
|
|
|
168
220
|
L'adapter `observability/opentelemetry` génère `config/adapters/outbound/opentelemetry.yaml`, met à
|
|
169
|
-
jour `.env`,
|
|
170
|
-
`Arclith.fastapi()` construit l'application.
|
|
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`.
|
|
171
225
|
|
|
172
226
|
Parcours complet avec entité, API, LangGraph, LangSmith et LM Studio:
|
|
173
227
|
[`docs/agent-quickstart.md`](../docs/agent-quickstart.md).
|
|
@@ -236,11 +290,12 @@ config/
|
|
|
236
290
|
soft_delete.yaml # soft_delete: { retention_days }
|
|
237
291
|
secrets.yaml # secrets: { resolver, mappings, vault, yaml }
|
|
238
292
|
adapters/
|
|
239
|
-
adapters.yaml # adapters: { logger, repository }
|
|
293
|
+
adapters.yaml # adapters: { logger, repository, observability.enabled }
|
|
240
294
|
outbound/
|
|
241
295
|
mongodb.yaml # adapters.mongodb: { db_name, multitenant }
|
|
242
296
|
duckdb.yaml # adapters.duckdb: { path, multitenant }
|
|
243
297
|
mariadb.yaml # adapters.mariadb: { host, port, database, user, ... }
|
|
298
|
+
lm.yaml # adapters.lm: { provider, model_name, api_key, base_url }
|
|
244
299
|
langsmith.yaml # adapters.langsmith: { tracing, project, endpoint, ... }
|
|
245
300
|
opentelemetry.yaml # adapters.opentelemetry: { endpoint, protocol, traces, metrics, ... }
|
|
246
301
|
inbound/
|
|
@@ -258,7 +313,10 @@ Pour changer l'adapter actif sans passer par le wizard :
|
|
|
258
313
|
```yaml
|
|
259
314
|
# config/adapters/adapters.yaml
|
|
260
315
|
repository: duckdb # memory | mongodb | duckdb | mariadb
|
|
261
|
-
observability:
|
|
316
|
+
observability:
|
|
317
|
+
enabled:
|
|
318
|
+
- langsmith
|
|
319
|
+
- opentelemetry
|
|
262
320
|
```
|
|
263
321
|
|
|
264
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():
|
|
@@ -38,6 +38,18 @@ class FileTemplateSpec:
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class SecretMappingSpec:
|
|
43
|
+
field_path: str
|
|
44
|
+
secret_key: str
|
|
45
|
+
|
|
46
|
+
def to_dict(self) -> dict[str, str]:
|
|
47
|
+
return {
|
|
48
|
+
"field_path": self.field_path,
|
|
49
|
+
"secret_key": self.secret_key,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
41
53
|
@dataclass(frozen=True)
|
|
42
54
|
class AdapterSpec:
|
|
43
55
|
name: str
|
|
@@ -49,6 +61,7 @@ class AdapterSpec:
|
|
|
49
61
|
env_path: str | None = None
|
|
50
62
|
env_template: str = ""
|
|
51
63
|
file_templates: tuple[FileTemplateSpec, ...] = ()
|
|
64
|
+
secret_mappings: tuple[SecretMappingSpec, ...] = ()
|
|
52
65
|
parameters: tuple[ParameterSpec, ...] = ()
|
|
53
66
|
entity_scoped: bool = True
|
|
54
67
|
|
|
@@ -61,6 +74,9 @@ class AdapterSpec:
|
|
|
61
74
|
def has_file_templates(self) -> bool:
|
|
62
75
|
return bool(self.file_templates)
|
|
63
76
|
|
|
77
|
+
def has_secret_mappings(self) -> bool:
|
|
78
|
+
return bool(self.secret_mappings)
|
|
79
|
+
|
|
64
80
|
def to_dict(self) -> dict[str, Any]:
|
|
65
81
|
return {
|
|
66
82
|
"name": self.name,
|
|
@@ -70,6 +86,7 @@ class AdapterSpec:
|
|
|
70
86
|
"config_path": self.config_path,
|
|
71
87
|
"env_path": self.env_path,
|
|
72
88
|
"file_templates": [file_template.to_dict() for file_template in self.file_templates],
|
|
89
|
+
"secret_mappings": [secret_mapping.to_dict() for secret_mapping in self.secret_mappings],
|
|
73
90
|
"parameters": [parameter.to_dict() for parameter in self.parameters],
|
|
74
91
|
"entity_scoped": self.entity_scoped,
|
|
75
92
|
}
|
|
@@ -106,14 +123,14 @@ class CapabilitySpec:
|
|
|
106
123
|
REPOSITORY_CAPABILITY = CapabilitySpec(
|
|
107
124
|
name="repository",
|
|
108
125
|
layer="outbound",
|
|
109
|
-
description="Persistance des
|
|
126
|
+
description="Persistance des entités métier derrière un port repository.",
|
|
110
127
|
activation_config_key="repository",
|
|
111
128
|
adapters=(
|
|
112
129
|
AdapterSpec(
|
|
113
130
|
name="memory",
|
|
114
131
|
capability="repository",
|
|
115
132
|
layer="outbound",
|
|
116
|
-
description="Stockage volatile en
|
|
133
|
+
description="Stockage volatile en mémoire pour dev, tests et smoke locaux.",
|
|
117
134
|
),
|
|
118
135
|
AdapterSpec(
|
|
119
136
|
name="mongodb",
|
|
@@ -144,7 +161,7 @@ db_name: {db_name} # uri -> secrets.yaml ou Vault (fallback single-tenant)
|
|
|
144
161
|
name="duckdb",
|
|
145
162
|
capability="repository",
|
|
146
163
|
layer="outbound",
|
|
147
|
-
description="Repository fichier local pour SQL analytique et
|
|
164
|
+
description="Repository fichier local pour SQL analytique et démos sans serveur.",
|
|
148
165
|
config_path="config/adapters/outbound/duckdb.yaml",
|
|
149
166
|
config_template="""\
|
|
150
167
|
multitenant: false
|
|
@@ -227,7 +244,7 @@ API_CAPABILITY = CapabilitySpec(
|
|
|
227
244
|
name="fastapi",
|
|
228
245
|
capability="api",
|
|
229
246
|
layer="inbound",
|
|
230
|
-
description="Application FastAPI
|
|
247
|
+
description="Application FastAPI configurée par Arclith.fastapi().",
|
|
231
248
|
config_path="config/adapters/inbound/fastapi.yaml",
|
|
232
249
|
config_template="""\
|
|
233
250
|
host: {host}
|
|
@@ -269,7 +286,7 @@ MCP_CAPABILITY = CapabilitySpec(
|
|
|
269
286
|
name="fastmcp",
|
|
270
287
|
capability="mcp",
|
|
271
288
|
layer="inbound",
|
|
272
|
-
description="Serveur FastMCP
|
|
289
|
+
description="Serveur FastMCP configuré par Arclith.fastmcp() et les runners MCP.",
|
|
273
290
|
config_path="config/adapters/inbound/fastmcp.yaml",
|
|
274
291
|
config_template="""\
|
|
275
292
|
host: {host}
|
|
@@ -294,17 +311,144 @@ port: {port}
|
|
|
294
311
|
),
|
|
295
312
|
)
|
|
296
313
|
|
|
314
|
+
LLM_CAPABILITY = CapabilitySpec(
|
|
315
|
+
name="llm",
|
|
316
|
+
layer="outbound",
|
|
317
|
+
description="Configuration LLM pour planners et agents via une factory Arclith.",
|
|
318
|
+
activation_config_key=None,
|
|
319
|
+
adapters=(
|
|
320
|
+
AdapterSpec(
|
|
321
|
+
name="lmstudio",
|
|
322
|
+
capability="llm",
|
|
323
|
+
layer="outbound",
|
|
324
|
+
description="LLM local LM Studio exposé par l'API OpenAI-compatible.",
|
|
325
|
+
config_path="config/adapters/outbound/lm.yaml",
|
|
326
|
+
config_template="""\
|
|
327
|
+
provider: openai
|
|
328
|
+
model_name: "{model_name}"
|
|
329
|
+
api_key: "{api_key}"
|
|
330
|
+
base_url: "{base_url}"
|
|
331
|
+
""",
|
|
332
|
+
parameters=(
|
|
333
|
+
ParameterSpec(
|
|
334
|
+
name="model_name",
|
|
335
|
+
kind="string",
|
|
336
|
+
prompt="Model ID LM Studio",
|
|
337
|
+
default="remplacer-par-le-model-id-lm-studio",
|
|
338
|
+
),
|
|
339
|
+
ParameterSpec(
|
|
340
|
+
name="base_url",
|
|
341
|
+
kind="string",
|
|
342
|
+
prompt="Endpoint OpenAI-compatible LM Studio",
|
|
343
|
+
default="http://127.0.0.1:1234/v1",
|
|
344
|
+
),
|
|
345
|
+
ParameterSpec(
|
|
346
|
+
name="api_key",
|
|
347
|
+
kind="string",
|
|
348
|
+
prompt="API key LM Studio",
|
|
349
|
+
default="lm-studio",
|
|
350
|
+
secret=True,
|
|
351
|
+
),
|
|
352
|
+
),
|
|
353
|
+
entity_scoped=False,
|
|
354
|
+
),
|
|
355
|
+
AdapterSpec(
|
|
356
|
+
name="openai",
|
|
357
|
+
capability="llm",
|
|
358
|
+
layer="outbound",
|
|
359
|
+
description="Modèle OpenAI via protocole OpenAI-compatible.",
|
|
360
|
+
config_path="config/adapters/outbound/lm.yaml",
|
|
361
|
+
config_template="""\
|
|
362
|
+
provider: openai
|
|
363
|
+
model_name: "{model_name}"
|
|
364
|
+
api_key: ""
|
|
365
|
+
base_url: "{base_url}"
|
|
366
|
+
""",
|
|
367
|
+
env_path=".env",
|
|
368
|
+
env_template="""\
|
|
369
|
+
OPENAI_API_KEY={api_key}
|
|
370
|
+
""",
|
|
371
|
+
secret_mappings=(
|
|
372
|
+
SecretMappingSpec(
|
|
373
|
+
field_path="adapters.lm.api_key",
|
|
374
|
+
secret_key="OPENAI_API_KEY",
|
|
375
|
+
),
|
|
376
|
+
),
|
|
377
|
+
parameters=(
|
|
378
|
+
ParameterSpec(
|
|
379
|
+
name="model_name",
|
|
380
|
+
kind="string",
|
|
381
|
+
prompt="Modèle OpenAI",
|
|
382
|
+
default="gpt-4o-mini",
|
|
383
|
+
),
|
|
384
|
+
ParameterSpec(
|
|
385
|
+
name="base_url",
|
|
386
|
+
kind="string",
|
|
387
|
+
prompt="Endpoint OpenAI-compatible",
|
|
388
|
+
default="https://api.openai.com/v1",
|
|
389
|
+
),
|
|
390
|
+
ParameterSpec(
|
|
391
|
+
name="api_key",
|
|
392
|
+
kind="string",
|
|
393
|
+
prompt="OPENAI_API_KEY",
|
|
394
|
+
default="",
|
|
395
|
+
secret=True,
|
|
396
|
+
),
|
|
397
|
+
),
|
|
398
|
+
entity_scoped=False,
|
|
399
|
+
),
|
|
400
|
+
AdapterSpec(
|
|
401
|
+
name="anthropic",
|
|
402
|
+
capability="llm",
|
|
403
|
+
layer="outbound",
|
|
404
|
+
description="Modèle Anthropic pour planners et agents.",
|
|
405
|
+
config_path="config/adapters/outbound/lm.yaml",
|
|
406
|
+
config_template="""\
|
|
407
|
+
provider: anthropic
|
|
408
|
+
model_name: "{model_name}"
|
|
409
|
+
api_key: ""
|
|
410
|
+
""",
|
|
411
|
+
env_path=".env",
|
|
412
|
+
env_template="""\
|
|
413
|
+
ANTHROPIC_API_KEY={api_key}
|
|
414
|
+
""",
|
|
415
|
+
secret_mappings=(
|
|
416
|
+
SecretMappingSpec(
|
|
417
|
+
field_path="adapters.lm.api_key",
|
|
418
|
+
secret_key="ANTHROPIC_API_KEY",
|
|
419
|
+
),
|
|
420
|
+
),
|
|
421
|
+
parameters=(
|
|
422
|
+
ParameterSpec(
|
|
423
|
+
name="model_name",
|
|
424
|
+
kind="string",
|
|
425
|
+
prompt="Modèle Anthropic",
|
|
426
|
+
default="claude-sonnet-4-5",
|
|
427
|
+
),
|
|
428
|
+
ParameterSpec(
|
|
429
|
+
name="api_key",
|
|
430
|
+
kind="string",
|
|
431
|
+
prompt="ANTHROPIC_API_KEY",
|
|
432
|
+
default="",
|
|
433
|
+
secret=True,
|
|
434
|
+
),
|
|
435
|
+
),
|
|
436
|
+
entity_scoped=False,
|
|
437
|
+
),
|
|
438
|
+
),
|
|
439
|
+
)
|
|
440
|
+
|
|
297
441
|
AGENT_CAPABILITY = CapabilitySpec(
|
|
298
442
|
name="agent",
|
|
299
443
|
layer="inbound",
|
|
300
|
-
description="Adapter agent qui expose les cas d'usage
|
|
444
|
+
description="Adapter agent qui expose les cas d'usage métier via un runtime IA.",
|
|
301
445
|
activation_config_key=None,
|
|
302
446
|
adapters=(
|
|
303
447
|
AdapterSpec(
|
|
304
448
|
name="langgraph",
|
|
305
449
|
capability="agent",
|
|
306
450
|
layer="inbound",
|
|
307
|
-
description="Entrypoint LangGraph Studio
|
|
451
|
+
description="Entrypoint LangGraph Studio basé sur la tuyauterie Arclith.",
|
|
308
452
|
config_path="config/adapters/inbound/langgraph.yaml",
|
|
309
453
|
config_template="""\
|
|
310
454
|
name: "{graph_name}"
|
|
@@ -375,7 +519,7 @@ agent = arclith.langgraph(AgentState, register_agent, name="{graph_name}")
|
|
|
375
519
|
OBSERVABILITY_CAPABILITY = CapabilitySpec(
|
|
376
520
|
name="observability",
|
|
377
521
|
layer="outbound",
|
|
378
|
-
description="
|
|
522
|
+
description="Observabilité activable en parallèle via LangSmith et OpenTelemetry.",
|
|
379
523
|
activation_config_key="observability",
|
|
380
524
|
adapters=(
|
|
381
525
|
AdapterSpec(
|
|
@@ -435,7 +579,6 @@ LANGSMITH_API_KEY={api_key}
|
|
|
435
579
|
description="Export OTLP traces/metrics et instrumentation FastAPI.",
|
|
436
580
|
config_path="config/adapters/outbound/opentelemetry.yaml",
|
|
437
581
|
config_template="""\
|
|
438
|
-
enabled: {enabled}
|
|
439
582
|
service_name: "{service_name}"
|
|
440
583
|
endpoint: "{endpoint}"
|
|
441
584
|
protocol: "{protocol}"
|
|
@@ -453,12 +596,6 @@ OTEL_EXPORTER_OTLP_PROTOCOL={protocol}
|
|
|
453
596
|
OTEL_EXPORTER_OTLP_HEADERS={headers}
|
|
454
597
|
""",
|
|
455
598
|
parameters=(
|
|
456
|
-
ParameterSpec(
|
|
457
|
-
name="enabled",
|
|
458
|
-
kind="boolean",
|
|
459
|
-
prompt="Activer OpenTelemetry",
|
|
460
|
-
default=True,
|
|
461
|
-
),
|
|
462
599
|
ParameterSpec(
|
|
463
600
|
name="service_name",
|
|
464
601
|
kind="string",
|
|
@@ -486,7 +623,7 @@ OTEL_EXPORTER_OTLP_HEADERS={headers}
|
|
|
486
623
|
ParameterSpec(
|
|
487
624
|
name="metrics",
|
|
488
625
|
kind="boolean",
|
|
489
|
-
prompt="Exporter les
|
|
626
|
+
prompt="Exporter les métriques",
|
|
490
627
|
default=False,
|
|
491
628
|
),
|
|
492
629
|
ParameterSpec(
|
|
@@ -498,7 +635,7 @@ OTEL_EXPORTER_OTLP_HEADERS={headers}
|
|
|
498
635
|
ParameterSpec(
|
|
499
636
|
name="metrics_export_interval_millis",
|
|
500
637
|
kind="string",
|
|
501
|
-
prompt="Intervalle export
|
|
638
|
+
prompt="Intervalle export métriques en ms",
|
|
502
639
|
default="60000",
|
|
503
640
|
),
|
|
504
641
|
ParameterSpec(
|
|
@@ -518,6 +655,7 @@ CAPABILITY_CATALOG = (
|
|
|
518
655
|
REPOSITORY_CAPABILITY,
|
|
519
656
|
API_CAPABILITY,
|
|
520
657
|
MCP_CAPABILITY,
|
|
658
|
+
LLM_CAPABILITY,
|
|
521
659
|
AGENT_CAPABILITY,
|
|
522
660
|
OBSERVABILITY_CAPABILITY,
|
|
523
661
|
)
|