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
mfcli/models/mcu.py ADDED
@@ -0,0 +1,97 @@
1
+ from typing import List
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class MCUData(BaseModel):
7
+ """Complete MCU cheat sheet for firmware boot essentials."""
8
+ mcu_name: str = Field(..., description="MCU part number")
9
+ mcu_family: str = Field("", description="MCU family or series")
10
+ architecture: str = Field("", description="CPU architecture (e.g., ARM Cortex-M4, RISC-V, AVR)")
11
+ core_frequency_max: str = Field("", description="Maximum core frequency")
12
+
13
+
14
+ class MemoryRegion(BaseModel):
15
+ """Represents a memory region in the MCU."""
16
+ name: str = Field(..., description="Memory region name (e.g., FLASH, RAM, ROM)")
17
+ start_address: str = Field(..., description="Start address in hex (e.g., 0x08000000)")
18
+ size: str = Field(..., description="Size in bytes or with unit (e.g., 128KB, 0x20000)")
19
+ type: str = Field(..., description="Type: flash, ram, rom, peripheral, reserved")
20
+ description: str = Field("", description="Additional details about this region")
21
+
22
+
23
+ class MemoryMap(BaseModel):
24
+ memory_map: List[MemoryRegion] = Field(default_factory=list, description="Memory regions for linker script")
25
+
26
+
27
+ class ClockConfiguration(BaseModel):
28
+ """Represents clock configuration details."""
29
+ clock_source: str = Field(..., description="Clock source (e.g., HSI, HSE, PLL, Internal RC)")
30
+ frequency_range: str = Field("", description="Frequency range or typical frequency")
31
+ configuration_steps: List[str] = Field(default_factory=list, description="Steps to configure this clock")
32
+ registers: List[str] = Field(default_factory=list, description="Key registers to configure")
33
+ notes: str = Field("", description="Important notes about clock configuration")
34
+
35
+
36
+ class ClockConfig(BaseModel):
37
+ clock_config: ClockConfiguration = Field(..., description="Represents clock configuration details.")
38
+
39
+
40
+ class InterruptVector(BaseModel):
41
+ """Represents an interrupt vector entry."""
42
+ vector_number: int = Field(..., description="Interrupt vector number or position")
43
+ name: str = Field(..., description="Interrupt name")
44
+ address: str = Field("", description="Vector table address in hex")
45
+ priority: str = Field("", description="Default or configurable priority")
46
+ description: str = Field("", description="What triggers this interrupt")
47
+
48
+
49
+ class InterruptVectors(BaseModel):
50
+ interrupt_vectors: List[InterruptVector] = Field(..., description="Represents interrupt vector entities.")
51
+
52
+
53
+ class StartupRequirements(BaseModel):
54
+ """Represents startup and boot requirements."""
55
+ boot_mode: str = Field("", description="Boot mode or boot source selection")
56
+ startup_code_location: str = Field("", description="Where startup code should be placed")
57
+ vector_table_offset: str = Field("", description="Vector table offset register (VTOR)")
58
+ stack_pointer_init: str = Field("", description="How to initialize stack pointer")
59
+ minimum_init_sequence: List[str] = Field(default_factory=list, description="Minimum steps to boot")
60
+ watchdog_handling: str = Field("", description="How to handle watchdog timer at startup")
61
+
62
+
63
+ class StartupRequirementsModel(BaseModel):
64
+ startup_requirements: StartupRequirements = Field(..., description="Represents startup and boot requirements.")
65
+
66
+
67
+ class PeripheralInfo(BaseModel):
68
+ """Represents a peripheral module."""
69
+ name: str = Field(..., description="Peripheral name (e.g., GPIO, UART0, Timer1)")
70
+ base_address: str = Field("", description="Base address in hex")
71
+ clock_enable: str = Field("", description="How to enable clock for this peripheral")
72
+ initialization_steps: List[str] = Field(default_factory=list, description="Basic init steps")
73
+ key_registers: List[str] = Field(default_factory=list, description="Important registers")
74
+
75
+
76
+ class EssentialPeripherals(BaseModel):
77
+ essential_peripherals: List[PeripheralInfo] = Field(..., description="Represents essential peripherals")
78
+
79
+
80
+ class MCULinkerScriptInfo(BaseModel):
81
+ linker_script_notes: str = Field("", description="Notes for creating linker script")
82
+
83
+
84
+ class MCUHelloWorldSteps(BaseModel):
85
+ hello_world_steps: List[str] = Field(default_factory=list, description="Steps to create a hello world app")
86
+
87
+
88
+ class MCUDebuggingInterface(BaseModel):
89
+ debugging_interface: str = Field("", description="Debug interface (JTAG, SWD, etc.)")
90
+
91
+
92
+ class MCUProgrammingInterface(BaseModel):
93
+ programming_interface: str = Field("", description="Programming interface and method")
94
+
95
+
96
+ class MCUDatasheetReferences(BaseModel):
97
+ datasheet_references: List[str] = Field(default_factory=list, description="Key datasheet sections/pages")
@@ -0,0 +1,26 @@
1
+ from typing import List, Literal
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class ErrataIDs(BaseModel):
7
+ ids: list[str]
8
+
9
+
10
+ class ErrataTopLevelSummary(BaseModel):
11
+ errata_document: str = Field(..., description="Errata document name (e.g., Silicon Errata Rev 1.2 - March 2024)")
12
+ mcu_name: str = Field(..., description="MCU name (e.g., MSPM0L1306)")
13
+ recommendations: list[str] = Field(default_factory=list, description="Top-level firmware recommendations")
14
+
15
+
16
+ class ErrataItem(BaseModel):
17
+ """Individual errata issue."""
18
+ errata_id: str = Field(..., description="Errata ID/number (e.g., 'I2C_01', 'ADV0123')")
19
+ title: str = Field(..., description="Brief title of the issue")
20
+ affected_modules: List[str] = Field(default_factory=list, description="Affected peripherals/modules")
21
+ severity: Literal['Critical', 'Major', 'Minor']
22
+ description: str = Field(..., description="What the issue is")
23
+ conditions: str = Field("", description="When/how the issue occurs")
24
+ firmware_workaround: str = Field("", description="Firmware workaround or mitigation")
25
+ impact: str = Field("", description="Impact on firmware operation")
26
+ affected_revisions: List[str] = Field(default_factory=list, description="Silicon revisions affected")
@@ -0,0 +1,59 @@
1
+ from datetime import datetime
2
+ from typing import List, Optional
3
+
4
+ from pydantic import BaseModel
5
+ from sqlalchemy import Column, TIMESTAMP, func
6
+ from sqlmodel import Field, Relationship, SQLModel
7
+
8
+ from mfcli.models.bom import BOM
9
+ from mfcli.models.pipeline_run import PipelineRun
10
+
11
+
12
+ class Pin(BaseModel):
13
+ """Represents a single pin connection."""
14
+ pin: str = Field(..., description="Pin number or name")
15
+ net: str = Field(..., description="Net name this pin connects to")
16
+
17
+
18
+ class Component(BaseModel):
19
+ """Represents a component with its pins and connections."""
20
+ ref_des: str = Field(..., description="Reference designator (C1, R5, U2, etc.)")
21
+ part_number: str = Field(..., description="Part number or footprint")
22
+ pins: List[Pin] = Field(default_factory=list, description="List of pin connections")
23
+
24
+
25
+ class NetlistSchema(BaseModel):
26
+ """Top-level schema for parsed netlist."""
27
+ components: List[Component] = Field(default_factory=list, description="List of components")
28
+
29
+
30
+ class Netlist(SQLModel, table=True):
31
+ __tablename__ = "netlists"
32
+ id: Optional[int] = Field(default=None, primary_key=True)
33
+ pipeline_run_id: int = Field(foreign_key='pipeline_runs.id', index=True, nullable=False, ondelete="CASCADE")
34
+ pipeline_run: Optional["PipelineRun"] = Relationship()
35
+ components: List["NetlistComponent"] = Relationship(back_populates='netlist', cascade_delete=True)
36
+ created_at: datetime = Field(
37
+ sa_column=Column(TIMESTAMP(timezone=True), server_default=func.now(), nullable=False)
38
+ )
39
+
40
+
41
+ class NetlistComponent(SQLModel, table=True):
42
+ __tablename__ = "netlist_components"
43
+ id: Optional[int] = Field(default=None, primary_key=True)
44
+ netlist_id: int = Field(foreign_key='netlists.id', index=True, nullable=False, ondelete="CASCADE")
45
+ netlist: Optional["Netlist"] = Relationship(back_populates='components')
46
+ pins: List["NetlistPin"] = Relationship(back_populates='netlist_component', cascade_delete=True)
47
+ ref_des: str = Field(..., max_length=100, nullable=False)
48
+ part_number: str = Field(max_length=255, nullable=False, index=True)
49
+ bom_entry_id: Optional[int] = Field(foreign_key='bom.id', index=True, ondelete='CASCADE')
50
+ bom_entry: Optional["BOM"] = Relationship()
51
+
52
+
53
+ class NetlistPin(SQLModel, table=True):
54
+ __tablename__ = "netlist_pins"
55
+ id: Optional[int] = Field(default=None, primary_key=True)
56
+ netlist_component_id: int = Field(foreign_key='netlist_components.id', index=True, nullable=False, ondelete="CASCADE")
57
+ netlist_component: NetlistComponent = Relationship()
58
+ pin: str = Field(..., max_length=100, nullable=False)
59
+ net: str = Field(..., max_length=100, nullable=False)
@@ -0,0 +1,25 @@
1
+ from typing import Optional
2
+
3
+ from sqlalchemy import Column, Integer, ForeignKey
4
+ from sqlmodel import SQLModel, Field, Relationship
5
+
6
+
7
+ class PDFPart(SQLModel, table=True):
8
+ __tablename__ = "pdf_parts"
9
+
10
+ id: Optional[int] = Field(default=None, primary_key=True)
11
+ file_id: int = Field(
12
+ sa_column=Column(
13
+ Integer,
14
+ ForeignKey("files.id", ondelete="CASCADE"),
15
+ nullable=False,
16
+ index=True
17
+ )
18
+ )
19
+ file: Optional["File"] = Relationship(back_populates='pdf_parts')
20
+ path: str = Field(max_length=600, nullable=False)
21
+ gemini_file_id: str = Field(max_length=40, nullable=True)
22
+ start_page: int = Field(nullable=True)
23
+ end_page: int = Field(nullable=True)
24
+ title: str = Field(nullable=True)
25
+ section_no: int = Field(nullable=True)
@@ -0,0 +1,34 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from sqlalchemy import Integer, func, Text, Column, DateTime, ForeignKey
5
+ from sqlmodel import SQLModel, Field, Relationship
6
+
7
+ from mfcli.constants.pipeline_run_status import PIPELINE_STATUS_IN_PROGRESS
8
+ from mfcli.models.project import Project
9
+
10
+
11
+ class PipelineRun(SQLModel, table=True):
12
+ __tablename__ = "pipeline_runs"
13
+
14
+ id: Optional[int] = Field(default=None, primary_key=True)
15
+ project_id: int = Field(
16
+ sa_column=Column(
17
+ Integer,
18
+ ForeignKey("projects.id", ondelete="CASCADE"),
19
+ nullable=True,
20
+ index=True
21
+ )
22
+ )
23
+ project: Optional["Project"] = Relationship(back_populates='runs')
24
+ status: int = Field(
25
+ sa_column=Column(Integer, nullable=False, server_default=str(PIPELINE_STATUS_IN_PROGRESS))
26
+ )
27
+ errors: Optional[str] = Field(default=None, sa_column=Column(Text, nullable=True))
28
+
29
+ created_at: datetime = Field(
30
+ sa_column=Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
31
+ )
32
+ updated_at: datetime = Field(
33
+ sa_column=Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
34
+ )
@@ -0,0 +1,27 @@
1
+ from datetime import datetime
2
+ from typing import Optional, List
3
+
4
+ from sqlalchemy import Column, DateTime, func
5
+ from sqlmodel import SQLModel, Field, Relationship
6
+
7
+ project_name_regex = r"^[A-Za-z0-9_-]+$"
8
+
9
+
10
+ class Project(SQLModel, table=True):
11
+ __tablename__ = "projects"
12
+
13
+ id: Optional[int] = Field(default=None, primary_key=True)
14
+ runs: List["PipelineRun"] = Relationship(back_populates='project', cascade_delete=True)
15
+ name: str = Field(
16
+ nullable=False,
17
+ unique=True,
18
+ index=True,
19
+ min_length=3,
20
+ max_length=45,
21
+ regex=project_name_regex
22
+ )
23
+ index_id: str = Field(index=True, unique=True)
24
+ repo_dir: str = Field(index=True, unique=True)
25
+ created_at: datetime = Field(
26
+ sa_column=Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
27
+ )
@@ -0,0 +1,15 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class VectorizeCheatSheetsConfig(BaseModel):
5
+ vectorize_errata: bool = Field(default=False)
6
+ vectorize_mcu: bool = Field(default=False)
7
+ vectorize_debug_setup: bool = Field(default=False)
8
+ vectorize_functional_blocks: bool = Field(default=False)
9
+
10
+
11
+ class ProjectConfig(BaseModel):
12
+ name: str
13
+ vectorize_hw_files: bool = Field(default=True)
14
+ vectorize_datasheets: bool = Field(default=True)
15
+ vectorize_cheat_sheets_config: VectorizeCheatSheetsConfig = Field(default_factory=VectorizeCheatSheetsConfig)
File without changes
File without changes
@@ -0,0 +1,28 @@
1
+ from mfcli.models.bom import BOM
2
+ from mfcli.models.file import File
3
+ from mfcli.models.netlist import NetlistComponent, Netlist
4
+ from mfcli.utils.logger import get_logger
5
+ from mfcli.utils.orm import Session
6
+
7
+ logger = get_logger(__name__)
8
+
9
+
10
+ def map_netlist_to_bom_entries(db: Session, pipeline_run_id: int):
11
+ logger.debug(f"Mapping netlist to BOM entries for pipeline: {pipeline_run_id}")
12
+ components: list[NetlistComponent] = (
13
+ db.query(NetlistComponent)
14
+ .join(NetlistComponent.netlist)
15
+ .filter(Netlist.pipeline_run_id == pipeline_run_id)
16
+ .all()
17
+ )
18
+ results = (
19
+ db.query(BOM.id, BOM.value)
20
+ .join(BOM.file)
21
+ .filter(File.pipeline_run_id == pipeline_run_id)
22
+ .all()
23
+ )
24
+ bom_value_id_map = {result[1]: result[0] for result in results}
25
+ for component in components:
26
+ if bom_value_id_map.get(component.part_number):
27
+ component.bom_entry_id = bom_value_id_map[component.part_number]
28
+ logger.debug(f"All netlist entries mapped: {pipeline_run_id}")
File without changes
File without changes
@@ -0,0 +1,74 @@
1
+ import asyncio
2
+ from asyncio import Semaphore
3
+ from typing import Dict, Type, List
4
+
5
+ from google.genai.types import File
6
+ from pydantic import BaseModel, Field
7
+
8
+ from mfcli.client.gemini import Gemini
9
+ from mfcli.models.bom import BOMSchema
10
+ from mfcli.utils.logger import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+ system_instructions = (
15
+ """
16
+ You are the BOM Generator agent. Your role is to extract components from PDF schematics
17
+ and generate a Bill of Materials (BOM) in CSV format.
18
+ """
19
+ )
20
+
21
+ identify_bom_entries_instructions = (
22
+ """
23
+ You will return a list of names for Bill of Materials (BOM) entries from this schematic file.
24
+ """
25
+ )
26
+
27
+ generate_bom_entry_instructions = (
28
+ """
29
+ Analyze the schematic and extract BOM entry data for component: {}:
30
+ - Reference designators (R1, C1, U1, etc.)
31
+ - Component values (10kΩ, 100nF, etc.)
32
+ - Part numbers and manufacturers (if visible)
33
+ - Footprints/packages (if visible)
34
+ - Any other available information
35
+ """
36
+ )
37
+
38
+
39
+ class BOMEntryNames(BaseModel):
40
+ names: List[str] = Field(default_factory=list, description="BOM entry names")
41
+
42
+
43
+ class BOMGenerator:
44
+ def __init__(self, gemini: Gemini):
45
+ self._gemini = gemini
46
+ self._sem = Semaphore(5)
47
+
48
+ async def _generate(self, file: File, model: Type[BaseModel], instructions: str) -> BaseModel:
49
+ async with self._sem:
50
+ return await self._gemini.generate(
51
+ prompt=instructions,
52
+ instructions=system_instructions,
53
+ response_model=model,
54
+ files=[file]
55
+ )
56
+
57
+ async def _get_bom_entry_names(self, file: File) -> List[str]:
58
+ response = await self._generate(file, BOMEntryNames, identify_bom_entries_instructions)
59
+ return response.names
60
+
61
+ async def generate(self, file: File) -> List[Dict]:
62
+ bom_entry_names = await self._get_bom_entry_names(file)
63
+ tasks = []
64
+ for name in bom_entry_names:
65
+ bom_entry_instructions = generate_bom_entry_instructions.format(name)
66
+ tasks.append(self._generate(file, BOMSchema, bom_entry_instructions))
67
+ results: List[BOMSchema | Exception] = await asyncio.gather(*tasks, return_exceptions=True)
68
+ response_data = []
69
+ for result in results:
70
+ if isinstance(result, Exception):
71
+ logger.warn(f"There was an error generating BOM entry data: {result}")
72
+ continue
73
+ response_data.append(result.model_dump())
74
+ return response_data
@@ -0,0 +1,71 @@
1
+ import asyncio
2
+ from asyncio import Semaphore
3
+ from typing import Dict, Type, List
4
+
5
+ from google.genai.types import File as GeminiFile
6
+ from pydantic import BaseModel
7
+
8
+ from mfcli.models.debug_setup import (
9
+ DSTargetMCU,
10
+ DebugInterface,
11
+ DebugTool,
12
+ DriverInfo,
13
+ VSCodeLaunchConfig,
14
+ DSAdditionalTools,
15
+ DSTroubleshootingGuide,
16
+ DSQuickStartGuide
17
+ )
18
+ from mfcli.models.file import File
19
+ from mfcli.pipeline.analysis.generators.debug_setup.instructions import (
20
+ ds_mcu_instructions,
21
+ ds_debug_int_instructions,
22
+ ds_recommended_tool_instructions,
23
+ ds_drivers_instructions,
24
+ ds_vscode_launch_instructions,
25
+ ds_add_tools_instructions,
26
+ ds_troubleshooting_instructions,
27
+ ds_quick_start_guide_instructions,
28
+ ds_system_instructions
29
+ )
30
+ from mfcli.pipeline.analysis.generators.generator_base import GeneratorBase
31
+ from mfcli.pipeline.run_context import PipelineRunContext
32
+ from mfcli.utils.logger import get_logger
33
+
34
+ logger = get_logger(__name__)
35
+
36
+ DSModelInstructionsMap = {
37
+ DSTargetMCU: ds_mcu_instructions,
38
+ DebugInterface: ds_debug_int_instructions,
39
+ DebugTool: ds_recommended_tool_instructions,
40
+ DriverInfo: ds_drivers_instructions,
41
+ VSCodeLaunchConfig: ds_vscode_launch_instructions,
42
+ DSAdditionalTools: ds_add_tools_instructions,
43
+ DSTroubleshootingGuide: ds_troubleshooting_instructions,
44
+ DSQuickStartGuide: ds_quick_start_guide_instructions
45
+ }
46
+
47
+
48
+ class DSCheatSheetGenerator(GeneratorBase):
49
+ def __init__(self, context: PipelineRunContext, db_file: File, uploads: List[GeminiFile]):
50
+ super().__init__(context, db_file, uploads)
51
+ self._sem = Semaphore(5)
52
+
53
+ async def _extract_ds_data(self, model: Type[BaseModel], instructions: str) -> BaseModel:
54
+ return await self._context.gemini.generate(
55
+ prompt=instructions,
56
+ instructions=ds_system_instructions,
57
+ response_model=model,
58
+ files=self._uploads
59
+ )
60
+
61
+ async def generate(self) -> Dict:
62
+ tasks = []
63
+ for model, instructions in DSModelInstructionsMap.items():
64
+ tasks.append(self._extract_ds_data(model, instructions))
65
+ results: list[BaseModel] = await asyncio.gather(*tasks)
66
+ response_dict = {}
67
+ for result in results:
68
+ response_dict.update(result.model_dump())
69
+ return {
70
+ "debug_setup": response_dict
71
+ }
@@ -0,0 +1,150 @@
1
+ ds_system_instructions = (
2
+ """
3
+ You are the Debug Setup Cheat Sheet Generator agent. Your role is to analyze schematics
4
+ to extract debug interface information and provide comprehensive setup guidance including:
5
+ - Recommended JTAG/SWD tools based on the MCU and interface
6
+ - Driver installation for Windows and Linux
7
+ - VS Code launch.json configuration
8
+ - GDB server setup
9
+ - Quick start guide
10
+
11
+
12
+ Analyze the schematic to identify debug interface:
13
+ Look for:
14
+ - Debug connector (J-TAG, SWD, ICSP headers)
15
+ - Pin labels (SWDIO, SWCLK, TMS, TCK, TDI, TDO, etc.)
16
+ - Target MCU identification
17
+ - Voltage levels
18
+ - Connector type and pinout
19
+
20
+ Determine recommended debug tool based on MCU:
21
+
22
+ Common mappings:
23
+ - STM32 → ST-Link V3 (official), J-Link (professional)
24
+ - NXP LPC/Kinetis → LPC-Link2, J-Link
25
+ - Nordic nRF → J-Link, nRF DK
26
+ - TI MSP430/MSPM0 → MSP-FET, XDS110
27
+ - Microchip PIC/AVR → PICkit, ICD, MPLAB Snap
28
+ - ARM Cortex-M (generic) → CMSIS-DAP, J-Link
29
+ - ESP32 → ESP-Prog, J-Link
30
+ - RISC-V → J-Link, OpenOCD compatible probes
31
+ """
32
+ )
33
+
34
+ ds_mcu_instructions = (
35
+ """
36
+ Extract information systematically:
37
+
38
+ TARGET MCU:
39
+ - Identify the MCU from schematic
40
+ - Note the architecture (ARM Cortex-M4, AVR, etc.)
41
+ """
42
+ )
43
+
44
+ ds_debug_int_instructions = (
45
+ """
46
+ Extract information systematically:
47
+
48
+ DEBUG INTERFACE:
49
+ - interface_type: JTAG, SWD, ICSP, PDI, UPDI, etc.
50
+ - pin_count: Number of pins on debug connector
51
+ - pin_mapping: Map pin numbers to signals
52
+ Example: {"1": "VCC", "2": "SWDIO", "3": "GND", "4": "SWCLK"}
53
+ - connector_type: Physical connector (10-pin Cortex, 20-pin JTAG, etc.)
54
+ - voltage_level: Target voltage (typically 3.3V or 5V)
55
+ """
56
+ )
57
+
58
+ ds_recommended_tool_instructions = (
59
+ """
60
+ Extract information systematically:
61
+
62
+ RECOMMENDED TOOL:
63
+ - name: Primary tool recommendation (e.g., "J-Link", "ST-Link V3")
64
+ - model: Specific model if applicable
65
+ - purchase_links: Where to buy (Digikey, Mouser, official sites)
66
+ - compatibility_notes: Why this tool is recommended
67
+ - alternative_tools: Other compatible options
68
+ """
69
+ )
70
+
71
+ ds_drivers_instructions = (
72
+ """
73
+ Extract information systematically:
74
+
75
+ 1. DRIVERS (Windows):
76
+ - download_url: Where to download drivers
77
+ - installation_steps: Step-by-step installation for Windows
78
+ Example:
79
+ 1. Download from [URL]
80
+ 2. Run installer
81
+ 3. Connect debugger
82
+ 4. Verify in Device Manager
83
+
84
+ 2. DRIVERS (Linux):
85
+ - package_name: Package to install (if available)
86
+ - installation_steps: Commands to install
87
+ Example for J-Link:
88
+ 1. Download from segger.com
89
+ 2. sudo dpkg -i JLink_*.deb
90
+ 3. Add udev rules
91
+ 4. sudo usermod -a -G plugdev $USER
92
+
93
+ 3. GDB SERVER:
94
+ - server_name: GDB server to use (JLinkGDBServer, OpenOCD, pyOCD, etc.)
95
+ - command_line: How to start server
96
+ Example: "JLinkGDBServer -device STM32F407VG -if SWD -speed 4000"
97
+ - config_file: Configuration if needed (OpenOCD .cfg)
98
+ - port: GDB port (default 3333)
99
+ """
100
+ )
101
+
102
+ ds_vscode_launch_instructions = (
103
+ """
104
+ Extract information systematically:
105
+
106
+ VS CODE LAUNCH.JSON:
107
+ - configuration_name: Name for the config (e.g., "Debug with J-Link")
108
+ - configuration_type: cortex-debug, cppdbg, etc.
109
+ - launch_json: Complete configuration object for .vscode/launch.json
110
+ - extensions_required: VS Code extensions needed
111
+ Common: "marus25.cortex-debug", "ms-vscode.cpptools"
112
+ - setup_notes: Any additional VS Code setup
113
+ """
114
+ )
115
+
116
+ ds_add_tools_instructions = (
117
+ """
118
+ Extract information systematically:
119
+
120
+ ADDITIONAL TOOLS:
121
+ - List helpful tools: OpenOCD, pyOCD, gdb-multiarch, etc.
122
+ """
123
+ )
124
+
125
+ ds_troubleshooting_instructions = (
126
+ """
127
+ Extract information systematically:
128
+
129
+ TROUBLESHOOTING:
130
+ - Common issues and solutions
131
+ - Connection problems
132
+ - Driver issues
133
+ - Permission issues (Linux)
134
+ """
135
+ )
136
+
137
+ ds_quick_start_guide_instructions = (
138
+ """
139
+ Extract information systematically:
140
+
141
+ QUICK START GUIDE:
142
+ Step-by-step instructions to get debugging working:
143
+ 1. Install drivers
144
+ 2. Connect debugger to target
145
+ 3. Install VS Code extensions
146
+ 4. Create/copy launch.json
147
+ 5. Build project
148
+ 6. Start debugging (F5)
149
+ """
150
+ )