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,89 @@
1
+ from typing import Type, Optional
2
+
3
+ from pydantic import BaseModel, Field
4
+ from sqlmodel import SQLModel
5
+
6
+ from mfcli.agents.tools.general import format_instructions
7
+ from mfcli.client.gemini import Gemini
8
+ from mfcli.constants.file_types import FileSubtypes
9
+ from mfcli.models.bom import BOM, BOMSchema
10
+ from mfcli.models.netlist import Netlist
11
+ from mfcli.utils.logger import get_logger
12
+
13
+ logger = get_logger(__name__)
14
+
15
+ SubtypeSchemas: dict[FileSubtypes, Type[BaseModel]] = {
16
+ FileSubtypes.BOM: BOMSchema
17
+ }
18
+ SubtypeModels: dict[FileSubtypes, Type[SQLModel]] = {
19
+ FileSubtypes.BOM: BOM,
20
+ FileSubtypes.KICAD_LEGACY_NET: Netlist,
21
+ FileSubtypes.PADS_PCB_ASCII: Netlist,
22
+ FileSubtypes.KICAD_SPICE: Netlist,
23
+ FileSubtypes.PROTEL_ALTIUM: Netlist
24
+ }
25
+
26
+
27
+ class SchemaMapping(BaseModel):
28
+ input_field: Optional[str] = Field(None, description="A field found in the sample file")
29
+ mapped_field: str = Field(..., description="The field found in the backend to be mapped to")
30
+
31
+
32
+ class SchemaMappings(BaseModel):
33
+ fields: list[SchemaMapping] = Field(default_factory=list, description="List of schema mappings")
34
+
35
+
36
+ schema_mapper_instructions = format_instructions(
37
+ """
38
+ You are responsible for mapping fields found in a file to a backend schema.
39
+ You will be given the schema for a file subtype, like Bill of Materials (BOM).
40
+ You will map whatever fields you see in the file, to fields in the backend.
41
+ For example, a column "Designator" found in a BOM file would map to "reference" in the backend.
42
+
43
+ You must respond **only** with valid JSON that exactly matches the `SchemaMappings` model:
44
+
45
+ - The top-level object must have a key `"fields"` containing a list.
46
+ - Each item in the list must be an object with:
47
+ - `"input_field"` (optional string) — the field name from the file header.
48
+ - `"mapped_field"` (required string) — the corresponding backend field name.
49
+
50
+ Do **not** include any markdown, text, or code fences. Respond **only** with JSON.
51
+
52
+ Example of valid response:
53
+
54
+ {
55
+ "fields": [
56
+ {"input_field": "RefDes", "mapped_field": "reference"},
57
+ {"input_field": "Value", "mapped_field": "value"},
58
+ {"input_field": "Qty", "mapped_field": "quantity"},
59
+ {"input_field": "Description", "mapped_field": "description"}
60
+ ]
61
+ }
62
+ """
63
+ )
64
+
65
+
66
+ async def map_schema(gemini: Gemini, subtype: int, text: str) -> SchemaMappings | None:
67
+ if not SubtypeSchemas.get(subtype):
68
+ logger.debug(f"No subtype mapping required for subtype: {subtype}")
69
+ return
70
+ schema = str(SubtypeSchemas[subtype].model_json_schema())
71
+ prompt = format_instructions(
72
+ f"""
73
+ {schema_mapper_instructions}
74
+
75
+ Here is the backend schema for this filetype:
76
+
77
+ {schema}
78
+
79
+ Here is the file contents:
80
+
81
+ {text}
82
+
83
+ """
84
+ )
85
+ return await gemini.generate(
86
+ prompt=prompt,
87
+ instructions=schema_mapper_instructions,
88
+ response_model=SchemaMappings
89
+ )
@@ -0,0 +1,115 @@
1
+ from typing import Literal
2
+
3
+ from google.genai.types import File as GeminiFile
4
+ from pydantic import BaseModel
5
+
6
+ from mfcli.agents.tools.general import format_instructions
7
+ from mfcli.client.gemini import Gemini
8
+ from mfcli.constants.file_types import (
9
+ FileTypes,
10
+ FileSubtypes,
11
+ FILE_SUBTYPE_UNKNOWN,
12
+ PDFSubtypeDescriptions,
13
+ OtherFileTypeDescriptions,
14
+ PDFFileSubtypeNames,
15
+ OtherFileSubtypeNames
16
+ )
17
+ from mfcli.models.file import File
18
+ from mfcli.pipeline.parsers.netlist.kicad_legacy_net import is_kicad_legacy_netlist
19
+ from mfcli.pipeline.parsers.netlist.protel_detector import is_protel_netlist
20
+ from mfcli.utils.files import is_text_mime_type
21
+ from mfcli.utils.logger import get_logger
22
+
23
+ sub_classifier_instructions = format_instructions(
24
+ """
25
+ You are the sub-classifier agent for an engineering document processing pipeline.
26
+ You will receive the first 50 lines of text from a file.
27
+ You will examine the content of the file, and determine the sub-type of this file.
28
+ You will be given the sub-types and sub-type descriptions.
29
+ If you are not able to determine the sub-type you will respond with "UNKNOWN".
30
+
31
+ Here are the valid sub-types and a description of each:
32
+
33
+ {}
34
+
35
+ """
36
+ )
37
+
38
+ logger = get_logger(__name__)
39
+
40
+
41
+ class PDFSubtypeClassifierResponse(BaseModel):
42
+ type: PDFFileSubtypeNames
43
+
44
+
45
+ class OtherFileSubtypeClassifierResponse(BaseModel):
46
+ type: OtherFileSubtypeNames
47
+
48
+
49
+ FileClass = Literal['pdf', 'other']
50
+
51
+
52
+ class FileSubtypeAnalyzer:
53
+ def __init__(self, gemini: Gemini):
54
+ self._gemini = gemini
55
+
56
+ async def _get_subtype_from_gemini(
57
+ self,
58
+ prompt: str,
59
+ instructions: str,
60
+ gemini_file: GeminiFile | None = None,
61
+ file_class: FileClass = 'other'
62
+ ) -> str:
63
+ model = OtherFileSubtypeClassifierResponse if file_class == 'other' else PDFSubtypeClassifierResponse
64
+ files = [gemini_file] if gemini_file else None
65
+ response = await self._gemini.generate(
66
+ prompt=prompt,
67
+ instructions=instructions,
68
+ response_model=model,
69
+ files=files
70
+ )
71
+ return response.type
72
+
73
+ async def _get_subtype(
74
+ self,
75
+ prompt: str,
76
+ file: File,
77
+ gemini_file: GeminiFile | None = None,
78
+ file_class: FileClass = 'other'
79
+ ) -> None:
80
+ logger.debug(f"Fetching subtype for file: {file.name}")
81
+ relevant_subtype_descriptions = PDFSubtypeDescriptions if file.type == FileTypes.PDF else OtherFileTypeDescriptions
82
+ logger.debug(f"Relevant subtypes: {relevant_subtype_descriptions.keys()}")
83
+ instructions = sub_classifier_instructions.format(relevant_subtype_descriptions)
84
+ subtype = await self._get_subtype_from_gemini(prompt, instructions, gemini_file, file_class)
85
+ logger.debug(f"Subtype discovered: {subtype}")
86
+ if subtype == FILE_SUBTYPE_UNKNOWN:
87
+ raise RuntimeError(f"Could not determine the file subtype for file: {file.name}")
88
+ if not subtype in relevant_subtype_descriptions:
89
+ raise RuntimeError(f"LLM responded with invalid subtype: {subtype}")
90
+ file.sub_type = FileSubtypes.get(subtype)
91
+
92
+ async def analyze_pdf(
93
+ self,
94
+ file: File,
95
+ gemini_file: GeminiFile
96
+ ) -> None:
97
+ prompt = "Determine this PDF file subtype"
98
+ await self._get_subtype(prompt, file, gemini_file, 'pdf')
99
+
100
+ async def analyze_file(
101
+ self,
102
+ file: File,
103
+ text: str
104
+ ) -> None:
105
+ # Handle text MIME types
106
+ if text and is_text_mime_type(file.mime_type):
107
+ if file.type == FileTypes.NET:
108
+ if is_kicad_legacy_netlist(text):
109
+ file.sub_type = FileSubtypes.KICAD_LEGACY_NET.value
110
+ elif is_protel_netlist(text):
111
+ file.sub_type = FileSubtypes.PROTEL_ALTIUM.value
112
+
113
+ # If subtype cannot be parsed, use LLM to determine subtype
114
+ if not file.sub_type:
115
+ await self._get_subtype(text[0:500], file)
File without changes
@@ -0,0 +1,256 @@
1
+ """Utility for creating and managing Cline workspace rules files."""
2
+ import os
3
+ from pathlib import Path
4
+ from mfcli.utils.tools import get_git_root
5
+ from mfcli.utils.logger import get_logger
6
+
7
+ logger = get_logger(__name__)
8
+
9
+
10
+ def get_cline_rules_content() -> str:
11
+ """Get the content for the multifactor.md Cline rules file."""
12
+ return """# Multifactor Hardware Project Guidelines
13
+
14
+ This project uses the **mfcli** (Multifactor CLI) tool for hardware engineering document processing and analysis.
15
+
16
+ ## MCP Server Integration
17
+
18
+ This project has access to the **mfcli-mcp** Model Context Protocol server, which provides AI-powered access to the project's hardware documentation knowledge base.
19
+
20
+ ### When to Use the MCP Server
21
+
22
+ **ALWAYS query the mfcli MCP server** when working on tasks that involve:
23
+ - Hardware specifications and datasheets
24
+ - MCU (microcontroller) information
25
+ - Component details and part numbers
26
+ - Schematic analysis
27
+ - BOM (Bill of Materials) data
28
+ - Debug setup configurations
29
+ - Functional block diagrams
30
+ - Pin configurations
31
+ - Power management specifications
32
+ - Any hardware-related context
33
+
34
+ ### How to Use the MCP Server
35
+
36
+ Use the `query_local_rag` tool to search the project's knowledge base:
37
+
38
+ ```
39
+ Query the local RAG for "<your search query>" in project "<project_name>"
40
+ ```
41
+
42
+ **Examples:**
43
+ - "Query the local RAG for 'MSPM0L130x voltage specifications'"
44
+ - "Search the knowledge base for 'debug interface pinout'"
45
+ - "Find information about 'power supply requirements'"
46
+
47
+ The MCP server will return:
48
+ - Relevant document chunks from processed files
49
+ - Metadata (file names, document types)
50
+ - Similarity scores (lower = more relevant)
51
+
52
+ **Note:** After the first query, the project name is remembered, so you only need to specify it once per session.
53
+
54
+ ## Hardware Cheat Sheets
55
+
56
+ The project includes a **`hw_cheat_sheets/`** folder within the `multifactor/` directory that contains AI-generated JSON summaries of key hardware information:
57
+
58
+ ### Available Cheat Sheets
59
+
60
+ 1. **MCU Datasheets** (`mcu_*.json`)
61
+ - Register maps
62
+ - Peripheral descriptions
63
+ - Technical specifications
64
+ - Pin configurations
65
+ - Memory maps
66
+
67
+ 2. **MCU Errata** (`errata_*.json`)
68
+ - Known hardware issues
69
+ - Workarounds and fixes
70
+ - Affected chip revisions
71
+ - Severity levels
72
+
73
+ 3. **Debug Setup** (`debug_setup_*.json`)
74
+ - Debug interface configurations
75
+ - Pin assignments for debugging
76
+ - Programming instructions
77
+ - Tool requirements
78
+
79
+ 4. **Functional Blocks** (`functional_blocks_*.json`)
80
+ - System architecture
81
+ - Block diagrams
82
+ - Component interconnections
83
+ - Signal flow
84
+
85
+ ### Using Cheat Sheets
86
+
87
+ **When to use:**
88
+ - Quick reference for common specifications
89
+ - Understanding system architecture
90
+ - Getting started with a new MCU
91
+ - Identifying debug configurations
92
+
93
+ **How to access:**
94
+ 1. List available cheat sheets: `ls multifactor/hw_cheat_sheets/`
95
+ 2. Read specific cheat sheet: Read the JSON file directly
96
+ 3. The JSON format makes it easy to extract specific information programmatically
97
+
98
+ **Example workflow:**
99
+ ```
100
+ 1. Check hw_cheat_sheets/ for a quick overview of the MCU
101
+ 2. Use query_local_rag for detailed information from datasheets
102
+ 3. Combine both sources for comprehensive understanding
103
+ ```
104
+
105
+ ## Project Structure
106
+
107
+ ```
108
+ <project_root>/
109
+ └── multifactor/ # Main project folder
110
+ ├── config.json # Project configuration
111
+ ├── file_docket.json # File tracking metadata
112
+ ├── context/ # Input files for processing
113
+ ├── hw_cheat_sheets/ # AI-generated hardware summaries (JSON)
114
+ ├── generated_files/ # Generated BOMs and outputs
115
+ ├── data_sheets/ # Downloaded component datasheets
116
+ └── pdf_parts/ # Extracted PDF segments
117
+ ```
118
+
119
+ ## Best Practices
120
+
121
+ ### 1. Always Check Context First
122
+ - Review hw_cheat_sheets/ for quick reference
123
+ - Query the MCP server for detailed information
124
+ - This ensures accurate, project-specific responses
125
+
126
+ ### 2. Be Specific in Queries
127
+ - Use part numbers when available
128
+ - Include relevant keywords (e.g., "voltage", "pinout", "timing")
129
+ - Reference specific sections when needed
130
+
131
+ ### 3. Combine Multiple Sources
132
+ - Cheat sheets for overview
133
+ - MCP queries for detailed specs
134
+ - Cross-reference between documents
135
+
136
+ ### 4. Project Awareness
137
+ - All hardware files should be placed in `multifactor/context/` for processing
138
+ - Run `mfcli run` to process new or modified files
139
+ - The knowledge base stays in sync with processed documents
140
+
141
+ ## Common Tasks
142
+
143
+ ### Getting MCU Specifications
144
+ 1. Check `hw_cheat_sheets/mcu_*.json` for quick specs
145
+ 2. Query MCP: "Find voltage and temperature specifications for [MCU_NAME]"
146
+ 3. Cross-reference with datasheets in `data_sheets/`
147
+
148
+ ### Understanding Debug Setup
149
+ 1. Read `hw_cheat_sheets/debug_setup_*.json`
150
+ 2. Query MCP: "What are the debug interface pin assignments?"
151
+ 3. Look for programming instructions
152
+
153
+ ### Analyzing Schematics
154
+ 1. Query MCP: "What components are used in [section]?"
155
+ 2. Check `generated_files/` for extracted BOMs
156
+ 3. Review component datasheets in `data_sheets/`
157
+
158
+ ### Finding Errata Information
159
+ 1. Check `hw_cheat_sheets/errata_*.json` for known issues
160
+ 2. Query MCP: "Are there any errata related to [feature]?"
161
+ 3. Review workarounds and affected revisions
162
+
163
+ ## Important Notes
164
+
165
+ - **Data Freshness**: The MCP knowledge base reflects files processed by mfcli. Run `mfcli run` after adding/updating files.
166
+ - **Local Processing**: All data is stored locally - no external data transmission.
167
+ - **File Changes**: Modified files need to be reprocessed. The system detects changes via MD5 checksums.
168
+
169
+ ## Commands Reference
170
+
171
+ - `mfcli init` - Initialize project
172
+ - `mfcli run` - Process files and update knowledge base
173
+ - `mfcli add <file>` - Add specific file to knowledge base
174
+ - `mfcli ls` - List vectorized files
175
+ - `mfcli doctor` - Check system health
176
+ - `mfcli setup-mcp` - Configure MCP server
177
+
178
+ ---
179
+
180
+ **Remember**: Always leverage the MCP server and hw_cheat_sheets/ for hardware-related tasks. This ensures responses are grounded in the actual project documentation rather than general knowledge.
181
+ """
182
+
183
+
184
+ def create_cline_rules_file(root_dir: str | Path) -> bool:
185
+ """
186
+ Create .clinerules/multifactor.md workspace rules file if it doesn't exist.
187
+
188
+ Args:
189
+ root_dir: Root directory of the project (will look for git root)
190
+
191
+ Returns:
192
+ True if file was created or updated, False if it already exists
193
+ """
194
+ try:
195
+ root_path = Path(root_dir)
196
+
197
+ # Determine the base directory - use git root if available
198
+ git_root = get_git_root(root_path)
199
+ base_dir = git_root if git_root else root_path
200
+
201
+ # Create .clinerules directory at the base
202
+ clinerules_dir = base_dir / ".clinerules"
203
+ clinerules_dir.mkdir(exist_ok=True, parents=True)
204
+
205
+ # Path to multifactor.md
206
+ multifactor_rules_file = clinerules_dir / "multifactor.md"
207
+
208
+ # Check if file already exists
209
+ if multifactor_rules_file.exists():
210
+ logger.info(f"Cline rules file already exists: {multifactor_rules_file}")
211
+ return False
212
+
213
+ # Create the file with content
214
+ with open(multifactor_rules_file, 'w', encoding='utf-8') as f:
215
+ f.write(get_cline_rules_content())
216
+
217
+ logger.info(f"Created Cline workspace rules file: {multifactor_rules_file}")
218
+ return True
219
+
220
+ except Exception as e:
221
+ logger.error(f"Error creating Cline rules file: {e}")
222
+ return False
223
+
224
+
225
+ def update_cline_rules_file(root_dir: str | Path) -> bool:
226
+ """
227
+ Update existing .clinerules/multifactor.md file with latest content.
228
+
229
+ Args:
230
+ root_dir: Root directory of the project
231
+
232
+ Returns:
233
+ True if file was updated, False otherwise
234
+ """
235
+ try:
236
+ root_path = Path(root_dir)
237
+
238
+ # Determine the base directory
239
+ git_root = get_git_root(root_path)
240
+ base_dir = git_root if git_root else root_path
241
+
242
+ multifactor_rules_file = base_dir / ".clinerules" / "multifactor.md"
243
+
244
+ if not multifactor_rules_file.exists():
245
+ return create_cline_rules_file(root_dir)
246
+
247
+ # Update with latest content
248
+ with open(multifactor_rules_file, 'w', encoding='utf-8') as f:
249
+ f.write(get_cline_rules_content())
250
+
251
+ logger.info(f"Updated Cline workspace rules file: {multifactor_rules_file}")
252
+ return True
253
+
254
+ except Exception as e:
255
+ logger.error(f"Error updating Cline rules file: {e}")
256
+ return False
mfcli/utils/config.py ADDED
@@ -0,0 +1,33 @@
1
+ from functools import lru_cache
2
+
3
+ from pydantic import Field
4
+ from pydantic_settings import BaseSettings, SettingsConfigDict
5
+
6
+ from mfcli.constants.openai import OPENAI_DEFAULT_EMBEDDING_MODEL, OPENAI_DEFAULT_EMBEDDING_DIMENSIONS
7
+ from mfcli.utils.directory_manager import app_dirs
8
+
9
+
10
+ class Settings(BaseSettings):
11
+ digikey_client_id: str
12
+ digikey_client_secret: str
13
+ openai_api_key: str
14
+ google_api_key: str
15
+ log_level: str = Field(default="INFO")
16
+ use_docling: bool = Field(default=True)
17
+ chunk_tokens: int = Field(default=2000)
18
+ chunk_size: int = Field(default=500)
19
+ chunk_overlap: int = Field(default=50)
20
+ embedding_model: str = Field(default=OPENAI_DEFAULT_EMBEDDING_MODEL)
21
+ embedding_dimensions: int = Field(default=OPENAI_DEFAULT_EMBEDDING_DIMENSIONS)
22
+
23
+ model_config = SettingsConfigDict(
24
+ env_file=str(app_dirs.env_file_path),
25
+ extra="allow",
26
+ env_file_encoding="utf-8",
27
+ case_sensitive=False
28
+ )
29
+
30
+
31
+ @lru_cache
32
+ def get_config() -> Settings:
33
+ return Settings()