lovarch-cli 0.3.0__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.
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/CHANGELOG.md +19 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/PKG-INFO +9 -1
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/README.md +8 -0
- lovarch_cli-0.3.2/lovarch_cli/cad/__init__.py +4 -0
- lovarch_cli-0.3.2/lovarch_cli/cad/floorplan.py +134 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/cli.py +24 -0
- lovarch_cli-0.3.2/lovarch_cli/commands/archchat_cmd.py +115 -0
- lovarch_cli-0.3.2/lovarch_cli/commands/cad_cmd.py +107 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/do_cmd.py +34 -0
- lovarch_cli-0.3.2/lovarch_cli/commands/progetto_cmd.py +79 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/verifica_cmd.py +82 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/verify/__init__.py +6 -1
- lovarch_cli-0.3.2/lovarch_cli/verify/computo.py +149 -0
- lovarch_cli-0.3.2/lovarch_cli/verify/pratica.py +122 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/version.py +1 -1
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/workflows/platform.py +31 -0
- lovarch_cli-0.3.2/lovarch_cli/workflows/progetto.py +129 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/.gitignore +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/LICENSE +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/__init__.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/__main__.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/agents/__init__.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/agents/prompts.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/agents/runner.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/ai/__init__.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/ai/gateway.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/api.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/auth/__init__.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/auth/keyring_store.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/auth/local_server.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/auth/pkce.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/auth/session.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/clients/__init__.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/clients/factory.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/clients/local_client.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/clients/lovarch_storage.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/clients/lovarch_supabase.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/clients/persistence.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/clients/storage.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/__init__.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/account.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/agent_cmd.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/audit.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/config_cmd.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/consolidate.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/context_cmd.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/dev.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/init.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/jobs_cmd.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/login.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/mcp_cmd.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/run.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/signup.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/skills_cmd.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/status.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/commands/upgrade.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/config.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/config_store.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/credits/__init__.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/credits/base.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/credits/factory.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/credits/local.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/credits/lovarch.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/i18n/__init__.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/i18n/loader.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/i18n/translations/en.json +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/i18n/translations/es.json +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/i18n/translations/it.json +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/i18n/translations/pt.json +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/mcp/__init__.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/mcp/server.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/mcp/tools.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/sample_downloader.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/skills/lovarch-capitolato/SKILL.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/skills/lovarch-direzione-lavori/SKILL.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/skills/lovarch-interior-designer/SKILL.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/skills/lovarch-preventivi/SKILL.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/skills/lovarch-render/SKILL.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/skills/lovarch-verifica-normativa/SKILL.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/README.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/auditor-input.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/bim-engineer.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/briefing-architect.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/cad-engineer.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/capitolato-writer.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/computo-engineer.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/concept-designer.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/contratto-architect.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/deliverable-builder.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/energy-prelim.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/pratiche-it.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/progetto-chief.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/quality-dati.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/quality-misure.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/quality-normativa.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/quality-output.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/agents/regolatorio-it.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/checklists/handoff-quality-gate.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/checklists/quality-dati-checklist.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/checklists/quality-misure-checklist.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/checklists/quality-normativa-checklist.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/checklists/quality-output-checklist.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/config.yaml +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/CHANGELOG.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/agents-prd.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/architettura-progetto-rules.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/handoff-card-template.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/mocks/catasto-visura.json +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/mocks/firma-envelope.json +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/data/prezzario-lombardia-sample.json +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/api_clients.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/architect_profile.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/deliverable_generators.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/generate_attico_brera_dwg.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/generate_chianti_dxf.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/generate_chianti_images.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/generate_real_sample_images.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/generate_sample_assets.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/input_parser.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/lovarch_client.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/pipeline_runner.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/render_dxf_to_png.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/run_palestra_demo.sh +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/simulate_squad_execution.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/scripts/validate-squad.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/tasks/audit-input.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/tasks/compute-metric.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/tasks/consolidate-dossier.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/tasks/generate-cad-plan.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/tasks/generate-ifc-model.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/tasks/write-capitolato.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/templates/asseverazione-tecnica.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/templates/capitolato-uni-11337.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/templates/cila-comune-milano.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/templates/contratto-cnappc.md +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad/workflows/dal-brief-al-cantiere.yaml +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/squad_loader.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/verify/contratto.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/verify/dossier.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/verify/misure.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/verify/normativa.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/lovarch_cli/workflows/__init__.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/pyproject.toml +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/scripts/refresh_squad_vendor.py +0 -0
- {lovarch_cli-0.3.0 → lovarch_cli-0.3.2}/scripts/sync_squad.py +0 -0
|
@@ -5,6 +5,25 @@ 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
|
+
|
|
17
|
+
## [0.3.1] — 2026-07-04
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- `lovarch do script <topic>` — script di contenuto strutturato via piattaforma (schema drift della EF scripts-generate corretto lato monorepo; refund resiliente).
|
|
21
|
+
- `lovarch verifica computo <file> [--region --version]` — confronto deterministico (gratis) delle voci di un computo col prezzario regionale (tabella `prezzari`, seed Lombardia): codici inesistenti, prezzi fuori tolleranza (±20%), unità incoerenti, totale.
|
|
22
|
+
- `lovarch verifica pratica <file> [--tipo CILA|SCIA]` — verifica adversarial (2 modelli) di una pratica edilizia: completezza (catasto, titolo, asseverazione) e coerenza titolo↔intervento.
|
|
23
|
+
|
|
24
|
+
### Notes
|
|
25
|
+
- Persistenza premium: il runner ora crea il progetto CRM completo nell'account dell'utente tramite la EF `cli-persist` (scrittura controllata lato server, nessuna scrittura cross-tenant).
|
|
26
|
+
|
|
8
27
|
## [Unreleased]
|
|
9
28
|
|
|
10
29
|
(No unreleased changes yet — last release was v0.3.0.)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lovarch-cli
|
|
3
|
-
Version: 0.3.
|
|
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,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 ""))
|
|
@@ -154,3 +154,37 @@ def site_command(
|
|
|
154
154
|
err_console.print(f"[red]✗ {out.get('error')}[/red]"); raise typer.Exit(1)
|
|
155
155
|
console.print(f"[green]✓[/green] Sito: [bold]{out['saved_to']}[/bold] ({out.get('bytes', 0)} bytes)")
|
|
156
156
|
|
|
157
|
+
|
|
158
|
+
@do_app.command("script")
|
|
159
|
+
def script_command(
|
|
160
|
+
topic: str = typer.Argument(..., help="Argomento dello script."),
|
|
161
|
+
type: str = typer.Option("reel", "--type", help="reel | post | carousel | video…"),
|
|
162
|
+
goal: str = typer.Option("educare", "--goal", help="Obiettivo del contenuto."),
|
|
163
|
+
tone: str = typer.Option("professionale", "--tone", help="Tono di voce."),
|
|
164
|
+
cta: str = typer.Option("", "--cta", help="Call-to-action."),
|
|
165
|
+
output: Path = typer.Option(None, "--output", "-o", help="Salva lo script su file."),
|
|
166
|
+
language: str = typer.Option(None, "--language"),
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Script di contenuto strutturato via piattaforma (addebita crediti)."""
|
|
169
|
+
wf = _workflows()
|
|
170
|
+
from lovarch_cli.workflows import WorkflowError
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
s = asyncio.run(wf.script(topic, type=type, goal=goal, tone=tone, cta=cta,
|
|
174
|
+
language=_lang(language)))
|
|
175
|
+
except WorkflowError as exc:
|
|
176
|
+
err_console.print(f"[red]✗ {exc}[/red]"); raise typer.Exit(1)
|
|
177
|
+
|
|
178
|
+
title = s.get("title") or topic
|
|
179
|
+
content = s.get("content") or ""
|
|
180
|
+
console.print(f"\n[bold gold1]{title}[/bold gold1]\n")
|
|
181
|
+
if content:
|
|
182
|
+
console.print(content)
|
|
183
|
+
if s.get("keywords"):
|
|
184
|
+
console.print("\n[dim]" + " ".join(s["keywords"]) + "[/dim]")
|
|
185
|
+
if not s.get("persisted", True):
|
|
186
|
+
console.print("\n[yellow]⚠ Non salvato nel tuo account (crediti rimborsati).[/yellow]")
|
|
187
|
+
if output:
|
|
188
|
+
Path(output).expanduser().write_text(content or title, encoding="utf-8")
|
|
189
|
+
console.print(f"\n[green]✓[/green] salvato: {output}")
|
|
190
|
+
|
|
@@ -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 ""))
|
|
@@ -49,6 +49,88 @@ def misure_command(
|
|
|
49
49
|
raise typer.Exit(0 if report.verdict == "PASS" else (2 if report.verdict == "CONCERNS" else 1))
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
@verifica_app.command("computo")
|
|
53
|
+
def computo_command(
|
|
54
|
+
computo: Path = typer.Argument(..., help="Computo metrico (.csv o .json: codice,quantita,prezzo_unitario)."),
|
|
55
|
+
region: str = typer.Option("Lombardia", "--region", help="Regione del prezzario di riferimento."),
|
|
56
|
+
version: str = typer.Option(None, "--version", help="Versione del prezzario (opzionale)."),
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Confronta le voci del computo col prezzario regionale (gratis, deterministico)."""
|
|
59
|
+
from lovarch_cli.auth.session import LovarchSession
|
|
60
|
+
from lovarch_cli.verify import verify_computo
|
|
61
|
+
from lovarch_cli.verify.computo import ComputoError
|
|
62
|
+
|
|
63
|
+
session = LovarchSession.load()
|
|
64
|
+
if session is None:
|
|
65
|
+
err_console.print("[red]✗ Non autenticato. Esegui `lovarch login --premium`.[/red]")
|
|
66
|
+
raise typer.Exit(1)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
report = asyncio.run(verify_computo(session, computo, region=region, version=version))
|
|
70
|
+
except ComputoError as exc:
|
|
71
|
+
err_console.print(f"[red]✗ {exc}[/red]")
|
|
72
|
+
raise typer.Exit(1)
|
|
73
|
+
|
|
74
|
+
table = Table(title=f"verifica computo — {computo.name}", header_style="bold gold1")
|
|
75
|
+
table.add_column("Metrica", style="cyan")
|
|
76
|
+
table.add_column("Valore", justify="right")
|
|
77
|
+
table.add_row("Prezzario", str(report.stats.get("prezzario", "—")))
|
|
78
|
+
table.add_row("Voci totali", str(report.stats.get("voci_totali", "—")))
|
|
79
|
+
table.add_row("Voci verificate", str(report.stats.get("voci_verificate", "—")))
|
|
80
|
+
table.add_row("Codici sconosciuti", str(report.stats.get("codici_sconosciuti", 0)))
|
|
81
|
+
table.add_row("Prezzi fuori tolleranza", str(report.stats.get("prezzi_fuori_tolleranza", 0)))
|
|
82
|
+
table.add_row("Unità incoerenti", str(report.stats.get("unita_incoerenti", 0)))
|
|
83
|
+
table.add_row("Totale computo (€)", f"{report.stats.get('totale_computo_eur', 0):,.2f}")
|
|
84
|
+
console.print(table)
|
|
85
|
+
for f in report.findings:
|
|
86
|
+
console.print(f" [yellow]·[/yellow] {f}")
|
|
87
|
+
_print_verdict(report.verdict)
|
|
88
|
+
raise typer.Exit(0 if report.verdict == "PASS" else (2 if report.verdict == "CONCERNS" else 1))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@verifica_app.command("pratica")
|
|
92
|
+
def pratica_command(
|
|
93
|
+
documento: Path = typer.Argument(..., help="Pratica edilizia (.pdf, .md, .txt)."),
|
|
94
|
+
tipo: str = typer.Option(None, "--tipo", help="CILA | SCIA (se noto)."),
|
|
95
|
+
language: str = typer.Option(None, "--language", help="Lingua del report."),
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Verifica adversarial di una pratica CILA/SCIA (2 modelli · debita crediti)."""
|
|
98
|
+
from lovarch_cli.ai import LovarchAiGateway
|
|
99
|
+
from lovarch_cli.auth.session import LovarchSession
|
|
100
|
+
from lovarch_cli.i18n import current_lang
|
|
101
|
+
from lovarch_cli.verify import verify_pratica
|
|
102
|
+
from lovarch_cli.verify.normativa import NormativaError
|
|
103
|
+
|
|
104
|
+
session = LovarchSession.load()
|
|
105
|
+
if session is None:
|
|
106
|
+
err_console.print("[red]✗ Non autenticato. Esegui `lovarch login --premium`.[/red]")
|
|
107
|
+
raise typer.Exit(1)
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
report = asyncio.run(verify_pratica(
|
|
111
|
+
LovarchAiGateway(session), documento, tipo=tipo,
|
|
112
|
+
language=language or current_lang(),
|
|
113
|
+
))
|
|
114
|
+
except NormativaError as exc:
|
|
115
|
+
err_console.print(f"[red]✗ {exc}[/red]")
|
|
116
|
+
raise typer.Exit(1)
|
|
117
|
+
|
|
118
|
+
console.print(f"[dim]Tipo pratica: {report.tipo}[/dim]")
|
|
119
|
+
table = Table(title=f"verifica pratica — {documento.name}", header_style="bold gold1")
|
|
120
|
+
table.add_column("Area", style="cyan")
|
|
121
|
+
table.add_column("Severità", justify="center")
|
|
122
|
+
table.add_column("Motivo")
|
|
123
|
+
for f in report.findings:
|
|
124
|
+
sev = str(f.get("severity", "")).lower()
|
|
125
|
+
icon = {"critical": "[red]✗[/red]", "concern": "[yellow]?[/yellow]", "info": "[dim]·[/dim]"}.get(sev, "?")
|
|
126
|
+
table.add_row(str(f.get("area", "—")), icon, str(f.get("reason", ""))[:100])
|
|
127
|
+
console.print(table)
|
|
128
|
+
console.print("[dim]Firma e responsabilità restano del tecnico abilitato (BOZZA).[/dim]")
|
|
129
|
+
console.print(f"[dim]Crediti addebitati: {report.credits_charged}[/dim]")
|
|
130
|
+
_print_verdict(report.verdict)
|
|
131
|
+
raise typer.Exit(0 if report.verdict == "PASS" else (2 if report.verdict == "CONCERNS" else 1))
|
|
132
|
+
|
|
133
|
+
|
|
52
134
|
@verifica_app.command("normativa")
|
|
53
135
|
def normativa_command(
|
|
54
136
|
documento: Path = typer.Argument(..., help="Documento da verificare (.pdf, .md, .txt)."),
|