mfcli 0.2.0__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.
- mfcli/.env.example +72 -0
- mfcli/__init__.py +0 -0
- mfcli/agents/__init__.py +0 -0
- mfcli/agents/controller/__init__.py +0 -0
- mfcli/agents/controller/agent.py +19 -0
- mfcli/agents/controller/config.yaml +27 -0
- mfcli/agents/controller/tools.py +42 -0
- mfcli/agents/tools/general.py +118 -0
- mfcli/alembic/env.py +61 -0
- mfcli/alembic/script.py.mako +28 -0
- mfcli/alembic/versions/6ccc0c7c397c_added_fields_to_pdf_parts_model.py +39 -0
- mfcli/alembic/versions/769019ef4870_added_gemini_file_path_to_pdf_part_model.py +33 -0
- mfcli/alembic/versions/7a2e3a779fdc_added_functional_block_and_component_.py +54 -0
- mfcli/alembic/versions/7d5adb2a47a7_added_pdf_parts_model.py +41 -0
- mfcli/alembic/versions/7fcb7d6a5836_init.py +167 -0
- mfcli/alembic/versions/e0f2b5765c72_added_cascade_delete_for_models_that_.py +32 -0
- mfcli/alembic.ini +147 -0
- mfcli/cli/__init__.py +0 -0
- mfcli/cli/dependencies.py +59 -0
- mfcli/cli/main.py +192 -0
- mfcli/client/__init__.py +0 -0
- mfcli/client/chroma_db.py +184 -0
- mfcli/client/docling.py +44 -0
- mfcli/client/gemini.py +252 -0
- mfcli/client/llama_parse.py +38 -0
- mfcli/client/vector_db.py +93 -0
- mfcli/constants/__init__.py +0 -0
- mfcli/constants/base_enum.py +18 -0
- mfcli/constants/directory_names.py +1 -0
- mfcli/constants/file_types.py +189 -0
- mfcli/constants/gemini.py +1 -0
- mfcli/constants/openai.py +6 -0
- mfcli/constants/pipeline_run_status.py +3 -0
- mfcli/crud/__init__.py +0 -0
- mfcli/crud/file.py +42 -0
- mfcli/crud/functional_blocks.py +26 -0
- mfcli/crud/netlist.py +18 -0
- mfcli/crud/pipeline_run.py +17 -0
- mfcli/crud/project.py +99 -0
- mfcli/digikey/__init__.py +0 -0
- mfcli/digikey/digikey.py +105 -0
- mfcli/main.py +5 -0
- mfcli/mcp/__init__.py +0 -0
- mfcli/mcp/configs/cline_mcp_settings.json +11 -0
- mfcli/mcp/configs/mfcli.mcp.json +7 -0
- mfcli/mcp/mcp_instance.py +6 -0
- mfcli/mcp/server.py +37 -0
- mfcli/mcp/state_manager.py +51 -0
- mfcli/mcp/tools/__init__.py +0 -0
- mfcli/mcp/tools/query_knowledgebase.py +108 -0
- mfcli/models/__init__.py +10 -0
- mfcli/models/base.py +10 -0
- mfcli/models/bom.py +71 -0
- mfcli/models/datasheet.py +10 -0
- mfcli/models/debug_setup.py +64 -0
- mfcli/models/file.py +43 -0
- mfcli/models/file_docket.py +94 -0
- mfcli/models/file_metadata.py +19 -0
- mfcli/models/functional_blocks.py +94 -0
- mfcli/models/llm_response.py +5 -0
- mfcli/models/mcu.py +97 -0
- mfcli/models/mcu_errata.py +26 -0
- mfcli/models/netlist.py +59 -0
- mfcli/models/pdf_parts.py +25 -0
- mfcli/models/pipeline_run.py +34 -0
- mfcli/models/project.py +27 -0
- mfcli/models/project_metadata.py +15 -0
- mfcli/pipeline/__init__.py +0 -0
- mfcli/pipeline/analysis/__init__.py +0 -0
- mfcli/pipeline/analysis/bom_netlist_mapper.py +28 -0
- mfcli/pipeline/analysis/generators/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/bom/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/bom/bom.py +74 -0
- mfcli/pipeline/analysis/generators/debug_setup/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/debug_setup/debug_setup.py +71 -0
- mfcli/pipeline/analysis/generators/debug_setup/instructions.py +150 -0
- mfcli/pipeline/analysis/generators/functional_blocks/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/functional_blocks/functional_blocks.py +93 -0
- mfcli/pipeline/analysis/generators/functional_blocks/instructions.py +34 -0
- mfcli/pipeline/analysis/generators/functional_blocks/validator.py +94 -0
- mfcli/pipeline/analysis/generators/generator.py +258 -0
- mfcli/pipeline/analysis/generators/generator_base.py +18 -0
- mfcli/pipeline/analysis/generators/mcu/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/mcu/instructions.py +156 -0
- mfcli/pipeline/analysis/generators/mcu/mcu.py +84 -0
- mfcli/pipeline/analysis/generators/mcu_errata/__init__.py +1 -0
- mfcli/pipeline/analysis/generators/mcu_errata/instructions.py +77 -0
- mfcli/pipeline/analysis/generators/mcu_errata/mcu_errata.py +95 -0
- mfcli/pipeline/analysis/generators/summary/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/summary/summary.py +47 -0
- mfcli/pipeline/classifier.py +93 -0
- mfcli/pipeline/data_enricher.py +15 -0
- mfcli/pipeline/extractor.py +34 -0
- mfcli/pipeline/extractors/__init__.py +0 -0
- mfcli/pipeline/extractors/pdf.py +12 -0
- mfcli/pipeline/parser.py +120 -0
- mfcli/pipeline/parsers/__init__.py +0 -0
- mfcli/pipeline/parsers/netlist/__init__.py +0 -0
- mfcli/pipeline/parsers/netlist/edif.py +93 -0
- mfcli/pipeline/parsers/netlist/kicad_legacy_net.py +326 -0
- mfcli/pipeline/parsers/netlist/kicad_spice.py +135 -0
- mfcli/pipeline/parsers/netlist/pads.py +185 -0
- mfcli/pipeline/parsers/netlist/protel.py +166 -0
- mfcli/pipeline/parsers/netlist/protel_detector.py +29 -0
- mfcli/pipeline/pipeline.py +419 -0
- mfcli/pipeline/preprocessors/__init__.py +0 -0
- mfcli/pipeline/preprocessors/user_guide.py +127 -0
- mfcli/pipeline/run_context.py +32 -0
- mfcli/pipeline/schema_mapper.py +89 -0
- mfcli/pipeline/sub_classifier.py +115 -0
- mfcli/utils/__init__.py +0 -0
- mfcli/utils/config.py +33 -0
- mfcli/utils/configurator.py +324 -0
- mfcli/utils/data_cleaner.py +82 -0
- mfcli/utils/datasheet_vectorizer.py +281 -0
- mfcli/utils/directory_manager.py +96 -0
- mfcli/utils/file_upload.py +298 -0
- mfcli/utils/files.py +16 -0
- mfcli/utils/http_requests.py +54 -0
- mfcli/utils/kb_lister.py +89 -0
- mfcli/utils/kb_remover.py +173 -0
- mfcli/utils/logger.py +28 -0
- mfcli/utils/mcp_configurator.py +311 -0
- mfcli/utils/migrations.py +18 -0
- mfcli/utils/orm.py +43 -0
- mfcli/utils/pdf_splitter.py +63 -0
- mfcli/utils/query_service.py +22 -0
- mfcli/utils/system_check.py +306 -0
- mfcli/utils/tools.py +31 -0
- mfcli/utils/vectorizer.py +28 -0
- mfcli-0.2.0.dist-info/METADATA +841 -0
- mfcli-0.2.0.dist-info/RECORD +136 -0
- mfcli-0.2.0.dist-info/WHEEL +5 -0
- mfcli-0.2.0.dist-info/entry_points.txt +3 -0
- mfcli-0.2.0.dist-info/licenses/LICENSE +21 -0
- mfcli-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import chromadb
|
|
2
|
+
from chromadb.utils import embedding_functions
|
|
3
|
+
from typing import Annotated, Optional
|
|
4
|
+
|
|
5
|
+
from mfcli.crud.project import get_project_by_name
|
|
6
|
+
from mfcli.mcp.mcp_instance import mcp
|
|
7
|
+
from mfcli.mcp.state_manager import state_manager
|
|
8
|
+
from mfcli.utils.config import get_config
|
|
9
|
+
from mfcli.utils.directory_manager import app_dirs
|
|
10
|
+
from mfcli.utils.orm import Session
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_chroma_index(index_name: str):
|
|
14
|
+
config = get_config()
|
|
15
|
+
chroma_client = chromadb.PersistentClient(path=app_dirs.chroma_db_dir)
|
|
16
|
+
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
|
|
17
|
+
api_key=config.openai_api_key,
|
|
18
|
+
model_name=config.embedding_model
|
|
19
|
+
)
|
|
20
|
+
return chroma_client.get_collection(index_name, embedding_function=openai_ef)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_index_id_for_project_name(project_name: str):
|
|
24
|
+
with Session() as db:
|
|
25
|
+
project = get_project_by_name(db, project_name)
|
|
26
|
+
return project.index_id
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@mcp.tool()
|
|
30
|
+
def query_local_rag(
|
|
31
|
+
query: Annotated[str, "Search query for engineering documentation (e.g., 'IEC 61000-4-2', 'power management')"],
|
|
32
|
+
project_name: Annotated[Optional[str], "The name of the project. RECOMMENDED: Provide this parameter even though it's optional. The project name can be found in the config.json file located in the .multifactor folder. If not provided, uses the last known project name."] = None,
|
|
33
|
+
n_results: Annotated[int, "Number of results to return (1-20, default: 8)"] = 8,
|
|
34
|
+
task_progress: Annotated[Optional[str], "A checklist showing task progress after this tool use is completed"] = None
|
|
35
|
+
) -> dict:
|
|
36
|
+
"""Query local hardware engineering documentation.
|
|
37
|
+
|
|
38
|
+
Use this tool to search for engineering documentation, design patterns,
|
|
39
|
+
technical specifications, and best practices from the local knowledge base.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
query: Search query for engineering documentation (e.g., "IEC 61000-4-2", "power management")
|
|
43
|
+
project_name: RECOMMENDED - The name of the project. Although optional, it is recommended to provide this parameter.
|
|
44
|
+
The project name can be found in the config.json file located in the .multifactor folder.
|
|
45
|
+
If not provided, uses the last known project name.
|
|
46
|
+
n_results: Number of results to return (1-20, default: 8)
|
|
47
|
+
task_progress: A checklist showing task progress after this tool use is completed
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Dictionary containing:
|
|
51
|
+
- documents: List of text chunks matching the query
|
|
52
|
+
- metadatas: Metadata for each document
|
|
53
|
+
- distances: Similarity distances (lower is more similar)
|
|
54
|
+
- count: Number of results returned
|
|
55
|
+
- chromadb_path: Path to the ChromaDB database
|
|
56
|
+
- project_name: The project name that was used for the query
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
# If project_name is not provided, use the last known project name
|
|
60
|
+
if project_name is None:
|
|
61
|
+
project_name = state_manager.get_last_project_name()
|
|
62
|
+
if project_name is None:
|
|
63
|
+
return {
|
|
64
|
+
"error": "No project name provided and no previous project name found. Please provide a project_name parameter. The project name can be found in the config.json file located in the .multifactor folder.",
|
|
65
|
+
"documents": [],
|
|
66
|
+
"metadatas": [],
|
|
67
|
+
"distances": [],
|
|
68
|
+
"count": 0,
|
|
69
|
+
"chromadb_path": app_dirs.chroma_db_dir,
|
|
70
|
+
"project_name": None
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Store the project name for future use
|
|
74
|
+
state_manager.set_last_project_name(project_name)
|
|
75
|
+
|
|
76
|
+
collection = get_chroma_index(get_index_id_for_project_name(project_name))
|
|
77
|
+
|
|
78
|
+
# Clamp n_results between 1 and 20
|
|
79
|
+
n_results = min(max(n_results, 1), 20)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
results = collection.query(
|
|
83
|
+
query_texts=[query],
|
|
84
|
+
n_results=n_results
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
documents = results["documents"][0] if results["documents"] else []
|
|
88
|
+
metadatas = results["metadatas"][0] if results["metadatas"] else []
|
|
89
|
+
distances = results["distances"][0] if results["distances"] else []
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
"documents": documents,
|
|
93
|
+
"metadatas": metadatas,
|
|
94
|
+
"distances": distances,
|
|
95
|
+
"count": len(documents),
|
|
96
|
+
"chromadb_path": app_dirs.chroma_db_dir,
|
|
97
|
+
"project_name": project_name
|
|
98
|
+
}
|
|
99
|
+
except Exception as e:
|
|
100
|
+
return {
|
|
101
|
+
"error": str(e),
|
|
102
|
+
"documents": [],
|
|
103
|
+
"metadatas": [],
|
|
104
|
+
"distances": [],
|
|
105
|
+
"count": 0,
|
|
106
|
+
"chromadb_path": app_dirs.chroma_db_dir,
|
|
107
|
+
"project_name": project_name
|
|
108
|
+
}
|
mfcli/models/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from mfcli.models.file import File
|
|
2
|
+
from mfcli.models.pdf_parts import PDFPart
|
|
3
|
+
from mfcli.models.pipeline_run import PipelineRun
|
|
4
|
+
from mfcli.models.bom import BOM
|
|
5
|
+
from mfcli.models.datasheet import Datasheet
|
|
6
|
+
from mfcli.models.netlist import Netlist
|
|
7
|
+
from mfcli.models.netlist import NetlistPin
|
|
8
|
+
from mfcli.models.netlist import NetlistComponent
|
|
9
|
+
from mfcli.models.project import Project
|
|
10
|
+
from mfcli.models.functional_blocks import FunctionalBlock, FunctionalBlockComponent
|
mfcli/models/base.py
ADDED
mfcli/models/bom.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from sqlalchemy import Column, func, TIMESTAMP
|
|
5
|
+
|
|
6
|
+
from mfcli.models.file import File
|
|
7
|
+
|
|
8
|
+
from sqlmodel import SQLModel, Field, Relationship
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BOMSchema(BaseModel):
|
|
13
|
+
# Required core fields
|
|
14
|
+
reference: str = Field(..., description="Reference designators (C1, R3, U2, etc.)")
|
|
15
|
+
value: str = Field(..., description="Electrical value or part name (10kΩ, MSPM0L1306)")
|
|
16
|
+
quantity: int = Field(..., description="Quantity of identical components")
|
|
17
|
+
description: str = Field(..., description="General text describing component")
|
|
18
|
+
|
|
19
|
+
# Optional fields
|
|
20
|
+
manufacturer: Optional[str] = Field(None, description="Manufacturer name")
|
|
21
|
+
mpn: Optional[str] = Field(None, description="Manufacturer part number")
|
|
22
|
+
lifecycle_status: Optional[str] = Field(None, description="Maturity (Active / NRND / Obsolete)")
|
|
23
|
+
supplier: Optional[str] = Field(None, description="Distributor / supplier")
|
|
24
|
+
supplier_part: Optional[str] = Field(None, description="Supplier SKU or catalog number")
|
|
25
|
+
supplier_unit_price: Optional[float] = Field(None, description="Unit cost")
|
|
26
|
+
supplier_subtotal: Optional[float] = Field(None, description="Quantity × unit cost")
|
|
27
|
+
revision_id: Optional[str] = Field(None, description="Design revision identifier")
|
|
28
|
+
revision_state: Optional[str] = Field(None, description="Engineering state (Released / Draft)")
|
|
29
|
+
revision_status: Optional[str] = Field(None, description="Workflow status (Approved / Pending)")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BOM(SQLModel, table=True):
|
|
33
|
+
__tablename__ = "bom"
|
|
34
|
+
|
|
35
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
36
|
+
file_id: int = Field(
|
|
37
|
+
foreign_key="files.id",
|
|
38
|
+
index=True,
|
|
39
|
+
nullable=False,
|
|
40
|
+
ondelete="CASCADE"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Relationship
|
|
44
|
+
file: Optional["File"] = Relationship()
|
|
45
|
+
|
|
46
|
+
# Required core fields
|
|
47
|
+
reference: str = Field(max_length=100, nullable=False)
|
|
48
|
+
value: str = Field(max_length=255, nullable=False)
|
|
49
|
+
quantity: int = Field(nullable=False)
|
|
50
|
+
description: str = Field(max_length=600, nullable=False)
|
|
51
|
+
|
|
52
|
+
# Optional fields
|
|
53
|
+
manufacturer: Optional[str] = Field(default=None, max_length=255)
|
|
54
|
+
mpn: Optional[str] = Field(default=None, max_length=255)
|
|
55
|
+
lifecycle_status: Optional[str] = Field(default=None, max_length=50)
|
|
56
|
+
supplier: Optional[str] = Field(default=None, max_length=255)
|
|
57
|
+
supplier_part: Optional[str] = Field(default=None, max_length=255)
|
|
58
|
+
supplier_unit_price: Optional[float] = Field(default=None)
|
|
59
|
+
supplier_subtotal: Optional[float] = Field(default=None)
|
|
60
|
+
revision_id: Optional[str] = Field(default=None, max_length=50)
|
|
61
|
+
revision_state: Optional[str] = Field(default=None, max_length=50)
|
|
62
|
+
revision_status: Optional[str] = Field(default=None, max_length=100)
|
|
63
|
+
datasheet: Optional[str] = Field(default=None, nullable=True, max_length=500)
|
|
64
|
+
|
|
65
|
+
# Timestamps
|
|
66
|
+
created_at: datetime = Field(
|
|
67
|
+
sa_column=Column(TIMESTAMP(timezone=True), server_default=func.now(), nullable=False)
|
|
68
|
+
)
|
|
69
|
+
updated_at: datetime = Field(
|
|
70
|
+
sa_column=Column(TIMESTAMP(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
71
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from sqlmodel import SQLModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Datasheet(SQLModel, table=True):
|
|
7
|
+
__tablename__ = "datasheets"
|
|
8
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
9
|
+
part_number: str = Field(index=True, nullable=False, max_length=100, unique=False)
|
|
10
|
+
datasheet: str = Field(index=True, nullable=False, max_length=500, unique=False)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import Dict, List, Any
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DebugInterface(BaseModel):
|
|
7
|
+
"""Debug interface configuration from schematic."""
|
|
8
|
+
interface_type: str = Field(..., description="Debug interface type: JTAG, SWD, ICSP, etc.")
|
|
9
|
+
pin_count: int = Field(0, description="Number of pins in debug connector")
|
|
10
|
+
pin_mapping: Dict[str, str] = Field(default_factory=dict,
|
|
11
|
+
description="Pin number to signal mapping (e.g., {'1': 'VCC', '2': 'SWDIO'})")
|
|
12
|
+
connector_type: str = Field("", description="Physical connector type (e.g., 10-pin Cortex, 20-pin JTAG)")
|
|
13
|
+
voltage_level: str = Field("", description="Target voltage level (3.3V, 5V, etc.)")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DebugTool(BaseModel):
|
|
17
|
+
"""Recommended debug tool/probe."""
|
|
18
|
+
name: str = Field(..., description="Tool name (e.g., J-Link, ST-Link, CMSIS-DAP)")
|
|
19
|
+
model: str = Field("", description="Specific model recommendation")
|
|
20
|
+
purchase_links: List[str] = Field(default_factory=list, description="Where to buy")
|
|
21
|
+
compatibility_notes: str = Field("", description="Why this tool is recommended")
|
|
22
|
+
alternative_tools: List[str] = Field(default_factory=list, description="Alternative compatible tools")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DriverInfo(BaseModel):
|
|
26
|
+
"""Driver installation information."""
|
|
27
|
+
windows: Dict[str, Any] = Field(default_factory=dict,
|
|
28
|
+
description="Windows driver info: download_url, installation_steps")
|
|
29
|
+
linux: Dict[str, Any] = Field(default_factory=dict,
|
|
30
|
+
description="Linux driver info: package_name, installation_steps")
|
|
31
|
+
macos: Dict[str, Any] = Field(default_factory=dict, description="macOS driver info (optional)")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class VSCodeLaunchConfig(BaseModel):
|
|
35
|
+
"""VS Code launch.json configuration."""
|
|
36
|
+
configuration_name: str = Field(..., description="Name for this configuration")
|
|
37
|
+
configuration_type: str = Field(..., description="Debugger type: cortex-debug, cppdbg, etc.")
|
|
38
|
+
launch_json: Dict[str, Any] = Field(default_factory=dict, description="Complete launch.json configuration")
|
|
39
|
+
extensions_required: List[str] = Field(default_factory=list, description="Required VS Code extensions")
|
|
40
|
+
setup_notes: str = Field("", description="Additional setup notes")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class GDBServerConfig(BaseModel):
|
|
44
|
+
"""GDB server configuration."""
|
|
45
|
+
server_name: str = Field(..., description="GDB server name (e.g., JLinkGDBServer, OpenOCD)")
|
|
46
|
+
command_line: str = Field("", description="Command to start GDB server")
|
|
47
|
+
config_file: str = Field("", description="Configuration file content or path")
|
|
48
|
+
port: int = Field(3333, description="GDB server port")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DSTargetMCU(BaseModel):
|
|
52
|
+
target_mcu: str = Field(..., description="Target MCU from schematic")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class DSAdditionalTools(BaseModel):
|
|
56
|
+
additional_tools: List[str] = Field(default_factory=list, description="Additional helpful tools")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DSTroubleshootingGuide(BaseModel):
|
|
60
|
+
troubleshooting: List[str] = Field(default_factory=list, description="Common issues and solutions")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class DSQuickStartGuide(BaseModel):
|
|
64
|
+
quick_start_guide: List[str] = Field(default_factory=list, description="Step-by-step quick start")
|
mfcli/models/file.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional, List
|
|
3
|
+
|
|
4
|
+
from sqlalchemy import func, UniqueConstraint, Column, DateTime
|
|
5
|
+
from sqlmodel import SQLModel, Field, Relationship
|
|
6
|
+
|
|
7
|
+
from mfcli.models.pipeline_run import PipelineRun
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class File(SQLModel, table=True):
|
|
11
|
+
__tablename__ = "files"
|
|
12
|
+
|
|
13
|
+
__table_args__ = (
|
|
14
|
+
UniqueConstraint("md5", "pipeline_run_id", name="uq_file_md5_pipeline_run"),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
18
|
+
pipeline_run_id: int = Field(
|
|
19
|
+
foreign_key="pipeline_runs.id",
|
|
20
|
+
index=True,
|
|
21
|
+
nullable=False,
|
|
22
|
+
ondelete="CASCADE"
|
|
23
|
+
)
|
|
24
|
+
pipeline_run: Optional["PipelineRun"] = Relationship()
|
|
25
|
+
pdf_parts: List["PDFPart"] = Relationship(back_populates='file', cascade_delete=True)
|
|
26
|
+
|
|
27
|
+
name: str = Field(max_length=255, nullable=False, index=True)
|
|
28
|
+
type: int = Field(nullable=False, index=True)
|
|
29
|
+
sub_type: Optional[int] = Field(default=None, index=True)
|
|
30
|
+
mime_type: str = Field(max_length=255, nullable=False)
|
|
31
|
+
md5: str = Field(max_length=32, nullable=False, index=True)
|
|
32
|
+
ext: str = Field(max_length=10, min_length=2, index=True, nullable=True)
|
|
33
|
+
path: str = Field(max_length=600, nullable=True)
|
|
34
|
+
gemini_file_id: str = Field(max_length=40, nullable=True)
|
|
35
|
+
is_datasheet: int = Field(default=0, nullable=False, index=True)
|
|
36
|
+
|
|
37
|
+
# Database-managed timestamps
|
|
38
|
+
created_at: datetime = Field(
|
|
39
|
+
sa_column=Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
40
|
+
)
|
|
41
|
+
updated_at: datetime = Field(
|
|
42
|
+
sa_column=Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
43
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FileDocketEntry(BaseModel):
|
|
9
|
+
name: str
|
|
10
|
+
path: str
|
|
11
|
+
vectorize: bool
|
|
12
|
+
sub_type: str
|
|
13
|
+
md5: str | None = Field(default=None)
|
|
14
|
+
is_datasheet: bool = Field(default=False)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FileDocketEntries(BaseModel):
|
|
18
|
+
entries: List[FileDocketEntry] = Field(default_factory=list)
|
|
19
|
+
|
|
20
|
+
def add(self, entry: FileDocketEntry):
|
|
21
|
+
self.entries.append(entry)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FileDocket:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self._docket = FileDocketEntries()
|
|
27
|
+
self._md5_entry_map: Dict[str, FileDocketEntry] = {}
|
|
28
|
+
self._path_entry_map: Dict[str, FileDocketEntry] = {}
|
|
29
|
+
|
|
30
|
+
def add(self, entry: FileDocketEntry):
|
|
31
|
+
self._docket.add(entry)
|
|
32
|
+
if entry.md5:
|
|
33
|
+
self._md5_entry_map[entry.md5] = entry
|
|
34
|
+
# Use normalized path as key (convert to lowercase for case-insensitive comparison on Windows)
|
|
35
|
+
normalized_path = Path(entry.path).as_posix().lower()
|
|
36
|
+
self._path_entry_map[normalized_path] = entry
|
|
37
|
+
|
|
38
|
+
def get_by_md5(self, md5: str) -> Optional[FileDocketEntry]:
|
|
39
|
+
return self._md5_entry_map.get(md5)
|
|
40
|
+
|
|
41
|
+
def get_by_path(self, path: str) -> Optional[FileDocketEntry]:
|
|
42
|
+
normalized_path = Path(path).as_posix().lower()
|
|
43
|
+
return self._path_entry_map.get(normalized_path)
|
|
44
|
+
|
|
45
|
+
def remove(self, entry: FileDocketEntry):
|
|
46
|
+
"""Remove an entry from the docket"""
|
|
47
|
+
if entry in self._docket.entries:
|
|
48
|
+
self._docket.entries.remove(entry)
|
|
49
|
+
if entry.md5 and entry.md5 in self._md5_entry_map:
|
|
50
|
+
del self._md5_entry_map[entry.md5]
|
|
51
|
+
normalized_path = Path(entry.path).as_posix().lower()
|
|
52
|
+
if normalized_path in self._path_entry_map:
|
|
53
|
+
del self._path_entry_map[normalized_path]
|
|
54
|
+
|
|
55
|
+
def load_from_json(self, json_path: Path):
|
|
56
|
+
"""Load existing file docket from JSON file"""
|
|
57
|
+
if not json_path.exists():
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
with open(json_path, 'r') as f:
|
|
62
|
+
data = json.load(f)
|
|
63
|
+
|
|
64
|
+
# Convert the saved format back to entries
|
|
65
|
+
all_entries = []
|
|
66
|
+
for category in ['hw_files', 'datasheets', 'cheat_sheets']:
|
|
67
|
+
if category in data:
|
|
68
|
+
all_entries.extend(data[category])
|
|
69
|
+
|
|
70
|
+
# Create FileDocketEntry objects and add them
|
|
71
|
+
for entry_data in all_entries:
|
|
72
|
+
entry = FileDocketEntry(**entry_data)
|
|
73
|
+
self.add(entry)
|
|
74
|
+
|
|
75
|
+
except Exception as e:
|
|
76
|
+
# If there's an error loading, just start fresh
|
|
77
|
+
print(f"Warning: Could not load existing file_docket.json: {e}")
|
|
78
|
+
|
|
79
|
+
def get_entries(self) -> Dict:
|
|
80
|
+
datasheets = []
|
|
81
|
+
cheat_sheets = []
|
|
82
|
+
hw_files = []
|
|
83
|
+
for entry in self._docket.entries:
|
|
84
|
+
if entry.is_datasheet:
|
|
85
|
+
datasheets.append(entry.model_dump())
|
|
86
|
+
elif entry.sub_type == "CHEAT_SHEET":
|
|
87
|
+
cheat_sheets.append(entry.model_dump())
|
|
88
|
+
else:
|
|
89
|
+
hw_files.append(entry.model_dump())
|
|
90
|
+
return {
|
|
91
|
+
"hw_files": hw_files,
|
|
92
|
+
"datasheets": datasheets,
|
|
93
|
+
"cheat_sheets": cheat_sheets
|
|
94
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from mfcli.constants.file_types import FileSubtypes, FileTypes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FileMetadata(BaseModel):
|
|
9
|
+
name: str
|
|
10
|
+
size: int
|
|
11
|
+
md5: str
|
|
12
|
+
path: str
|
|
13
|
+
mime: str
|
|
14
|
+
ext: str
|
|
15
|
+
type_id: FileTypes
|
|
16
|
+
type_name: str
|
|
17
|
+
subtype_id: Optional[FileSubtypes] = Field(None)
|
|
18
|
+
subtype_name: Optional[str] = Field(None)
|
|
19
|
+
is_datasheet: bool
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from sqlmodel import SQLModel, Field as DBField, Relationship
|
|
5
|
+
|
|
6
|
+
from mfcli.models.pipeline_run import PipelineRun
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# DB Models
|
|
10
|
+
|
|
11
|
+
class FunctionalBlock(SQLModel, table=True):
|
|
12
|
+
__tablename__ = 'functional_blocks'
|
|
13
|
+
id: Optional[int] = DBField(default=None, primary_key=True)
|
|
14
|
+
pipeline_run_id: int = DBField(
|
|
15
|
+
foreign_key="pipeline_runs.id",
|
|
16
|
+
index=True,
|
|
17
|
+
nullable=False,
|
|
18
|
+
ondelete="CASCADE"
|
|
19
|
+
)
|
|
20
|
+
pipeline_run: Optional["PipelineRun"] = Relationship()
|
|
21
|
+
name: str = DBField(max_length=255, nullable=False, index=True)
|
|
22
|
+
description: str = DBField(nullable=False)
|
|
23
|
+
components: List["FunctionalBlockComponent"] = Relationship(back_populates='functional_block', cascade_delete=True)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FunctionalBlockComponent(SQLModel, table=True):
|
|
27
|
+
__tablename__ = 'functional_block_components'
|
|
28
|
+
id: Optional[int] = DBField(default=None, primary_key=True)
|
|
29
|
+
functional_block_id: int = DBField(
|
|
30
|
+
foreign_key="functional_blocks.id",
|
|
31
|
+
index=True,
|
|
32
|
+
nullable=False,
|
|
33
|
+
ondelete="CASCADE"
|
|
34
|
+
)
|
|
35
|
+
functional_block: Optional["FunctionalBlock"] = Relationship(back_populates='components')
|
|
36
|
+
ref: str = DBField(max_length=45, nullable=False)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class FunctionalBlocks(BaseModel):
|
|
40
|
+
functional_blocks: list[str] = Field(..., description="Names of functional blocks")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Pydantic models
|
|
44
|
+
|
|
45
|
+
class PinInfo(BaseModel):
|
|
46
|
+
"""Represents pin configuration information."""
|
|
47
|
+
pin_number: str = Field(..., description="Pin number or identifier")
|
|
48
|
+
pin_name: str = Field(..., description="Pin name or signal name")
|
|
49
|
+
direction: str = Field("", description="Pin direction: input, output, bidirectional, power, ground")
|
|
50
|
+
pull_config: str = Field("", description="Pull-up/pull-down configuration: pull-up, pull-down, none, external")
|
|
51
|
+
voltage_level: str = Field("", description="Voltage level (e.g., 3.3V, 5V, 1.8V)")
|
|
52
|
+
function: str = Field("", description="Pin function description")
|
|
53
|
+
connected_to: str = Field("", description="What this pin connects to")
|
|
54
|
+
alternate_functions: List[str] = Field(default_factory=list, description="Alternative pin functions")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ConnectorInfo(BaseModel):
|
|
58
|
+
"""Represents connector information."""
|
|
59
|
+
connector_ref: str = Field(..., description="Connector reference designator (e.g., J1, P2)")
|
|
60
|
+
connector_type: str = Field("", description="Connector type (e.g., USB-C, JST-XH, header)")
|
|
61
|
+
pin_count: int = Field(0, description="Number of pins in connector")
|
|
62
|
+
pins: List[PinInfo] = Field(default_factory=list, description="Pin information for connector")
|
|
63
|
+
description: str = Field("", description="Connector description and purpose")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class FunctionalBlockMeta(BaseModel):
|
|
67
|
+
name: str = Field(..., description="Name of the functional block (e.g., 'Power Supply', 'USB Interface')")
|
|
68
|
+
description: str = Field(..., description="Detailed description of what this block does")
|
|
69
|
+
components: List[str] = Field(default_factory=list, description="Reference designators of components in this block")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class FBPins(BaseModel):
|
|
73
|
+
pins: List[PinInfo] = Field(default_factory=list, description="Pin information for this functional block")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class FBConnectors(BaseModel):
|
|
77
|
+
connectors: List[ConnectorInfo] = Field(default_factory=list, description="Connectors associated with this block")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class FBVoltageRails(BaseModel):
|
|
81
|
+
voltage_rails: List[str] = Field(default_factory=list,
|
|
82
|
+
description="Voltage rails used by this block (e.g., 3.3V, 5V)")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class FBInitializationNotes(BaseModel):
|
|
86
|
+
initialization_notes: str = Field("", description="Notes on how to initialize/configure this block in firmware")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class FBDependencies(BaseModel):
|
|
90
|
+
dependencies: List[str] = Field(default_factory=list, description="Other functional blocks this depends on")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class FBFirmwareRequirements(BaseModel):
|
|
94
|
+
firmware_requirements: str = Field("", description="Key firmware requirements for this block")
|
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")
|