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.
- 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 +200 -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 +144 -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 +470 -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/cline_rules.py +256 -0
- mfcli/utils/config.py +33 -0
- mfcli/utils/configurator.py +324 -0
- mfcli/utils/data_cleaner.py +114 -0
- mfcli/utils/datasheet_vectorizer.py +283 -0
- mfcli/utils/directory_manager.py +116 -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 +394 -0
- mfcli/utils/migrations.py +18 -0
- mfcli/utils/orm.py +43 -0
- mfcli/utils/pdf_splitter.py +63 -0
- mfcli/utils/pre_uninstall.py +167 -0
- mfcli/utils/query_service.py +22 -0
- mfcli/utils/system_check.py +306 -0
- mfcli/utils/tools.py +98 -0
- mfcli/utils/vectorizer.py +28 -0
- mfcli-0.2.1.dist-info/METADATA +956 -0
- mfcli-0.2.1.dist-info/RECORD +138 -0
- mfcli-0.2.1.dist-info/WHEEL +5 -0
- mfcli-0.2.1.dist-info/entry_points.txt +4 -0
- mfcli-0.2.1.dist-info/licenses/LICENSE +21 -0
- mfcli-0.2.1.dist-info/top_level.txt +1 -0
mfcli/utils/files.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
AppDataFileTypes = Literal['datasheets', 'files']
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def file_access_check(file_path: Path) -> bool:
|
|
10
|
+
if not os.path.exists(file_path) or not os.access(file_path, os.R_OK):
|
|
11
|
+
return False
|
|
12
|
+
return True
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_text_mime_type(mime_type: str) -> bool:
|
|
16
|
+
return bool(re.match(r"^text/.+$", mime_type))
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from requests.adapters import HTTPAdapter
|
|
3
|
+
from urllib3.util.retry import Retry
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_retry_session(
|
|
7
|
+
retries: int = 3,
|
|
8
|
+
backoff_factor: float = 0.5,
|
|
9
|
+
status_forcelist: tuple = (500, 502, 503, 504),
|
|
10
|
+
allowed_methods: tuple = ("GET", "POST", "PUT", "DELETE", "PATCH")
|
|
11
|
+
) -> requests.Session:
|
|
12
|
+
"""Create a requests session with retry logic."""
|
|
13
|
+
session = requests.Session()
|
|
14
|
+
retry = Retry(
|
|
15
|
+
total=retries,
|
|
16
|
+
read=retries,
|
|
17
|
+
connect=retries,
|
|
18
|
+
backoff_factor=backoff_factor,
|
|
19
|
+
status_forcelist=status_forcelist,
|
|
20
|
+
allowed_methods=allowed_methods,
|
|
21
|
+
raise_on_status=False,
|
|
22
|
+
)
|
|
23
|
+
adapter = HTTPAdapter(max_retries=retry)
|
|
24
|
+
session.mount("http://", adapter)
|
|
25
|
+
session.mount("https://", adapter)
|
|
26
|
+
return session
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def http_request(
|
|
30
|
+
method: str,
|
|
31
|
+
url: str,
|
|
32
|
+
retries: int = 3,
|
|
33
|
+
timeout: int = 10,
|
|
34
|
+
**kwargs
|
|
35
|
+
) -> requests.Response:
|
|
36
|
+
"""
|
|
37
|
+
Perform an HTTP request with automatic retry logic.
|
|
38
|
+
|
|
39
|
+
:param method: HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
40
|
+
:param url: URL to request
|
|
41
|
+
:param retries: Number of retry attempts
|
|
42
|
+
:param timeout: Timeout per request in seconds
|
|
43
|
+
:param kwargs: Any requests.request() parameters (headers, data, json, etc.)
|
|
44
|
+
:return: requests.Response object
|
|
45
|
+
"""
|
|
46
|
+
session = get_retry_session(retries=retries)
|
|
47
|
+
try:
|
|
48
|
+
response = session.request(method.upper(), url, allow_redirects=True, timeout=timeout, **kwargs)
|
|
49
|
+
response.raise_for_status()
|
|
50
|
+
return response
|
|
51
|
+
except requests.exceptions.RequestException:
|
|
52
|
+
raise
|
|
53
|
+
finally:
|
|
54
|
+
session.close()
|
mfcli/utils/kb_lister.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from typing import Dict, List, Set
|
|
2
|
+
from mfcli.client.chroma_db import get_chromadb_client_for_project_name
|
|
3
|
+
from mfcli.utils.logger import get_logger
|
|
4
|
+
from mfcli.utils.orm import Session
|
|
5
|
+
|
|
6
|
+
logger = get_logger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def list_vectorized_files(project_name: str) -> Dict[str, List[str]]:
|
|
10
|
+
"""
|
|
11
|
+
List all files that have been vectorized into the ChromaDB database for a project.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
project_name: Name of the project
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Dictionary mapping purpose to list of file names
|
|
18
|
+
"""
|
|
19
|
+
try:
|
|
20
|
+
with Session() as db:
|
|
21
|
+
chroma_client = get_chromadb_client_for_project_name(db, project_name)
|
|
22
|
+
|
|
23
|
+
# Get all documents from the collection
|
|
24
|
+
collection = chroma_client._collection
|
|
25
|
+
results = collection.get()
|
|
26
|
+
|
|
27
|
+
if not results or not results.get('metadatas'):
|
|
28
|
+
logger.info("No vectorized files found in the knowledge base")
|
|
29
|
+
return {}
|
|
30
|
+
|
|
31
|
+
# Group files by purpose
|
|
32
|
+
files_by_purpose: Dict[str, Set[str]] = {}
|
|
33
|
+
|
|
34
|
+
for metadata in results['metadatas']:
|
|
35
|
+
if metadata:
|
|
36
|
+
file_name = metadata.get('file_name', 'Unknown')
|
|
37
|
+
purpose = metadata.get('purpose', 'unknown')
|
|
38
|
+
|
|
39
|
+
if purpose not in files_by_purpose:
|
|
40
|
+
files_by_purpose[purpose] = set()
|
|
41
|
+
files_by_purpose[purpose].add(file_name)
|
|
42
|
+
|
|
43
|
+
# Convert sets to sorted lists for consistent output
|
|
44
|
+
result = {
|
|
45
|
+
purpose: sorted(list(files))
|
|
46
|
+
for purpose, files in files_by_purpose.items()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logger.error(f"Failed to list vectorized files for project: {project_name}")
|
|
53
|
+
logger.exception(e)
|
|
54
|
+
raise
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def print_vectorized_files(project_name: str) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Print all vectorized files in a formatted manner.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
project_name: Name of the project
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
files_by_purpose = list_vectorized_files(project_name)
|
|
66
|
+
|
|
67
|
+
if not files_by_purpose:
|
|
68
|
+
print(f"\nNo files have been vectorized for project '{project_name}' yet.")
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
print(f"\nVectorized files in knowledge base for project '{project_name}':")
|
|
72
|
+
print("=" * 70)
|
|
73
|
+
|
|
74
|
+
total_files = 0
|
|
75
|
+
for purpose in sorted(files_by_purpose.keys()):
|
|
76
|
+
files = files_by_purpose[purpose]
|
|
77
|
+
print(f"\n{purpose.upper()} ({len(files)} file{'s' if len(files) != 1 else ''}):")
|
|
78
|
+
print("-" * 70)
|
|
79
|
+
for file_name in files:
|
|
80
|
+
print(f" • {file_name}")
|
|
81
|
+
total_files += len(files)
|
|
82
|
+
|
|
83
|
+
print("\n" + "=" * 70)
|
|
84
|
+
print(f"Total: {total_files} unique file{'s' if total_files != 1 else ''} vectorized\n")
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Failed to print vectorized files for project: {project_name}")
|
|
88
|
+
print(f"\nError: Failed to list vectorized files. Check logs for details.")
|
|
89
|
+
raise
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List
|
|
4
|
+
from mfcli.client.chroma_db import get_chromadb_client_for_project_name
|
|
5
|
+
from mfcli.models.file_docket import FileDocket
|
|
6
|
+
from mfcli.utils.directory_manager import app_dirs
|
|
7
|
+
from mfcli.utils.logger import get_logger
|
|
8
|
+
from mfcli.utils.orm import Session
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _remove_from_file_docket(matching_files: set) -> int:
|
|
14
|
+
"""
|
|
15
|
+
Remove files from the file_docket.json based on file names.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
matching_files: Set of file names to remove
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Number of entries removed from docket
|
|
22
|
+
"""
|
|
23
|
+
if not app_dirs.file_docket_path or not app_dirs.file_docket_path.exists():
|
|
24
|
+
logger.warning("file_docket.json not found, skipping docket removal")
|
|
25
|
+
return 0
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
# Load existing file docket
|
|
29
|
+
docket = FileDocket()
|
|
30
|
+
docket.load_from_json(app_dirs.file_docket_path)
|
|
31
|
+
|
|
32
|
+
# Track how many entries we remove
|
|
33
|
+
removed_count = 0
|
|
34
|
+
|
|
35
|
+
# Find and remove matching entries
|
|
36
|
+
entries_to_remove = []
|
|
37
|
+
for entry in docket._docket.entries:
|
|
38
|
+
if entry.name in matching_files:
|
|
39
|
+
entries_to_remove.append(entry)
|
|
40
|
+
|
|
41
|
+
for entry in entries_to_remove:
|
|
42
|
+
docket.remove(entry)
|
|
43
|
+
removed_count += 1
|
|
44
|
+
logger.debug(f"Removed from docket: {entry.name}")
|
|
45
|
+
|
|
46
|
+
# Save updated docket back to file
|
|
47
|
+
if removed_count > 0:
|
|
48
|
+
json_data = json.dumps(docket.get_entries(), indent=2)
|
|
49
|
+
with open(app_dirs.file_docket_path, "w") as f:
|
|
50
|
+
f.write(json_data)
|
|
51
|
+
logger.info(f"Updated file_docket.json, removed {removed_count} entries")
|
|
52
|
+
|
|
53
|
+
return removed_count
|
|
54
|
+
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logger.error(f"Failed to remove files from file_docket: {e}")
|
|
57
|
+
logger.exception(e)
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def remove_files_from_kb(project_name: str, filename_pattern: str, confirm: bool = True) -> int:
|
|
62
|
+
"""
|
|
63
|
+
Remove files from the ChromaDB knowledge base that match the given filename pattern.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
project_name: Name of the project
|
|
67
|
+
filename_pattern: Full or partial filename to match (case-insensitive)
|
|
68
|
+
confirm: If True, ask for confirmation before deleting
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Number of chunks deleted
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
with Session() as db:
|
|
75
|
+
chroma_client = get_chromadb_client_for_project_name(db, project_name)
|
|
76
|
+
|
|
77
|
+
# Get all documents from the collection
|
|
78
|
+
collection = chroma_client._collection
|
|
79
|
+
results = collection.get()
|
|
80
|
+
|
|
81
|
+
if not results or not results.get('metadatas'):
|
|
82
|
+
logger.info("No files found in the knowledge base")
|
|
83
|
+
print("No files found in the knowledge base.")
|
|
84
|
+
return 0
|
|
85
|
+
|
|
86
|
+
# Find matching file chunks
|
|
87
|
+
matching_ids: List[str] = []
|
|
88
|
+
matching_files: set = set()
|
|
89
|
+
|
|
90
|
+
for idx, metadata in enumerate(results['metadatas']):
|
|
91
|
+
if metadata:
|
|
92
|
+
file_name = metadata.get('file_name', '')
|
|
93
|
+
# Case-insensitive partial match
|
|
94
|
+
if filename_pattern.lower() in file_name.lower():
|
|
95
|
+
matching_ids.append(results['ids'][idx])
|
|
96
|
+
matching_files.add(file_name)
|
|
97
|
+
|
|
98
|
+
if not matching_ids:
|
|
99
|
+
print(f"\nNo files matching '{filename_pattern}' found in the knowledge base.")
|
|
100
|
+
return 0
|
|
101
|
+
|
|
102
|
+
# Display matching files
|
|
103
|
+
print(f"\nFound {len(matching_files)} file(s) matching '{filename_pattern}':")
|
|
104
|
+
for file_name in sorted(matching_files):
|
|
105
|
+
print(f" • {file_name}")
|
|
106
|
+
print(f"\nTotal chunks to delete: {len(matching_ids)}")
|
|
107
|
+
|
|
108
|
+
# Confirm deletion
|
|
109
|
+
if confirm:
|
|
110
|
+
response = input("\nAre you sure you want to delete these files? (yes/no): ")
|
|
111
|
+
if response.lower() not in ['yes', 'y']:
|
|
112
|
+
print("Deletion cancelled.")
|
|
113
|
+
return 0
|
|
114
|
+
|
|
115
|
+
# Delete the chunks from ChromaDB
|
|
116
|
+
collection.delete(ids=matching_ids)
|
|
117
|
+
|
|
118
|
+
# Remove files from file_docket
|
|
119
|
+
removed_from_docket = _remove_from_file_docket(matching_files)
|
|
120
|
+
|
|
121
|
+
print(f"\nSuccessfully deleted {len(matching_ids)} chunks from {len(matching_files)} file(s).")
|
|
122
|
+
if removed_from_docket > 0:
|
|
123
|
+
print(f"Removed {removed_from_docket} file(s) from file_docket.json")
|
|
124
|
+
logger.info(f"Deleted {len(matching_ids)} chunks from {len(matching_files)} files matching '{filename_pattern}'")
|
|
125
|
+
logger.info(f"Removed {removed_from_docket} entries from file_docket")
|
|
126
|
+
|
|
127
|
+
return len(matching_ids)
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(f"Failed to remove files from knowledge base for project: {project_name}")
|
|
131
|
+
logger.exception(e)
|
|
132
|
+
print(f"\nError: Failed to remove files. Check logs for details.")
|
|
133
|
+
raise
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def list_matching_files(project_name: str, filename_pattern: str) -> List[str]:
|
|
137
|
+
"""
|
|
138
|
+
List files that match the given filename pattern without deleting them.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
project_name: Name of the project
|
|
142
|
+
filename_pattern: Full or partial filename to match (case-insensitive)
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
List of matching filenames
|
|
146
|
+
"""
|
|
147
|
+
try:
|
|
148
|
+
with Session() as db:
|
|
149
|
+
chroma_client = get_chromadb_client_for_project_name(db, project_name)
|
|
150
|
+
|
|
151
|
+
# Get all documents from the collection
|
|
152
|
+
collection = chroma_client._collection
|
|
153
|
+
results = collection.get()
|
|
154
|
+
|
|
155
|
+
if not results or not results.get('metadatas'):
|
|
156
|
+
return []
|
|
157
|
+
|
|
158
|
+
# Find matching files
|
|
159
|
+
matching_files: set = set()
|
|
160
|
+
|
|
161
|
+
for metadata in results['metadatas']:
|
|
162
|
+
if metadata:
|
|
163
|
+
file_name = metadata.get('file_name', '')
|
|
164
|
+
# Case-insensitive partial match
|
|
165
|
+
if filename_pattern.lower() in file_name.lower():
|
|
166
|
+
matching_files.add(file_name)
|
|
167
|
+
|
|
168
|
+
return sorted(list(matching_files))
|
|
169
|
+
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.error(f"Failed to list matching files for project: {project_name}")
|
|
172
|
+
logger.exception(e)
|
|
173
|
+
return []
|
mfcli/utils/logger.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from mfcli.utils.config import get_config
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def setup_logging():
|
|
8
|
+
config = get_config()
|
|
9
|
+
formatter = logging.Formatter(
|
|
10
|
+
fmt="%(asctime)s [%(levelname)s] %(name)s:%(funcName)s:%(lineno)d: %(message)s",
|
|
11
|
+
datefmt="%Y-%m-%d %H:%M:%S"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
15
|
+
handler.setFormatter(formatter)
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger()
|
|
18
|
+
logger.setLevel(config.log_level)
|
|
19
|
+
logger.addHandler(handler)
|
|
20
|
+
logger.propagate = False
|
|
21
|
+
|
|
22
|
+
logging.getLogger("google_genai").setLevel(logging.ERROR)
|
|
23
|
+
logging.getLogger("httpx").setLevel(logging.ERROR)
|
|
24
|
+
logging.getLogger("docling").setLevel(logging.ERROR)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_logger(name=None):
|
|
28
|
+
return logging.getLogger(name or __name__)
|