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.

Files changed (151) hide show
  1. {basic_memory-0.6.0 → basic_memory-0.7.0}/CHANGELOG.md +41 -0
  2. {basic_memory-0.6.0 → basic_memory-0.7.0}/Makefile +4 -2
  3. {basic_memory-0.6.0 → basic_memory-0.7.0}/PKG-INFO +2 -2
  4. {basic_memory-0.6.0 → basic_memory-0.7.0}/pyproject.toml +2 -2
  5. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/__init__.py +1 -1
  6. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/routers/knowledge_router.py +0 -8
  7. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/routers/memory_router.py +26 -10
  8. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/routers/resource_router.py +14 -8
  9. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/routers/search_router.py +17 -9
  10. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/app.py +1 -1
  11. basic_memory-0.7.0/src/basic_memory/cli/commands/db.py +28 -0
  12. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/import_chatgpt.py +31 -27
  13. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/import_claude_conversations.py +29 -27
  14. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/import_claude_projects.py +30 -29
  15. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/import_memory_json.py +28 -26
  16. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/status.py +8 -6
  17. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/sync.py +6 -3
  18. basic_memory-0.7.0/src/basic_memory/cli/commands/tools.py +157 -0
  19. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/main.py +1 -0
  20. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/config.py +1 -1
  21. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/db.py +1 -0
  22. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/deps.py +5 -1
  23. basic_memory-0.7.0/src/basic_memory/mcp/tools/knowledge.py +68 -0
  24. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/tools/memory.py +48 -29
  25. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/tools/notes.py +66 -72
  26. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/tools/search.py +13 -4
  27. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/repository/search_repository.py +3 -0
  28. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/memory.py +3 -0
  29. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/request.py +1 -1
  30. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/search.py +2 -0
  31. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/context_service.py +14 -6
  32. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/search_service.py +3 -1
  33. basic_memory-0.7.0/src/basic_memory/sync/sync_service.py +174 -0
  34. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/utils.py +4 -7
  35. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/api/test_memory_router.py +30 -0
  36. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/api/test_resource_router.py +77 -0
  37. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/api/test_search_router.py +12 -22
  38. basic_memory-0.7.0/tests/cli/test_cli_tools.py +294 -0
  39. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/cli/test_import_chatgpt.py +3 -6
  40. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/mcp/test_tool_knowledge.py +74 -0
  41. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/mcp/test_tool_memory.py +6 -2
  42. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/mcp/test_tool_notes.py +29 -0
  43. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/mcp/test_tool_search.py +21 -0
  44. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/schemas/test_search.py +1 -1
  45. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/services/test_context_service.py +0 -12
  46. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/services/test_link_resolver.py +0 -8
  47. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/services/test_search_service.py +19 -0
  48. {basic_memory-0.6.0 → basic_memory-0.7.0}/uv.lock +22 -3
  49. basic_memory-0.6.0/src/basic_memory/cli/commands/db.py +0 -25
  50. basic_memory-0.6.0/src/basic_memory/mcp/tools/knowledge.py +0 -56
  51. basic_memory-0.6.0/src/basic_memory/sync/sync_service.py +0 -165
  52. {basic_memory-0.6.0 → basic_memory-0.7.0}/.github/workflows/pr-title.yml +0 -0
  53. {basic_memory-0.6.0 → basic_memory-0.7.0}/.github/workflows/release.yml +0 -0
  54. {basic_memory-0.6.0 → basic_memory-0.7.0}/.github/workflows/test.yml +0 -0
  55. {basic_memory-0.6.0 → basic_memory-0.7.0}/.gitignore +0 -0
  56. {basic_memory-0.6.0 → basic_memory-0.7.0}/.python-version +0 -0
  57. {basic_memory-0.6.0 → basic_memory-0.7.0}/CITATION.cff +0 -0
  58. {basic_memory-0.6.0 → basic_memory-0.7.0}/CODE_OF_CONDUCT.md +0 -0
  59. {basic_memory-0.6.0 → basic_memory-0.7.0}/CONTRIBUTING.md +0 -0
  60. {basic_memory-0.6.0 → basic_memory-0.7.0}/LICENSE +0 -0
  61. {basic_memory-0.6.0 → basic_memory-0.7.0}/README.md +0 -0
  62. {basic_memory-0.6.0 → basic_memory-0.7.0}/basic-memory.md +0 -0
  63. {basic_memory-0.6.0 → basic_memory-0.7.0}/installer/Basic.icns +0 -0
  64. {basic_memory-0.6.0 → basic_memory-0.7.0}/installer/README.md +0 -0
  65. {basic_memory-0.6.0 → basic_memory-0.7.0}/installer/icon.svg +0 -0
  66. {basic_memory-0.6.0 → basic_memory-0.7.0}/installer/installer.py +0 -0
  67. {basic_memory-0.6.0 → basic_memory-0.7.0}/installer/make_icons.sh +0 -0
  68. {basic_memory-0.6.0 → basic_memory-0.7.0}/installer/setup.py +0 -0
  69. {basic_memory-0.6.0 → basic_memory-0.7.0}/memory.json +0 -0
  70. {basic_memory-0.6.0 → basic_memory-0.7.0}/scripts/install.sh +0 -0
  71. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/alembic/README +0 -0
  72. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/alembic/env.py +0 -0
  73. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/alembic/migrations.py +0 -0
  74. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/alembic/script.py.mako +0 -0
  75. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py +0 -0
  76. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/__init__.py +0 -0
  77. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/app.py +0 -0
  78. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/api/routers/__init__.py +0 -0
  79. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/__init__.py +0 -0
  80. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/__init__.py +0 -0
  81. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/cli/commands/mcp.py +0 -0
  82. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/file_utils.py +0 -0
  83. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/markdown/__init__.py +0 -0
  84. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/markdown/entity_parser.py +0 -0
  85. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/markdown/markdown_processor.py +0 -0
  86. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/markdown/plugins.py +0 -0
  87. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/markdown/schemas.py +0 -0
  88. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/markdown/utils.py +0 -0
  89. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/__init__.py +0 -0
  90. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/async_client.py +0 -0
  91. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/server.py +0 -0
  92. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/tools/__init__.py +0 -0
  93. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/mcp/tools/utils.py +0 -0
  94. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/models/__init__.py +0 -0
  95. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/models/base.py +0 -0
  96. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/models/knowledge.py +0 -0
  97. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/models/search.py +0 -0
  98. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/repository/__init__.py +0 -0
  99. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/repository/entity_repository.py +0 -0
  100. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/repository/observation_repository.py +0 -0
  101. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/repository/relation_repository.py +0 -0
  102. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/repository/repository.py +0 -0
  103. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/__init__.py +0 -0
  104. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/base.py +0 -0
  105. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/delete.py +0 -0
  106. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/discovery.py +0 -0
  107. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/schemas/response.py +0 -0
  108. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/__init__.py +0 -0
  109. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/entity_service.py +0 -0
  110. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/exceptions.py +0 -0
  111. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/file_service.py +0 -0
  112. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/link_resolver.py +0 -0
  113. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/services/service.py +0 -0
  114. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/sync/__init__.py +0 -0
  115. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/sync/file_change_scanner.py +0 -0
  116. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/sync/utils.py +0 -0
  117. {basic_memory-0.6.0 → basic_memory-0.7.0}/src/basic_memory/sync/watch_service.py +0 -0
  118. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/__init__.py +0 -0
  119. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/api/conftest.py +0 -0
  120. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/api/test_knowledge_router.py +0 -0
  121. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/cli/test_import_claude_conversations.py +0 -0
  122. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/cli/test_import_claude_projects.py +0 -0
  123. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/cli/test_import_memory_json.py +0 -0
  124. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/cli/test_status.py +0 -0
  125. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/cli/test_sync.py +0 -0
  126. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/conftest.py +0 -0
  127. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/edit_file_test.py +0 -0
  128. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/__init__.py +0 -0
  129. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_entity_parser.py +0 -0
  130. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_markdown_plugins.py +0 -0
  131. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_markdown_processor.py +0 -0
  132. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_observation_edge_cases.py +0 -0
  133. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_parser_edge_cases.py +0 -0
  134. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_relation_edge_cases.py +0 -0
  135. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/markdown/test_task_detection.py +0 -0
  136. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/mcp/conftest.py +0 -0
  137. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/mcp/test_tool_utils.py +0 -0
  138. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/repository/test_entity_repository.py +0 -0
  139. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/repository/test_observation_repository.py +0 -0
  140. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/repository/test_relation_repository.py +0 -0
  141. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/repository/test_repository.py +0 -0
  142. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/schemas/test_memory_url.py +0 -0
  143. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/schemas/test_schemas.py +0 -0
  144. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/services/test_entity_service.py +0 -0
  145. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/services/test_file_service.py +0 -0
  146. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/sync/test_file_change_scanner.py +0 -0
  147. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/sync/test_sync_service.py +0 -0
  148. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/sync/test_watch_service.py +0 -0
  149. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/test_basic_memory.py +0 -0
  150. {basic_memory-0.6.0 → basic_memory-0.7.0}/tests/utils/test_file_utils.py +0 -0
  151. {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.6.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.6.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
 
@@ -1,3 +1,3 @@
1
1
  """basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
2
2
 
3
- __version__ = "0.6.0"
3
+ __version__ = "0.7.0"
@@ -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, related_results=related_results, metadata=metadata
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
- max_results: int = 10,
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}` max_results: `{max_results}`"
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, max_results=max_results
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
- max_results: int = 10,
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}` max_results: `{max_results}`"
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, max_results=max_results
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) -> list[int]:
24
+ def get_entity_ids(item: SearchIndexRow) -> set[int]:
25
25
  match item.type:
26
26
  case SearchItemType.ENTITY:
27
- return [item.id]
27
+ return {item.id}
28
28
  case SearchItemType.OBSERVATION:
29
- return [item.entity_id] # pyright: ignore [reportReturnType]
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 [from_entity, to_entity] if to_entity else [from_entity] # pyright: ignore [reportReturnType]
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 = [id for result in search_results for id in get_entity_ids(result)]
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, Depends, BackgroundTasks
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 get_search_service
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(query: SearchQuery, search_service: SearchService = Depends(get_search_service)):
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
- results = await search_service.search(query)
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(results=search_results)
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.Option(..., help="Path to ChatGPT conversations.json file")
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
- try:
229
- if conversations_json:
230
- if not conversations_json.exists():
231
- typer.echo(f"Error: File not found: {conversations_json}", err=True)
232
- raise typer.Exit(1)
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
- # Get markdown processor
235
- markdown_processor = asyncio.run(get_markdown_processor())
236
+ # Get markdown processor
237
+ markdown_processor = asyncio.run(get_markdown_processor())
236
238
 
237
- # Process the file
238
- base_path = config.home / folder
239
- console.print(f"\nImporting chats from {conversations_json}...writing to {base_path}")
240
- results = asyncio.run(
241
- process_chatgpt_json(conversations_json, folder, markdown_processor)
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
- # Show results
245
- console.print(
246
- Panel(
247
- f"[green]Import complete![/green]\n\n"
248
- f"Imported {results['conversations']} conversations\n"
249
- f"Containing {results['messages']} messages",
250
- expand=False,
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
- console.print("\nRun 'basic-memory sync' to index the new files.")
258
+ console.print("\nRun 'basic-memory sync' to index the new files.")
255
259
 
256
- except Exception as e:
257
- logger.error("Import failed")
258
- typer.echo(f"Error during import: {e}", err=True)
259
- raise typer.Exit(1)
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
- try:
182
- if not conversations_json.exists():
183
- typer.echo(f"Error: File not found: {conversations_json}", err=True)
184
- raise typer.Exit(1)
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
- # Get markdown processor
187
- markdown_processor = asyncio.run(get_markdown_processor())
188
-
189
- # Process the file
190
- base_path = config.home / folder
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
- console.print("\nRun 'basic-memory sync' to index the new files.")
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
- except Exception as e:
209
- logger.error("Import failed")
210
- typer.echo(f"Error during import: {e}", err=True)
211
- raise typer.Exit(1)
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)
@@ -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
- try:
165
- if projects_json:
166
- if not projects_json.exists():
167
- typer.echo(f"Error: File not found: {projects_json}", err=True)
168
- raise typer.Exit(1)
169
-
170
- # Get markdown processor
171
- markdown_processor = asyncio.run(get_markdown_processor())
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
- console.print("\nRun 'basic-memory sync' to index the new files.")
191
+ console.print("\nRun 'basic-memory sync' to index the new files.")
191
192
 
192
- except Exception as e:
193
- logger.error("Import failed")
194
- typer.echo(f"Error during import: {e}", err=True)
195
- raise typer.Exit(1)
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)