lovarch-cli 0.3.1__tar.gz → 0.3.2__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 (145) hide show
  1. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/CHANGELOG.md +9 -0
  2. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/PKG-INFO +9 -1
  3. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/README.md +8 -0
  4. lovarch_cli-0.3.2/lovarch_cli/cad/__init__.py +4 -0
  5. lovarch_cli-0.3.2/lovarch_cli/cad/floorplan.py +134 -0
  6. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/cli.py +24 -0
  7. lovarch_cli-0.3.2/lovarch_cli/commands/archchat_cmd.py +115 -0
  8. lovarch_cli-0.3.2/lovarch_cli/commands/cad_cmd.py +107 -0
  9. lovarch_cli-0.3.2/lovarch_cli/commands/progetto_cmd.py +79 -0
  10. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/version.py +1 -1
  11. lovarch_cli-0.3.2/lovarch_cli/workflows/progetto.py +129 -0
  12. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/.gitignore +0 -0
  13. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/LICENSE +0 -0
  14. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/__init__.py +0 -0
  15. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/__main__.py +0 -0
  16. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/agents/__init__.py +0 -0
  17. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/agents/prompts.py +0 -0
  18. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/agents/runner.py +0 -0
  19. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/ai/__init__.py +0 -0
  20. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/ai/gateway.py +0 -0
  21. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/api.py +0 -0
  22. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/auth/__init__.py +0 -0
  23. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/auth/keyring_store.py +0 -0
  24. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/auth/local_server.py +0 -0
  25. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/auth/pkce.py +0 -0
  26. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/auth/session.py +0 -0
  27. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/clients/__init__.py +0 -0
  28. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/clients/factory.py +0 -0
  29. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/clients/local_client.py +0 -0
  30. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/clients/lovarch_storage.py +0 -0
  31. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/clients/lovarch_supabase.py +0 -0
  32. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/clients/persistence.py +0 -0
  33. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/clients/storage.py +0 -0
  34. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/__init__.py +0 -0
  35. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/account.py +0 -0
  36. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/agent_cmd.py +0 -0
  37. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/audit.py +0 -0
  38. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/config_cmd.py +0 -0
  39. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/consolidate.py +0 -0
  40. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/context_cmd.py +0 -0
  41. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/dev.py +0 -0
  42. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/do_cmd.py +0 -0
  43. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/init.py +0 -0
  44. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/jobs_cmd.py +0 -0
  45. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/login.py +0 -0
  46. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/mcp_cmd.py +0 -0
  47. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/run.py +0 -0
  48. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/signup.py +0 -0
  49. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/skills_cmd.py +0 -0
  50. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/status.py +0 -0
  51. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/upgrade.py +0 -0
  52. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/commands/verifica_cmd.py +0 -0
  53. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/config.py +0 -0
  54. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/config_store.py +0 -0
  55. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/credits/__init__.py +0 -0
  56. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/credits/base.py +0 -0
  57. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/credits/factory.py +0 -0
  58. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/credits/local.py +0 -0
  59. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/credits/lovarch.py +0 -0
  60. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/i18n/__init__.py +0 -0
  61. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/i18n/loader.py +0 -0
  62. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/i18n/translations/en.json +0 -0
  63. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/i18n/translations/es.json +0 -0
  64. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/i18n/translations/it.json +0 -0
  65. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/i18n/translations/pt.json +0 -0
  66. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/mcp/__init__.py +0 -0
  67. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/mcp/server.py +0 -0
  68. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/mcp/tools.py +0 -0
  69. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/sample_downloader.py +0 -0
  70. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/skills/lovarch-capitolato/SKILL.md +0 -0
  71. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/skills/lovarch-direzione-lavori/SKILL.md +0 -0
  72. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/skills/lovarch-interior-designer/SKILL.md +0 -0
  73. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/skills/lovarch-preventivi/SKILL.md +0 -0
  74. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/skills/lovarch-render/SKILL.md +0 -0
  75. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/skills/lovarch-verifica-normativa/SKILL.md +0 -0
  76. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/README.md +0 -0
  77. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/auditor-input.md +0 -0
  78. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/bim-engineer.md +0 -0
  79. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/briefing-architect.md +0 -0
  80. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/cad-engineer.md +0 -0
  81. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/capitolato-writer.md +0 -0
  82. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/computo-engineer.md +0 -0
  83. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/concept-designer.md +0 -0
  84. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/contratto-architect.md +0 -0
  85. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/deliverable-builder.md +0 -0
  86. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/energy-prelim.md +0 -0
  87. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/pratiche-it.md +0 -0
  88. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/progetto-chief.md +0 -0
  89. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/quality-dati.md +0 -0
  90. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/quality-misure.md +0 -0
  91. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/quality-normativa.md +0 -0
  92. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/quality-output.md +0 -0
  93. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/regolatorio-it.md +0 -0
  94. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/checklists/handoff-quality-gate.md +0 -0
  95. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/checklists/quality-dati-checklist.md +0 -0
  96. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/checklists/quality-misure-checklist.md +0 -0
  97. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/checklists/quality-normativa-checklist.md +0 -0
  98. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/checklists/quality-output-checklist.md +0 -0
  99. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/config.yaml +0 -0
  100. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/CHANGELOG.md +0 -0
  101. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/agents-prd.md +0 -0
  102. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/architettura-progetto-rules.md +0 -0
  103. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/handoff-card-template.md +0 -0
  104. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/mocks/catasto-visura.json +0 -0
  105. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/mocks/firma-envelope.json +0 -0
  106. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/prezzario-lombardia-sample.json +0 -0
  107. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/api_clients.py +0 -0
  108. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/architect_profile.py +0 -0
  109. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/deliverable_generators.py +0 -0
  110. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/generate_attico_brera_dwg.py +0 -0
  111. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/generate_chianti_dxf.py +0 -0
  112. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/generate_chianti_images.py +0 -0
  113. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/generate_real_sample_images.py +0 -0
  114. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/generate_sample_assets.py +0 -0
  115. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/input_parser.py +0 -0
  116. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/lovarch_client.py +0 -0
  117. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/pipeline_runner.py +0 -0
  118. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/render_dxf_to_png.py +0 -0
  119. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/run_palestra_demo.sh +0 -0
  120. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/simulate_squad_execution.py +0 -0
  121. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/validate-squad.py +0 -0
  122. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/tasks/audit-input.md +0 -0
  123. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/tasks/compute-metric.md +0 -0
  124. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/tasks/consolidate-dossier.md +0 -0
  125. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/tasks/generate-cad-plan.md +0 -0
  126. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/tasks/generate-ifc-model.md +0 -0
  127. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/tasks/write-capitolato.md +0 -0
  128. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/templates/asseverazione-tecnica.md +0 -0
  129. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/templates/capitolato-uni-11337.md +0 -0
  130. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/templates/cila-comune-milano.md +0 -0
  131. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/templates/contratto-cnappc.md +0 -0
  132. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad/workflows/dal-brief-al-cantiere.yaml +0 -0
  133. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/squad_loader.py +0 -0
  134. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/verify/__init__.py +0 -0
  135. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/verify/computo.py +0 -0
  136. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/verify/contratto.py +0 -0
  137. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/verify/dossier.py +0 -0
  138. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/verify/misure.py +0 -0
  139. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/verify/normativa.py +0 -0
  140. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/verify/pratica.py +0 -0
  141. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/workflows/__init__.py +0 -0
  142. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/lovarch_cli/workflows/platform.py +0 -0
  143. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/pyproject.toml +0 -0
  144. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/scripts/refresh_squad_vendor.py +0 -0
  145. {lovarch_cli-0.3.1 → lovarch_cli-0.3.2}/scripts/sync_squad.py +0 -0
@@ -5,6 +5,15 @@ All notable changes to `lovarch-cli` are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.2] — 2026-07-04
9
+
10
+ ### Added
11
+ - `lovarch archchat list|read` — leggi le conversazioni ArchChat del tuo studio (sola lettura, **nessun credito**): non esegue l'IA di ArchChat, ti dà accesso a ciò che c'è già.
12
+ - `lovarch cad genera` — genera una **pianta DXF 2D reale** (9 layer ISO + cartiglio CNAPPC) da specifiche di ambienti o da un brief (`--brief`, IA→layout). Geometria deterministica gratis; passa `lovarch verifica misure`.
13
+
14
+ ### Notes
15
+ - MCP remoto: OAuth 2.1 (conettori claude.ai senza chiave manuale) + tool `archchat_list`/`archchat_read`.
16
+
8
17
  ## [0.3.1] — 2026-07-04
9
18
 
10
19
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lovarch-cli
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: AI-powered architectural project execution CLI by Lovarch
5
5
  Project-URL: Homepage, https://archprime.io
6
6
  Project-URL: Documentation, https://lovarch.com/cli-docs
@@ -222,6 +222,14 @@ lovarch skills install # → ~/.claude/skills
222
222
  Skill disponibili: `interior-designer`, `capitolato`, `preventivi`,
223
223
  `direzione-lavori`, `verifica-normativa`, `render`.
224
224
 
225
+ ## Guida per persona
226
+
227
+ Architetto, interior designer, geometra, impresa/DL: quale superficie usare
228
+ (Skill / CLI / MCP) e cosa costa crediti → **[docs/guida-per-persona.md](docs/guida-per-persona.md)**.
229
+
230
+ Workflow composto per interni: `lovarch progetto interni "<brief>" --renders 2`
231
+ (concept → render → preventivo → mini-dossier).
232
+
225
233
  ## Server MCP (Claude Code / Claude / IDE)
226
234
 
227
235
  Il CLI espone le sue capacità come server **MCP** — 15 tools (render, verifica,
@@ -163,6 +163,14 @@ lovarch skills install # → ~/.claude/skills
163
163
  Skill disponibili: `interior-designer`, `capitolato`, `preventivi`,
164
164
  `direzione-lavori`, `verifica-normativa`, `render`.
165
165
 
166
+ ## Guida per persona
167
+
168
+ Architetto, interior designer, geometra, impresa/DL: quale superficie usare
169
+ (Skill / CLI / MCP) e cosa costa crediti → **[docs/guida-per-persona.md](docs/guida-per-persona.md)**.
170
+
171
+ Workflow composto per interni: `lovarch progetto interni "<brief>" --renders 2`
172
+ (concept → render → preventivo → mini-dossier).
173
+
166
174
  ## Server MCP (Claude Code / Claude / IDE)
167
175
 
168
176
  Il CLI espone le sue capacità come server **MCP** — 15 tools (render, verifica,
@@ -0,0 +1,4 @@
1
+ """CAD generation (2D DXF via ezdxf). Deterministic, no credits."""
2
+ from lovarch_cli.cad.floorplan import generate_floorplan, FloorplanResult
3
+
4
+ __all__ = ["generate_floorplan", "FloorplanResult"]
@@ -0,0 +1,134 @@
1
+ """Deterministic 2D CAD floor-plan generator (DXF via ezdxf).
2
+
3
+ Produces a real .dxf an architect can open in AutoCAD/BricsCAD, laid out on the
4
+ 9 ISO layers the platform expects (CAD-A-WALL, -WALL-EXT, -DOOR, -WIND, -DIM,
5
+ -TEXT, -SYMB, -FURN, -CART) with room labels and a CNAPPC cartiglio. No AI, no
6
+ credits — the geometry is computed. It round-trips: a plan generated here passes
7
+ `lovarch verifica misure`.
8
+
9
+ Input: a list of rooms {name, width_m, height_m}. Rooms are packed left-to-right
10
+ in a single band (simple, predictable). Names are matched to the standard Italian
11
+ room labels for the cartiglio/verify.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass, field
16
+ from pathlib import Path
17
+
18
+ ISO_LAYERS = {
19
+ "CAD-A-WALL": 7, "CAD-A-WALL-EXT": 7, "CAD-A-DOOR": 3, "CAD-A-WIND": 4,
20
+ "CAD-A-DIM": 2, "CAD-A-TEXT": 7, "CAD-A-SYMB": 1, "CAD-A-FURN": 8,
21
+ "CAD-A-CART": 7,
22
+ }
23
+
24
+ STANDARD_ROOMS = [
25
+ ("INGRESSO", 2.0, 3.0), ("SOGGIORNO", 5.0, 4.0), ("CUCINA", 3.0, 3.0),
26
+ ("STUDIO", 3.0, 3.0), ("CAMERA", 4.0, 3.5), ("BAGNO", 2.0, 2.5),
27
+ ("LAVANDERIA", 2.0, 2.0),
28
+ ]
29
+
30
+ _LABEL_ALIASES = {
31
+ "ingresso": "INGRESSO", "entrata": "INGRESSO", "soggiorno": "SOGGIORNO",
32
+ "living": "SOGGIORNO", "salotto": "SOGGIORNO", "cucina": "CUCINA",
33
+ "studio": "STUDIO", "ufficio": "STUDIO", "camera": "CAMERA",
34
+ "camera da letto": "CAMERA", "bagno": "BAGNO", "wc": "BAGNO",
35
+ "lavanderia": "LAVANDERIA",
36
+ }
37
+
38
+
39
+ @dataclass
40
+ class FloorplanResult:
41
+ path: str
42
+ rooms: list = field(default_factory=list)
43
+ total_area_m2: float = 0.0
44
+ layers: int = 0
45
+
46
+
47
+ def _norm_label(name: str) -> str:
48
+ n = (name or "").strip().lower()
49
+ return _LABEL_ALIASES.get(n, name.strip().upper())
50
+
51
+
52
+ def generate_floorplan(
53
+ rooms: list[dict] | None,
54
+ *,
55
+ out_path: str | Path,
56
+ progetto: str = "Progetto",
57
+ cliente: str = "Cliente",
58
+ architetto: str = "Arch.",
59
+ scala: str = "1:100",
60
+ data: str = "",
61
+ ) -> FloorplanResult:
62
+ """Generate a DXF floor plan and write it to out_path."""
63
+ import ezdxf
64
+
65
+ specs = rooms if rooms else [{"name": n, "width_m": w, "height_m": h} for n, w, h in STANDARD_ROOMS]
66
+
67
+ doc = ezdxf.new(setup=True)
68
+ msp = doc.modelspace()
69
+ for name, color in ISO_LAYERS.items():
70
+ if name not in doc.layers:
71
+ doc.layers.add(name, color=color)
72
+
73
+ def rect(x0, y0, x1, y1, layer):
74
+ msp.add_lwpolyline([(x0, y0), (x1, y0), (x1, y1), (x0, y1)], close=True,
75
+ dxfattribs={"layer": layer})
76
+
77
+ x = 0.0
78
+ gap = 0.15 # wall thickness between rooms
79
+ max_h = 0.0
80
+ placed = []
81
+ total_area = 0.0
82
+ for spec in specs:
83
+ label = _norm_label(str(spec.get("name", "")))
84
+ w = float(spec.get("width_m", 3.0))
85
+ h = float(spec.get("height_m", 3.0))
86
+ # interior walls
87
+ rect(x, 0, x + w, h, "CAD-A-WALL")
88
+ # a door opening on the bottom wall
89
+ dx = x + w / 2
90
+ msp.add_line((dx - 0.4, 0), (dx + 0.4, 0), dxfattribs={"layer": "CAD-A-DOOR"})
91
+ # a window on the top wall
92
+ msp.add_line((dx - 0.6, h), (dx + 0.6, h), dxfattribs={"layer": "CAD-A-WIND"})
93
+ # furniture placeholder (centered box)
94
+ rect(x + w * 0.35, h * 0.35, x + w * 0.65, h * 0.65, "CAD-A-FURN")
95
+ # room label + area
96
+ area = w * h
97
+ total_area += area
98
+ msp.add_text(f"{label} {area:.1f} mq",
99
+ dxfattribs={"layer": "CAD-A-TEXT", "height": 0.25}
100
+ ).set_placement((x + 0.2, h / 2))
101
+ # dimension text (simple, on DIM layer)
102
+ msp.add_text(f"{w:.2f}", dxfattribs={"layer": "CAD-A-DIM", "height": 0.18}
103
+ ).set_placement((x + w / 2 - 0.3, -0.4))
104
+ placed.append((label, w, h))
105
+ x += w + gap
106
+ max_h = max(max_h, h)
107
+
108
+ total_w = x
109
+ # exterior boundary
110
+ rect(-0.1, -0.1, total_w, max_h + 0.1, "CAD-A-WALL-EXT")
111
+ # north arrow symbol
112
+ msp.add_line((total_w + 1, 0), (total_w + 1, 1.2), dxfattribs={"layer": "CAD-A-SYMB"})
113
+ msp.add_text("N", dxfattribs={"layer": "CAD-A-SYMB", "height": 0.3}).set_placement((total_w + 0.85, 1.3))
114
+
115
+ # Cartiglio CNAPPC (title block) — all required fields on CAD-A-CART.
116
+ cy = -1.2
117
+ rect(0, cy - 2.2, 8, cy, "CAD-A-CART")
118
+ lines = [
119
+ f"PROGETTO: {progetto}", f"CLIENTE: {cliente}", f"ARCHITETTO: {architetto}",
120
+ f"SCALA: {scala}", f"DATA: {data or 'in redazione'}",
121
+ f"SUPERFICIE: {total_area:.1f} mq",
122
+ ]
123
+ for i, line in enumerate(lines):
124
+ msp.add_text(line, dxfattribs={"layer": "CAD-A-CART", "height": 0.22}
125
+ ).set_placement((0.2, cy - 0.4 - i * 0.34))
126
+
127
+ out = Path(out_path).expanduser()
128
+ doc.saveas(str(out))
129
+ return FloorplanResult(
130
+ path=str(out),
131
+ rooms=[{"label": l, "width_m": w, "height_m": h} for l, w, h in placed],
132
+ total_area_m2=round(total_area, 1),
133
+ layers=len(ISO_LAYERS),
134
+ )
@@ -257,6 +257,30 @@ app.add_typer(
257
257
  help="Agenti architetto/interior/cantiere (interior-designer, direzione-lavori...).",
258
258
  )
259
259
 
260
+ from lovarch_cli.commands.progetto_cmd import progetto_app # noqa: E402
261
+
262
+ app.add_typer(
263
+ progetto_app,
264
+ name="progetto",
265
+ help="Workflow composti (progetto interni: concept → render → preventivo → dossier).",
266
+ )
267
+
268
+ from lovarch_cli.commands.archchat_cmd import archchat_app # noqa: E402
269
+
270
+ app.add_typer(
271
+ archchat_app,
272
+ name="archchat",
273
+ help="Leggi le tue conversazioni ArchChat dello studio (sola lettura, nessun credito).",
274
+ )
275
+
276
+ from lovarch_cli.commands.cad_cmd import cad_app # noqa: E402
277
+
278
+ app.add_typer(
279
+ cad_app,
280
+ name="cad",
281
+ help="Genera CAD 2D (DXF) da specifiche di ambienti (deterministico, gratis).",
282
+ )
283
+
260
284
  from lovarch_cli.commands.skills_cmd import skills_app # noqa: E402
261
285
 
262
286
  app.add_typer(
@@ -0,0 +1,115 @@
1
+ """`lovarch archchat` — read your ArchChat (office) conversations from the terminal.
2
+
3
+ lovarch archchat list
4
+ lovarch archchat read <conversation_id>
5
+
6
+ READ-ONLY. This does NOT run the ArchChat AI and costs NO credits — since you're
7
+ already working in your own agent (Claude Code), the point is to pull information
8
+ that already exists in your ArchChat history, not to spend credits per message.
9
+ Reads go through your session with RLS (you only see your own conversations).
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+
15
+ import typer
16
+ from rich.console import Console
17
+ from rich.table import Table
18
+
19
+ console = Console()
20
+ err_console = Console(stderr=True)
21
+
22
+ archchat_app = typer.Typer(
23
+ help="Leggi le tue conversazioni ArchChat (sola lettura, nessun credito).",
24
+ no_args_is_help=True,
25
+ )
26
+
27
+
28
+ async def _get(session, path: str, params: dict) -> list:
29
+ resp = await session.request("GET", path, params=params)
30
+ if resp.status_code != 200:
31
+ raise RuntimeError(f"HTTP {resp.status_code}")
32
+ data = resp.json()
33
+ return data if isinstance(data, list) else []
34
+
35
+
36
+ @archchat_app.command("list")
37
+ def list_command(
38
+ limit: int = typer.Option(20, "--limit", help="Numero di conversazioni."),
39
+ ) -> None:
40
+ """Elenca le tue conversazioni ArchChat (titolo, agente, messaggi, data)."""
41
+ from lovarch_cli.auth.session import LovarchSession
42
+
43
+ session = LovarchSession.load()
44
+ if session is None:
45
+ err_console.print("[red]✗ Non autenticato. Esegui `lovarch login --premium`.[/red]")
46
+ raise typer.Exit(1)
47
+
48
+ try:
49
+ convs = asyncio.run(_get(session, "/rest/v1/archchat_conversations", {
50
+ "select": "id,title,current_agent,message_count,last_message_at",
51
+ "order": "last_message_at.desc",
52
+ "limit": str(max(1, min(limit, 100))),
53
+ }))
54
+ except RuntimeError as exc:
55
+ err_console.print(f"[red]✗ {exc}[/red]"); raise typer.Exit(1)
56
+
57
+ if not convs:
58
+ console.print("[dim]Nessuna conversazione ArchChat.[/dim]")
59
+ return
60
+ table = Table(title="ArchChat — conversazioni", header_style="bold gold1")
61
+ table.add_column("ID", style="cyan", no_wrap=True)
62
+ table.add_column("Titolo")
63
+ table.add_column("Agente")
64
+ table.add_column("Msg", justify="right")
65
+ table.add_column("Ultimo")
66
+ for c in convs:
67
+ last = str(c.get("last_message_at") or "")[:10]
68
+ table.add_row(str(c.get("id", ""))[:8], str(c.get("title") or "—")[:44],
69
+ str(c.get("current_agent") or "—"), str(c.get("message_count") or 0), last)
70
+ console.print(table)
71
+ console.print("[dim]Leggi una conversazione: lovarch archchat read <id>[/dim]")
72
+
73
+
74
+ @archchat_app.command("read")
75
+ def read_command(
76
+ conversation_id: str = typer.Argument(..., help="ID della conversazione (vedi `archchat list`)."),
77
+ limit: int = typer.Option(200, "--limit", help="Numero massimo di messaggi."),
78
+ ) -> None:
79
+ """Leggi i messaggi di una conversazione ArchChat."""
80
+ from lovarch_cli.auth.session import LovarchSession
81
+ from rich.markdown import Markdown
82
+
83
+ session = LovarchSession.load()
84
+ if session is None:
85
+ err_console.print("[red]✗ Non autenticato. Esegui `lovarch login --premium`.[/red]")
86
+ raise typer.Exit(1)
87
+
88
+ async def _run():
89
+ convs = await _get(session, "/rest/v1/archchat_conversations", {
90
+ "select": "id,title", "id": f"eq.{conversation_id}", "limit": "1",
91
+ })
92
+ if not convs:
93
+ raise RuntimeError("conversazione non trovata (o non tua).")
94
+ msgs = await _get(session, "/rest/v1/archchat_messages", {
95
+ "select": "role,content,agent_type,created_at",
96
+ "conversation_id": f"eq.{conversation_id}",
97
+ "order": "created_at.asc",
98
+ "limit": str(max(1, min(limit, 500))),
99
+ })
100
+ return convs[0], msgs
101
+
102
+ try:
103
+ conv, msgs = asyncio.run(_run())
104
+ except RuntimeError as exc:
105
+ err_console.print(f"[red]✗ {exc}[/red]"); raise typer.Exit(1)
106
+
107
+ console.print(f"\n[bold gold1]{conv.get('title') or 'ArchChat'}[/bold gold1]\n")
108
+ role_style = {"user": "cyan", "assistant": "green", "system": "dim"}
109
+ for m in msgs:
110
+ role = str(m.get("role") or "?")
111
+ who = m.get("agent_type") or role
112
+ console.print(f"[{role_style.get(role, 'white')}]● {who}[/]")
113
+ console.print(Markdown(str(m.get("content") or "")))
114
+ console.print("")
115
+ console.print(f"[dim]{len(msgs)} messaggi · sola lettura, nessun credito.[/dim]")
@@ -0,0 +1,107 @@
1
+ """`lovarch cad` — generate real 2D CAD (DXF) from room specs.
2
+
3
+ lovarch cad genera -o pianta.dxf # standard 7-room flat
4
+ lovarch cad genera --rooms rooms.json -o pianta.dxf # your rooms
5
+ lovarch cad genera --brief "attico 90mq, 2 camere" -o pianta.dxf # AI layout (crediti)
6
+
7
+ Deterministic geometry (ezdxf) laid out on the 9 ISO layers with a CNAPPC
8
+ cartiglio — no credits for the geometry. `--brief` uses the platform AI only to
9
+ turn a description into a room list (that step debits credits). The output passes
10
+ `lovarch verifica misure`.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import asyncio
15
+ import json
16
+ from pathlib import Path
17
+
18
+ import typer
19
+ from rich.console import Console
20
+ from rich.table import Table
21
+
22
+ console = Console()
23
+ err_console = Console(stderr=True)
24
+
25
+ cad_app = typer.Typer(
26
+ help="Genera CAD 2D (DXF) da specifiche di ambienti (deterministico, gratis).",
27
+ no_args_is_help=True,
28
+ )
29
+
30
+ _LAYOUT_SYSTEM = (
31
+ "Sei un assistente di progettazione. Data una descrizione, restituisci SOLO "
32
+ "JSON valido con la lista degli ambienti e le dimensioni in metri: "
33
+ '{"rooms": [{"name": "soggiorno", "width_m": 5.0, "height_m": 4.0}, ...]}. '
34
+ "Usa nomi italiani standard (ingresso, soggiorno, cucina, studio, camera, "
35
+ "bagno, lavanderia). Dimensioni realistiche e coerenti con la superficie."
36
+ )
37
+
38
+
39
+ @cad_app.command("genera")
40
+ def genera_command(
41
+ output: Path = typer.Option(Path("pianta.dxf"), "--output", "-o", help="File DXF di output."),
42
+ rooms_file: Path = typer.Option(None, "--rooms", help="JSON con la lista ambienti."),
43
+ brief: str = typer.Option(None, "--brief", help="Descrizione → layout via IA (crediti)."),
44
+ progetto: str = typer.Option("Progetto", "--progetto"),
45
+ cliente: str = typer.Option("Cliente", "--cliente"),
46
+ architetto: str = typer.Option("Arch.", "--architetto"),
47
+ scala: str = typer.Option("1:100", "--scala"),
48
+ data: str = typer.Option("", "--data"),
49
+ verifica: bool = typer.Option(True, "--verifica/--no-verifica", help="Verifica il DXF generato."),
50
+ language: str = typer.Option(None, "--language"),
51
+ ) -> None:
52
+ """Genera una pianta DXF (9 layer ISO + cartiglio CNAPPC)."""
53
+ from lovarch_cli.cad import generate_floorplan
54
+
55
+ rooms = None
56
+ credits_charged = 0
57
+
58
+ if brief:
59
+ # AI layout: turn the description into a room list (debits credits).
60
+ from lovarch_cli.ai import AiGatewayError, LovarchAiGateway
61
+ from lovarch_cli.auth.session import LovarchSession
62
+ from lovarch_cli.i18n import current_lang
63
+
64
+ session = LovarchSession.load()
65
+ if session is None:
66
+ err_console.print("[red]✗ Non autenticato (--brief richiede login). `lovarch login --premium`.[/red]")
67
+ raise typer.Exit(1)
68
+ try:
69
+ res = asyncio.run(LovarchAiGateway(session).generate_text(
70
+ brief, role="executor", system=_LAYOUT_SYSTEM, max_tokens=800,
71
+ language=language or current_lang(), operation_type="cad:layout",
72
+ ))
73
+ credits_charged = res.credits_charged
74
+ parsed = json.loads(res.text[res.text.find("{"):res.text.rfind("}") + 1])
75
+ rooms = parsed.get("rooms")
76
+ except (AiGatewayError, ValueError) as exc:
77
+ err_console.print(f"[red]✗ layout IA non riuscito: {exc}[/red]")
78
+ raise typer.Exit(1)
79
+ elif rooms_file:
80
+ try:
81
+ data_in = json.loads(Path(rooms_file).expanduser().read_text(encoding="utf-8"))
82
+ rooms = data_in.get("rooms") if isinstance(data_in, dict) else data_in
83
+ except (json.JSONDecodeError, OSError) as exc:
84
+ err_console.print(f"[red]✗ rooms non leggibile: {exc}[/red]")
85
+ raise typer.Exit(1)
86
+
87
+ result = generate_floorplan(
88
+ rooms, out_path=output, progetto=progetto, cliente=cliente,
89
+ architetto=architetto, scala=scala, data=data,
90
+ )
91
+
92
+ table = Table(title=f"CAD generato — {result.path}", header_style="bold gold1")
93
+ table.add_column("Ambiente", style="cyan")
94
+ table.add_column("L (m)", justify="right")
95
+ table.add_column("H (m)", justify="right")
96
+ for r in result.rooms:
97
+ table.add_row(str(r["label"]), f"{r['width_m']:.2f}", f"{r['height_m']:.2f}")
98
+ console.print(table)
99
+ console.print(f"[green]✓[/green] {result.path} · {result.total_area_m2} mq · {result.layers} layer ISO"
100
+ + (f" · crediti: {credits_charged}" if credits_charged else " · gratis"))
101
+
102
+ if verifica:
103
+ from lovarch_cli.verify import verify_misure
104
+ report = verify_misure(result.path)
105
+ style = {"PASS": "green", "CONCERNS": "yellow", "REJECT": "red"}.get(report.verdict, "white")
106
+ console.print(f"[dim]verifica misure:[/dim] [bold {style}]{report.verdict}[/bold {style}]"
107
+ + (f" — {report.findings[0]}" if report.findings else ""))
@@ -0,0 +1,79 @@
1
+ """`lovarch progetto` — composed, phased workflows that chain the agents.
2
+
3
+ lovarch progetto interni "attico 90mq, stile caldo minimale, cliente ama il legno"
4
+ lovarch progetto interni "..." --renders 2 --no-preventivo -o dossier.md
5
+
6
+ The interior workflow runs: interior-designer (concept) → optional render(s) →
7
+ optional preventivo → assembled mini-dossier. Text uses the platform models;
8
+ renders debit image credits. Everything after the concept is opt-in.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ from pathlib import Path
14
+
15
+ import typer
16
+ from rich.console import Console
17
+ from rich.markdown import Markdown
18
+
19
+ console = Console()
20
+ err_console = Console(stderr=True)
21
+
22
+ progetto_app = typer.Typer(
23
+ help="Workflow composti (progetto interni: concept → render → preventivo → dossier).",
24
+ no_args_is_help=True,
25
+ )
26
+
27
+
28
+ @progetto_app.command("interni")
29
+ def interni_command(
30
+ brief: str = typer.Argument(..., help="Brief del progetto di interni."),
31
+ renders: int = typer.Option(0, "--renders", help="Numero di render da generare (crediti)."),
32
+ preventivo: bool = typer.Option(True, "--preventivo/--no-preventivo", help="Includere il preventivo."),
33
+ lead: str = typer.Option(None, "--lead", help="ID cliente CRM da usare come contesto."),
34
+ language: str = typer.Option(None, "--language", help="Lingua dell'output."),
35
+ output: Path = typer.Option(None, "--output", "-o", help="Salva il dossier markdown su file."),
36
+ ) -> None:
37
+ """Progetto di interni composto: concept → render → preventivo → mini-dossier."""
38
+ from lovarch_cli.ai import AiGatewayError, InsufficientCreditsError, LovarchAiGateway
39
+ from lovarch_cli.auth.session import LovarchSession
40
+ from lovarch_cli.i18n import current_lang
41
+ from lovarch_cli.workflows.progetto import progetto_interni
42
+
43
+ session = LovarchSession.load()
44
+ if session is None:
45
+ err_console.print("[red]✗ Non autenticato. Esegui `lovarch login --premium`.[/red]")
46
+ raise typer.Exit(1)
47
+
48
+ def _on_phase(name: str) -> None:
49
+ console.print(f"[dim]→ {name}…[/dim]")
50
+
51
+ try:
52
+ result = asyncio.run(progetto_interni(
53
+ LovarchAiGateway(session), brief,
54
+ language=language or current_lang(),
55
+ want_render=renders > 0,
56
+ want_preventivo=preventivo,
57
+ lead_id=lead,
58
+ render_count=max(renders, 1),
59
+ on_phase=_on_phase,
60
+ ))
61
+ except InsufficientCreditsError as exc:
62
+ err_console.print(f"[red]✗ Crediti insufficienti: disponibili {exc.available}, richiesti {exc.needed}.[/red]")
63
+ raise typer.Exit(1)
64
+ except AiGatewayError as exc:
65
+ err_console.print(f"[red]✗ {exc}[/red]")
66
+ raise typer.Exit(1)
67
+
68
+ console.print(Markdown(result.dossier_md))
69
+ for w in result.warnings:
70
+ console.print(f" [yellow]· {w}[/yellow]")
71
+ console.print(f"\n[dim]Crediti addebitati: {result.credits_charged}[/dim]")
72
+ if output:
73
+ out = Path(output).expanduser()
74
+ out.write_text(result.dossier_md, encoding="utf-8")
75
+ # Save renders next to the dossier as render-1.png, … (referenced in the md).
76
+ for i, img in enumerate(result.renders, 1):
77
+ (out.parent / f"render-{i}.png").write_bytes(img)
78
+ console.print(f"[green]✓[/green] dossier salvato: {output}"
79
+ + (f" (+{len(result.renders)} render)" if result.renders else ""))
@@ -10,4 +10,4 @@ Bump policy (semver):
10
10
  - MINOR: new subcommands, new agents, new languages
11
11
  - PATCH: bug fixes, dependency bumps
12
12
  """
13
- __version__ = "0.3.1"
13
+ __version__ = "0.3.2"
@@ -0,0 +1,129 @@
1
+ """Composed workflow `progetto interni` — chains the standalone agents into a
2
+ single phased run: interior-designer (concept) → optional render(s) → optional
3
+ preventivo → assembled mini-dossier (markdown).
4
+
5
+ This is what turns the F11 agents from standalone tools into a real workflow.
6
+ Everything is opt-in (the user chooses what to generate); text runs on the
7
+ platform text models (Sonnet/Opus by role) and renders debit image credits.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ from typing import Any
13
+
14
+ from lovarch_cli.agents import run_agent
15
+ from lovarch_cli.ai import AiGatewayError, LovarchAiGateway
16
+
17
+
18
+ @dataclass
19
+ class ProgettoInterniResult:
20
+ dossier_md: str
21
+ sections: dict = field(default_factory=dict) # concept / preventivo
22
+ renders: list[bytes] = field(default_factory=list) # generated render image bytes
23
+ credits_charged: int = 0
24
+ warnings: list[str] = field(default_factory=list)
25
+
26
+
27
+ async def progetto_interni(
28
+ gateway: LovarchAiGateway,
29
+ brief: str,
30
+ *,
31
+ language: str = "it",
32
+ want_render: bool = False,
33
+ want_preventivo: bool = True,
34
+ lead_id: str | None = None,
35
+ render_count: int = 1,
36
+ on_phase: Any = None, # optional callback(phase_name) for progress UI
37
+ ) -> ProgettoInterniResult:
38
+ """Run the composed interior-design workflow. Each phase is opt-in."""
39
+ def _phase(name: str) -> None:
40
+ if callable(on_phase):
41
+ on_phase(name)
42
+
43
+ credits = 0
44
+ sections: dict = {}
45
+ renders: list[bytes] = []
46
+ warnings: list[str] = []
47
+
48
+ # Phase 1 — Concept (interior-designer, always).
49
+ _phase("interior-designer")
50
+ concept = await run_agent(
51
+ gateway, "interior-designer", brief, language=language, lead_id=lead_id,
52
+ )
53
+ credits += concept.credits_charged
54
+ sections["concept"] = concept.text
55
+
56
+ # Phase 2 — Render(s) (opt-in; debits image credits).
57
+ if want_render:
58
+ _phase("render")
59
+ # Derive a render prompt from the brief; keep it grounded in the concept.
60
+ render_prompt = (
61
+ f"Interior design render, fotorealistico. {brief}. "
62
+ "Illuminazione naturale, materiali coerenti col concept, "
63
+ "composizione elegante da presentazione."
64
+ )
65
+ for _ in range(max(1, render_count)):
66
+ try:
67
+ r = await gateway.generate_image(
68
+ render_prompt, quality="medium", aspect="16:9",
69
+ operation_type="progetto-interni:render",
70
+ )
71
+ credits += r.credits_charged
72
+ if r.image_bytes:
73
+ renders.append(r.image_bytes)
74
+ except AiGatewayError as exc:
75
+ warnings.append(f"render non riuscito: {exc}")
76
+ break
77
+
78
+ # Phase 3 — Preventivo (opt-in; the preventivi agent uses the fiscal context).
79
+ if want_preventivo:
80
+ _phase("preventivi")
81
+ try:
82
+ prev = await run_agent(
83
+ gateway, "preventivi",
84
+ f"Progetto di interni. Brief: {brief}\n\nConcept sintetico:\n{concept.text[:2000]}",
85
+ language=language, lead_id=lead_id,
86
+ )
87
+ credits += prev.credits_charged
88
+ sections["preventivo"] = prev.text
89
+ except AiGatewayError as exc:
90
+ warnings.append(f"preventivo non generato: {exc}")
91
+
92
+ # Phase 4 — Assemble the mini-dossier. Renders are referenced by the file
93
+ # names the command will save them under (render-1.png, …).
94
+ _phase("dossier")
95
+ dossier = _assemble_dossier(brief, sections, len(renders), language)
96
+
97
+ return ProgettoInterniResult(
98
+ dossier_md=dossier,
99
+ sections=sections,
100
+ renders=renders,
101
+ credits_charged=credits,
102
+ warnings=warnings,
103
+ )
104
+
105
+
106
+ def _assemble_dossier(
107
+ brief: str, sections: dict, render_count: int, language: str,
108
+ ) -> str:
109
+ banner = {
110
+ "it": "> **BOZZA** — elaborato generato con IA. Firma e responsabilità "
111
+ "restano del professionista abilitato.",
112
+ "en": "> **DRAFT** — AI-generated. Sign-off and liability remain with the "
113
+ "licensed professional.",
114
+ "pt": "> **RASCUNHO** — gerado por IA. Assinatura e responsabilidade "
115
+ "permanecem do profissional habilitado.",
116
+ "es": "> **BORRADOR** — generado con IA. Firma y responsabilidad del "
117
+ "profesional habilitado.",
118
+ }.get(language, "")
119
+
120
+ parts = [f"# Progetto di interni\n\n{banner}\n", f"**Brief:** {brief}\n"]
121
+ if sections.get("concept"):
122
+ parts.append("## Concept e progetto\n\n" + sections["concept"])
123
+ if render_count:
124
+ parts.append("## Render\n\n" + "\n".join(
125
+ f"![render {i}](render-{i}.png)" for i in range(1, render_count + 1)
126
+ ))
127
+ if sections.get("preventivo"):
128
+ parts.append("## Preventivo / Proposta\n\n" + sections["preventivo"])
129
+ return "\n\n".join(parts) + "\n"
File without changes
File without changes
File without changes