mfcli 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 (138) hide show
  1. mfcli/.env.example +72 -0
  2. mfcli/__init__.py +0 -0
  3. mfcli/agents/__init__.py +0 -0
  4. mfcli/agents/controller/__init__.py +0 -0
  5. mfcli/agents/controller/agent.py +19 -0
  6. mfcli/agents/controller/config.yaml +27 -0
  7. mfcli/agents/controller/tools.py +42 -0
  8. mfcli/agents/tools/general.py +118 -0
  9. mfcli/alembic/env.py +61 -0
  10. mfcli/alembic/script.py.mako +28 -0
  11. mfcli/alembic/versions/6ccc0c7c397c_added_fields_to_pdf_parts_model.py +39 -0
  12. mfcli/alembic/versions/769019ef4870_added_gemini_file_path_to_pdf_part_model.py +33 -0
  13. mfcli/alembic/versions/7a2e3a779fdc_added_functional_block_and_component_.py +54 -0
  14. mfcli/alembic/versions/7d5adb2a47a7_added_pdf_parts_model.py +41 -0
  15. mfcli/alembic/versions/7fcb7d6a5836_init.py +167 -0
  16. mfcli/alembic/versions/e0f2b5765c72_added_cascade_delete_for_models_that_.py +32 -0
  17. mfcli/alembic.ini +147 -0
  18. mfcli/cli/__init__.py +0 -0
  19. mfcli/cli/dependencies.py +59 -0
  20. mfcli/cli/main.py +200 -0
  21. mfcli/client/__init__.py +0 -0
  22. mfcli/client/chroma_db.py +184 -0
  23. mfcli/client/docling.py +44 -0
  24. mfcli/client/gemini.py +252 -0
  25. mfcli/client/llama_parse.py +38 -0
  26. mfcli/client/vector_db.py +93 -0
  27. mfcli/constants/__init__.py +0 -0
  28. mfcli/constants/base_enum.py +18 -0
  29. mfcli/constants/directory_names.py +1 -0
  30. mfcli/constants/file_types.py +189 -0
  31. mfcli/constants/gemini.py +1 -0
  32. mfcli/constants/openai.py +6 -0
  33. mfcli/constants/pipeline_run_status.py +3 -0
  34. mfcli/crud/__init__.py +0 -0
  35. mfcli/crud/file.py +42 -0
  36. mfcli/crud/functional_blocks.py +26 -0
  37. mfcli/crud/netlist.py +18 -0
  38. mfcli/crud/pipeline_run.py +17 -0
  39. mfcli/crud/project.py +144 -0
  40. mfcli/digikey/__init__.py +0 -0
  41. mfcli/digikey/digikey.py +105 -0
  42. mfcli/main.py +5 -0
  43. mfcli/mcp/__init__.py +0 -0
  44. mfcli/mcp/configs/cline_mcp_settings.json +11 -0
  45. mfcli/mcp/configs/mfcli.mcp.json +7 -0
  46. mfcli/mcp/mcp_instance.py +6 -0
  47. mfcli/mcp/server.py +37 -0
  48. mfcli/mcp/state_manager.py +51 -0
  49. mfcli/mcp/tools/__init__.py +0 -0
  50. mfcli/mcp/tools/query_knowledgebase.py +108 -0
  51. mfcli/models/__init__.py +10 -0
  52. mfcli/models/base.py +10 -0
  53. mfcli/models/bom.py +71 -0
  54. mfcli/models/datasheet.py +10 -0
  55. mfcli/models/debug_setup.py +64 -0
  56. mfcli/models/file.py +43 -0
  57. mfcli/models/file_docket.py +94 -0
  58. mfcli/models/file_metadata.py +19 -0
  59. mfcli/models/functional_blocks.py +94 -0
  60. mfcli/models/llm_response.py +5 -0
  61. mfcli/models/mcu.py +97 -0
  62. mfcli/models/mcu_errata.py +26 -0
  63. mfcli/models/netlist.py +59 -0
  64. mfcli/models/pdf_parts.py +25 -0
  65. mfcli/models/pipeline_run.py +34 -0
  66. mfcli/models/project.py +27 -0
  67. mfcli/models/project_metadata.py +15 -0
  68. mfcli/pipeline/__init__.py +0 -0
  69. mfcli/pipeline/analysis/__init__.py +0 -0
  70. mfcli/pipeline/analysis/bom_netlist_mapper.py +28 -0
  71. mfcli/pipeline/analysis/generators/__init__.py +0 -0
  72. mfcli/pipeline/analysis/generators/bom/__init__.py +0 -0
  73. mfcli/pipeline/analysis/generators/bom/bom.py +74 -0
  74. mfcli/pipeline/analysis/generators/debug_setup/__init__.py +0 -0
  75. mfcli/pipeline/analysis/generators/debug_setup/debug_setup.py +71 -0
  76. mfcli/pipeline/analysis/generators/debug_setup/instructions.py +150 -0
  77. mfcli/pipeline/analysis/generators/functional_blocks/__init__.py +0 -0
  78. mfcli/pipeline/analysis/generators/functional_blocks/functional_blocks.py +93 -0
  79. mfcli/pipeline/analysis/generators/functional_blocks/instructions.py +34 -0
  80. mfcli/pipeline/analysis/generators/functional_blocks/validator.py +94 -0
  81. mfcli/pipeline/analysis/generators/generator.py +258 -0
  82. mfcli/pipeline/analysis/generators/generator_base.py +18 -0
  83. mfcli/pipeline/analysis/generators/mcu/__init__.py +0 -0
  84. mfcli/pipeline/analysis/generators/mcu/instructions.py +156 -0
  85. mfcli/pipeline/analysis/generators/mcu/mcu.py +84 -0
  86. mfcli/pipeline/analysis/generators/mcu_errata/__init__.py +1 -0
  87. mfcli/pipeline/analysis/generators/mcu_errata/instructions.py +77 -0
  88. mfcli/pipeline/analysis/generators/mcu_errata/mcu_errata.py +95 -0
  89. mfcli/pipeline/analysis/generators/summary/__init__.py +0 -0
  90. mfcli/pipeline/analysis/generators/summary/summary.py +47 -0
  91. mfcli/pipeline/classifier.py +93 -0
  92. mfcli/pipeline/data_enricher.py +15 -0
  93. mfcli/pipeline/extractor.py +34 -0
  94. mfcli/pipeline/extractors/__init__.py +0 -0
  95. mfcli/pipeline/extractors/pdf.py +12 -0
  96. mfcli/pipeline/parser.py +120 -0
  97. mfcli/pipeline/parsers/__init__.py +0 -0
  98. mfcli/pipeline/parsers/netlist/__init__.py +0 -0
  99. mfcli/pipeline/parsers/netlist/edif.py +93 -0
  100. mfcli/pipeline/parsers/netlist/kicad_legacy_net.py +326 -0
  101. mfcli/pipeline/parsers/netlist/kicad_spice.py +135 -0
  102. mfcli/pipeline/parsers/netlist/pads.py +185 -0
  103. mfcli/pipeline/parsers/netlist/protel.py +166 -0
  104. mfcli/pipeline/parsers/netlist/protel_detector.py +29 -0
  105. mfcli/pipeline/pipeline.py +470 -0
  106. mfcli/pipeline/preprocessors/__init__.py +0 -0
  107. mfcli/pipeline/preprocessors/user_guide.py +127 -0
  108. mfcli/pipeline/run_context.py +32 -0
  109. mfcli/pipeline/schema_mapper.py +89 -0
  110. mfcli/pipeline/sub_classifier.py +115 -0
  111. mfcli/utils/__init__.py +0 -0
  112. mfcli/utils/cline_rules.py +256 -0
  113. mfcli/utils/config.py +33 -0
  114. mfcli/utils/configurator.py +324 -0
  115. mfcli/utils/data_cleaner.py +114 -0
  116. mfcli/utils/datasheet_vectorizer.py +283 -0
  117. mfcli/utils/directory_manager.py +116 -0
  118. mfcli/utils/file_upload.py +298 -0
  119. mfcli/utils/files.py +16 -0
  120. mfcli/utils/http_requests.py +54 -0
  121. mfcli/utils/kb_lister.py +89 -0
  122. mfcli/utils/kb_remover.py +173 -0
  123. mfcli/utils/logger.py +28 -0
  124. mfcli/utils/mcp_configurator.py +394 -0
  125. mfcli/utils/migrations.py +18 -0
  126. mfcli/utils/orm.py +43 -0
  127. mfcli/utils/pdf_splitter.py +63 -0
  128. mfcli/utils/pre_uninstall.py +167 -0
  129. mfcli/utils/query_service.py +22 -0
  130. mfcli/utils/system_check.py +306 -0
  131. mfcli/utils/tools.py +98 -0
  132. mfcli/utils/vectorizer.py +28 -0
  133. mfcli-0.2.1.dist-info/METADATA +956 -0
  134. mfcli-0.2.1.dist-info/RECORD +138 -0
  135. mfcli-0.2.1.dist-info/WHEEL +5 -0
  136. mfcli-0.2.1.dist-info/entry_points.txt +4 -0
  137. mfcli-0.2.1.dist-info/licenses/LICENSE +21 -0
  138. mfcli-0.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,324 @@
1
+ """Interactive configuration wizard for mfcli."""
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from mfcli.utils.directory_manager import app_dirs
8
+
9
+
10
+ def get_env_path() -> Path:
11
+ """Get the path to the .env file."""
12
+ return app_dirs.env_file_path
13
+
14
+
15
+ def read_existing_env() -> dict:
16
+ """Read existing environment variables from .env file."""
17
+ env_path = get_env_path()
18
+ env_vars = {}
19
+
20
+ if env_path.exists():
21
+ with open(env_path, 'r') as f:
22
+ for line in f:
23
+ line = line.strip()
24
+ if line and not line.startswith('#') and '=' in line:
25
+ key, value = line.split('=', 1)
26
+ env_vars[key.strip()] = value.strip()
27
+
28
+ return env_vars
29
+
30
+
31
+ def write_env_file(env_vars: dict) -> None:
32
+ """Write environment variables to .env file."""
33
+ env_path = get_env_path()
34
+ env_path.parent.mkdir(parents=True, exist_ok=True)
35
+
36
+ # Read the template
37
+ template_path = Path(__file__).parent.parent / '.env.example'
38
+ if not template_path.exists():
39
+ # Fallback: create basic template
40
+ template_content = []
41
+ for key in env_vars:
42
+ template_content.append(f"{key}={env_vars[key]}")
43
+ content = '\n'.join(template_content)
44
+ else:
45
+ with open(template_path, 'r') as f:
46
+ template_content = f.read()
47
+
48
+ # Replace placeholder values with actual values
49
+ content = template_content
50
+ for key, value in env_vars.items():
51
+ # Replace the placeholder value in the template
52
+ content = content.replace(f"{key}=your_{key.lower()}_here", f"{key}={value}")
53
+ content = content.replace(f"{key}=your_{key.replace('_', ' ').lower()}_here", f"{key}={value}")
54
+ # Handle specific patterns
55
+ if key == 'google_api_key':
56
+ content = content.replace(f"{key}=your_google_api_key_here", f"{key}={value}")
57
+ elif key == 'openai_api_key':
58
+ content = content.replace(f"{key}=your_openai_api_key_here", f"{key}={value}")
59
+ elif key == 'llama_cloud_api_key':
60
+ content = content.replace(f"{key}=your_llamaparse_api_key_here", f"{key}={value}")
61
+ elif key == 'digikey_client_id':
62
+ content = content.replace(f"{key}=your_digikey_client_id_here", f"{key}={value}")
63
+ elif key == 'digikey_client_secret':
64
+ content = content.replace(f"{key}=your_digikey_client_secret_here", f"{key}={value}")
65
+
66
+ with open(env_path, 'w') as f:
67
+ f.write(content)
68
+
69
+
70
+ def prompt_for_value(
71
+ key: str,
72
+ description: str,
73
+ link: Optional[str] = None,
74
+ current_value: Optional[str] = None,
75
+ required: bool = True
76
+ ) -> Optional[str]:
77
+ """Prompt user for a configuration value."""
78
+ print(f"\n{'='*70}")
79
+ print(f" {description}")
80
+ if link:
81
+ print(f" Get your key: {link}")
82
+ if current_value and current_value != f"your_{key.lower()}_here":
83
+ print(f" Current value: {current_value[:20]}..." if len(current_value) > 20 else f" Current value: {current_value}")
84
+ prompt = f" Enter new value (press Enter to keep current): "
85
+ else:
86
+ prompt = f" Enter value{' (required)' if required else ' (optional)'}: "
87
+
88
+ print(f"{'='*70}")
89
+
90
+ value = input(prompt).strip()
91
+
92
+ if not value:
93
+ if current_value and current_value != f"your_{key.lower()}_here":
94
+ return current_value
95
+ elif not required:
96
+ return None
97
+ else:
98
+ print(" ❌ This value is required!")
99
+ return prompt_for_value(key, description, link, current_value, required)
100
+
101
+ return value
102
+
103
+
104
+ def validate_api_key(key_name: str, api_key: str) -> bool:
105
+ """Validate an API key by making a test request."""
106
+ print(f"\n Validating {key_name}...", end=' ')
107
+ sys.stdout.flush()
108
+
109
+ try:
110
+ if key_name == "Google API":
111
+ import google.generativeai as genai
112
+ genai.configure(api_key=api_key)
113
+ # Test with a simple list models call
114
+ list(genai.list_models())
115
+ print("✅")
116
+ return True
117
+
118
+ elif key_name == "OpenAI API":
119
+ from openai import OpenAI
120
+ client = OpenAI(api_key=api_key)
121
+ # Test with a simple models list call
122
+ client.models.list()
123
+ print("✅")
124
+ return True
125
+
126
+ elif key_name == "LlamaParse API":
127
+ import requests
128
+ headers = {"Authorization": f"Bearer {api_key}"}
129
+ # LlamaParse doesn't have a simple test endpoint, so we'll just check format
130
+ if len(api_key) > 20:
131
+ print("✅ (format check)")
132
+ return True
133
+ else:
134
+ print("❌ Invalid format")
135
+ return False
136
+
137
+ elif key_name == "DigiKey API":
138
+ # DigiKey validation would require OAuth flow, so we'll just check format
139
+ if len(api_key) > 10:
140
+ print("✅ (format check)")
141
+ return True
142
+ else:
143
+ print("❌ Invalid format")
144
+ return False
145
+
146
+ except Exception as e:
147
+ print(f"❌ ({str(e)[:50]}...)")
148
+ return False
149
+
150
+ return True
151
+
152
+
153
+ def run_configuration_wizard() -> None:
154
+ """Run the interactive configuration wizard."""
155
+ print("\n" + "="*70)
156
+ print(" MFCLI CONFIGURATION WIZARD")
157
+ print("="*70)
158
+ print("\n This wizard will help you configure mfcli with your API keys.")
159
+ print(" You can press Ctrl+C at any time to exit.\n")
160
+
161
+ try:
162
+ # Read existing configuration
163
+ existing_env = read_existing_env()
164
+ new_env = existing_env.copy()
165
+
166
+ # Google API Key
167
+ value = prompt_for_value(
168
+ "google_api_key",
169
+ "Google Gemini API Key",
170
+ "https://aistudio.google.com/app/apikey",
171
+ existing_env.get("google_api_key"),
172
+ required=True
173
+ )
174
+ if value:
175
+ new_env["google_api_key"] = value
176
+ validate_api_key("Google API", value)
177
+
178
+ # OpenAI API Key
179
+ value = prompt_for_value(
180
+ "openai_api_key",
181
+ "OpenAI API Key (for embeddings)",
182
+ "https://platform.openai.com/api-keys",
183
+ existing_env.get("openai_api_key"),
184
+ required=True
185
+ )
186
+ if value:
187
+ new_env["openai_api_key"] = value
188
+ validate_api_key("OpenAI API", value)
189
+
190
+ # LlamaParse API Key
191
+ value = prompt_for_value(
192
+ "llama_cloud_api_key",
193
+ "LlamaParse API Key (for PDF parsing)",
194
+ "https://cloud.llamaindex.ai/",
195
+ existing_env.get("llama_cloud_api_key"),
196
+ required=True
197
+ )
198
+ if value:
199
+ new_env["llama_cloud_api_key"] = value
200
+ validate_api_key("LlamaParse API", value)
201
+
202
+ # DigiKey Client ID
203
+ value = prompt_for_value(
204
+ "digikey_client_id",
205
+ "DigiKey Client ID (for datasheet downloads)",
206
+ "https://developer.digikey.com/",
207
+ existing_env.get("digikey_client_id"),
208
+ required=True
209
+ )
210
+ if value:
211
+ new_env["digikey_client_id"] = value
212
+ validate_api_key("DigiKey API", value)
213
+
214
+ # DigiKey Client Secret
215
+ value = prompt_for_value(
216
+ "digikey_client_secret",
217
+ "DigiKey Client Secret",
218
+ None,
219
+ existing_env.get("digikey_client_secret"),
220
+ required=True
221
+ )
222
+ if value:
223
+ new_env["digikey_client_secret"] = value
224
+
225
+ # Embedding configuration
226
+ print("\n" + "="*70)
227
+ print(" Vector Database Configuration")
228
+ print("="*70)
229
+ print(" Using default values:")
230
+ print(" - Chunk size: 1000")
231
+ print(" - Chunk overlap: 200")
232
+ print(" - Embedding model: text-embedding-3-small")
233
+ print(" - Embedding dimensions: 1536")
234
+
235
+ change_defaults = input("\n Change these defaults? (y/N): ").strip().lower()
236
+
237
+ if change_defaults == 'y':
238
+ value = input(" Chunk size [1000]: ").strip()
239
+ new_env["chunk_size"] = value if value else "1000"
240
+
241
+ value = input(" Chunk overlap [200]: ").strip()
242
+ new_env["chunk_overlap"] = value if value else "200"
243
+
244
+ value = input(" Embedding model [text-embedding-3-small]: ").strip()
245
+ new_env["embedding_model"] = value if value else "text-embedding-3-small"
246
+
247
+ value = input(" Embedding dimensions [1536]: ").strip()
248
+ new_env["embedding_dimensions"] = value if value else "1536"
249
+ else:
250
+ new_env["chunk_size"] = existing_env.get("chunk_size", "1000")
251
+ new_env["chunk_overlap"] = existing_env.get("chunk_overlap", "200")
252
+ new_env["embedding_model"] = existing_env.get("embedding_model", "text-embedding-3-small")
253
+ new_env["embedding_dimensions"] = existing_env.get("embedding_dimensions", "1536")
254
+
255
+ # Write configuration
256
+ write_env_file(new_env)
257
+
258
+ env_path = get_env_path()
259
+ print("\n" + "="*70)
260
+ print(" ✅ Configuration saved successfully!")
261
+ print(f" Location: {env_path}")
262
+ print("="*70)
263
+ print("\n Next steps:")
264
+ print(" 1. Run 'mfcli init' in your hardware project directory")
265
+ print(" 2. Run 'mfcli run' to process your documents")
266
+ print(" 3. (Optional) Run 'mfcli setup-mcp' to configure MCP server")
267
+ print("\n")
268
+
269
+ except KeyboardInterrupt:
270
+ print("\n\n ⚠️ Configuration cancelled.")
271
+ sys.exit(0)
272
+
273
+
274
+ def check_configuration() -> None:
275
+ """Check and validate existing configuration."""
276
+ print("\n" + "="*70)
277
+ print(" CONFIGURATION CHECK")
278
+ print("="*70)
279
+
280
+ env_path = get_env_path()
281
+
282
+ if not env_path.exists():
283
+ print(f"\n ❌ Configuration file not found: {env_path}")
284
+ print("\n Run 'mfcli configure' to create your configuration.")
285
+ return
286
+
287
+ print(f"\n Configuration file: {env_path}")
288
+
289
+ env_vars = read_existing_env()
290
+
291
+ required_keys = [
292
+ ("google_api_key", "Google Gemini API"),
293
+ ("openai_api_key", "OpenAI API"),
294
+ ("llama_cloud_api_key", "LlamaParse API"),
295
+ ("digikey_client_id", "DigiKey Client ID"),
296
+ ("digikey_client_secret", "DigiKey Client Secret"),
297
+ ]
298
+
299
+ print("\n Checking configuration:")
300
+ all_valid = True
301
+
302
+ for key, name in required_keys:
303
+ value = env_vars.get(key)
304
+ if not value or value.startswith("your_"):
305
+ print(f" ❌ {name}: Not configured")
306
+ all_valid = False
307
+ else:
308
+ masked_value = value[:8] + "..." if len(value) > 8 else value
309
+ print(f" ✅ {name}: {masked_value}")
310
+
311
+ print("\n Vector database configuration:")
312
+ print(f" - Chunk size: {env_vars.get('chunk_size', 'Not set')}")
313
+ print(f" - Chunk overlap: {env_vars.get('chunk_overlap', 'Not set')}")
314
+ print(f" - Embedding model: {env_vars.get('embedding_model', 'Not set')}")
315
+ print(f" - Embedding dimensions: {env_vars.get('embedding_dimensions', 'Not set')}")
316
+
317
+ if all_valid:
318
+ print("\n ✅ All required configuration values are set!")
319
+ print("\n To validate API keys, run: mfcli doctor")
320
+ else:
321
+ print("\n ⚠️ Some configuration values are missing.")
322
+ print(" Run 'mfcli configure' to complete your configuration.")
323
+
324
+ print("="*70 + "\n")
@@ -0,0 +1,114 @@
1
+ import os.path
2
+ import shutil
3
+ import sys
4
+ from pathlib import Path
5
+ from textwrap import dedent
6
+ from typing import List
7
+
8
+ from mfcli.client.chroma_db import ChromaClient
9
+ from mfcli.models.datasheet import Datasheet
10
+ from mfcli.models.project import Project
11
+ from mfcli.utils.config import get_config
12
+ from mfcli.utils.directory_manager import app_dirs, init_directory_structure
13
+ from mfcli.utils.logger import get_logger, setup_logging
14
+ from mfcli.utils.orm import Session
15
+ from mfcli.utils.query_service import QueryService
16
+
17
+ logger = get_logger(__name__)
18
+
19
+ warning_message = dedent(
20
+ """
21
+
22
+ WARNING: This will permanently delete all mfcli data, including datasheets, cheat sheets, and project data.
23
+ Should we proceed? (Y/n):
24
+
25
+ """
26
+ )
27
+
28
+
29
+ class DataCleaner:
30
+ def __init__(self, db: Session):
31
+ self._db = db
32
+ self._query_service = QueryService(db)
33
+ self._config = get_config()
34
+
35
+ @staticmethod
36
+ def _remove_dir(dir_path: Path):
37
+ if not os.path.isdir(dir_path):
38
+ logger.warning(f"Directory does not exist: {dir_path}")
39
+ return
40
+ try:
41
+ shutil.rmtree(dir_path)
42
+ except Exception as e:
43
+ logger.exception(e)
44
+ logger.error(f"Error deleting directory: {dir_path}")
45
+
46
+ @staticmethod
47
+ def _remove_file(file_path: Path):
48
+ if not os.path.isfile(file_path):
49
+ logger.debug(f"File does not exist: {file_path}")
50
+ return
51
+ try:
52
+ os.remove(file_path)
53
+ logger.debug(f"Removed file: {file_path}")
54
+ except Exception as e:
55
+ logger.exception(e)
56
+ logger.error(f"Error deleting file: {file_path}")
57
+
58
+ def clean(self):
59
+ logger.info("Cleaning mfcli data")
60
+
61
+ # Clear all datasheet entries from the database
62
+ datasheets: List[Datasheet] = self._query_service.query_all(Datasheet)
63
+ if datasheets:
64
+ logger.debug(f"Deleting {len(datasheets)} datasheet entries from database")
65
+ for datasheet in datasheets:
66
+ self._db.delete(datasheet)
67
+ self._db.commit()
68
+ logger.info(f"Deleted {len(datasheets)} datasheet entries")
69
+
70
+ projects: List[Project] = self._query_service.query_all(Project)
71
+ for project in projects:
72
+ init_directory_structure(project.repo_dir)
73
+ config_dir = Path(project.repo_dir) / ".multifactor"
74
+ chroma_db = ChromaClient(project.index_id)
75
+ chroma_db.delete_collection()
76
+
77
+ # Remove config files
78
+ if app_dirs.config_file_path:
79
+ logger.debug(f"Removing config file: {app_dirs.config_file_path}")
80
+ self._remove_file(app_dirs.config_file_path)
81
+ if app_dirs.file_docket_path:
82
+ logger.debug(f"Removing file docket: {app_dirs.file_docket_path}")
83
+ self._remove_file(app_dirs.file_docket_path)
84
+
85
+ for dir_path in [
86
+ config_dir,
87
+ app_dirs.agent_instructions_dir,
88
+ app_dirs.data_sheets_dir,
89
+ app_dirs.fw_tasks_dir,
90
+ app_dirs.generated_files_dir,
91
+ app_dirs.reqs_dir,
92
+ app_dirs.cheat_sheets_dir,
93
+ app_dirs.pdf_parts_dir
94
+ ]:
95
+ logger.debug(f"Removing directory: {dir_path}")
96
+ self._remove_dir(dir_path)
97
+ self._db.delete(project)
98
+ self._db.commit()
99
+ logger.info("All mfcli data has been cleaned")
100
+
101
+
102
+ def run_data_cleaner():
103
+ with Session() as db:
104
+ DataCleaner(db).clean()
105
+
106
+
107
+ def clean_app_data(user_accepted: bool = False):
108
+ setup_logging()
109
+ if not user_accepted:
110
+ user_input = input(warning_message)
111
+ if not user_input.strip() == 'Y':
112
+ logger.debug("User cancelled")
113
+ sys.exit()
114
+ run_data_cleaner()