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.
Files changed (122) hide show
  1. lovarch_cli/__init__.py +16 -0
  2. lovarch_cli/__main__.py +10 -0
  3. lovarch_cli/ai/__init__.py +21 -0
  4. lovarch_cli/ai/gateway.py +240 -0
  5. lovarch_cli/api.py +111 -0
  6. lovarch_cli/auth/__init__.py +32 -0
  7. lovarch_cli/auth/keyring_store.py +214 -0
  8. lovarch_cli/auth/local_server.py +165 -0
  9. lovarch_cli/auth/pkce.py +57 -0
  10. lovarch_cli/auth/session.py +189 -0
  11. lovarch_cli/cli.py +262 -0
  12. lovarch_cli/clients/__init__.py +33 -0
  13. lovarch_cli/clients/factory.py +54 -0
  14. lovarch_cli/clients/local_client.py +432 -0
  15. lovarch_cli/clients/lovarch_storage.py +174 -0
  16. lovarch_cli/clients/lovarch_supabase.py +295 -0
  17. lovarch_cli/clients/persistence.py +166 -0
  18. lovarch_cli/clients/storage.py +66 -0
  19. lovarch_cli/commands/__init__.py +10 -0
  20. lovarch_cli/commands/account.py +172 -0
  21. lovarch_cli/commands/audit.py +394 -0
  22. lovarch_cli/commands/config_cmd.py +80 -0
  23. lovarch_cli/commands/consolidate.py +217 -0
  24. lovarch_cli/commands/context_cmd.py +73 -0
  25. lovarch_cli/commands/dev.py +287 -0
  26. lovarch_cli/commands/do_cmd.py +120 -0
  27. lovarch_cli/commands/init.py +218 -0
  28. lovarch_cli/commands/jobs_cmd.py +95 -0
  29. lovarch_cli/commands/login.py +202 -0
  30. lovarch_cli/commands/mcp_cmd.py +26 -0
  31. lovarch_cli/commands/run.py +375 -0
  32. lovarch_cli/commands/signup.py +185 -0
  33. lovarch_cli/commands/status.py +243 -0
  34. lovarch_cli/commands/upgrade.py +108 -0
  35. lovarch_cli/commands/verifica_cmd.py +174 -0
  36. lovarch_cli/config.py +101 -0
  37. lovarch_cli/config_store.py +111 -0
  38. lovarch_cli/credits/__init__.py +35 -0
  39. lovarch_cli/credits/base.py +84 -0
  40. lovarch_cli/credits/factory.py +36 -0
  41. lovarch_cli/credits/local.py +34 -0
  42. lovarch_cli/credits/lovarch.py +56 -0
  43. lovarch_cli/i18n/__init__.py +27 -0
  44. lovarch_cli/i18n/loader.py +121 -0
  45. lovarch_cli/i18n/translations/en.json +168 -0
  46. lovarch_cli/i18n/translations/es.json +168 -0
  47. lovarch_cli/i18n/translations/it.json +168 -0
  48. lovarch_cli/i18n/translations/pt.json +168 -0
  49. lovarch_cli/mcp/__init__.py +9 -0
  50. lovarch_cli/mcp/server.py +199 -0
  51. lovarch_cli/mcp/tools.py +372 -0
  52. lovarch_cli/sample_downloader.py +255 -0
  53. lovarch_cli/squad/README.md +206 -0
  54. lovarch_cli/squad/agents/auditor-input.md +353 -0
  55. lovarch_cli/squad/agents/bim-engineer.md +404 -0
  56. lovarch_cli/squad/agents/briefing-architect.md +249 -0
  57. lovarch_cli/squad/agents/cad-engineer.md +278 -0
  58. lovarch_cli/squad/agents/capitolato-writer.md +256 -0
  59. lovarch_cli/squad/agents/computo-engineer.md +258 -0
  60. lovarch_cli/squad/agents/concept-designer.md +399 -0
  61. lovarch_cli/squad/agents/contratto-architect.md +243 -0
  62. lovarch_cli/squad/agents/deliverable-builder.md +253 -0
  63. lovarch_cli/squad/agents/energy-prelim.md +388 -0
  64. lovarch_cli/squad/agents/pratiche-it.md +251 -0
  65. lovarch_cli/squad/agents/progetto-chief.md +768 -0
  66. lovarch_cli/squad/agents/quality-dati.md +409 -0
  67. lovarch_cli/squad/agents/quality-misure.md +418 -0
  68. lovarch_cli/squad/agents/quality-normativa.md +417 -0
  69. lovarch_cli/squad/agents/quality-output.md +436 -0
  70. lovarch_cli/squad/agents/regolatorio-it.md +278 -0
  71. lovarch_cli/squad/checklists/handoff-quality-gate.md +232 -0
  72. lovarch_cli/squad/checklists/quality-dati-checklist.md +134 -0
  73. lovarch_cli/squad/checklists/quality-misure-checklist.md +139 -0
  74. lovarch_cli/squad/checklists/quality-normativa-checklist.md +121 -0
  75. lovarch_cli/squad/checklists/quality-output-checklist.md +116 -0
  76. lovarch_cli/squad/config.yaml +408 -0
  77. lovarch_cli/squad/data/CHANGELOG.md +272 -0
  78. lovarch_cli/squad/data/agents-prd.md +428 -0
  79. lovarch_cli/squad/data/architettura-progetto-rules.md +328 -0
  80. lovarch_cli/squad/data/handoff-card-template.md +231 -0
  81. lovarch_cli/squad/data/mocks/catasto-visura.json +72 -0
  82. lovarch_cli/squad/data/mocks/firma-envelope.json +43 -0
  83. lovarch_cli/squad/data/prezzario-lombardia-sample.json +312 -0
  84. lovarch_cli/squad/scripts/api_clients.py +206 -0
  85. lovarch_cli/squad/scripts/architect_profile.py +276 -0
  86. lovarch_cli/squad/scripts/deliverable_generators.py +844 -0
  87. lovarch_cli/squad/scripts/generate_attico_brera_dwg.py +369 -0
  88. lovarch_cli/squad/scripts/generate_chianti_dxf.py +368 -0
  89. lovarch_cli/squad/scripts/generate_chianti_images.py +223 -0
  90. lovarch_cli/squad/scripts/generate_real_sample_images.py +189 -0
  91. lovarch_cli/squad/scripts/generate_sample_assets.py +382 -0
  92. lovarch_cli/squad/scripts/lovarch_client.py +1046 -0
  93. lovarch_cli/squad/scripts/pipeline_runner.py +2095 -0
  94. lovarch_cli/squad/scripts/render_dxf_to_png.py +57 -0
  95. lovarch_cli/squad/scripts/run_palestra_demo.sh +277 -0
  96. lovarch_cli/squad/scripts/simulate_squad_execution.py +515 -0
  97. lovarch_cli/squad/scripts/validate-squad.py +383 -0
  98. lovarch_cli/squad/tasks/audit-input.md +146 -0
  99. lovarch_cli/squad/tasks/compute-metric.md +105 -0
  100. lovarch_cli/squad/tasks/consolidate-dossier.md +187 -0
  101. lovarch_cli/squad/tasks/generate-cad-plan.md +120 -0
  102. lovarch_cli/squad/tasks/generate-ifc-model.md +108 -0
  103. lovarch_cli/squad/tasks/write-capitolato.md +100 -0
  104. lovarch_cli/squad/templates/asseverazione-tecnica.md +126 -0
  105. lovarch_cli/squad/templates/capitolato-uni-11337.md +235 -0
  106. lovarch_cli/squad/templates/cila-comune-milano.md +177 -0
  107. lovarch_cli/squad/templates/contratto-cnappc.md +220 -0
  108. lovarch_cli/squad/workflows/dal-brief-al-cantiere.yaml +218 -0
  109. lovarch_cli/squad_loader.py +114 -0
  110. lovarch_cli/verify/__init__.py +15 -0
  111. lovarch_cli/verify/contratto.py +110 -0
  112. lovarch_cli/verify/dossier.py +97 -0
  113. lovarch_cli/verify/misure.py +83 -0
  114. lovarch_cli/verify/normativa.py +178 -0
  115. lovarch_cli/version.py +13 -0
  116. lovarch_cli/workflows/__init__.py +9 -0
  117. lovarch_cli/workflows/platform.py +212 -0
  118. lovarch_cli-0.2.1.dist-info/METADATA +232 -0
  119. lovarch_cli-0.2.1.dist-info/RECORD +122 -0
  120. lovarch_cli-0.2.1.dist-info/WHEEL +4 -0
  121. lovarch_cli-0.2.1.dist-info/entry_points.txt +3 -0
  122. 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
+ [![CI](https://github.com/ArchPrime-official/lovarch-cli/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/ArchPrime-official/lovarch-cli/actions/workflows/ci.yml)
63
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
64
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
65
+ [![Release](https://img.shields.io/github/v/release/ArchPrime-official/lovarch-cli?include_prereleases)](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