lovarch-cli 0.2.1__py3-none-any.whl
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/__init__.py +16 -0
- lovarch_cli/__main__.py +10 -0
- lovarch_cli/ai/__init__.py +21 -0
- lovarch_cli/ai/gateway.py +240 -0
- lovarch_cli/api.py +111 -0
- lovarch_cli/auth/__init__.py +32 -0
- lovarch_cli/auth/keyring_store.py +214 -0
- lovarch_cli/auth/local_server.py +165 -0
- lovarch_cli/auth/pkce.py +57 -0
- lovarch_cli/auth/session.py +189 -0
- lovarch_cli/cli.py +262 -0
- lovarch_cli/clients/__init__.py +33 -0
- lovarch_cli/clients/factory.py +54 -0
- lovarch_cli/clients/local_client.py +432 -0
- lovarch_cli/clients/lovarch_storage.py +174 -0
- lovarch_cli/clients/lovarch_supabase.py +295 -0
- lovarch_cli/clients/persistence.py +166 -0
- lovarch_cli/clients/storage.py +66 -0
- lovarch_cli/commands/__init__.py +10 -0
- lovarch_cli/commands/account.py +172 -0
- lovarch_cli/commands/audit.py +394 -0
- lovarch_cli/commands/config_cmd.py +80 -0
- lovarch_cli/commands/consolidate.py +217 -0
- lovarch_cli/commands/context_cmd.py +73 -0
- lovarch_cli/commands/dev.py +287 -0
- lovarch_cli/commands/do_cmd.py +120 -0
- lovarch_cli/commands/init.py +218 -0
- lovarch_cli/commands/jobs_cmd.py +95 -0
- lovarch_cli/commands/login.py +202 -0
- lovarch_cli/commands/mcp_cmd.py +26 -0
- lovarch_cli/commands/run.py +375 -0
- lovarch_cli/commands/signup.py +185 -0
- lovarch_cli/commands/status.py +243 -0
- lovarch_cli/commands/upgrade.py +108 -0
- lovarch_cli/commands/verifica_cmd.py +174 -0
- lovarch_cli/config.py +101 -0
- lovarch_cli/config_store.py +111 -0
- lovarch_cli/credits/__init__.py +35 -0
- lovarch_cli/credits/base.py +84 -0
- lovarch_cli/credits/factory.py +36 -0
- lovarch_cli/credits/local.py +34 -0
- lovarch_cli/credits/lovarch.py +56 -0
- lovarch_cli/i18n/__init__.py +27 -0
- lovarch_cli/i18n/loader.py +121 -0
- lovarch_cli/i18n/translations/en.json +168 -0
- lovarch_cli/i18n/translations/es.json +168 -0
- lovarch_cli/i18n/translations/it.json +168 -0
- lovarch_cli/i18n/translations/pt.json +168 -0
- lovarch_cli/mcp/__init__.py +9 -0
- lovarch_cli/mcp/server.py +199 -0
- lovarch_cli/mcp/tools.py +372 -0
- lovarch_cli/sample_downloader.py +255 -0
- lovarch_cli/squad/README.md +206 -0
- lovarch_cli/squad/agents/auditor-input.md +353 -0
- lovarch_cli/squad/agents/bim-engineer.md +404 -0
- lovarch_cli/squad/agents/briefing-architect.md +249 -0
- lovarch_cli/squad/agents/cad-engineer.md +278 -0
- lovarch_cli/squad/agents/capitolato-writer.md +256 -0
- lovarch_cli/squad/agents/computo-engineer.md +258 -0
- lovarch_cli/squad/agents/concept-designer.md +399 -0
- lovarch_cli/squad/agents/contratto-architect.md +243 -0
- lovarch_cli/squad/agents/deliverable-builder.md +253 -0
- lovarch_cli/squad/agents/energy-prelim.md +388 -0
- lovarch_cli/squad/agents/pratiche-it.md +251 -0
- lovarch_cli/squad/agents/progetto-chief.md +768 -0
- lovarch_cli/squad/agents/quality-dati.md +409 -0
- lovarch_cli/squad/agents/quality-misure.md +418 -0
- lovarch_cli/squad/agents/quality-normativa.md +417 -0
- lovarch_cli/squad/agents/quality-output.md +436 -0
- lovarch_cli/squad/agents/regolatorio-it.md +278 -0
- lovarch_cli/squad/checklists/handoff-quality-gate.md +232 -0
- lovarch_cli/squad/checklists/quality-dati-checklist.md +134 -0
- lovarch_cli/squad/checklists/quality-misure-checklist.md +139 -0
- lovarch_cli/squad/checklists/quality-normativa-checklist.md +121 -0
- lovarch_cli/squad/checklists/quality-output-checklist.md +116 -0
- lovarch_cli/squad/config.yaml +408 -0
- lovarch_cli/squad/data/CHANGELOG.md +272 -0
- lovarch_cli/squad/data/agents-prd.md +428 -0
- lovarch_cli/squad/data/architettura-progetto-rules.md +328 -0
- lovarch_cli/squad/data/handoff-card-template.md +231 -0
- lovarch_cli/squad/data/mocks/catasto-visura.json +72 -0
- lovarch_cli/squad/data/mocks/firma-envelope.json +43 -0
- lovarch_cli/squad/data/prezzario-lombardia-sample.json +312 -0
- lovarch_cli/squad/scripts/api_clients.py +206 -0
- lovarch_cli/squad/scripts/architect_profile.py +276 -0
- lovarch_cli/squad/scripts/deliverable_generators.py +844 -0
- lovarch_cli/squad/scripts/generate_attico_brera_dwg.py +369 -0
- lovarch_cli/squad/scripts/generate_chianti_dxf.py +368 -0
- lovarch_cli/squad/scripts/generate_chianti_images.py +223 -0
- lovarch_cli/squad/scripts/generate_real_sample_images.py +189 -0
- lovarch_cli/squad/scripts/generate_sample_assets.py +382 -0
- lovarch_cli/squad/scripts/lovarch_client.py +1046 -0
- lovarch_cli/squad/scripts/pipeline_runner.py +2095 -0
- lovarch_cli/squad/scripts/render_dxf_to_png.py +57 -0
- lovarch_cli/squad/scripts/run_palestra_demo.sh +277 -0
- lovarch_cli/squad/scripts/simulate_squad_execution.py +515 -0
- lovarch_cli/squad/scripts/validate-squad.py +383 -0
- lovarch_cli/squad/tasks/audit-input.md +146 -0
- lovarch_cli/squad/tasks/compute-metric.md +105 -0
- lovarch_cli/squad/tasks/consolidate-dossier.md +187 -0
- lovarch_cli/squad/tasks/generate-cad-plan.md +120 -0
- lovarch_cli/squad/tasks/generate-ifc-model.md +108 -0
- lovarch_cli/squad/tasks/write-capitolato.md +100 -0
- lovarch_cli/squad/templates/asseverazione-tecnica.md +126 -0
- lovarch_cli/squad/templates/capitolato-uni-11337.md +235 -0
- lovarch_cli/squad/templates/cila-comune-milano.md +177 -0
- lovarch_cli/squad/templates/contratto-cnappc.md +220 -0
- lovarch_cli/squad/workflows/dal-brief-al-cantiere.yaml +218 -0
- lovarch_cli/squad_loader.py +114 -0
- lovarch_cli/verify/__init__.py +15 -0
- lovarch_cli/verify/contratto.py +110 -0
- lovarch_cli/verify/dossier.py +97 -0
- lovarch_cli/verify/misure.py +83 -0
- lovarch_cli/verify/normativa.py +178 -0
- lovarch_cli/version.py +13 -0
- lovarch_cli/workflows/__init__.py +9 -0
- lovarch_cli/workflows/platform.py +212 -0
- lovarch_cli-0.2.1.dist-info/METADATA +232 -0
- lovarch_cli-0.2.1.dist-info/RECORD +122 -0
- lovarch_cli-0.2.1.dist-info/WHEEL +4 -0
- lovarch_cli-0.2.1.dist-info/entry_points.txt +3 -0
- lovarch_cli-0.2.1.dist-info/licenses/LICENSE +38 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""verifica normativa — adversarial two-model check of normative citations.
|
|
2
|
+
|
|
3
|
+
Pipeline:
|
|
4
|
+
0. Extract document text (pypdf for .pdf; plain read for .md/.txt).
|
|
5
|
+
1. Deterministic pre-pass: regex of canonical Italian building references
|
|
6
|
+
(same table the squad's Q2 verifier uses) — free.
|
|
7
|
+
2. EXTRACT (Sonnet 5 · executor): list every normative citation with its
|
|
8
|
+
claimed subject, as strict JSON.
|
|
9
|
+
3. REFUTE (Opus 4.8 · verifier): adversarially challenge each citation —
|
|
10
|
+
does the article exist? does it regulate what the document claims?
|
|
11
|
+
Defaults to skeptical; returns per-citation verdicts as strict JSON.
|
|
12
|
+
4. Merge: any refuted citation → REJECT; doubts → CONCERNS.
|
|
13
|
+
|
|
14
|
+
Credits: debited per real tokens via cli-ai-text (executor + verifier calls).
|
|
15
|
+
Report language follows the user's configured language.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import re
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
CANONICAL_REFS: dict[str, list[str]] = {
|
|
26
|
+
"DPR 380/2001": [r"dpr\s*380", r"d\.p\.r\.\s*380", r"testo unico edilizia"],
|
|
27
|
+
"UNI 11337": [r"uni\s*11337"],
|
|
28
|
+
"CAM 2025": [r"cam\s*edilizia", r"dm\s*23/06/2022", r"criteri ambientali"],
|
|
29
|
+
"NTC 2018": [r"ntc\s*2018", r"dm\s*17/01/2018"],
|
|
30
|
+
"D.Lgs 81/2008": [r"d\.?lgs\.?\s*81"],
|
|
31
|
+
"D.Lgs 42/2004": [r"d\.?lgs\.?\s*42", r"codice beni culturali"],
|
|
32
|
+
"L. 49/2023": [r"49/2023", r"equo compenso"],
|
|
33
|
+
"GDPR": [r"gdpr", r"2016/679"],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_EXTRACT_SYSTEM = (
|
|
37
|
+
"Sei un assistente tecnico-normativo per l'edilizia italiana. Estrai dal "
|
|
38
|
+
"documento OGNI citazione normativa (leggi, decreti, norme UNI, articoli) "
|
|
39
|
+
"insieme a ciò che il documento AFFERMA che quella norma regoli. Rispondi "
|
|
40
|
+
"SOLO con JSON valido, nessun altro testo: "
|
|
41
|
+
'{"citations": [{"reference": "...", "article": "... o null", '
|
|
42
|
+
'"claim": "cosa il documento afferma che questa norma regola"}]}'
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
_REFUTE_SYSTEM = (
|
|
46
|
+
"Sei un verificatore normativo ADVERSARIALE per l'edilizia italiana. Per "
|
|
47
|
+
"ogni citazione, cerca attivamente di CONFUTARLA: l'articolo esiste? La "
|
|
48
|
+
"norma regola davvero ciò che viene affermato? Nel dubbio, sii scettico e "
|
|
49
|
+
"segnala. Esempi noti: DPR 380/2001 art. 99 riguarda il conglomerato "
|
|
50
|
+
"cementizio armato (NON le CILA); la L.49/2023 (equo compenso) vincola solo "
|
|
51
|
+
"verso contraenti forti (PA/banche/grandi imprese), NON i clienti privati. "
|
|
52
|
+
"Rispondi SOLO con JSON valido: "
|
|
53
|
+
'{"verdicts": [{"reference": "...", "status": "ok" | "refuted" | "doubt", '
|
|
54
|
+
'"reason": "spiegazione breve"}]}'
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class NormativaReport:
|
|
60
|
+
verdict: str # PASS | CONCERNS | REJECT
|
|
61
|
+
citations: list[dict] = field(default_factory=list)
|
|
62
|
+
verdicts: list[dict] = field(default_factory=list)
|
|
63
|
+
canonical_found: list[str] = field(default_factory=list)
|
|
64
|
+
credits_charged: int = 0
|
|
65
|
+
notes: list[str] = field(default_factory=list)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class NormativaError(Exception):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def extract_text(path: str | Path) -> str:
|
|
73
|
+
"""Read document text — pypdf for PDFs, plain read otherwise."""
|
|
74
|
+
p = Path(path).expanduser()
|
|
75
|
+
if not p.is_file():
|
|
76
|
+
raise NormativaError(f"file non trovato: {p}")
|
|
77
|
+
if p.suffix.lower() == ".pdf":
|
|
78
|
+
try:
|
|
79
|
+
from pypdf import PdfReader
|
|
80
|
+
except ImportError as exc:
|
|
81
|
+
raise NormativaError(
|
|
82
|
+
"pypdf non installato — necessario per i PDF (pip install pypdf)."
|
|
83
|
+
) from exc
|
|
84
|
+
try:
|
|
85
|
+
reader = PdfReader(str(p))
|
|
86
|
+
return " ".join((page.extract_text() or "") for page in reader.pages)
|
|
87
|
+
except Exception as exc: # noqa: BLE001
|
|
88
|
+
raise NormativaError(f"PDF non leggibile: {str(exc)[:120]}") from exc
|
|
89
|
+
return p.read_text(encoding="utf-8", errors="ignore")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def scan_canonical(text: str) -> list[str]:
|
|
93
|
+
"""Deterministic pre-pass: which canonical references appear in the text."""
|
|
94
|
+
low = text.lower()
|
|
95
|
+
return [name for name, patterns in CANONICAL_REFS.items()
|
|
96
|
+
if any(re.search(pat, low) for pat in patterns)]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _parse_json(raw: str) -> dict:
|
|
100
|
+
"""Robust JSON extraction from an LLM answer (tolerates fences/preamble)."""
|
|
101
|
+
raw = raw.strip()
|
|
102
|
+
if raw.startswith("```"):
|
|
103
|
+
raw = re.sub(r"^```(?:json)?\s*|\s*```$", "", raw, flags=re.S)
|
|
104
|
+
try:
|
|
105
|
+
return json.loads(raw)
|
|
106
|
+
except json.JSONDecodeError:
|
|
107
|
+
match = re.search(r"\{.*\}", raw, flags=re.S)
|
|
108
|
+
if match:
|
|
109
|
+
try:
|
|
110
|
+
return json.loads(match.group(0))
|
|
111
|
+
except json.JSONDecodeError:
|
|
112
|
+
pass
|
|
113
|
+
raise NormativaError("il modello non ha restituito JSON valido.")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
async def verify_normativa(
|
|
117
|
+
gateway: Any,
|
|
118
|
+
document_path: str | Path,
|
|
119
|
+
*,
|
|
120
|
+
language: str = "it",
|
|
121
|
+
max_chars: int = 60_000,
|
|
122
|
+
) -> NormativaReport:
|
|
123
|
+
"""Adversarial normative check of one document. Debits credits (2 calls)."""
|
|
124
|
+
text = extract_text(document_path)
|
|
125
|
+
if not text.strip():
|
|
126
|
+
raise NormativaError("documento vuoto o testo non estraibile.")
|
|
127
|
+
if len(text) > max_chars:
|
|
128
|
+
text = text[:max_chars]
|
|
129
|
+
|
|
130
|
+
canonical = scan_canonical(text)
|
|
131
|
+
credits = 0
|
|
132
|
+
|
|
133
|
+
# 1 · EXTRACT (executor → Sonnet 5)
|
|
134
|
+
extraction = await gateway.generate_text(
|
|
135
|
+
f"DOCUMENTO:\n\n{text}",
|
|
136
|
+
role="executor",
|
|
137
|
+
system=_EXTRACT_SYSTEM,
|
|
138
|
+
max_tokens=2000,
|
|
139
|
+
language=language,
|
|
140
|
+
operation_type="verify:normativa:extract",
|
|
141
|
+
)
|
|
142
|
+
credits += extraction.credits_charged
|
|
143
|
+
citations = _parse_json(extraction.text).get("citations") or []
|
|
144
|
+
if not citations:
|
|
145
|
+
return NormativaReport(
|
|
146
|
+
verdict="CONCERNS",
|
|
147
|
+
canonical_found=canonical,
|
|
148
|
+
credits_charged=credits,
|
|
149
|
+
notes=["nessuna citazione normativa trovata nel documento"],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# 2 · REFUTE (verifier → Opus 4.8) — independent adversarial pass
|
|
153
|
+
refutation = await gateway.generate_text(
|
|
154
|
+
"CITAZIONI DA CONFUTARE:\n\n" + json.dumps(citations, ensure_ascii=False),
|
|
155
|
+
role="verifier",
|
|
156
|
+
system=_REFUTE_SYSTEM,
|
|
157
|
+
max_tokens=2500,
|
|
158
|
+
language=language,
|
|
159
|
+
operation_type="verify:normativa:refute",
|
|
160
|
+
)
|
|
161
|
+
credits += refutation.credits_charged
|
|
162
|
+
verdicts = _parse_json(refutation.text).get("verdicts") or []
|
|
163
|
+
|
|
164
|
+
statuses = [str(v.get("status", "doubt")).lower() for v in verdicts]
|
|
165
|
+
if any(s == "refuted" for s in statuses):
|
|
166
|
+
overall = "REJECT"
|
|
167
|
+
elif any(s == "doubt" for s in statuses) or not verdicts:
|
|
168
|
+
overall = "CONCERNS"
|
|
169
|
+
else:
|
|
170
|
+
overall = "PASS"
|
|
171
|
+
|
|
172
|
+
return NormativaReport(
|
|
173
|
+
verdict=overall,
|
|
174
|
+
citations=citations,
|
|
175
|
+
verdicts=verdicts,
|
|
176
|
+
canonical_found=canonical,
|
|
177
|
+
credits_charged=credits,
|
|
178
|
+
)
|
lovarch_cli/version.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""lovarch-cli version — single source of truth.
|
|
2
|
+
|
|
3
|
+
Read by:
|
|
4
|
+
- pyproject.toml [tool.hatch.version] for build
|
|
5
|
+
- lovarch_cli/__init__.py for runtime
|
|
6
|
+
- arch --version flag
|
|
7
|
+
|
|
8
|
+
Bump policy (semver):
|
|
9
|
+
- MAJOR: breaking CLI API (subcommand removal, flag rename)
|
|
10
|
+
- MINOR: new subcommands, new agents, new languages
|
|
11
|
+
- PATCH: bug fixes, dependency bumps
|
|
12
|
+
"""
|
|
13
|
+
__version__ = "0.2.1"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Platform workflows — Lovarch's product features exposed to the CLI/MCP.
|
|
2
|
+
|
|
3
|
+
Each workflow wraps an existing platform Edge Function (the same ones the web
|
|
4
|
+
app uses), called with the user's premium session so credits are debited
|
|
5
|
+
identically to the app. Cost is reported ONLY in the user's credits.
|
|
6
|
+
"""
|
|
7
|
+
from lovarch_cli.workflows.platform import PlatformWorkflows, WorkflowError
|
|
8
|
+
|
|
9
|
+
__all__ = ["PlatformWorkflows", "WorkflowError"]
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Wrappers over Lovarch platform Edge Functions (Render Studio, branding,
|
|
2
|
+
contents). First batch of the TOP-12 catalog: render, colors, copy.
|
|
3
|
+
|
|
4
|
+
Design rules (Pablo, 2026-07-03):
|
|
5
|
+
- Cost is the USER's credits, debited server-side by each EF — never expose
|
|
6
|
+
provider amounts. Wrappers report outputs; balance can be read via
|
|
7
|
+
`lovarch credits`.
|
|
8
|
+
- Output language follows the user's configured language — every call takes an
|
|
9
|
+
explicit ``language`` that callers should source from cli-user-context.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import base64
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from lovarch_cli.auth.session import LovarchSession
|
|
19
|
+
|
|
20
|
+
# Render generation regularly takes 30-120s (image models).
|
|
21
|
+
_RENDER_TIMEOUT = 300.0
|
|
22
|
+
_DEFAULT_TIMEOUT = 120.0
|
|
23
|
+
|
|
24
|
+
RENDER_MODES = {
|
|
25
|
+
None, # legacy sketch/text → render (2D)
|
|
26
|
+
"room_render", # 3D room render
|
|
27
|
+
"render_3d",
|
|
28
|
+
"plan_to_3d", # 2D plan → 3D
|
|
29
|
+
"lighting_only",
|
|
30
|
+
"closeup_detail",
|
|
31
|
+
"closeup_angle",
|
|
32
|
+
}
|
|
33
|
+
COLOR_STYLES = {"modern", "vintage", "natural", "bold", "custom"}
|
|
34
|
+
COPY_MODES = {"post", "story", "carousel"}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class WorkflowError(Exception):
|
|
38
|
+
"""A platform workflow call failed (message is user-safe)."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class RenderResult:
|
|
43
|
+
image_bytes: bytes | None # decoded when the EF returns a data URL
|
|
44
|
+
image_url: str | None # set when the EF returns a hosted URL
|
|
45
|
+
message: str | None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PlatformWorkflows:
|
|
49
|
+
"""Platform feature calls bound to a premium session."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, session: LovarchSession) -> None:
|
|
52
|
+
self._session = session
|
|
53
|
+
|
|
54
|
+
async def _invoke(
|
|
55
|
+
self, ef: str, body: dict, *, timeout: float = _DEFAULT_TIMEOUT
|
|
56
|
+
) -> dict:
|
|
57
|
+
response = await self._session.request(
|
|
58
|
+
"POST", f"/functions/v1/{ef}", json=body, timeout=timeout
|
|
59
|
+
)
|
|
60
|
+
try:
|
|
61
|
+
data = response.json()
|
|
62
|
+
except ValueError:
|
|
63
|
+
data = {}
|
|
64
|
+
if response.status_code == 402:
|
|
65
|
+
raise WorkflowError(
|
|
66
|
+
"Crediti insufficienti per questa operazione. "
|
|
67
|
+
f"(disponibili: {data.get('credits_available', '?')}, "
|
|
68
|
+
f"richiesti: {data.get('credits_needed', '?')})"
|
|
69
|
+
)
|
|
70
|
+
if response.status_code != 200:
|
|
71
|
+
detail = data.get("error") if isinstance(data, dict) else None
|
|
72
|
+
raise WorkflowError(f"{ef}: {detail or f'HTTP {response.status_code}'}")
|
|
73
|
+
return data if isinstance(data, dict) else {}
|
|
74
|
+
|
|
75
|
+
# ── Render Studio ──────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
async def render(
|
|
78
|
+
self,
|
|
79
|
+
description: str,
|
|
80
|
+
*,
|
|
81
|
+
mode: str | None = None,
|
|
82
|
+
render_style: str = "moderno",
|
|
83
|
+
aspect_ratio: str = "16:9",
|
|
84
|
+
reference_image_path: str | Path | None = None,
|
|
85
|
+
language: str = "it",
|
|
86
|
+
) -> RenderResult:
|
|
87
|
+
"""Photorealistic render via render-ai-generate.
|
|
88
|
+
|
|
89
|
+
``mode=None`` = sketch/text→render (2D). ``room_render``/``plan_to_3d``
|
|
90
|
+
= 3D modes (higher credit cost, debited server-side). A reference image
|
|
91
|
+
(sketch, photo, plan) is sent base64-inline when provided.
|
|
92
|
+
"""
|
|
93
|
+
if mode not in RENDER_MODES:
|
|
94
|
+
raise WorkflowError(
|
|
95
|
+
f"Modo render non valido: {mode}. Validi: "
|
|
96
|
+
+ ", ".join(sorted(m or "(default)" for m in RENDER_MODES))
|
|
97
|
+
)
|
|
98
|
+
body: dict[str, Any] = {
|
|
99
|
+
"description": description,
|
|
100
|
+
"renderStyle": render_style,
|
|
101
|
+
"aspectRatio": aspect_ratio,
|
|
102
|
+
"language": language,
|
|
103
|
+
}
|
|
104
|
+
if mode:
|
|
105
|
+
body["mode"] = mode
|
|
106
|
+
if reference_image_path:
|
|
107
|
+
raw = Path(reference_image_path).expanduser().read_bytes()
|
|
108
|
+
body["referenceImage"] = (
|
|
109
|
+
"data:image/png;base64," + base64.b64encode(raw).decode()
|
|
110
|
+
)
|
|
111
|
+
data = await self._invoke("render-ai-generate", body, timeout=_RENDER_TIMEOUT)
|
|
112
|
+
image = data.get("image")
|
|
113
|
+
if not image:
|
|
114
|
+
raise WorkflowError("render-ai-generate non ha restituito un'immagine.")
|
|
115
|
+
if isinstance(image, str) and image.startswith("data:"):
|
|
116
|
+
payload = image.split(",", 1)[1]
|
|
117
|
+
return RenderResult(
|
|
118
|
+
image_bytes=base64.b64decode(payload),
|
|
119
|
+
image_url=None,
|
|
120
|
+
message=data.get("message"),
|
|
121
|
+
)
|
|
122
|
+
return RenderResult(image_bytes=None, image_url=str(image), message=data.get("message"))
|
|
123
|
+
|
|
124
|
+
# ── Branding: color palette ────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
async def colors(
|
|
127
|
+
self,
|
|
128
|
+
*,
|
|
129
|
+
style: str = "modern",
|
|
130
|
+
base_colors: list[str] | None = None,
|
|
131
|
+
image_url: str | None = None,
|
|
132
|
+
language: str = "it",
|
|
133
|
+
) -> dict:
|
|
134
|
+
"""Color palette via colors-generate. With ``image_url`` the palette is
|
|
135
|
+
extracted from the image; otherwise generated from style/base colors."""
|
|
136
|
+
if style not in COLOR_STYLES:
|
|
137
|
+
raise WorkflowError(
|
|
138
|
+
f"Stile non valido: {style}. Validi: {', '.join(sorted(COLOR_STYLES))}"
|
|
139
|
+
)
|
|
140
|
+
body: dict[str, Any] = {"style": style, "language": language}
|
|
141
|
+
if base_colors:
|
|
142
|
+
body["baseColors"] = base_colors
|
|
143
|
+
if image_url:
|
|
144
|
+
body["imageUrl"] = image_url
|
|
145
|
+
body["extractFromImage"] = True
|
|
146
|
+
return await self._invoke("colors-generate", body)
|
|
147
|
+
|
|
148
|
+
# ── Contents: copy/caption ─────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
async def copy(
|
|
151
|
+
self,
|
|
152
|
+
brief: str,
|
|
153
|
+
*,
|
|
154
|
+
mode: str = "post",
|
|
155
|
+
slide_count: int = 5,
|
|
156
|
+
language: str = "it",
|
|
157
|
+
) -> dict:
|
|
158
|
+
"""Marketing copy (caption + hashtags + headline/slides) via
|
|
159
|
+
content-copy-generate."""
|
|
160
|
+
if mode not in COPY_MODES:
|
|
161
|
+
raise WorkflowError(
|
|
162
|
+
f"Formato non valido: {mode}. Validi: {', '.join(sorted(COPY_MODES))}"
|
|
163
|
+
)
|
|
164
|
+
data = await self._invoke("content-copy-generate", {
|
|
165
|
+
"brief": brief,
|
|
166
|
+
"mode": mode,
|
|
167
|
+
"slideCount": slide_count,
|
|
168
|
+
"language": language,
|
|
169
|
+
})
|
|
170
|
+
return data.get("copy") or data
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ── Async jobs (video / Shotstack / upscale) ───────────────────────────────
|
|
174
|
+
|
|
175
|
+
# User-facing job fields. cost_usd is deliberately EXCLUDED — user-facing cost
|
|
176
|
+
# is always credits (credits_charged/credits_refunded).
|
|
177
|
+
_JOB_FIELDS = (
|
|
178
|
+
"id,engine,model,status,prompt,output_url,credits_charged,credits_refunded,"
|
|
179
|
+
"error_message,duration,aspect_ratio,created_at,done_at"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class JobsMixin:
|
|
184
|
+
"""PostgREST reads of content_video_jobs (owner RLS) via the user session."""
|
|
185
|
+
|
|
186
|
+
async def jobs_list(self, *, limit: int = 10) -> list[dict]:
|
|
187
|
+
response = await self._session.request(
|
|
188
|
+
"GET",
|
|
189
|
+
f"/rest/v1/content_video_jobs?select={_JOB_FIELDS}"
|
|
190
|
+
f"&order=created_at.desc&limit={limit}",
|
|
191
|
+
)
|
|
192
|
+
if response.status_code != 200:
|
|
193
|
+
raise WorkflowError(f"jobs: HTTP {response.status_code}")
|
|
194
|
+
data = response.json()
|
|
195
|
+
return data if isinstance(data, list) else []
|
|
196
|
+
|
|
197
|
+
async def job_status(self, job_id: str) -> dict:
|
|
198
|
+
response = await self._session.request(
|
|
199
|
+
"GET",
|
|
200
|
+
f"/rest/v1/content_video_jobs?id=eq.{job_id}&select={_JOB_FIELDS}",
|
|
201
|
+
)
|
|
202
|
+
if response.status_code != 200:
|
|
203
|
+
raise WorkflowError(f"job {job_id}: HTTP {response.status_code}")
|
|
204
|
+
data = response.json()
|
|
205
|
+
if not data:
|
|
206
|
+
raise WorkflowError(f"job non trovato: {job_id}")
|
|
207
|
+
return data[0]
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# Attach the mixin methods to PlatformWorkflows (single public class).
|
|
211
|
+
PlatformWorkflows.jobs_list = JobsMixin.jobs_list # type: ignore[attr-defined]
|
|
212
|
+
PlatformWorkflows.job_status = JobsMixin.job_status # type: ignore[attr-defined]
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lovarch-cli
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: AI-powered architectural project execution CLI by Lovarch
|
|
5
|
+
Project-URL: Homepage, https://archprime.io
|
|
6
|
+
Project-URL: Documentation, https://lovarch.com/cli-docs
|
|
7
|
+
Project-URL: Repository, https://github.com/ArchPrime-official/lovarch-cli
|
|
8
|
+
Project-URL: Issues, https://github.com/ArchPrime-official/lovarch-cli/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/ArchPrime-official/lovarch-cli/releases
|
|
10
|
+
Project-URL: Course, https://lovarch.com/corso
|
|
11
|
+
Author-email: Pablo Ruan Lopes <pablo@archprime.io>
|
|
12
|
+
License: MIT
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Keywords: ai,architecture,bim,cad,cila,ifc,italy,lovarch,uni-11337
|
|
15
|
+
Classifier: Development Status :: 3 - Alpha
|
|
16
|
+
Classifier: Environment :: Console
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Classifier: Natural Language :: English
|
|
21
|
+
Classifier: Natural Language :: Italian
|
|
22
|
+
Classifier: Natural Language :: Portuguese
|
|
23
|
+
Classifier: Natural Language :: Spanish
|
|
24
|
+
Classifier: Operating System :: OS Independent
|
|
25
|
+
Classifier: Programming Language :: Python :: 3
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
29
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
30
|
+
Classifier: Topic :: Scientific/Engineering
|
|
31
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
32
|
+
Requires-Python: >=3.11
|
|
33
|
+
Requires-Dist: ezdxf<2.0,>=1.3
|
|
34
|
+
Requires-Dist: httpx<0.29,>=0.27
|
|
35
|
+
Requires-Dist: ifcopenshell<0.9,>=0.7
|
|
36
|
+
Requires-Dist: keyring<26.0,>=24.0
|
|
37
|
+
Requires-Dist: pillow<12.0,>=10.0
|
|
38
|
+
Requires-Dist: pydantic<3.0,>=2.6
|
|
39
|
+
Requires-Dist: pypdf<6.0,>=4.0
|
|
40
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
41
|
+
Requires-Dist: reportlab<5.0,>=4.0
|
|
42
|
+
Requires-Dist: rich<14.0,>=13.7
|
|
43
|
+
Requires-Dist: typer<0.16,>=0.12
|
|
44
|
+
Requires-Dist: xlsxwriter<4.0,>=3.2
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: mcp>=1.2; extra == 'dev'
|
|
47
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
48
|
+
Requires-Dist: pre-commit>=3.6; extra == 'dev'
|
|
49
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
51
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
52
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
53
|
+
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
|
|
54
|
+
Provides-Extra: mcp
|
|
55
|
+
Requires-Dist: mcp>=1.2; extra == 'mcp'
|
|
56
|
+
Provides-Extra: premium
|
|
57
|
+
Requires-Dist: supabase<3.0,>=2.4; extra == 'premium'
|
|
58
|
+
Description-Content-Type: text/markdown
|
|
59
|
+
|
|
60
|
+
# lovarch-cli
|
|
61
|
+
|
|
62
|
+
[](https://github.com/ArchPrime-official/lovarch-cli/actions/workflows/ci.yml)
|
|
63
|
+
[](https://opensource.org/licenses/MIT)
|
|
64
|
+
[](https://www.python.org/downloads/)
|
|
65
|
+
[](https://github.com/ArchPrime-official/lovarch-cli/releases)
|
|
66
|
+
|
|
67
|
+
> **AI-powered architectural project execution CLI** — squad di 17 agenti specializzati che esegue audit input, briefing, normativa IT, CAD, BIM/IFC, computo metrico, capitolato, pratiche edilizie (CILA/SCIA), contratto CNAPPC, energy/LCA preliminare, dossier consolidato — in 14 minuti vs 3 settimane di lavoro tradizionale.
|
|
68
|
+
|
|
69
|
+
> ⚠️ **Status: BETA (v0.1.x)** — distribuito via Homebrew tap + pipx-from-git. Usato in produzione dagli iscritti al [Corso IA Avanzato per Architetti](https://lovarch.com/corso) (€1.497). Pubblicazione su PyPI valutata per v0.2+.
|
|
70
|
+
|
|
71
|
+
🌐 **Lingue:** [🇮🇹 Italiano](README.md) (default) · [🇵🇹 Português](README.pt.md) · [🇬🇧 English](README.en.md) · [🇪🇸 Español](README.es.md)
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Cosa fa
|
|
76
|
+
|
|
77
|
+
`lovarch-cli` orchestra il **Squad Architettura-Progetto** di Lovarch — 17 agenti AI con framework documentati (mind clones di Schumacher, Baldwin, Mazria, Deming, Juran, English, Dodds) — per generare 27 deliverable architettonici conformi alla normativa italiana:
|
|
78
|
+
|
|
79
|
+
- **Audit input** (18 controlli) prima di iniziare
|
|
80
|
+
- **CAD quotato** DXF/PDF (UNI ISO 5457, ±1mm)
|
|
81
|
+
- **BIM IFC4 LOD 300**
|
|
82
|
+
- **Computo metrico** (Prezzario Lombardia 2025)
|
|
83
|
+
- **Capitolato Speciale d'Appalto** (UNI 11337-7 + CAM 2025)
|
|
84
|
+
- **Pratiche edilizie pre-compilate** (CILA/SCIA/Paesaggistica)
|
|
85
|
+
- **Contratto CNAPPC** + Equo Compenso L.49/2023
|
|
86
|
+
- **APE Preliminare + LCA Embodied Carbon**
|
|
87
|
+
- **Dossier finale** ZIP con 27 documenti
|
|
88
|
+
|
|
89
|
+
## Due modalità
|
|
90
|
+
|
|
91
|
+
### 🆓 Free Mode (registrazione richiesta)
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
lovarch signup
|
|
95
|
+
# → Cadastro: Nome completo, email, telefono, paese, lingua
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- Esegui il squad **localmente** con i tuoi propri API keys (OpenAI, Mapbox, fal.ai)
|
|
99
|
+
- Storage in `~/.lovarch/projects/` (filesystem locale)
|
|
100
|
+
- Database in `~/.lovarch/local.db` (SQLite)
|
|
101
|
+
- Tutti i 17 agenti disponibili
|
|
102
|
+
- Tu paghi le tue API direttamente ai provider
|
|
103
|
+
|
|
104
|
+
### ⭐ Premium Mode (login Lovarch)
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
arch login --premium
|
|
108
|
+
# → Apre il browser per autenticazione Lovarch (PKCE flow)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- Login con il tuo account Lovarch esistente
|
|
112
|
+
- Crediti inclusi nel piano (Personal €49 / Studio €99 / Business €199)
|
|
113
|
+
- Backend Lovarch (Supabase + S3 + Edge Functions ottimizzate)
|
|
114
|
+
- Sincronizzazione web app `lovarch.com/admin/squad-execution/{id}/live`
|
|
115
|
+
- Team member ownership automatico
|
|
116
|
+
|
|
117
|
+
## Installazione
|
|
118
|
+
|
|
119
|
+
### 🍺 Homebrew (raccomandato — macOS / Linux)
|
|
120
|
+
|
|
121
|
+
Il modo più semplice. Funziona anche per chi non ha familiarità con Python:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
brew tap archprime-official/lovarch
|
|
125
|
+
brew install lovarch-cli
|
|
126
|
+
lovarch --version
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`brew upgrade lovarch-cli` aggiorna alla nuova release.
|
|
130
|
+
|
|
131
|
+
### 📦 pipx — install isolato da GitHub
|
|
132
|
+
|
|
133
|
+
Se preferisci un'install Python isolata senza Homebrew (utile su Linux server,
|
|
134
|
+
WSL, o se hai già pipx configurato):
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Ultima release (anche pre-release):
|
|
138
|
+
pipx install git+https://github.com/ArchPrime-official/lovarch-cli.git@v0.1.2
|
|
139
|
+
|
|
140
|
+
# Oppure dal branch main (rolling):
|
|
141
|
+
pipx install git+https://github.com/ArchPrime-official/lovarch-cli.git
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`pipx upgrade lovarch-cli` aggiorna alla revisione successiva.
|
|
145
|
+
|
|
146
|
+
### 🛠️ Da sorgente (per sviluppatori / contributori)
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
git clone https://github.com/ArchPrime-official/lovarch-cli.git
|
|
150
|
+
cd lovarch-cli
|
|
151
|
+
python3.12 -m venv .venv && source .venv/bin/activate
|
|
152
|
+
pip install -e ".[dev]"
|
|
153
|
+
pytest tests/ # → 142 passing
|
|
154
|
+
lovarch --version
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Vedi [CONTRIBUTING.md](./CONTRIBUTING.md) per il workflow di sviluppo.
|
|
158
|
+
|
|
159
|
+
> Backward compatibility: il comando `arch` è alias di `lovarch` per chi ha già muscle memory dalla versione interna di sviluppo.
|
|
160
|
+
>
|
|
161
|
+
> PyPI: la pubblicazione su `pip install lovarch-cli` è rimandata a v0.2+ (i metodi qui sopra coprono tutti i casi d'uso senza dipendere dalla burocrazia PyPI token/2FA).
|
|
162
|
+
|
|
163
|
+
## Quick start
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Verifica installazione
|
|
167
|
+
lovarch --version # → lovarch-cli 0.1.2
|
|
168
|
+
|
|
169
|
+
# Primo login (interattivo: Free o Premium)
|
|
170
|
+
lovarch login
|
|
171
|
+
|
|
172
|
+
# Inizializza un progetto (con --sample scarica villa-chianti da GitHub Releases)
|
|
173
|
+
lovarch init villa-toscana --sample
|
|
174
|
+
|
|
175
|
+
# Audit dei 18 input prima di girare il pipeline
|
|
176
|
+
lovarch audit villa-toscana
|
|
177
|
+
|
|
178
|
+
# Esegui workflow (Free=dry-run · Premium=produzione)
|
|
179
|
+
lovarch run dal-brief-al-cantiere villa-toscana
|
|
180
|
+
|
|
181
|
+
# Consolida deliverable in DOSSIER.zip
|
|
182
|
+
lovarch consolidate villa-toscana
|
|
183
|
+
|
|
184
|
+
# Stato di tutti i progetti
|
|
185
|
+
lovarch status
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Comandi disponibili
|
|
189
|
+
|
|
190
|
+
| Comando | Descrizione |
|
|
191
|
+
|---------|-------------|
|
|
192
|
+
| `arch login` | Login Free o Premium |
|
|
193
|
+
| `arch signup` | Cadastro Free interattivo |
|
|
194
|
+
| `arch config` | Configurazione (API keys, lingua, storage path) — _in arrivo (v0.2)_ |
|
|
195
|
+
| `arch init <progetto>` | Crea nuovo progetto con struttura sample-input |
|
|
196
|
+
| `arch audit <progetto>` | Esegue audit input (gate di ingresso) |
|
|
197
|
+
| `arch run <workflow>` | Esegue workflow completo |
|
|
198
|
+
| `arch consolidate <progetto>` | Genera DOSSIER.zip finale |
|
|
199
|
+
| `arch status <id>` | Stato di una esecuzione |
|
|
200
|
+
| `arch upgrade` | CTA per passare da Free a Premium |
|
|
201
|
+
| `arch account delete` | Right-to-erasure GDPR |
|
|
202
|
+
|
|
203
|
+
Vedi `arch --help` per dettagli completi.
|
|
204
|
+
|
|
205
|
+
## Limiti dichiarati
|
|
206
|
+
|
|
207
|
+
`lovarch-cli` **NON** sostituisce:
|
|
208
|
+
|
|
209
|
+
- ❌ Firma digitale qualificata (QES) dell'architetto abilitato
|
|
210
|
+
- ❌ Calcolo strutturale NTC 2018 (richiede ingegnere strutturale)
|
|
211
|
+
- ❌ Rilievo metrico in loco (richiede sopralluogo)
|
|
212
|
+
- ❌ Coordinamento sicurezza CSP/CSE (D.Lgs 81/2008)
|
|
213
|
+
- ❌ Responsabilità professionale del tecnico
|
|
214
|
+
|
|
215
|
+
L'utente assume la responsabilità di verifica e revisione professionale prima di qualsiasi presentazione ad autorità.
|
|
216
|
+
|
|
217
|
+
## Licenza
|
|
218
|
+
|
|
219
|
+
MIT License — vedi [LICENSE](LICENSE).
|
|
220
|
+
|
|
221
|
+
## Link
|
|
222
|
+
|
|
223
|
+
- 🌐 [archprime.io](https://archprime.io) · [lovarch.com](https://lovarch.com)
|
|
224
|
+
- 📚 [Documentazione](https://docs.archprime.io/cli)
|
|
225
|
+
- 🐛 [Issues](https://github.com/ArchPrime-official/lovarch-cli/issues)
|
|
226
|
+
- 📋 [Releases](https://github.com/ArchPrime-official/lovarch-cli/releases) · [Changelog](./CHANGELOG.md)
|
|
227
|
+
- 🤝 [Contributing](./CONTRIBUTING.md)
|
|
228
|
+
- 🎓 [Corso IA Avanzato per Architetti](https://lovarch.com/corso) (€1.497)
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
🤖 Powered by [Lovarch](https://lovarch.com) — AI Growth System for Architects & Designers
|