loro-agent 0.1.0__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.
loro/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Loro enterprise agent harness."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.0"
loro/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from loro.cli import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
@@ -0,0 +1 @@
1
+ """Artifact generation modules for documents, presentations, spreadsheets, and briefs."""
@@ -0,0 +1,54 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+ from loro.artifacts.common import ArtifactResult, ensure_output_dir, slugify, title_from_prompt
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class Brief:
9
+ title: str
10
+ summary: str
11
+ risks: list[str]
12
+ next_steps: list[str]
13
+
14
+
15
+ def create_brief_artifact(
16
+ prompt: str,
17
+ output_dir: Path,
18
+ brief_type: str = "meeting",
19
+ ) -> ArtifactResult:
20
+ title = title_from_prompt(prompt, f"Loro {brief_type.title()} Brief")
21
+ slug = slugify(f"{brief_type}-{title}", "brief")
22
+ output_dir = ensure_output_dir(output_dir)
23
+ path = output_dir / f"{slug}.md"
24
+ brief = Brief(
25
+ title=title,
26
+ summary=prompt.strip(),
27
+ risks=[
28
+ "Source context may be incomplete.",
29
+ "Facts and metrics require human validation before distribution.",
30
+ ],
31
+ next_steps=[
32
+ "Confirm audience and decision needed.",
33
+ "Attach source documents or governed data references.",
34
+ "Assign owners for follow-up actions.",
35
+ ],
36
+ )
37
+ path.write_text(
38
+ f"# {brief.title}\n\n"
39
+ f"Type: {brief_type}\n\n"
40
+ "## Summary\n\n"
41
+ f"{brief.summary}\n\n"
42
+ "## Risks\n\n"
43
+ + "\n".join(f"- {risk}" for risk in brief.risks)
44
+ + "\n\n## Next Steps\n\n"
45
+ + "\n".join(f"- {step}" for step in brief.next_steps)
46
+ + "\n",
47
+ encoding="utf-8",
48
+ )
49
+ return ArtifactResult(
50
+ title=title,
51
+ kind=f"{brief_type}-brief",
52
+ paths=[path],
53
+ summary=f"Created {brief_type} brief artifact: {path}",
54
+ )
@@ -0,0 +1,54 @@
1
+ import json
2
+ from dataclasses import dataclass
3
+ from datetime import UTC, datetime
4
+ from pathlib import Path
5
+ from re import sub
6
+ from typing import Any
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class ArtifactResult:
11
+ title: str
12
+ kind: str
13
+ paths: list[Path]
14
+ summary: str
15
+
16
+
17
+ def slugify(value: str, fallback: str = "artifact") -> str:
18
+ normalized = sub(r"[^a-zA-Z0-9]+", "-", value.strip().lower()).strip("-")
19
+ return normalized or fallback
20
+
21
+
22
+ def ensure_output_dir(output_dir: Path) -> Path:
23
+ output_dir.mkdir(parents=True, exist_ok=True)
24
+ return output_dir
25
+
26
+
27
+ def title_from_prompt(prompt: str, fallback: str) -> str:
28
+ cleaned = " ".join(prompt.strip().split())
29
+ if not cleaned:
30
+ return fallback
31
+ return cleaned[:80].rstrip(" .")
32
+
33
+
34
+ def write_provenance(
35
+ *,
36
+ result: ArtifactResult,
37
+ prompt_preview: str,
38
+ assumptions: list[str] | None = None,
39
+ ) -> Path:
40
+ path = result.paths[0].with_suffix(result.paths[0].suffix + ".provenance.json")
41
+ payload: dict[str, Any] = {
42
+ "title": result.title,
43
+ "kind": result.kind,
44
+ "paths": [str(item) for item in result.paths],
45
+ "prompt_preview": prompt_preview,
46
+ "assumptions": assumptions or [
47
+ "Generated from user-approved prompt text.",
48
+ "No external governed data was queried by this artifact generator.",
49
+ ],
50
+ "created_at": datetime.now(UTC).isoformat(),
51
+ "generator": "loro.mvp.artifacts",
52
+ }
53
+ path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
54
+ return path
@@ -0,0 +1,58 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+ from docx import Document
5
+
6
+ from loro.artifacts.common import ArtifactResult, ensure_output_dir, slugify, title_from_prompt
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class DocumentDraft:
11
+ title: str
12
+ markdown: str
13
+
14
+
15
+ def create_markdown_document(title: str, body: str) -> DocumentDraft:
16
+ return DocumentDraft(title=title, markdown=f"# {title}\n\n{body.strip()}\n")
17
+
18
+
19
+ def create_document_artifact(prompt: str, output_dir: Path) -> ArtifactResult:
20
+ title = title_from_prompt(prompt, "Loro Document")
21
+ slug = slugify(title, "document")
22
+ output_dir = ensure_output_dir(output_dir)
23
+ markdown_path = output_dir / f"{slug}.md"
24
+ docx_path = output_dir / f"{slug}.docx"
25
+
26
+ body = (
27
+ "## Purpose\n\n"
28
+ f"{prompt.strip()}\n\n"
29
+ "## Draft\n\n"
30
+ "This document was generated by Loro's deterministic MVP artifact engine. "
31
+ "Future model integration will expand this draft with richer context, memory retrieval, "
32
+ "and governed enterprise data citations.\n\n"
33
+ "## Assumptions\n\n"
34
+ "- The user approved the prompt as source context.\n"
35
+ "- No restricted data was embedded by the scaffolded generator.\n\n"
36
+ "## Next Steps\n\n"
37
+ "- Review the draft for accuracy.\n"
38
+ "- Add organization-specific template requirements.\n"
39
+ "- Confirm whether any content should be promoted to local or shared memory.\n"
40
+ )
41
+ draft = create_markdown_document(title, body)
42
+ markdown_path.write_text(draft.markdown, encoding="utf-8")
43
+
44
+ document = Document()
45
+ document.add_heading(title, level=1)
46
+ for section in body.split("\n\n"):
47
+ if section.startswith("## "):
48
+ document.add_heading(section.removeprefix("## ").strip(), level=2)
49
+ elif section.strip():
50
+ document.add_paragraph(section.strip())
51
+ document.save(docx_path)
52
+
53
+ return ArtifactResult(
54
+ title=title,
55
+ kind="document",
56
+ paths=[markdown_path, docx_path],
57
+ summary=f"Created document artifacts: {markdown_path} and {docx_path}",
58
+ )
@@ -0,0 +1,80 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+ from pptx import Presentation
5
+
6
+ from loro.artifacts.common import ArtifactResult, ensure_output_dir, slugify, title_from_prompt
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class Slide:
11
+ title: str
12
+ bullets: list[str]
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class PresentationOutline:
17
+ title: str
18
+ slides: list[Slide]
19
+
20
+
21
+ def create_presentation_artifact(prompt: str, output_dir: Path) -> ArtifactResult:
22
+ title = title_from_prompt(prompt, "Loro Presentation")
23
+ slug = slugify(title, "presentation")
24
+ output_dir = ensure_output_dir(output_dir)
25
+ outline_path = output_dir / f"{slug}-outline.md"
26
+ pptx_path = output_dir / f"{slug}.pptx"
27
+
28
+ outline = PresentationOutline(
29
+ title=title,
30
+ slides=[
31
+ Slide(
32
+ title="Context",
33
+ bullets=[prompt.strip(), "Audience and goals should be reviewed."],
34
+ ),
35
+ Slide(
36
+ title="Key Points",
37
+ bullets=[
38
+ "Summarize the current state.",
39
+ "Call out decisions, risks, and opportunities.",
40
+ "Tie recommendations to enterprise memory and governed data when available.",
41
+ ],
42
+ ),
43
+ Slide(
44
+ title="Next Steps",
45
+ bullets=[
46
+ "Validate facts and metrics.",
47
+ "Apply organization template and branding.",
48
+ "Confirm follow-up owners.",
49
+ ],
50
+ ),
51
+ ],
52
+ )
53
+ outline_sections = [f"# {outline.title}"]
54
+ outline_sections.extend(
55
+ f"## {slide.title}\n" + "\n".join(f"- {item}" for item in slide.bullets)
56
+ for slide in outline.slides
57
+ )
58
+ outline_path.write_text("\n\n".join(outline_sections) + "\n", encoding="utf-8")
59
+
60
+ deck = Presentation()
61
+ title_slide = deck.slides.add_slide(deck.slide_layouts[0])
62
+ title_slide.shapes.title.text = title
63
+ title_slide.placeholders[1].text = "Generated by Loro"
64
+ for slide in outline.slides:
65
+ ppt_slide = deck.slides.add_slide(deck.slide_layouts[1])
66
+ ppt_slide.shapes.title.text = slide.title
67
+ body = ppt_slide.shapes.placeholders[1].text_frame
68
+ body.clear()
69
+ for index, bullet in enumerate(slide.bullets):
70
+ paragraph = body.paragraphs[0] if index == 0 else body.add_paragraph()
71
+ paragraph.text = bullet
72
+ paragraph.level = 0
73
+ deck.save(pptx_path)
74
+
75
+ return ArtifactResult(
76
+ title=title,
77
+ kind="presentation",
78
+ paths=[outline_path, pptx_path],
79
+ summary=f"Created presentation artifacts: {outline_path} and {pptx_path}",
80
+ )
@@ -0,0 +1,66 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+ from openpyxl import Workbook, load_workbook
5
+ from openpyxl.chart import BarChart, Reference
6
+
7
+ from loro.artifacts.common import ArtifactResult, ensure_output_dir, slugify, title_from_prompt
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class WorkbookPlan:
12
+ title: str
13
+ sheets: list[str]
14
+ assumptions: list[str]
15
+
16
+
17
+ def create_spreadsheet_artifact(prompt: str, output_dir: Path) -> ArtifactResult:
18
+ title = title_from_prompt(prompt, "Loro Workbook")
19
+ slug = slugify(title, "workbook")
20
+ output_dir = ensure_output_dir(output_dir)
21
+ xlsx_path = output_dir / f"{slug}.xlsx"
22
+ csv_path = output_dir / f"{slug}-summary.csv"
23
+
24
+ rows = [
25
+ ("Category", "Planned", "Actual", "Variance"),
26
+ ("Scope", 10, 8, "=C2-B2"),
27
+ ("Schedule", 10, 9, "=C3-B3"),
28
+ ("Risk", 10, 6, "=C4-B4"),
29
+ ]
30
+
31
+ workbook = Workbook()
32
+ sheet = workbook.active
33
+ sheet.title = "Summary"
34
+ sheet["A1"] = title
35
+ sheet["A2"] = "Prompt"
36
+ sheet["B2"] = prompt.strip()
37
+ start_row = 4
38
+ for row_index, row in enumerate(rows, start=start_row):
39
+ for column_index, value in enumerate(row, start=1):
40
+ sheet.cell(row=row_index, column=column_index, value=value)
41
+
42
+ chart = BarChart()
43
+ chart.title = "Planned vs Actual"
44
+ chart.y_axis.title = "Score"
45
+ chart.x_axis.title = "Category"
46
+ data = Reference(sheet, min_col=2, max_col=3, min_row=start_row, max_row=start_row + 3)
47
+ categories = Reference(sheet, min_col=1, min_row=start_row + 1, max_row=start_row + 3)
48
+ chart.add_data(data, titles_from_data=True)
49
+ chart.set_categories(categories)
50
+ sheet.add_chart(chart, "F4")
51
+ workbook.save(xlsx_path)
52
+
53
+ csv_path.write_text(
54
+ "\n".join(",".join(str(item) for item in row) for row in rows) + "\n",
55
+ encoding="utf-8",
56
+ )
57
+
58
+ # Validate the workbook can be opened after writing.
59
+ load_workbook(xlsx_path, data_only=False).close()
60
+
61
+ return ArtifactResult(
62
+ title=title,
63
+ kind="spreadsheet",
64
+ paths=[xlsx_path, csv_path],
65
+ summary=f"Created spreadsheet artifacts: {xlsx_path} and {csv_path}",
66
+ )
loro/audit.py ADDED
@@ -0,0 +1,42 @@
1
+ import json
2
+ from dataclasses import dataclass, field
3
+ from datetime import UTC, datetime
4
+ from pathlib import Path
5
+ from typing import Any
6
+ from uuid import uuid4
7
+
8
+ from loro.config import AuditConfig
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class AuditEvent:
13
+ event_type: str
14
+ details: dict[str, Any] = field(default_factory=dict)
15
+ event_id: str = field(default_factory=lambda: str(uuid4()))
16
+ created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
17
+
18
+
19
+ class AuditLogger:
20
+ def __init__(self, config: AuditConfig) -> None:
21
+ self.config = config
22
+ self.path = Path(config.path).expanduser()
23
+
24
+ def write(self, event_type: str, **details: Any) -> AuditEvent:
25
+ event = AuditEvent(event_type=event_type, details=details)
26
+ if not self.config.enabled:
27
+ return event
28
+ self.path.parent.mkdir(parents=True, exist_ok=True)
29
+ payload = {
30
+ "event_id": event.event_id,
31
+ "event_type": event.event_type,
32
+ "created_at": event.created_at.isoformat(),
33
+ "details": event.details,
34
+ }
35
+ with self.path.open("a", encoding="utf-8") as file:
36
+ file.write(json.dumps(payload, default=str) + "\n")
37
+ return event
38
+
39
+
40
+ def prompt_preview(prompt: str, limit: int = 160) -> str:
41
+ normalized = " ".join(prompt.split())
42
+ return normalized[:limit]