basic-memory 0.9.0__py3-none-any.whl → 0.10.0__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 +1 -1
- basic_memory/api/routers/project_info_router.py +3 -4
- basic_memory/cli/commands/import_chatgpt.py +5 -6
- basic_memory/cli/commands/import_claude_conversations.py +5 -6
- basic_memory/cli/commands/import_claude_projects.py +5 -6
- basic_memory/config.py +3 -4
- basic_memory/file_utils.py +3 -3
- basic_memory/markdown/markdown_processor.py +1 -1
- basic_memory/mcp/prompts/ai_assistant_guide.py +5 -4
- basic_memory/services/file_service.py +3 -5
- basic_memory/sync/watch_service.py +23 -13
- basic_memory-0.10.0.dist-info/METADATA +386 -0
- {basic_memory-0.9.0.dist-info → basic_memory-0.10.0.dist-info}/RECORD +16 -16
- basic_memory-0.9.0.dist-info/METADATA +0 -736
- {basic_memory-0.9.0.dist-info → basic_memory-0.10.0.dist-info}/WHEEL +0 -0
- {basic_memory-0.9.0.dist-info → basic_memory-0.10.0.dist-info}/entry_points.txt +0 -0
- {basic_memory-0.9.0.dist-info → basic_memory-0.10.0.dist-info}/licenses/LICENSE +0 -0
basic_memory/__init__.py
CHANGED
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
import json
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
|
|
6
|
-
from fastapi import APIRouter
|
|
7
|
-
from sqlalchemy import text
|
|
8
|
-
|
|
9
6
|
from basic_memory.config import config, config_manager
|
|
10
7
|
from basic_memory.deps import (
|
|
11
8
|
ProjectInfoRepositoryDep,
|
|
@@ -18,6 +15,8 @@ from basic_memory.schemas import (
|
|
|
18
15
|
SystemStatus,
|
|
19
16
|
)
|
|
20
17
|
from basic_memory.sync.watch_service import WATCH_STATUS_JSON
|
|
18
|
+
from fastapi import APIRouter
|
|
19
|
+
from sqlalchemy import text
|
|
21
20
|
|
|
22
21
|
router = APIRouter(prefix="/stats", tags=["statistics"])
|
|
23
22
|
|
|
@@ -262,7 +261,7 @@ async def get_system_status() -> SystemStatus:
|
|
|
262
261
|
watch_status_path = config.home / ".basic-memory" / WATCH_STATUS_JSON
|
|
263
262
|
if watch_status_path.exists():
|
|
264
263
|
try:
|
|
265
|
-
watch_status = json.loads(watch_status_path.read_text())
|
|
264
|
+
watch_status = json.loads(watch_status_path.read_text(encoding="utf-8"))
|
|
266
265
|
except Exception: # pragma: no cover
|
|
267
266
|
pass
|
|
268
267
|
|
|
@@ -7,15 +7,14 @@ from pathlib import Path
|
|
|
7
7
|
from typing import Dict, Any, List, Annotated, Set, Optional
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
|
-
from loguru import logger
|
|
11
|
-
from rich.console import Console
|
|
12
|
-
from rich.panel import Panel
|
|
13
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
14
|
-
|
|
15
10
|
from basic_memory.cli.app import import_app
|
|
16
11
|
from basic_memory.config import config
|
|
17
12
|
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
|
18
13
|
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
|
|
14
|
+
from loguru import logger
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
19
18
|
|
|
20
19
|
console = Console()
|
|
21
20
|
|
|
@@ -167,7 +166,7 @@ async def process_chatgpt_json(
|
|
|
167
166
|
read_task = progress.add_task("Reading chat data...", total=None)
|
|
168
167
|
|
|
169
168
|
# Read conversations
|
|
170
|
-
conversations = json.loads(json_path.read_text())
|
|
169
|
+
conversations = json.loads(json_path.read_text(encoding="utf-8"))
|
|
171
170
|
progress.update(read_task, total=len(conversations))
|
|
172
171
|
|
|
173
172
|
# Process each conversation
|
|
@@ -7,15 +7,14 @@ from pathlib import Path
|
|
|
7
7
|
from typing import Dict, Any, List, Annotated
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
|
-
from loguru import logger
|
|
11
|
-
from rich.console import Console
|
|
12
|
-
from rich.panel import Panel
|
|
13
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
14
|
-
|
|
15
10
|
from basic_memory.cli.app import claude_app
|
|
16
11
|
from basic_memory.config import config
|
|
17
12
|
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
|
18
13
|
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
|
|
14
|
+
from loguru import logger
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
19
18
|
|
|
20
19
|
console = Console()
|
|
21
20
|
|
|
@@ -124,7 +123,7 @@ async def process_conversations_json(
|
|
|
124
123
|
read_task = progress.add_task("Reading chat data...", total=None)
|
|
125
124
|
|
|
126
125
|
# Read chat data - handle array of arrays format
|
|
127
|
-
data = json.loads(json_path.read_text())
|
|
126
|
+
data = json.loads(json_path.read_text(encoding="utf-8"))
|
|
128
127
|
conversations = [chat for chat in data]
|
|
129
128
|
progress.update(read_task, total=len(conversations))
|
|
130
129
|
|
|
@@ -6,15 +6,14 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Dict, Any, Annotated, Optional
|
|
7
7
|
|
|
8
8
|
import typer
|
|
9
|
-
from loguru import logger
|
|
10
|
-
from rich.console import Console
|
|
11
|
-
from rich.panel import Panel
|
|
12
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
13
|
-
|
|
14
9
|
from basic_memory.cli.app import claude_app
|
|
15
10
|
from basic_memory.config import config
|
|
16
11
|
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
|
17
12
|
from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
|
|
13
|
+
from loguru import logger
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
18
17
|
|
|
19
18
|
console = Console()
|
|
20
19
|
|
|
@@ -103,7 +102,7 @@ async def process_projects_json(
|
|
|
103
102
|
read_task = progress.add_task("Reading project data...", total=None)
|
|
104
103
|
|
|
105
104
|
# Read project data
|
|
106
|
-
data = json.loads(json_path.read_text())
|
|
105
|
+
data = json.loads(json_path.read_text(encoding="utf-8"))
|
|
107
106
|
progress.update(read_task, total=len(data))
|
|
108
107
|
|
|
109
108
|
# Track import counts
|
basic_memory/config.py
CHANGED
|
@@ -5,13 +5,12 @@ import os
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any, Dict, Literal, Optional
|
|
7
7
|
|
|
8
|
+
import basic_memory
|
|
9
|
+
from basic_memory.utils import setup_logging
|
|
8
10
|
from loguru import logger
|
|
9
11
|
from pydantic import Field, field_validator
|
|
10
12
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
11
13
|
|
|
12
|
-
import basic_memory
|
|
13
|
-
from basic_memory.utils import setup_logging
|
|
14
|
-
|
|
15
14
|
DATABASE_NAME = "memory.db"
|
|
16
15
|
DATA_DIR_NAME = ".basic-memory"
|
|
17
16
|
CONFIG_FILE_NAME = "config.json"
|
|
@@ -111,7 +110,7 @@ class ConfigManager:
|
|
|
111
110
|
"""Load configuration from file or create default."""
|
|
112
111
|
if self.config_file.exists():
|
|
113
112
|
try:
|
|
114
|
-
data = json.loads(self.config_file.read_text())
|
|
113
|
+
data = json.loads(self.config_file.read_text(encoding="utf-8"))
|
|
115
114
|
return BasicMemoryConfig(**data)
|
|
116
115
|
except Exception as e:
|
|
117
116
|
logger.error(f"Failed to load config: {e}")
|
basic_memory/file_utils.py
CHANGED
|
@@ -85,7 +85,7 @@ async def write_file_atomic(path: FilePath, content: str) -> None:
|
|
|
85
85
|
temp_path = path_obj.with_suffix(".tmp")
|
|
86
86
|
|
|
87
87
|
try:
|
|
88
|
-
temp_path.write_text(content)
|
|
88
|
+
temp_path.write_text(content, encoding="utf-8")
|
|
89
89
|
temp_path.replace(path_obj)
|
|
90
90
|
logger.debug("Wrote file atomically", path=str(path_obj), content_length=len(content))
|
|
91
91
|
except Exception as e: # pragma: no cover
|
|
@@ -203,7 +203,7 @@ async def update_frontmatter(path: FilePath, updates: Dict[str, Any]) -> str:
|
|
|
203
203
|
path_obj = Path(path) if isinstance(path, str) else path
|
|
204
204
|
|
|
205
205
|
# Read current content
|
|
206
|
-
content = path_obj.read_text()
|
|
206
|
+
content = path_obj.read_text(encoding="utf-8")
|
|
207
207
|
|
|
208
208
|
# Parse current frontmatter
|
|
209
209
|
current_fm = {}
|
|
@@ -215,7 +215,7 @@ async def update_frontmatter(path: FilePath, updates: Dict[str, Any]) -> str:
|
|
|
215
215
|
new_fm = {**current_fm, **updates}
|
|
216
216
|
|
|
217
217
|
# Write new file with updated frontmatter
|
|
218
|
-
yaml_fm = yaml.dump(new_fm, sort_keys=False)
|
|
218
|
+
yaml_fm = yaml.dump(new_fm, sort_keys=False, allow_unicode=True)
|
|
219
219
|
final_content = f"---\n{yaml_fm}---\n\n{content.strip()}"
|
|
220
220
|
|
|
221
221
|
logger.debug("Updating frontmatter", path=str(path_obj), update_keys=list(updates.keys()))
|
|
@@ -83,7 +83,7 @@ class MarkdownProcessor:
|
|
|
83
83
|
"""
|
|
84
84
|
# Dirty check if needed
|
|
85
85
|
if expected_checksum is not None:
|
|
86
|
-
current_content = path.read_text()
|
|
86
|
+
current_content = path.read_text(encoding="utf-8")
|
|
87
87
|
current_checksum = await file_utils.compute_checksum(current_content)
|
|
88
88
|
if current_checksum != expected_checksum:
|
|
89
89
|
raise DirtyFileError(f"File {path} has been modified")
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
from loguru import logger
|
|
4
|
-
|
|
5
3
|
from basic_memory.mcp.server import mcp
|
|
4
|
+
from loguru import logger
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
@mcp.resource(
|
|
@@ -20,7 +19,9 @@ def ai_assistant_guide() -> str:
|
|
|
20
19
|
A focused guide on Basic Memory usage.
|
|
21
20
|
"""
|
|
22
21
|
logger.info("Loading AI assistant guide resource")
|
|
23
|
-
guide_doc =
|
|
24
|
-
|
|
22
|
+
guide_doc = (
|
|
23
|
+
Path(__file__).parent.parent.parent.parent.parent / "static" / "ai_assistant_guide.md"
|
|
24
|
+
)
|
|
25
|
+
content = guide_doc.read_text(encoding="utf-8")
|
|
25
26
|
logger.info(f"Loaded AI assistant guide ({len(content)} chars)")
|
|
26
27
|
return content
|
|
@@ -5,8 +5,6 @@ from os import stat_result
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any, Dict, Tuple, Union
|
|
7
7
|
|
|
8
|
-
from loguru import logger
|
|
9
|
-
|
|
10
8
|
from basic_memory import file_utils
|
|
11
9
|
from basic_memory.file_utils import FileError
|
|
12
10
|
from basic_memory.markdown.markdown_processor import MarkdownProcessor
|
|
@@ -14,6 +12,7 @@ from basic_memory.models import Entity as EntityModel
|
|
|
14
12
|
from basic_memory.schemas import Entity as EntitySchema
|
|
15
13
|
from basic_memory.services.exceptions import FileOperationError
|
|
16
14
|
from basic_memory.utils import FilePath
|
|
15
|
+
from loguru import logger
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
class FileService:
|
|
@@ -171,8 +170,7 @@ class FileService:
|
|
|
171
170
|
|
|
172
171
|
try:
|
|
173
172
|
logger.debug("Reading file", operation="read_file", path=str(full_path))
|
|
174
|
-
|
|
175
|
-
content = full_path.read_text()
|
|
173
|
+
content = full_path.read_text(encoding="utf-8")
|
|
176
174
|
checksum = await file_utils.compute_checksum(content)
|
|
177
175
|
|
|
178
176
|
logger.debug(
|
|
@@ -236,7 +234,7 @@ class FileService:
|
|
|
236
234
|
try:
|
|
237
235
|
if self.is_markdown(path):
|
|
238
236
|
# read str
|
|
239
|
-
content = full_path.read_text()
|
|
237
|
+
content = full_path.read_text(encoding="utf-8")
|
|
240
238
|
else:
|
|
241
239
|
# read bytes
|
|
242
240
|
content = full_path.read_bytes()
|
|
@@ -5,16 +5,15 @@ from datetime import datetime
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import List, Optional, Set
|
|
7
7
|
|
|
8
|
+
from basic_memory.config import ProjectConfig
|
|
9
|
+
from basic_memory.services.file_service import FileService
|
|
10
|
+
from basic_memory.sync.sync_service import SyncService
|
|
8
11
|
from loguru import logger
|
|
9
12
|
from pydantic import BaseModel
|
|
10
13
|
from rich.console import Console
|
|
11
14
|
from watchfiles import awatch
|
|
12
15
|
from watchfiles.main import FileChange, Change
|
|
13
16
|
|
|
14
|
-
from basic_memory.config import ProjectConfig
|
|
15
|
-
from basic_memory.services.file_service import FileService
|
|
16
|
-
from basic_memory.sync.sync_service import SyncService
|
|
17
|
-
|
|
18
17
|
WATCH_STATUS_JSON = "watch-status.json"
|
|
19
18
|
|
|
20
19
|
|
|
@@ -138,6 +137,10 @@ class WatchService:
|
|
|
138
137
|
if part.startswith("."):
|
|
139
138
|
return False
|
|
140
139
|
|
|
140
|
+
# Skip temp files used in atomic operations
|
|
141
|
+
if path.endswith(".tmp"):
|
|
142
|
+
return False
|
|
143
|
+
|
|
141
144
|
return True
|
|
142
145
|
|
|
143
146
|
async def write_status(self):
|
|
@@ -161,6 +164,11 @@ class WatchService:
|
|
|
161
164
|
for change, path in changes:
|
|
162
165
|
# convert to relative path
|
|
163
166
|
relative_path = str(Path(path).relative_to(directory))
|
|
167
|
+
|
|
168
|
+
# Skip .tmp files - they're temporary and shouldn't be synced
|
|
169
|
+
if relative_path.endswith(".tmp"):
|
|
170
|
+
continue
|
|
171
|
+
|
|
164
172
|
if change == Change.added:
|
|
165
173
|
adds.append(relative_path)
|
|
166
174
|
elif change == Change.deleted:
|
|
@@ -184,8 +192,8 @@ class WatchService:
|
|
|
184
192
|
# We don't need to process directories, only the files inside them
|
|
185
193
|
# This prevents errors when trying to compute checksums or read directories as files
|
|
186
194
|
added_full_path = directory / added_path
|
|
187
|
-
if added_full_path.is_dir():
|
|
188
|
-
logger.debug("Skipping
|
|
195
|
+
if not added_full_path.exists() or added_full_path.is_dir():
|
|
196
|
+
logger.debug("Skipping non-existent or directory path", path=added_path)
|
|
189
197
|
processed.add(added_path)
|
|
190
198
|
continue
|
|
191
199
|
|
|
@@ -247,10 +255,12 @@ class WatchService:
|
|
|
247
255
|
if path not in processed:
|
|
248
256
|
# Skip directories - only process files
|
|
249
257
|
full_path = directory / path
|
|
250
|
-
if full_path.is_dir():
|
|
251
|
-
logger.debug(
|
|
252
|
-
|
|
253
|
-
|
|
258
|
+
if not full_path.exists() or full_path.is_dir():
|
|
259
|
+
logger.debug(
|
|
260
|
+
"Skipping non-existent or directory path", path=path
|
|
261
|
+
) # pragma: no cover
|
|
262
|
+
processed.add(path) # pragma: no cover
|
|
263
|
+
continue # pragma: no cover
|
|
254
264
|
|
|
255
265
|
logger.debug("Processing new file", path=path)
|
|
256
266
|
entity, checksum = await self.sync_service.sync_file(path, new=True)
|
|
@@ -281,8 +291,8 @@ class WatchService:
|
|
|
281
291
|
if path not in processed:
|
|
282
292
|
# Skip directories - only process files
|
|
283
293
|
full_path = directory / path
|
|
284
|
-
if full_path.is_dir():
|
|
285
|
-
logger.debug("Skipping directory", path=path)
|
|
294
|
+
if not full_path.exists() or full_path.is_dir():
|
|
295
|
+
logger.debug("Skipping non-existent or directory path", path=path)
|
|
286
296
|
processed.add(path)
|
|
287
297
|
continue
|
|
288
298
|
|
|
@@ -341,4 +351,4 @@ class WatchService:
|
|
|
341
351
|
duration_ms=duration_ms,
|
|
342
352
|
)
|
|
343
353
|
|
|
344
|
-
await self.write_status()
|
|
354
|
+
await self.write_status()
|