basic-memory 0.6.0__tar.gz → 0.7.0__tar.gz
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.
Potentially problematic release.
This version of basic-memory might be problematic. Click here for more details.
- {basic_memory-0.6.0 → basic_memory-0.7.0}/CHANGELOG.md +41 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/Makefile +4 -2
- {basic_memory-0.6.0 → basic_memory-0.7.0}/PKG-INFO +2 -2
- {basic_memory-0.6.0 → basic_memory-0.7.0}/pyproject.toml +2 -2
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/__init__.py +1 -1
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/routers/knowledge_router.py +0 -8
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/routers/memory_router.py +26 -10
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/routers/resource_router.py +14 -8
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/routers/search_router.py +17 -9
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/app.py +1 -1
- basic_memory-0.7.0/src/basic_memory/cli/commands/db.py +28 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/import_chatgpt.py +31 -27
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/import_claude_conversations.py +29 -27
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/import_claude_projects.py +30 -29
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/import_memory_json.py +28 -26
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/status.py +8 -6
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/sync.py +6 -3
- basic_memory-0.7.0/src/basic_memory/cli/commands/tools.py +157 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/main.py +1 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/config.py +1 -1
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/db.py +1 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/deps.py +5 -1
- basic_memory-0.7.0/src/basic_memory/mcp/tools/knowledge.py +68 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/tools/memory.py +48 -29
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/tools/notes.py +66 -72
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/tools/search.py +13 -4
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/repository/search_repository.py +3 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/memory.py +3 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/request.py +1 -1
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/search.py +2 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/context_service.py +14 -6
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/search_service.py +3 -1
- basic_memory-0.7.0/src/basic_memory/sync/sync_service.py +174 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/utils.py +4 -7
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/api/test_memory_router.py +30 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/api/test_resource_router.py +77 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/api/test_search_router.py +12 -22
- basic_memory-0.7.0/tests/cli/test_cli_tools.py +294 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/cli/test_import_chatgpt.py +3 -6
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/mcp/test_tool_knowledge.py +74 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/mcp/test_tool_memory.py +6 -2
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/mcp/test_tool_notes.py +29 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/mcp/test_tool_search.py +21 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/schemas/test_search.py +1 -1
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/services/test_context_service.py +0 -12
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/services/test_link_resolver.py +0 -8
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/services/test_search_service.py +19 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/uv.lock +22 -3
- basic_memory-0.6.0/src/basic_memory/cli/commands/db.py +0 -25
- basic_memory-0.6.0/src/basic_memory/mcp/tools/knowledge.py +0 -56
- basic_memory-0.6.0/src/basic_memory/sync/sync_service.py +0 -165
- {basic_memory-0.6.0 → basic_memory-0.7.0}/.github/workflows/pr-title.yml +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/.github/workflows/release.yml +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/.github/workflows/test.yml +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/.gitignore +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/.python-version +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/CITATION.cff +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/CODE_OF_CONDUCT.md +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/CONTRIBUTING.md +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/LICENSE +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/README.md +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/basic-memory.md +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/installer/Basic.icns +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/installer/README.md +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/installer/icon.svg +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/installer/installer.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/installer/make_icons.sh +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/installer/setup.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/memory.json +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/scripts/install.sh +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/alembic/README +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/alembic/env.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/alembic/migrations.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/alembic/script.py.mako +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/app.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/routers/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/mcp.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/file_utils.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/markdown/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/markdown/entity_parser.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/markdown/markdown_processor.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/markdown/plugins.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/markdown/schemas.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/markdown/utils.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/async_client.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/server.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/tools/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/tools/utils.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/models/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/models/base.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/models/knowledge.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/models/search.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/repository/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/repository/entity_repository.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/repository/observation_repository.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/repository/relation_repository.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/repository/repository.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/base.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/delete.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/discovery.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/response.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/entity_service.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/exceptions.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/file_service.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/link_resolver.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/service.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/sync/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/sync/file_change_scanner.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/sync/utils.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/sync/watch_service.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/api/conftest.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/api/test_knowledge_router.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/cli/test_import_claude_conversations.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/cli/test_import_claude_projects.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/cli/test_import_memory_json.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/cli/test_status.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/cli/test_sync.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/conftest.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/edit_file_test.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/__init__.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_entity_parser.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_markdown_plugins.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_markdown_processor.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_observation_edge_cases.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_parser_edge_cases.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_relation_edge_cases.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_task_detection.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/mcp/conftest.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/mcp/test_tool_utils.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/repository/test_entity_repository.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/repository/test_observation_repository.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/repository/test_relation_repository.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/repository/test_repository.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/schemas/test_memory_url.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/schemas/test_schemas.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/services/test_entity_service.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/services/test_file_service.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/sync/test_file_change_scanner.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/sync/test_sync_service.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/sync/test_watch_service.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/test_basic_memory.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/utils/test_file_utils.py +0 -0
- {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/utils/test_permalink_formatting.py +0 -0
|
@@ -1,8 +1,49 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.7.0 (2025-02-19)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- Add logfire instrumentation to tools
|
|
9
|
+
([`3e8e3e8`](https://github.com/basicmachines-co/basic-memory/commit/3e8e3e8961eae2e82839746e28963191b0aef0a0))
|
|
10
|
+
|
|
11
|
+
- Add logfire spans to cli
|
|
12
|
+
([`00d23a5`](https://github.com/basicmachines-co/basic-memory/commit/00d23a5ee15ddac4ea45e702dcd02ab9f0509276))
|
|
13
|
+
|
|
14
|
+
- Add logfire spans to cli
|
|
15
|
+
([`812136c`](https://github.com/basicmachines-co/basic-memory/commit/812136c8c22ad191d14ff32dcad91aae076d4120))
|
|
16
|
+
|
|
17
|
+
- Search query pagination params
|
|
18
|
+
([`bc9ca07`](https://github.com/basicmachines-co/basic-memory/commit/bc9ca0744ffe4296d7d597b4dd9b7c73c2d63f3f))
|
|
19
|
+
|
|
20
|
+
### Chores
|
|
21
|
+
|
|
22
|
+
- Fix tests
|
|
23
|
+
([`57984aa`](https://github.com/basicmachines-co/basic-memory/commit/57984aa912625dcde7877afb96d874c164af2896))
|
|
24
|
+
|
|
25
|
+
- Remove unused tests
|
|
26
|
+
([`2c8ed17`](https://github.com/basicmachines-co/basic-memory/commit/2c8ed1737d6769fe1ef5c96f8a2bd75b9899316a))
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
- Add cli commands for mcp tools
|
|
31
|
+
([`f5a7541`](https://github.com/basicmachines-co/basic-memory/commit/f5a7541da17e97403b7a702720a05710f68b223a))
|
|
32
|
+
|
|
33
|
+
- Add pagination to build_context and recent_activity
|
|
34
|
+
([`0123544`](https://github.com/basicmachines-co/basic-memory/commit/0123544556513af943d399d70b849b142b834b15))
|
|
35
|
+
|
|
36
|
+
- Add pagination to read_notes
|
|
37
|
+
([`02f8e86`](https://github.com/basicmachines-co/basic-memory/commit/02f8e866923d5793d2620076c709c920d99f2c4f))
|
|
38
|
+
|
|
39
|
+
|
|
4
40
|
## v0.6.0 (2025-02-18)
|
|
5
41
|
|
|
42
|
+
### Chores
|
|
43
|
+
|
|
44
|
+
- Re-add sync status console on watch
|
|
45
|
+
([`66b57e6`](https://github.com/basicmachines-co/basic-memory/commit/66b57e682f2e9c432bffd4af293b0d1db1d3469b))
|
|
46
|
+
|
|
6
47
|
### Features
|
|
7
48
|
|
|
8
49
|
- Configure logfire telemetry ([#12](https://github.com/basicmachines-co/basic-memory/pull/12),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: install test lint clean format type-check installer-mac installer-win
|
|
1
|
+
.PHONY: install test lint clean format type-check installer-mac installer-win check
|
|
2
2
|
|
|
3
3
|
install:
|
|
4
4
|
pip install -e ".[dev]"
|
|
@@ -40,4 +40,6 @@ installer-win:
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
update-deps:
|
|
43
|
-
uv lock f--upgrade
|
|
43
|
+
uv lock f--upgrade
|
|
44
|
+
|
|
45
|
+
check: lint format type-check test
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: basic-memory
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Local-first knowledge management combining Zettelkasten with knowledge graphs
|
|
5
5
|
Project-URL: Homepage, https://github.com/basicmachines-co/basic-memory
|
|
6
6
|
Project-URL: Repository, https://github.com/basicmachines-co/basic-memory
|
|
@@ -15,7 +15,7 @@ Requires-Dist: dateparser>=1.2.0
|
|
|
15
15
|
Requires-Dist: fastapi[standard]>=0.115.8
|
|
16
16
|
Requires-Dist: greenlet>=3.1.1
|
|
17
17
|
Requires-Dist: icecream>=2.1.3
|
|
18
|
-
Requires-Dist: logfire[fastapi,sqlalchemy,sqlite3]>=3.6.0
|
|
18
|
+
Requires-Dist: logfire[fastapi,httpx,sqlalchemy,sqlite3]>=3.6.0
|
|
19
19
|
Requires-Dist: loguru>=0.7.3
|
|
20
20
|
Requires-Dist: markdown-it-py>=3.0.0
|
|
21
21
|
Requires-Dist: mcp>=1.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "basic-memory"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0"
|
|
4
4
|
description = "Local-first knowledge management combining Zettelkasten with knowledge graphs"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12.1"
|
|
@@ -29,7 +29,7 @@ dependencies = [
|
|
|
29
29
|
"fastapi[standard]>=0.115.8",
|
|
30
30
|
"alembic>=1.14.1",
|
|
31
31
|
"qasync>=0.27.1",
|
|
32
|
-
"logfire[fastapi,sqlalchemy,sqlite3]>=3.6.0",
|
|
32
|
+
"logfire[fastapi,httpx,sqlalchemy,sqlite3]>=3.6.0",
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
|
|
@@ -94,11 +94,8 @@ async def get_entity(
|
|
|
94
94
|
try:
|
|
95
95
|
entity = await entity_service.get_by_permalink(permalink)
|
|
96
96
|
result = EntityResponse.model_validate(entity)
|
|
97
|
-
|
|
98
|
-
logger.info(f"response: get_entity with result={result}")
|
|
99
97
|
return result
|
|
100
98
|
except EntityNotFoundError:
|
|
101
|
-
logger.error(f"Error: Entity with {permalink} not found")
|
|
102
99
|
raise HTTPException(status_code=404, detail=f"Entity with {permalink} not found")
|
|
103
100
|
|
|
104
101
|
|
|
@@ -114,8 +111,6 @@ async def get_entities(
|
|
|
114
111
|
result = EntityListResponse(
|
|
115
112
|
entities=[EntityResponse.model_validate(entity) for entity in entities]
|
|
116
113
|
)
|
|
117
|
-
|
|
118
|
-
logger.info(f"response: get_entities with result={result}")
|
|
119
114
|
return result
|
|
120
115
|
|
|
121
116
|
|
|
@@ -135,7 +130,6 @@ async def delete_entity(
|
|
|
135
130
|
|
|
136
131
|
entity = await link_resolver.resolve_link(identifier)
|
|
137
132
|
if entity is None:
|
|
138
|
-
logger.info("response: delete_entity with result=DeleteEntitiesResponse(deleted=False)")
|
|
139
133
|
return DeleteEntitiesResponse(deleted=False)
|
|
140
134
|
|
|
141
135
|
# Delete the entity
|
|
@@ -145,7 +139,6 @@ async def delete_entity(
|
|
|
145
139
|
background_tasks.add_task(search_service.delete_by_permalink, entity.permalink)
|
|
146
140
|
|
|
147
141
|
result = DeleteEntitiesResponse(deleted=deleted)
|
|
148
|
-
logger.info(f"response: delete_entity with result={result}")
|
|
149
142
|
return result
|
|
150
143
|
|
|
151
144
|
|
|
@@ -166,5 +159,4 @@ async def delete_entities(
|
|
|
166
159
|
background_tasks.add_task(search_service.delete_by_permalink, permalink)
|
|
167
160
|
|
|
168
161
|
result = DeleteEntitiesResponse(deleted=deleted)
|
|
169
|
-
logger.info(f"response: delete_entities with result={result}")
|
|
170
162
|
return result
|
|
@@ -24,7 +24,7 @@ from basic_memory.services.context_service import ContextResultRow
|
|
|
24
24
|
router = APIRouter(prefix="/memory", tags=["memory"])
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
async def to_graph_context(context, entity_repository: EntityRepository):
|
|
27
|
+
async def to_graph_context(context, entity_repository: EntityRepository, page: int, page_size: int):
|
|
28
28
|
# return results
|
|
29
29
|
async def to_summary(item: SearchIndexRow | ContextResultRow):
|
|
30
30
|
match item.type:
|
|
@@ -66,7 +66,11 @@ async def to_graph_context(context, entity_repository: EntityRepository):
|
|
|
66
66
|
metadata = MemoryMetadata.model_validate(context["metadata"])
|
|
67
67
|
# Transform to GraphContext
|
|
68
68
|
return GraphContext(
|
|
69
|
-
primary_results=primary_results,
|
|
69
|
+
primary_results=primary_results,
|
|
70
|
+
related_results=related_results,
|
|
71
|
+
metadata=metadata,
|
|
72
|
+
page=page,
|
|
73
|
+
page_size=page_size,
|
|
70
74
|
)
|
|
71
75
|
|
|
72
76
|
|
|
@@ -77,7 +81,9 @@ async def recent(
|
|
|
77
81
|
type: Annotated[list[SearchItemType] | None, Query()] = None,
|
|
78
82
|
depth: int = 1,
|
|
79
83
|
timeframe: TimeFrame = "7d",
|
|
80
|
-
|
|
84
|
+
page: int = 1,
|
|
85
|
+
page_size: int = 10,
|
|
86
|
+
max_related: int = 10,
|
|
81
87
|
) -> GraphContext:
|
|
82
88
|
# return all types by default
|
|
83
89
|
types = (
|
|
@@ -87,16 +93,20 @@ async def recent(
|
|
|
87
93
|
)
|
|
88
94
|
|
|
89
95
|
logger.debug(
|
|
90
|
-
f"Getting recent context: `{types}` depth: `{depth}` timeframe: `{timeframe}`
|
|
96
|
+
f"Getting recent context: `{types}` depth: `{depth}` timeframe: `{timeframe}` page: `{page}` page_size: `{page_size}` max_related: `{max_related}`"
|
|
91
97
|
)
|
|
92
98
|
# Parse timeframe
|
|
93
99
|
since = parse(timeframe)
|
|
100
|
+
limit = page_size
|
|
101
|
+
offset = (page - 1) * page_size
|
|
94
102
|
|
|
95
103
|
# Build context
|
|
96
104
|
context = await context_service.build_context(
|
|
97
|
-
types=types, depth=depth, since=since,
|
|
105
|
+
types=types, depth=depth, since=since, limit=limit, offset=offset, max_related=max_related
|
|
106
|
+
)
|
|
107
|
+
return await to_graph_context(
|
|
108
|
+
context, entity_repository=entity_repository, page=page, page_size=page_size
|
|
98
109
|
)
|
|
99
|
-
return await to_graph_context(context, entity_repository=entity_repository)
|
|
100
110
|
|
|
101
111
|
|
|
102
112
|
# get_memory_context needs to be declared last so other paths can match
|
|
@@ -109,21 +119,27 @@ async def get_memory_context(
|
|
|
109
119
|
uri: str,
|
|
110
120
|
depth: int = 1,
|
|
111
121
|
timeframe: TimeFrame = "7d",
|
|
112
|
-
|
|
122
|
+
page: int = 1,
|
|
123
|
+
page_size: int = 10,
|
|
124
|
+
max_related: int = 10,
|
|
113
125
|
) -> GraphContext:
|
|
114
126
|
"""Get rich context from memory:// URI."""
|
|
115
127
|
# add the project name from the config to the url as the "host
|
|
116
128
|
# Parse URI
|
|
117
129
|
logger.debug(
|
|
118
|
-
f"Getting context for URI: `{uri}` depth: `{depth}` timeframe: `{timeframe}`
|
|
130
|
+
f"Getting context for URI: `{uri}` depth: `{depth}` timeframe: `{timeframe}` page: `{page}` page_size: `{page_size}` max_related: `{max_related}`"
|
|
119
131
|
)
|
|
120
132
|
memory_url = normalize_memory_url(uri)
|
|
121
133
|
|
|
122
134
|
# Parse timeframe
|
|
123
135
|
since = parse(timeframe)
|
|
136
|
+
limit = page_size
|
|
137
|
+
offset = (page - 1) * page_size
|
|
124
138
|
|
|
125
139
|
# Build context
|
|
126
140
|
context = await context_service.build_context(
|
|
127
|
-
memory_url, depth=depth, since=since,
|
|
141
|
+
memory_url, depth=depth, since=since, limit=limit, offset=offset, max_related=max_related
|
|
142
|
+
)
|
|
143
|
+
return await to_graph_context(
|
|
144
|
+
context, entity_repository=entity_repository, page=page, page_size=page_size
|
|
128
145
|
)
|
|
129
|
-
return await to_graph_context(context, entity_repository=entity_repository)
|
|
@@ -21,16 +21,16 @@ from basic_memory.schemas.search import SearchQuery, SearchItemType
|
|
|
21
21
|
router = APIRouter(prefix="/resource", tags=["resources"])
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def get_entity_ids(item: SearchIndexRow) ->
|
|
24
|
+
def get_entity_ids(item: SearchIndexRow) -> set[int]:
|
|
25
25
|
match item.type:
|
|
26
26
|
case SearchItemType.ENTITY:
|
|
27
|
-
return
|
|
27
|
+
return {item.id}
|
|
28
28
|
case SearchItemType.OBSERVATION:
|
|
29
|
-
return
|
|
29
|
+
return {item.entity_id} # pyright: ignore [reportReturnType]
|
|
30
30
|
case SearchItemType.RELATION:
|
|
31
31
|
from_entity = item.from_id
|
|
32
32
|
to_entity = item.to_id # pyright: ignore [reportReturnType]
|
|
33
|
-
return
|
|
33
|
+
return {from_entity, to_entity} if to_entity else {from_entity} # pyright: ignore [reportReturnType]
|
|
34
34
|
case _: # pragma: no cover
|
|
35
35
|
raise ValueError(f"Unexpected type: {item.type}")
|
|
36
36
|
|
|
@@ -44,6 +44,8 @@ async def get_resource_content(
|
|
|
44
44
|
file_service: FileServiceDep,
|
|
45
45
|
background_tasks: BackgroundTasks,
|
|
46
46
|
identifier: str,
|
|
47
|
+
page: int = 1,
|
|
48
|
+
page_size: int = 10,
|
|
47
49
|
) -> FileResponse:
|
|
48
50
|
"""Get resource content by identifier: name or permalink."""
|
|
49
51
|
logger.debug(f"Getting content for: {identifier}")
|
|
@@ -52,6 +54,10 @@ async def get_resource_content(
|
|
|
52
54
|
entity = await link_resolver.resolve_link(identifier)
|
|
53
55
|
results = [entity] if entity else []
|
|
54
56
|
|
|
57
|
+
# pagination for multiple results
|
|
58
|
+
limit = page_size
|
|
59
|
+
offset = (page - 1) * page_size
|
|
60
|
+
|
|
55
61
|
# search using the identifier as a permalink
|
|
56
62
|
if not results:
|
|
57
63
|
# if the identifier contains a wildcard, use GLOB search
|
|
@@ -60,13 +66,13 @@ async def get_resource_content(
|
|
|
60
66
|
if "*" in identifier
|
|
61
67
|
else SearchQuery(permalink=identifier)
|
|
62
68
|
)
|
|
63
|
-
search_results = await search_service.search(query)
|
|
69
|
+
search_results = await search_service.search(query, limit, offset)
|
|
64
70
|
if not search_results:
|
|
65
71
|
raise HTTPException(status_code=404, detail=f"Resource not found: {identifier}")
|
|
66
72
|
|
|
67
|
-
# get the entities related to the search results
|
|
68
|
-
entity_ids =
|
|
69
|
-
results = await entity_service.get_entities_by_id(entity_ids)
|
|
73
|
+
# get the deduplicated entities related to the search results
|
|
74
|
+
entity_ids = {id for result in search_results for id in get_entity_ids(result)}
|
|
75
|
+
results = await entity_service.get_entities_by_id(list(entity_ids))
|
|
70
76
|
|
|
71
77
|
# return single response
|
|
72
78
|
if len(results) == 1:
|
|
@@ -2,27 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import asdict
|
|
4
4
|
|
|
5
|
-
from fastapi import APIRouter,
|
|
5
|
+
from fastapi import APIRouter, BackgroundTasks
|
|
6
6
|
|
|
7
|
-
from basic_memory.services.search_service import SearchService
|
|
8
7
|
from basic_memory.schemas.search import SearchQuery, SearchResult, SearchResponse
|
|
9
|
-
from basic_memory.deps import
|
|
8
|
+
from basic_memory.deps import SearchServiceDep
|
|
10
9
|
|
|
11
10
|
router = APIRouter(prefix="/search", tags=["search"])
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
@router.post("/", response_model=SearchResponse)
|
|
15
|
-
async def search(
|
|
14
|
+
async def search(
|
|
15
|
+
query: SearchQuery,
|
|
16
|
+
search_service: SearchServiceDep,
|
|
17
|
+
page: int = 1,
|
|
18
|
+
page_size: int = 10,
|
|
19
|
+
):
|
|
16
20
|
"""Search across all knowledge and documents."""
|
|
17
|
-
|
|
21
|
+
limit = page_size
|
|
22
|
+
offset = (page - 1) * page_size
|
|
23
|
+
results = await search_service.search(query, limit=limit, offset=offset)
|
|
18
24
|
search_results = [SearchResult.model_validate(asdict(r)) for r in results]
|
|
19
|
-
return SearchResponse(
|
|
25
|
+
return SearchResponse(
|
|
26
|
+
results=search_results,
|
|
27
|
+
current_page=page,
|
|
28
|
+
page_size=page_size,
|
|
29
|
+
)
|
|
20
30
|
|
|
21
31
|
|
|
22
32
|
@router.post("/reindex")
|
|
23
|
-
async def reindex(
|
|
24
|
-
background_tasks: BackgroundTasks, search_service: SearchService = Depends(get_search_service)
|
|
25
|
-
):
|
|
33
|
+
async def reindex(background_tasks: BackgroundTasks, search_service: SearchServiceDep):
|
|
26
34
|
"""Recreate and populate the search index."""
|
|
27
35
|
await search_service.reindex_all(background_tasks=background_tasks)
|
|
28
36
|
return {"status": "ok", "message": "Reindex initiated"}
|
|
@@ -6,7 +6,7 @@ from basic_memory import db
|
|
|
6
6
|
from basic_memory.config import config
|
|
7
7
|
from basic_memory.utils import setup_logging
|
|
8
8
|
|
|
9
|
-
setup_logging(log_file=".basic-memory/basic-memory-cli.log") # pragma: no cover
|
|
9
|
+
setup_logging(log_file=".basic-memory/basic-memory-cli.log", console=False) # pragma: no cover
|
|
10
10
|
|
|
11
11
|
asyncio.run(db.run_migrations(config))
|
|
12
12
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Database management commands."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
import logfire
|
|
6
|
+
import typer
|
|
7
|
+
from loguru import logger
|
|
8
|
+
|
|
9
|
+
from basic_memory.alembic import migrations
|
|
10
|
+
from basic_memory.cli.app import app
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command()
|
|
14
|
+
def reset(
|
|
15
|
+
reindex: bool = typer.Option(False, "--reindex", help="Rebuild indices from filesystem"),
|
|
16
|
+
): # pragma: no cover
|
|
17
|
+
"""Reset database (drop all tables and recreate)."""
|
|
18
|
+
with logfire.span("reset"): # pyright: ignore [reportGeneralTypeIssues]
|
|
19
|
+
if typer.confirm("This will delete all data in your db. Are you sure?"):
|
|
20
|
+
logger.info("Resetting database...")
|
|
21
|
+
asyncio.run(migrations.reset_database())
|
|
22
|
+
|
|
23
|
+
if reindex:
|
|
24
|
+
# Import and run sync
|
|
25
|
+
from basic_memory.cli.commands.sync import sync
|
|
26
|
+
|
|
27
|
+
logger.info("Rebuilding search index from filesystem...")
|
|
28
|
+
sync(watch=False) # pyright: ignore
|
|
@@ -6,6 +6,7 @@ from datetime import datetime
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Dict, Any, List, Annotated, Set, Optional
|
|
8
8
|
|
|
9
|
+
import logfire
|
|
9
10
|
import typer
|
|
10
11
|
from loguru import logger
|
|
11
12
|
from rich.console import Console
|
|
@@ -209,7 +210,7 @@ async def get_markdown_processor() -> MarkdownProcessor:
|
|
|
209
210
|
@import_app.command(name="chatgpt", help="Import conversations from ChatGPT JSON export.")
|
|
210
211
|
def import_chatgpt(
|
|
211
212
|
conversations_json: Annotated[
|
|
212
|
-
Path, typer.
|
|
213
|
+
Path, typer.Argument(help="Path to ChatGPT conversations.json file")
|
|
213
214
|
] = Path("conversations.json"),
|
|
214
215
|
folder: Annotated[
|
|
215
216
|
str, typer.Option(help="The folder to place the files in.")
|
|
@@ -225,35 +226,38 @@ def import_chatgpt(
|
|
|
225
226
|
After importing, run 'basic-memory sync' to index the new files.
|
|
226
227
|
"""
|
|
227
228
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
with logfire.span("import chatgpt"): # pyright: ignore [reportGeneralTypeIssues]
|
|
230
|
+
try:
|
|
231
|
+
if conversations_json:
|
|
232
|
+
if not conversations_json.exists():
|
|
233
|
+
typer.echo(f"Error: File not found: {conversations_json}", err=True)
|
|
234
|
+
raise typer.Exit(1)
|
|
233
235
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
+
# Get markdown processor
|
|
237
|
+
markdown_processor = asyncio.run(get_markdown_processor())
|
|
236
238
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
239
|
+
# Process the file
|
|
240
|
+
base_path = config.home / folder
|
|
241
|
+
console.print(
|
|
242
|
+
f"\nImporting chats from {conversations_json}...writing to {base_path}"
|
|
243
|
+
)
|
|
244
|
+
results = asyncio.run(
|
|
245
|
+
process_chatgpt_json(conversations_json, folder, markdown_processor)
|
|
246
|
+
)
|
|
243
247
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
248
|
+
# Show results
|
|
249
|
+
console.print(
|
|
250
|
+
Panel(
|
|
251
|
+
f"[green]Import complete![/green]\n\n"
|
|
252
|
+
f"Imported {results['conversations']} conversations\n"
|
|
253
|
+
f"Containing {results['messages']} messages",
|
|
254
|
+
expand=False,
|
|
255
|
+
)
|
|
251
256
|
)
|
|
252
|
-
)
|
|
253
257
|
|
|
254
|
-
|
|
258
|
+
console.print("\nRun 'basic-memory sync' to index the new files.")
|
|
255
259
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
+
except Exception as e:
|
|
261
|
+
logger.error("Import failed")
|
|
262
|
+
typer.echo(f"Error during import: {e}", err=True)
|
|
263
|
+
raise typer.Exit(1)
|
|
@@ -6,6 +6,7 @@ from datetime import datetime
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Dict, Any, List, Annotated
|
|
8
8
|
|
|
9
|
+
import logfire
|
|
9
10
|
import typer
|
|
10
11
|
from loguru import logger
|
|
11
12
|
from rich.console import Console
|
|
@@ -178,34 +179,35 @@ def import_claude(
|
|
|
178
179
|
After importing, run 'basic-memory sync' to index the new files.
|
|
179
180
|
"""
|
|
180
181
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
with logfire.span("import claude conversations"): # pyright: ignore [reportGeneralTypeIssues]
|
|
183
|
+
try:
|
|
184
|
+
if not conversations_json.exists():
|
|
185
|
+
typer.echo(f"Error: File not found: {conversations_json}", err=True)
|
|
186
|
+
raise typer.Exit(1)
|
|
187
|
+
|
|
188
|
+
# Get markdown processor
|
|
189
|
+
markdown_processor = asyncio.run(get_markdown_processor())
|
|
185
190
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
console.print(f"\nImporting chats from {conversations_json}...writing to {base_path}")
|
|
192
|
-
results = asyncio.run(
|
|
193
|
-
process_conversations_json(conversations_json, base_path, markdown_processor)
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
# Show results
|
|
197
|
-
console.print(
|
|
198
|
-
Panel(
|
|
199
|
-
f"[green]Import complete![/green]\n\n"
|
|
200
|
-
f"Imported {results['conversations']} conversations\n"
|
|
201
|
-
f"Containing {results['messages']} messages",
|
|
202
|
-
expand=False,
|
|
191
|
+
# Process the file
|
|
192
|
+
base_path = config.home / folder
|
|
193
|
+
console.print(f"\nImporting chats from {conversations_json}...writing to {base_path}")
|
|
194
|
+
results = asyncio.run(
|
|
195
|
+
process_conversations_json(conversations_json, base_path, markdown_processor)
|
|
203
196
|
)
|
|
204
|
-
)
|
|
205
197
|
|
|
206
|
-
|
|
198
|
+
# Show results
|
|
199
|
+
console.print(
|
|
200
|
+
Panel(
|
|
201
|
+
f"[green]Import complete![/green]\n\n"
|
|
202
|
+
f"Imported {results['conversations']} conversations\n"
|
|
203
|
+
f"Containing {results['messages']} messages",
|
|
204
|
+
expand=False,
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
207
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
console.print("\nRun 'basic-memory sync' to index the new files.")
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.error("Import failed")
|
|
212
|
+
typer.echo(f"Error during import: {e}", err=True)
|
|
213
|
+
raise typer.Exit(1)
|
{basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/import_claude_projects.py
RENAMED
|
@@ -5,6 +5,7 @@ import json
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Dict, Any, Annotated, Optional
|
|
7
7
|
|
|
8
|
+
import logfire
|
|
8
9
|
import typer
|
|
9
10
|
from loguru import logger
|
|
10
11
|
from rich.console import Console
|
|
@@ -160,36 +161,36 @@ def import_projects(
|
|
|
160
161
|
|
|
161
162
|
After importing, run 'basic-memory sync' to index the new files.
|
|
162
163
|
"""
|
|
164
|
+
with logfire.span("import claude projects"): # pyright: ignore [reportGeneralTypeIssues]
|
|
165
|
+
try:
|
|
166
|
+
if projects_json:
|
|
167
|
+
if not projects_json.exists():
|
|
168
|
+
typer.echo(f"Error: File not found: {projects_json}", err=True)
|
|
169
|
+
raise typer.Exit(1)
|
|
170
|
+
|
|
171
|
+
# Get markdown processor
|
|
172
|
+
markdown_processor = asyncio.run(get_markdown_processor())
|
|
173
|
+
|
|
174
|
+
# Process the file
|
|
175
|
+
base_path = config.home / base_folder if base_folder else config.home
|
|
176
|
+
console.print(f"\nImporting projects from {projects_json}...writing to {base_path}")
|
|
177
|
+
results = asyncio.run(
|
|
178
|
+
process_projects_json(projects_json, base_path, markdown_processor)
|
|
179
|
+
)
|
|
163
180
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
# Process the file
|
|
174
|
-
base_path = config.home / base_folder if base_folder else config.home
|
|
175
|
-
console.print(f"\nImporting projects from {projects_json}...writing to {base_path}")
|
|
176
|
-
results = asyncio.run(
|
|
177
|
-
process_projects_json(projects_json, base_path, markdown_processor)
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
# Show results
|
|
181
|
-
console.print(
|
|
182
|
-
Panel(
|
|
183
|
-
f"[green]Import complete![/green]\n\n"
|
|
184
|
-
f"Imported {results['documents']} project documents\n"
|
|
185
|
-
f"Imported {results['prompts']} prompt templates",
|
|
186
|
-
expand=False,
|
|
181
|
+
# Show results
|
|
182
|
+
console.print(
|
|
183
|
+
Panel(
|
|
184
|
+
f"[green]Import complete![/green]\n\n"
|
|
185
|
+
f"Imported {results['documents']} project documents\n"
|
|
186
|
+
f"Imported {results['prompts']} prompt templates",
|
|
187
|
+
expand=False,
|
|
188
|
+
)
|
|
187
189
|
)
|
|
188
|
-
)
|
|
189
190
|
|
|
190
|
-
|
|
191
|
+
console.print("\nRun 'basic-memory sync' to index the new files.")
|
|
191
192
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
except Exception as e:
|
|
194
|
+
logger.error("Import failed")
|
|
195
|
+
typer.echo(f"Error during import: {e}", err=True)
|
|
196
|
+
raise typer.Exit(1)
|