remote-claude 0.1.0 → 0.1.2

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,534 @@
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
+
65
+ # 方式一:官方安装脚本
66
+ if curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null; then
67
+ export PATH="$HOME/.local/bin:$PATH"
68
+ fi
69
+
70
+ # 方式二:brew(macOS 备用)
71
+ if ! command -v uv &> /dev/null && [[ "$OS" == "Darwin" ]] && command -v brew &> /dev/null; then
72
+ print_warning "官方脚本安装失败,尝试 brew install uv..."
73
+ brew install uv
74
+ fi
75
+
76
+ # 方式三:pip/pip3 备用
77
+ if ! command -v uv &> /dev/null; then
78
+ print_warning "尝试 pip 安装 uv..."
79
+ if command -v pip3 &> /dev/null; then
80
+ pip3 install uv --quiet
81
+ elif command -v pip &> /dev/null; then
82
+ pip install uv --quiet
83
+ fi
84
+ export PATH="$HOME/.local/bin:$PATH"
85
+ fi
86
+
87
+ if command -v uv &> /dev/null; then
88
+ print_success "uv 安装成功"
89
+
90
+ # 确保 ~/.local/bin 写入 shell rc(uv 官方脚本可能写 .zprofile 而非 .zshrc)
91
+ local _RC
92
+ if [[ -n "$ZSH_VERSION" ]] || [[ "$(basename "$SHELL")" == "zsh" ]]; then
93
+ _RC="$HOME/.zshrc"
94
+ else
95
+ _RC="$HOME/.bashrc"
96
+ fi
97
+ if ! grep -qF "$HOME/.local/bin" "$_RC" 2>/dev/null; then
98
+ echo "" >> "$_RC"
99
+ echo "# uv" >> "$_RC"
100
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$_RC"
101
+ print_success "已将 \$HOME/.local/bin 写入 $_RC"
102
+ fi
103
+ else
104
+ print_error "uv 安装失败,请手动安装: https://docs.astral.sh/uv/"
105
+ exit 1
106
+ fi
107
+ }
108
+
109
+ # 检查并安装 tmux(要求 3.6+)
110
+ check_tmux() {
111
+ print_header "检查 tmux"
112
+
113
+ REQUIRED_MAJOR=3
114
+ REQUIRED_MINOR=6
115
+
116
+ install_tmux() {
117
+ if [[ "$OS" == "Darwin" ]]; then
118
+ if ! command -v brew &> /dev/null; then
119
+ print_warning "未找到 Homebrew,正在自动安装..."
120
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
121
+ # 将 Homebrew 加入 PATH(Apple Silicon / Intel 路径不同)
122
+ if [[ -x "/opt/homebrew/bin/brew" ]]; then
123
+ eval "$(/opt/homebrew/bin/brew shellenv)"
124
+ elif [[ -x "/usr/local/bin/brew" ]]; then
125
+ eval "$(/usr/local/bin/brew shellenv)"
126
+ fi
127
+ if ! command -v brew &> /dev/null; then
128
+ print_error "Homebrew 安装失败,请手动安装后重试: https://brew.sh"
129
+ exit 1
130
+ fi
131
+ print_success "Homebrew 安装成功"
132
+ fi
133
+ brew install tmux
134
+ elif [[ "$OS" == "Linux" ]]; then
135
+ if command -v apt-get &> /dev/null; then
136
+ sudo apt-get update && sudo apt-get install -y tmux
137
+ elif command -v yum &> /dev/null; then
138
+ sudo yum install -y tmux
139
+ elif command -v pacman &> /dev/null; then
140
+ sudo pacman -Sy --noconfirm tmux
141
+ elif command -v apk &> /dev/null; then
142
+ sudo apk add --no-cache tmux
143
+ elif command -v zypper &> /dev/null; then
144
+ sudo zypper install -y tmux
145
+ else
146
+ print_warning "无法识别包管理器,尝试从源码编译 tmux..."
147
+ install_tmux_from_source
148
+ return
149
+ fi
150
+ fi
151
+ print_success "tmux 安装成功"
152
+ }
153
+
154
+ install_tmux_from_source() {
155
+ local TMUX_VERSION_TAG="3.6a"
156
+ local TMUX_URL="https://github.com/tmux/tmux/releases/download/${TMUX_VERSION_TAG}/tmux-${TMUX_VERSION_TAG}.tar.gz"
157
+
158
+ print_warning "包管理器版本不满足要求,尝试从源码编译 tmux ${TMUX_VERSION_TAG}..."
159
+
160
+ # 安装编译依赖
161
+ if [[ "$OS" == "Darwin" ]]; then
162
+ brew install libevent ncurses pkg-config bison 2>/dev/null || true
163
+ elif command -v apt-get &> /dev/null; then
164
+ sudo apt-get install -y build-essential libevent-dev libncurses5-dev libncursesw5-dev bison pkg-config
165
+ elif command -v yum &> /dev/null; then
166
+ sudo yum groupinstall -y "Development Tools"
167
+ sudo yum install -y libevent-devel ncurses-devel bison
168
+ fi
169
+
170
+ # 确定安装前缀
171
+ local PREFIX="/usr/local"
172
+ if ! sudo -n true 2>/dev/null; then
173
+ print_warning "无 sudo 权限,将安装到 \$HOME/.local"
174
+ PREFIX="$HOME/.local"
175
+ fi
176
+
177
+ # 创建临时目录,编译完成后清理
178
+ local TMPDIR
179
+ TMPDIR=$(mktemp -d)
180
+ trap "rm -rf '$TMPDIR'" RETURN
181
+
182
+ print_warning "下载 tmux-${TMUX_VERSION_TAG}.tar.gz..."
183
+ if ! curl -fsSL "$TMUX_URL" -o "$TMPDIR/tmux.tar.gz"; then
184
+ print_warning "下载失败,请检查网络或手动安装 tmux ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+"
185
+ WARNINGS+=("tmux 源码下载失败,请手动安装 tmux ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+")
186
+ return
187
+ fi
188
+
189
+ tar -xzf "$TMPDIR/tmux.tar.gz" -C "$TMPDIR"
190
+ local SRC_DIR
191
+ SRC_DIR=$(find "$TMPDIR" -maxdepth 1 -type d -name "tmux-*" | head -1)
192
+
193
+ print_warning "编译 tmux(可能需要几分钟)..."
194
+ if ! (cd "$SRC_DIR" && ./configure --prefix="$PREFIX" && make -j"$(nproc 2>/dev/null || echo 2)"); then
195
+ print_warning "编译失败,请手动安装 tmux ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+"
196
+ WARNINGS+=("tmux 源码编译失败,请手动安装 tmux ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+")
197
+ return
198
+ fi
199
+
200
+ if [[ "$PREFIX" == "/usr/local" ]]; then
201
+ sudo make -C "$SRC_DIR" install
202
+ else
203
+ make -C "$SRC_DIR" install
204
+ # 若 $HOME/.local/bin 不在 PATH 中,自动写入 shell 配置
205
+ if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
206
+ export PATH="$HOME/.local/bin:$PATH"
207
+ local _RC
208
+ if [[ "$(basename "$SHELL")" == "zsh" ]]; then
209
+ _RC="$HOME/.zshrc"
210
+ else
211
+ _RC="$HOME/.bashrc"
212
+ fi
213
+ local _PATH_LINE='export PATH="$HOME/.local/bin:$PATH"'
214
+ if ! grep -qF "$HOME/.local/bin" "$_RC" 2>/dev/null; then
215
+ echo "" >> "$_RC"
216
+ echo "# remote-claude: tmux 路径" >> "$_RC"
217
+ echo "$_PATH_LINE" >> "$_RC"
218
+ print_success "已自动将 \$HOME/.local/bin 加入 PATH(写入 $_RC)"
219
+ fi
220
+ fi
221
+ fi
222
+
223
+ print_success "tmux ${TMUX_VERSION_TAG} 源码编译安装完成(前缀:${PREFIX})"
224
+ }
225
+
226
+ check_version() {
227
+ # tmux -V 输出格式:tmux 3.6 或 tmux 3.4a
228
+ local ver_str
229
+ ver_str=$(tmux -V | awk '{print $2}')
230
+ local major minor
231
+ major=$(echo "$ver_str" | cut -d. -f1)
232
+ minor=$(echo "$ver_str" | cut -d. -f2 | tr -dc '0-9')
233
+ if [[ "$major" -gt "$REQUIRED_MAJOR" ]] || \
234
+ [[ "$major" -eq "$REQUIRED_MAJOR" && "${minor:-0}" -ge "$REQUIRED_MINOR" ]]; then
235
+ return 0
236
+ fi
237
+ return 1
238
+ }
239
+
240
+ if command -v tmux &> /dev/null; then
241
+ TMUX_VERSION=$(tmux -V)
242
+ if check_version; then
243
+ print_success "$TMUX_VERSION 已安装(满足 >= ${REQUIRED_MAJOR}.${REQUIRED_MINOR})"
244
+ return
245
+ else
246
+ print_warning "$TMUX_VERSION 版本过低,需要 ${REQUIRED_MAJOR}.${REQUIRED_MINOR} 或更高,正在升级..."
247
+ install_tmux
248
+ # 升级后再次验证,版本仍不满足则走源码编译(跨平台)
249
+ if ! check_version; then
250
+ install_tmux_from_source
251
+ if check_version; then
252
+ print_success "tmux 已升级至 $(tmux -V)"
253
+ else
254
+ print_warning "源码编译后版本仍不满足要求($(tmux -V)),需要 ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+"
255
+ WARNINGS+=("tmux 版本不满足要求($(tmux -V)),需要 ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+,请手动升级")
256
+ fi
257
+ else
258
+ print_success "tmux 已升级至 $(tmux -V)"
259
+ fi
260
+ fi
261
+ else
262
+ print_warning "未找到 tmux,正在安装..."
263
+ install_tmux
264
+ if ! check_version; then
265
+ install_tmux_from_source
266
+ if ! check_version; then
267
+ print_warning "源码编译后版本仍不满足要求($(tmux -V)),需要 ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+"
268
+ WARNINGS+=("tmux 版本不满足要求($(tmux -V)),需要 ${REQUIRED_MAJOR}.${REQUIRED_MINOR}+,请手动升级")
269
+ fi
270
+ fi
271
+ fi
272
+ }
273
+
274
+ # 检查 Claude CLI
275
+ check_claude() {
276
+ print_header "检查 Claude CLI"
277
+
278
+ if command -v claude &> /dev/null; then
279
+ print_success "Claude CLI 已安装"
280
+ return
281
+ fi
282
+
283
+ print_warning "未找到 Claude CLI"
284
+ print_info "请访问 https://claude.ai/code 安装 Claude CLI"
285
+
286
+ if $NPM_MODE; then
287
+ print_info "(npm 模式:跳过交互,请安装后重新运行)"
288
+ return
289
+ fi
290
+
291
+ read -p "$(echo -e ${YELLOW}是否已安装 Claude CLI?${NC} [y/N]: )" -n 1 -r
292
+ echo
293
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
294
+ print_error "请先安装 Claude CLI 后再运行此脚本"
295
+ exit 1
296
+ fi
297
+
298
+ if ! command -v claude &> /dev/null; then
299
+ print_error "仍未找到 claude 命令,请检查安装或 PATH 配置"
300
+ exit 1
301
+ fi
302
+ }
303
+
304
+ # 安装 Python 依赖
305
+ install_dependencies() {
306
+ print_header "安装 Python 依赖"
307
+
308
+ if [ ! -f "pyproject.toml" ]; then
309
+ print_error "未找到 pyproject.toml 文件"
310
+ exit 1
311
+ fi
312
+
313
+ print_info "正在通过 uv 同步依赖..."
314
+ if $NPM_MODE; then
315
+ uv sync --frozen
316
+ else
317
+ uv sync
318
+ fi
319
+
320
+ print_success "依赖安装完成"
321
+ }
322
+
323
+ # 配置飞书环境
324
+ configure_lark() {
325
+ print_header "配置飞书客户端"
326
+
327
+ ENV_FILE="$HOME/.remote-claude/.env"
328
+ mkdir -p "$HOME/.remote-claude"
329
+
330
+ # 迁移旧 .env(项目根目录)到新位置
331
+ if [ -f ".env" ] && [ ! -f "$ENV_FILE" ]; then
332
+ mv ".env" "$ENV_FILE"
333
+ print_success "已将 .env 迁移到 $ENV_FILE"
334
+ fi
335
+
336
+ if [ -f "$ENV_FILE" ]; then
337
+ print_warning ".env 文件已存在($ENV_FILE),跳过配置"
338
+ return
339
+ fi
340
+
341
+ if [ ! -f ".env.example" ]; then
342
+ print_error "未找到 .env.example 文件"
343
+ exit 1
344
+ fi
345
+
346
+ read -p "$(echo -e ${YELLOW}是否需要配置飞书客户端?${NC} [y/N]: )" -n 1 -r
347
+ echo
348
+
349
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
350
+ cp .env.example "$ENV_FILE"
351
+ print_success ".env 文件已创建于 $ENV_FILE"
352
+ print_warning "请编辑 $ENV_FILE,填写以下信息:"
353
+ print_info " - FEISHU_APP_ID: 飞书应用的 App ID"
354
+ print_info " - FEISHU_APP_SECRET: 飞书应用的 App Secret"
355
+ print_info ""
356
+ print_info "获取方式: 登录飞书开放平台 -> 创建应用 -> 凭证与基础信息"
357
+ else
358
+ print_info "跳过飞书配置(可稍后手动配置)"
359
+ fi
360
+ }
361
+
362
+ # 创建必要目录
363
+ create_directories() {
364
+ print_header "创建运行目录"
365
+
366
+ SOCKET_DIR="/tmp/remote-claude"
367
+ USER_DATA_DIR="$HOME/.remote-claude"
368
+
369
+ if [ ! -d "$SOCKET_DIR" ]; then
370
+ mkdir -p "$SOCKET_DIR"
371
+ print_success "创建目录: $SOCKET_DIR"
372
+ else
373
+ print_info "目录已存在: $SOCKET_DIR"
374
+ fi
375
+
376
+ if [ ! -d "$USER_DATA_DIR" ]; then
377
+ mkdir -p "$USER_DATA_DIR"
378
+ print_success "创建目录: $USER_DATA_DIR"
379
+ else
380
+ print_info "目录已存在: $USER_DATA_DIR"
381
+ fi
382
+ }
383
+
384
+ # 设置可执行权限
385
+ set_permissions() {
386
+ print_header "设置执行权限"
387
+
388
+ chmod +x remote_claude.py
389
+ chmod +x server/server.py
390
+ chmod +x client/client.py
391
+
392
+ print_success "已设置执行权限"
393
+ }
394
+
395
+ # 安装快捷命令(符号链接到 bin 目录)
396
+ configure_shell() {
397
+ print_header "安装快捷命令"
398
+
399
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
400
+ chmod +x "$SCRIPT_DIR/bin/cla" "$SCRIPT_DIR/bin/cl" "$SCRIPT_DIR/bin/remote-claude"
401
+
402
+ # 优先 /usr/local/bin,权限不够则选 ~/bin 或 ~/.local/bin 中已在 PATH 里的
403
+ BIN_DIR="/usr/local/bin"
404
+ if ! ln -sf "$SCRIPT_DIR/bin/cla" "$BIN_DIR/cla" 2>/dev/null; then
405
+ if [[ ":$PATH:" == *":$HOME/bin:"* ]]; then
406
+ BIN_DIR="$HOME/bin"
407
+ elif [[ ":$PATH:" == *":$HOME/.local/bin:"* ]]; then
408
+ BIN_DIR="$HOME/.local/bin"
409
+ else
410
+ BIN_DIR="$HOME/.local/bin"
411
+ # 自动写入 PATH 到 shell 配置文件
412
+ export PATH="$BIN_DIR:$PATH"
413
+ local _RC
414
+ if [[ -n "$ZSH_VERSION" ]] || [[ "$(basename "$SHELL")" == "zsh" ]]; then
415
+ _RC="$HOME/.zshrc"
416
+ else
417
+ _RC="$HOME/.bashrc"
418
+ fi
419
+ local _PATH_LINE='export PATH="$HOME/.local/bin:$PATH"'
420
+ if ! grep -qF "$HOME/.local/bin" "$_RC" 2>/dev/null; then
421
+ echo "" >> "$_RC"
422
+ echo "# remote-claude: 快捷命令路径" >> "$_RC"
423
+ echo "$_PATH_LINE" >> "$_RC"
424
+ print_success "已自动将 \$HOME/.local/bin 加入 PATH(写入 $_RC)"
425
+ fi
426
+ fi
427
+ mkdir -p "$BIN_DIR"
428
+ ln -sf "$SCRIPT_DIR/bin/cla" "$BIN_DIR/cla"
429
+ ln -sf "$SCRIPT_DIR/bin/cl" "$BIN_DIR/cl"
430
+ ln -sf "$SCRIPT_DIR/bin/remote-claude" "$BIN_DIR/remote-claude"
431
+ else
432
+ ln -sf "$SCRIPT_DIR/bin/cl" "$BIN_DIR/cl"
433
+ ln -sf "$SCRIPT_DIR/bin/remote-claude" "$BIN_DIR/remote-claude"
434
+ fi
435
+
436
+ print_success "已安装 cla、cl 和 remote-claude 到 $BIN_DIR"
437
+ print_info " cla - 启动飞书客户端 + 以当前目录路径+时间戳为会话名启动 Claude"
438
+ print_info " cl - 同 cla,但跳过权限确认"
439
+ print_info " remote-claude - Remote Claude 主命令(start/attach/list/kill/lark)"
440
+
441
+ # 安装 shell 自动补全
442
+ local COMPLETION_LINE="source \"$SCRIPT_DIR/scripts/completion.sh\""
443
+ local SHELL_RC=""
444
+ if [[ -n "$ZSH_VERSION" ]] || [[ "$(basename "$SHELL")" == "zsh" ]]; then
445
+ SHELL_RC="$HOME/.zshrc"
446
+ else
447
+ SHELL_RC="$HOME/.bashrc"
448
+ fi
449
+
450
+ if [[ -f "$SHELL_RC" ]] && grep -qF "$SCRIPT_DIR/scripts/completion.sh" "$SHELL_RC" 2>/dev/null; then
451
+ print_info "自动补全已配置($SHELL_RC)"
452
+ else
453
+ echo "" >> "$SHELL_RC"
454
+ echo "# remote-claude 自动补全" >> "$SHELL_RC"
455
+ echo "$COMPLETION_LINE" >> "$SHELL_RC"
456
+ print_success "已添加自动补全到 $SHELL_RC(重新打开终端后生效)"
457
+ fi
458
+ }
459
+
460
+ # 重启飞书客户端
461
+ restart_lark_client() {
462
+ print_header "重启飞书客户端"
463
+
464
+ LARK_PID_FILE="/tmp/remote-claude/lark.pid"
465
+
466
+ if [ ! -f "$LARK_PID_FILE" ] && ! pgrep -f "lark_client/main.py" &>/dev/null; then
467
+ print_info "飞书客户端未运行,跳过重启"
468
+ return
469
+ fi
470
+
471
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
472
+ print_info "正在重启飞书客户端..."
473
+ cd "$SCRIPT_DIR"
474
+ uv run python3 remote_claude.py lark restart
475
+ print_success "飞书客户端已重启"
476
+ }
477
+
478
+ # 显示使用说明
479
+ show_usage() {
480
+ print_header "安装完成!"
481
+
482
+ cat << EOF
483
+ ${YELLOW}快捷命令:${NC}
484
+
485
+ ${GREEN}cla${NC} - 启动飞书客户端 + 以当前目录+时间戳为会话名启动 Claude
486
+ ${GREEN}cl${NC} - 同 cla,但跳过权限确认
487
+
488
+ 详细使用说明请阅读 README.md
489
+
490
+ EOF
491
+ }
492
+
493
+ # 主流程
494
+ main() {
495
+ # 解析参数
496
+ NPM_MODE=false
497
+ for arg in "$@"; do
498
+ [[ "$arg" == "--npm" ]] && NPM_MODE=true
499
+ done
500
+
501
+ echo ""
502
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
503
+ echo -e "${GREEN} Remote Claude 初始化脚本${NC}"
504
+ echo -e "${GREEN} 双端共享 Claude CLI 工具${NC}"
505
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
506
+ echo ""
507
+
508
+ check_os
509
+ check_uv
510
+ check_tmux
511
+ check_claude
512
+ install_dependencies
513
+ if ! $NPM_MODE; then
514
+ configure_lark
515
+ fi
516
+ create_directories
517
+ set_permissions
518
+ configure_shell
519
+ restart_lark_client
520
+ show_usage
521
+
522
+ if [ ${#WARNINGS[@]} -gt 0 ]; then
523
+ echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
524
+ echo -e "${YELLOW}⚠ 注意事项${NC}"
525
+ echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
526
+ for w in "${WARNINGS[@]}"; do
527
+ echo -e "${YELLOW}⚠${NC} $w"
528
+ done
529
+ echo ""
530
+ fi
531
+ }
532
+
533
+ # 运行主流程
534
+ 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.2",
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('.', '_')