本文档为标准落地配置方案,旨在供开发者或 AI Agent 直接读取并一键执行配置。配置完成后,将具备:

  1. 眼科级抗疲劳体验:微温浅抹茶豆沙绿护眼底色 + 墨迹黑字 + 焦糖琥珀色思考字,彻底告别反光与视疲劳;
  2. 1 步直达战场:敲 cx 自动免密登录、自动进工作区、自动挂入后台保活会话,配置与代码解耦,换服务器零成本;
  3. 跨机器剪贴板与文件瞬移:Mac 截图在服务器敲 pi 或 Yazi 敲 P 秒级落地;本地文件拖入终端直接拉取上传;
  4. 全能终端管理:Yazi 终端看图自动铺满并支持自由缩放、Glow 2.0 原生 Markdown 彩色宽幅排版、路径双击 c c 秒拷回 Mac;
  5. 跨 Worktree 智能文件直达 (wto):解决 Claude Code 在独立 worktree 中改动代码后路径不匹配痛点,fzf 上下键直接挑选,回车秒开 Yazi 定位高亮。

架构拓扑

+-------------------------------------------------------------+
| Mac 本地工作站 (macOS)                                      |
|                                                             |
| 1. Ghostty 终端 (MesloLGL Nerd Font + 中值护眼豆沙绿)       |
| 2. 本地桥接服务: ~/.config/cx/mac_bridge.py (监听 127.0.0.1:19222)|
| 3. SSH 客户端: ~/.ssh/config (RemoteForward 19222)          |
| 4. Shell 极简引入: ~/.zshrc -> ~/.config/cx/ (工具/配置/桥接一体)|
+------------------------------+------------------------------+
                               |  SSH (反向隧道 19222)
                               v
+-------------------------------------------------------------+
| 远程 Linux 服务器 (Ubuntu / Debian / CentOS)                |
|                                                             |
| 1. Tmux 3.5a: 开启 allow-passthrough (Kitty 图形穿透)       |
| 2. 交互工具: pi (剪贴板图), up (拖拽上传), cb (拷回 Mac),     |
|             wto (跨 Worktree 智能检索直通 Yazi)              |
| 3. Yazi (v26.9+): zoom 缩放、piper + glow 2.0、copy-mac     |
+-------------------------------------------------------------+

参数占位符约定:
- <SERVER_HOST>:服务器 SSH 别名(示例:dev-server)
- <SERVER_IP>:服务器真实 IP(示例:10.0.0.10)
- <REMOTE_USER>:远程服务器登录用户名(示例:developer)
- <PROJECT_NAME>:默认主项目目录名(示例:my_project)


第一部分:Mac 本地配置(AI 自动执行指南)

1.1 安装必要 CLI 工具

通过 Homebrew 安装剪贴板提取器及终端工具:

brew install pngpaste glow lazygit

1.2 配置 Ghostty 终端

写入或覆盖 ~/.config/ghostty/config:

# ==============================================================================
# Ghostty 配置 - 眼科防疲劳浅豆沙绿定制版
# ==============================================================================

# 1. 主题与色彩(经典草木豆沙绿中值色 #c1d9c3,明度 80%,防反光不刺眼)
background = #c1d9c3
foreground = #1c2c21
cursor-color = #286349
cursor-text = #c1d9c3
selection-background = #a2cca6
selection-foreground = #1c2c21

# ANSI 16 色调色板(墨水纸感:黄色调为深焦糖色 #7a3e00,确保 Claude 思考文字凝实清晰)
palette = 0=#1c2c21
palette = 1=#ba3838
palette = 2=#2a752e
palette = 3=#7a3e00
palette = 4=#175983
palette = 5=#762c8e
palette = 6=#10766d
palette = 7=#cedecf
palette = 8=#44584a
palette = 9=#cf2828
palette = 10=#328537
palette = 11=#7a3e00
palette = 12=#176ebd
palette = 13=#871fa2
palette = 14=#008273
palette = 15=#f0f7f0

# 智能对比度防护(确保状态栏小字和辅助灰色绝对清晰)
minimum-contrast = 1.15

# 2. 窗口外观(浅色模式设为 1.0 纯色不透,杜绝桌面杂色重影干扰)
background-opacity = 1.0
background-blur = false
window-padding-x = 14
window-padding-y = 12
window-padding-balance = true
macos-titlebar-style = transparent

# 3. 字体配置
font-family = MesloLGL Nerd Font
font-size = 14.5

# 4. 光标与按键
cursor-style = block
cursor-style-blink = false
macos-option-as-alt = true

# 5. 滚动与控速(驯服触控板暴冲)
scrollback-limit = 100000
mouse-scroll-multiplier = precision:1,discrete:1

# 6. 剪贴板交互
clipboard-read = allow
clipboard-write = allow
copy-on-select = clipboard

# 7. 终端环境
term = xterm-256color

# 8. 分屏快捷键
keybind = super+d=new_split:right
keybind = super+shift+d=new_split:down
keybind = super+[=goto_split:previous
keybind = super+]=goto_split:next
keybind = super+shift+enter=toggle_split_zoom
keybind = super+ctrl+==equalize_splits
keybind = super+enter=unbind

1.3 部署 Mac 本地跨端桥接服务

创建本地桥接脚本 ~/.config/cx/mac_bridge.py:

#!/usr/bin/env python3
import http.server
import subprocess
import os
import urllib.parse
import sys

PORT = 19222

def detect_clipboard():
    # 1. 尝试检测 Finder 复制的文件 (Cmd+C)
    script = """
    try
        set singleItem to (the clipboard as «class furl»)
        return POSIX path of singleItem
    on error
        try
            set theList to (the clipboard as list)
            set res to {}
            repeat with itm in theList
                try
                    set end of res to POSIX path of itm
                end try
            end repeat
            set AppleScript's text item delimiters to linefeed
            return res as text
        on error
            return ""
        end try
    end try
    """
    try:
        proc = subprocess.run(['osascript', '-e', script], capture_output=True, text=True, timeout=2)
        out = proc.stdout.strip()
        if out:
            lines = [l.strip() for l in out.splitlines() if l.strip()]
            valid = [p for p in lines if os.path.exists(p)]
            if valid:
                return {
                    "type": "files",
                    "files": [{"path": p, "name": os.path.basename(p), "size": os.path.getsize(p)} for p in valid]
                }
    except Exception:
        pass

    # 2. 尝试检测剪贴板原生图片 (截屏数据)
    try:
        png_check = subprocess.run(['/opt/homebrew/bin/pngpaste', '-'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=1)
        if png_check.returncode == 0 and len(png_check.stdout) > 0:
            return {"type": "image", "size": len(png_check.stdout)}
    except Exception:
        pass

    # 3. 尝试检测 pbpaste 文本内容是否指向有效本地文件
    try:
        proc = subprocess.run(['pbpaste'], capture_output=True, text=True, timeout=1)
        text = proc.stdout.strip()
        if text.startswith('/') and '\n' not in text and os.path.exists(text):
            return {
                "type": "files",
                "files": [{"path": text, "name": os.path.basename(text), "size": os.path.getsize(text)}]
            }
        elif text:
            return {"type": "text", "preview": text[:60]}
    except Exception:
        pass

    return {"type": "empty"}

class BridgeHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)
        path = parsed.path

        if path == '/ping':
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'pong')
            return

        elif path == '/clipboard-status':
            status = detect_clipboard()
            body = json.dumps(status, ensure_ascii=False).encode('utf-8')
            self.send_response(200)
            self.send_header('Content-Type', 'application/json; charset=utf-8')
            self.send_header('Content-Length', str(len(body)))
            self.end_headers()
            self.wfile.write(body)
            return

        elif path == '/clipboard-image':
            try:
                proc = subprocess.run(['/opt/homebrew/bin/pngpaste', '-'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                if proc.returncode == 0 and len(proc.stdout) > 0:
                    self.send_response(200)
                    self.send_header('Content-Type', 'image/png')
                    self.send_header('Content-Length', str(len(proc.stdout)))
                    self.end_headers()
                    self.wfile.write(proc.stdout)
                else:
                    self.send_response(404)
                    self.send_header('Content-Type', 'text/plain; charset=utf-8')
                    self.end_headers()
                    self.wfile.write(b'Clipboard does not contain an image')
            except Exception as e:
                self.send_response(500)
                self.send_header('Content-Type', 'text/plain; charset=utf-8')
                self.end_headers()
                self.wfile.write(str(e).encode())

        elif path == '/fetch-file':
            query = urllib.parse.parse_qs(parsed.query)
            filepath = query.get('path', [''])[0]
            filepath = os.path.expanduser(filepath)
            
            if not os.path.exists(filepath):
                self.send_response(404)
                self.send_header('Content-Type', 'text/plain; charset=utf-8')
                self.end_headers()
                self.wfile.write(f'File not found: {filepath}'.encode())
                return
            
            try:
                f = open(filepath, 'rb')
                size = os.path.getsize(filepath)
                self.send_response(200)
                self.send_header('Content-Type', 'application/octet-stream')
                self.send_header('Content-Length', str(size))
                self.end_headers()
                with f:
                    while chunk := f.read(65536):
                        self.wfile.write(chunk)
            except PermissionError as e:
                self.send_response(403)
                self.send_header('Content-Type', 'text/plain; charset=utf-8')
                self.end_headers()
                self.wfile.write(f'Permission denied on Mac: {e}'.encode())
            except Exception as e:
                self.send_response(500)
                self.send_header('Content-Type', 'text/plain; charset=utf-8')
                self.end_headers()
                self.wfile.write(str(e).encode())

        elif path == '/open-remote-vscode':
            query = urllib.parse.parse_qs(parsed.query)
            remote_path = query.get('path', [''])[0]
            host = query.get('host', ['<SERVER_HOST>'])[0]
            if remote_path:
                try:
                    url = f"vscode://vscode-remote/ssh-remote+{host}{remote_path}"
                    subprocess.Popen(['open', url])
                    self.send_response(200)
                    self.end_headers()
                    self.wfile.write(b'ok')
                    return
                except Exception as e:
                    self.send_response(500)
                    self.end_headers()
                    self.wfile.write(str(e).encode())
                    return
            else:
                self.send_response(400)
                self.end_headers()
                return

        elif path == '/open-browser':
            query = urllib.parse.parse_qs(parsed.query)
            target_url = query.get('url', [''])[0]
            if target_url and (target_url.startswith('http://') or target_url.startswith('https://')):
                try:
                    subprocess.Popen(['open', target_url])
                    self.send_response(200)
                    self.end_headers()
                    self.wfile.write(b'ok')
                    return
                except Exception as e:
                    self.send_response(500)
                    self.end_headers()
                    self.wfile.write(str(e).encode())
                    return
            else:
                self.send_response(400)
                self.end_headers()
                self.wfile.write(b'invalid url')
                return

        elif path == '/preview':
            preview_file = '/tmp/wto_preview.html'
            if os.path.exists(preview_file):
                try:
                    with open(preview_file, 'rb') as f:
                        content = f.read()
                    self.send_response(200)
                    self.send_header('Content-Type', 'text/html; charset=utf-8')
                    self.send_header('Content-Length', str(len(content)))
                    self.end_headers()
                    self.wfile.write(content)
                    return
                except Exception as e:
                    self.send_response(500)
                    self.send_header('Content-Type', 'text/plain; charset=utf-8')
                    self.end_headers()
                    self.wfile.write(str(e).encode())
                    return
            else:
                self.send_response(404)
                self.send_header('Content-Type', 'text/plain; charset=utf-8')
                self.end_headers()
                self.wfile.write(b'No preview available')
                return

        else:
            self.send_response(404)
            self.end_headers()

    def do_POST(self):
        parsed = urllib.parse.urlparse(self.path)
        if parsed.path == '/set-clipboard':
            length = int(self.headers.get('Content-Length', 0))
            data = self.rfile.read(length)
            try:
                proc = subprocess.Popen(['pbcopy'], stdin=subprocess.PIPE)
                proc.communicate(input=data)
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b'ok')
            except Exception as e:
                self.send_response(500)
                self.end_headers()
                self.wfile.write(str(e).encode())
        elif parsed.path == '/preview-upload':
            length = int(self.headers.get('Content-Length', 0))
            data = self.rfile.read(length)
            try:
                preview_file = '/tmp/wto_preview.html'
                with open(preview_file, 'wb') as f:
                    f.write(data)
                
                preview_url = f"http://127.0.0.1:{PORT}/preview"
                subprocess.Popen(['open', preview_url])
                
                self.send_response(200)
                self.send_header('Content-Type', 'application/json; charset=utf-8')
                self.end_headers()
                self.wfile.write(json.dumps({'url': preview_url}).encode('utf-8'))
            except Exception as e:
                self.send_response(500)
                self.send_header('Content-Type', 'text/plain; charset=utf-8')
                self.end_headers()
                self.wfile.write(str(e).encode())
        elif parsed.path == '/save-file':
            query = urllib.parse.parse_qs(parsed.query)
            raw_filename = query.get('filename', ['downloaded_file'])[0]
            clean_filename = os.path.basename(raw_filename) or 'downloaded_file'
            dest_dir = os.path.expanduser('~/Downloads')
            os.makedirs(dest_dir, exist_ok=True)
            
            # 重名处理:若已存在则追加 (1), (2) 防覆盖
            name, ext = os.path.splitext(clean_filename)
            target_path = os.path.join(dest_dir, clean_filename)
            counter = 1
            while os.path.exists(target_path):
                target_path = os.path.join(dest_dir, f"{name} ({counter}){ext}")
                counter += 1

            length = int(self.headers.get('Content-Length', 0))
            remaining = length
            try:
                with open(target_path, 'wb') as f:
                    while remaining > 0:
                        chunk_size = min(65536, remaining)
                        chunk = self.rfile.read(chunk_size)
                        if not chunk:
                            break
                        f.write(chunk)
                        remaining -= len(chunk)

                # 自动在 Mac Finder 中高亮选中该文件
                reveal = query.get('reveal', ['1'])[0]
                if reveal != '0':
                    subprocess.Popen(['open', '-R', target_path])

                self.send_response(200)
                self.send_header('Content-Type', 'application/json; charset=utf-8')
                self.end_headers()
                resp = {
                    'ok': True,
                    'filename': os.path.basename(target_path),
                    'path': target_path,
                    'size': os.path.getsize(target_path)
                }
                self.wfile.write(json.dumps(resp, ensure_ascii=False).encode('utf-8'))
            except Exception as e:
                self.send_response(500)
                self.send_header('Content-Type', 'text/plain; charset=utf-8')
                self.end_headers()
                self.wfile.write(str(e).encode('utf-8'))
        else:
            self.send_response(404)
            self.end_headers()

    def log_message(self, format, *args):
        pass

if __name__ == '__main__':
    server = http.server.ThreadingHTTPServer(('127.0.0.1', PORT), BridgeHandler)
    server.serve_forever()

赋权、创建软链并启动:

chmod +x ~/.config/cx/mac_bridge.py
ln -sf ~/.config/cx/mac_bridge.py ~/.local/bin/mac_bridge.py
pkill -f mac_bridge.py 2>/dev/null || true
nohup python3 ~/.config/cx/mac_bridge.py >/dev/null 2>&1 &

1.4 配置 SSH 客户端反向转发 (~/.ssh/config)

在 ~/.ssh/config 中针对目标服务器添加配置(请替换 <SERVER_HOST>、<SERVER_IP>、<REMOTE_USER>):

Host <SERVER_HOST>
    HostName <SERVER_IP>
    User <REMOTE_USER>
    RequestTTY yes
    RemoteForward 19222 127.0.0.1:19222

1.5 配置 Mac 本地 Shell 直达函数与解耦配置 (cx)

为了在更换服务器或换项目时彻底解耦、无需频繁修改 ~/.zshrc,我们将连接目标参数独立抽取到单独的配置文件中。

1.5.1 创建目标服务器配置文件 (~/.config/cx/config)

mkdir -p ~/.config/cx
cat << 'EOF' > ~/.config/cx/config
# ==============================================================================
# cx 远程开发环境配置 (~/.config/cx/config)
# 以后换服务器或换项目,直接修改这里的参数即可!
# ==============================================================================

# SSH 目标主机别名(对应 ~/.ssh/config 里的 Host)
CX_HOST="<SERVER_HOST>"

# 远程默认主项目名称
CX_PROJECT="<PROJECT_NAME>"

# 远程工作区主目录
CX_REMOTE_WORKSPACES="/data/azrael/workspaces"

# 远程环境入口脚本(若不存在自动降级)
CX_REMOTE_BASH="~/.local/bin/azbash"
EOF

1.5.2 创建独立工具脚本 (~/.config/cx/cx.zsh)

将快捷别名与 cx 完整函数写入独立文件,彻底避免频繁修改 ~/.zshrc:

cat << 'EOF' > ~/.config/cx/cx.zsh
# ==============================================================================
# cx 远程终端与开发工具包 (~/.config/cx/cx.zsh)
# 集中管理 cx 函数、快捷别名及本地跨机器桥接守护服务
# ==============================================================================

# 1. 自动守护本地跨机器桥接服务 (监听 127.0.0.1:19222)
_cx_bridge_script="$HOME/.config/cx/mac_bridge.py"
pgrep -f "mac_bridge.py" >/dev/null || ([ -f "$_cx_bridge_script" ] && nohup python3 "$_cx_bridge_script" >/dev/null 2>&1 & disown)

# 2. 本地常用快捷别名与函数
alias lg='lazygit'

md() {
  if [ -z "$1" ]; then
    glow
  else
    glow -p "$@"
  fi
}
alias gw='glow'

# 3. cx 远程开发极速直达函数 (读取同目录 config 与 mac_bridge.py)
unalias cx 2>/dev/null || true
function cx() {
  local cfg_dir="$HOME/.config/cx"
  local cfg_file="$cfg_dir/config"
  local bridge_script="$cfg_dir/mac_bridge.py"

  # 自动保证桥接运行
  pgrep -f "mac_bridge.py" >/dev/null || ([ -f "$bridge_script" ] && nohup python3 "$bridge_script" >/dev/null 2>&1 & disown)

  local default_host="<SERVER_HOST>"
  local default_project="<PROJECT_NAME>"
  local default_workspaces="$HOME/workspaces"
  local default_bash=""

  [ -f "$cfg_file" ] && source "$cfg_file"

  local host="${CX_HOST:-$default_host}"
  local project="${CX_PROJECT:-$default_project}"
  local remote_workspaces="${CX_REMOTE_WORKSPACES:-$default_workspaces}"
  local remote_bash="${CX_REMOTE_BASH:-$default_bash}"

  local arg="${1:-$project}"

  # 帮助命令支持: -h, --help, help, h, --hlep
  if [[ "$arg" == "-h" || "$arg" == "--help" || "$arg" == "help" || "$arg" == "h" || "$arg" == "--hlep" ]]; then
    cat << EOF
cx - 远程开发环境极速直达工具 (目标服务器: $host)

用法:
  cx                    连入/恢复默认主会话 ($project)
  cx new                自动新建序号递增的全新独立会话 (如 ${project}_1)
  cx <序号>             连入/新建对应序号会话 (例: cx 1 -> ${project}_1)
  cx <名字>             连入/新建指定名称会话 (例: cx test)
  cx ls                 查看当前远程所有后台保活会话 (别名: list)
  cx k                  弹出交互式菜单挑选关闭会话 (别名: kill)
  cx k <名字>           直接关闭指定后台会话
  cx ka                 一键关闭全部后台保活会话 (别名: kill-all)
  cx bridge             查看本地跨机器传输桥接服务运行状态
  cx bridge restart     一键重启本地传输桥接服务 (监听 127.0.0.1:19222)
  cx raw                普通 SSH 直连服务器 (不接管 tmux,别名: ssh)
  cx config             查看/配置目标服务器参数 ($cfg_file)
  cx -h, h, help        显示本帮助说明 (别名: --help)

常用场景:
  • 日常开发/上班:       直接敲 cx
  • 另起独立新任务:     敲 cx new 或 cx 1
  • 查后台正在跑什么:   敲 cx ls
  • 关掉用完的会话:     敲 cx k
  • 本地桥接异常/卡死: 敲 cx bridge restart
  • 更换目标服务器:     敲 cx config 修改配置
EOF
    return 0
  fi

  # 桥接服务生命周期管理
  if [[ "$arg" == "bridge" || "$arg" == "br" ]]; then
    local sub="${2:-status}"
    if [[ "$sub" == "restart" || "$sub" == "re" ]]; then
      echo "⚡ 正在重启本地传输桥接服务..."
      pkill -f "mac_bridge.py" 2>/dev/null || true
      sleep 0.5
      if [ -f "$bridge_script" ]; then
        nohup python3 "$bridge_script" >/dev/null 2>&1 & disown
        sleep 0.5
      fi
    elif [[ "$sub" == "stop" ]]; then
      pkill -f "mac_bridge.py" 2>/dev/null && echo "✔ 本地传输桥接服务已停止" || echo "⚠️ 桥接服务未在运行"
      return 0
    elif [[ "$sub" == "start" ]]; then
      if pgrep -f "mac_bridge.py" >/dev/null; then
        echo "✔ 本地传输桥接服务已在运行中"
      else
        [ -f "$bridge_script" ] && nohup python3 "$bridge_script" >/dev/null 2>&1 & disown
        sleep 0.5
        echo "✔ 本地传输桥接服务已启动"
      fi
      return 0
    fi

    # 打印状态
    local pids=$(pgrep -f "mac_bridge.py" | tr '\n' ' ')
    echo "📄 桥接服务脚本: $bridge_script"
    if [ -n "$pids" ]; then
      local probe=$(curl -s --max-time 1 http://127.0.0.1:19222/ping 2>/dev/null)
      if [ "$probe" = "pong" ]; then
        echo "✔ 运行状态: 正在运行 (PID: $pids, 端口 19222 响应: $probe)"
      else
        echo "⚠️ 运行状态: 进程存在 (PID: $pids) 但端口 19222 未响应,可执行 cx bridge restart 重试"
      fi
    else
      echo "✖ 运行状态: 未运行 (可执行 cx bridge restart 启动)"
    fi
    return 0
  fi

  if [[ "$arg" == "config" || "$arg" == "conf" ]]; then
    echo "📄 当前 cx 配置文件路径: $cfg_file"
    echo "----------------------------------------"
    if [ -f "$cfg_file" ]; then
      cat "$cfg_file"
    else
      echo "⚠️ 配置文件尚未创建,当前使用内置默认值: host=$host, project=$project"
    fi
    echo "----------------------------------------"
    echo "💡 提示:使用编辑器直接修改该文件即可随时切换目标服务器/项目!"
    return 0
  fi

  if [[ "$arg" == "ls" || "$arg" == "list" ]]; then
    ssh -q -o ClearAllForwardings=yes -o RemoteCommand=none "$host" "bash -c 'echo \"=== 当前后台保活会话 ===\"; tmux ls 2>/dev/null || echo \"暂无运行中的会话\"; echo \"\"; echo \"💡 连接提示: cx 默认主会话 | cx new 开新会话 | cx 1/2/3 进对应序号 | cx <名字> 进指定会话 | cx -h 查看完整帮助\"'"
  elif [[ "$arg" == "kill" || "$arg" == "k" ]]; then
    local target="$2"
    if [ -n "$target" ]; then
      ssh -q -o ClearAllForwardings=yes -o RemoteCommand=none "$host" "bash -c 'tmux kill-session -t $target 2>/dev/null && echo \"✔ 已关闭会话: $target\" || echo \"✖ 未找到会话: $target\"'"
    else
      ssh -q -o ClearAllForwardings=yes -o RemoteCommand=none -t "$host" "bash -i -c 'tkill'"
    fi
  elif [[ "$arg" == "kill-all" || "$arg" == "ka" ]]; then
    ssh -q -o ClearAllForwardings=yes -o RemoteCommand=none "$host" "bash -c 'tmux kill-server 2>/dev/null; echo \"✔ 已关闭全部后台保活会话\"'"
  elif [[ "$arg" == "raw" || "$arg" == "ssh" ]]; then
    ssh "$host"
  elif [[ "$arg" == "new" || "$arg" == "add" ]]; then
    ssh -o LogLevel=ERROR -o RemoteCommand=none -t "$host" "bash -c '
      i=1
      while tmux has-session -t \"${project}_\$i\" 2>/dev/null; do
        i=\$((i+1))
      done
      sess=\"${project}_\$i\"
      echo \"⚡ 创建全新独立会话: \$sess\"
      cd \"$remote_workspaces/$project\" 2>/dev/null || cd ~
      exec tmux new-session -s \"\$sess\"
    '"
  else
    local sess="$arg"
    if [[ "$arg" =~ ^[0-9]+$ ]]; then
      sess="${project}_${arg}"
    fi
    ssh -o LogLevel=ERROR -o RemoteCommand=none -t "$host" "bash -c '
      if tmux has-session -t \"$sess\" 2>/dev/null; then
        exec tmux attach -t \"$sess\"
      fi
      proj_dir=\"$remote_workspaces/$arg\"
      [ ! -d \"\$proj_dir\" ] && proj_dir=\"$remote_workspaces/$project\"
      [ ! -d \"\$proj_dir\" ] && proj_dir=\"\$HOME\"
      cd \"\$proj_dir\" && exec tmux new-session -s \"$sess\"
    '"
  fi
}
EOF

1.5.3 在 Mac ~/.zshrc 中单行载入

在 ~/.zshrc 末尾仅需追加一行保护加载语句,即可完成全套工具链挂载:

# 加载 cx 远程终端与开发工具包
[ -f "$HOME/.config/cx/cx.zsh" ] && source "$HOME/.config/cx/cx.zsh"

第二部分:远程服务器端配置(AI 自动执行指南)

在目标 Linux 服务器执行以下步骤(在当前用户环境下执行,无需 root 权限)。

2.1 部署 Tmux 3.5a 与 ImageMagick 桥接

下载并解压 tmux 3.5a 运行包:

mkdir -p ~/.local/bin ~/.local/share /tmp/tmux_install
cd /tmp/tmux_install
curl -sL https://github.com/nelsonenzo/tmux-appimage/releases/download/3.5a/tmux.appimage -o tmux.appimage
chmod +x tmux.appimage
./tmux.appimage --appimage-extract
rm -rf ~/.local/share/tmux-3.5a
mv squashfs-root ~/.local/share/tmux-3.5a
rm -rf /tmp/tmux_install

# 编写 wrapper 脚本
cat << 'BASH' > ~/.local/bin/tmux
#!/bin/bash
DIR="$HOME/.local/share/tmux-3.5a"
export TERMINFO="$DIR/usr/share/terminfo"
export LD_LIBRARY_PATH="$DIR/usr/lib:$LD_LIBRARY_PATH"
exec "$DIR/usr/bin/tmux" "$@"
BASH
chmod +x ~/.local/bin/tmux

# 建立 ImageMagick 7 兼容软链
which convert >/dev/null && ln -sf $(which convert) ~/.local/bin/magick

2.2 配置 Tmux (~/.tmux.conf)

写入 ~/.tmux.conf:

# 1. 开启鼠标支持与高历史缓冲区
set -g mouse on
set -g history-limit 50000

# 2. 终端类型与真彩色
set -g default-terminal "xterm-256color"
set -as terminal-overrides ",*:Tc"

# 3. 编号从 1 开始
set -g base-index 1
setw -g pane-base-index 1
set -g renumber-windows on

# 4. 状态栏样式
set -g status-style "bg=#181825,fg=#cdd6f4"
set -g status-left "#[fg=#89b4fa,bold] [#S] "
set -g status-right "#[fg=#a6adc8]%Y-%m-%d %H:%M "
setw -g window-status-current-style "fg=#a6e3a1,bold"

# 5. 保留默认前缀键 Ctrl+b(严禁占用 Ctrl+a,确保命令行 Ctrl+a 跳行首、Ctrl+e 跳行末完好无损)
# (无需额外配置,tmux 默认前缀即为 Ctrl+b)

# 6. 驯服触控板滚轮速度(平稳降至 2 行)
bind-key -T copy-mode WheelUpPane select-pane \; send-keys -X -N 2 scroll-up
bind-key -T copy-mode WheelDownPane select-pane \; send-keys -X -N 2 scroll-down
bind-key -T copy-mode-vi WheelUpPane select-pane \; send-keys -X -N 2 scroll-up
bind-key -T copy-mode-vi WheelDownPane select-pane \; send-keys -X -N 2 scroll-down

# 7. 开启图形协议透传 (Kitty Graphics / Sixel 原生看图必备)
set -g allow-passthrough on
set -ga update-environment TERM
set -ga update-environment TERM_PROGRAM

2.3 部署服务器端跨端工具 (pi, pf, up, down, cb, wto)

在 ~/.local/bin/ 创建工具:

1. ~/.local/bin/pi & ~/.local/bin/pf(智能粘贴:支持 Mac 截图图片 + Finder 复制的文件)

cat << 'BASH' > ~/.local/bin/pi
#!/bin/bash
# ==============================================================================
# pi (Paste It) / pf (Paste File)
# 智能粘贴:支持 Mac 截图图片、Mac Finder 复制的文件 (Cmd+C)、Mac 剪贴板文件路径
# ==============================================================================

STATUS_JSON=$(curl -s --max-time 3 http://127.0.0.1:19222/clipboard-status 2>/dev/null)

if [ -z "$STATUS_JSON" ]; then
    echo -e "\033[31m✘ [Mac ➔ Server]\033[0m 无法连接 Mac 本地桥接服务 (HTTP 19222)"
    exit 1
fi

TYPE=$(echo "$STATUS_JSON" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('type', 'empty'))" 2>/dev/null)

if [ "$TYPE" = "files" ]; then
    echo "$STATUS_JSON" | python3 -c "
import sys, json, urllib.parse, subprocess, os

data = json.load(sys.stdin)
files = data.get('files', [])
custom_name = sys.argv[1] if len(sys.argv) > 1 else ''

for i, f in enumerate(files):
    remote_path = f['path']
    orig_name = f['name']
    target_name = custom_name if (custom_name and len(files) == 1) else orig_name
    
    encoded = urllib.parse.quote(remote_path)
    url = f'http://127.0.0.1:19222/fetch-file?path={encoded}'
    
    res = subprocess.run(['curl', '-s', '-o', target_name, '-w', '%{http_code}', url], capture_output=True, text=True)
    http_code = res.stdout.strip()
    
    if http_code == '200' and os.path.exists(target_name):
        size = os.path.getsize(target_name)
        if size < 1024:
            size_str = f'{size} B'
        elif size < 1024 * 1024:
            size_str = f'{size / 1024:.1f} KB'
        else:
            size_str = f'{size / (1024 * 1024):.1f} MB'
        print(f'\033[32m✔ [Mac ➔ Server]\033[0m 成功从 Mac 剪贴板粘贴文件: \033[1m{target_name}\033[0m ({size_str})')
    else:
        print(f'\033[31m✘ [Mac ➔ Server]\033[0m 拉取文件失败: {orig_name} (HTTP {http_code})')
" "$1"

elif [ "$TYPE" = "image" ]; then
    TARGET="$1"
    [ -z "$TARGET" ] && TARGET="clip_$(date +%Y%m%d_%H%M%S).png"
    
    HTTP_CODE=$(curl -s -o "$TARGET" -w "%{http_code}" http://127.0.0.1:19222/clipboard-image 2>/dev/null || echo "000")
    if [ "$HTTP_CODE" = "200" ]; then
        SIZE=$(ls -lh "$TARGET" | awk '{print $5}')
        echo -e "\033[32m✔ [Mac ➔ Server]\033[0m 成功从剪贴板粘贴图片: \033[1m$TARGET\033[0m ($SIZE)"
    else
        rm -f "$TARGET"
        echo -e "\033[31m✘ [Mac ➔ Server]\033[0m 粘贴图片失败 (HTTP $HTTP_CODE)"
        exit 1
    fi

elif [ "$TYPE" = "text" ]; then
    PREVIEW=$(echo "$STATUS_JSON" | python3 -c "import sys, json; print(json.load(sys.stdin).get('preview', ''))")
    echo -e "\033[33mℹ [Mac ➔ Server]\033[0m Mac 剪贴板里是文本内容,非文件或图片:"
    echo -e "  \033[90m\"$PREVIEW\"\033[0m"
    exit 0

else
    echo -e "\033[33m⚠ [Mac ➔ Server]\033[0m Mac 剪贴板里当前没有文件或图片数据"
    exit 1
fi
BASH
chmod +x ~/.local/bin/pi
ln -sf ~/.local/bin/pi ~/.local/bin/pf

2. ~/.local/bin/up(接收拖拽文件秒传)

cat << 'BASH' > ~/.local/bin/up
#!/bin/bash
if [ -z "$1" ]; then
    echo -e "使用方法: \033[1mup [直接把 Mac 上的图片或文件拖到这里] 回车\033[0m"
    exit 1
fi

for FILEPATH in "$@"; do
    FILEPATH=$(echo "$FILEPATH" | sed -e "s/^['\"]//" -e "s/['\"]$//")
    FILENAME=$(basename "$FILEPATH")
    
    echo -e "正在从 Mac 拉取文件: \033[34m$FILENAME\033[0m ..."
    ENCODED_PATH=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "$FILEPATH")
    
    HTTP_CODE=$(curl -s -o "$FILENAME" -w "%{http_code}" "http://127.0.0.1:19222/fetch-file?path=$ENCODED_PATH" 2>/dev/null)
    
    if [ "$HTTP_CODE" = "200" ]; then
        SIZE=$(ls -lh "$FILENAME" | awk '{print $5}')
        echo -e "\033[32m✔ [Mac ➔ Server]\033[0m 上传成功: \033[1m$FILENAME\033[0m ($SIZE)"
    elif [ "$HTTP_CODE" = "404" ]; then
        rm -f "$FILENAME"
        echo -e "\033[31m✘ [Mac ➔ Server]\033[0m Mac 本地找不到该文件: $FILEPATH"
    elif [ "$HTTP_CODE" = "403" ]; then
        rm -f "$FILENAME"
        echo -e "\033[31m✘ [Mac ➔ Server]\033[0m Mac 系统权限拒绝读取该文件: $FILEPATH"
    else
        rm -f "$FILENAME"
        echo -e "\033[31m✘ [Mac ➔ Server]\033[0m 拉取失败 (HTTP $HTTP_CODE)"
    fi
done
BASH
chmod +x ~/.local/bin/up

3. ~/.local/bin/cb(把文本或管道写入 Mac 剪贴板)

cat << 'BASH' > ~/.local/bin/cb
#!/bin/bash
if [ -n "$1" ]; then
    echo -n "$1" | curl -s -X POST --data-binary @- http://127.0.0.1:19222/set-clipboard
elif [ ! -t 0 ]; then
    curl -s -X POST --data-binary @- http://127.0.0.1:19222/set-clipboard
else
    echo "使用方法: echo '文本' | cb 或 cb '文本'"
fi
BASH
chmod +x ~/.local/bin/cb
ln -sf ~/.local/bin/cb ~/.local/bin/wl-copy
ln -sf ~/.local/bin/cb ~/.local/bin/xclip

4. ~/.local/bin/wto(跨 Git Worktree 文件快速定位,支持直通 Yazi 或原地启动 HTTP 唤醒 Mac 浏览器)

cat << 'PY' > ~/.local/bin/wto
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
wto (WorkTree Open)
跨 Git Worktree 文件快速定位,fzf 上下键选择,直通 Yazi 或极速本地 HTTP 浏览器预览
"""

import os
import sys
import shutil
import socket
import urllib.parse
import json
import subprocess
import datetime
from pathlib import Path

BOLD = "\033[1m"
GREEN = "\033[38;2;40;167;69m"
CYAN = "\033[38;2;23;162;184m"
YELLOW = "\033[38;2;255;193;7m"
ORANGE = "\033[38;2;253;126;20m"
RED = "\033[38;2;220;53;69m"
GRAY = "\033[38;2;108;117;125m"
RESET = "\033[0m"

def get_server_ip():
    conn = os.environ.get("SSH_CONNECTION", "")
    if conn:
        parts = conn.split()
        if len(parts) >= 3:
            return parts[2]
    res = subprocess.run(["hostname", "-I"], capture_output=True, text=True)
    if res.stdout.strip():
        return res.stdout.strip().split()[0]
    return "127.0.0.1"

def get_git_repo_root():
    res = subprocess.run(["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True)
    if res.returncode == 0 and res.stdout.strip():
        return Path(res.stdout.strip())
    return None

def get_all_worktrees(repo_root):
    res = subprocess.run(["git", "-C", str(repo_root), "worktree", "list", "--porcelain"], capture_output=True, text=True)
    if res.returncode != 0:
        return []
    
    worktrees = []
    current = {}
    for line in res.stdout.splitlines() +[""]:
        if not line:
            if current and "path" in current:
                worktrees.append(current)
            current = {}
        elif line.startswith("worktree "):
            current["path"] = Path(line[9:]).resolve()
            current["name"] = current["path"].name
        elif line.startswith("branch "):
            current["branch"] = line[7:].removeprefix("refs/heads/")
        elif line == "detached":
            current["branch"] = "(detached)"
    return worktrees

def get_file_status(wt_path, rel_path):
    abs_path = wt_path / rel_path
    if not abs_path.exists():
        return None
    
    st_res = subprocess.run(["git", "-C", str(wt_path), "status", "-s", "--", str(rel_path)], capture_output=True, text=True)
    st = st_res.stdout.strip()
    
    mtime = os.path.getmtime(abs_path)
    dt = datetime.datetime.fromtimestamp(mtime)
    time_str = dt.strftime("%m-%d %H:%M")
    
    if st.startswith("??"):
        status_label = "[?? 新增]"
        status_color = GREEN
    elif "M" in st:
        status_label = "[M  修改]"
        status_color = YELLOW
    elif "D" in st:
        status_label = "[D  删除]"
        status_color = RED
    elif st:
        status_label = f"[{st[:7]:<7}]"
        status_color = CYAN
    else:
        status_label = "[clean  ]"
        status_color = GRAY
        
    return {
        "status_label": status_label,
        "status_color": status_color,
        "time_str": time_str,
        "abs_path": str(abs_path),
        "rel_path": str(rel_path),
        "mtime": mtime
    }

def find_target_in_worktrees(worktrees, target_input):
    if not target_input or not target_input.strip():
        return []

    # 1. 若传入已存在的确切物理文件(如 Yazi 传参),直接精准返回单项,绝不误弹列表
    target_p = Path(target_input).resolve()
    if target_p.is_file():
        info = get_file_status(target_p.parent, target_p.name)
        if info:
            for wt in worktrees:
                try:
                    rel = str(target_p.relative_to(wt["path"]))
                    info["wt"] = wt
                    info["rel_path"] = rel
                    info["score"] = 200
                    return [info]
                except ValueError:
                    pass
            info["wt"] = {"name": target_p.parent.name, "path": target_p.parent}
            info["score"] = 200
            return [info]

    target_clean = target_input.strip("/").strip()
    target_lower = target_clean.lower()
    target_name = Path(target_clean).name.lower()
    matches = []

    for wt in worktrees:
        wt_path = wt["path"]
        
        # 1. 相对路径完全匹配
        exact = wt_path / target_clean
        if exact.is_file():
            info = get_file_status(wt_path, target_clean)
            if info:
                info["wt"] = wt
                info["score"] = 100
                matches.append(info)
            continue

        # 2. 从 git ls-files 或文件树抓取文件列表
        files = []
        try:
            res = subprocess.run(
                ["git", "-C", str(wt_path), "ls-files", "-c", "-o", "--exclude-standard"],
                capture_output=True,
                text=True
            )
            if res.returncode == 0 and res.stdout.strip():
                files = [line.strip() for line in res.stdout.splitlines() if line.strip()]
        except Exception:
            pass

        if not files:
            for root, dirs, fnames in os.walk(wt_path):
                dirs[:] = [d for d in dirs if d not in (".git", ".claude", "node_modules", ".venv", "__pycache__", ".runtime")]
                root_p = Path(root)
                for fn in fnames:
                    try:
                        rel = str((root_p / fn).relative_to(wt_path))
                        files.append(rel)
                    except Exception:
                        pass

        # 3. 智能多维度打分模糊匹配
        for rel in files:
            p = Path(rel)
            full_name = p.name.lower()
            stem_name = p.stem.lower()
            rel_lower = rel.lower()

            score = 0
            if rel_lower == target_lower or rel_lower.endswith("/" + target_lower):
                score = 100
            elif full_name == target_name:
                score = 90
            elif stem_name == target_name:
                score = 85  # 省略后缀精准命中(如输入 口播与动作对齐方案 -> 命中 口播与动作对齐方案.md)
            elif full_name.startswith(target_name) or stem_name.startswith(target_name):
                score = 70
            elif target_name in stem_name or target_name in full_name:
                score = 60  # 文件名部分包含关键词
            elif target_lower in rel_lower:
                score = 40  # 路径包含关键词

            if score > 0:
                info = get_file_status(wt_path, rel)
                if info:
                    info["wt"] = wt
                    info["score"] = score
                    matches.append(info)

    # 去重并按匹配得分排序
    seen = set()
    unique = []
    matches.sort(key=lambda m: (-m.get("score", 0), len(m["rel_path"])))
    for m in matches:
        if m["abs_path"] not in seen:
            seen.add(m["abs_path"])
            unique.append(m)
    return unique

def list_all_dirty_files(worktrees):
    all_files = []
    for wt in worktrees:
        wt_path = wt["path"]
        res = subprocess.run(["git", "-C", str(wt_path), "status", "-s"], capture_output=True, text=True)
        for line in res.stdout.splitlines():
            line = line.strip()
            if not line:
                continue
            parts = line.split(maxsplit=1)
            if len(parts) == 2:
                rel = parts[1]
                info = get_file_status(wt_path, rel)
                if info:
                    info["wt"] = wt
                    all_files.append(info)
    all_files.sort(key=lambda x: x["mtime"], reverse=True)
    return all_files

def select_with_fzf(hits, prompt_title="⚡ 选择 Worktree 文件"):
    if not hits:
        return None
    if len(hits) == 1:
        return hits[0]
    if not sys.stdin.isatty():
        return hits[0]
    
    lines = []
    for h in hits:
        wt_name = h["wt"]["name"]
        st_color = h["status_color"]
        st_label = h["status_label"]
        time_str = h["time_str"]
        rel = h["rel_path"]
        abs_path = h["abs_path"]
        
        display = f"🌿 {BOLD}{CYAN}{wt_name:<26}{RESET} {st_color}{st_label}{RESET}  {GRAY}{time_str}{RESET}  {rel}"
        lines.append(f"{display}\t{abs_path}")
    
    input_text = "\n".join(lines) + "\n"
    
    fzf_cmd = [
        "fzf",
        f"--prompt={prompt_title}: ",
        "--height=40%",
        "--layout=reverse",
        "--border",
        "--ansi",
        "--delimiter=\t",
        "--with-nth=1",
        "--select-1",
        "--exit-0"
    ]
    
    try:
        p = subprocess.Popen(fzf_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
        out, _ = p.communicate(input=input_text)
        if p.returncode != 0 or not out.strip():
            return None
        selected_line = out.strip()
        parts = selected_line.split("\t")
        target_path = parts[-1]
        matching_hit = next((h for h in hits if h["abs_path"] == target_path), None)
        return matching_hit or {"abs_path": target_path}
    except Exception as e:
        print(f"fzf 启动失败: {e}", file=sys.stderr)
        return None

def print_help():
    print(f"""
{BOLD}{GREEN}wto (Worktree Open){RESET} - 跨 Worktree 智能文件检索与秒开工具

{BOLD}用法:{RESET}
  {CYAN}wto{RESET}                      扫描所有活跃 Worktree 中未提交的改动,上下键挑选后在 Yazi 打开
  {CYAN}wto <文件名或相对路径>{RESET}    跨所有活跃 Worktree 智能模糊匹配该文件,回车直通 Yazi
  {CYAN}wto -h <文件名>{RESET}          原地启动轻量 HTTP 服务,渲染 Markdown ({BOLD}含 Mermaid 流程图{RESET}) 或 HTML,
                           并自动唤醒 Mac 浏览器打开;{GREEN}生成的内网链接可直接复制分享给同事!{RESET}
  {CYAN}wto -h{RESET}                   扫描所有改动文件,上下键挑选后原地起 HTTP 服务在浏览器打开
  {CYAN}wto --help{RESET}               显示本使用说明

{BOLD}快捷参数:{RESET}
  {YELLOW}-h, --html, --browser, -w{RESET}  启用浏览器预览模式(原地起 HTTP 服务 + 浏览器打开)
  {YELLOW}--help{RESET}                     查看帮助信息
""")

def start_browser_preview(file_path, wt_info=None):
    file_p = Path(file_path).resolve()
    if not file_p.exists():
        print(f"{RED}文件不存在: {file_path}{RESET}")
        return

    target_dir = file_p.parent
    file_name = file_p.name

    # 寻找空闲端口 (3480..3500, 18080..18100)
    port = 0
    for p in list(range(3480, 3500)) + list(range(18080, 18100)):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            try:
                s.bind(('0.0.0.0', p))
                port = p
                break
            except OSError:
                continue
    if not port:
        port = 18080

    server_ip = get_server_ip()

    class RawFileHandler(http.server.SimpleHTTPRequestHandler):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, directory=str(target_dir), **kwargs)

        def guess_type(self, path):
            p = str(path).lower()
            if p.endswith((".md", ".markdown")):
                return "text/markdown; charset=utf-8"
            elif p.endswith((".html", ".htm")):
                return "text/html; charset=utf-8"
            elif p.endswith((".txt", ".log", ".prompt")):
                return "text/plain; charset=utf-8"
            return super().guess_type(path)

        def do_GET(self):
            parsed = urllib.parse.urlparse(self.path)
            clean_path = urllib.parse.unquote(parsed.path.lstrip("/"))
            
            if not clean_path:
                self.send_response(302)
                self.send_header("Location", "/" + urllib.parse.quote(file_name))
                self.end_headers()
                return

            super().do_GET()

        def log_message(self, format, *args):
            pass

    preview_url = f"http://{server_ip}:{port}/{urllib.parse.quote(file_name)}"

    # 唤醒 Mac 本地默认浏览器
    bridge_url = f"http://127.0.0.1:19222/open-browser?url={urllib.parse.quote(preview_url)}"
    notify_res = subprocess.run(["curl", "-s", "--max-time", "2", bridge_url], capture_output=True, text=True)
    auto_opened = (notify_res.stdout.strip() == "ok")

    print(f"\n{BOLD}{GREEN}🌐 原地 HTTP 预览服务已就绪!{RESET}")
    print(f"🔗 访问地址 {BOLD}: {BOLD}{CYAN}{preview_url}{RESET}")
    if auto_opened:
        print(f"{GREEN}✔ 成功唤醒 Mac 本地浏览器打开该文件(原样返回,交由浏览器插件高保真渲染)!{RESET}")
    else:
        print(f"{YELLOW}💡 提示:在 Ghostty 终端按住 Cmd 点击上方链接即可在 Mac 浏览器打开{RESET}")
    print(f"{GRAY}(按 Ctrl+C 停止服务并返回终端){RESET}\n")

    class ThreadedHTTPServer(socketserver.ThreadingTCPServer):
        daemon_threads = True
        allow_reuse_address = True

    try:
        with ThreadedHTTPServer(("0.0.0.0", port), RawFileHandler) as httpd:
            try:
                httpd.serve_forever()
            except KeyboardInterrupt:
                pass
            finally:
                try:
                    httpd.server_close()
                except Exception:
                    pass
    except Exception:
        pass
    print(f"\n{GRAY}HTTP 预览服务已关闭。{RESET}", flush=True)
    os._exit(0)

def main():
    args = sys.argv[1:]

    # 处理 --help
    if any(a in ("--help", "-help", "help") for a in args):
        print_help()
        sys.exit(0)

    is_http_mode = False
    filtered_args = []
    for arg in args:
        if arg in ("-h", "--html", "--http", "--browser", "-w"):
            is_http_mode = True
        else:
            filtered_args.append(arg)

    target = filtered_args[0] if filtered_args else None

    repo = get_git_repo_root()
    if not repo:
        print(f"{RED}✘ 错误:当前目录不在 Git 仓库内!{RESET}")
        sys.exit(1)

    worktrees = get_all_worktrees(repo)
    if not worktrees:
        print(f"{RED}✘ 错误:未读取到任何 worktree 信息!{RESET}")
        sys.exit(1)

    if target:
        hits = find_target_in_worktrees(worktrees, target)
    else:
        hits = list_all_dirty_files(worktrees)

    if not hits:
        print(f"{YELLOW}⚠️  未在任何 Worktree 中找到匹配的文件:{target or '(所有工作区无未提交改动)'}{RESET}")
        sys.exit(0)

    prompt = "🌐 选择文件 (回车启动 HTTP 并打开 Mac 浏览器)" if is_http_mode else "⚡ 选择文件 (回车直通 Yazi)"
    hit = select_with_fzf(hits, prompt_title=prompt)
    if not hit:
        sys.exit(0)

    selected_file = hit["abs_path"]

    cb_bin = shutil.which("cb") or os.path.expanduser("~/.local/bin/cb")
    if os.path.exists(cb_bin):
        subprocess.run([cb_bin, selected_file], capture_output=True)

    if is_http_mode:
        start_browser_preview(selected_file, hit)
    else:
        yazi_bin = shutil.which("yazi") or os.path.expanduser("~/.local/bin/yazi")
        if not os.path.exists(yazi_bin):
            print(f"{RED}✘ 错误:未找到 yazi 二进制文件!{RESET}")
            sys.exit(1)
        os.execv(yazi_bin, [yazi_bin, selected_file])

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        sys.exit(0)
PY
chmod +x ~/.local/bin/wto

5. ~/.local/bin/down(一键下载:将远程服务器文件或目录秒传到 Mac 的 ~/Downloads)

cat << 'PY' > ~/.local/bin/down
#!/usr/bin/env python3
# ==============================================================================
# down (Download to Mac) - 一键将服务器文件/目录极速下载到 Mac 的 ~/Downloads
# ==============================================================================
import sys
import os
import urllib.parse
import urllib.request
import json
import tarfile
import tempfile
import subprocess
import shutil

BRIDGE_URL = "http://127.0.0.1:19222"

HELP_TEXT = """\033[1mdown\033[0m (Download to Mac) - 一键将服务器文件下载到 Mac 的 ~/Downloads

\033[1m用法:\033[0m
  down <文件或目录>...       将一个或多个文件直接下载到 Mac 的 ~/Downloads
  down                      不带参数时,交互式弹出文件列表(fzf 上下键挑选秒下载)
  down --help, -h           查看帮助说明

\033[1m亮点:\033[0m
  • 自动识别目录:指定目录时会自动压缩为 .tar.gz 传回 Mac
  • 零等待唤醒:文件传完后 Mac 会自动在 Finder 中高亮选中该文件
  • 重名保护:若 Mac 的 ~/Downloads 已有同名文件,自动追加 (1), (2) 防覆盖
"""

def format_size(size_bytes):
    if size_bytes < 1024:
        return f"{size_bytes} B"
    elif size_bytes < 1024 * 1024:
        return f"{size_bytes / 1024:.1f} KB"
    else:
        return f"{size_bytes / (1024 * 1024):.1f} MB"

def check_bridge():
    try:
        req = urllib.request.Request(f"{BRIDGE_URL}/ping", method="GET")
        with urllib.request.urlopen(req, timeout=2) as resp:
            return resp.status == 200
    except Exception:
        return False

def upload_file(local_path, custom_name=None):
    if not os.path.exists(local_path):
        print(f"\033[31m✘ [Server ➔ Mac]\033[0m 文件不存在: {local_path}")
        return False

    is_temp_tar = False
    actual_path = local_path
    filename = custom_name or os.path.basename(local_path.rstrip("/\\"))

    # 如果是目录,自动打包为 tar.gz
    if os.path.isdir(local_path):
        clean_dir = os.path.normpath(local_path)
        base_name = os.path.basename(clean_dir) or "archive"
        filename = f"{base_name}.tar.gz"
        print(f"\033[34mℹ [Server ➔ Mac]\033[0m 检测到目录,正在打包压缩为 \033[1m{filename}\033[0m ...")
        
        temp_file = tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False)
        temp_file.close()
        try:
            with tarfile.open(temp_file.name, "w:gz") as tar:
                tar.add(local_path, arcname=base_name)
            actual_path = temp_file.name
            is_temp_tar = True
        except Exception as e:
            print(f"\033[31m✘ [Server ➔ Mac]\033[0m 打包目录失败: {e}")
            if os.path.exists(temp_file.name):
                os.unlink(temp_file.name)
            return False

    file_size = os.path.getsize(actual_path)
    url = f"{BRIDGE_URL}/save-file?filename={urllib.parse.quote(filename)}&reveal=1"

    try:
        with open(actual_path, "rb") as f:
            req = urllib.request.Request(
                url,
                data=f,
                headers={
                    "Content-Type": "application/octet-stream",
                    "Content-Length": str(file_size)
                },
                method="POST"
            )
            with urllib.request.urlopen(req, timeout=180) as resp:
                result = json.loads(resp.read().decode("utf-8"))
                saved_name = result.get("filename", filename)
                size_str = format_size(result.get("size", file_size))
                print(f"\033[32m✔ [Server ➔ Mac]\033[0m 成功下载到 Mac: \033[1m~/Downloads/{saved_name}\033[0m ({size_str})")
                return True
    except Exception as e:
        print(f"\033[31m✘ [Server ➔ Mac]\033[0m 下载失败 ({filename}): {e}")
        return False
    finally:
        if is_temp_tar and os.path.exists(actual_path):
            try:
                os.unlink(actual_path)
            except Exception:
                pass

def pick_file_fzf():
    if not shutil.which("fzf"):
        print("\033[31m✘ [Server ➔ Mac]\033[0m 未指定文件且系统未安装 fzf,请直接指定文件名,如: down <文件名>")
        return None
    
    find_cmd = [
        "find", ".",
        "-maxdepth", "3",
        "-not", "-path", "*/.*",
        "-not", "-path", "*/node_modules/*",
        "-not", "-path", "*/.venv/*",
        "-not", "-path", "*/__pycache__/*",
        "-type", "f"
    ]
    fzf_cmd = [
        "fzf",
        "--height=40%",
        "--reverse",
        "--prompt=选择要下载到 Mac 的文件 > ",
        "--preview=head -n 20 {} 2>/dev/null"
    ]
    try:
        p1 = subprocess.Popen(find_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
        p2 = subprocess.Popen(fzf_cmd, stdin=p1.stdout, stdout=subprocess.PIPE, text=True)
        p1.stdout.close()
        out, _ = p2.communicate()
        selected = out.strip()
        return selected if selected else None
    except Exception:
        return None

def main():
    args = sys.argv[1:]
    if args and args[0] in ("-h", "--help", "help"):
        print(HELP_TEXT)
        return

    if not check_bridge():
        print("\033[31m✘ [Server ➔ Mac]\033[0m 无法连接 Mac 本地桥接服务 (HTTP 19222)")
        print("  提示: 请检查是否通过带有端口转发的 SSH 登录 (RemoteForward 19222 127.0.0.1:19222)")
        sys.exit(1)

    if not args:
        selected = pick_file_fzf()
        if not selected:
            sys.exit(0)
        upload_file(selected)
    else:
        for path in args:
            upload_file(path)

if __name__ == "__main__":
    main()
PY
chmod +x ~/.local/bin/down

2.4 安装部署 Yazi (v26.9+) 与插件套件

安装最新版纯静态 Yazi:

curl -sL https://github.com/sxyazi/yazi/releases/download/v26.9.1/yazi-x86_64-unknown-linux-musl.zip -o /tmp/yazi.zip
unzip -o /tmp/yazi.zip -d /tmp/yazi_bin
mv /tmp/yazi_bin/yazi-x86_64-unknown-linux-musl/yazi ~/.local/bin/yazi
mv /tmp/yazi_bin/yazi-x86_64-unknown-linux-musl/ya ~/.local/bin/ya
chmod +x ~/.local/bin/yazi ~/.local/bin/ya
rm -rf /tmp/yazi.zip /tmp/yazi_bin

安装并配置四大专属插件:

  1. zoom.yazi(自适应等比铺满与按键缩放)
  2. piper.yazi(官方通用流预览)
  3. paste-img.yazi(按 P 直接粘贴 Mac 截图)
  4. copy-mac.yazi(按 c c 把绝对路径直接复制到 Mac)

创建 ~/.config/yazi/plugins/zoom.yazi/main.lua:

--- @since 26.1.22
ya.clone = ya.clone or Url

local get = ya.sync(function(st, url) return st.last == url and st.level end)
local save = ya.sync(function(st, url, new)
    local h = cx.active.current.hovered
    if h and h.url == url then
        st.last, st.level = url, new
        return true
    end
end)
local lock = ya.sync(function(st, url, old, new)
    if st.last == url and st.level == old then
        st.level = new
        return true
    end
end)
local move = ya.sync(function(st)
    local h = cx.active.current.hovered
    if not h then return end
    if st.last ~= h.url then st.last, st.level = ya.clone(h.url), 0 end
    return { url = h.url, level = st.level }
end)

local function end_(job, err)
    if not job.old_level then
        ya.preview_widget(job, err and ui.Text(err):area(job.area):wrap(ui.Wrap.YES))
    elseif err then
        ya.notify { title = "Zoom", content = tostring(err), timeout = 5, level = "error" }
    end
end

local function canvas(area)
    local cw, ch = rt.term.cell_size()
    if not cw or cw <= 0 or ch <= 0 then
        return rt.preview.max_width, rt.preview.max_height
    end
    return math.min(rt.preview.max_width, math.floor(area.w * cw)),
        math.min(rt.preview.max_height, math.floor(area.h * ch))
end

local function peek(_, job)
    local url = job.file.url
    local info, err = ya.image_info(url)
    if not info then
        return end_(job, Err("Failed to get image info: %s", err))
    end

    local level = ya.clamp(-10, job.new_level or get(ya.clone(url)) or tonumber(job.args[1]) or 0, 10)
    local sync = function()
        if job.old_level then return lock(url, job.old_level, level) else return save(url, level) end
    end

    local max_w, max_h = canvas(job.area)
    local scale_w, scale_h = max_w / info.w, max_h / info.h
    local base_scale = math.min(scale_w, scale_h)
    local base_w = math.max(1, math.floor(info.w * base_scale))
    local base_h = math.max(1, math.floor(info.h * base_scale))

    local factor = 1.0 + (level * 0.15)
    if factor < 0.1 then factor = 0.1 end

    local new_w = math.min(max_w, math.floor(base_w * factor))
    local new_h = math.min(max_h, math.floor(base_h * factor))

    local tmp = os.tmpname()
    local output, err = Command("magick"):arg {
        tostring(job.file.path),
        "-auto-orient", "-strip",
        "-sample", string.format("%dx%d", new_w, new_h),
        "-quality", rt.preview.image_quality,
        string.format("WEBP:%s", tmp),
    }:output()

    if not output then
        end_(job, Err("Failed to start `magick`: %s", err))
    elseif not output.status.success then
        end_(job, Err("`magick` error %s: %s", output.status.code, output.stderr))
    elseif sync() then
        ya.image_show(Url(tmp), job.area)
    end
    end_(job)
end

local function entry(self, job)
    local st = move()
    if not st then return end
    local motion = tonumber(job.args[1]) or 0
    local new = (job.args[1] == "0" or motion == 0) and 0 or ya.clamp(-10, st.level + motion, 10)
    if new ~= st.level or job.args[1] == "0" then
        Stat = Stat or Cha
        local stat = Stat { mode = tonumber("100644", 8) }
        peek(self, {
            area = ui.area("preview"),
            args = {},
            file = File { url = st.url, cha = stat, stat = stat, lstat = stat },
            skip = 0,
            new_level = new,
            old_level = st.level,
        })
    end
end

return { peek = peek, entry = entry }

创建 ~/.config/yazi/plugins/copy-mac.yazi/main.lua:

--- @sync entry
return {
    entry = function(self, job)
        local h = cx.active.current.hovered
        if not h then return end
        
        local text = ""
        local kind = job.args[1] or "path"
        if kind == "path" then
            text = tostring(h.url)
        elseif kind == "filename" or kind == "f" then
            text = tostring(h.name)
        elseif kind == "dirpath" or kind == "d" then
            text = tostring(cx.active.current.cwd)
        elseif kind == "name_without_ext" or kind == "n" then
            text = tostring(h.name:gsub("%.%w+$", ""))
        else
            text = tostring(h.url)
        end

        local child = Command("sh")
            :arg({ "-c", "curl -s -X POST --data-binary @- http://127.0.0.1:19222/set-clipboard" })
            :stdin(Command.PIPED)
            :spawn()

        if child then
            child:write_all(text)
            child:flush()
            child:wait()
            ya.notify { title = "Copied to Mac", content = text, timeout = 2, level = "info" }
        else
            ya.notify { title = "Copy Failed", content = "无法连接 Mac 剪贴板", timeout = 3, level = "error" }
        end
    end,
}

创建 ~/.config/yazi/plugins/paste-img.yazi/main.lua:

--- @sync entry
return {
    entry = function()
        local child, err = Command("pi"):spawn()

        if not child then
            ya.notify { title = "Paste", content = "启动失败: " .. tostring(err), timeout = 3, level = "error" }
            return
        end

        local status = child:wait()
        if status and status.success then
            ya.notify { title = "Paste", content = "✔ 剪贴板内容已成功粘贴入当前目录!", timeout = 3, level = "info" }
        else
            ya.notify { title = "Paste", content = "✘ 粘贴失败:剪贴板无数据或桥接未连接", timeout = 3, level = "warn" }
        end
    end,
}

写入配置文件 ~/.config/yazi/yazi.toml:

[preview]
max_width  = 3840
max_height = 2160
image_quality = 85

[plugin]
prepend_previewers = [
  { url = "*.{png,jpg,jpeg,webp,gif,bmp,PNG,JPG,JPEG,WEBP,GIF,BMP}", run = "zoom" },
  { mime = "image/*", run = "zoom" },
  { url = "*.md", run = 'piper -- CLICOLOR_FORCE=1 glow -w=$w -s=dark "$1"' },
  { url = "*.markdown", run = 'piper -- CLICOLOR_FORCE=1 glow -w=$w -s=dark "$1"' },
]

写入配置文件 ~/.config/yazi/keymap.toml:

# 窗口全屏放大/恢复
[[mgr.prepend_keymap]]
on   = "T"
run  = "plugin toggle-pane max-preview"
desc = "全屏放大 / 恢复右侧预览窗口"

# 图片缩放
[[mgr.prepend_keymap]]
on   = "+"
run  = "plugin zoom 1"
desc = "图片预览放大"

[[mgr.prepend_keymap]]
on   = "="
run  = "plugin zoom 1"
desc = "图片预览放大"

[[mgr.prepend_keymap]]
on   = "-"
run  = "plugin zoom -1"
desc = "图片预览缩小"

[[mgr.prepend_keymap]]
on   = "0"
run  = "plugin zoom 0"
desc = "重置图片缩放比例"

# 从 Mac 粘贴截图
[[mgr.prepend_keymap]]
on   = "P"
run  = "plugin paste-img"
desc = "从 Mac 剪贴板直接粘贴图片或文件到当前目录"

# 路径复制直达 Mac 剪贴板
[[mgr.prepend_keymap]]
on   = [ "c", "c" ]
run  = "plugin copy-mac -- path"
desc = "复制文件绝对路径到 Mac 剪贴板"

[[mgr.prepend_keymap]]
on   = [ "c", "f" ]
run  = "plugin copy-mac -- filename"
desc = "复制文件名到 Mac 剪贴板"

[[mgr.prepend_keymap]]
on   = [ "c", "d" ]
run  = "plugin copy-mac -- dirpath"
desc = "复制当前目录路径到 Mac 剪贴板"

[[mgr.prepend_keymap]]
on   = [ "c", "n" ]
run  = "plugin copy-mac -- name_without_ext"
desc = "复制无后缀文件名到 Mac 剪贴板"

# 预览区滚动
[[mgr.prepend_keymap]]
on   = [ "<C-e>" ]
run  = "seek 5"
desc = "预览窗口向下滚动 5 行"

[[mgr.prepend_keymap]]
on   = [ "<C-y>" ]
run  = "seek -5"
desc = "预览窗口向上滚动 5 行"

# 在 Mac 浏览器中通过 HTTP 打开当前文件
[[mgr.prepend_keymap]]
on   = "B"
run  = 'shell "wto -h %h" --block'
desc = "在 Mac 浏览器中 HTTP 预览当前文件"

[[mgr.prepend_keymap]]
on   = "b"
run  = 'shell "wto -h %h" --block'
desc = "在 Mac 浏览器中 HTTP 预览当前文件"

2.6 终端美化与极简提示符 (~/.config/starship.toml)

采用 Catppuccin 渐变胶囊样式,并彻底去除 $?: 等令人眼花的 Git 杂乱状态符号,保持终端极致清爽:

cat << 'TOML' > ~/.config/starship.toml
"$schema" = 'https://starship.rs/config-schema.json'

format = """
[](red)\
$os\
$username\
[](bg:peach fg:red)\
$directory\
[](bg:yellow fg:peach)\
$git_branch\
$git_status\
[ ](yellow)\
$character"""

palette = 'catppuccin_mocha'

[os]
disabled = false
style = "bg:red fg:crust"

[os.symbols]
Macos = "󰀵"
Ubuntu = "󰕈"
Linux = "󰌽"

[username]
show_always = true
style_user = "bg:red fg:crust"
style_root = "bg:red fg:crust"
format = '[[ $user ](fg:crust bg:red)]($style)'

[directory]
style = "fg:crust bg:peach"
format = "[ $path ]($style)"
truncation_length = 3
truncation_symbol = "…/"

[git_branch]
symbol = ""
style = "bg:yellow"
format = '[[ $symbol $branch ](fg:crust bg:yellow)]($style)'

# 极简模式:关闭复杂的 stash/untracked 符号($?:),仅保留干净的分支名
[git_status]
disabled = true
style = "bg:yellow"
format = '[[($all_status$ahead_behind )](fg:crust bg:yellow)]($style)'

[nodejs]
disabled = true

[time]
disabled = true

[cmd_duration]
disabled = true

[character]
disabled = false
success_symbol = '[❯](bold green)'
error_symbol = '[❯](bold red)'

[palettes.catppuccin_mocha]
rosewater = "#f5e0dc"
flamingo = "#f2cdcd"
pink = "#f5c2e7"
mauve = "#cba6f7"
red = "#f38ba8"
maroon = "#eba0ac"
peach = "#fab387"
yellow = "#f9e2af"
green = "#a6e3a1"
teal = "#94e2d5"
sky = "#89dceb"
sapphire = "#74c7ec"
blue = "#89b4fa"
lavender = "#b4befe"
text = "#cdd6f4"
subtext1 = "#bac2de"
subtext0 = "#a6adc8"
overlay2 = "#9399b2"
overlay1 = "#7f849c"
overlay0 = "#6c7086"
surface2 = "#585b70"
surface1 = "#45475a"
surface0 = "#313244"
base = "#1e1e2e"
mantle = "#181825"
crust = "#11111b"
TOML

2.7 部署远程 Shell 快捷别名与工作流 (~/.bashrc)

在远程服务器的 ~/.bashrc 末尾添加如下快捷别名与函数,实现工作区秒切、Tmux 窗口无痛管理与神级终端工具链集成:

# ------------------------------------------------------------------------------
# 远程开发快捷别名与工作流
# ------------------------------------------------------------------------------
# 1. 项目直达
alias ai3='cd ~/workspaces/<PROJECT_NAME> && tmux new -A -s <PROJECT_NAME>'
alias cdai='cd ~/workspaces/<PROJECT_NAME>'

# 2. 终端神级工具链
alias lg='lazygit'            # Git TUI 神器(按 w 看/切 worktree,按 3 看 commit 树)
alias y='~/.local/bin/yazi'
alias yazi='~/.local/bin/yazi' # 现代化文件管理器(方向键穿梭,Kitty 原生看图)
alias wt='git worktree list'   # 查看当前项目所有 git worktree 工作树
alias gw='glow'

# 3. Tmux 窗口快捷操作(保持当前目录)
alias new='tmux new-window -c "$(pwd)"'
alias cw='new'
alias rn='tmux rename-window'
alias close='exit'
alias tls='tmux ls'

# 4. 函数定义(规范无别名冲突)
ta() {
  tmux new -A -s "${1:-main}"
}

md() {
  if [ -z "$1" ]; then
    glow
  else
    glow -p "$@"
  fi
}

ws() {
  local target_dir
  if [ -z "$1" ]; then
    target_dir=$(find ~/workspaces -mindepth 1 -maxdepth 1 -type d -exec basename {} \; 2>/dev/null | sort | fzf --prompt="⚡ 选择工作区项目: " --height=40% --layout=reverse --border)
    [ -z "$target_dir" ] && return 0
  else
    target_dir="$1"
  fi
  local full_path="$HOME/workspaces/$target_dir"
  if [ -d "$full_path" ]; then
    cd "$full_path"
    tmux new -A -s "$target_dir"
  else
    echo "未找到目录: $full_path"
  fi
}

tkill() {
  local target="$1"
  if [ -z "$target" ]; then
    target=$(tmux list-sessions -F "#{session_name}" 2>/dev/null | fzf --prompt="🗑️ 选择要关闭的保活会话: " --height=40% --layout=reverse --border)
    [ -z "$target" ] && return 0
  fi
  if [ "$target" = "all" ]; then
    tmux kill-server
    echo "✔ 已关闭全部后台保活会话"
  else
    tmux kill-session -t "$target" 2>/dev/null && echo "✔ 已关闭会话: $target" || echo "✖ 未找到会话: $target"
  fi
}

# 加载 Starship 彩虹状态栏(Catppuccin Powerline)
if [ -x "$HOME/.local/bin/starship" ]; then
  eval "$($HOME/.local/bin/starship init bash)"
fi

第三部分:完整验证核对清单(验收流程)

按顺序执行以下命令验证全链路是否正常:

验证项 执行命令 期望输出 / 正确现象
1. Mac 本地桥接服务 curl -s http://127.0.0.1:19222/ping 输出 pong
2. 连入服务器反向隧道 cx 然后在服务器执行 curl -s http://127.0.0.1:19222/ping 输出 pong(证明隧道通畅)
3. Tmux 版本与选项 在服务器执行 tmux -V && tmux show -g allow-passthrough 输出 tmux 3.5a 与 allow-passthrough on
4. 粘贴 Mac 截图或文件 在 Mac 截图或在 Finder 复制文件后,在服务器执行 pi 输出 ✔ [Mac ➔ Server] 成功从剪贴板粘贴图片/文件
5. 拖拽文件秒传 在服务器执行 up /Users/.../test.png 输出 ✔ [Mac ➔ Server] 上传成功
6. Yazi 路径复制到 Mac 在服务器运行 y,光标停在文件敲两次 c 右上角提示 Copied to Mac,回 Mac 按 Cmd+V 成功粘贴
7. 图片看图与缩放 在 Yazi 停在图片上按 T 图片瞬间铺满全屏,按 + 进一步放大,按 0 还原
8. 跨 Worktree 智能检索 在服务器执行 wto 或 wto <文件> 底部弹起 40% fzf 窗口,上下键移动,回车直通 Yazi
9. 浏览器原地文件预览 在服务器执行 wto -h <文件> 原地启动轻量 HTTP 服务,原样返回文件(支持浏览器 Markdown 插件高保真渲染),自动唤醒 Mac 浏览器打开,内网链接可直接发给同事,Ctrl+C 退出
10. 服务器文件秒传回 Mac 在服务器执行 down <文件或目录> 输出 ✔ [Server ➔ Mac] 成功下载到 Mac: ~/Downloads/...,Mac Finder 自动聚焦高亮选中

第四部分:日常操作随手备忘

# --- 会话连接与管理 (Mac 本地,配置解耦自 ~/.config/cx/config) ---
cx                # 默认直达主项目会话
cx new            # 自动新建独立序号会话 (my_project_1, _2 ...)
cx 1 / cx 2       # 直接进对应序号会话
cx ls             # 查看后台有哪些会话在跑 (免端口转发冲突)
cx k              # 交互式选择关闭会话
cx ka             # 一键关闭全部后台会话
cx config         # 查看/切换当前目标服务器与项目配置
cx bridge         # 查看本地跨端桥接服务运行状态 (127.0.0.1:19222)
cx bridge restart # 一键重启本地桥接服务 (解决端口阻塞或异常)
cx -h / cx h     # 查看 cx 命令行帮助与中文说明 (亦支持 help / --help)

# --- 远程工作区直达与会话调度 (远程服务器) ---
ws                # fzf 弹出工作区列表,上下键挑选回车直达项目并挂接 tmux
ws <项目名>       # 直接切到指定项目目录并挂入对应会话
ai3               # 秒进主项目并恢复/挂接主会话
cdai              # 纯切到主项目目录
ta [会话名]       # 挂接指定会话(默认 main,不存在则新建)
tls               # 查看所有后台 tmux 会话
tkill             # fzf 交互式挑着杀掉会话
tkill all         # 一键杀掉全部后台保活会话

# --- Tmux 窗口极速操作 (远程服务器内) ---
new / cw          # 在当前工作目录下新建 tmux 窗口(保持当前 pwd,不跳回根目录)
rn                # 重命名当前 tmux 窗口
close             # 退出当前窗格或窗口 (exit)

# --- Git Worktree 与文档速查 (远程服务器) ---
wt                # 一键查看当前仓库所有 Git Worktree 工作树及分支
lg                # 启动 Lazygit(按 w 查/切 worktree,按 3 查 commit 树)
md <文件>         # Glow 彩色高亮分页预览 Markdown 文档
md                # Glow 交互式浏览当前目录所有文档
gw                # glow 原生命令别名

# --- 跨端文件流转与 Worktree 检索 (远程服务器) ---
pi / pf           # 智能粘贴:Mac 截图像图贴图;Finder 复制文件(Cmd+C)则原名拉取!
pi test.png       # 指定文件名保存(仅单文件/截图时生效)
up [把文件拖进来] # 直接把本地文件拉取到当前目录
down <文件/目录>  # 一键秒下到 Mac ~/Downloads,目录自动打 .tar.gz,Finder 自动高亮!
down              # 不带参数:fzf 上下键挑选文件极速下载到 Mac
down --help       # 查看 down 命令行帮助
wto <文件名>      # 跨所有 Worktree 智能匹配该文件,fzf 上下键选择,回车直通 Yazi 并高亮
wto -h <文件名>   # 原地起 HTTP 服务,原样返回文件(交由浏览器 Markdown 插件高保真渲染),链接可直接发同事,Ctrl+C 退出
wto -h            # 扫描所有 Worktree 中近期改动的未提交代码,上下键挑选后起 HTTP 浏览器查看
wto --help        # 查看 wto 命令行帮助与用法说明

# --- Yazi 专属神键 (远程服务器) ---
y                 # 启动 Yazi
q                 # 普通退出
Q (大写)          # 退出并自动 cd 到最后选中的目录(极力推荐)
c c               # 复制选中文件的绝对路径到 Mac 剪贴板!
c f               # 复制文件名到 Mac 剪贴板
P (大写)          # 直接从 Mac 剪贴板粘贴最新截图或复制的文件并高亮展示
T (大写)          # 全屏放大/还原右侧预览(图片自适应撑满,文档宽幅排版)
+ / =             # 图片手动放大 15%
-                 # 图片手动缩小
0                 # 重置图片缩放为最佳贴合尺寸
Ctrl+e / Ctrl+y   # 预览窗口下滚/上滚 5 行
B / b             # 在 Mac 浏览器中通过 HTTP 原地打开当前文件(可发同事,按 Ctrl+C 秒回 Yazi)

# --- Tmux 窗口与分屏操作 (远程服务器,前缀键统一为 Ctrl+b,完全释放 Ctrl+a 跳行首) ---
Ctrl+b 然后按 }   # 当前窗口与右侧/下方窗口互换位置!
Ctrl+b 然后按 {   # 当前窗口与左侧/上方窗口互换位置!
Ctrl+b 然后按 Ctrl+o # 顺时针旋转轮换所有分屏
Ctrl+b 然后按 Space  # 一键切换分屏排版(左右、上下、网格四分屏)
Ctrl+b 然后按 !   # 把当前分屏弹射出去变成独立的新全屏窗口
Ctrl+b 然后按 z   # 当前分屏临时最大化 / 还原
Comments
Write a Comment

Tags

css3   魅力CSS   nodejs   loading   CSS   疯狂的菊花   html5   animation   compiler   编译脚本   png   WordPress   智能   旅行   优化   模板   历史记录   跨域   manifest   frame   canvas   动画   js   离线应用   codelet   transform   抽取   java   兼容问题   发布脚本   富文本   那一年在他乡   htaccess   iframe   帧动画   加载速度   intelligent   跨浏览器   DNS解析   插件   checkbox   单边   step-start   vary   复选框   自动更新   转换   文本溢出   盒子阴影   menu   blob   西安   滑动背景   box-shadow   内存占用   键盘事件   python   auto   text overflow   background   所见所得   android   rotate   字节数   合并   文本框   slide   字符串连接符   协议   伪类   兄弟选择符   网格   节点位置比较   空白   斜线拼接   自定义命令   溢出   clock   素描   无法更新   分隔符   字符编码   body   下载文件   精灵图   step   nodej   ubuntu   apache   css3选择器   创建文件   多级菜单   编辑状态   ajax   ghostty   垂直   chrome   管道   时钟   firefox   背景   coding   文件上传   createobjecturl   linux   游记   下载   放射渐变   版本号   宽高   照片   localStorage   渐变背景   图片   图片拼接   属性值检测   自动生成   计算   返回键   oauth   合图   reset   调用   cavnas   漏洞   按钮   margin   线性渐变   xsrf   被黑   tab   checked   阴影   修复   border   消失   step-end   sprite   common-upload   菜单   兄弟选择器   字符串   svn   九寨沟   缩进   remote   css遮罩   svg   添加系统服务   gzip   插入代码   动态   加速   模拟