remote-claude 0.2.9 → 0.2.10

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.
@@ -318,15 +318,20 @@ class LarkHandler:
318
318
  server_script = script_dir / "server" / "server.py"
319
319
  cmd = [sys.executable, str(server_script), session_name]
320
320
 
321
- logger.info(f"启动会话: {session_name}, 工作目录: {work_dir}, 命令: {cmd}")
321
+ logger.info(f"启动会话: {session_name}, 工作目录: {work_dir}, 命令: {' '.join(cmd)}")
322
322
  _track_stats('lark', 'cmd_start', session_name=session_name, chat_id=chat_id)
323
323
 
324
324
  try:
325
325
  import os as _os
326
+ from datetime import datetime as _datetime
326
327
  env = _os.environ.copy()
327
328
  env.pop("CLAUDECODE", None)
328
329
 
329
- subprocess.Popen(
330
+ from utils.session import USER_DATA_DIR
331
+ log_path = USER_DATA_DIR / "startup.log"
332
+ start_time = _datetime.now()
333
+
334
+ proc = subprocess.Popen(
330
335
  cmd,
331
336
  stdout=subprocess.DEVNULL,
332
337
  stderr=subprocess.DEVNULL,
@@ -335,13 +340,38 @@ class LarkHandler:
335
340
  env=env,
336
341
  )
337
342
 
343
+ def _read_log_since(since):
344
+ if not log_path.exists():
345
+ return ""
346
+ lines = []
347
+ for line in log_path.read_text(encoding="utf-8").splitlines():
348
+ try:
349
+ ts = _datetime.strptime(line[:23], "%Y-%m-%d %H:%M:%S.%f")
350
+ if ts >= since:
351
+ lines.append(line)
352
+ except ValueError:
353
+ if lines:
354
+ lines.append(line)
355
+ return "\n".join(lines)
356
+
338
357
  socket_path = get_socket_path(session_name)
339
- for _ in range(120):
358
+ for i in range(120):
340
359
  await asyncio.sleep(0.1)
341
360
  if socket_path.exists():
342
361
  break
362
+ if (i + 1) % 10 == 0:
363
+ elapsed = (i + 1) // 10
364
+ rc = proc.poll()
365
+ if rc is not None:
366
+ log_content = _read_log_since(start_time)
367
+ logger.warning(f"会话启动失败: server 进程已退出 (exitcode={rc}, elapsed={elapsed}s)\n{log_content}")
368
+ await card_service.send_text(chat_id, f"错误: Server 进程意外退出 (code={rc})\n\n{log_content}")
369
+ return
370
+ logger.info(f"等待 server socket... ({elapsed}s)")
343
371
  else:
344
- await card_service.send_text(chat_id, "错误: 会话启动超时")
372
+ log_content = _read_log_since(start_time)
373
+ logger.error(f"会话启动超时 (12s), session={session_name}\n{log_content}")
374
+ await card_service.send_text(chat_id, f"错误: 会话启动超时 (12s)\n\n{log_content}")
345
375
  return
346
376
 
347
377
  ok = await self._attach(chat_id, session_name)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remote-claude",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "双端共享 Claude CLI 工具",
5
5
  "bin": {
6
6
  "remote-claude": "bin/remote-claude",
@@ -33,7 +33,7 @@
33
33
  "license": "MIT",
34
34
  "repository": {
35
35
  "type": "git",
36
- "url": "git+https://github.com/yuyangzi/remote_claude.git"
36
+ "url": "git+https://github.com/yyzybb537/remote_claude.git"
37
37
  },
38
38
  "keywords": [
39
39
  "claude",
package/remote_claude.py CHANGED
@@ -11,11 +11,13 @@ Remote Claude - 双端共享 Claude CLI 工具
11
11
  """
12
12
 
13
13
  import argparse
14
+ import logging
14
15
  import os
15
16
  import sys
16
17
  import subprocess
17
18
  import time
18
19
  import signal
20
+ from datetime import datetime
19
21
  from pathlib import Path
20
22
 
21
23
  # 确保项目根目录在 sys.path 中,以便 import client / server 子模块
@@ -31,7 +33,7 @@ from utils.session import (
31
33
  is_lark_running, get_lark_pid, get_lark_status, get_lark_pid_file,
32
34
  save_lark_status, cleanup_lark,
33
35
  USER_DATA_DIR, ensure_user_data_dir, get_lark_log_file,
34
- get_env_snapshot_path
36
+ get_env_snapshot_path,
35
37
  )
36
38
 
37
39
 
@@ -91,7 +93,22 @@ def cmd_start(args):
91
93
 
92
94
  server_cmd = f"{env_prefix}uv run --project '{SCRIPT_DIR}' python3 '{server_script}'{debug_flag}{debug_verbose_flag}{cli_type_flag} -- '{session_name}' {claude_args_str}"
93
95
 
94
- print(f"启动会话: {session_name}")
96
+ # 配置启动日志(写文件 + stdout)
97
+ _log_path = USER_DATA_DIR / "startup.log"
98
+ _start_logger = logging.getLogger('Start')
99
+ if not _start_logger.handlers:
100
+ _handler_file = logging.FileHandler(_log_path, encoding="utf-8")
101
+ _handler_file.setFormatter(logging.Formatter(
102
+ "%(asctime)s.%(msecs)03d [%(name)s] %(levelname)s %(message)s",
103
+ datefmt="%Y-%m-%d %H:%M:%S",
104
+ ))
105
+ _start_logger.addHandler(_handler_file)
106
+ _start_logger.setLevel(logging.INFO)
107
+ _start_logger.propagate = False
108
+
109
+ start_time = datetime.now()
110
+ _start_logger.info(f"启动会话: {session_name}")
111
+ _start_logger.info(f"server_cmd: {server_cmd}")
95
112
 
96
113
  # 创建 tmux 会话,运行 server(detached,仅后台)
97
114
  if not tmux_create_session(session_name, server_cmd, detached=True):
@@ -100,12 +117,30 @@ def cmd_start(args):
100
117
 
101
118
  # 等待 server 启动
102
119
  socket_path = get_socket_path(session_name)
103
- for _ in range(50): # 最多等待 5 秒
120
+ for i in range(50): # 最多等待 5 秒
104
121
  if socket_path.exists():
105
122
  break
106
123
  time.sleep(0.1)
124
+ if (i + 1) % 10 == 0:
125
+ elapsed = (i + 1) // 10
126
+ print(f"等待 Server 启动... ({elapsed}s)")
107
127
  else:
108
- print("错误: Server 启动超时")
128
+ print("错误: Server 启动超时 (5s)")
129
+ # 过滤出本次启动后的日志行
130
+ if _log_path.exists():
131
+ lines = []
132
+ for line in _log_path.read_text(encoding="utf-8").splitlines():
133
+ try:
134
+ ts = datetime.strptime(line[:23], "%Y-%m-%d %H:%M:%S.%f")
135
+ if ts >= start_time:
136
+ lines.append(line)
137
+ except ValueError:
138
+ if lines: # 多行日志的续行,附到上一条
139
+ lines.append(line)
140
+ if lines:
141
+ print(f"--- Server 日志 ({_log_path}) ---")
142
+ print("\n".join(lines))
143
+ print("-------------------")
109
144
  tmux_kill_session(session_name)
110
145
  return 1
111
146
 
package/server/server.py CHANGED
@@ -9,6 +9,7 @@ Proxy Server
9
9
  """
10
10
 
11
11
  import asyncio
12
+ import logging
12
13
  import os
13
14
  import pty
14
15
  import signal
@@ -35,9 +36,12 @@ from utils.protocol import (
35
36
  )
36
37
  from utils.session import (
37
38
  get_socket_path, get_pid_file, ensure_socket_dir,
38
- generate_client_id, cleanup_session, _safe_filename, get_env_file
39
+ generate_client_id, cleanup_session, _safe_filename, get_env_file,
40
+ SOCKET_DIR
39
41
  )
40
42
 
43
+ logger = logging.getLogger('Server')
44
+
41
45
  # 加载用户 .env 配置(支持 CLAUDE_COMMAND 等)
42
46
  try:
43
47
  from dotenv import load_dotenv
@@ -64,6 +68,11 @@ class _FrameObs:
64
68
  status_line: Optional[object] # 本帧的 StatusLine(None=无)
65
69
  block_blink: bool = False # 本帧最后一个 OutputBlock 是否 is_streaming=True
66
70
  has_background_agents: bool = False # 底部栏是否有后台 agent 信息
71
+ # 用于字符变化检测(增强闪烁判断)
72
+ last_ob_start_row: int = -1 # 最后 OutputBlock 的起始行号(跨帧识别同一 block)
73
+ last_ob_indicator_char: str = '' # 指示符字符值(pyte char.data)
74
+ last_ob_indicator_fg: str = '' # 指示符前景色(pyte char.fg)
75
+ last_ob_indicator_bold: bool = False # 指示符 bold 属性(影响显示亮度)
67
76
 
68
77
 
69
78
  @dataclass
@@ -154,6 +163,9 @@ class OutputWatcher:
154
163
 
155
164
  def feed(self, data: bytes):
156
165
  self._renderer.feed(data) # 直接喂持久化 screen,不再缓存原始字节
166
+ # 诊断日志:记录 PTY 数据到达
167
+ if data:
168
+ logger.debug(f"[diag-feed] len={len(data)} data={data[:50]!r}")
157
169
  try:
158
170
  loop = asyncio.get_running_loop()
159
171
  except RuntimeError:
@@ -185,6 +197,8 @@ class OutputWatcher:
185
197
 
186
198
  async def _flush(self):
187
199
  self._pending = False
200
+ # 诊断日志:记录 flush 触发时间和帧窗口大小
201
+ logger.debug(f"[diag-flush] ts={time.time():.6f} window_size={len(self._frame_window)}")
188
202
  try:
189
203
  from utils.components import StatusLine, BottomBar, Divider, OutputBlock, AgentPanelBlock, OptionBlock
190
204
 
@@ -224,10 +238,24 @@ class OutputWatcher:
224
238
  # 4a. 记录原始帧观测(必须用未平滑的原始值)
225
239
  last_ob_blink = False
226
240
  last_ob_content = ''
241
+ last_ob_start_row = -1
242
+ last_ob_indicator_char = ''
243
+ last_ob_indicator_fg = ''
244
+ last_ob_indicator_bold = False
227
245
  for b in reversed(visible_blocks):
228
246
  if isinstance(b, OutputBlock):
229
247
  last_ob_blink = b.is_streaming
230
248
  last_ob_content = b.content[:40]
249
+ last_ob_start_row = b.start_row
250
+ # 直接读 pyte screen buffer 获取原始字符属性(用于变化检测)
251
+ if b.start_row >= 0:
252
+ try:
253
+ char = self._renderer.screen.buffer[b.start_row][0]
254
+ last_ob_indicator_char = str(getattr(char, 'data', ''))
255
+ last_ob_indicator_fg = str(getattr(char, 'fg', ''))
256
+ last_ob_indicator_bold = bool(getattr(char, 'bold', False))
257
+ except (KeyError, IndexError):
258
+ pass
231
259
  break
232
260
  if last_ob_blink:
233
261
  _blink_log = open(f"/tmp/remote-claude/{self._session_name}_blink.log", "a")
@@ -241,6 +269,10 @@ class OutputWatcher:
241
269
  status_line=raw_status_line,
242
270
  block_blink=last_ob_blink,
243
271
  has_background_agents=getattr(raw_bottom_bar, 'has_background_agents', False) if raw_bottom_bar else False,
272
+ last_ob_start_row=last_ob_start_row,
273
+ last_ob_indicator_char=last_ob_indicator_char,
274
+ last_ob_indicator_fg=last_ob_indicator_fg,
275
+ last_ob_indicator_bold=last_ob_indicator_bold,
244
276
  ))
245
277
  cutoff = now - self.WINDOW_SECONDS
246
278
  while self._frame_window and self._frame_window[0].ts < cutoff:
@@ -262,9 +294,27 @@ class OutputWatcher:
262
294
  None
263
295
  )
264
296
 
265
- # 4c. block blink 平滑:窗口内任意帧有 blink → streaming
266
- # 修改 visible_blocks 中最后一个 OutputBlock(平滑后再合并)
297
+ # 4c. block blink 平滑:两种触发路径
298
+ # 路径1:窗口内任意帧有 pyte blink 属性
267
299
  window_block_active = any(o.block_blink for o in window_list)
300
+
301
+ # 路径2:窗口内同一 block 的指示符字符值/颜色/bold 有变化(增强闪烁判断)
302
+ if not window_block_active and last_ob_start_row >= 0:
303
+ same_block = [o for o in window_list if o.last_ob_start_row == last_ob_start_row]
304
+ if len(same_block) >= 2:
305
+ chars = {o.last_ob_indicator_char for o in same_block if o.last_ob_indicator_char}
306
+ fgs = {o.last_ob_indicator_fg for o in same_block if o.last_ob_indicator_fg}
307
+ bolds = {o.last_ob_indicator_bold for o in same_block}
308
+ if len(chars) > 1 or len(fgs) > 1 or len(bolds) > 1:
309
+ window_block_active = True
310
+ # 记录字符变化触发原因
311
+ _blink_log = open(f"/tmp/remote-claude/{self._session_name}_blink.log", "a")
312
+ _blink_log.write(
313
+ f"[{time.strftime('%H:%M:%S')}] char-change row={last_ob_start_row}"
314
+ f" chars={chars} fgs={fgs} bolds={bolds}\n"
315
+ )
316
+ _blink_log.close()
317
+
268
318
  if window_block_active:
269
319
  for b in reversed(visible_blocks):
270
320
  if isinstance(b, OutputBlock):
@@ -303,6 +353,16 @@ class OutputWatcher:
303
353
  layout_mode=self._parser.last_layout_mode,
304
354
  cli_type=self._cli_type,
305
355
  )
356
+ # 诊断日志:检测最终输出中是否有同时存在 status_line 和 SystemBlock 的情况
357
+ if display_status:
358
+ status_prefix = display_status.raw[:30] if hasattr(display_status, 'raw') else str(display_status)[:30]
359
+ has_systemblock_with_status = any(
360
+ b.__class__.__name__ == 'SystemBlock' and
361
+ hasattr(b, 'content') and status_prefix in b.content
362
+ for b in all_blocks
363
+ )
364
+ if has_systemblock_with_status:
365
+ logger.debug(f"[diag-output] BOTH status_line and SystemBlock present! status_line={status_prefix!r}")
306
366
  self.last_window = window
307
367
 
308
368
  # 7. 输出
@@ -342,7 +402,7 @@ class OutputWatcher:
342
402
  lines.append(f" ansi_raw={sl.ansi_raw[:120]!r}")
343
403
  lines.append(f" ansi_render: {sl.ansi_indicator} {sl.ansi_raw[:120]}\x1b[0m")
344
404
  else:
345
- lines.append(f"status_line: {sl.ansi_indicator} {sl.ansi_raw[:120]}\x1b[0m")
405
+ lines.append(f"status_line: {sl.ansi_raw[:120]}\x1b[0m")
346
406
  else:
347
407
  lines.append("status_line: None")
348
408
  # BottomBar
@@ -529,7 +589,7 @@ class ClientConnection:
529
589
  self.writer.write(data)
530
590
  await self.writer.drain()
531
591
  except Exception as e:
532
- print(f"[Server] 发送消息失败 ({self.client_id}): {e}")
592
+ logger.warning(f"发送消息失败 ({self.client_id}): {e}")
533
593
 
534
594
  async def read_message(self) -> Optional[Message]:
535
595
  """读取一条消息"""
@@ -540,7 +600,7 @@ class ClientConnection:
540
600
  try:
541
601
  return decode_message(line)
542
602
  except Exception as e:
543
- print(f"[Server] 解析消息失败: {e}")
603
+ logger.warning(f"解析消息失败: {e}")
544
604
  continue
545
605
 
546
606
  # 读取更多数据
@@ -608,6 +668,8 @@ class ProxyServer:
608
668
 
609
669
  async def start(self):
610
670
  """启动服务器"""
671
+ t0 = time.time()
672
+ logger.info(f"正在启动 (session={self.session_name})")
611
673
  ensure_socket_dir()
612
674
 
613
675
  # 清理旧的 socket 文件
@@ -615,12 +677,15 @@ class ProxyServer:
615
677
  self.socket_path.unlink()
616
678
 
617
679
  # 启动 PTY
680
+ t1 = time.time()
618
681
  self._start_pty()
682
+ logger.info(f"PTY 已启动 ({(time.time()-t1)*1000:.0f}ms)")
619
683
 
620
684
  # 写入 PID 文件
621
685
  self.pid_file.write_text(str(os.getpid()))
622
686
 
623
687
  # 启动 Unix Socket 服务器
688
+ t2 = time.time()
624
689
  self.server = await asyncio.start_unix_server(
625
690
  self._handle_client,
626
691
  path=str(self.socket_path)
@@ -628,7 +693,7 @@ class ProxyServer:
628
693
 
629
694
  self.running = True
630
695
  _track_stats('session', 'start', session_name=self.session_name)
631
- print(f"[Server] 已启动: {self.socket_path}")
696
+ logger.info(f"已启动: {self.socket_path} (Socket {(time.time()-t2)*1000:.0f}ms, 总计 {(time.time()-t0)*1000:.0f}ms)")
632
697
 
633
698
  # 启动 PTY 读取任务
634
699
  asyncio.create_task(self._read_pty())
@@ -664,8 +729,14 @@ class ProxyServer:
664
729
  try:
665
730
  with open(env_snapshot_path) as _f:
666
731
  _extra_env = _json.load(_f)
732
+ logger.info(f"环境快照已加载 ({len(_extra_env)} 个变量)")
667
733
  except Exception:
668
- pass
734
+ logger.warning("环境快照加载失败,使用当前进程环境")
735
+
736
+ # 提前计算命令(fork 后父子进程共享,方便父进程打印和子进程执行)
737
+ import shlex as _shlex
738
+ _cmd_parts = _shlex.split(self._get_effective_cmd())
739
+ _full_cmd = ' '.join(_cmd_parts + self.claude_args)
669
740
 
670
741
  try:
671
742
  pid, fd = pty.fork()
@@ -688,10 +759,25 @@ class ProxyServer:
688
759
  # 清除 tmux 标识变量(PTY 数据不经过 tmux,不应让 Claude CLI 误判终端环境)
689
760
  child_env.pop('TMUX', None)
690
761
  child_env.pop('TMUX_PANE', None)
691
- import shlex as _shlex
692
- _cmd_parts = _shlex.split(self._get_effective_cmd())
693
- os.execvpe(_cmd_parts[0], _cmd_parts + self.claude_args, child_env)
694
- os._exit(1) # execvpe 失败时兜底退出
762
+ try:
763
+ os.execvpe(_cmd_parts[0], _cmd_parts + self.claude_args, child_env)
764
+ except Exception as _e:
765
+ msg = f"启动失败: 命令 '{_cmd_parts[0]}' 无法执行: {_e}"
766
+ os.write(1, (msg + "\n").encode()) # 写到 PTY
767
+ # fork 后不能安全使用 logging,直接追加写日志文件
768
+ try:
769
+ import time as _t
770
+ _ts = _t.strftime("%Y-%m-%d %H:%M:%S")
771
+ _ms = int((_t.time() % 1) * 1000)
772
+ _log_line = f"{_ts}.{_ms:03d} [Server] ERROR {msg}\n"
773
+ _home = os.path.expanduser("~")
774
+ _log_file = os.path.join(_home, ".remote-claude", "startup.log")
775
+ with open(_log_file, "a", encoding="utf-8") as _f:
776
+ _f.write(_log_line)
777
+ except Exception:
778
+ pass
779
+ os._exit(127) # 127 = command not found (shell convention)
780
+ os._exit(1) # 理论上不可达
695
781
  else:
696
782
  # 父进程
697
783
  self.master_fd = fd
@@ -706,7 +792,8 @@ class ProxyServer:
706
792
  fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
707
793
 
708
794
  cli_label = self.cli_type.capitalize()
709
- print(f"[Server] {cli_label} 已启动 (PID: {pid}, PTY: {self.PTY_COLS}×{self.PTY_ROWS})")
795
+ logger.info(f"启动命令: {_full_cmd}")
796
+ logger.info(f"{cli_label} 已启动 (PID: {pid}, PTY: {self.PTY_COLS}×{self.PTY_ROWS})")
710
797
 
711
798
  _COALESCE_MAX = 64 * 1024 # 64KB,防止单次广播过大
712
799
 
@@ -744,11 +831,19 @@ class ProxyServer:
744
831
  break
745
832
  except Exception as e:
746
833
  if self.running:
747
- print(f"[Server] 读取 PTY 错误: {e}")
834
+ logger.error(f"读取 PTY 错误: {e}")
748
835
  break
749
836
 
750
- # Claude 退出
751
- print("[Server] Claude 已退出")
837
+ # Claude 退出,获取 exit code 以便诊断
838
+ try:
839
+ _, status = os.waitpid(self.child_pid, os.WNOHANG)
840
+ if status != 0:
841
+ exit_code = os.waitstatus_to_exitcode(status)
842
+ logger.error(f"CLI 进程异常退出 (exit_code={exit_code})")
843
+ else:
844
+ logger.info("Claude 已退出")
845
+ except Exception:
846
+ logger.info("Claude 已退出")
752
847
  await self._shutdown()
753
848
 
754
849
  def _read_pty_sync(self) -> Optional[bytes]:
@@ -766,7 +861,7 @@ class ProxyServer:
766
861
  client = ClientConnection(client_id, reader, writer)
767
862
  self.clients[client_id] = client
768
863
 
769
- print(f"[Server] 客户端连接: {client_id}")
864
+ logger.info(f"客户端连接: {client_id}")
770
865
  _track_stats('session', 'attach', session_name=self.session_name)
771
866
 
772
867
  # 发送历史输出
@@ -782,12 +877,12 @@ class ProxyServer:
782
877
  break
783
878
  await self._handle_message(client_id, msg)
784
879
  except Exception as e:
785
- print(f"[Server] 客户端处理错误 ({client_id}): {e}")
880
+ logger.error(f"客户端处理错误 ({client_id}): {e}")
786
881
  finally:
787
882
  # 清理
788
883
  del self.clients[client_id]
789
884
  client.close()
790
- print(f"[Server] 客户端断开: {client_id}")
885
+ logger.info(f"客户端断开: {client_id}")
791
886
 
792
887
  async def _handle_message(self, client_id: str, msg: Message):
793
888
  """处理客户端消息"""
@@ -804,7 +899,7 @@ class ProxyServer:
804
899
  _track_stats('terminal', 'input', session_name=self.session_name,
805
900
  value=len(data))
806
901
  except Exception as e:
807
- print(f"[Server] 写入 PTY 错误: {e}")
902
+ logger.error(f"写入 PTY 错误: {e}")
808
903
 
809
904
  # 广播输入给其他客户端(飞书侧可以感知终端用户的输入内容)
810
905
  for cid, client in list(self.clients.items()):
@@ -824,7 +919,7 @@ class ProxyServer:
824
919
  winsize = struct.pack('HHHH', msg.rows, msg.cols, 0, 0)
825
920
  fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, winsize)
826
921
  except Exception as e:
827
- print(f"[Server] 调整终端大小错误: {e}")
922
+ logger.error(f"调整终端大小错误: {e}")
828
923
 
829
924
  async def _broadcast_output(self, data: bytes):
830
925
  """广播输出给所有客户端,同时喂给 OutputWatcher 生成快照"""
@@ -863,7 +958,7 @@ class ProxyServer:
863
958
  # 清理文件
864
959
  cleanup_session(self.session_name)
865
960
 
866
- print("[Server] 已关闭")
961
+ logger.info("已关闭")
867
962
 
868
963
 
869
964
  def run_server(session_name: str, claude_args: list = None,
@@ -903,7 +998,22 @@ if __name__ == "__main__":
903
998
  help="debug 日志输出完整诊断信息(indicator、repr 等)")
904
999
  args = parser.parse_args()
905
1000
 
1001
+ # 配置日志:写文件(供故障诊断)+ stdout(供 tmux attach 时查看)
1002
+ from utils.session import USER_DATA_DIR
1003
+ USER_DATA_DIR.mkdir(parents=True, exist_ok=True)
1004
+ _log_path = USER_DATA_DIR / "startup.log"
1005
+ logging.basicConfig(
1006
+ level=logging.DEBUG,
1007
+ format="%(asctime)s.%(msecs)03d [%(name)s] %(levelname)s %(message)s",
1008
+ datefmt="%Y-%m-%d %H:%M:%S",
1009
+ handlers=[
1010
+ logging.FileHandler(_log_path, encoding="utf-8"),
1011
+ logging.StreamHandler(sys.stdout),
1012
+ ],
1013
+ )
1014
+
906
1015
  claude_cmd = os.environ.get("CLAUDE_COMMAND", "claude")
1016
+ logger.info(f"CLAUDE_COMMAND={claude_cmd!r}")
907
1017
  run_server(args.session_name, args.claude_args, claude_cmd=claude_cmd,
908
1018
  cli_type=args.cli_type,
909
1019
  debug_screen=args.debug_screen, debug_verbose=args.debug_verbose)
package/utils/session.py CHANGED
@@ -101,6 +101,8 @@ def tmux_create_session(session_name: str, command: str, detached: bool = True)
101
101
  args.extend(["-x", "200", "-y", "50"]) # 默认大小
102
102
  args.append(command)
103
103
 
104
+ import logging as _logging
105
+ _logging.getLogger('Start').info(f"tmux_cmd: {' '.join(args)}")
104
106
  result = subprocess.run(args, capture_output=True)
105
107
  if result.returncode == 0:
106
108
  # 启用鼠标支持,允许在 tmux 窗口内用鼠标滚轮查看历史输出