universal-mcp 0.1.12__py3-none-any.whl → 0.1.13rc2__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.
- universal_mcp/applications/__init__.py +51 -7
- universal_mcp/cli.py +109 -17
- universal_mcp/integrations/__init__.py +1 -1
- universal_mcp/integrations/integration.py +79 -0
- universal_mcp/servers/README.md +79 -0
- universal_mcp/servers/server.py +17 -29
- universal_mcp/stores/README.md +74 -0
- universal_mcp/stores/store.py +0 -2
- universal_mcp/templates/README.md.j2 +93 -0
- universal_mcp/templates/api_client.py.j2 +27 -0
- universal_mcp/tools/README.md +86 -0
- universal_mcp/tools/tools.py +1 -1
- universal_mcp/utils/agentr.py +90 -0
- universal_mcp/utils/api_generator.py +166 -208
- universal_mcp/utils/openapi.py +221 -321
- universal_mcp/utils/singleton.py +23 -0
- {universal_mcp-0.1.12.dist-info → universal_mcp-0.1.13rc2.dist-info}/METADATA +16 -41
- universal_mcp-0.1.13rc2.dist-info/RECORD +38 -0
- universal_mcp/applications/ahrefs/README.md +0 -76
- universal_mcp/applications/ahrefs/__init__.py +0 -0
- universal_mcp/applications/ahrefs/app.py +0 -2291
- universal_mcp/applications/cal_com_v2/README.md +0 -175
- universal_mcp/applications/cal_com_v2/__init__.py +0 -0
- universal_mcp/applications/cal_com_v2/app.py +0 -5390
- universal_mcp/applications/calendly/README.md +0 -78
- universal_mcp/applications/calendly/__init__.py +0 -0
- universal_mcp/applications/calendly/app.py +0 -1195
- universal_mcp/applications/clickup/README.md +0 -160
- universal_mcp/applications/clickup/__init__.py +0 -0
- universal_mcp/applications/clickup/app.py +0 -5009
- universal_mcp/applications/coda/README.md +0 -133
- universal_mcp/applications/coda/__init__.py +0 -0
- universal_mcp/applications/coda/app.py +0 -3671
- universal_mcp/applications/e2b/README.md +0 -37
- universal_mcp/applications/e2b/app.py +0 -65
- universal_mcp/applications/elevenlabs/README.md +0 -84
- universal_mcp/applications/elevenlabs/__init__.py +0 -0
- universal_mcp/applications/elevenlabs/app.py +0 -1402
- universal_mcp/applications/falai/README.md +0 -42
- universal_mcp/applications/falai/__init__.py +0 -0
- universal_mcp/applications/falai/app.py +0 -332
- universal_mcp/applications/figma/README.md +0 -74
- universal_mcp/applications/figma/__init__.py +0 -0
- universal_mcp/applications/figma/app.py +0 -1261
- universal_mcp/applications/firecrawl/README.md +0 -45
- universal_mcp/applications/firecrawl/app.py +0 -268
- universal_mcp/applications/github/README.md +0 -47
- universal_mcp/applications/github/app.py +0 -429
- universal_mcp/applications/gong/README.md +0 -88
- universal_mcp/applications/gong/__init__.py +0 -0
- universal_mcp/applications/gong/app.py +0 -2297
- universal_mcp/applications/google_calendar/app.py +0 -442
- universal_mcp/applications/google_docs/README.md +0 -40
- universal_mcp/applications/google_docs/app.py +0 -88
- universal_mcp/applications/google_drive/README.md +0 -44
- universal_mcp/applications/google_drive/app.py +0 -286
- universal_mcp/applications/google_mail/README.md +0 -47
- universal_mcp/applications/google_mail/app.py +0 -664
- universal_mcp/applications/google_sheet/README.md +0 -42
- universal_mcp/applications/google_sheet/app.py +0 -150
- universal_mcp/applications/hashnode/app.py +0 -81
- universal_mcp/applications/hashnode/prompt.md +0 -23
- universal_mcp/applications/heygen/README.md +0 -69
- universal_mcp/applications/heygen/__init__.py +0 -0
- universal_mcp/applications/heygen/app.py +0 -956
- universal_mcp/applications/mailchimp/README.md +0 -306
- universal_mcp/applications/mailchimp/__init__.py +0 -0
- universal_mcp/applications/mailchimp/app.py +0 -10937
- universal_mcp/applications/markitdown/app.py +0 -44
- universal_mcp/applications/notion/README.md +0 -55
- universal_mcp/applications/notion/__init__.py +0 -0
- universal_mcp/applications/notion/app.py +0 -527
- universal_mcp/applications/perplexity/README.md +0 -37
- universal_mcp/applications/perplexity/app.py +0 -65
- universal_mcp/applications/reddit/README.md +0 -45
- universal_mcp/applications/reddit/app.py +0 -379
- universal_mcp/applications/replicate/README.md +0 -65
- universal_mcp/applications/replicate/__init__.py +0 -0
- universal_mcp/applications/replicate/app.py +0 -980
- universal_mcp/applications/resend/README.md +0 -38
- universal_mcp/applications/resend/app.py +0 -37
- universal_mcp/applications/retell_ai/README.md +0 -46
- universal_mcp/applications/retell_ai/__init__.py +0 -0
- universal_mcp/applications/retell_ai/app.py +0 -333
- universal_mcp/applications/rocketlane/README.md +0 -42
- universal_mcp/applications/rocketlane/__init__.py +0 -0
- universal_mcp/applications/rocketlane/app.py +0 -194
- universal_mcp/applications/serpapi/README.md +0 -37
- universal_mcp/applications/serpapi/app.py +0 -73
- universal_mcp/applications/spotify/README.md +0 -116
- universal_mcp/applications/spotify/__init__.py +0 -0
- universal_mcp/applications/spotify/app.py +0 -2526
- universal_mcp/applications/supabase/README.md +0 -112
- universal_mcp/applications/supabase/__init__.py +0 -0
- universal_mcp/applications/supabase/app.py +0 -2970
- universal_mcp/applications/tavily/README.md +0 -38
- universal_mcp/applications/tavily/app.py +0 -51
- universal_mcp/applications/wrike/README.md +0 -71
- universal_mcp/applications/wrike/__init__.py +0 -0
- universal_mcp/applications/wrike/app.py +0 -1372
- universal_mcp/applications/youtube/README.md +0 -82
- universal_mcp/applications/youtube/__init__.py +0 -0
- universal_mcp/applications/youtube/app.py +0 -1428
- universal_mcp/applications/zenquotes/README.md +0 -37
- universal_mcp/applications/zenquotes/app.py +0 -31
- universal_mcp/integrations/agentr.py +0 -112
- universal_mcp-0.1.12.dist-info/RECORD +0 -119
- {universal_mcp-0.1.12.dist-info → universal_mcp-0.1.13rc2.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.12.dist-info → universal_mcp-0.1.13rc2.dist-info}/entry_points.txt +0 -0
@@ -1,40 +1,13 @@
|
|
1
|
-
import ast
|
2
|
-
import importlib.util
|
3
1
|
import inspect
|
4
2
|
import os
|
5
|
-
import traceback
|
6
3
|
from pathlib import Path
|
4
|
+
from loguru import logger
|
5
|
+
import shutil
|
6
|
+
import importlib.util
|
7
|
+
from jinja2 import Environment, FileSystemLoader, TemplateError, select_autoescape
|
7
8
|
|
8
|
-
from universal_mcp.utils.docgen import process_file
|
9
9
|
from universal_mcp.utils.openapi import generate_api_client, load_schema
|
10
10
|
|
11
|
-
README_TEMPLATE = """
|
12
|
-
# {name} MCP Server
|
13
|
-
|
14
|
-
An MCP Server for the {name} API.
|
15
|
-
|
16
|
-
## Supported Integrations
|
17
|
-
|
18
|
-
- AgentR
|
19
|
-
- API Key (Coming Soon)
|
20
|
-
- OAuth (Coming Soon)
|
21
|
-
|
22
|
-
## Tools
|
23
|
-
|
24
|
-
{tools}
|
25
|
-
|
26
|
-
## Usage
|
27
|
-
|
28
|
-
- Login to AgentR
|
29
|
-
- Follow the quickstart guide to setup MCP Server for your client
|
30
|
-
- Visit Apps Store and enable the {name} app
|
31
|
-
- Restart the MCP Server
|
32
|
-
|
33
|
-
### Local Development
|
34
|
-
|
35
|
-
- Follow the README to test with the local MCP Server
|
36
|
-
"""
|
37
|
-
|
38
11
|
|
39
12
|
def echo(message: str, err: bool = False) -> None:
|
40
13
|
"""Echo a message to the console, with optional error flag."""
|
@@ -53,70 +26,6 @@ def validate_and_load_schema(schema_path: Path) -> dict:
|
|
53
26
|
echo(f"Error loading schema: {e}", err=True)
|
54
27
|
raise
|
55
28
|
|
56
|
-
|
57
|
-
def write_and_verify_code(output_path: Path, code: str) -> None:
|
58
|
-
"""Write generated code to file and verify its contents."""
|
59
|
-
with open(output_path, "w") as f:
|
60
|
-
f.write(code)
|
61
|
-
echo(f"Generated API client at: {output_path}")
|
62
|
-
|
63
|
-
try:
|
64
|
-
with open(output_path) as f:
|
65
|
-
file_content = f.read()
|
66
|
-
echo(f"Successfully wrote {len(file_content)} bytes to {output_path}")
|
67
|
-
ast.parse(file_content)
|
68
|
-
echo("Python syntax check passed")
|
69
|
-
except SyntaxError as e:
|
70
|
-
echo(f"Warning: Generated file has syntax error: {e}", err=True)
|
71
|
-
except Exception as e:
|
72
|
-
echo(f"Error verifying output file: {e}", err=True)
|
73
|
-
|
74
|
-
|
75
|
-
async def generate_docstrings(script_path: str) -> dict[str, int]:
|
76
|
-
"""Generate docstrings for the given script file."""
|
77
|
-
echo(f"Adding docstrings to {script_path}...")
|
78
|
-
|
79
|
-
if not os.path.exists(script_path):
|
80
|
-
echo(f"Warning: File {script_path} does not exist", err=True)
|
81
|
-
return {"functions_processed": 0}
|
82
|
-
|
83
|
-
try:
|
84
|
-
with open(script_path) as f:
|
85
|
-
content = f.read()
|
86
|
-
echo(f"Successfully read {len(content)} bytes from {script_path}")
|
87
|
-
except Exception as e:
|
88
|
-
echo(f"Error reading file for docstring generation: {e}", err=True)
|
89
|
-
return {"functions_processed": 0}
|
90
|
-
|
91
|
-
try:
|
92
|
-
processed = process_file(script_path)
|
93
|
-
return {"functions_processed": processed}
|
94
|
-
except Exception as e:
|
95
|
-
echo(f"Error running docstring generation: {e}", err=True)
|
96
|
-
traceback.print_exc()
|
97
|
-
return {"functions_processed": 0}
|
98
|
-
|
99
|
-
|
100
|
-
def setup_app_directory(folder_name: str, source_file: Path) -> tuple[Path, Path]:
|
101
|
-
"""Set up application directory structure and copy generated code."""
|
102
|
-
applications_dir = Path(__file__).parent.parent / "applications"
|
103
|
-
app_dir = applications_dir / folder_name
|
104
|
-
app_dir.mkdir(exist_ok=True)
|
105
|
-
|
106
|
-
init_file = app_dir / "__init__.py"
|
107
|
-
if not init_file.exists():
|
108
|
-
with open(init_file, "w") as f:
|
109
|
-
f.write("")
|
110
|
-
|
111
|
-
app_file = app_dir / "app.py"
|
112
|
-
with open(source_file) as src, open(app_file, "w") as dest:
|
113
|
-
app_content = src.read()
|
114
|
-
dest.write(app_content)
|
115
|
-
|
116
|
-
echo(f"API client installed at: {app_file}")
|
117
|
-
return app_dir, app_file
|
118
|
-
|
119
|
-
|
120
29
|
def get_class_info(module: any) -> tuple[str | None, any]:
|
121
30
|
"""Find the main class in the generated module."""
|
122
31
|
for name, obj in inspect.getmembers(module):
|
@@ -124,146 +33,195 @@ def get_class_info(module: any) -> tuple[str | None, any]:
|
|
124
33
|
return name, obj
|
125
34
|
return None, None
|
126
35
|
|
127
|
-
|
128
|
-
def collect_tools(class_obj: any, folder_name: str) -> list[tuple[str, str]]:
|
129
|
-
"""Collect tool information from the class."""
|
130
|
-
tools = []
|
131
|
-
|
132
|
-
# Try to get tools from list_tools method
|
133
|
-
if class_obj and hasattr(class_obj, "list_tools"):
|
134
|
-
try:
|
135
|
-
instance = class_obj()
|
136
|
-
tool_list = instance.list_tools()
|
137
|
-
|
138
|
-
for tool in tool_list:
|
139
|
-
func_name = tool.__name__
|
140
|
-
if func_name.startswith("_") or func_name in ("__init__", "list_tools"):
|
141
|
-
continue
|
142
|
-
|
143
|
-
doc = tool.__doc__ or f"Function for {func_name.replace('_', ' ')}"
|
144
|
-
summary = doc.split("\n\n")[0].strip()
|
145
|
-
tools.append((func_name, summary))
|
146
|
-
except Exception as e:
|
147
|
-
echo(f"Note: Couldn't instantiate class to get tool list: {e}")
|
148
|
-
|
149
|
-
# Fall back to inspecting class methods directly
|
150
|
-
if not tools and class_obj:
|
151
|
-
for name, method in inspect.getmembers(class_obj, inspect.isfunction):
|
152
|
-
if name.startswith("_") or name in ("__init__", "list_tools"):
|
153
|
-
continue
|
154
|
-
|
155
|
-
doc = method.__doc__ or f"Function for {name.replace('_', ' ')}"
|
156
|
-
summary = doc.split("\n\n")[0].strip()
|
157
|
-
tools.append((name, summary))
|
158
|
-
|
159
|
-
return tools
|
160
|
-
|
161
|
-
|
162
36
|
def generate_readme(
|
163
|
-
app_dir: Path, folder_name: str, tools: list
|
37
|
+
app_dir: Path, folder_name: str, tools: list
|
164
38
|
) -> Path:
|
165
|
-
"""Generate README.md with API documentation.
|
39
|
+
"""Generate README.md with API documentation.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
app_dir: Directory where the README will be generated
|
43
|
+
folder_name: Name of the application folder
|
44
|
+
tools: List of Function objects from the OpenAPI schema
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
Path to the generated README file
|
48
|
+
|
49
|
+
Raises:
|
50
|
+
FileNotFoundError: If the template directory doesn't exist
|
51
|
+
TemplateError: If there's an error rendering the template
|
52
|
+
IOError: If there's an error writing the README file
|
53
|
+
"""
|
166
54
|
app = folder_name.replace("_", " ").title()
|
55
|
+
logger.info(f"Generating README for {app} in {app_dir}")
|
167
56
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
57
|
+
# Format tools into (name, description) tuples
|
58
|
+
formatted_tools = []
|
59
|
+
for tool in tools:
|
60
|
+
name = tool.__name__
|
61
|
+
description = tool.__doc__.strip().split("\n")[0]
|
62
|
+
formatted_tools.append((name, description))
|
63
|
+
|
64
|
+
# Set up Jinja2 environment
|
65
|
+
template_dir = Path(__file__).parent.parent / "templates"
|
66
|
+
if not template_dir.exists():
|
67
|
+
logger.error(f"Template directory not found: {template_dir}")
|
68
|
+
raise FileNotFoundError(f"Template directory not found: {template_dir}")
|
69
|
+
|
70
|
+
try:
|
71
|
+
env = Environment(
|
72
|
+
loader=FileSystemLoader(template_dir),
|
73
|
+
autoescape=select_autoescape()
|
183
74
|
)
|
75
|
+
template = env.get_template("README.md.j2")
|
76
|
+
except Exception as e:
|
77
|
+
logger.error(f"Error loading template: {e}")
|
78
|
+
raise TemplateError(f"Error loading template: {e}")
|
79
|
+
|
80
|
+
# Render the template
|
81
|
+
try:
|
82
|
+
readme_content = template.render(
|
83
|
+
name=app,
|
84
|
+
tools=formatted_tools
|
85
|
+
)
|
86
|
+
except Exception as e:
|
87
|
+
logger.error(f"Error rendering template: {e}")
|
88
|
+
raise TemplateError(f"Error rendering template: {e}")
|
184
89
|
|
185
|
-
|
186
|
-
name=app,
|
187
|
-
tools=tools_content,
|
188
|
-
usage="",
|
189
|
-
)
|
90
|
+
# Write the README file
|
190
91
|
readme_file = app_dir / "README.md"
|
191
|
-
|
192
|
-
|
92
|
+
try:
|
93
|
+
with open(readme_file, "w") as f:
|
94
|
+
f.write(readme_content)
|
95
|
+
logger.info(f"Documentation generated at: {readme_file}")
|
96
|
+
except Exception as e:
|
97
|
+
logger.error(f"Error writing README file: {e}")
|
98
|
+
raise IOError(f"Error writing README file: {e}")
|
193
99
|
|
194
|
-
echo(f"Documentation generated at: {readme_file}")
|
195
100
|
return readme_file
|
196
101
|
|
102
|
+
def test_correct_output(gen_file: Path):
|
103
|
+
# Check file is non-empty
|
104
|
+
if gen_file.stat().st_size == 0:
|
105
|
+
msg = f"Generated file {gen_file} is empty."
|
106
|
+
logger.error(msg)
|
107
|
+
return False
|
108
|
+
|
109
|
+
# Basic import test on generated code
|
110
|
+
try:
|
111
|
+
spec = importlib.util.spec_from_file_location("temp_module", gen_file)
|
112
|
+
module = importlib.util.module_from_spec(spec)
|
113
|
+
spec.loader.exec_module(module) # type: ignore
|
114
|
+
logger.info("Intermediate code import test passed.")
|
115
|
+
return True
|
116
|
+
except Exception as e:
|
117
|
+
logger.error(f"Import test failed for generated code: {e}")
|
118
|
+
return False
|
119
|
+
return True
|
197
120
|
|
198
|
-
|
121
|
+
|
122
|
+
def generate_api_from_schema(
|
199
123
|
schema_path: Path,
|
200
124
|
output_path: Path | None = None,
|
201
125
|
add_docstrings: bool = True,
|
202
|
-
) ->
|
126
|
+
) -> tuple[Path, Path]:
|
127
|
+
"""
|
128
|
+
Generate API client from OpenAPI schema and write to app.py with a README.
|
129
|
+
|
130
|
+
Steps:
|
131
|
+
1. Parse and validate the OpenAPI schema.
|
132
|
+
2. Generate client code.
|
133
|
+
3. Ensure output directory exists.
|
134
|
+
4. Write code to an intermediate app_generated.py and perform basic import checks.
|
135
|
+
5. Copy/overwrite intermediate file to app.py.
|
136
|
+
6. Collect tools and generate README.md.
|
203
137
|
"""
|
204
|
-
|
138
|
+
# Local imports for logging and file operations
|
205
139
|
|
206
|
-
Args:
|
207
|
-
schema_path: Path to the OpenAPI schema file
|
208
|
-
output_path: Output file path - should match the API name (e.g., 'twitter.py' for Twitter API)
|
209
|
-
add_docstrings: Whether to add docstrings to the generated code
|
210
140
|
|
211
|
-
|
212
|
-
|
213
|
-
|
141
|
+
logger.info("Starting API generation for schema: %s", schema_path)
|
142
|
+
|
143
|
+
# 1. Parse and validate schema
|
214
144
|
try:
|
215
145
|
schema = validate_and_load_schema(schema_path)
|
146
|
+
logger.info("Schema loaded and validated successfully.")
|
147
|
+
except Exception as e:
|
148
|
+
logger.error("Failed to load or validate schema: %s", e)
|
149
|
+
raise
|
150
|
+
|
151
|
+
# 2. Generate client code
|
152
|
+
try:
|
216
153
|
code = generate_api_client(schema)
|
154
|
+
logger.info("API client code generated.")
|
155
|
+
except Exception as e:
|
156
|
+
logger.error("Code generation failed: %s", e)
|
157
|
+
raise
|
217
158
|
|
218
|
-
|
219
|
-
|
159
|
+
# If no output_path provided, return raw code
|
160
|
+
if not output_path:
|
161
|
+
logger.debug("No output_path provided, returning code as string.")
|
162
|
+
return {"code": code}
|
163
|
+
|
164
|
+
# 3. Ensure output directory exists
|
165
|
+
target_dir = output_path
|
166
|
+
if not target_dir.exists():
|
167
|
+
logger.info("Creating output directory: %s", target_dir)
|
168
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
169
|
+
|
170
|
+
# 4. Write to intermediate file and perform basic checks
|
171
|
+
gen_file = target_dir / "app_generated.py"
|
172
|
+
logger.info("Writing generated code to intermediate file: %s", gen_file)
|
173
|
+
with open(gen_file, "w") as f:
|
174
|
+
f.write(code)
|
220
175
|
|
221
|
-
|
222
|
-
|
176
|
+
if not test_correct_output(gen_file):
|
177
|
+
logger.error("Generated code validation failed for '%s'. Aborting generation.", gen_file)
|
178
|
+
logger.info("Next steps:")
|
179
|
+
logger.info(" 1) Review your OpenAPI schema for potential mismatches.")
|
180
|
+
logger.info(" 2) Inspect '%s' for syntax or logic errors in the generated code.", gen_file)
|
181
|
+
logger.info(" 3) Correct the issues and re-run the command.")
|
182
|
+
return {"error": "Validation failed. See logs above for detailed instructions."}
|
183
|
+
|
184
|
+
# 5. Copy to final app.py (overwrite if exists)
|
185
|
+
app_file = target_dir / "app.py"
|
186
|
+
if app_file.exists():
|
187
|
+
logger.warning("Overwriting existing file: %s", app_file)
|
188
|
+
else:
|
189
|
+
logger.info("Creating new file: %s", app_file)
|
190
|
+
shutil.copy(gen_file, app_file)
|
191
|
+
logger.info("App file written to: %s", app_file)
|
223
192
|
|
224
|
-
|
193
|
+
# 6. Collect tools and generate README
|
194
|
+
import importlib.util
|
195
|
+
import sys
|
225
196
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
else:
|
232
|
-
echo("Docstring generation failed", err=True)
|
233
|
-
else:
|
234
|
-
echo("Skipping docstring generation as requested")
|
197
|
+
# Load the generated module as "temp_module"
|
198
|
+
spec = importlib.util.spec_from_file_location("temp_module", str(app_file))
|
199
|
+
module = importlib.util.module_from_spec(spec)
|
200
|
+
sys.modules["temp_module"] = module
|
201
|
+
spec.loader.exec_module(module)
|
235
202
|
|
236
|
-
|
203
|
+
# Retrieve the generated API class
|
204
|
+
class_name, cls = get_class_info(module)
|
237
205
|
|
206
|
+
# Instantiate client and collect its tools
|
207
|
+
tools = []
|
208
|
+
if cls:
|
238
209
|
try:
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
210
|
+
client = cls()
|
211
|
+
tools = client.list_tools()
|
212
|
+
except Exception as e:
|
213
|
+
logger.warning("Failed to instantiate '%s' or list tools: %s", class_name, e)
|
214
|
+
else:
|
215
|
+
logger.warning("No generated class found in module 'temp_module'")
|
216
|
+
readme_file = generate_readme(target_dir, output_path.stem, tools)
|
217
|
+
logger.info("README generated at: %s", readme_file)
|
243
218
|
|
244
|
-
class_name, class_obj = get_class_info(module)
|
245
|
-
if not class_name:
|
246
|
-
class_name = folder_name.capitalize() + "App"
|
247
219
|
|
248
|
-
|
249
|
-
|
220
|
+
# Cleanup intermediate file
|
221
|
+
try:
|
222
|
+
os.remove(gen_file)
|
223
|
+
logger.debug("Cleaned up intermediate file: %s", gen_file)
|
224
|
+
except Exception as e:
|
225
|
+
logger.warning("Could not remove intermediate file %s: %s", gen_file, e)
|
250
226
|
|
251
|
-
|
252
|
-
echo(f"Error generating documentation: {e}", err=True)
|
253
|
-
readme_file = None
|
254
|
-
|
255
|
-
return {
|
256
|
-
"app_file": str(app_file),
|
257
|
-
"readme_file": str(readme_file) if readme_file else None,
|
258
|
-
}
|
259
|
-
|
260
|
-
finally:
|
261
|
-
if output_path and output_path.exists():
|
262
|
-
try:
|
263
|
-
output_path.unlink()
|
264
|
-
echo(f"Cleaned up temporary file: {output_path}")
|
265
|
-
except Exception as e:
|
266
|
-
echo(
|
267
|
-
f"Warning: Could not remove temporary file {output_path}: {e}",
|
268
|
-
err=True,
|
269
|
-
)
|
227
|
+
return app_file, readme_file
|