remote-claude 0.2.11 → 0.2.13

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
@@ -26,3 +26,7 @@ ALLOWED_USERS=ou_xxxxx,ou_yyyyy
26
26
  # 支持: DEBUG / INFO / WARNING / ERROR
27
27
  # LARK_LOG_LEVEL=INFO
28
28
 
29
+ # server 日志级别(可选,默认 INFO)
30
+ # 支持: DEBUG / INFO / WARNING / ERROR
31
+ # SERVER_LOG_LEVEL=INFO
32
+
package/README.md CHANGED
@@ -272,3 +272,4 @@ CLAUDE_COMMAND=/usr/local/bin/claude
272
272
 
273
273
  - [CLAUDE.md](./CLAUDE.md) — 项目架构和开发说明
274
274
  - [LARK_CLIENT_GUIDE.md](./LARK_CLIENT_GUIDE.md) — 飞书客户端完整指南
275
+ - [docker/README.md](./docker/README.md) — Docker 测试(npm 包发布前验证)
package/client/client.py CHANGED
@@ -57,16 +57,55 @@ class RemoteClient:
57
57
  async def connect(self) -> bool:
58
58
  """连接到服务器"""
59
59
  if not self.socket_path.exists():
60
- print(f"错误: 会话 '{self.session_name}' 不存在")
60
+ print(
61
+ f"❌ 错误: Socket 文件不存在\n"
62
+ f" 会话名: {self.session_name}\n"
63
+ f" Socket 路径: {self.socket_path}\n"
64
+ f"\n"
65
+ f" 请使用 `python3 remote_claude.py list` 查看可用会话"
66
+ )
61
67
  return False
62
68
 
63
69
  try:
64
70
  self.reader, self.writer = await asyncio.open_unix_connection(
65
71
  path=str(self.socket_path)
66
72
  )
73
+ print(f"✅ 已连接到会话: {self.session_name}")
67
74
  return True
75
+ except ConnectionRefusedError as e:
76
+ # 检查进程状态
77
+ from utils.session import list_active_sessions
78
+ sessions = list_active_sessions()
79
+ session_exists = any(s["name"] == self.session_name for s in sessions)
80
+
81
+ print(
82
+ f"❌ 连接失败: Connection refused\n"
83
+ f" 会话名: {self.session_name}\n"
84
+ f" Socket 路径: {self.socket_path}\n"
85
+ f" 文件存在: {self.socket_path.exists()}\n"
86
+ f" 会话在列表中: {session_exists}\n"
87
+ f"\n"
88
+ f" 当前活跃会话:"
89
+ )
90
+ for s in sessions:
91
+ print(f" - {s['name']} (PID: {s.get('pid', 'N/A')})")
92
+ print(
93
+ f"\n"
94
+ f" 可能原因:\n"
95
+ f" 1. Server 进程已终止但 Socket 文件残留\n"
96
+ f" 2. Socket 文件权限错误\n"
97
+ f"\n"
98
+ f" 建议操作:\n"
99
+ f" python3 remote_claude.py kill {self.session_name}\n"
100
+ f" python3 remote_claude.py start {self.session_name}"
101
+ )
102
+ return False
68
103
  except Exception as e:
69
- print(f"连接失败: {e}")
104
+ print(
105
+ f"❌ 连接失败: {type(e).__name__}: {e}\n"
106
+ f" 会话名: {self.session_name}\n"
107
+ f" Socket 路径: {self.socket_path}"
108
+ )
70
109
  return False
71
110
 
72
111
  async def run(self):
package/init.sh CHANGED
@@ -159,6 +159,19 @@ check_uv() {
159
159
  check_tmux() {
160
160
  print_header "检查 tmux"
161
161
 
162
+ # CI 模式:跳过 tmux 版本检查(Docker 环境可能没有 sudo)
163
+ if [ "$CI_MODE" = "true" ]; then
164
+ if command -v tmux &> /dev/null; then
165
+ TMUX_VERSION=$(tmux -V)
166
+ print_success "$TMUX_VERSION 已安装(CI 模式跳过版本检查)"
167
+ return
168
+ else
169
+ print_error "未找到 tmux"
170
+ WARNINGS+=("tmux 未安装,CI 模式跳过版本检查")
171
+ return
172
+ fi
173
+ fi
174
+
162
175
  REQUIRED_MAJOR=3
163
176
  REQUIRED_MINOR=6
164
177
 
@@ -581,6 +594,11 @@ main() {
581
594
  [[ "$arg" == "--npm" ]] && NPM_MODE=true
582
595
  done
583
596
 
597
+ # CI 模式:跳过 tmux 版本检查(CI 环境可能没有 sudo)
598
+ if [ -n "$CI" ]; then
599
+ export CI_MODE=true
600
+ fi
601
+
584
602
  echo ""
585
603
  echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
586
604
  echo -e "${GREEN} Remote Claude 初始化脚本${NC}"
@@ -47,6 +47,14 @@ class SessionBridge:
47
47
  async def connect(self) -> bool:
48
48
  """连接到会话"""
49
49
  if not self.socket_path.exists():
50
+ logger.error(
51
+ f"连接失败: Socket 文件不存在\n"
52
+ f" 会话名: {self.session_name}\n"
53
+ f" Socket 路径: {self.socket_path}\n"
54
+ f" 请确认:\n"
55
+ f" 1. 会话已启动 (使用 /list 查看)\n"
56
+ f" 2. 会话名拼写正确"
57
+ )
50
58
  return False
51
59
  try:
52
60
  self.reader, self.writer = await asyncio.open_unix_connection(
@@ -54,9 +62,40 @@ class SessionBridge:
54
62
  )
55
63
  self.running = True
56
64
  self._read_task = asyncio.create_task(self._read_loop())
65
+ logger.info(f"连接成功: {self.session_name}")
57
66
  return True
67
+ except FileNotFoundError:
68
+ logger.error(
69
+ f"连接失败: Socket 文件不存在\n"
70
+ f" 会话名: {self.session_name}\n"
71
+ f" Socket 路径: {self.socket_path}\n"
72
+ f" 可能原因: Socket 文件在连接前被删除"
73
+ )
74
+ return False
75
+ except ConnectionRefusedError as e:
76
+ # 检查进程状态
77
+ sessions = list_active_sessions()
78
+ session_exists = any(s["name"] == self.session_name for s in sessions)
79
+
80
+ logger.error(
81
+ f"连接失败: Connection refused\n"
82
+ f" 会话名: {self.session_name}\n"
83
+ f" Socket 路径: {self.socket_path}\n"
84
+ f" 文件存在: {self.socket_path.exists()}\n"
85
+ f" 会话在列表中: {session_exists}\n"
86
+ f" 当前活跃会话: {[s['name'] for s in sessions]}\n"
87
+ f" 可能原因:\n"
88
+ f" 1. Server 进程已终止但 Socket 文件残留\n"
89
+ f" 2. Socket 文件权限错误\n"
90
+ f" 建议: 使用 /kill {self.session_name} 清理后重新启动"
91
+ )
92
+ return False
58
93
  except Exception as e:
59
- logger.error(f"连接失败: {e}")
94
+ logger.error(
95
+ f"连接失败: {type(e).__name__}: {e}\n"
96
+ f" 会话名: {self.session_name}\n"
97
+ f" Socket 路径: {self.socket_path}"
98
+ )
60
99
  return False
61
100
 
62
101
  async def disconnect(self):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remote-claude",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "双端共享 Claude CLI 工具",
5
5
  "bin": {
6
6
  "remote-claude": "bin/remote-claude",
@@ -17,6 +17,7 @@
17
17
  "init.sh",
18
18
  "remote_claude.py",
19
19
  "server/*.py",
20
+ "server/parsers/*.py",
20
21
  "client/*.py",
21
22
  "utils/*.py",
22
23
  "lark_client/__init__.py",
@@ -0,0 +1,13 @@
1
+ """终端屏幕解析器包
2
+
3
+ 提供可插拔的解析器架构,支持 Claude CLI 和 Codex CLI(及未来其他 CLI 工具)。
4
+
5
+ 使用方法:
6
+ from parsers import ClaudeParser, CodexParser, BaseParser
7
+ """
8
+
9
+ from .base_parser import BaseParser
10
+ from .claude_parser import ClaudeParser, ScreenParser # ScreenParser 为向后兼容别名
11
+ from .codex_parser import CodexParser
12
+
13
+ __all__ = ['BaseParser', 'ClaudeParser', 'CodexParser', 'ScreenParser']
@@ -0,0 +1,44 @@
1
+ """终端屏幕解析器基类
2
+
3
+ 定义统一接口:parse(screen) -> List[Component]
4
+ ClaudeParser 和 CodexParser 均继承此类。
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import List
9
+
10
+ import pyte
11
+
12
+
13
+ class BaseParser(ABC):
14
+ """终端屏幕解析器基类。
15
+
16
+ 具体实现(ClaudeParser、CodexParser 等)继承此类,
17
+ 负责将 pyte.Screen 快照解析为 Component 列表。
18
+
19
+ Component 列表包含两类组件:
20
+ - 累积型 Block:OutputBlock, UserInput, PlanBlock, SystemBlock
21
+ - 状态型组件:StatusLine, BottomBar, AgentPanelBlock, OptionBlock
22
+
23
+ OutputWatcher 读取 last_input_text / last_input_ansi_text / last_layout_mode
24
+ 等属性来辅助生成 ClaudeWindow 快照。
25
+ """
26
+
27
+ # 最近一次解析结果的辅助属性(由子类在 parse() 中更新)
28
+ last_input_text: str = ''
29
+ last_input_ansi_text: str = ''
30
+ last_parse_timing: str = ''
31
+ last_layout_mode: str = 'normal'
32
+
33
+ @abstractmethod
34
+ def parse(self, screen: pyte.Screen) -> List:
35
+ """解析 pyte 屏幕,返回 Component 列表。
36
+
37
+ Args:
38
+ screen: pyte.Screen 快照(持久化渲染器的当前状态)
39
+
40
+ Returns:
41
+ Component 列表,包含累积型 Block 和状态型组件的混合。
42
+ 调用方(OutputWatcher)负责按类型分拣。
43
+ """
44
+ ...