basic-memory 0.12.2__py3-none-any.whl → 0.12.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of basic-memory might be problematic. Click here for more details.

basic_memory/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
2
2
 
3
- __version__ = "0.12.2"
3
+ __version__ = "0.12.3"
@@ -111,7 +111,7 @@ def format_not_found_message(identifier: str) -> str:
111
111
  ## Search Instead
112
112
  Try searching for related content:
113
113
  ```
114
- search(query="{identifier}")
114
+ search_notes(query="{identifier}")
115
115
  ```
116
116
 
117
117
  ## Recent Activity
@@ -172,7 +172,7 @@ def format_related_results(identifier: str, results) -> str:
172
172
  ## Search For More Results
173
173
  To see more related content:
174
174
  ```
175
- search(query="{identifier}")
175
+ search_notes(query="{identifier}")
176
176
  ```
177
177
 
178
178
  ## Create New Note
@@ -1,6 +1,6 @@
1
1
  """Recent activity tool for Basic Memory MCP server."""
2
2
 
3
- from typing import Optional, List
3
+ from typing import List, Union
4
4
 
5
5
  from loguru import logger
6
6
 
@@ -14,7 +14,7 @@ from basic_memory.schemas.search import SearchItemType
14
14
 
15
15
  @mcp.tool(
16
16
  description="""Get recent activity from across the knowledge base.
17
-
17
+
18
18
  Timeframe supports natural language formats like:
19
19
  - "2 days ago"
20
20
  - "last week"
@@ -25,9 +25,9 @@ from basic_memory.schemas.search import SearchItemType
25
25
  """,
26
26
  )
27
27
  async def recent_activity(
28
- type: Optional[List[SearchItemType]] = None,
29
- depth: Optional[int] = 1,
30
- timeframe: Optional[TimeFrame] = "7d",
28
+ type: Union[str, List[str]] = "",
29
+ depth: int = 1,
30
+ timeframe: TimeFrame = "7d",
31
31
  page: int = 1,
32
32
  page_size: int = 10,
33
33
  max_related: int = 10,
@@ -35,11 +35,14 @@ async def recent_activity(
35
35
  """Get recent activity across the knowledge base.
36
36
 
37
37
  Args:
38
- type: Filter by content type(s). Valid options:
39
- - ["entity"] for knowledge entities
40
- - ["relation"] for connections between entities
41
- - ["observation"] for notes and observations
38
+ type: Filter by content type(s). Can be a string or list of strings.
39
+ Valid options:
40
+ - "entity" or ["entity"] for knowledge entities
41
+ - "relation" or ["relation"] for connections between entities
42
+ - "observation" or ["observation"] for notes and observations
42
43
  Multiple types can be combined: ["entity", "relation"]
44
+ Case-insensitive: "ENTITY" and "entity" are treated the same.
45
+ Default is an empty string, which returns all types.
43
46
  depth: How many relation hops to traverse (1-3 recommended)
44
47
  timeframe: Time window to search. Supports natural language:
45
48
  - Relative: "2 days ago", "last week", "yesterday"
@@ -59,14 +62,17 @@ async def recent_activity(
59
62
  # Get all entities for the last 10 days (default)
60
63
  recent_activity()
61
64
 
62
- # Get all entities from yesterday
65
+ # Get all entities from yesterday (string format)
66
+ recent_activity(type="entity", timeframe="yesterday")
67
+
68
+ # Get all entities from yesterday (list format)
63
69
  recent_activity(type=["entity"], timeframe="yesterday")
64
70
 
65
71
  # Get recent relations and observations
66
72
  recent_activity(type=["relation", "observation"], timeframe="today")
67
73
 
68
74
  # Look back further with more context
69
- recent_activity(type=["entity"], depth=2, timeframe="2 weeks ago")
75
+ recent_activity(type="entity", depth=2, timeframe="2 weeks ago")
70
76
 
71
77
  Notes:
72
78
  - Higher depth values (>3) may impact performance with large result sets
@@ -86,11 +92,27 @@ async def recent_activity(
86
92
  if timeframe:
87
93
  params["timeframe"] = timeframe # pyright: ignore
88
94
 
89
- # send enum values if we have an enum, else send string value
95
+ # Validate and convert type parameter
90
96
  if type:
91
- params["type"] = [ # pyright: ignore
92
- type.value if isinstance(type, SearchItemType) else type for type in type
93
- ]
97
+ # Convert single string to list
98
+ if isinstance(type, str):
99
+ type_list = [type]
100
+ else:
101
+ type_list = type
102
+
103
+ # Validate each type against SearchItemType enum
104
+ validated_types = []
105
+ for t in type_list:
106
+ try:
107
+ # Try to convert string to enum
108
+ if isinstance(t, str):
109
+ validated_types.append(SearchItemType(t.lower()))
110
+ except ValueError:
111
+ valid_types = [t.value for t in SearchItemType]
112
+ raise ValueError(f"Invalid type: {t}. Valid types are: {valid_types}")
113
+
114
+ # Add validated types to params
115
+ params["type"] = [t.value for t in validated_types] # pyright: ignore
94
116
 
95
117
  response = await call_get(
96
118
  client,
basic_memory/utils.py CHANGED
@@ -5,11 +5,11 @@ import os
5
5
  import logging
6
6
  import re
7
7
  import sys
8
+ import unicodedata
8
9
  from pathlib import Path
9
- from typing import Optional, Protocol, Union, runtime_checkable, List
10
+ from typing import Optional, Protocol, Union, runtime_checkable, List, Any
10
11
 
11
12
  from loguru import logger
12
- from unidecode import unidecode
13
13
 
14
14
 
15
15
  @runtime_checkable
@@ -27,23 +27,23 @@ FilePath = Union[Path, str]
27
27
  logging.getLogger("opentelemetry.sdk.metrics._internal.instrument").setLevel(logging.ERROR)
28
28
 
29
29
 
30
- def generate_permalink(file_path: Union[Path, str, PathLike]) -> str:
31
- """Generate a stable permalink from a file path.
32
-
33
- Args:
34
- file_path: Original file path (str, Path, or PathLike)
30
+ def generate_permalink(file_path: Union[Path, str, Any]) -> str:
31
+ """
32
+ Generate a permalink from a file path.
35
33
 
36
34
  Returns:
37
35
  Normalized permalink that matches validation rules. Converts spaces and underscores
38
- to hyphens for consistency.
36
+ to hyphens for consistency. Preserves non-ASCII characters like Chinese.
39
37
 
40
38
  Examples:
41
39
  >>> generate_permalink("docs/My Feature.md")
42
40
  'docs/my-feature'
43
- >>> generate_permalink("specs/API (v2).md")
41
+ >>> generate_permalink("specs/API_v2.md")
44
42
  'specs/api-v2'
45
43
  >>> generate_permalink("design/unified_model_refactor.md")
46
44
  'design/unified-model-refactor'
45
+ >>> generate_permalink("中文/测试文档.md")
46
+ '中文/测试文档'
47
47
  """
48
48
  # Convert Path to string if needed
49
49
  path_str = str(file_path)
@@ -51,24 +51,74 @@ def generate_permalink(file_path: Union[Path, str, PathLike]) -> str:
51
51
  # Remove extension
52
52
  base = os.path.splitext(path_str)[0]
53
53
 
54
- # Transliterate unicode to ascii
55
- ascii_text = unidecode(base)
54
+ # Create a transliteration mapping for specific characters
55
+ transliteration_map = {
56
+ "ø": "o", # Handle Søren -> soren
57
+ "å": "a", # Handle Kierkegård -> kierkegard
58
+ "ü": "u", # Handle Müller -> muller
59
+ "é": "e", # Handle Café -> cafe
60
+ "è": "e", # Handle Mère -> mere
61
+ "ê": "e", # Handle Fête -> fete
62
+ "à": "a", # Handle À la mode -> a la mode
63
+ "ç": "c", # Handle Façade -> facade
64
+ "ñ": "n", # Handle Niño -> nino
65
+ "ö": "o", # Handle Björk -> bjork
66
+ "ä": "a", # Handle Häagen -> haagen
67
+ # Add more mappings as needed
68
+ }
69
+
70
+ # Process character by character, transliterating Latin characters with diacritics
71
+ result = ""
72
+ for char in base:
73
+ # Direct mapping for known characters
74
+ if char.lower() in transliteration_map:
75
+ result += transliteration_map[char.lower()]
76
+ # General case using Unicode normalization
77
+ elif unicodedata.category(char).startswith("L") and ord(char) > 127:
78
+ # Decompose the character (e.g., ü -> u + combining diaeresis)
79
+ decomposed = unicodedata.normalize("NFD", char)
80
+ # If decomposition produced multiple characters and first one is ASCII
81
+ if len(decomposed) > 1 and ord(decomposed[0]) < 128:
82
+ # Keep only the base character
83
+ result += decomposed[0].lower()
84
+ else:
85
+ # For non-Latin scripts like Chinese, preserve the character
86
+ result += char
87
+ else:
88
+ # Add the character as is
89
+ result += char
90
+
91
+ # Handle special punctuation cases for apostrophes
92
+ result = result.replace("'", "")
56
93
 
57
94
  # Insert dash between camelCase
58
- ascii_text = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", ascii_text)
95
+ # This regex finds boundaries between lowercase and uppercase letters
96
+ result = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", result)
97
+
98
+ # Insert dash between Chinese and Latin character boundaries
99
+ # This is needed for cases like "中文English" -> "中文-english"
100
+ result = re.sub(r"([\u4e00-\u9fff])([a-zA-Z])", r"\1-\2", result)
101
+ result = re.sub(r"([a-zA-Z])([\u4e00-\u9fff])", r"\1-\2", result)
59
102
 
60
- # Convert to lowercase
61
- lower_text = ascii_text.lower()
103
+ # Convert ASCII letters to lowercase, preserve non-ASCII characters
104
+ lower_text = "".join(c.lower() if c.isascii() and c.isalpha() else c for c in result)
62
105
 
63
- # replace underscores with hyphens
106
+ # Replace underscores with hyphens
64
107
  text_with_hyphens = lower_text.replace("_", "-")
65
108
 
66
- # Replace remaining invalid chars with hyphens
67
- clean_text = re.sub(r"[^a-z0-9/\-]", "-", text_with_hyphens)
109
+ # Replace spaces and unsafe ASCII characters with hyphens, but preserve non-ASCII characters
110
+ # Include common Chinese character ranges and other non-ASCII characters
111
+ clean_text = re.sub(
112
+ r"[^a-z0-9\u4e00-\u9fff\u3000-\u303f\u3400-\u4dbf/\-]", "-", text_with_hyphens
113
+ )
68
114
 
69
115
  # Collapse multiple hyphens
70
116
  clean_text = re.sub(r"-+", "-", clean_text)
71
117
 
118
+ # Remove hyphens between adjacent Chinese characters only
119
+ # This handles cases like "你好-世界" -> "你好世界"
120
+ clean_text = re.sub(r"([\u4e00-\u9fff])-([\u4e00-\u9fff])", r"\1\2", clean_text)
121
+
72
122
  # Clean each path segment
73
123
  segments = clean_text.split("/")
74
124
  clean_segments = [s.strip("-") for s in segments]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basic-memory
3
- Version: 0.12.2
3
+ Version: 0.12.3
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
@@ -367,9 +367,9 @@ config:
367
367
  "command": "uvx",
368
368
  "args": [
369
369
  "basic-memory",
370
- "mcp",
371
370
  "--project",
372
- "your-project-name"
371
+ "your-project-name",
372
+ "mcp"
373
373
  ]
374
374
  }
375
375
  }
@@ -1,9 +1,9 @@
1
- basic_memory/__init__.py,sha256=9r3qVuD_n9X5pYE_X1n8qlu86LtJPBHz5Z4U15_KkCM,123
1
+ basic_memory/__init__.py,sha256=nFAcIbL7D1Mt34yxum-5H8dZECTf5-PIvqTAopg2nCg,123
2
2
  basic_memory/config.py,sha256=jZmBOj4Gl2l56pApiN88s6juPDaX1g2LcvuVUnUeG0Q,9203
3
3
  basic_memory/db.py,sha256=8SmrmNAlJlmYT9yIJiPwNq8SN8mB2rbW5t33Rqpyl2I,6052
4
4
  basic_memory/deps.py,sha256=yI6RL_5-8LXw7ywSJ_84BXAczDtv2h9GFLw-E9XDJFg,5770
5
5
  basic_memory/file_utils.py,sha256=eaxTKLLEbTIy_Mb_Iv_Dmt4IXAJSrZGVi-Knrpyci3E,6700
6
- basic_memory/utils.py,sha256=2dbUbChLn5gwQgPY-m5XKYOBbeKFEwnRUrZDsm2kGIE,5026
6
+ basic_memory/utils.py,sha256=BL6DDRiMF1gNcDr_guRAYflooSrSlDniJh96ApdzuDY,7555
7
7
  basic_memory/alembic/alembic.ini,sha256=IEZsnF8CbbZnkwBr67LzKKNobHuzTaQNUvM8Psop5xc,3733
8
8
  basic_memory/alembic/env.py,sha256=GyQpEpQu84flqAdelxR0-H9nbkHrVoCboYGfmltBDoA,2737
9
9
  basic_memory/alembic/migrations.py,sha256=lriHPXDdBLSNXEW3QTpU0SJKuVd1V-8NrVkpN3qfsUQ,718
@@ -57,8 +57,8 @@ basic_memory/mcp/tools/canvas.py,sha256=fHC90eshnSSmROTBV-tBB-FSuXSpYVj_BcDrc96p
57
57
  basic_memory/mcp/tools/delete_note.py,sha256=mnrgOv-D7f6nsgZIAK0Wvyn0dbkwCg8adW_xJd7jwc0,829
58
58
  basic_memory/mcp/tools/project_info.py,sha256=pyoHpOMhjMIvZFku2iEIpXc2XDtbnNeb-OMrJlYR9LU,1710
59
59
  basic_memory/mcp/tools/read_content.py,sha256=PKnvLzNmHfzoIxRKXNaYW5P5q0d1azVwG9juPXPYeQo,8148
60
- basic_memory/mcp/tools/read_note.py,sha256=e9un3pKPiIgUzY5x-aFYxBkgQMDwrLAYMMqmDzgq0FQ,6451
61
- basic_memory/mcp/tools/recent_activity.py,sha256=S0LgIk9RaeYzIsi2FIHs0KK7R1K-LJy3QaSokGlY9ew,3501
60
+ basic_memory/mcp/tools/read_note.py,sha256=RyLI7WnZMgM9CkdCZiDcq3cByIXZH2iuYm9HVb3QV24,6463
61
+ basic_memory/mcp/tools/recent_activity.py,sha256=_P5cn4_34HYjP7U5qM4Fb_yibw6KzNmVx_tkEwyH9cU,4399
62
62
  basic_memory/mcp/tools/search.py,sha256=TU9Q89rv9lCUmAWjxcc-J_jFQE8CAQM0lM_b-J8VLRc,3786
63
63
  basic_memory/mcp/tools/utils.py,sha256=tOWklfSlDcoAJCRBmxkCVwkTY_TDBa5vOGxzU8J5eiQ,13636
64
64
  basic_memory/mcp/tools/write_note.py,sha256=d7vb8vqwLmDJfSOYV_ZWJlbJD9tZyKvLlb7LTMgPXSM,4990
@@ -93,8 +93,8 @@ basic_memory/services/service.py,sha256=V-d_8gOV07zGIQDpL-Ksqs3ZN9l3qf3HZOK1f_YN
93
93
  basic_memory/sync/__init__.py,sha256=CVHguYH457h2u2xoM8KvOilJC71XJlZ-qUh8lHcjYj4,156
94
94
  basic_memory/sync/sync_service.py,sha256=ZIgaukAsS8PRf5FBPYGT2lVdn--YuGLd8AJShA79IYk,19602
95
95
  basic_memory/sync/watch_service.py,sha256=ipkW9zK1MhisvdHambB9sesB6vNm0OapMZZM7w0GmsQ,14338
96
- basic_memory-0.12.2.dist-info/METADATA,sha256=An_KcV3Ns11AxyUjuKCTdRCjUSA-KFTyw4D4eubnJi4,14992
97
- basic_memory-0.12.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
98
- basic_memory-0.12.2.dist-info/entry_points.txt,sha256=wvE2mRF6-Pg4weIYcfQ-86NOLZD4WJg7F7TIsRVFLb8,90
99
- basic_memory-0.12.2.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
100
- basic_memory-0.12.2.dist-info/RECORD,,
96
+ basic_memory-0.12.3.dist-info/METADATA,sha256=JweKYzLuafX1wlbPDDqZ1KVsD2bygQifXzOdn2_g6ig,14992
97
+ basic_memory-0.12.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
98
+ basic_memory-0.12.3.dist-info/entry_points.txt,sha256=wvE2mRF6-Pg4weIYcfQ-86NOLZD4WJg7F7TIsRVFLb8,90
99
+ basic_memory-0.12.3.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
100
+ basic_memory-0.12.3.dist-info/RECORD,,