chore: import upstream snapshot with attribution
pi-agent-plugin checks / lint (push) Has been cancelled
pi-agent-plugin checks / test (20) (push) Has been cancelled
pi-agent-plugin checks / test (22) (push) Has been cancelled
pi-agent-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / check_changes (push) Has been cancelled
TypeScript SDK CI / changelog_check (push) Has been cancelled
ci / changelog_check (push) Has been cancelled
ci / check_changes (push) Has been cancelled
ci / build_mem0 (3.10) (push) Has been cancelled
ci / build_mem0 (3.11) (push) Has been cancelled
ci / build_mem0 (3.12) (push) Has been cancelled
CLI Node CI / lint (push) Has been cancelled
CLI Node CI / test (20) (push) Has been cancelled
CLI Node CI / test (22) (push) Has been cancelled
CLI Node CI / build (push) Has been cancelled
CLI Python CI / lint (push) Has been cancelled
CLI Python CI / test (3.10) (push) Has been cancelled
CLI Python CI / test (3.11) (push) Has been cancelled
CLI Python CI / test (3.12) (push) Has been cancelled
CLI Python CI / build (push) Has been cancelled
openclaw checks / lint (push) Has been cancelled
openclaw checks / test (20) (push) Has been cancelled
openclaw checks / test (22) (push) Has been cancelled
openclaw checks / build (push) Has been cancelled
opencode-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (22) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (22) (push) Has been cancelled
pi-agent-plugin checks / lint (push) Has been cancelled
pi-agent-plugin checks / test (20) (push) Has been cancelled
pi-agent-plugin checks / test (22) (push) Has been cancelled
pi-agent-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / check_changes (push) Has been cancelled
TypeScript SDK CI / changelog_check (push) Has been cancelled
ci / changelog_check (push) Has been cancelled
ci / check_changes (push) Has been cancelled
ci / build_mem0 (3.10) (push) Has been cancelled
ci / build_mem0 (3.11) (push) Has been cancelled
ci / build_mem0 (3.12) (push) Has been cancelled
CLI Node CI / lint (push) Has been cancelled
CLI Node CI / test (20) (push) Has been cancelled
CLI Node CI / test (22) (push) Has been cancelled
CLI Node CI / build (push) Has been cancelled
CLI Python CI / lint (push) Has been cancelled
CLI Python CI / test (3.10) (push) Has been cancelled
CLI Python CI / test (3.11) (push) Has been cancelled
CLI Python CI / test (3.12) (push) Has been cancelled
CLI Python CI / build (push) Has been cancelled
openclaw checks / lint (push) Has been cancelled
openclaw checks / test (20) (push) Has been cancelled
openclaw checks / test (22) (push) Has been cancelled
openclaw checks / build (push) Has been cancelled
opencode-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (22) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (22) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse a mem0 export file and output JSON.
|
||||
|
||||
Input: path to a mem0-export-*.md file (sys.argv[1])
|
||||
Output: JSON array of memory records to stdout
|
||||
Exit: 0 always
|
||||
|
||||
Each block in the file is delimited by lines containing exactly "---".
|
||||
Blocks have a YAML-like frontmatter section (key: value lines) followed
|
||||
by a blank line and the memory content text.
|
||||
|
||||
Example block format:
|
||||
---
|
||||
id: abc123
|
||||
created_at: 2024-01-01T00:00:00Z
|
||||
type: task_learnings
|
||||
confidence: 0.9
|
||||
branch: main
|
||||
files: src/foo.py, src/bar.py
|
||||
categories: coding_conventions, task_learnings
|
||||
---
|
||||
The actual memory content text goes here.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def parse_blocks(content: str) -> list[dict]:
|
||||
"""Split content on '---' boundaries and parse each block.
|
||||
|
||||
Returns a list of dicts with keys:
|
||||
id, type, confidence, branch, files (list), categories (list), content (str)
|
||||
|
||||
Blocks with empty content are skipped.
|
||||
Missing optional fields default to "" (scalar) or [] (list fields).
|
||||
"""
|
||||
# Normalise line endings
|
||||
content = content.replace("\r\n", "\n").replace("\r", "\n")
|
||||
|
||||
# Split on lines that are exactly "---"
|
||||
raw_blocks = re.split(r"(?m)^---\s*$", content)
|
||||
|
||||
# After splitting on "---", the structure for each memory is:
|
||||
# raw_blocks[0] = preamble (before first ---, typically empty)
|
||||
# raw_blocks[1] = frontmatter for block 1
|
||||
# raw_blocks[2] = content for block 1
|
||||
# raw_blocks[3] = frontmatter for block 2
|
||||
# raw_blocks[4] = content for block 2
|
||||
# ...
|
||||
# So frontmatter blocks are at odd indices (1, 3, 5, ...) and
|
||||
# content blocks at even indices (2, 4, 6, ...).
|
||||
|
||||
results: list[dict] = []
|
||||
|
||||
# Pair up frontmatter + content starting at index 1
|
||||
i = 1
|
||||
while i < len(raw_blocks):
|
||||
frontmatter_raw = raw_blocks[i]
|
||||
content_raw = raw_blocks[i + 1] if i + 1 < len(raw_blocks) else ""
|
||||
|
||||
# Parse the frontmatter key-value pairs
|
||||
fm = _parse_frontmatter(frontmatter_raw)
|
||||
|
||||
# Strip leading/trailing whitespace from content
|
||||
memory_content = content_raw.strip()
|
||||
|
||||
# Skip blocks with empty content
|
||||
if not memory_content:
|
||||
i += 2
|
||||
continue
|
||||
|
||||
record = {
|
||||
"id": fm.get("id", ""),
|
||||
"type": fm.get("type", ""),
|
||||
"confidence": fm.get("confidence", ""),
|
||||
"branch": fm.get("branch", ""),
|
||||
"files": _parse_list_field(fm.get("files", "")),
|
||||
"categories": _parse_list_field(fm.get("categories", "")),
|
||||
"content": memory_content,
|
||||
}
|
||||
|
||||
# Include created_at if present
|
||||
if "created_at" in fm:
|
||||
record["created_at"] = fm["created_at"]
|
||||
|
||||
results.append(record)
|
||||
i += 2
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _parse_frontmatter(text: str) -> dict[str, str]:
|
||||
"""Parse simple 'key: value' lines from frontmatter text.
|
||||
|
||||
Only the first colon is used as the delimiter — values may contain colons.
|
||||
Lines not matching 'key: value' are ignored.
|
||||
"""
|
||||
result: dict[str, str] = {}
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
match = re.match(r"^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.*)$", line)
|
||||
if match:
|
||||
key = match.group(1).strip()
|
||||
value = match.group(2).strip()
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
def _parse_list_field(value: str) -> list[str]:
|
||||
"""Split a comma-separated value into a list, stripping whitespace.
|
||||
|
||||
Returns [] for empty/whitespace-only input.
|
||||
"""
|
||||
if not value or not value.strip():
|
||||
return []
|
||||
return [item.strip() for item in value.split(",") if item.strip()]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: parse_export_file.py <path-to-export-file>", file=sys.stderr)
|
||||
print("[]")
|
||||
sys.exit(0)
|
||||
|
||||
filepath = sys.argv[1]
|
||||
try:
|
||||
if filepath == "-":
|
||||
content = sys.stdin.read()
|
||||
else:
|
||||
with open(filepath, encoding="utf-8", errors="replace") as f:
|
||||
content = f.read()
|
||||
except OSError as e:
|
||||
print(f"Error reading file: {e}", file=sys.stderr)
|
||||
print("[]")
|
||||
sys.exit(0)
|
||||
|
||||
records = parse_blocks(content)
|
||||
print(json.dumps(records, ensure_ascii=False, indent=2))
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user