remote-claude 1.0.3 → 1.0.5
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 +12 -136
- package/bin/cdx +9 -1
- package/bin/cl +9 -1
- package/bin/cla +9 -1
- package/bin/cx +9 -1
- package/bin/remote-claude +9 -23
- package/client/client.py +1 -1
- package/init.sh +30 -97
- package/lark_client/card_builder.py +53 -33
- package/lark_client/lark_handler.py +159 -42
- package/lark_client/main.py +6 -4
- package/lark_client/session_bridge.py +2 -2
- package/lark_client/setup_wizard.py +999 -0
- package/lark_client/shared_memory_poller.py +137 -112
- package/package.json +1 -1
- package/remote_claude.py +251 -0
- package/server/server.py +31 -19
- package/utils/session.py +81 -30
- package/scripts/check-env.sh +0 -43
package/server/server.py
CHANGED
|
@@ -9,6 +9,7 @@ Proxy Server
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import asyncio
|
|
12
|
+
import atexit
|
|
12
13
|
import logging
|
|
13
14
|
import os
|
|
14
15
|
import pty
|
|
@@ -35,8 +36,8 @@ from utils.protocol import (
|
|
|
35
36
|
encode_message, decode_message
|
|
36
37
|
)
|
|
37
38
|
from utils.session import (
|
|
38
|
-
get_socket_path, get_pid_file, ensure_socket_dir,
|
|
39
|
-
generate_client_id, cleanup_session, _safe_filename, get_env_file,
|
|
39
|
+
get_socket_path, get_pid_file, get_name_file, ensure_socket_dir,
|
|
40
|
+
generate_client_id, cleanup_session, _safe_filename, _log_filename, get_env_file,
|
|
40
41
|
SOCKET_DIR
|
|
41
42
|
)
|
|
42
43
|
|
|
@@ -184,12 +185,12 @@ class OutputWatcher:
|
|
|
184
185
|
self._on_snapshot = on_snapshot # 回调:写共享内存
|
|
185
186
|
self._debug_screen = debug_screen # --debug-screen 开启后才写 _screen.log
|
|
186
187
|
self._debug_verbose = debug_verbose # --debug-verbose 开启后输出 indicator/repr 等诊断信息
|
|
187
|
-
|
|
188
|
-
self._debug_file = f"/tmp/remote-claude/{
|
|
188
|
+
log_name = _log_filename(session_name)
|
|
189
|
+
self._debug_file = f"/tmp/remote-claude/{log_name}_messages.log"
|
|
189
190
|
# PTY 原始字节流日志(仅 --debug-screen 开启时使用)
|
|
190
191
|
self._raw_log_fd = None
|
|
191
192
|
if debug_screen:
|
|
192
|
-
raw_log_path = f"/tmp/remote-claude/{
|
|
193
|
+
raw_log_path = f"/tmp/remote-claude/{log_name}_pty_raw.log"
|
|
193
194
|
try:
|
|
194
195
|
self._raw_log_fd = open(raw_log_path, "a", encoding="ascii", buffering=1)
|
|
195
196
|
except Exception:
|
|
@@ -663,7 +664,7 @@ class OutputWatcher:
|
|
|
663
664
|
每个字符的 fg/bg 颜色通过 ANSI SGR 序列直接嵌入,
|
|
664
665
|
cat _screen.log 即可在终端看到与 pyte 渲染一致的着色效果。
|
|
665
666
|
"""
|
|
666
|
-
base = f"/tmp/remote-claude/{
|
|
667
|
+
base = f"/tmp/remote-claude/{_log_filename(self._session_name)}"
|
|
667
668
|
try:
|
|
668
669
|
# pyte 屏幕快照(覆盖写,只保留最新一帧)
|
|
669
670
|
screen_path = base + "_screen.log"
|
|
@@ -803,12 +804,13 @@ class ProxyServer:
|
|
|
803
804
|
"""Proxy Server"""
|
|
804
805
|
|
|
805
806
|
def __init__(self, session_name: str, claude_args: list = None,
|
|
806
|
-
claude_cmd: str = "claude",
|
|
807
|
+
claude_cmd: str = "claude", codex_cmd: str = "codex",
|
|
807
808
|
cli_type: str = "claude",
|
|
808
809
|
debug_screen: bool = False, debug_verbose: bool = False):
|
|
809
810
|
self.session_name = session_name
|
|
810
811
|
self.claude_args = claude_args or []
|
|
811
812
|
self.claude_cmd = claude_cmd
|
|
813
|
+
self.codex_cmd = codex_cmd
|
|
812
814
|
self.cli_type = cli_type
|
|
813
815
|
self.debug_screen = debug_screen
|
|
814
816
|
self.debug_verbose = debug_verbose
|
|
@@ -863,6 +865,9 @@ class ProxyServer:
|
|
|
863
865
|
# 写入 PID 文件
|
|
864
866
|
self.pid_file.write_text(str(os.getpid()))
|
|
865
867
|
|
|
868
|
+
# 写入会话名映射文件(供 list_active_sessions 恢复原始显示名)
|
|
869
|
+
get_name_file(self.session_name).write_text(self.session_name)
|
|
870
|
+
|
|
866
871
|
# 启动 Unix Socket 服务器
|
|
867
872
|
t2 = time.time()
|
|
868
873
|
self.server = await asyncio.start_unix_server(
|
|
@@ -915,9 +920,9 @@ class ProxyServer:
|
|
|
915
920
|
logger.info(f"已重定向 stderr 到 {error_log_path}")
|
|
916
921
|
|
|
917
922
|
# 添加运行阶段日志文件
|
|
918
|
-
|
|
923
|
+
log_name = _log_filename(self.session_name)
|
|
919
924
|
runtime_handler = logging.FileHandler(
|
|
920
|
-
f"{SOCKET_DIR}/{
|
|
925
|
+
f"{SOCKET_DIR}/{log_name}_server.log",
|
|
921
926
|
encoding="utf-8"
|
|
922
927
|
)
|
|
923
928
|
runtime_handler.setFormatter(logging.Formatter(
|
|
@@ -930,7 +935,7 @@ class ProxyServer:
|
|
|
930
935
|
# DEBUG 级别时额外记录调试日志到独立文件
|
|
931
936
|
if SERVER_LOG_LEVEL_MAP == logging.DEBUG:
|
|
932
937
|
debug_handler = logging.FileHandler(
|
|
933
|
-
f"{SOCKET_DIR}/{
|
|
938
|
+
f"{SOCKET_DIR}/{log_name}_debug.log",
|
|
934
939
|
encoding="utf-8"
|
|
935
940
|
)
|
|
936
941
|
debug_handler.setLevel(logging.DEBUG)
|
|
@@ -940,14 +945,14 @@ class ProxyServer:
|
|
|
940
945
|
))
|
|
941
946
|
debug_handler._debug_handler = True # 标记,方便后续清理
|
|
942
947
|
root_logger.addHandler(debug_handler)
|
|
943
|
-
logger.info(f"已启用 DEBUG 日志: {
|
|
948
|
+
logger.info(f"已启用 DEBUG 日志: {log_name}_debug.log")
|
|
944
949
|
|
|
945
|
-
logger.info(f"日志已切换到运行阶段: {
|
|
950
|
+
logger.info(f"日志已切换到运行阶段: {log_name}_server.log")
|
|
946
951
|
|
|
947
952
|
def _get_effective_cmd(self) -> str:
|
|
948
|
-
"""根据 cli_type
|
|
953
|
+
"""根据 cli_type 返回实际执行的命令"""
|
|
949
954
|
if self.cli_type == "codex":
|
|
950
|
-
return
|
|
955
|
+
return self.codex_cmd
|
|
951
956
|
return self.claude_cmd
|
|
952
957
|
|
|
953
958
|
def _start_pty(self):
|
|
@@ -1193,21 +1198,27 @@ class ProxyServer:
|
|
|
1193
1198
|
|
|
1194
1199
|
|
|
1195
1200
|
def run_server(session_name: str, claude_args: list = None,
|
|
1196
|
-
claude_cmd: str = "claude",
|
|
1201
|
+
claude_cmd: str = "claude", codex_cmd: str = "codex",
|
|
1197
1202
|
cli_type: str = "claude",
|
|
1198
1203
|
debug_screen: bool = False, debug_verbose: bool = False):
|
|
1199
1204
|
"""运行服务器"""
|
|
1200
1205
|
server = ProxyServer(session_name, claude_args, claude_cmd=claude_cmd,
|
|
1201
|
-
cli_type=cli_type,
|
|
1206
|
+
codex_cmd=codex_cmd, cli_type=cli_type,
|
|
1202
1207
|
debug_screen=debug_screen, debug_verbose=debug_verbose)
|
|
1203
1208
|
|
|
1209
|
+
# atexit 兜底:任何退出路径(包括被 SIGKILL 以外的信号强杀)都记录日志
|
|
1210
|
+
atexit.register(lambda: logger.warning("server 进程退出(atexit)[session=%s]", session_name))
|
|
1211
|
+
|
|
1204
1212
|
# 信号处理
|
|
1205
1213
|
def signal_handler(signum, frame):
|
|
1206
|
-
|
|
1214
|
+
sig_name = signal.Signals(signum).name if hasattr(signal, 'Signals') else str(signum)
|
|
1215
|
+
logger.warning("收到退出信号: %s (signum=%d)", sig_name, signum)
|
|
1216
|
+
print(f"\n[Server] 收到退出信号: {sig_name}")
|
|
1207
1217
|
asyncio.create_task(server._shutdown())
|
|
1208
1218
|
|
|
1209
1219
|
signal.signal(signal.SIGINT, signal_handler)
|
|
1210
1220
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
1221
|
+
signal.signal(signal.SIGHUP, signal_handler) # tmux 崩溃时也能优雅退出
|
|
1211
1222
|
|
|
1212
1223
|
# 运行
|
|
1213
1224
|
try:
|
|
@@ -1251,7 +1262,8 @@ if __name__ == "__main__":
|
|
|
1251
1262
|
logging.getLogger().addHandler(startup_handler)
|
|
1252
1263
|
|
|
1253
1264
|
claude_cmd = os.environ.get("CLAUDE_COMMAND", "claude")
|
|
1254
|
-
|
|
1265
|
+
codex_cmd = os.environ.get("CODEX_COMMAND", "codex")
|
|
1266
|
+
logger.info(f"CLAUDE_COMMAND={claude_cmd!r}, CODEX_COMMAND={codex_cmd!r}")
|
|
1255
1267
|
run_server(args.session_name, args.claude_args, claude_cmd=claude_cmd,
|
|
1256
|
-
cli_type=args.cli_type,
|
|
1268
|
+
codex_cmd=codex_cmd, cli_type=args.cli_type,
|
|
1257
1269
|
debug_screen=args.debug_screen, debug_verbose=args.debug_verbose)
|
package/utils/session.py
CHANGED
|
@@ -20,11 +20,6 @@ SOCKET_DIR = Path("/tmp/remote-claude")
|
|
|
20
20
|
USER_DATA_DIR = Path.home() / ".remote-claude"
|
|
21
21
|
TMUX_SESSION_PREFIX = "rc-"
|
|
22
22
|
|
|
23
|
-
# macOS AF_UNIX sun_path 限制 104 字节
|
|
24
|
-
# /tmp/remote-claude/ = 19 字节, .sock = 5 字节
|
|
25
|
-
_MAX_SOCKET_PATH = 104
|
|
26
|
-
_MAX_FILENAME = _MAX_SOCKET_PATH - len(str(SOCKET_DIR)) - 1 - len(".sock")
|
|
27
|
-
|
|
28
23
|
|
|
29
24
|
def get_env_file() -> Path:
|
|
30
25
|
"""获取 .env 配置文件路径"""
|
|
@@ -47,14 +42,15 @@ def ensure_user_data_dir():
|
|
|
47
42
|
|
|
48
43
|
|
|
49
44
|
def _safe_filename(session_name: str) -> str:
|
|
50
|
-
"""
|
|
51
|
-
name = session_name.replace('/', '_').replace('.', '_')
|
|
52
|
-
if len(name) <= _MAX_FILENAME:
|
|
53
|
-
return name
|
|
54
|
-
# 超长:直接用完整 32 字符 MD5,彻底避免路径超限
|
|
45
|
+
"""将会话名转为安全文件名(MD5 hash)"""
|
|
55
46
|
return hashlib.md5(session_name.encode()).hexdigest()
|
|
56
47
|
|
|
57
48
|
|
|
49
|
+
def _log_filename(session_name: str) -> str:
|
|
50
|
+
"""将会话名转为可读的安全文件名(用于日志文件,不受路径长度限制)"""
|
|
51
|
+
return session_name.replace("/", "_").replace(".", "_").replace(" ", "_")
|
|
52
|
+
|
|
53
|
+
|
|
58
54
|
def get_socket_path(session_name: str) -> Path:
|
|
59
55
|
"""获取会话的 socket 路径"""
|
|
60
56
|
return SOCKET_DIR / f"{_safe_filename(session_name)}.sock"
|
|
@@ -75,6 +71,11 @@ def get_env_snapshot_path(session_name: str) -> Path:
|
|
|
75
71
|
return SOCKET_DIR / f"{_safe_filename(session_name)}_env.json"
|
|
76
72
|
|
|
77
73
|
|
|
74
|
+
def get_name_file(session_name: str) -> Path:
|
|
75
|
+
"""获取会话名映射文件路径(存储原始会话名,供 list 恢复显示名)"""
|
|
76
|
+
return SOCKET_DIR / f"{_safe_filename(session_name)}.name"
|
|
77
|
+
|
|
78
|
+
|
|
78
79
|
def ensure_socket_dir():
|
|
79
80
|
"""确保 socket 目录存在"""
|
|
80
81
|
SOCKET_DIR.mkdir(parents=True, exist_ok=True)
|
|
@@ -221,13 +222,19 @@ def get_process_cwd(pid: int) -> Optional[str]:
|
|
|
221
222
|
|
|
222
223
|
|
|
223
224
|
def list_active_sessions() -> List[dict]:
|
|
224
|
-
"""列出所有活跃会话
|
|
225
|
+
"""列出所有活跃会话
|
|
226
|
+
|
|
227
|
+
注意:sock 文件的 stem 已经是 MD5 哈希(_safe_filename 的输出),
|
|
228
|
+
因此直接用 stem 构造 PID/MQ 文件路径,不再经过 get_pid_file 等函数
|
|
229
|
+
(否则会被 _safe_filename 二次哈希)。
|
|
230
|
+
"""
|
|
225
231
|
ensure_socket_dir()
|
|
226
232
|
sessions = []
|
|
227
233
|
|
|
228
234
|
for sock_file in SOCKET_DIR.glob("*.sock"):
|
|
229
|
-
|
|
230
|
-
|
|
235
|
+
# stem 已经是 _safe_filename() 的输出(MD5 哈希)
|
|
236
|
+
safe_name = sock_file.stem
|
|
237
|
+
pid_file = SOCKET_DIR / f"{safe_name}.pid"
|
|
231
238
|
|
|
232
239
|
# 检查 socket 文件是否有效(进程是否存在)
|
|
233
240
|
if pid_file.exists():
|
|
@@ -237,6 +244,17 @@ def list_active_sessions() -> List[dict]:
|
|
|
237
244
|
os.kill(pid, 0)
|
|
238
245
|
# 获取进程 CWD
|
|
239
246
|
cwd = get_process_cwd(pid)
|
|
247
|
+
|
|
248
|
+
# 恢复原始会话名(从 .name 文件读取,不存在则回退到哈希值)
|
|
249
|
+
name_file = SOCKET_DIR / f"{safe_name}.name"
|
|
250
|
+
if name_file.exists():
|
|
251
|
+
try:
|
|
252
|
+
display_name = name_file.read_text().strip()
|
|
253
|
+
except OSError:
|
|
254
|
+
display_name = safe_name
|
|
255
|
+
else:
|
|
256
|
+
display_name = safe_name
|
|
257
|
+
|
|
240
258
|
# 获取启动时间(PID 文件的修改时间,文件可能已被并发清理)
|
|
241
259
|
import datetime
|
|
242
260
|
try:
|
|
@@ -246,7 +264,7 @@ def list_active_sessions() -> List[dict]:
|
|
|
246
264
|
mtime = 0
|
|
247
265
|
start_time = "?"
|
|
248
266
|
|
|
249
|
-
# 读取 .mq 文件获取 cli_type
|
|
267
|
+
# 读取 .mq 文件获取 cli_type
|
|
250
268
|
try:
|
|
251
269
|
import sys
|
|
252
270
|
from pathlib import Path
|
|
@@ -255,29 +273,38 @@ def list_active_sessions() -> List[dict]:
|
|
|
255
273
|
if project_root not in sys.path:
|
|
256
274
|
sys.path.insert(0, project_root)
|
|
257
275
|
from server.shared_state import SharedStateReader
|
|
258
|
-
|
|
276
|
+
# 用 _BypassHashReader 直接传入 safe_name 避免二次哈希
|
|
277
|
+
mq_path = SOCKET_DIR / f"{safe_name}.mq"
|
|
278
|
+
reader = SharedStateReader.__new__(SharedStateReader)
|
|
279
|
+
reader._path = mq_path
|
|
259
280
|
snapshot = reader.read()
|
|
260
281
|
cli_type = snapshot.get("cli_type", "claude")
|
|
261
282
|
except Exception as e:
|
|
262
|
-
# 添加详细日志记录,便于诊断问题
|
|
263
283
|
import logging
|
|
264
284
|
logger = logging.getLogger('Session')
|
|
265
|
-
logger.warning(f"读取共享内存 cli_type 失败: session={
|
|
266
|
-
cli_type = "claude"
|
|
285
|
+
logger.warning(f"读取共享内存 cli_type 失败: session={display_name}, error={e}")
|
|
286
|
+
cli_type = "claude"
|
|
287
|
+
|
|
288
|
+
# tmux 会话名也用 safe_name 直接构造
|
|
289
|
+
tmux_name = f"{TMUX_SESSION_PREFIX}{safe_name}"
|
|
290
|
+
tmux_exists = subprocess.run(
|
|
291
|
+
["tmux", "has-session", "-t", tmux_name],
|
|
292
|
+
capture_output=True
|
|
293
|
+
).returncode == 0
|
|
267
294
|
|
|
268
295
|
sessions.append({
|
|
269
|
-
"name":
|
|
296
|
+
"name": display_name,
|
|
270
297
|
"socket": str(sock_file),
|
|
271
298
|
"pid": pid,
|
|
272
299
|
"cwd": cwd or "",
|
|
273
300
|
"start_time": start_time,
|
|
274
301
|
"mtime": mtime,
|
|
275
|
-
"tmux":
|
|
302
|
+
"tmux": tmux_exists,
|
|
276
303
|
"cli_type": cli_type
|
|
277
304
|
})
|
|
278
305
|
except (ProcessLookupError, ValueError, OSError):
|
|
279
306
|
# 进程不存在或文件被并发清理,清理残留文件
|
|
280
|
-
|
|
307
|
+
_cleanup_by_safe_name(safe_name)
|
|
281
308
|
else:
|
|
282
309
|
# 没有 PID 文件,清理 socket
|
|
283
310
|
sock_file.unlink(missing_ok=True)
|
|
@@ -287,14 +314,32 @@ def list_active_sessions() -> List[dict]:
|
|
|
287
314
|
|
|
288
315
|
|
|
289
316
|
def cleanup_session(session_name: str):
|
|
290
|
-
"""
|
|
291
|
-
|
|
292
|
-
|
|
317
|
+
"""清理会话残留文件(传入原始会话名,经过 _safe_filename 转换)"""
|
|
318
|
+
safe = _safe_filename(session_name)
|
|
319
|
+
_cleanup_by_safe_name(safe, session_name)
|
|
293
320
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
321
|
+
|
|
322
|
+
def _cleanup_by_safe_name(safe_name: str, session_name: Optional[str] = None):
|
|
323
|
+
"""清理会话残留文件(传入已经是 MD5 哈希的 safe_name,不再二次哈希)"""
|
|
324
|
+
# 尝试从 .name 文件读取原始会话名(用于清理可读日志文件)
|
|
325
|
+
if session_name is None:
|
|
326
|
+
name_file = SOCKET_DIR / f"{safe_name}.name"
|
|
327
|
+
if name_file.exists():
|
|
328
|
+
try:
|
|
329
|
+
session_name = name_file.read_text().strip()
|
|
330
|
+
except OSError:
|
|
331
|
+
pass
|
|
332
|
+
for suffix in [".sock", ".pid", ".mq", ".name", "_env.json"]:
|
|
333
|
+
(SOCKET_DIR / f"{safe_name}{suffix}").unlink(missing_ok=True)
|
|
334
|
+
# 清理带后缀的日志文件(使用可读文件名)
|
|
335
|
+
log_suffixes = ["_messages.log", "_screen.log", "_server.log", "_debug.log", "_pty_raw.log"]
|
|
336
|
+
if session_name:
|
|
337
|
+
log_name = _log_filename(session_name)
|
|
338
|
+
for suffix in log_suffixes:
|
|
339
|
+
(SOCKET_DIR / f"{log_name}{suffix}").unlink(missing_ok=True)
|
|
340
|
+
# 兼容旧的 MD5 日志文件
|
|
341
|
+
for suffix in log_suffixes:
|
|
342
|
+
(SOCKET_DIR / f"{safe_name}{suffix}").unlink(missing_ok=True)
|
|
298
343
|
|
|
299
344
|
|
|
300
345
|
def is_session_active(session_name: str) -> bool:
|
|
@@ -346,8 +391,14 @@ def is_lark_running() -> bool:
|
|
|
346
391
|
try:
|
|
347
392
|
pid = int(pid_file.read_text().strip())
|
|
348
393
|
os.kill(pid, 0)
|
|
349
|
-
|
|
350
|
-
|
|
394
|
+
# 额外验证:确认进程确实是我们的 lark_client(防止 PID 复用误判)
|
|
395
|
+
import subprocess
|
|
396
|
+
result = subprocess.run(
|
|
397
|
+
["ps", "-p", str(pid), "-o", "command="],
|
|
398
|
+
capture_output=True, text=True, timeout=2
|
|
399
|
+
)
|
|
400
|
+
return "lark_client" in result.stdout
|
|
401
|
+
except (ProcessLookupError, ValueError, OSError, Exception):
|
|
351
402
|
return False
|
|
352
403
|
|
|
353
404
|
|
package/scripts/check-env.sh
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# 检查 .env 中 FEISHU_APP_ID/APP_SECRET 是否已配置,未配置则交互引导
|
|
3
|
-
# 用法: source scripts/check-env.sh "$INSTALL_DIR"
|
|
4
|
-
|
|
5
|
-
INSTALL_DIR="${1:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
6
|
-
ENV_FILE="$HOME/.remote-claude/.env"
|
|
7
|
-
mkdir -p "$HOME/.remote-claude"
|
|
8
|
-
ENV_OK=false
|
|
9
|
-
|
|
10
|
-
if [ -f "$ENV_FILE" ]; then
|
|
11
|
-
APP_ID=$(grep -E '^FEISHU_APP_ID=' "$ENV_FILE" | cut -d= -f2)
|
|
12
|
-
APP_SECRET=$(grep -E '^FEISHU_APP_SECRET=' "$ENV_FILE" | cut -d= -f2)
|
|
13
|
-
if [ -n "$APP_ID" ] && [ "$APP_ID" != "cli_xxxxx" ] && \
|
|
14
|
-
[ -n "$APP_SECRET" ] && [ "$APP_SECRET" != "xxxxx" ]; then
|
|
15
|
-
ENV_OK=true
|
|
16
|
-
fi
|
|
17
|
-
fi
|
|
18
|
-
|
|
19
|
-
if [ "$ENV_OK" = false ]; then
|
|
20
|
-
echo ""
|
|
21
|
-
echo "飞书客户端尚未配置,需要填写应用凭证。"
|
|
22
|
-
echo "(在飞书开发者后台创建应用获取: https://open.feishu.cn/app)"
|
|
23
|
-
echo ""
|
|
24
|
-
echo -e "\033[33m飞书机器人配置文档参考:https://github.com/yyzybb537/remote_claude\033[0m"
|
|
25
|
-
echo ""
|
|
26
|
-
read -p "FEISHU_APP_ID: " INPUT_APP_ID
|
|
27
|
-
read -p "FEISHU_APP_SECRET: " INPUT_APP_SECRET
|
|
28
|
-
|
|
29
|
-
if [ -z "$INPUT_APP_ID" ] || [ -z "$INPUT_APP_SECRET" ]; then
|
|
30
|
-
echo "错误: APP_ID 和 APP_SECRET 不能为空"
|
|
31
|
-
exit 1
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
cp "$INSTALL_DIR/.env.example" "$ENV_FILE"
|
|
35
|
-
sed -i.bak "s/^FEISHU_APP_ID=.*/FEISHU_APP_ID=$INPUT_APP_ID/" "$ENV_FILE"
|
|
36
|
-
sed -i.bak "s/^FEISHU_APP_SECRET=.*/FEISHU_APP_SECRET=$INPUT_APP_SECRET/" "$ENV_FILE"
|
|
37
|
-
rm -f "$ENV_FILE.bak"
|
|
38
|
-
|
|
39
|
-
echo ""
|
|
40
|
-
echo "配置已保存到 $ENV_FILE"
|
|
41
|
-
echo "(可选配置, 如"白名单"等可稍后编辑该文件)"
|
|
42
|
-
echo ""
|
|
43
|
-
fi
|