basic-memory 0.4.3__tar.gz → 0.6.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 (148) hide show
  1. {basic_memory-0.4.3 → basic_memory-0.6.0}/CHANGELOG.md +21 -0
  2. {basic_memory-0.4.3 → basic_memory-0.6.0}/Makefile +2 -2
  3. {basic_memory-0.4.3 → basic_memory-0.6.0}/PKG-INFO +2 -1
  4. {basic_memory-0.4.3 → basic_memory-0.6.0}/installer/installer.py +8 -1
  5. {basic_memory-0.4.3 → basic_memory-0.6.0}/pyproject.toml +2 -1
  6. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/__init__.py +1 -1
  7. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/api/app.py +7 -0
  8. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/api/routers/resource_router.py +1 -1
  9. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/cli/main.py +0 -4
  10. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/config.py +5 -0
  11. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/mcp/tools/notes.py +41 -23
  12. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/utils.py +35 -4
  13. basic_memory-0.6.0/tests/__init__.py +4 -0
  14. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/api/test_resource_router.py +0 -1
  15. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/mcp/test_tool_knowledge.py +11 -10
  16. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/mcp/test_tool_notes.py +129 -66
  17. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/mcp/test_tool_search.py +3 -2
  18. basic_memory-0.6.0/tests/test_basic_memory.py +47 -0
  19. {basic_memory-0.4.3 → basic_memory-0.6.0}/uv.lock +384 -1
  20. basic_memory-0.4.3/tests/mcp/test_tool_get_entity.py +0 -45
  21. basic_memory-0.4.3/tests/test_basic_memory.py +0 -8
  22. {basic_memory-0.4.3 → basic_memory-0.6.0}/.github/workflows/pr-title.yml +0 -0
  23. {basic_memory-0.4.3 → basic_memory-0.6.0}/.github/workflows/release.yml +0 -0
  24. {basic_memory-0.4.3 → basic_memory-0.6.0}/.github/workflows/test.yml +0 -0
  25. {basic_memory-0.4.3 → basic_memory-0.6.0}/.gitignore +0 -0
  26. {basic_memory-0.4.3 → basic_memory-0.6.0}/.python-version +0 -0
  27. {basic_memory-0.4.3 → basic_memory-0.6.0}/CITATION.cff +0 -0
  28. {basic_memory-0.4.3 → basic_memory-0.6.0}/CODE_OF_CONDUCT.md +0 -0
  29. {basic_memory-0.4.3 → basic_memory-0.6.0}/CONTRIBUTING.md +0 -0
  30. {basic_memory-0.4.3 → basic_memory-0.6.0}/LICENSE +0 -0
  31. {basic_memory-0.4.3 → basic_memory-0.6.0}/README.md +0 -0
  32. {basic_memory-0.4.3 → basic_memory-0.6.0}/basic-memory.md +0 -0
  33. {basic_memory-0.4.3 → basic_memory-0.6.0}/installer/Basic.icns +0 -0
  34. {basic_memory-0.4.3 → basic_memory-0.6.0}/installer/README.md +0 -0
  35. {basic_memory-0.4.3 → basic_memory-0.6.0}/installer/icon.svg +0 -0
  36. {basic_memory-0.4.3 → basic_memory-0.6.0}/installer/make_icons.sh +0 -0
  37. {basic_memory-0.4.3 → basic_memory-0.6.0}/installer/setup.py +0 -0
  38. {basic_memory-0.4.3 → basic_memory-0.6.0}/memory.json +0 -0
  39. {basic_memory-0.4.3 → basic_memory-0.6.0}/scripts/install.sh +0 -0
  40. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/alembic/README +0 -0
  41. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/alembic/env.py +0 -0
  42. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/alembic/migrations.py +0 -0
  43. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/alembic/script.py.mako +0 -0
  44. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/alembic/versions/3dae7c7b1564_initial_schema.py +0 -0
  45. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/api/__init__.py +0 -0
  46. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/api/routers/__init__.py +0 -0
  47. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/api/routers/knowledge_router.py +0 -0
  48. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/api/routers/memory_router.py +0 -0
  49. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/api/routers/search_router.py +0 -0
  50. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/cli/__init__.py +0 -0
  51. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/cli/app.py +0 -0
  52. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/cli/commands/__init__.py +0 -0
  53. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/cli/commands/db.py +0 -0
  54. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/cli/commands/import_chatgpt.py +0 -0
  55. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/cli/commands/import_claude_conversations.py +0 -0
  56. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/cli/commands/import_claude_projects.py +0 -0
  57. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/cli/commands/import_memory_json.py +0 -0
  58. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/cli/commands/mcp.py +0 -0
  59. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/cli/commands/status.py +0 -0
  60. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/cli/commands/sync.py +0 -0
  61. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/db.py +0 -0
  62. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/deps.py +0 -0
  63. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/file_utils.py +0 -0
  64. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/markdown/__init__.py +0 -0
  65. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/markdown/entity_parser.py +0 -0
  66. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/markdown/markdown_processor.py +0 -0
  67. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/markdown/plugins.py +0 -0
  68. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/markdown/schemas.py +0 -0
  69. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/markdown/utils.py +0 -0
  70. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/mcp/__init__.py +0 -0
  71. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/mcp/async_client.py +0 -0
  72. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/mcp/server.py +0 -0
  73. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/mcp/tools/__init__.py +0 -0
  74. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/mcp/tools/knowledge.py +0 -0
  75. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/mcp/tools/memory.py +0 -0
  76. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/mcp/tools/search.py +0 -0
  77. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/mcp/tools/utils.py +0 -0
  78. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/models/__init__.py +0 -0
  79. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/models/base.py +0 -0
  80. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/models/knowledge.py +0 -0
  81. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/models/search.py +0 -0
  82. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/repository/__init__.py +0 -0
  83. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/repository/entity_repository.py +0 -0
  84. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/repository/observation_repository.py +0 -0
  85. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/repository/relation_repository.py +0 -0
  86. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/repository/repository.py +0 -0
  87. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/repository/search_repository.py +0 -0
  88. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/schemas/__init__.py +0 -0
  89. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/schemas/base.py +0 -0
  90. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/schemas/delete.py +0 -0
  91. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/schemas/discovery.py +0 -0
  92. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/schemas/memory.py +0 -0
  93. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/schemas/request.py +0 -0
  94. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/schemas/response.py +0 -0
  95. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/schemas/search.py +0 -0
  96. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/services/__init__.py +0 -0
  97. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/services/context_service.py +0 -0
  98. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/services/entity_service.py +0 -0
  99. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/services/exceptions.py +0 -0
  100. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/services/file_service.py +0 -0
  101. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/services/link_resolver.py +0 -0
  102. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/services/search_service.py +0 -0
  103. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/services/service.py +0 -0
  104. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/sync/__init__.py +0 -0
  105. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/sync/file_change_scanner.py +0 -0
  106. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/sync/sync_service.py +0 -0
  107. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/sync/utils.py +0 -0
  108. {basic_memory-0.4.3 → basic_memory-0.6.0}/src/basic_memory/sync/watch_service.py +0 -0
  109. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/api/conftest.py +0 -0
  110. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/api/test_knowledge_router.py +0 -0
  111. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/api/test_memory_router.py +0 -0
  112. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/api/test_search_router.py +0 -0
  113. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/cli/test_import_chatgpt.py +0 -0
  114. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/cli/test_import_claude_conversations.py +0 -0
  115. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/cli/test_import_claude_projects.py +0 -0
  116. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/cli/test_import_memory_json.py +0 -0
  117. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/cli/test_status.py +0 -0
  118. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/cli/test_sync.py +0 -0
  119. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/conftest.py +0 -0
  120. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/edit_file_test.py +0 -0
  121. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/markdown/__init__.py +0 -0
  122. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/markdown/test_entity_parser.py +0 -0
  123. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/markdown/test_markdown_plugins.py +0 -0
  124. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/markdown/test_markdown_processor.py +0 -0
  125. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/markdown/test_observation_edge_cases.py +0 -0
  126. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/markdown/test_parser_edge_cases.py +0 -0
  127. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/markdown/test_relation_edge_cases.py +0 -0
  128. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/markdown/test_task_detection.py +0 -0
  129. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/mcp/conftest.py +0 -0
  130. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/mcp/test_tool_memory.py +0 -0
  131. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/mcp/test_tool_utils.py +0 -0
  132. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/repository/test_entity_repository.py +0 -0
  133. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/repository/test_observation_repository.py +0 -0
  134. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/repository/test_relation_repository.py +0 -0
  135. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/repository/test_repository.py +0 -0
  136. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/schemas/test_memory_url.py +0 -0
  137. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/schemas/test_schemas.py +0 -0
  138. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/schemas/test_search.py +0 -0
  139. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/services/test_context_service.py +0 -0
  140. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/services/test_entity_service.py +0 -0
  141. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/services/test_file_service.py +0 -0
  142. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/services/test_link_resolver.py +0 -0
  143. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/services/test_search_service.py +0 -0
  144. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/sync/test_file_change_scanner.py +0 -0
  145. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/sync/test_sync_service.py +0 -0
  146. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/sync/test_watch_service.py +0 -0
  147. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/utils/test_file_utils.py +0 -0
  148. {basic_memory-0.4.3 → basic_memory-0.6.0}/tests/utils/test_permalink_formatting.py +0 -0
@@ -1,6 +1,27 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v0.6.0 (2025-02-18)
5
+
6
+ ### Features
7
+
8
+ - Configure logfire telemetry ([#12](https://github.com/basicmachines-co/basic-memory/pull/12),
9
+ [`6da1438`](https://github.com/basicmachines-co/basic-memory/commit/6da143898bd45cdab8db95b5f2b75810fbb741ba))
10
+
11
+ Co-authored-by: phernandez <phernandez@basicmachines.co>
12
+
13
+
14
+ ## v0.5.0 (2025-02-18)
15
+
16
+ ### Features
17
+
18
+ - Return semantic info in markdown after write_note
19
+ ([#11](https://github.com/basicmachines-co/basic-memory/pull/11),
20
+ [`0689e7a`](https://github.com/basicmachines-co/basic-memory/commit/0689e7a730497827bf4e16156ae402ddc5949077))
21
+
22
+ Co-authored-by: phernandez <phernandez@basicmachines.co>
23
+
24
+
4
25
  ## v0.4.3 (2025-02-18)
5
26
 
6
27
  ### Bug Fixes
@@ -4,7 +4,7 @@ install:
4
4
  pip install -e ".[dev]"
5
5
 
6
6
  test:
7
- pytest -p pytest_mock -v
7
+ uv run pytest -p pytest_mock -v
8
8
 
9
9
  lint:
10
10
  ruff check . --fix
@@ -40,4 +40,4 @@ installer-win:
40
40
 
41
41
 
42
42
  update-deps:
43
- uv lock f--upgrade
43
+ uv lock f--upgrade
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basic-memory
3
- Version: 0.4.3
3
+ Version: 0.6.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,6 +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
19
  Requires-Dist: loguru>=0.7.3
19
20
  Requires-Dist: markdown-it-py>=3.0.0
20
21
  Requires-Dist: mcp>=1.2.0
@@ -49,7 +49,14 @@ def update_claude_config():
49
49
  config = {"mcpServers": {}}
50
50
 
51
51
  # Add/update basic-memory config
52
- config["mcpServers"]["basic-memory"] = {"command": "uvx", "args": ["basic-memory", "mcp"]}
52
+ config["mcpServers"]["basic-memory"] = {
53
+ "command": "uvx",
54
+ "args": ["basic-memory@latest", "mcp"],
55
+ "env": {
56
+ "BASIC_MEMORY_ENV": "user",
57
+ "LOGFIRE_TOKEN": "n2Fpvn34LjKYq8TdF1ZrXMgdBPXGn4HfXy6tYghZ55dB",
58
+ },
59
+ }
53
60
 
54
61
  # Write back config
55
62
  config_path.write_text(json.dumps(config, indent=2))
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "basic-memory"
3
- version = "0.4.3"
3
+ version = "0.6.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,6 +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
33
  ]
33
34
 
34
35
 
@@ -1,3 +1,3 @@
1
1
  """basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
2
2
 
3
- __version__ = "0.4.3"
3
+ __version__ = "0.6.0"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from contextlib import asynccontextmanager
4
4
 
5
+ import logfire
5
6
  from fastapi import FastAPI, HTTPException
6
7
  from fastapi.exception_handlers import http_exception_handler
7
8
  from loguru import logger
@@ -10,11 +11,13 @@ import basic_memory
10
11
  from basic_memory import db
11
12
  from basic_memory.config import config as app_config
12
13
  from basic_memory.api.routers import knowledge, search, memory, resource
14
+ from basic_memory.utils import setup_logging
13
15
 
14
16
 
15
17
  @asynccontextmanager
16
18
  async def lifespan(app: FastAPI): # pragma: no cover
17
19
  """Lifecycle manager for the FastAPI app."""
20
+ setup_logging(log_file=".basic-memory/basic-memory.log")
18
21
  logger.info(f"Starting Basic Memory API {basic_memory.__version__}")
19
22
  await db.run_migrations(app_config)
20
23
  yield
@@ -30,6 +33,10 @@ app = FastAPI(
30
33
  lifespan=lifespan,
31
34
  )
32
35
 
36
+ if app_config != "test":
37
+ logfire.instrument_fastapi(app)
38
+
39
+
33
40
  # Include routers
34
41
  app.include_router(knowledge.router)
35
42
  app.include_router(search.router)
@@ -31,7 +31,7 @@ def get_entity_ids(item: SearchIndexRow) -> list[int]:
31
31
  from_entity = item.from_id
32
32
  to_entity = item.to_id # pyright: ignore [reportReturnType]
33
33
  return [from_entity, to_entity] if to_entity else [from_entity] # pyright: ignore [reportReturnType]
34
- case _:
34
+ case _: # pragma: no cover
35
35
  raise ValueError(f"Unexpected type: {item.type}")
36
36
 
37
37
 
@@ -1,7 +1,6 @@
1
1
  """Main CLI entry point for basic-memory.""" # pragma: no cover
2
2
 
3
3
  from basic_memory.cli.app import app # pragma: no cover
4
- from basic_memory.utils import setup_logging # pragma: no cover
5
4
 
6
5
  # Register commands
7
6
  from basic_memory.cli.commands import ( # noqa: F401 # pragma: no cover
@@ -16,8 +15,5 @@ from basic_memory.cli.commands import ( # noqa: F401 # pragma: no cover
16
15
  )
17
16
 
18
17
 
19
- # Set up logging when module is imported
20
- setup_logging(log_file=".basic-memory/basic-memory-cli.log") # pragma: no cover
21
-
22
18
  if __name__ == "__main__": # pragma: no cover
23
19
  app()
@@ -1,6 +1,7 @@
1
1
  """Configuration management for basic-memory."""
2
2
 
3
3
  from pathlib import Path
4
+ from typing import Literal
4
5
 
5
6
  from pydantic import Field, field_validator
6
7
  from pydantic_settings import BaseSettings, SettingsConfigDict
@@ -8,10 +9,14 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
8
9
  DATABASE_NAME = "memory.db"
9
10
  DATA_DIR_NAME = ".basic-memory"
10
11
 
12
+ Environment = Literal["test", "dev", "prod"]
13
+
11
14
 
12
15
  class ProjectConfig(BaseSettings):
13
16
  """Configuration for a specific basic-memory project."""
14
17
 
18
+ env: Environment = Field(default="dev", description="Environment name")
19
+
15
20
  # Default to ~/basic-memory but allow override with env var: BASIC_MEMORY_HOME
16
21
  home: Path = Field(
17
22
  default_factory=lambda: Path.home() / "basic-memory",
@@ -17,15 +17,14 @@ from basic_memory.schemas.memory import memory_url_path
17
17
 
18
18
 
19
19
  @mcp.tool(
20
- description="Create or update a markdown note. Returns the permalink for referencing.",
20
+ description="Create or update a markdown note. Returns a markdown formatted summary of the semantic content.",
21
21
  )
22
22
  async def write_note(
23
23
  title: str,
24
24
  content: str,
25
25
  folder: str,
26
26
  tags: Optional[List[str]] = None,
27
- verbose: bool = False,
28
- ) -> EntityResponse | str:
27
+ ) -> str:
29
28
  """Write a markdown note to the knowledge base.
30
29
 
31
30
  The content can include semantic observations and relations using markdown syntax.
@@ -53,14 +52,16 @@ async def write_note(
53
52
  content: Markdown content for the note, can include observations and relations
54
53
  folder: the folder where the file should be saved
55
54
  tags: Optional list of tags to categorize the note
56
- verbose: If True, returns full EntityResponse with semantic info
57
55
 
58
56
  Returns:
59
- If verbose=False: Permalink that can be used to reference the note
60
- If verbose=True: EntityResponse with full semantic details
57
+ A markdown formatted summary of the semantic content, including:
58
+ - Creation/update status
59
+ - File path and checksum
60
+ - Observation counts by category
61
+ - Relation counts (resolved/unresolved)
62
+ - Tags if present
61
63
 
62
64
  Examples:
63
- # Note with both explicit and inline relations
64
65
  write_note(
65
66
  title="Search Implementation",
66
67
  content="# Search Component\\n\\n"
@@ -73,20 +74,6 @@ async def write_note(
73
74
  "- depends_on [[Database Schema]]",
74
75
  folder="docs/components"
75
76
  )
76
-
77
- # Note with tags
78
- write_note(
79
- title="Error Handling Design",
80
- content="# Error Handling\\n\\n"
81
- "This design builds on [[Reliability Design]].\\n\\n"
82
- "## Approach\\n"
83
- "- [design] Use error codes #architecture\\n"
84
- "- [tech] Implement retry logic #implementation\\n\\n"
85
- "## Relations\\n"
86
- "- extends [[Base Error Handling]]",
87
- folder="docs/design",
88
- tags=["architecture", "reliability"]
89
- )
90
77
  """
91
78
  logger.info(f"Writing note folder:'{folder}' title: '{title}'")
92
79
 
@@ -101,12 +88,43 @@ async def write_note(
101
88
  entity_metadata=metadata,
102
89
  )
103
90
 
104
- # Use existing knowledge tool
91
+ # Create or update via knowledge API
105
92
  logger.info(f"Creating {entity.permalink}")
106
93
  url = f"/knowledge/entities/{entity.permalink}"
107
94
  response = await call_put(client, url, json=entity.model_dump())
108
95
  result = EntityResponse.model_validate(response.json())
109
- return result if verbose else result.permalink
96
+
97
+ # Format semantic summary based on status code
98
+ action = "Created" if response.status_code == 201 else "Updated"
99
+ assert result.checksum is not None
100
+ summary = [
101
+ f"# {action} {result.file_path} ({result.checksum[:8]})",
102
+ f"permalink: {result.permalink}",
103
+ ]
104
+
105
+ if result.observations:
106
+ categories = {}
107
+ for obs in result.observations:
108
+ categories[obs.category] = categories.get(obs.category, 0) + 1
109
+
110
+ summary.append("\n## Observations")
111
+ for category, count in sorted(categories.items()):
112
+ summary.append(f"- {category}: {count}")
113
+
114
+ if result.relations:
115
+ unresolved = sum(1 for r in result.relations if not r.to_id)
116
+ resolved = len(result.relations) - unresolved
117
+
118
+ summary.append("\n## Relations")
119
+ summary.append(f"- Resolved: {resolved}")
120
+ if unresolved:
121
+ summary.append(f"- Unresolved: {unresolved}")
122
+ summary.append("\nUnresolved relations will be retried on next sync.")
123
+
124
+ if tags:
125
+ summary.append(f"\n## Tags\n- {', '.join(tags)}")
126
+
127
+ return "\n".join(summary)
110
128
 
111
129
 
112
130
  @mcp.tool(description="Read note content by title, permalink, relation, or pattern")
@@ -9,8 +9,11 @@ from typing import Optional, Union
9
9
  from loguru import logger
10
10
  from unidecode import unidecode
11
11
 
12
+ import basic_memory
12
13
  from basic_memory.config import config
13
14
 
15
+ import logfire
16
+
14
17
 
15
18
  def generate_permalink(file_path: Union[Path, str]) -> str:
16
19
  """Generate a stable permalink from a file path.
@@ -61,19 +64,45 @@ def generate_permalink(file_path: Union[Path, str]) -> str:
61
64
  return "/".join(clean_segments)
62
65
 
63
66
 
64
- def setup_logging(home_dir: Path = config.home, log_file: Optional[str] = None) -> None:
67
+ def setup_logging(
68
+ home_dir: Path = config.home, log_file: Optional[str] = None
69
+ ) -> None: # pragma: no cover
65
70
  """
66
71
  Configure logging for the application.
72
+ :param home_dir: the root directory for the application
73
+ :param log_file: the name of the log file to write to
74
+ :param app: the fastapi application instance
67
75
  """
68
76
 
69
77
  # Remove default handler and any existing handlers
70
78
  logger.remove()
71
79
 
72
- # Add file handler
73
- if log_file:
80
+ # Add file handler if we are not running tests
81
+ if log_file and config.env != "test":
82
+ # enable pydantic logfire
83
+ logfire.configure(
84
+ code_source=logfire.CodeSource(
85
+ repository="https://github.com/basicmachines-co/basic-memory",
86
+ revision=basic_memory.__version__,
87
+ root_path="/src/basic_memory",
88
+ ),
89
+ environment=config.env,
90
+ )
91
+ logger.configure(handlers=[logfire.loguru_handler()])
92
+
93
+ # instrument code spans
94
+ logfire.instrument_sqlite3()
95
+ logfire.instrument_pydantic()
96
+
97
+ from basic_memory.db import _engine as engine
98
+
99
+ if engine:
100
+ logfire.instrument_sqlalchemy(engine=engine)
101
+
102
+ # setup logger
74
103
  log_path = home_dir / log_file
75
104
  logger.add(
76
- str(log_path), # loguru expects a string path
105
+ str(log_path),
77
106
  level=config.log_level,
78
107
  rotation="100 MB",
79
108
  retention="10 days",
@@ -85,3 +114,5 @@ def setup_logging(home_dir: Path = config.home, log_file: Optional[str] = None)
85
114
 
86
115
  # Add stderr handler
87
116
  logger.add(sys.stderr, level=config.log_level, backtrace=True, diagnose=True, colorize=True)
117
+
118
+ logger.info(f"ENV: '{config.env}' Log level: '{config.log_level}' Logging to {log_file}")
@@ -0,0 +1,4 @@
1
+ from basic_memory.config import config
2
+
3
+ # set config.env to "test" for pytest to prevent logging to file in utils.setup_logging()
4
+ config.env = "test"
@@ -158,7 +158,6 @@ async def test_get_resource_entities(client, test_config, entity_repository):
158
158
  entity2 = EntityResponse(**entity_response)
159
159
 
160
160
  assert len(entity2.relations) == 1
161
- relation = entity2.relations[0]
162
161
 
163
162
  # Test getting the content via the relation
164
163
  response = await client.get("/resource/test/*")
@@ -13,7 +13,7 @@ from basic_memory.schemas.delete import DeleteEntitiesRequest
13
13
  async def test_get_single_entity(client):
14
14
  """Test retrieving a single entity."""
15
15
  # First create an entity
16
- permalink = await notes.write_note(
16
+ result = await notes.write_note(
17
17
  title="Test Note",
18
18
  folder="test",
19
19
  content="""
@@ -22,9 +22,10 @@ async def test_get_single_entity(client):
22
22
  """,
23
23
  tags=["test", "documentation"],
24
24
  )
25
+ assert result
25
26
 
26
27
  # Get the entity
27
- entity = await get_entity(permalink)
28
+ entity = await get_entity("test/test-note")
28
29
 
29
30
  # Verify entity details
30
31
  assert entity.title == "Test Note"
@@ -36,40 +37,40 @@ async def test_get_single_entity(client):
36
37
  async def test_get_multiple_entities(client):
37
38
  """Test retrieving multiple entities."""
38
39
  # Create two test entities
39
- permalink1 = await notes.write_note(
40
+ await notes.write_note(
40
41
  title="Test Note 1",
41
42
  folder="test",
42
43
  content="# Test 1",
43
44
  )
44
- permalink2 = await notes.write_note(
45
+ await notes.write_note(
45
46
  title="Test Note 2",
46
47
  folder="test",
47
48
  content="# Test 2",
48
49
  )
49
50
 
50
51
  # Get both entities
51
- request = GetEntitiesRequest(permalinks=[permalink1, permalink2])
52
+ request = GetEntitiesRequest(permalinks=["test/test-note-1", "test/test-note-2"])
52
53
  response = await get_entities(request)
53
54
 
54
55
  # Verify we got both entities
55
56
  assert len(response.entities) == 2
56
57
  permalinks = {e.permalink for e in response.entities}
57
- assert permalink1 in permalinks
58
- assert permalink2 in permalinks
58
+ assert "test/test-note-1" in permalinks
59
+ assert "test/test-note-2" in permalinks
59
60
 
60
61
 
61
62
  @pytest.mark.asyncio
62
63
  async def test_delete_entities(client):
63
64
  """Test deleting entities."""
64
65
  # Create a test entity
65
- permalink = await notes.write_note(
66
+ await notes.write_note(
66
67
  title="Test Note",
67
68
  folder="test",
68
69
  content="# Test Note to Delete",
69
70
  )
70
71
 
71
72
  # Delete the entity
72
- request = DeleteEntitiesRequest(permalinks=[permalink])
73
+ request = DeleteEntitiesRequest(permalinks=["test/test-note"])
73
74
  response = await delete_entities(request)
74
75
 
75
76
  # Verify deletion
@@ -77,7 +78,7 @@ async def test_delete_entities(client):
77
78
 
78
79
  # Verify entity no longer exists
79
80
  with pytest.raises(ToolError):
80
- await get_entity(permalink)
81
+ await get_entity("test/test-note")
81
82
 
82
83
 
83
84
  @pytest.mark.asyncio