remote-claude 0.1.0 → 0.1.1

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.
package/.env.example CHANGED
@@ -10,6 +10,3 @@ FEISHU_APP_SECRET=xxxxx
10
10
  ENABLE_USER_WHITELIST=false
11
11
  ALLOWED_USERS=ou_xxxxx,ou_yyyyy
12
12
 
13
- # 机器人名称(用于群聊命名,默认 Claude)
14
- BOT_NAME=Ys-Claude
15
-
package/init.sh ADDED
@@ -0,0 +1,468 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ # 颜色定义
6
+ RED=$'\033[0;31m'
7
+ GREEN=$'\033[0;32m'
8
+ YELLOW=$'\033[1;33m'
9
+ NC=$'\033[0m' # No Color
10
+
11
+ # 末尾汇总警告
12
+ WARNINGS=()
13
+
14
+ # 打印函数
15
+ print_info() {
16
+ echo -e "${GREEN}ℹ${NC} $1"
17
+ }
18
+
19
+ print_success() {
20
+ echo -e "${GREEN}✓${NC} $1"
21
+ }
22
+
23
+ print_warning() {
24
+ echo -e "${YELLOW}⚠${NC} $1"
25
+ }
26
+
27
+ print_error() {
28
+ echo -e "${RED}✗${NC} $1"
29
+ }
30
+
31
+ print_header() {
32
+ echo ""
33
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
34
+ echo -e "${GREEN}$1${NC}"
35
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
36
+ echo ""
37
+ }
38
+
39
+ # 检查操作系统
40
+ check_os() {
41
+ print_header "检查系统环境"
42
+
43
+ OS=$(uname -s)
44
+ if [[ "$OS" != "Darwin" && "$OS" != "Linux" ]]; then
45
+ print_error "不支持的操作系统: $OS"
46
+ print_error "Remote Claude 仅支持 macOS 和 Linux"
47
+ exit 1
48
+ fi
49
+
50
+ print_success "操作系统: $OS"
51
+ }
52
+
53
+ # 检查 uv
54
+ check_uv() {
55
+ print_header "检查 uv"
56
+
57
+ if command -v uv &> /dev/null; then
58
+ UV_VERSION=$(uv --version)
59
+ print_success "$UV_VERSION 已安装"
60
+ return
61
+ fi
62
+
63
+ print_warning "未找到 uv,正在安装..."
64
+ curl -LsSf https://astral.sh/uv/install.sh | sh
65
+ # 重新加载 PATH
66
+ export PATH="$HOME/.local/bin:$PATH"
67
+
68
+ if command -v uv &> /dev/null; then
69
+ print_success "uv 安装成功"
70
+ else
71
+ print_error "uv 安装失败,请手动安装: https://docs.astral.sh/uv/"
72
+ exit 1
73
+ fi
74
+ }
75
+
76
+ # 检查并安装 tmux(要求 3.6+)
77
+ check_tmux() {
78
+ print_header "检查 tmux"
79
+
80
+ REQUIRED_MAJOR=3
81
+ REQUIRED_MINOR=6
82
+
83
+ install_tmux() {
84
+ if [[ "$OS" == "Darwin" ]]; then
85
+ if ! command -v brew &> /dev/null; then
86
+ print_error "未找到 Homebrew,请先安装: https://brew.sh"
87
+ exit 1
88
+ fi
89
+ brew install tmux
90
+ elif [[ "$OS" == "Linux" ]]; then
91
+ if command -v apt-get &> /dev/null; then
92
+ sudo apt-get update && sudo apt-get install -y tmux
93
+ elif command -v yum &> /dev/null; then
94
+ sudo yum install -y tmux
95
+ else
96
+ print_error "无法自动安装 tmux,请手动安装 3.6 或更高版本"
97
+ exit 1
98
+ fi
99
+ fi
100
+ print_success "tmux 安装成功"
101
+ }
102
+
103
+ install_tmux_from_source() {
104
+ local TMUX_VERSION_TAG="3.6a"
105
+ local TMUX_URL="https://github.com/tmux/tmux/releases/download/${TMUX_VERSION_TAG}/tmux-${TMUX_VERSION_TAG}.tar.gz"
106
+
107
+ print_warning "包管理器版本不满足要求,尝试从源码编译 tmux ${TMUX_VERSION_TAG}..."
108
+
109
+ # 安装编译依赖
110
+ if command -v apt-get &> /dev/null; then
111
+ sudo apt-get install -y build-essential libevent-dev libncurses5-dev libncursesw5-dev bison pkg-config
112
+ elif command -v yum &> /dev/null; then
113
+ sudo yum groupinstall -y "Development Tools"
114
+ sudo yum install -y libevent-devel ncurses-devel bison
115
+ fi
116
+
117
+ # 确定安装前缀
118
+ local PREFIX="/usr/local"
119
+ if ! sudo -n true 2>/dev/null; then
120
+ print_warning "无 sudo 权限,将安装到 \$HOME/.local"
121
+ PREFIX="$HOME/.local"
122
+ fi
123
+
124
+ # 创建临时目录,编译完成后清理
125
+ local TMPDIR
126
+ TMPDIR=$(mktemp -d)
127
+ trap "rm -rf '$TMPDIR'" RETURN
128
+
129
+ print_warning "下载 tmux-${TMUX_VERSION_TAG}.tar.gz..."
130
+ if ! curl -fsSL "$TMUX_URL" -o "$TMPDIR/tmux.tar.gz"; then
131
+ print_warning "下载失败,请检查网络或手动安装 tmux ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+"
132
+ WARNINGS+=("tmux 源码下载失败,请手动安装 tmux ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+")
133
+ return
134
+ fi
135
+
136
+ tar -xzf "$TMPDIR/tmux.tar.gz" -C "$TMPDIR"
137
+ local SRC_DIR
138
+ SRC_DIR=$(find "$TMPDIR" -maxdepth 1 -type d -name "tmux-*" | head -1)
139
+
140
+ print_warning "编译 tmux(可能需要几分钟)..."
141
+ if ! (cd "$SRC_DIR" && ./configure --prefix="$PREFIX" && make -j"$(nproc 2>/dev/null || echo 2)"); then
142
+ print_warning "编译失败,请手动安装 tmux ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+"
143
+ WARNINGS+=("tmux 源码编译失败,请手动安装 tmux ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+")
144
+ return
145
+ fi
146
+
147
+ if [[ "$PREFIX" == "/usr/local" ]]; then
148
+ sudo make -C "$SRC_DIR" install
149
+ else
150
+ make -C "$SRC_DIR" install
151
+ # 若 $HOME/.local/bin 不在 PATH 中,提示用户
152
+ if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
153
+ print_warning "tmux 已安装到 $HOME/.local/bin,请将其加入 PATH:"
154
+ print_warning " echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.bashrc && source ~/.bashrc"
155
+ WARNINGS+=("tmux 安装在 \$HOME/.local/bin,请将其加入 PATH 后重新运行 init.sh")
156
+ fi
157
+ fi
158
+
159
+ print_success "tmux ${TMUX_VERSION_TAG} 源码编译安装完成(前缀:${PREFIX})"
160
+ }
161
+
162
+ check_version() {
163
+ # tmux -V 输出格式:tmux 3.6 或 tmux 3.4a
164
+ local ver_str
165
+ ver_str=$(tmux -V | awk '{print $2}')
166
+ local major minor
167
+ major=$(echo "$ver_str" | cut -d. -f1)
168
+ minor=$(echo "$ver_str" | cut -d. -f2 | tr -dc '0-9')
169
+ if [[ "$major" -gt "$REQUIRED_MAJOR" ]] || \
170
+ [[ "$major" -eq "$REQUIRED_MAJOR" && "${minor:-0}" -ge "$REQUIRED_MINOR" ]]; then
171
+ return 0
172
+ fi
173
+ return 1
174
+ }
175
+
176
+ if command -v tmux &> /dev/null; then
177
+ TMUX_VERSION=$(tmux -V)
178
+ if check_version; then
179
+ print_success "$TMUX_VERSION 已安装(满足 >= ${REQUIRED_MAJOR}.${REQUIRED_MINOR})"
180
+ return
181
+ else
182
+ print_warning "$TMUX_VERSION 版本过低,需要 ${REQUIRED_MAJOR}.${REQUIRED_MINOR} 或更高,正在升级..."
183
+ install_tmux
184
+ # 升级后再次验证
185
+ if ! check_version; then
186
+ if [[ "$OS" == "Linux" ]]; then
187
+ install_tmux_from_source
188
+ if ! check_version; then
189
+ print_warning "源码编译安装后版本仍不满足要求($(tmux -V)),请手动安装 tmux ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+"
190
+ WARNINGS+=("tmux 版本不满足要求($(tmux -V)),需要 ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+,请手动升级")
191
+ else
192
+ print_success "tmux 已升级至 $(tmux -V)"
193
+ fi
194
+ else
195
+ print_warning "升级后版本仍不满足要求($(tmux -V)),请手动安装 tmux ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+"
196
+ WARNINGS+=("tmux 版本不满足要求($(tmux -V)),需要 ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+,请手动升级")
197
+ fi
198
+ else
199
+ print_success "tmux 已升级至 $(tmux -V)"
200
+ fi
201
+ fi
202
+ else
203
+ print_warning "未找到 tmux,正在安装..."
204
+ install_tmux
205
+ if ! check_version; then
206
+ if [[ "$OS" == "Linux" ]]; then
207
+ install_tmux_from_source
208
+ if ! check_version; then
209
+ print_warning "源码编译安装后版本仍不满足要求($(tmux -V)),请手动安装 tmux ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+"
210
+ WARNINGS+=("tmux 版本不满足要求($(tmux -V)),需要 ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+,请手动升级")
211
+ fi
212
+ else
213
+ print_warning "安装的版本不满足要求($(tmux -V)),请手动安装 tmux ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+"
214
+ WARNINGS+=("tmux 版本不满足要求($(tmux -V)),需要 ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+,请手动升级")
215
+ fi
216
+ fi
217
+ fi
218
+ }
219
+
220
+ # 检查 Claude CLI
221
+ check_claude() {
222
+ print_header "检查 Claude CLI"
223
+
224
+ if command -v claude &> /dev/null; then
225
+ print_success "Claude CLI 已安装"
226
+ return
227
+ fi
228
+
229
+ print_warning "未找到 Claude CLI"
230
+ print_info "请访问 https://claude.ai/code 安装 Claude CLI"
231
+
232
+ if $NPM_MODE; then
233
+ print_info "(npm 模式:跳过交互,请安装后重新运行)"
234
+ return
235
+ fi
236
+
237
+ read -p "$(echo -e ${YELLOW}是否已安装 Claude CLI?${NC} [y/N]: )" -n 1 -r
238
+ echo
239
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
240
+ print_error "请先安装 Claude CLI 后再运行此脚本"
241
+ exit 1
242
+ fi
243
+
244
+ if ! command -v claude &> /dev/null; then
245
+ print_error "仍未找到 claude 命令,请检查安装或 PATH 配置"
246
+ exit 1
247
+ fi
248
+ }
249
+
250
+ # 安装 Python 依赖
251
+ install_dependencies() {
252
+ print_header "安装 Python 依赖"
253
+
254
+ if [ ! -f "pyproject.toml" ]; then
255
+ print_error "未找到 pyproject.toml 文件"
256
+ exit 1
257
+ fi
258
+
259
+ print_info "正在通过 uv 同步依赖..."
260
+ if $NPM_MODE; then
261
+ uv sync --frozen
262
+ else
263
+ uv sync
264
+ fi
265
+
266
+ print_success "依赖安装完成"
267
+ }
268
+
269
+ # 配置飞书环境
270
+ configure_lark() {
271
+ print_header "配置飞书客户端"
272
+
273
+ ENV_FILE="$HOME/.remote-claude/.env"
274
+ mkdir -p "$HOME/.remote-claude"
275
+
276
+ # 迁移旧 .env(项目根目录)到新位置
277
+ if [ -f ".env" ] && [ ! -f "$ENV_FILE" ]; then
278
+ mv ".env" "$ENV_FILE"
279
+ print_success "已将 .env 迁移到 $ENV_FILE"
280
+ fi
281
+
282
+ if [ -f "$ENV_FILE" ]; then
283
+ print_warning ".env 文件已存在($ENV_FILE),跳过配置"
284
+ return
285
+ fi
286
+
287
+ if [ ! -f ".env.example" ]; then
288
+ print_error "未找到 .env.example 文件"
289
+ exit 1
290
+ fi
291
+
292
+ read -p "$(echo -e ${YELLOW}是否需要配置飞书客户端?${NC} [y/N]: )" -n 1 -r
293
+ echo
294
+
295
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
296
+ cp .env.example "$ENV_FILE"
297
+ print_success ".env 文件已创建于 $ENV_FILE"
298
+ print_warning "请编辑 $ENV_FILE,填写以下信息:"
299
+ print_info " - FEISHU_APP_ID: 飞书应用的 App ID"
300
+ print_info " - FEISHU_APP_SECRET: 飞书应用的 App Secret"
301
+ print_info ""
302
+ print_info "获取方式: 登录飞书开放平台 -> 创建应用 -> 凭证与基础信息"
303
+ else
304
+ print_info "跳过飞书配置(可稍后手动配置)"
305
+ fi
306
+ }
307
+
308
+ # 创建必要目录
309
+ create_directories() {
310
+ print_header "创建运行目录"
311
+
312
+ SOCKET_DIR="/tmp/remote-claude"
313
+ USER_DATA_DIR="$HOME/.remote-claude"
314
+
315
+ if [ ! -d "$SOCKET_DIR" ]; then
316
+ mkdir -p "$SOCKET_DIR"
317
+ print_success "创建目录: $SOCKET_DIR"
318
+ else
319
+ print_info "目录已存在: $SOCKET_DIR"
320
+ fi
321
+
322
+ if [ ! -d "$USER_DATA_DIR" ]; then
323
+ mkdir -p "$USER_DATA_DIR"
324
+ print_success "创建目录: $USER_DATA_DIR"
325
+ else
326
+ print_info "目录已存在: $USER_DATA_DIR"
327
+ fi
328
+ }
329
+
330
+ # 设置可执行权限
331
+ set_permissions() {
332
+ print_header "设置执行权限"
333
+
334
+ chmod +x remote_claude.py
335
+ chmod +x server/server.py
336
+ chmod +x client/client.py
337
+
338
+ print_success "已设置执行权限"
339
+ }
340
+
341
+ # 安装快捷命令(符号链接到 bin 目录)
342
+ configure_shell() {
343
+ print_header "安装快捷命令"
344
+
345
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
346
+ chmod +x "$SCRIPT_DIR/bin/cla" "$SCRIPT_DIR/bin/cl" "$SCRIPT_DIR/bin/remote-claude"
347
+
348
+ # 优先 /usr/local/bin,权限不够则选 ~/bin 或 ~/.local/bin 中已在 PATH 里的
349
+ BIN_DIR="/usr/local/bin"
350
+ if ! ln -sf "$SCRIPT_DIR/bin/cla" "$BIN_DIR/cla" 2>/dev/null; then
351
+ if [[ ":$PATH:" == *":$HOME/bin:"* ]]; then
352
+ BIN_DIR="$HOME/bin"
353
+ elif [[ ":$PATH:" == *":$HOME/.local/bin:"* ]]; then
354
+ BIN_DIR="$HOME/.local/bin"
355
+ else
356
+ BIN_DIR="$HOME/.local/bin"
357
+ print_warning "cla/cl/remote-claude 将安装到 $BIN_DIR,但该目录不在 PATH 中"
358
+ print_info "请将以下行添加到 shell 配置文件(~/.zshrc 或 ~/.bashrc):"
359
+ echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
360
+ fi
361
+ mkdir -p "$BIN_DIR"
362
+ ln -sf "$SCRIPT_DIR/bin/cla" "$BIN_DIR/cla"
363
+ ln -sf "$SCRIPT_DIR/bin/cl" "$BIN_DIR/cl"
364
+ ln -sf "$SCRIPT_DIR/bin/remote-claude" "$BIN_DIR/remote-claude"
365
+ else
366
+ ln -sf "$SCRIPT_DIR/bin/cl" "$BIN_DIR/cl"
367
+ ln -sf "$SCRIPT_DIR/bin/remote-claude" "$BIN_DIR/remote-claude"
368
+ fi
369
+
370
+ print_success "已安装 cla、cl 和 remote-claude 到 $BIN_DIR"
371
+ print_info " cla - 启动飞书客户端 + 以当前目录路径+时间戳为会话名启动 Claude"
372
+ print_info " cl - 同 cla,但跳过权限确认"
373
+ print_info " remote-claude - Remote Claude 主命令(start/attach/list/kill/lark)"
374
+
375
+ # 安装 shell 自动补全
376
+ local COMPLETION_LINE="source \"$SCRIPT_DIR/scripts/completion.sh\""
377
+ local SHELL_RC=""
378
+ if [[ -n "$ZSH_VERSION" ]] || [[ "$(basename "$SHELL")" == "zsh" ]]; then
379
+ SHELL_RC="$HOME/.zshrc"
380
+ else
381
+ SHELL_RC="$HOME/.bashrc"
382
+ fi
383
+
384
+ if [[ -f "$SHELL_RC" ]] && grep -qF "$SCRIPT_DIR/scripts/completion.sh" "$SHELL_RC" 2>/dev/null; then
385
+ print_info "自动补全已配置($SHELL_RC)"
386
+ else
387
+ echo "" >> "$SHELL_RC"
388
+ echo "# remote-claude 自动补全" >> "$SHELL_RC"
389
+ echo "$COMPLETION_LINE" >> "$SHELL_RC"
390
+ print_success "已添加自动补全到 $SHELL_RC(重新打开终端后生效)"
391
+ fi
392
+ }
393
+
394
+ # 重启飞书客户端
395
+ restart_lark_client() {
396
+ print_header "重启飞书客户端"
397
+
398
+ LARK_PID_FILE="/tmp/remote-claude/lark.pid"
399
+
400
+ if [ ! -f "$LARK_PID_FILE" ] && ! pgrep -f "lark_client/main.py" &>/dev/null; then
401
+ print_info "飞书客户端未运行,跳过重启"
402
+ return
403
+ fi
404
+
405
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
406
+ print_info "正在重启飞书客户端..."
407
+ cd "$SCRIPT_DIR"
408
+ uv run python3 remote_claude.py lark restart
409
+ print_success "飞书客户端已重启"
410
+ }
411
+
412
+ # 显示使用说明
413
+ show_usage() {
414
+ print_header "安装完成!"
415
+
416
+ cat << EOF
417
+ ${YELLOW}快捷命令:${NC}
418
+
419
+ ${GREEN}cla${NC} - 启动飞书客户端 + 以当前目录+时间戳为会话名启动 Claude
420
+ ${GREEN}cl${NC} - 同 cla,但跳过权限确认
421
+
422
+ 详细使用说明请阅读 README.md
423
+
424
+ EOF
425
+ }
426
+
427
+ # 主流程
428
+ main() {
429
+ # 解析参数
430
+ NPM_MODE=false
431
+ for arg in "$@"; do
432
+ [[ "$arg" == "--npm" ]] && NPM_MODE=true
433
+ done
434
+
435
+ echo ""
436
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
437
+ echo -e "${GREEN} Remote Claude 初始化脚本${NC}"
438
+ echo -e "${GREEN} 双端共享 Claude CLI 工具${NC}"
439
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
440
+ echo ""
441
+
442
+ check_os
443
+ check_uv
444
+ check_tmux
445
+ check_claude
446
+ install_dependencies
447
+ if ! $NPM_MODE; then
448
+ configure_lark
449
+ fi
450
+ create_directories
451
+ set_permissions
452
+ configure_shell
453
+ restart_lark_client
454
+ show_usage
455
+
456
+ if [ ${#WARNINGS[@]} -gt 0 ]; then
457
+ echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
458
+ echo -e "${YELLOW}⚠ 注意事项${NC}"
459
+ echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
460
+ for w in "${WARNINGS[@]}"; do
461
+ echo -e "${YELLOW}⚠${NC} $w"
462
+ done
463
+ echo ""
464
+ fi
465
+ }
466
+
467
+ # 运行主流程
468
+ main "$@"
@@ -6,9 +6,21 @@ import os
6
6
  from pathlib import Path
7
7
  from dotenv import load_dotenv
8
8
 
9
- # 加载 .env 文件
10
- BASE_DIR = Path(__file__).resolve().parent.parent
11
- load_dotenv(BASE_DIR / ".env")
9
+ import sys
10
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
11
+ from utils.session import USER_DATA_DIR, get_env_file
12
+
13
+ # 加载 .env 文件,优先从 ~/.remote-claude/.env 读取
14
+ _env_file = get_env_file()
15
+ _old_env_file = Path(__file__).resolve().parent.parent / ".env"
16
+
17
+ if not _env_file.exists() and _old_env_file.exists():
18
+ import shutil
19
+ USER_DATA_DIR.mkdir(parents=True, exist_ok=True)
20
+ shutil.move(str(_old_env_file), str(_env_file))
21
+ print(f"[config] 已将 .env 迁移到 {_env_file}")
22
+
23
+ load_dotenv(_env_file)
12
24
 
13
25
  # 飞书应用配置
14
26
  FEISHU_APP_ID = os.getenv("FEISHU_APP_ID", "")
@@ -31,7 +31,7 @@ from .card_builder import (
31
31
  from .shared_memory_poller import SharedMemoryPoller, CardSlice
32
32
 
33
33
  sys.path.insert(0, str(Path(__file__).parent.parent))
34
- from utils.session import list_active_sessions, get_socket_path
34
+ from utils.session import list_active_sessions, get_socket_path, get_chat_bindings_file, ensure_user_data_dir
35
35
 
36
36
  try:
37
37
  from stats import track as _track_stats
@@ -42,9 +42,18 @@ except Exception:
42
42
  class LarkHandler:
43
43
  """飞书消息处理器(群聊/私聊统一逻辑)"""
44
44
 
45
- _CHAT_BINDINGS_FILE = Path("/tmp/remote-claude/lark_chat_bindings.json")
45
+ _CHAT_BINDINGS_FILE = get_chat_bindings_file()
46
+ _OLD_CHAT_BINDINGS_FILE = Path("/tmp/remote-claude/lark_chat_bindings.json")
46
47
 
47
48
  def __init__(self):
49
+ # 兼容迁移:旧绑定文件存在而新路径不存在时,自动迁移
50
+ if not self._CHAT_BINDINGS_FILE.exists() and self._OLD_CHAT_BINDINGS_FILE.exists():
51
+ try:
52
+ import shutil
53
+ ensure_user_data_dir()
54
+ shutil.move(str(self._OLD_CHAT_BINDINGS_FILE), str(self._CHAT_BINDINGS_FILE))
55
+ except Exception as e:
56
+ logger.warning(f"迁移旧绑定文件失败: {e}")
48
57
  # chat_id → SessionBridge(活跃连接)
49
58
  self._bridges: Dict[str, SessionBridge] = {}
50
59
  # chat_id → session_name(当前连接状态)
@@ -68,7 +77,7 @@ class LarkHandler:
68
77
 
69
78
  def _save_chat_bindings(self):
70
79
  try:
71
- self._CHAT_BINDINGS_FILE.parent.mkdir(parents=True, exist_ok=True)
80
+ ensure_user_data_dir()
72
81
  self._CHAT_BINDINGS_FILE.write_text(
73
82
  json.dumps(self._chat_bindings, ensure_ascii=False)
74
83
  )
@@ -250,7 +250,7 @@ class LarkBot:
250
250
  # 检查配置
251
251
  if not config.FEISHU_APP_ID or not config.FEISHU_APP_SECRET:
252
252
  print("错误: 请配置 FEISHU_APP_ID 和 FEISHU_APP_SECRET")
253
- print("在 .env 文件中添加:")
253
+ print("在 ~/.remote-claude/.env 文件中添加:")
254
254
  print(" FEISHU_APP_ID=your_app_id")
255
255
  print(" FEISHU_APP_SECRET=your_app_secret")
256
256
  return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remote-claude",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "双端共享 Claude CLI 工具",
5
5
  "bin": {
6
6
  "remote-claude": "bin/remote-claude",
@@ -13,6 +13,7 @@
13
13
  "files": [
14
14
  "bin/",
15
15
  "scripts/",
16
+ "init.sh",
16
17
  "remote_claude.py",
17
18
  "server/*.py",
18
19
  "client/*.py",
package/remote_claude.py CHANGED
@@ -29,7 +29,8 @@ from utils.session import (
29
29
  tmux_kill_session,
30
30
  list_active_sessions, is_session_active, cleanup_session,
31
31
  is_lark_running, get_lark_pid, get_lark_status, get_lark_pid_file,
32
- save_lark_status, cleanup_lark
32
+ save_lark_status, cleanup_lark,
33
+ USER_DATA_DIR, ensure_user_data_dir, get_lark_log_file
33
34
  )
34
35
 
35
36
 
@@ -196,9 +197,10 @@ def cmd_lark_start(args):
196
197
  print("正在启动飞书客户端...")
197
198
 
198
199
  ensure_socket_dir()
200
+ ensure_user_data_dir()
199
201
 
200
202
  # 启动守护进程(使用 -m 模块方式运行,确保相对导入正常工作)
201
- log_file = SCRIPT_DIR / "lark_client.log"
203
+ log_file = get_lark_log_file()
202
204
 
203
205
  try:
204
206
  # 启动进程
@@ -319,7 +321,7 @@ def cmd_lark_status(args):
319
321
  print(f"运行时长: {status['uptime']}")
320
322
 
321
323
  # 检查日志文件
322
- log_file = SCRIPT_DIR / "lark_client.log"
324
+ log_file = get_lark_log_file()
323
325
  if log_file.exists():
324
326
  print(f"日志文件: {log_file}")
325
327
  print(f"日志大小: {log_file.stat().st_size / 1024:.1f} KB")
@@ -3,7 +3,8 @@
3
3
  # 用法: source scripts/check-env.sh "$INSTALL_DIR"
4
4
 
5
5
  INSTALL_DIR="${1:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
6
- ENV_FILE="$INSTALL_DIR/.env"
6
+ ENV_FILE="$HOME/.remote-claude/.env"
7
+ mkdir -p "$HOME/.remote-claude"
7
8
  ENV_OK=false
8
9
 
9
10
  if [ -f "$ENV_FILE" ]; then
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash
2
2
  set -e
3
3
 
4
- # 解析安装目录
4
+ # 解析安装目录(兼容符号链接)
5
5
  SOURCE="${BASH_SOURCE[0]}"
6
6
  while [ -L "$SOURCE" ]; do
7
7
  DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
@@ -10,67 +10,5 @@ while [ -L "$SOURCE" ]; do
10
10
  done
11
11
  INSTALL_DIR="$(cd -P "$(dirname "$SOURCE")" && cd .. && pwd)"
12
12
 
13
- echo ""
14
- echo "=== Remote Claude 安装后初始化 ==="
15
- echo ""
16
-
17
- # 1. 检查/安装 uv
18
- if ! command -v uv &>/dev/null; then
19
- if [ -f "$HOME/.local/bin/uv" ]; then
20
- export PATH="$HOME/.local/bin:$PATH"
21
- else
22
- echo "正在安装 uv..."
23
- curl -LsSf https://astral.sh/uv/install.sh | sh
24
- export PATH="$HOME/.local/bin:$PATH"
25
- fi
26
- fi
27
-
28
- if ! command -v uv &>/dev/null; then
29
- echo "错误: uv 安装失败,请手动安装: https://docs.astral.sh/uv/getting-started/installation/"
30
- exit 1
31
- fi
32
-
33
- echo "✓ uv $(uv --version)"
34
-
35
- # 2. 安装 Python 依赖
36
- echo "正在安装 Python 依赖..."
37
13
  cd "$INSTALL_DIR"
38
- uv sync --frozen
39
- echo "✓ Python 依赖安装完成"
40
-
41
- # 3. 设置文件可执行权限
42
- chmod +x "$INSTALL_DIR/bin/remote-claude"
43
- chmod +x "$INSTALL_DIR/bin/cla"
44
- chmod +x "$INSTALL_DIR/bin/cl"
45
- chmod +x "$INSTALL_DIR/scripts/check-env.sh"
46
- echo "✓ 文件权限设置完成"
47
-
48
- # 4. 检查可选依赖
49
- echo ""
50
- echo "=== 依赖检查 ==="
51
-
52
- if ! command -v tmux &>/dev/null; then
53
- echo "⚠ tmux 未安装(需要 >= 3.6)"
54
- echo " macOS: brew install tmux"
55
- echo " Linux: sudo apt install tmux 或 sudo yum install tmux"
56
- else
57
- TMUX_VER=$(tmux -V | grep -oE '[0-9]+\.[0-9]+' | head -1)
58
- echo "✓ tmux $TMUX_VER"
59
- fi
60
-
61
- if ! command -v claude &>/dev/null; then
62
- echo "⚠ claude CLI 未安装"
63
- echo " 安装方法: npm install -g @anthropic-ai/claude-code"
64
- else
65
- echo "✓ claude CLI 已安装"
66
- fi
67
-
68
- echo ""
69
- echo "=== 安装完成 ==="
70
- echo ""
71
- echo "使用方法:"
72
- echo " remote-claude start <会话名> 启动 Claude 会话"
73
- echo " remote-claude list 列出所有会话"
74
- echo " remote-claude attach <会话名> 连接到会话"
75
- echo " cla 启动飞书 + Claude(会引导配置飞书凭证)"
76
- echo ""
14
+ bash init.sh --npm
@@ -18,8 +18,19 @@ from typing import Optional
18
18
  from .machine import get_machine_id, get_machine_info
19
19
 
20
20
  # SQLite 数据库路径(持久化,不受 /tmp 清理影响)
21
- _DB_DIR = Path.home() / ".local" / "share" / "remote-claude"
21
+ _DB_DIR = Path.home() / ".remote-claude"
22
22
  _DB_PATH = _DB_DIR / "stats.db"
23
+ _OLD_DB_DIR = Path.home() / ".local" / "share" / "remote-claude"
24
+ _OLD_DB_PATH = _OLD_DB_DIR / "stats.db"
25
+
26
+ # 兼容迁移:旧 DB 存在而新路径不存在时,自动迁移
27
+ if not _DB_PATH.exists() and _OLD_DB_PATH.exists():
28
+ try:
29
+ import shutil as _shutil
30
+ _DB_DIR.mkdir(parents=True, exist_ok=True)
31
+ _shutil.move(str(_OLD_DB_PATH), str(_DB_PATH))
32
+ except Exception:
33
+ pass
23
34
 
24
35
  # 批量写入阈值
25
36
  _FLUSH_INTERVAL = 10.0 # 秒
package/stats/machine.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  机器标识模块
3
3
 
4
- 持久化 UUID(~/.remote-claude-id),用于 Mixpanel distinct_id 和跨机器去重。
4
+ 持久化 UUID(~/.remote-claude/machine-id),用于 Mixpanel distinct_id 和跨机器去重。
5
5
  """
6
6
 
7
7
  import os
@@ -10,16 +10,27 @@ import uuid
10
10
  from pathlib import Path
11
11
 
12
12
 
13
- _ID_FILE = Path.home() / ".remote-claude-id"
13
+ _USER_DIR = Path.home() / ".remote-claude"
14
+ _ID_FILE = _USER_DIR / "machine-id"
15
+ _OLD_ID_FILE = Path.home() / ".remote-claude-id"
14
16
  _machine_id: str | None = None
15
17
 
16
18
 
17
19
  def get_machine_id() -> str:
18
- """获取(或生成)机器 UUID,持久化到 ~/.remote-claude-id"""
20
+ """获取(或生成)机器 UUID,持久化到 ~/.remote-claude/machine-id"""
19
21
  global _machine_id
20
22
  if _machine_id:
21
23
  return _machine_id
22
24
 
25
+ # 兼容迁移:旧文件存在而新文件不存在时,自动迁移
26
+ if not _ID_FILE.exists() and _OLD_ID_FILE.exists():
27
+ try:
28
+ import shutil
29
+ _USER_DIR.mkdir(parents=True, exist_ok=True)
30
+ shutil.move(str(_OLD_ID_FILE), str(_ID_FILE))
31
+ except Exception:
32
+ pass
33
+
23
34
  if _ID_FILE.exists():
24
35
  try:
25
36
  _machine_id = _ID_FILE.read_text().strip()
@@ -31,6 +42,7 @@ def get_machine_id() -> str:
31
42
  # 首次生成
32
43
  _machine_id = str(uuid.uuid4())
33
44
  try:
45
+ _USER_DIR.mkdir(parents=True, exist_ok=True)
34
46
  _ID_FILE.write_text(_machine_id)
35
47
  except Exception:
36
48
  pass # 写失败也继续,只是无法持久化
package/stats/query.py CHANGED
@@ -7,7 +7,7 @@ import time
7
7
  from pathlib import Path
8
8
  from typing import Optional
9
9
 
10
- _DB_PATH = Path.home() / ".local" / "share" / "remote-claude" / "stats.db"
10
+ _DB_PATH = Path.home() / ".remote-claude" / "stats.db"
11
11
 
12
12
 
13
13
  def _get_conn() -> Optional[sqlite3.Connection]:
package/utils/session.py CHANGED
@@ -16,9 +16,30 @@ import uuid
16
16
 
17
17
  # 常量
18
18
  SOCKET_DIR = Path("/tmp/remote-claude")
19
+ USER_DATA_DIR = Path.home() / ".remote-claude"
19
20
  TMUX_SESSION_PREFIX = "rc-"
20
21
 
21
22
 
23
+ def get_env_file() -> Path:
24
+ """获取 .env 配置文件路径"""
25
+ return USER_DATA_DIR / ".env"
26
+
27
+
28
+ def get_chat_bindings_file() -> Path:
29
+ """获取飞书聊天绑定持久化文件路径"""
30
+ return USER_DATA_DIR / "lark_chat_bindings.json"
31
+
32
+
33
+ def get_lark_log_file() -> Path:
34
+ """获取飞书客户端日志文件路径"""
35
+ return USER_DATA_DIR / "lark_client.log"
36
+
37
+
38
+ def ensure_user_data_dir():
39
+ """确保用户数据目录存在"""
40
+ USER_DATA_DIR.mkdir(parents=True, exist_ok=True)
41
+
42
+
22
43
  def _safe_filename(session_name: str) -> str:
23
44
  """将会话名转为安全文件名(/ 和 . 替换为 _)"""
24
45
  return session_name.replace('/', '_').replace('.', '_')