arclith-cli 0.13.0__tar.gz → 0.15.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.
Files changed (30) hide show
  1. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/PKG-INFO +2 -2
  2. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/README.md +5 -3
  3. arclith_cli-0.15.0/arclith_cli/__init__.py +1 -0
  4. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/adapter_templates.py +46 -4
  5. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/add_adapter.py +8 -0
  6. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/capabilities.py +329 -3
  7. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/pyproject.toml +2 -2
  8. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/tests/e2e_manual.sh +1 -1
  9. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/tests/test_add_adapter.py +558 -171
  10. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/tests/test_capabilities.py +316 -53
  11. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/tests/test_core_scaffold.py +1 -1
  12. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/uv.lock +14 -3
  13. arclith_cli-0.13.0/arclith_cli/__init__.py +0 -1
  14. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/.gitignore +0 -0
  15. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/LICENSE +0 -0
  16. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/Makefile +0 -0
  17. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/core_scaffold.py +0 -0
  18. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/entity_scanner.py +0 -0
  19. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/export_config.py +0 -0
  20. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/init_project.py +0 -0
  21. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/main.py +0 -0
  22. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/project_paths.py +0 -0
  23. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/rename.py +0 -0
  24. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/runtime_templates.py +0 -0
  25. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/scaffold.py +0 -0
  26. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/arclith_cli/updater.py +0 -0
  27. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/tests/__init__.py +0 -0
  28. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/tests/test_e2e_scaffold.py +0 -0
  29. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/tests/test_project_paths.py +0 -0
  30. {arclith_cli-0.13.0 → arclith_cli-0.15.0}/tests/test_rename.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: arclith-cli
3
- Version: 0.13.0
3
+ Version: 0.15.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.16.0
189
+ Requires-Dist: arclith>=0.18.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
@@ -141,7 +141,7 @@ arclith-cli add-adapter --adapter mariadb --entity Recipe --param database=my_re
141
141
  arclith-cli add-adapter --capability api --adapter fastapi --param port=8080 --yes
142
142
  arclith-cli add-adapter --capability mcp --adapter fastmcp --param port=8081 --yes
143
143
  arclith-cli add-adapter --capability llm --adapter lmstudio --param model_name=qwen/qwen3.5-9b --yes
144
- arclith-cli add-adapter --capability agent --adapter langgraph --param graph_name=recipe_agent --yes
144
+ arclith-cli add-adapter --capability agent --adapter langgraph --param graph_name=recipe_agent --param stream_mode=updates,custom --yes
145
145
  arclith-cli add-adapter --capability observability --adapter langsmith
146
146
  arclith-cli add-adapter --capability observability --adapter opentelemetry --param service_name=my_recipe_service --yes
147
147
  arclith-cli add-adapter --capability runtime --adapter docker-image --yes
@@ -166,7 +166,7 @@ arclith-cli add-adapter --capability repository --adapter memory --entity Recipe
166
166
  - `lmstudio` → `model_name`, `base_url`, `api_key`
167
167
  - `openai` → `model_name`, `base_url`, `OPENAI_API_KEY`
168
168
  - `anthropic` → `model_name`, `ANTHROPIC_API_KEY`
169
- - `langgraph` → `graph_name`
169
+ - `langgraph` → `graph_name`, `stream_mode`
170
170
  - `langsmith` → `tracing`, `project`, `endpoint`, `LANGSMITH_API_KEY`
171
171
  - `opentelemetry` → `service_name`, `endpoint`, `traces_endpoint`, `metrics_endpoint`, `protocol`, `traces`, `metrics`, `instrument_fastapi`
172
172
  - `command-bus/rabbitmq` → `url`, `exchange`, `exchange_type`, `queue`, `routing_key`, `prefetch`, `consumer_name`, `concurrency`, `publisher_confirms`, `durable`, `retry_enabled`, `retry_requeue`, `dead_letter_exchange`, `dead_letter_routing_key`
@@ -240,7 +240,9 @@ l'environnement, un fichier local de secrets ou Vault selon le resolver choisi.
240
240
  L'adapter `agent/langgraph` génère `langgraph.json`, `config/adapters/inbound/langgraph.yaml` et
241
241
  `src/<package>/adapters/inbound/langgraph/agent.py`. Le projet ne modifie ensuite que ce fichier pour
242
242
  son agent. Comme `fastapi` et `fastmcp`, LangGraph est configuré par son nom produit dans
243
- `AppConfig.langgraph`, sans `adapters.agent`. L'adapter `observability/langsmith` génère
243
+ `AppConfig.langgraph`, sans `adapters.agent`. `stream_mode` vaut `updates` par défaut et accepte une
244
+ liste CSV comme `updates,custom` pour exposer les sorties de nodes et les événements
245
+ `get_stream_writer()`. L'adapter `observability/langsmith` génère
244
246
  `config/adapters/outbound/langsmith.yaml`, l'ajoute à `observability.enabled`, met
245
247
  à jour `.env` et ajoute `.env` au `.gitignore` si besoin. LangSmith Studio devient l'endroit standard
246
248
  pour tester les agents. Une `LANGSMITH_API_KEY` déjà présente est conservée si aucune nouvelle valeur
@@ -0,0 +1 @@
1
+ __version__ = "0.15.0"
@@ -66,6 +66,20 @@ class MariaDB{pascal}Repository(MariaDBRepository[{pascal}], {pascal}Repository)
66
66
 
67
67
  # TODO: add custom query methods here
68
68
  """,
69
+ "postgresql": """\
70
+ from arclith.adapters.outbound.postgresql.config import PostgreSQLConfig
71
+ from arclith.adapters.outbound.postgresql.repository import PostgreSQLRepository
72
+ from arclith.domain.ports.outbound.logger import Logger
73
+ from {domain_import}.models.{snake} import {pascal}
74
+ from {domain_import}.ports.outbound.{snake}_repository import {pascal}Repository
75
+
76
+
77
+ class PostgreSQL{pascal}Repository(PostgreSQLRepository[{pascal}], {pascal}Repository):
78
+ def __init__(self, config: PostgreSQLConfig, logger: Logger) -> None:
79
+ super().__init__(config, {pascal}, logger)
80
+
81
+ # TODO: add custom query methods here
82
+ """,
69
83
  }
70
84
 
71
85
  # ── repository.py re-export template ─────────────────────────────────────────
@@ -90,6 +104,11 @@ __all__ = ["DuckDB{pascal}Repository"]
90
104
  from {adapters_import}.outbound.mariadb.repositories.{snake}_repository import MariaDB{pascal}Repository
91
105
 
92
106
  __all__ = ["MariaDB{pascal}Repository"]
107
+ """,
108
+ "postgresql": """\
109
+ from {adapters_import}.outbound.postgresql.repositories.{snake}_repository import PostgreSQL{pascal}Repository
110
+
111
+ __all__ = ["PostgreSQL{pascal}Repository"]
93
112
  """,
94
113
  }
95
114
 
@@ -152,6 +171,29 @@ def _build_mariadb(cfg: AppConfig, _entity_class: type[{pascal}], log: Logger) -
152
171
  log,
153
172
  )
154
173
 
174
+ """,
175
+ "postgresql": """\
176
+ def _build_postgresql(cfg: AppConfig, _entity_class: type[{pascal}], log: Logger) -> {pascal}Repository:
177
+ from {adapters_import}.outbound.postgresql.repository import PostgreSQL{pascal}Repository
178
+ from arclith.adapters.outbound.postgresql.config import PostgreSQLConfig
179
+ postgresql = cfg.adapters.postgresql
180
+ if postgresql is None:
181
+ raise ValueError("PostgreSQL settings are required when repository=postgresql")
182
+ return PostgreSQL{pascal}Repository(
183
+ PostgreSQLConfig(
184
+ url=postgresql.url,
185
+ host=postgresql.host,
186
+ port=postgresql.port,
187
+ database=postgresql.database,
188
+ user=postgresql.user,
189
+ password=postgresql.password,
190
+ schema=postgresql.schema_name,
191
+ driver=postgresql.driver,
192
+ table_prefix=postgresql.table_prefix,
193
+ ),
194
+ log,
195
+ )
196
+
155
197
  """,
156
198
  }
157
199
 
@@ -169,7 +211,9 @@ def build_{snake}_service(arclith: Arclith) -> tuple[{pascal}Service, Logger]:
169
211
  """
170
212
 
171
213
 
172
- def render_container(pascal: str, snake: str, installed_adapters: list[str], import_vars: dict[str, str]) -> str:
214
+ def render_container(
215
+ pascal: str, snake: str, installed_adapters: list[str], import_vars: dict[str, str]
216
+ ) -> str:
173
217
  """Generate the full container file content for a given entity and its adapters."""
174
218
  # memory is always included (arclith built-in, needs no extra files)
175
219
  adapters = list(dict.fromkeys(["memory"] + installed_adapters))
@@ -182,9 +226,7 @@ def render_container(pascal: str, snake: str, installed_adapters: list[str], imp
182
226
  if a in _CONTAINER_FACTORY
183
227
  )
184
228
  registrations = "\n".join(
185
- f" .register(\"{a}\", _build_{a})"
186
- for a in adapters
187
- if a in _CONTAINER_FACTORY
229
+ f' .register("{a}", _build_{a})' for a in adapters if a in _CONTAINER_FACTORY
188
230
  )
189
231
  footer = _CONTAINER_FOOTER.format(**vars, registrations=registrations)
190
232
  return header + factories + footer
@@ -686,11 +686,19 @@ def _file_template_vars(
686
686
  "package_path": package_path,
687
687
  "langgraph_entrypoint": langgraph_entrypoint,
688
688
  "graph_name": graph_name,
689
+ "stream_mode_yaml": _langgraph_stream_mode_yaml(str(params.get("stream_mode") or "updates")),
689
690
  "secret_template_yaml": _secret_template_yaml(str(params.get("field_path") or "")),
690
691
  "secret_chain_yaml": _secret_chain_yaml(str(params.get("resolvers") or "")),
691
692
  }
692
693
 
693
694
 
695
+ def _langgraph_stream_mode_yaml(stream_mode: str) -> str:
696
+ values = _split_csv_values(stream_mode)
697
+ if len(values) == 1:
698
+ return f'"{values[0]}"'
699
+ return yaml.safe_dump(values, default_flow_style=True, sort_keys=False).strip()
700
+
701
+
694
702
  def _secret_template_yaml(field_path: str) -> str:
695
703
  keys = [key for key in field_path.split(".") if key]
696
704
  if not keys:
@@ -108,10 +108,16 @@ class AdapterSpec:
108
108
  "layer": self.layer,
109
109
  "description": self.description,
110
110
  "config_path": self.config_path,
111
- "merge_config_templates": [file_template.to_dict() for file_template in self.merge_config_templates],
111
+ "merge_config_templates": [
112
+ file_template.to_dict() for file_template in self.merge_config_templates
113
+ ],
112
114
  "env_path": self.env_path,
113
- "file_templates": [file_template.to_dict() for file_template in self.file_templates],
114
- "secret_mappings": [secret_mapping.to_dict() for secret_mapping in self.secret_mappings],
115
+ "file_templates": [
116
+ file_template.to_dict() for file_template in self.file_templates
117
+ ],
118
+ "secret_mappings": [
119
+ secret_mapping.to_dict() for secret_mapping in self.secret_mappings
120
+ ],
115
121
  "secret_resolver": self.secret_resolver,
116
122
  "secret_config_template": bool(self.secret_config_template),
117
123
  "gitignore_entries": list(self.gitignore_entries),
@@ -284,6 +290,305 @@ multitenant: false
284
290
  ),
285
291
  ),
286
292
  ),
293
+ AdapterSpec(
294
+ name="postgresql",
295
+ capability="repository",
296
+ layer="outbound",
297
+ description="Repository PostgreSQL async optionnel, pilote par SQLAlchemy et asyncpg, avec payload JSONB generique.",
298
+ config_path="config/adapters/outbound/postgresql.yaml",
299
+ config_template="""\
300
+ url: null # à mapper via config/secrets.yaml ou resolver env/vault si vous fournissez une URL complète
301
+ host: {host}
302
+ port: {port}
303
+ database: {database}
304
+ user: {user}
305
+ password: null # à mapper via config/secrets.yaml ou resolver env/vault
306
+ schema: {schema}
307
+ driver: {driver}
308
+ table_prefix: "{table_prefix}"
309
+ multitenant: {multitenant}
310
+ """,
311
+ secret_mappings=(
312
+ SecretMappingSpec(
313
+ field_path="adapters.postgresql.url",
314
+ secret_key="POSTGRESQL_URL",
315
+ ),
316
+ SecretMappingSpec(
317
+ field_path="adapters.postgresql.password",
318
+ secret_key="POSTGRESQL_PASSWORD",
319
+ ),
320
+ ),
321
+ parameters=(
322
+ ParameterSpec(
323
+ name="host",
324
+ kind="string",
325
+ prompt="host",
326
+ default="127.0.0.1",
327
+ ),
328
+ ParameterSpec(
329
+ name="port",
330
+ kind="string",
331
+ prompt="port",
332
+ default="5432",
333
+ ),
334
+ ParameterSpec(
335
+ name="database",
336
+ kind="string",
337
+ prompt="database",
338
+ default_from_project_name=True,
339
+ ),
340
+ ParameterSpec(
341
+ name="user",
342
+ kind="string",
343
+ prompt="user",
344
+ default="app",
345
+ ),
346
+ ParameterSpec(
347
+ name="schema",
348
+ kind="string",
349
+ prompt="schema",
350
+ default="public",
351
+ ),
352
+ ParameterSpec(
353
+ name="driver",
354
+ kind="string",
355
+ prompt="driver",
356
+ default="asyncpg",
357
+ ),
358
+ ParameterSpec(
359
+ name="table_prefix",
360
+ kind="string",
361
+ prompt="table_prefix",
362
+ default="",
363
+ ),
364
+ ParameterSpec(
365
+ name="multitenant",
366
+ kind="boolean",
367
+ prompt="multitenant",
368
+ default=False,
369
+ ),
370
+ ),
371
+ ),
372
+ ),
373
+ )
374
+
375
+ STORAGE_CAPABILITY = CapabilitySpec(
376
+ name="storage",
377
+ layer="outbound",
378
+ description="Stockage de fichiers et blobs derrière un port FileStoragePort.",
379
+ activation_config_key=None,
380
+ adapters=(
381
+ AdapterSpec(
382
+ name="filesystem",
383
+ capability="storage",
384
+ layer="outbound",
385
+ description="Stockage fichier local, typiquement monté dans Docker via un volume.",
386
+ config_path="config/adapters/outbound/storage.yaml",
387
+ config_template="""\
388
+ adapter: filesystem
389
+ root_path: "{root_path}"
390
+ prefix: "{prefix}"
391
+ create_root: {create_root}
392
+ multitenant: {multitenant}
393
+ """,
394
+ parameters=(
395
+ ParameterSpec(
396
+ name="root_path",
397
+ kind="string",
398
+ prompt="Chemin conteneur du dossier de fichiers",
399
+ default="/data/files",
400
+ ),
401
+ ParameterSpec(
402
+ name="prefix",
403
+ kind="string",
404
+ prompt="Préfixe d'objets",
405
+ default="",
406
+ ),
407
+ ParameterSpec(
408
+ name="create_root",
409
+ kind="boolean",
410
+ prompt="Créer le dossier racine au démarrage",
411
+ default=True,
412
+ ),
413
+ ParameterSpec(
414
+ name="multitenant",
415
+ kind="boolean",
416
+ prompt="Résoudre le stockage par tenant",
417
+ default=False,
418
+ ),
419
+ ),
420
+ entity_scoped=False,
421
+ ),
422
+ AdapterSpec(
423
+ name="s3",
424
+ capability="storage",
425
+ layer="outbound",
426
+ description="Stockage objet AWS S3 compatible MinIO via endpoint custom.",
427
+ config_path="config/adapters/outbound/storage.yaml",
428
+ config_template="""\
429
+ adapter: s3
430
+ # Credentials: chaîne AWS par défaut, variables d'environnement, rôle IAM ou TenantContext.
431
+ bucket_name: "{bucket_name}"
432
+ prefix: "{prefix}"
433
+ region_name: "{region_name}"
434
+ endpoint_url: {endpoint_url}
435
+ force_path_style: {force_path_style}
436
+ multitenant: {multitenant}
437
+ """,
438
+ parameters=(
439
+ ParameterSpec(
440
+ name="bucket_name",
441
+ kind="string",
442
+ prompt="Bucket S3",
443
+ default="my-bucket",
444
+ ),
445
+ ParameterSpec(
446
+ name="prefix",
447
+ kind="string",
448
+ prompt="Préfixe d'objets",
449
+ default="",
450
+ ),
451
+ ParameterSpec(
452
+ name="region_name",
453
+ kind="string",
454
+ prompt="Région AWS",
455
+ default="eu-west-3",
456
+ ),
457
+ ParameterSpec(
458
+ name="endpoint_url",
459
+ kind="string",
460
+ prompt="Endpoint S3 custom ou null",
461
+ default="null",
462
+ ),
463
+ ParameterSpec(
464
+ name="force_path_style",
465
+ kind="boolean",
466
+ prompt="Forcer le path-style S3",
467
+ default=False,
468
+ ),
469
+ ParameterSpec(
470
+ name="multitenant",
471
+ kind="boolean",
472
+ prompt="Résoudre le stockage par tenant",
473
+ default=False,
474
+ ),
475
+ ),
476
+ entity_scoped=False,
477
+ ),
478
+ AdapterSpec(
479
+ name="azure-blob",
480
+ capability="storage",
481
+ layer="outbound",
482
+ description="Stockage objet Azure Blob Storage.",
483
+ config_path="config/adapters/outbound/storage.yaml",
484
+ config_template="""\
485
+ adapter: azure-blob
486
+ account_url: "{account_url}"
487
+ container_name: "{container_name}"
488
+ prefix: "{prefix}"
489
+ connection_string: null
490
+ account_key: null
491
+ sas_token: null
492
+ use_default_credential: {use_default_credential}
493
+ multitenant: {multitenant}
494
+ # Credentials: mappez connection_string, account_key ou sas_token via
495
+ # config/secrets.yaml, Vault, ou TenantContext. use_default_credential=true
496
+ # délègue à DefaultAzureCredential du SDK Azure.
497
+ """,
498
+ secret_mappings=(
499
+ SecretMappingSpec(
500
+ field_path="adapters.storage.connection_string",
501
+ secret_key="AZURE_STORAGE_CONNECTION_STRING",
502
+ ),
503
+ SecretMappingSpec(
504
+ field_path="adapters.storage.account_key",
505
+ secret_key="AZURE_STORAGE_ACCOUNT_KEY",
506
+ ),
507
+ SecretMappingSpec(
508
+ field_path="adapters.storage.sas_token",
509
+ secret_key="AZURE_STORAGE_SAS_TOKEN",
510
+ ),
511
+ ),
512
+ parameters=(
513
+ ParameterSpec(
514
+ name="account_url",
515
+ kind="string",
516
+ prompt="URL du compte Azure Blob",
517
+ default="https://<account>.blob.core.windows.net",
518
+ ),
519
+ ParameterSpec(
520
+ name="container_name",
521
+ kind="string",
522
+ prompt="Container Azure Blob",
523
+ default="my-container",
524
+ ),
525
+ ParameterSpec(
526
+ name="prefix",
527
+ kind="string",
528
+ prompt="Préfixe d'objets",
529
+ default="",
530
+ ),
531
+ ParameterSpec(
532
+ name="use_default_credential",
533
+ kind="boolean",
534
+ prompt="Utiliser DefaultAzureCredential",
535
+ default=False,
536
+ ),
537
+ ParameterSpec(
538
+ name="multitenant",
539
+ kind="boolean",
540
+ prompt="Résoudre le stockage par tenant",
541
+ default=False,
542
+ ),
543
+ ),
544
+ entity_scoped=False,
545
+ ),
546
+ AdapterSpec(
547
+ name="gcs",
548
+ capability="storage",
549
+ layer="outbound",
550
+ description="Stockage objet Google Cloud Storage.",
551
+ config_path="config/adapters/outbound/storage.yaml",
552
+ config_template="""\
553
+ adapter: gcs
554
+ bucket_name: "{bucket_name}"
555
+ prefix: "{prefix}"
556
+ project_id: {project_id}
557
+ credentials_path: null
558
+ credentials_json: null
559
+ credentials_json_b64: null
560
+ multitenant: {multitenant}
561
+ # Credentials: use ADC/GOOGLE_APPLICATION_CREDENTIALS, config/secrets.yaml,
562
+ # Vault, or TenantContext. Never commit service account JSON.
563
+ """,
564
+ parameters=(
565
+ ParameterSpec(
566
+ name="bucket_name",
567
+ kind="string",
568
+ prompt="Bucket Google Cloud Storage",
569
+ default="my-bucket",
570
+ ),
571
+ ParameterSpec(
572
+ name="prefix",
573
+ kind="string",
574
+ prompt="Préfixe d'objets",
575
+ default="",
576
+ ),
577
+ ParameterSpec(
578
+ name="project_id",
579
+ kind="string",
580
+ prompt="Project ID GCP ou null",
581
+ default="null",
582
+ ),
583
+ ParameterSpec(
584
+ name="multitenant",
585
+ kind="boolean",
586
+ prompt="Résoudre le stockage par tenant",
587
+ default=False,
588
+ ),
589
+ ),
590
+ entity_scoped=False,
591
+ ),
287
592
  ),
288
593
  )
289
594
 
@@ -1263,6 +1568,7 @@ name: "{graph_name}"
1263
1568
  graph: "{graph_name}"
1264
1569
  entrypoint: "{langgraph_entrypoint}"
1265
1570
  env: ".env"
1571
+ stream_mode: {stream_mode_yaml}
1266
1572
  """,
1267
1573
  file_templates=(
1268
1574
  FileTemplateSpec(
@@ -1287,6 +1593,7 @@ env: ".env"
1287
1593
  from typing import Any, TypedDict
1288
1594
 
1289
1595
  from arclith import Arclith
1596
+ from langgraph.config import get_stream_writer
1290
1597
  from langgraph.graph import END, START
1291
1598
 
1292
1599
 
@@ -1300,6 +1607,8 @@ arclith = Arclith("config")
1300
1607
  # Template minimal volontaire: remplacer AgentState, run_agent et les edges par
1301
1608
  # l'état, les noeuds et les transitions propres au projet.
1302
1609
  async def run_agent(state: AgentState) -> AgentState:
1610
+ writer = get_stream_writer()
1611
+ writer({{"kind": "progress", "stage": "agent.started", "message": "Agent node started."}})
1303
1612
  return state
1304
1613
 
1305
1614
 
@@ -1320,6 +1629,22 @@ agent = arclith.langgraph(AgentState, register_agent, name="{graph_name}")
1320
1629
  prompt="Nom du graphe LangGraph",
1321
1630
  default="agent",
1322
1631
  ),
1632
+ ParameterSpec(
1633
+ name="stream_mode",
1634
+ kind="string",
1635
+ prompt="Mode(s) de streaming LangGraph",
1636
+ default="updates",
1637
+ choices=(
1638
+ "values",
1639
+ "updates",
1640
+ "custom",
1641
+ "messages",
1642
+ "checkpoints",
1643
+ "tasks",
1644
+ "debug",
1645
+ ),
1646
+ csv_choices=True,
1647
+ ),
1323
1648
  ),
1324
1649
  entity_scoped=False,
1325
1650
  ),
@@ -1478,6 +1803,7 @@ OTEL_EXPORTER_OTLP_HEADERS={headers}
1478
1803
 
1479
1804
  CAPABILITY_CATALOG = (
1480
1805
  REPOSITORY_CAPABILITY,
1806
+ STORAGE_CAPABILITY,
1481
1807
  CACHE_CAPABILITY,
1482
1808
  LOGGER_CAPABILITY,
1483
1809
  SECRETS_CAPABILITY,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arclith-cli"
7
- version = "0.13.0"
7
+ version = "0.15.0"
8
8
  description = "CLI scaffolding tool for arclith — hexagonal architecture framework"
9
9
  license = { file = "LICENSE" }
10
10
  requires-python = ">=3.13"
@@ -16,7 +16,7 @@ dependencies = [
16
16
  "typer>=0.15.0",
17
17
  "rich>=13.0.0",
18
18
  "httpx>=0.27.0",
19
- "arclith>=0.16.0",
19
+ "arclith>=0.18.0",
20
20
  ]
21
21
 
22
22
  [tool.uv.sources]
@@ -39,7 +39,7 @@ if grep -q '\[tool\.uv\.sources\]' pyproject.toml; then
39
39
  fi
40
40
 
41
41
  ARCLITH_VERSION=$(grep 'arclith\[' pyproject.toml | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
42
- MIN_ARCLITH_VERSION="0.16.0"
42
+ MIN_ARCLITH_VERSION="0.18.0"
43
43
  if version_lt "$ARCLITH_VERSION" "$MIN_ARCLITH_VERSION"; then
44
44
  echo -e "${RED}✗ FAIL: arclith version < $MIN_ARCLITH_VERSION (found: $ARCLITH_VERSION)${NC}"
45
45
  exit 1