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 +4 -0
- package/README.md +1 -0
- package/client/client.py +41 -2
- package/init.sh +18 -0
- package/lark_client/session_bridge.py +40 -1
- package/package.json +2 -1
- package/server/parsers/__init__.py +13 -0
- package/server/parsers/base_parser.py +44 -0
- package/server/parsers/claude_parser.py +1263 -0
- package/server/parsers/codex_parser.py +1496 -0
- package/server/server.py +50 -27
- package/utils/session.py +9 -1
package/.env.example
CHANGED
package/README.md
CHANGED
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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
|
+
...
|