dsh-mem0-memory 0.1.0

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.
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ setup.py — dsh-mem0-memory 一键初始化(三件套之「装机服务」)
5
+
6
+ 用法:
7
+ python setup.py --init 检查/补依赖、建数据目录、生成 .env(不存在时)、打印下一步
8
+ python setup.py --install 在 --init 基础上自动 pip 安装缺失依赖
9
+ python setup.py --check 只检查环境(Python 版本 / 依赖 / key 配置状态),不改任何东西
10
+
11
+ 职责(对应 README 安装五步的 ②③④):
12
+ 1. Python 版本检查(>= 3.10)
13
+ 2. pip 依赖检查:mem0ai>=2.0 qdrant-client fastembed jieba flask(缺失 → 提示,--install 自动装)
14
+ 3. 建数据目录(默认 ./mem0_store,可被 MEM0_DATA_DIR 或 --data-dir 覆盖)
15
+ 4. 生成 .env(不存在时从 .env.example 复制;已存在不覆盖)
16
+ 5. 打印下一步(启动 server + 首次导入)
17
+ """
18
+
19
+ import argparse
20
+ import importlib.metadata
21
+ import importlib.util
22
+ import io
23
+ import os
24
+ import shutil
25
+ import subprocess
26
+ import sys
27
+
28
+ # Windows 控制台默认 GBK:统一 UTF-8 输出(与 mem0-server.py 一致)
29
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
30
+ sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
31
+
32
+ # (分发名 pip 名, import 模块名, 最低版本) —— 注意 mem0ai vs mem0、qdrant-client vs qdrant_client
33
+ REQUIRED = [
34
+ ("mem0ai", "mem0", "2.0"),
35
+ ("qdrant-client", "qdrant_client", None),
36
+ ("fastembed", "fastembed", None),
37
+ ("jieba", "jieba", None),
38
+ ("flask", "flask", None),
39
+ ]
40
+
41
+
42
+ def py_version_ok():
43
+ return sys.version_info >= (3, 10)
44
+
45
+
46
+ def check_dependency(dist_name, import_name, min_version):
47
+ ok = importlib.util.find_spec(import_name) is not None
48
+ if not ok:
49
+ return False, None
50
+ try:
51
+ ver = importlib.metadata.version(dist_name)
52
+ except Exception:
53
+ ver = None
54
+ if min_version and ver:
55
+ def _norm(v):
56
+ try:
57
+ return tuple(int(x) for x in v.split(".")[:2])
58
+ except Exception:
59
+ return (0, 0)
60
+ if _norm(ver) < _norm(min_version):
61
+ return False, ver
62
+ return True, ver
63
+
64
+
65
+ def load_dotenv_state(env_file):
66
+ """只读 .env 看 key 是否已填(不修改环境)。"""
67
+ filled = {}
68
+ try:
69
+ with open(env_file, "r", encoding="utf-8") as f:
70
+ for line in f:
71
+ line = line.strip()
72
+ if not line or line.startswith("#") or "=" not in line:
73
+ continue
74
+ k, _, v = line.partition("=")
75
+ k = k.strip()
76
+ v = v.strip().strip('"').strip("'")
77
+ if k and v:
78
+ filled[k] = True
79
+ except FileNotFoundError:
80
+ pass
81
+ return filled
82
+
83
+
84
+ def main():
85
+ parser = argparse.ArgumentParser(description="dsh-mem0-memory 一键初始化")
86
+ parser.add_argument("--init", action="store_true", help="检查/补依赖 + 建目录 + 生成 .env")
87
+ parser.add_argument("--install", action="store_true", help="(与 --init 同用)自动 pip 安装缺失依赖")
88
+ parser.add_argument("--check", action="store_true", help="只检查环境,不改任何东西")
89
+ parser.add_argument("--data-dir", default=None, help="数据目录(默认 ./mem0_store 或 $MEM0_DATA_DIR)")
90
+ args = parser.parse_args()
91
+
92
+ mode = "check" if args.check else ("init" if args.init else None)
93
+ if mode is None:
94
+ parser.print_help()
95
+ sys.exit(0)
96
+
97
+ script_dir = os.path.dirname(os.path.abspath(__file__))
98
+ env_example = os.path.join(script_dir, ".env.example")
99
+ env_file = os.path.join(os.getcwd(), ".env")
100
+ data_dir = args.data_dir or os.environ.get("MEM0_DATA_DIR") or os.path.join(os.getcwd(), "mem0_store")
101
+
102
+ print("=" * 56)
103
+ print(" dsh-mem0-memory setup")
104
+ print("=" * 56)
105
+
106
+ # 1) Python 版本
107
+ print(f"[1/4] Python: {sys.version.split()[0]} (需要 >= 3.10)")
108
+ if not py_version_ok():
109
+ print(" FAIL: Python 版本过低,请升级到 3.10+")
110
+ sys.exit(1)
111
+
112
+ # 2) 依赖
113
+ print("[2/4] 依赖检查:")
114
+ missing = []
115
+ for dist_name, import_name, minv in REQUIRED:
116
+ ok, ver = check_dependency(dist_name, import_name, minv)
117
+ if ok:
118
+ print(f" ✅ {dist_name} {ver or ''}".rstrip())
119
+ else:
120
+ print(f" ❌ {dist_name} {('(需要 >= ' + minv + ',当前 ' + str(ver) + ')') if (minv and ver) else ('(需要 >= ' + minv + ')' if minv else '未安装')}")
121
+ missing.append(dist_name)
122
+
123
+ if mode == "check":
124
+ env_state = load_dotenv_state(env_file) if os.path.exists(env_file) else {}
125
+ key_state = "已填" if env_state.get("MEM0_LLM_API_KEY") or os.environ.get("MEM0_LLM_API_KEY") else "未填"
126
+ emb_state = "已填" if env_state.get("MEM0_EMBEDDING_API_KEY") or os.environ.get("MEM0_EMBEDDING_API_KEY") else "未填"
127
+ print(f" .env: {'存在' if os.path.exists(env_file) else '不存在'}(LLM key {key_state} / embedding key {emb_state})")
128
+ print(f" 数据目录: {data_dir}({'存在' if os.path.isdir(data_dir) else '不存在'})")
129
+ ok = not missing and py_version_ok()
130
+ print("=" * 56)
131
+ print(" ✅ 环境就绪" if ok else " ❌ 有缺失项,运行 `python setup.py --init [--install]`")
132
+ sys.exit(0 if ok else 1)
133
+
134
+ # init 模式
135
+ if missing:
136
+ cmd = " ".join(f"pip install {m}" for m in missing)
137
+ if args.install:
138
+ print(f" ↳ 自动安装: {cmd}")
139
+ r = subprocess.run(cmd, shell=True)
140
+ if r.returncode != 0:
141
+ print(" FAIL: pip 安装失败,请手动执行上面的命令")
142
+ sys.exit(1)
143
+ for m in list(missing):
144
+ entry = next((e for e in REQUIRED if e[0] == m), None)
145
+ ok, _ = check_dependency(entry[0], entry[1], entry[2]) if entry else (False, None)
146
+ if ok:
147
+ missing.remove(m)
148
+ if missing:
149
+ print(f" FAIL: 仍有缺失: {missing}")
150
+ sys.exit(1)
151
+ else:
152
+ print(f" ↳ 运行: {cmd}")
153
+ print(" (或加 --install 自动安装)")
154
+ sys.exit(1)
155
+
156
+ # 3) 数据目录
157
+ print("[3/4] 数据目录:")
158
+ if os.path.isdir(data_dir):
159
+ print(f" ✅ 已存在: {data_dir}")
160
+ else:
161
+ os.makedirs(data_dir, exist_ok=True)
162
+ print(f" ✅ 已创建: {data_dir}")
163
+
164
+ # 4) .env
165
+ print("[4/4] 环境配置:")
166
+ if os.path.exists(env_file):
167
+ print(f" ✅ .env 已存在(不覆盖): {env_file}")
168
+ elif os.path.exists(env_example):
169
+ shutil.copy2(env_example, env_file)
170
+ print(f" ✅ 已从模板生成: {env_file}")
171
+ print(" ⚠️ 请编辑 .env 填写 MEM0_LLM_API_KEY 与 MEM0_EMBEDDING_API_KEY")
172
+ else:
173
+ print(f" ❌ 模板缺失: {env_example}(发布包异常)")
174
+ sys.exit(1)
175
+
176
+ print("=" * 56)
177
+ print(" ✅ setup 完成。下一步:")
178
+ print(f" 1. 编辑 {env_file} 填写两个 API key")
179
+ print(" 2. 启动 server:")
180
+ print(" - Windows: start-server.bat (或 python mem0-server.py --port 18200)")
181
+ print(" - Linux/macOS: ./start-server.sh (或 python mem0-server.py --port 18200)")
182
+ print(" 3. 首次导入历史会话:node dsh-mem0-import.mjs --since-last")
183
+ print(" (或由插件在会话结束时自动增量导入)")
184
+ print("=" * 56)
185
+
186
+
187
+ if __name__ == "__main__":
188
+ main()
@@ -0,0 +1,47 @@
1
+ @echo off
2
+ REM ============================================================
3
+ REM dsh-mem0-memory - start local mem0 server (Windows)
4
+ REM Usage: start-server.bat [port] (default 18200)
5
+ REM Starts server in a background window, polls /health until
6
+ REM healthy, then prints status. Stop: close "mem0-server" window
7
+ REM or run: taskkill /fi "WINDOWTITLE eq mem0-server"
8
+ REM ============================================================
9
+ setlocal enabledelayedexpansion
10
+
11
+ set PORT=%~1
12
+ if "%PORT%"=="" set PORT=18200
13
+ set DIR=%~dp0
14
+
15
+ REM quick dependency check
16
+ python -c "import mem0, jieba, flask" >nul 2>&1
17
+ if errorlevel 1 (
18
+ echo [start-server] dependencies missing - run: python setup.py --init --install
19
+ exit /b 1
20
+ )
21
+
22
+ echo [start-server] starting mem0-server on port %PORT% ...
23
+ start "mem0-server" /min python "%DIR%mem0-server.py" --port %PORT%
24
+
25
+ REM health self-check: wait up to 120s
26
+ set /a tries=0
27
+ :waitloop
28
+ set /a tries+=1
29
+ if %tries% gtr 60 (
30
+ echo [start-server] FAIL: /health timeout after 120s, check server log
31
+ exit /b 1
32
+ )
33
+ ping -n 3 127.0.0.1 >nul
34
+ curl -s -m 3 "http://127.0.0.1:%PORT%/health" 2>nul | findstr "healthy" >nul
35
+ if errorlevel 1 (
36
+ curl -s -m 3 "http://127.0.0.1:%PORT%/health" 2>nul | findstr "init_failed" >nul
37
+ if not errorlevel 1 (
38
+ echo [start-server] FAIL: init failed
39
+ exit /b 1
40
+ )
41
+ echo [start-server] initializing... tries=!tries!
42
+ goto waitloop
43
+ )
44
+
45
+ echo [start-server] OK: healthy, port %PORT% - server running in background window
46
+ echo [start-server] stop: close "mem0-server" window, or taskkill /fi "WINDOWTITLE eq mem0-server"
47
+ endlocal
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================
3
+ # dsh-mem0-memory — 启动本地 mem0 server(Linux / macOS)
4
+ # 用法: ./start-server.sh [port] (默认 18200)
5
+ # 启动后轮询 /health 直到 healthy,然后保持前台运行(Ctrl+C 停止)
6
+ # ============================================================
7
+ set -euo pipefail
8
+
9
+ PORT="${1:-18200}"
10
+ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+
12
+ # 依赖快速检查
13
+ python3 -c "import mem0, jieba, flask" 2>/dev/null || {
14
+ echo "[start-server] 依赖缺失:请先运行 python3 setup.py --init --install"
15
+ exit 1
16
+ }
17
+
18
+ echo "[start-server] 启动 mem0-server on port ${PORT} ..."
19
+ python3 "${DIR}/mem0-server.py" --port "${PORT}" &
20
+ SERVER_PID=$!
21
+ trap 'kill ${SERVER_PID} 2>/dev/null || true' EXIT
22
+
23
+ # 健康自检:最多等 120 秒
24
+ for i in $(seq 1 60); do
25
+ STATUS=$(curl -s -m 3 "http://127.0.0.1:${PORT}/health" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status',''))" 2>/dev/null || true)
26
+ case "$STATUS" in
27
+ healthy)
28
+ echo "[start-server] OK: healthy(端口 ${PORT})"
29
+ echo "[start-server] 停止:Ctrl+C"
30
+ wait ${SERVER_PID}
31
+ exit 0
32
+ ;;
33
+ init_failed)
34
+ echo "[start-server] FAIL: 初始化失败"
35
+ exit 1
36
+ ;;
37
+ *)
38
+ echo "[start-server] 初始化中($((i*2))s)..."
39
+ sleep 2
40
+ ;;
41
+ esac
42
+ done
43
+ echo "[start-server] FAIL: /health 超时(120s)"
44
+ exit 1