mfcli 0.2.7__tar.gz → 0.2.9__tar.gz

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 (154) hide show
  1. {mfcli-0.2.7 → mfcli-0.2.9}/MCP_SETUP.md +32 -2
  2. {mfcli-0.2.7 → mfcli-0.2.9}/PKG-INFO +1 -1
  3. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/constants/file_types.py +43 -2
  4. mfcli-0.2.9/mfcli/pipeline/extractor.py +85 -0
  5. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/sub_classifier.py +10 -1
  6. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/data_cleaner.py +6 -3
  7. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/mcp_configurator.py +4 -2
  8. {mfcli-0.2.7 → mfcli-0.2.9}/pyproject.toml +1 -1
  9. mfcli-0.2.7/mfcli/pipeline/extractor.py +0 -37
  10. {mfcli-0.2.7 → mfcli-0.2.9}/BUILD.md +0 -0
  11. {mfcli-0.2.7 → mfcli-0.2.9}/CONFIGURATION.md +0 -0
  12. {mfcli-0.2.7 → mfcli-0.2.9}/INSTALL.md +0 -0
  13. {mfcli-0.2.7 → mfcli-0.2.9}/LICENSE +0 -0
  14. {mfcli-0.2.7 → mfcli-0.2.9}/MANIFEST.in +0 -0
  15. {mfcli-0.2.7 → mfcli-0.2.9}/README.md +0 -0
  16. {mfcli-0.2.7 → mfcli-0.2.9}/install.ps1 +0 -0
  17. {mfcli-0.2.7 → mfcli-0.2.9}/install.sh +0 -0
  18. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/.env.example +0 -0
  19. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/__init__.py +0 -0
  20. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/agents/__init__.py +0 -0
  21. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/agents/controller/__init__.py +0 -0
  22. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/agents/controller/agent.py +0 -0
  23. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/agents/controller/config.yaml +0 -0
  24. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/agents/controller/tools.py +0 -0
  25. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/agents/tools/general.py +0 -0
  26. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/alembic/env.py +0 -0
  27. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/alembic/script.py.mako +0 -0
  28. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/alembic/versions/6ccc0c7c397c_added_fields_to_pdf_parts_model.py +0 -0
  29. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/alembic/versions/769019ef4870_added_gemini_file_path_to_pdf_part_model.py +0 -0
  30. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/alembic/versions/7a2e3a779fdc_added_functional_block_and_component_.py +0 -0
  31. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/alembic/versions/7d5adb2a47a7_added_pdf_parts_model.py +0 -0
  32. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/alembic/versions/7fcb7d6a5836_init.py +0 -0
  33. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/alembic/versions/e0f2b5765c72_added_cascade_delete_for_models_that_.py +0 -0
  34. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/alembic/versions/f1234567890a_make_bom_description_nullable.py +0 -0
  35. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/alembic.ini +0 -0
  36. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/cli/__init__.py +0 -0
  37. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/cli/dependencies.py +0 -0
  38. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/cli/main.py +0 -0
  39. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/client/__init__.py +0 -0
  40. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/client/chroma_db.py +0 -0
  41. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/client/docling.py +0 -0
  42. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/client/gemini.py +0 -0
  43. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/client/vector_db.py +0 -0
  44. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/constants/__init__.py +0 -0
  45. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/constants/base_enum.py +0 -0
  46. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/constants/directory_names.py +0 -0
  47. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/constants/gemini.py +0 -0
  48. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/constants/openai.py +0 -0
  49. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/constants/pipeline_run_status.py +0 -0
  50. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/crud/__init__.py +0 -0
  51. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/crud/file.py +0 -0
  52. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/crud/functional_blocks.py +0 -0
  53. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/crud/netlist.py +0 -0
  54. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/crud/pipeline_run.py +0 -0
  55. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/crud/project.py +0 -0
  56. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/digikey/__init__.py +0 -0
  57. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/digikey/digikey.py +0 -0
  58. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/main.py +0 -0
  59. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/mcp/__init__.py +0 -0
  60. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/mcp/configs/cline_mcp_settings.json +0 -0
  61. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/mcp/configs/mfcli.mcp.json +0 -0
  62. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/mcp/mcp_instance.py +0 -0
  63. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/mcp/server.py +0 -0
  64. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/mcp/state_manager.py +0 -0
  65. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/mcp/tools/__init__.py +0 -0
  66. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/mcp/tools/query_knowledgebase.py +0 -0
  67. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/__init__.py +0 -0
  68. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/base.py +0 -0
  69. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/bom.py +0 -0
  70. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/datasheet.py +0 -0
  71. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/debug_setup.py +0 -0
  72. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/file.py +0 -0
  73. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/file_docket.py +0 -0
  74. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/file_metadata.py +0 -0
  75. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/functional_blocks.py +0 -0
  76. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/llm_response.py +0 -0
  77. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/mcu.py +0 -0
  78. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/mcu_errata.py +0 -0
  79. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/netlist.py +0 -0
  80. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/pdf_parts.py +0 -0
  81. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/pipeline_run.py +0 -0
  82. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/project.py +0 -0
  83. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/project_metadata.py +0 -0
  84. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/models/schematic_cheatsheet.py +0 -0
  85. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/__init__.py +0 -0
  86. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/__init__.py +0 -0
  87. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/bom_netlist_mapper.py +0 -0
  88. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/__init__.py +0 -0
  89. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/bom/__init__.py +0 -0
  90. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/bom/bom.py +0 -0
  91. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/debug_setup/__init__.py +0 -0
  92. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/debug_setup/debug_setup.py +0 -0
  93. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/debug_setup/instructions.py +0 -0
  94. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/functional_blocks/__init__.py +0 -0
  95. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/functional_blocks/functional_blocks.py +0 -0
  96. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/functional_blocks/instructions.py +0 -0
  97. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/functional_blocks/validator.py +0 -0
  98. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/generator.py +0 -0
  99. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/generator_base.py +0 -0
  100. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/mcu/__init__.py +0 -0
  101. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/mcu/instructions.py +0 -0
  102. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/mcu/mcu.py +0 -0
  103. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/mcu_errata/__init__.py +0 -0
  104. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/mcu_errata/instructions.py +0 -0
  105. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/mcu_errata/mcu_errata.py +0 -0
  106. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/schematic/__init__.py +0 -0
  107. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/schematic/instructions.py +0 -0
  108. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/schematic/schematic.py +0 -0
  109. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/summary/__init__.py +0 -0
  110. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/analysis/generators/summary/summary.py +0 -0
  111. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/classifier.py +0 -0
  112. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/data_enricher.py +0 -0
  113. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/extractors/__init__.py +0 -0
  114. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/extractors/pdf.py +0 -0
  115. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/parser.py +0 -0
  116. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/parsers/__init__.py +0 -0
  117. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/parsers/netlist/__init__.py +0 -0
  118. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/parsers/netlist/edif.py +0 -0
  119. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/parsers/netlist/kicad_legacy_net.py +0 -0
  120. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/parsers/netlist/kicad_spice.py +0 -0
  121. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/parsers/netlist/pads.py +0 -0
  122. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/parsers/netlist/protel.py +0 -0
  123. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/parsers/netlist/protel_detector.py +0 -0
  124. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/parsers/schematic/__init__.py +0 -0
  125. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/parsers/schematic/kicad_sch_detector.py +0 -0
  126. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/pipeline.py +0 -0
  127. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/preprocessors/__init__.py +0 -0
  128. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/preprocessors/user_guide.py +0 -0
  129. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/run_context.py +0 -0
  130. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/pipeline/schema_mapper.py +0 -0
  131. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/__init__.py +0 -0
  132. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/cheatsheet_regenerator.py +0 -0
  133. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/cline_rules.py +0 -0
  134. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/config.py +0 -0
  135. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/configurator.py +0 -0
  136. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/datasheet_vectorizer.py +0 -0
  137. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/directory_manager.py +0 -0
  138. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/file_upload.py +0 -0
  139. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/files.py +0 -0
  140. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/http_requests.py +0 -0
  141. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/kb_lister.py +0 -0
  142. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/kb_remover.py +0 -0
  143. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/logger.py +0 -0
  144. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/migrations.py +0 -0
  145. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/orm.py +0 -0
  146. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/pdf_splitter.py +0 -0
  147. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/pre_uninstall.py +0 -0
  148. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/query_service.py +0 -0
  149. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/ssl_installer.py +0 -0
  150. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/system_check.py +0 -0
  151. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/tools.py +0 -0
  152. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli/utils/vectorizer.py +0 -0
  153. {mfcli-0.2.7 → mfcli-0.2.9}/mfcli.egg-info/SOURCES.txt +0 -0
  154. {mfcli-0.2.7 → mfcli-0.2.9}/setup.cfg +0 -0
@@ -115,7 +115,8 @@ If the file doesn't exist, create it with this content:
115
115
  "disabled": false,
116
116
  "timeout": 60,
117
117
  "type": "stdio",
118
- "command": "mfcli-mcp"
118
+ "command": "mfcli-mcp",
119
+ "autoApprove": ["query_local_rag"]
119
120
  }
120
121
  }
121
122
  }
@@ -133,12 +134,15 @@ If the file exists, add the `mfcli-mcp` entry to the existing `mcpServers` objec
133
134
  "disabled": false,
134
135
  "timeout": 60,
135
136
  "type": "stdio",
136
- "command": "mfcli-mcp"
137
+ "command": "mfcli-mcp",
138
+ "autoApprove": ["query_local_rag"]
137
139
  }
138
140
  }
139
141
  }
140
142
  ```
141
143
 
144
+ **Note about `autoApprove`:** The `autoApprove` option allows the `query_local_rag` tool to run automatically without requiring explicit user approval each time. This streamlines the workflow when working with hardware documentation, as the tool is read-only and simply queries your local knowledge base.
145
+
142
146
  ### Step 3: Restart Your Editor
143
147
 
144
148
  Close and reopen your AI coding assistant for the changes to take effect.
@@ -382,6 +386,32 @@ Enable verbose logging for troubleshooting:
382
386
  }
383
387
  ```
384
388
 
389
+ ### Auto-Approval for Tools
390
+
391
+ The `autoApprove` option allows specific tools to run automatically without requiring user approval each time:
392
+
393
+ ```json
394
+ {
395
+ "mfcli-mcp": {
396
+ "autoApprove": ["query_local_rag"]
397
+ }
398
+ }
399
+ ```
400
+
401
+ **Benefits:**
402
+ - ✅ Streamlined workflow - No manual approval needed for each query
403
+ - ✅ Faster responses - AI assistant can query documentation immediately
404
+ - ✅ Better UX - More natural conversation flow
405
+ - ✅ Safe for read-only tools - `query_local_rag` only reads local data
406
+
407
+ **Security Note:** The `query_local_rag` tool is safe to auto-approve because:
408
+ - It only performs read operations on your local knowledge base
409
+ - No data is modified or transmitted externally
410
+ - It operates within your local filesystem permissions
411
+ - All data stays on your machine
412
+
413
+ If you prefer to manually approve each tool use, simply omit the `autoApprove` field from the configuration.
414
+
385
415
  ## Best Practices
386
416
 
387
417
  ### Document Organization
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mfcli
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: AI-powered CLI for analyzing hardware engineering documents
5
5
  Author: Multifactor AI
6
6
  License: MIT
@@ -13,6 +13,10 @@ class FileTypes(BaseEnum):
13
13
  PDF = 5
14
14
  SCH = 6
15
15
  KICAD_SCH = 7
16
+ TXT = 8
17
+ RTF = 9
18
+ DOCX = 10
19
+ JPG = 11
16
20
 
17
21
 
18
22
  class FileSubtypes(BaseEnum):
@@ -47,7 +51,9 @@ SchemalessFileSubtypes: set[FileSubtypes] = {
47
51
  FileSubtypes.ERRATA,
48
52
  FileSubtypes.MCU_DATASHEET,
49
53
  FileSubtypes.USER_GUIDE,
50
- FileSubtypes.REFERENCE_MANUAL
54
+ FileSubtypes.REFERENCE_MANUAL,
55
+ FileSubtypes.GENERAL_DATASHEET,
56
+ FileSubtypes.UNKNOWN
51
57
  }
52
58
 
53
59
  # File subtype names are for use with LLM, validating subtype response
@@ -67,7 +73,8 @@ OtherFileSubtypeNames = Literal[
67
73
  'PADS_PCB_ASCII',
68
74
  'KICAD_LEGACY_NET',
69
75
  'KICAD_SPICE',
70
- 'PROTEL_ALTIUM'
76
+ 'PROTEL_ALTIUM',
77
+ 'UNKNOWN'
71
78
  ]
72
79
 
73
80
  PDFMimeTypes = {
@@ -138,6 +145,40 @@ SupportedFileTypes = {
138
145
  "subtypes": {
139
146
  "SCHEMATIC"
140
147
  }
148
+ },
149
+ "TXT": {
150
+ "mime_types": {
151
+ "text/plain"
152
+ },
153
+ "subtypes": {
154
+ "UNKNOWN"
155
+ }
156
+ },
157
+ "RTF": {
158
+ "mime_types": {
159
+ "application/rtf",
160
+ "text/rtf"
161
+ },
162
+ "subtypes": {
163
+ "UNKNOWN"
164
+ }
165
+ },
166
+ "DOCX": {
167
+ "mime_types": {
168
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
169
+ },
170
+ "subtypes": {
171
+ "UNKNOWN"
172
+ }
173
+ },
174
+ "JPG": {
175
+ "mime_types": {
176
+ "image/jpeg",
177
+ "image/jpg"
178
+ },
179
+ "subtypes": {
180
+ "UNKNOWN"
181
+ }
141
182
  }
142
183
  }
143
184
 
@@ -0,0 +1,85 @@
1
+ from mfcli.constants.file_types import FileTypes
2
+ from mfcli.models.file import File
3
+ from mfcli.pipeline.extractors.pdf import extract_text_from_pdf
4
+ from mfcli.utils.files import is_text_mime_type
5
+ from mfcli.utils.logger import get_logger
6
+
7
+ logger = get_logger(__name__)
8
+
9
+
10
+ def extract_text_from_docx(file_bytes: bytes) -> str:
11
+ """Extract text from DOCX file using python-docx."""
12
+ try:
13
+ from io import BytesIO
14
+ import docx
15
+
16
+ doc = docx.Document(BytesIO(file_bytes))
17
+ text_parts = []
18
+ for paragraph in doc.paragraphs:
19
+ text_parts.append(paragraph.text)
20
+ return '\n'.join(text_parts)
21
+ except ImportError:
22
+ logger.warning("python-docx not installed, treating DOCX as binary")
23
+ return file_bytes.decode(errors='ignore')
24
+ except Exception as e:
25
+ logger.error(f"Error extracting text from DOCX: {e}")
26
+ return file_bytes.decode(errors='ignore')
27
+
28
+
29
+ def extract_text_from_rtf(file_bytes: bytes) -> str:
30
+ """Extract text from RTF file using striprtf."""
31
+ try:
32
+ from striprtf.striprtf import rtf_to_text
33
+ rtf_string = file_bytes.decode(errors='ignore')
34
+ return rtf_to_text(rtf_string)
35
+ except ImportError:
36
+ logger.warning("striprtf not installed, treating RTF as plain text")
37
+ return file_bytes.decode(errors='ignore')
38
+ except Exception as e:
39
+ logger.error(f"Error extracting text from RTF: {e}")
40
+ return file_bytes.decode(errors='ignore')
41
+
42
+
43
+ def extract_document_text(file: File, file_bytes: bytes) -> str:
44
+ """
45
+ Extract text from a file based on its MIME type.
46
+
47
+ For PDFs, uses PyMuPDF (fitz) for fast, reliable text extraction.
48
+ For text files, decodes the bytes directly.
49
+ For DOCX files, uses python-docx to extract text.
50
+ For RTF files, uses striprtf to extract text.
51
+ For image files (JPG, PNG), returns empty string (OCR not implemented).
52
+ KiCad schematics and other schematic files are treated as text files.
53
+
54
+ Args:
55
+ file: File object containing metadata
56
+ file_bytes: Raw file content as bytes
57
+
58
+ Returns:
59
+ Extracted text content
60
+
61
+ Raises:
62
+ ValueError: If the file type is not supported
63
+ """
64
+ if is_text_mime_type(file.mime_type):
65
+ return file_bytes.decode(errors='ignore')
66
+ elif file.type == FileTypes.PDF:
67
+ return extract_text_from_pdf(file_bytes)
68
+ elif file.type == FileTypes.DOCX:
69
+ return extract_text_from_docx(file_bytes)
70
+ elif file.type == FileTypes.RTF:
71
+ return extract_text_from_rtf(file_bytes)
72
+ elif file.type == FileTypes.JPG:
73
+ # Image files don't have extractable text without OCR
74
+ # Return empty string for now - file will still be vectorized if it's a PDF-embedded image
75
+ logger.info(f"Image file detected ({file.name}), no text extraction (OCR not implemented)")
76
+ return ""
77
+ elif file.type == FileTypes.CSV:
78
+ # CSV files (including Excel CSV with application/vnd.ms-excel MIME type)
79
+ # are text-based and should be decoded directly
80
+ return file_bytes.decode(errors='ignore')
81
+ elif file.type in (FileTypes.KICAD_SCH, FileTypes.SCH, FileTypes.TXT):
82
+ # KiCad schematics and plain text files are S-expression text files but may have
83
+ # application/octet-stream MIME type
84
+ return file_bytes.decode(errors='ignore')
85
+ raise ValueError(f"Unsupported MIME type: {file.mime_type}")
@@ -85,7 +85,9 @@ class FileSubtypeAnalyzer:
85
85
  subtype = await self._get_subtype_from_gemini(prompt, instructions, gemini_file, file_class)
86
86
  logger.debug(f"Subtype discovered: {subtype}")
87
87
  if subtype == FILE_SUBTYPE_UNKNOWN:
88
- raise RuntimeError(f"Could not determine the file subtype for file: {file.name}")
88
+ logger.warning(f"Could not determine the file subtype for file: {file.name}, using UNKNOWN")
89
+ file.sub_type = FileSubtypes.UNKNOWN.value
90
+ return
89
91
  if not subtype in relevant_subtype_descriptions:
90
92
  raise RuntimeError(f"LLM responded with invalid subtype: {subtype}")
91
93
  file.sub_type = FileSubtypes.get(subtype)
@@ -109,6 +111,13 @@ class FileSubtypeAnalyzer:
109
111
  file.sub_type = FileSubtypes.SCHEMATIC.value
110
112
  return
111
113
 
114
+ # Generic document file types (TXT, RTF, DOCX, JPG) default to UNKNOWN
115
+ # These files will be vectorized but not analyzed for specific subtypes
116
+ if file.type in (FileTypes.TXT, FileTypes.RTF, FileTypes.DOCX, FileTypes.JPG):
117
+ file.sub_type = FileSubtypes.UNKNOWN.value
118
+ logger.info(f"File type {FileTypes(file.type).name} auto-assigned UNKNOWN subtype")
119
+ return
120
+
112
121
  # Handle text MIME types for content-based detection
113
122
  if text and is_text_mime_type(file.mime_type):
114
123
  if file.type == FileTypes.NET:
@@ -19,7 +19,8 @@ logger = get_logger(__name__)
19
19
  warning_message = dedent(
20
20
  """
21
21
 
22
- WARNING: This will permanently delete all mfcli data for the current project, including datasheets, cheat sheets, and project data.
22
+ WARNING: This will permanently delete all mfcli data for the current project, including datasheets, cheat sheets, and project data.
23
+ The design folder and its contents will be preserved.
23
24
  Should we proceed? (Y/n):
24
25
 
25
26
  """
@@ -105,9 +106,8 @@ class DataCleaner:
105
106
  logger.debug(f"Removing file docket: {app_dirs.file_docket_path}")
106
107
  self._remove_file(app_dirs.file_docket_path)
107
108
 
108
- # Remove directories
109
+ # Remove directories (excluding design folder)
109
110
  for dir_path in [
110
- config_dir,
111
111
  app_dirs.data_sheets_dir,
112
112
  app_dirs.fw_tasks_dir,
113
113
  app_dirs.generated_files_dir,
@@ -118,6 +118,9 @@ class DataCleaner:
118
118
  logger.debug(f"Removing directory: {dir_path}")
119
119
  self._remove_dir(dir_path)
120
120
 
121
+ # Note: design_dir is intentionally NOT removed to preserve user input files
122
+ logger.info(f"Design folder preserved at: {app_dirs.design_dir}")
123
+
121
124
  # Delete project (CASCADE will handle related records like pipeline_runs)
122
125
  logger.debug(f"Deleting project from database: {project.name}")
123
126
  self._db.delete(project)
@@ -73,7 +73,8 @@ def get_mfcli_mcp_config() -> dict:
73
73
  "disabled": False,
74
74
  "timeout": 60,
75
75
  "type": "stdio",
76
- "command": "mfcli-mcp"
76
+ "command": "mfcli-mcp",
77
+ "autoApprove": ["query_local_rag"]
77
78
  }
78
79
  }
79
80
 
@@ -382,7 +383,8 @@ editor's MCP configuration file.
382
383
  "disabled": false,
383
384
  "timeout": 60,
384
385
  "type": "stdio",
385
- "command": "mfcli-mcp"
386
+ "command": "mfcli-mcp",
387
+ "autoApprove": ["query_local_rag"]
386
388
  }
387
389
  }
388
390
  }
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mfcli"
7
- version = "0.2.7"
7
+ version = "0.2.9"
8
8
  description = "AI-powered CLI for analyzing hardware engineering documents"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,37 +0,0 @@
1
- from mfcli.constants.file_types import FileTypes
2
- from mfcli.models.file import File
3
- from mfcli.pipeline.extractors.pdf import extract_text_from_pdf
4
- from mfcli.utils.files import is_text_mime_type
5
-
6
-
7
- def extract_document_text(file: File, file_bytes: bytes) -> str:
8
- """
9
- Extract text from a file based on its MIME type.
10
-
11
- For PDFs, uses PyMuPDF (fitz) for fast, reliable text extraction.
12
- For text files, decodes the bytes directly.
13
- KiCad schematics and other schematic files are treated as text files.
14
-
15
- Args:
16
- file: File object containing metadata
17
- file_bytes: Raw file content as bytes
18
-
19
- Returns:
20
- Extracted text content
21
-
22
- Raises:
23
- ValueError: If the file type is not supported
24
- """
25
- if is_text_mime_type(file.mime_type):
26
- return file_bytes.decode(errors='ignore')
27
- elif file.type == FileTypes.PDF:
28
- return extract_text_from_pdf(file_bytes)
29
- elif file.type == FileTypes.CSV:
30
- # CSV files (including Excel CSV with application/vnd.ms-excel MIME type)
31
- # are text-based and should be decoded directly
32
- return file_bytes.decode(errors='ignore')
33
- elif file.type in (FileTypes.KICAD_SCH, FileTypes.SCH):
34
- # KiCad schematics are S-expression text files but may have
35
- # application/octet-stream MIME type
36
- return file_bytes.decode(errors='ignore')
37
- raise ValueError(f"Unsupported MIME type: {file.mime_type}")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes