📢 转载信息
原文链接:https://sspai.com/post/105621
原文作者:Blanboom
我的主力电脑是一台 iMac,在固定的房间,这限制了我使用电脑的时间和场景。不过随着 Codex、Claude Code 等 CLI Coding Agent 的出现,我可以在 iPad 或手机上,通过 SSH 远程连接 Mac,在乘坐地铁等碎片时间远程开发,随时随地为「HEIF & HEVC 转换器」添加新功能。
在通过 SSH 远程开发的过程中,我对自己的开发环境进行了一系列配置和优化,例如:
- 通过 Bark 将 Claude Code/Codex 的通知推送到 iPhone
- 解决移动端网络变化/杀后台导致 SSH 断连
- 修改 SSH 配置文件让 1Password SSH Agent 与远程连接共存
我将自己进行的配置改动记录到这篇文章中,供自己后续维护和回顾,也方便各位读者参考。
将 Coding Agent 的通知发送到手机上
利用碎片时间远程开发,无法像使用电脑那样,盯着屏幕随时观察 CLI Coding Agent 的执行结果,所以我使用下面的 Python 脚本,通过 Bark 将执行结果发送到手机上。
此脚本同时兼容 Claude Code、Codex 和 OpenCode,并对推送内容做了加密处理,Bark 服务器和 Apple 都无法获取推送内容。
相关代码也已上传到 GitHub:
Bark 通知脚本,同时兼容 Codex、Claude Code 和 OpenCode
notify_claude_codex_bark.py
#!/usr/bin/env python3
import base64
import json
import sys
import urllib.parse
import urllib.request # Dependency: brew install cryptography
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
BARK_BASE = "https://api.day.app/xxx"
ENCRYPTION_KEY = "xxx"
ENCRYPTION_IV = "xxx"
OPENAI_ICON_URL = "https://images.ctfassets.net/j22is2dtoxu1/intercom-img-d177d076c9a5453052925143/49d5d812b0a6fcc20a14faa8c629d9fb/icon-ios-1024_401x.png"
# Claude symbol (CC0) from Wikimedia, publicly accessible without auth.
CLAUDE_ICON_URL = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b0/Claude_AI_symbol.svg/960px-Claude_AI_symbol.svg.png"
# OpenCode icon
OPENCODE_ICON_URL = "https://opencode.ai/apple-touch-icon.png"
def _load_key_iv():
key_bytes = ENCRYPTION_KEY.encode("utf-8")
if len(key_bytes) != 32:
return None, None
iv_bytes = ENCRYPTION_IV.encode("utf-8")
if len(iv_bytes) != 12:
return None, None
return key_bytes, iv_bytes
def _encrypt_aes_gcm(plaintext: bytes, key: bytes, iv: bytes):
aesgcm = AESGCM(key)
encrypted = aesgcm.encrypt(iv, plaintext, None)
return base64.b64encode(encrypted).decode("ascii")
def _load_payload() -> dict:
if len(sys.argv) > 1:
try:
return json.loads(sys.argv[1])
except json.JSONDecodeError:
return {}
try:
if not sys.stdin.isatty():
raw = sys.stdin.read().strip()
if raw:
return json.loads(raw)
except Exception:
return {}
return {}
def _detect_source(payload: dict) -> str:
"""Detect the source of the payload: 'claude', 'opencode', or 'codex'."""
if payload.get("hook_event_name"):
return "claude"
if payload.get("session_id") or payload.get("transcript_path"):
return "claude"
title = payload.get("title") or ""
if "Claude" in title:
return "claude"
if "OpenCode" in title or "opencode" in title.lower():
return "opencode"
event_type = payload.get("event") or payload.get("type") or ""
if event_type.startswith("session.") or event_type in ("session_completed", "file_edited"):
return "opencode"
return "codex"
def main() -> None:
payload = _load_payload()
source = _detect_source(payload)
event_type = (
payload.get("hook_event_name")
or payload.get("type")
or payload.get("event")
)
if source == "claude":
title = payload.get("title") or "Claude Code"
icon_url = CLAUDE_ICON_URL
elif source == "opencode":
title = payload.get("title") or "OpenCode"
icon_url = OPENCODE_ICON_URL
else:
title = payload.get("title") or "Codex"
icon_url = OPENAI_ICON_URL
subtitle = event_type
message = (
payload.get("last-assistant-message")
or payload.get("message")
or payload.get("summary")
)
if not message:
cwd = payload.get("cwd")
if cwd and event_type:
message = f"{event_type} in {cwd}"
elif cwd:
message = f"Event in {cwd}"
elif event_type:
message = f"Event: {event_type}"
else:
message = "Event"
push_payload = {
"title": title,
"markdown": message,
"icon": icon_url,
"action": "none",
}
if subtitle:
push_payload["subtitle"] = subtitle
key_bytes, iv_bytes = _load_key_iv()
if not key_bytes:
return
plaintext = json.dumps(push_payload, ensure_ascii=False, separators=(
评论区