devpilot-agentic-cli 1.0.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.
- agent/__init__.py +1 -0
- agent/a2a_client.py +94 -0
- agent/a2a_server.py +148 -0
- agent/cli.py +233 -0
- agent/config.py +232 -0
- agent/context.py +182 -0
- agent/history.py +172 -0
- agent/loop.py +102 -0
- agent/mcp_client.py +104 -0
- agent/providers/__init__.py +4 -0
- agent/providers/anthropic_provider.py +169 -0
- agent/providers/base.py +148 -0
- agent/providers/factory.py +35 -0
- agent/providers/openai_provider.py +194 -0
- agent/providers/system_prompt.py +132 -0
- agent/setup_wizard.py +309 -0
- agent/tools/__init__.py +15 -0
- agent/tools/a2a.py +56 -0
- agent/tools/base.py +52 -0
- agent/tools/diagram.py +131 -0
- agent/tools/doc_gen.py +163 -0
- agent/tools/fs.py +411 -0
- agent/tools/git_ops.py +145 -0
- agent/tools/registry.py +219 -0
- agent/tools/search_code.py +120 -0
- agent/tools/shell.py +118 -0
- agent/tools/web_search.py +105 -0
- agent/tui/__init__.py +3 -0
- agent/tui/app.py +557 -0
- agent/ui.py +263 -0
- devpilot_agentic_cli-1.0.0.dist-info/METADATA +288 -0
- devpilot_agentic_cli-1.0.0.dist-info/RECORD +35 -0
- devpilot_agentic_cli-1.0.0.dist-info/WHEEL +5 -0
- devpilot_agentic_cli-1.0.0.dist-info/entry_points.txt +2 -0
- devpilot_agentic_cli-1.0.0.dist-info/top_level.txt +1 -0
agent/tools/diagram.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agent/tools/diagram.py
|
|
3
|
+
──────────────────────
|
|
4
|
+
Mermaid diagram generation tool.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import subprocess
|
|
10
|
+
import tempfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
from agent.tools.base import BaseTool, ToolResult, ToolSchema
|
|
15
|
+
from agent.tools.fs import _safe_path
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from agent.config import Config
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_MERMAID_TEMPLATE = """flowchart TD
|
|
22
|
+
A[Start] --> B[Process]
|
|
23
|
+
B --> C[End]
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DiagramTool(BaseTool):
|
|
28
|
+
"""Generate Mermaid diagrams and save them as SVG or PNG files."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, config: "Config") -> None:
|
|
31
|
+
self._config = config
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def schema(self) -> ToolSchema:
|
|
35
|
+
return ToolSchema(
|
|
36
|
+
name="generate_diagram",
|
|
37
|
+
description=(
|
|
38
|
+
"Generate a diagram from Mermaid syntax. "
|
|
39
|
+
"Saves as SVG (default) or PNG using mermaid-cli (mmdc). "
|
|
40
|
+
"Also writes the source .mmd file. "
|
|
41
|
+
"Use for flowcharts, sequence diagrams, ER diagrams, class diagrams, etc. "
|
|
42
|
+
"Mermaid docs: https://mermaid.js.org/"
|
|
43
|
+
),
|
|
44
|
+
parameters={
|
|
45
|
+
"type": "object",
|
|
46
|
+
"properties": {
|
|
47
|
+
"mermaid_code": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"description": "Valid Mermaid diagram definition.",
|
|
50
|
+
},
|
|
51
|
+
"output_path": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "Output file path without extension (e.g. 'docs/architecture').",
|
|
54
|
+
},
|
|
55
|
+
"format": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"enum": ["svg", "png", "mmd"],
|
|
58
|
+
"description": "Output format. 'mmd' saves source only. Default: svg.",
|
|
59
|
+
"default": "svg",
|
|
60
|
+
},
|
|
61
|
+
"theme": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"enum": ["default", "dark", "neutral", "forest"],
|
|
64
|
+
"description": "Mermaid theme. Default: default.",
|
|
65
|
+
"default": "default",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
"required": ["mermaid_code", "output_path"],
|
|
69
|
+
},
|
|
70
|
+
required=["mermaid_code", "output_path"],
|
|
71
|
+
is_destructive=True,
|
|
72
|
+
sprint="Sprint 2",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
async def execute( # type: ignore[override]
|
|
76
|
+
self,
|
|
77
|
+
mermaid_code: str,
|
|
78
|
+
output_path: str,
|
|
79
|
+
format: str = "svg",
|
|
80
|
+
theme: str = "default",
|
|
81
|
+
) -> ToolResult:
|
|
82
|
+
results: list[str] = []
|
|
83
|
+
|
|
84
|
+
# Always write the .mmd source file
|
|
85
|
+
mmd_path = _safe_path(self._config.workdir, output_path + ".mmd")
|
|
86
|
+
try:
|
|
87
|
+
mmd_path.parent.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
mmd_path.write_text(mermaid_code, encoding="utf-8")
|
|
89
|
+
results.append(f"✓ Mermaid source written to {output_path}.mmd")
|
|
90
|
+
except Exception as e:
|
|
91
|
+
return ToolResult(f"Error writing .mmd file: {e}", is_error=True)
|
|
92
|
+
|
|
93
|
+
if format == "mmd":
|
|
94
|
+
return ToolResult("\n".join(results), is_error=False)
|
|
95
|
+
|
|
96
|
+
# Try to render via mmdc (mermaid-cli)
|
|
97
|
+
output_file = _safe_path(self._config.workdir, f"{output_path}.{format}")
|
|
98
|
+
try:
|
|
99
|
+
proc = subprocess.run(
|
|
100
|
+
[
|
|
101
|
+
"mmdc",
|
|
102
|
+
"-i", str(mmd_path),
|
|
103
|
+
"-o", str(output_file),
|
|
104
|
+
"-t", theme,
|
|
105
|
+
"--backgroundColor", "transparent",
|
|
106
|
+
],
|
|
107
|
+
capture_output=True,
|
|
108
|
+
text=True,
|
|
109
|
+
timeout=30,
|
|
110
|
+
)
|
|
111
|
+
if proc.returncode == 0:
|
|
112
|
+
results.append(f"✓ Diagram rendered to {output_path}.{format}")
|
|
113
|
+
else:
|
|
114
|
+
err = proc.stderr.strip() or proc.stdout.strip()
|
|
115
|
+
results.append(
|
|
116
|
+
f"⚠ mmdc rendering failed: {err}\n"
|
|
117
|
+
f" The .mmd source file was saved — render it manually with:\n"
|
|
118
|
+
f" npx @mermaid-js/mermaid-cli -i {output_path}.mmd -o {output_path}.{format}"
|
|
119
|
+
)
|
|
120
|
+
except FileNotFoundError:
|
|
121
|
+
results.append(
|
|
122
|
+
f"⚠ mmdc (mermaid-cli) not found. The .mmd source was saved.\n"
|
|
123
|
+
f" Install with: npm install -g @mermaid-js/mermaid-cli\n"
|
|
124
|
+
f" Then render: mmdc -i {output_path}.mmd -o {output_path}.{format}"
|
|
125
|
+
)
|
|
126
|
+
except subprocess.TimeoutExpired:
|
|
127
|
+
results.append(f"⚠ mmdc timed out after 30s. The .mmd source was saved.")
|
|
128
|
+
except Exception as e:
|
|
129
|
+
results.append(f"⚠ Rendering error: {e}. The .mmd source was saved.")
|
|
130
|
+
|
|
131
|
+
return ToolResult("\n".join(results), is_error=False)
|
agent/tools/doc_gen.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agent/tools/doc_gen.py
|
|
3
|
+
──────────────────────
|
|
4
|
+
Documentation generation tool — Markdown + PDF output.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from agent.tools.base import BaseTool, ToolResult, ToolSchema
|
|
13
|
+
from agent.tools.fs import _safe_path
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from agent.config import Config
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DocGenTool(BaseTool):
|
|
20
|
+
"""Generate Markdown and PDF documentation from provided content."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: "Config") -> None:
|
|
23
|
+
self._config = config
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def schema(self) -> ToolSchema:
|
|
27
|
+
return ToolSchema(
|
|
28
|
+
name="generate_docs",
|
|
29
|
+
description=(
|
|
30
|
+
"Generate documentation files. Converts Markdown to a polished PDF "
|
|
31
|
+
"or writes a Markdown file. Use to create README files, "
|
|
32
|
+
"API docs, reports, or any structured text document."
|
|
33
|
+
),
|
|
34
|
+
parameters={
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"content": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "Markdown content to write.",
|
|
40
|
+
},
|
|
41
|
+
"output_path": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "Output file path (e.g. 'docs/README.md' or 'docs/report.pdf').",
|
|
44
|
+
},
|
|
45
|
+
"format": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"enum": ["markdown", "pdf", "both"],
|
|
48
|
+
"description": "Output format: 'markdown', 'pdf', or 'both'. Default: 'markdown'.",
|
|
49
|
+
"default": "markdown",
|
|
50
|
+
},
|
|
51
|
+
"title": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "Document title for PDF header.",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
"required": ["content", "output_path"],
|
|
57
|
+
},
|
|
58
|
+
required=["content", "output_path"],
|
|
59
|
+
is_destructive=True,
|
|
60
|
+
sprint="Sprint 2",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
async def execute( # type: ignore[override]
|
|
64
|
+
self,
|
|
65
|
+
content: str,
|
|
66
|
+
output_path: str,
|
|
67
|
+
format: str = "markdown",
|
|
68
|
+
title: str | None = None,
|
|
69
|
+
) -> ToolResult:
|
|
70
|
+
results: list[str] = []
|
|
71
|
+
|
|
72
|
+
# Write Markdown
|
|
73
|
+
if format in ("markdown", "both"):
|
|
74
|
+
md_path = output_path if output_path.endswith(".md") else output_path.rstrip(".pdf") + ".md"
|
|
75
|
+
try:
|
|
76
|
+
safe_p = _safe_path(self._config.workdir, md_path)
|
|
77
|
+
safe_p.parent.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
safe_p.write_text(content, encoding="utf-8")
|
|
79
|
+
results.append(f"✓ Markdown written to {md_path}")
|
|
80
|
+
except Exception as e:
|
|
81
|
+
return ToolResult(f"Error writing Markdown: {e}", is_error=True)
|
|
82
|
+
|
|
83
|
+
# Write PDF
|
|
84
|
+
if format in ("pdf", "both"):
|
|
85
|
+
pdf_path = output_path if output_path.endswith(".pdf") else output_path.rstrip(".md") + ".pdf"
|
|
86
|
+
try:
|
|
87
|
+
html_content = _markdown_to_html(content, title=title)
|
|
88
|
+
safe_pdf = _safe_path(self._config.workdir, pdf_path)
|
|
89
|
+
safe_pdf.parent.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
_html_to_pdf(html_content, str(safe_pdf))
|
|
91
|
+
results.append(f"✓ PDF written to {pdf_path}")
|
|
92
|
+
except PdfGenerationError as e:
|
|
93
|
+
results.append(f"⚠ PDF generation failed: {e}")
|
|
94
|
+
except Exception as e:
|
|
95
|
+
results.append(f"⚠ PDF error: {e}")
|
|
96
|
+
|
|
97
|
+
if not results:
|
|
98
|
+
return ToolResult("Error: No output was generated.", is_error=True)
|
|
99
|
+
|
|
100
|
+
return ToolResult("\n".join(results), is_error=False)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class PdfGenerationError(Exception):
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _markdown_to_html(md_content: str, title: str | None = None) -> str:
|
|
108
|
+
"""Convert markdown to an HTML string with basic styling."""
|
|
109
|
+
try:
|
|
110
|
+
from markdown_it import MarkdownIt # type: ignore[import]
|
|
111
|
+
md = MarkdownIt()
|
|
112
|
+
body = md.render(md_content)
|
|
113
|
+
except ImportError:
|
|
114
|
+
# Minimal fallback: wrap in <pre>
|
|
115
|
+
body = f"<pre>{md_content}</pre>"
|
|
116
|
+
|
|
117
|
+
doc_title = title or "Document"
|
|
118
|
+
return f"""<!DOCTYPE html>
|
|
119
|
+
<html>
|
|
120
|
+
<head>
|
|
121
|
+
<meta charset="UTF-8">
|
|
122
|
+
<title>{doc_title}</title>
|
|
123
|
+
<style>
|
|
124
|
+
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
125
|
+
max-width: 860px; margin: 40px auto; line-height: 1.6; color: #1a1a1a; }}
|
|
126
|
+
h1, h2, h3 {{ border-bottom: 1px solid #e0e0e0; padding-bottom: 4px; }}
|
|
127
|
+
code {{ background: #f4f4f4; padding: 2px 5px; border-radius: 3px; font-size: 0.9em; }}
|
|
128
|
+
pre code {{ display: block; padding: 12px; overflow-x: auto; }}
|
|
129
|
+
blockquote {{ border-left: 4px solid #ccc; margin: 0; padding: 8px 16px; color: #555; }}
|
|
130
|
+
table {{ border-collapse: collapse; width: 100%; }}
|
|
131
|
+
th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
|
|
132
|
+
th {{ background: #f0f0f0; }}
|
|
133
|
+
</style>
|
|
134
|
+
</head>
|
|
135
|
+
<body>
|
|
136
|
+
{f'<h1>{doc_title}</h1>' if title else ''}
|
|
137
|
+
{body}
|
|
138
|
+
</body>
|
|
139
|
+
</html>"""
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _html_to_pdf(html_content: str, output_path: str) -> None:
|
|
143
|
+
"""Convert HTML string to PDF file using pdfkit (requires wkhtmltopdf)."""
|
|
144
|
+
try:
|
|
145
|
+
import pdfkit # type: ignore[import]
|
|
146
|
+
options = {
|
|
147
|
+
"page-size": "A4",
|
|
148
|
+
"margin-top": "20mm",
|
|
149
|
+
"margin-bottom": "20mm",
|
|
150
|
+
"margin-left": "20mm",
|
|
151
|
+
"margin-right": "20mm",
|
|
152
|
+
"encoding": "UTF-8",
|
|
153
|
+
"quiet": "",
|
|
154
|
+
}
|
|
155
|
+
pdfkit.from_string(html_content, output_path, options=options)
|
|
156
|
+
except ImportError:
|
|
157
|
+
raise PdfGenerationError("pdfkit not installed. Run: pip install pdfkit")
|
|
158
|
+
except OSError as e:
|
|
159
|
+
if "wkhtmltopdf" in str(e).lower():
|
|
160
|
+
raise PdfGenerationError(
|
|
161
|
+
"wkhtmltopdf binary not found. Download from: https://wkhtmltopdf.org/downloads.html"
|
|
162
|
+
)
|
|
163
|
+
raise PdfGenerationError(str(e))
|