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,185 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ PADS Netlist Parser
4
+
5
+ Parses PADS netlist files (Mentor Graphics) and extracts:
6
+ - Reference designators (ref_des)
7
+ - Part numbers/footprints
8
+ - Pin connections (pin number + net name)
9
+
10
+ Usage:
11
+ python pads_parser.py <netlist.txt> [--output <output.json>]
12
+
13
+ Example:
14
+ python pads_parser.py design.asc --output parsed.json
15
+ """
16
+
17
+ from pathlib import Path
18
+ from typing import Dict
19
+
20
+ from mfcli.models.netlist import Component, NetlistSchema, Pin
21
+
22
+
23
+ # ============================================================================
24
+ # PADS Parser
25
+ # ============================================================================
26
+
27
+ class PADSParser:
28
+ """Parser for PADS netlist files."""
29
+
30
+ def __init__(self, pads_content: str):
31
+ self.content = pads_content
32
+ self.lines = [line.strip() for line in pads_content.strip().split('\n')]
33
+ self.components: Dict[str, Component] = {}
34
+
35
+ def parse(self) -> NetlistSchema:
36
+ """Parse PADS content and return validated schema."""
37
+ # Step 1: Validate header
38
+ if not self._validate_header():
39
+ raise ValueError("Not a valid PADS netlist file (missing *PADS-PCB* header)")
40
+
41
+ # Step 2: Find section boundaries
42
+ part_start, part_end = self._find_section('*PART*')
43
+ net_start, net_end = self._find_section('*NET*')
44
+
45
+ # Step 3: Parse components
46
+ if part_start is not None and part_end is not None:
47
+ self._parse_parts(part_start, part_end)
48
+
49
+ # Step 4: Parse nets and add pins to components
50
+ if net_start is not None and net_end is not None:
51
+ self._parse_nets(net_start, net_end)
52
+
53
+ # Step 5: Validate and return
54
+ components_list = list(self.components.values())
55
+ return NetlistSchema(components=components_list)
56
+
57
+ def _validate_header(self) -> bool:
58
+ """Check if file has PADS header."""
59
+ return len(self.lines) > 0 and self.lines[0].startswith('*PADS-PCB*')
60
+
61
+ def _find_section(self, section_name: str) -> tuple:
62
+ """Find start and end indices of a section."""
63
+ try:
64
+ start_idx = self.lines.index(section_name) + 1
65
+
66
+ # Find end of section (next TOP-LEVEL section marker or *END*)
67
+ # Top-level markers: *PART*, *NET*, *END* (not *SIGNAL*)
68
+ top_level_markers = ['*PART*', '*NET*', '*END*']
69
+ end_idx = len(self.lines)
70
+ for i in range(start_idx, len(self.lines)):
71
+ if self.lines[i] in top_level_markers and self.lines[i] != section_name:
72
+ end_idx = i
73
+ break
74
+
75
+ return start_idx, end_idx
76
+ except ValueError:
77
+ return None, None
78
+
79
+ def _parse_parts(self, start_idx: int, end_idx: int):
80
+ """Parse the *PART* section to extract components."""
81
+ for i in range(start_idx, end_idx):
82
+ line = self.lines[i]
83
+
84
+ # Skip empty lines and section markers
85
+ if not line or line.startswith('*'):
86
+ continue
87
+
88
+ # Split line into ref_des and part_number
89
+ parts = line.split(None, 1) # Split on first whitespace
90
+ if len(parts) >= 1:
91
+ ref_des = parts[0]
92
+ part_number = parts[1] if len(parts) > 1 else ""
93
+
94
+ # Create component
95
+ self.components[ref_des] = Component(
96
+ ref_des=ref_des,
97
+ part_number=part_number,
98
+ pins=[]
99
+ )
100
+
101
+ def _parse_nets(self, start_idx: int, end_idx: int):
102
+ """Parse the *NET* section to extract pin connections."""
103
+ current_net = None
104
+
105
+ for i in range(start_idx, end_idx):
106
+ line = self.lines[i]
107
+
108
+ # Skip empty lines
109
+ if not line:
110
+ continue
111
+
112
+ # Check for signal/net definition
113
+ if line.startswith('*SIGNAL*'):
114
+ # Extract net name (everything after *SIGNAL*)
115
+ parts = line.split(None, 1)
116
+ if len(parts) > 1:
117
+ current_net = parts[1]
118
+ continue
119
+
120
+ # Skip other section markers
121
+ if line.startswith('*'):
122
+ current_net = None
123
+ continue
124
+
125
+ # Parse pin references if we have a current net
126
+ if current_net:
127
+ self._parse_pin_references(line, current_net)
128
+
129
+ def _parse_pin_references(self, line: str, net_name: str):
130
+ """Parse pin references from a line and add to components."""
131
+ # Split line into individual pin references
132
+ pin_refs = line.split()
133
+
134
+ for pin_ref in pin_refs:
135
+ # Skip empty strings
136
+ if not pin_ref:
137
+ continue
138
+
139
+ # Pin format: RefDes.PinNumber (e.g., C1.2, R14.1)
140
+ if '.' in pin_ref:
141
+ parts = pin_ref.split('.', 1)
142
+ ref_des = parts[0]
143
+ pin_number = parts[1]
144
+
145
+ # Add pin to component if component exists
146
+ if ref_des in self.components:
147
+ pin = Pin(pin=pin_number, net=net_name)
148
+
149
+ # Avoid duplicates
150
+ existing_pins = self.components[ref_des].pins
151
+ if not any(p.pin == pin.pin and p.net == pin.net for p in existing_pins):
152
+ self.components[ref_des].pins.append(pin)
153
+
154
+
155
+ # ============================================================================
156
+ # Main Functions
157
+ # ============================================================================
158
+
159
+ def parse_pads_file(filepath: Path) -> NetlistSchema:
160
+ """
161
+ Parse a PADS netlist file and return validated netlist schema.
162
+
163
+ Args:
164
+ filepath: Path to PADS netlist file
165
+
166
+ Returns:
167
+ NetlistSchema with components and pins
168
+
169
+ Raises:
170
+ FileNotFoundError: If file doesn't exist
171
+ ValidationError: If parsed data doesn't match schema
172
+ ValueError: If file is not a valid PADS netlist
173
+ """
174
+ if not filepath.exists():
175
+ raise FileNotFoundError(f"PADS netlist file not found: {filepath}")
176
+
177
+ # Read file content
178
+ with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
179
+ content = f.read()
180
+
181
+ # Parse
182
+ parser = PADSParser(content)
183
+ schema = parser.parse()
184
+
185
+ return schema
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Protel/Altium Designer Netlist Parser
4
+
5
+ Parses Protel/Altium Designer netlist files and extracts:
6
+ - Reference designators (ref_des)
7
+ - Part numbers/footprints
8
+ - Pin connections (pin number + net name)
9
+
10
+ Format example:
11
+ {COMPONENT PROTEL.PCB
12
+ {DETAIL
13
+ {SUBCOMP
14
+ {I <footprint>.PRT <ref_des>
15
+ {CN
16
+ <pin> <net>
17
+ ...
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
23
+ """
24
+
25
+ from pathlib import Path
26
+ from typing import Dict
27
+
28
+ from mfcli.models.netlist import Component, NetlistSchema, Pin
29
+
30
+
31
+ class ProtelParser:
32
+ """Parser for Protel/Altium Designer netlist files."""
33
+
34
+ def __init__(self, protel_content: str):
35
+ self.content = protel_content
36
+ self.lines = [line.strip() for line in protel_content.strip().split('\n')]
37
+ self.components: Dict[str, Component] = {}
38
+
39
+ def parse(self) -> NetlistSchema:
40
+ """Parse Protel content and return validated schema."""
41
+ # Validate header
42
+ if not self._validate_header():
43
+ raise ValueError("Not a valid Protel/Altium netlist file (missing {COMPONENT PROTEL.PCB header)")
44
+
45
+ # Parse components
46
+ self._parse_components()
47
+
48
+ # Validate and return
49
+ components_list = list(self.components.values())
50
+ return NetlistSchema(components=components_list)
51
+
52
+ def _validate_header(self) -> bool:
53
+ """Check if file has Protel/Altium header."""
54
+ return len(self.lines) > 0 and '{COMPONENT PROTEL.PCB' in self.lines[0]
55
+
56
+ def _parse_components(self):
57
+ """Parse components from the netlist."""
58
+ i = 0
59
+ while i < len(self.lines):
60
+ line = self.lines[i]
61
+
62
+ # Look for component definition: {I <footprint>.PRT <ref_des>
63
+ if line.startswith('{I ') and '.PRT ' in line:
64
+ # Extract footprint and ref_des
65
+ parts = line.split()
66
+ if len(parts) >= 3:
67
+ footprint = parts[1] # e.g., "0603.PRT"
68
+ ref_des = parts[2] # e.g., "C1"
69
+
70
+ # Remove .PRT extension from footprint
71
+ if footprint.endswith('.PRT'):
72
+ footprint = footprint[:-4]
73
+
74
+ # Create component
75
+ self.components[ref_des] = Component(
76
+ ref_des=ref_des,
77
+ part_number=footprint,
78
+ pins=[]
79
+ )
80
+
81
+ # Parse pins for this component
82
+ i = self._parse_pins(i + 1, ref_des)
83
+ continue
84
+
85
+ i += 1
86
+
87
+ def _parse_pins(self, start_idx: int, ref_des: str) -> int:
88
+ """
89
+ Parse pins for a component starting from the {CN block.
90
+ Returns the index after parsing all pins.
91
+ """
92
+ i = start_idx
93
+ in_cn_block = False
94
+
95
+ while i < len(self.lines):
96
+ line = self.lines[i]
97
+
98
+ # Check if we're entering the {CN block
99
+ if line == '{CN':
100
+ in_cn_block = True
101
+ i += 1
102
+ continue
103
+
104
+ # Check if we're exiting the {CN block or component block
105
+ if line == '}':
106
+ if in_cn_block:
107
+ in_cn_block = False
108
+ return i + 1 # Exit after {CN block closes
109
+ else:
110
+ return i + 1 # Exit after component block closes
111
+
112
+ # Parse pin connections within {CN block
113
+ if in_cn_block and line:
114
+ # Format: <pin_number> <net_name>
115
+ # Example: "1 3V3" or "2 GND"
116
+ parts = line.split(None, 1)
117
+ if len(parts) >= 1:
118
+ pin_number = parts[0]
119
+ net_name = parts[1] if len(parts) > 1 else ""
120
+
121
+ # Skip lines that don't start with a number (metadata)
122
+ if not pin_number or not pin_number[0].isdigit():
123
+ i += 1
124
+ continue
125
+
126
+ # Add pin to component
127
+ if ref_des in self.components and net_name:
128
+ pin = Pin(pin=pin_number, net=net_name)
129
+
130
+ # Avoid duplicates
131
+ existing_pins = self.components[ref_des].pins
132
+ if not any(p.pin == pin.pin and p.net == pin.net for p in existing_pins):
133
+ self.components[ref_des].pins.append(pin)
134
+
135
+ i += 1
136
+
137
+ return i
138
+
139
+
140
+ def parse_protel_file(filepath: Path) -> NetlistSchema:
141
+ """
142
+ Parse a Protel/Altium Designer netlist file and return validated netlist schema.
143
+
144
+ Args:
145
+ filepath: Path to Protel/Altium netlist file
146
+
147
+ Returns:
148
+ NetlistSchema with components and pins
149
+
150
+ Raises:
151
+ FileNotFoundError: If file doesn't exist
152
+ ValidationError: If parsed data doesn't match schema
153
+ ValueError: If file is not a valid Protel/Altium netlist
154
+ """
155
+ if not filepath.exists():
156
+ raise FileNotFoundError(f"Protel/Altium netlist file not found: {filepath}")
157
+
158
+ # Read file content
159
+ with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
160
+ content = f.read()
161
+
162
+ # Parse
163
+ parser = ProtelParser(content)
164
+ schema = parser.parse()
165
+
166
+ return schema
@@ -0,0 +1,29 @@
1
+ """
2
+ Protel/Altium Designer Netlist Detector
3
+
4
+ Helper function to detect if a file is a Protel/Altium Designer netlist.
5
+ """
6
+
7
+
8
+ def is_protel_netlist(content: str) -> bool:
9
+ """
10
+ Detect if content is from a Protel/Altium Designer netlist file.
11
+
12
+ Args:
13
+ content: File content to check
14
+
15
+ Returns:
16
+ True if content appears to be a Protel/Altium netlist
17
+ """
18
+ lines = content.strip().split('\n')
19
+
20
+ if not lines:
21
+ return False
22
+
23
+ # Check for Protel/Altium header in first few lines
24
+ for line in lines[:5]:
25
+ line = line.strip()
26
+ if '{COMPONENT PROTEL.PCB' in line:
27
+ return True
28
+
29
+ return False