公众号这种私域实质上是阻碍信息交流的。
沉迷MCP和Skills
其实我还没玩明白。
太多了,玩不明白。
在Emacs里连Open Science的OpenCode
内容是DeepSeek V4 Flash和Open Science共同生成。我唯一的修改是把备注里由DeepSeek生成挪前面了。你们能看出来是AI生成的么?别管质量了,快是真快,这些我不得写个半小时,调试这个不得调试半天。Agent大法好啊。
接上面沉迷MCP和Skills的话题,这几天玩了一个新东西:把Open Science Desktop启动的opencode服务,从WSL里的Emacs连过去用。
起因很简单:我整天泡在Emacs里,不想为了用opencode专门切到另一个窗口。Open Science Desktop本来就起了一个opencode服务,我的Emacs能不能直接连上去?
折腾了一圈,结论值得记下来。
发现opencode本来就是HTTP服务
Open Science Desktop启动opencode时,其实是跑了一个HTTP服务器,默认监听127.0.0.1,端口每次随机。它还带Basic Auth,密码是进程级环境变量OPENCODE_SERVER_PASSWORD,每次重启重新生成。
也就是说,它天生就是个可以被其他程序访问的服务,只是监听在本机回环地址上。
第一个坑:WSL连不到Windows的127.0.0.1
我的Emacs跑在WSL里。WSL2默认NAT模式下,连不到Windows宿主机上绑定127.0.0.1的服务——这是实测的,不是想当然。网关IP、localhost,都不通。
我的Windows是build 22000,不支持镜像网络,所以绕不开这个网络隔离。
解法:在Windows侧跑一个纯Python的TCP转发器,把某个端口转发到127.0.0.1:<端口>,WSL从网关IP连转发器即可。不需要管理员权限,这点很关键。
使用:python opencode-bridge.py <对外端口> <目标主机> <目标端口>。
三个占位符替换成你自己的值:
<网关IP>:WSL 网关 IP,在 WSL 里执行 grep nameserver /etc/resolv.conf 查到(形如 172.x.x.1)。
<端口>:转发器对外端口(随意挑个空闲的),以及目标端口(opencode 服务端口,见上)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import socket, threading, sys
LISTEN_HOST = "<网关IP>" LISTEN_PORT = int(sys.argv[1]) TARGET_HOST = sys.argv[2] TARGET_PORT = int(sys.argv[3])
def pipe(src, dst): try: while True: data = src.recv(65536) if not data: break dst.sendall(data) except OSError: pass finally: try: dst.shutdown(socket.SHUT_WR) except OSError: pass try: src.close() except OSError: pass
def handle(client): try: up = socket.create_connection((TARGET_HOST, TARGET_PORT), timeout=10) except OSError: client.close(); return threading.Thread(target=pipe, args=(client, up), daemon=True).start() threading.Thread(target=pipe, args=(up, client), daemon=True).start()
def main(): srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind((LISTEN_HOST, LISTEN_PORT)); srv.listen(128) print("forwarding %s:%d -> %s:%d" % (LISTEN_HOST, LISTEN_PORT, TARGET_HOST, TARGET_PORT), flush=True) while True: c, _ = srv.accept() threading.Thread(target=handle, args=(c,), daemon=True).start()
if __name__ == "__main__": main()
|
密码怎么处理
密码是进程级的,用户环境变量里没有。它在 Open Science Desktop 进程的环境变量 OPENCODE_SERVER_PASSWORD 里,每次重启重新生成,所以没法一次性配好。解决方案:把它写进WSL的一个文件里,Emacs配置读取时从文件取,不硬编码;重启后重新执行一次写入即可。
把密码写进 WSL 文件(<base64> 替换为密码的 base64,实际取密码的方式见上文):
1 2
| wsl.exe -e bash -c "printf '%s' '<base64>' | base64 -d > ~/.config/osc-opencode/password && chmod 600 ~/.config/osc-opencode/password"
|
Emacs里这样读:
1 2 3 4 5
| (defun opencode-desktop-read-password () (let ((f (expand-file-name "osc-opencode/password" "~/.config/"))) (if (file-readable-p f) (string-trim (with-temp-buffer (insert-file-contents f) (buffer-string))) (error "password file not found: %s" f))))
|
用现成的opencode.el
手写HTTP客户端太累了,GitHub上有现成的opencode.el,支持attach到已有的opencode服务。Emacs 29+内置的package-vc就能装:
1 2
| M-x package-vc-install RET https://github.com/karta0807913/opencode.el.git RET
|
配置基本三样:服务器地址、端口、密码。
折腾中踩的坑
C-c o键位时好时坏:因为opencode-mode是全局minor mode,要主动激活。
- 会话列表空:opencode.el按当前目录过滤会话。要在对的工作目录里
M-x cd。
- 界面卡busy:WSL下opencode.el把chat buffer的目录设成了Windows路径(如
C:\home\kel\.emacs.d),导致SSE子进程起不来。加个钩子修正。
1 2 3 4 5 6 7 8
| ;; WSL路径修正:把被设成Windows路径的目录改回WSL路径 (defun opencode--wsl-valid-dir (dir) (let ((exp (ignore-errors (expand-file-name dir)))) (if (and exp (file-directory-p exp)) exp (expand-file-name "~/")))) (defun opencode--wsl-fix-chat-dir () (when (string-match "^[A-Za-z]:\\\\" default-directory) (setq default-directory (opencode--wsl-valid-dir default-directory)))) (add-hook 'opencode-chat-mode-hook #'opencode--wsl-fix-chat-dir)
|
现在的工作流
- Windows侧起转发器
- Emacs里
M-x cd到工作目录
C-c o O attach
C-c o c开会话
整体能用,但每次换目录要cd一下,还是有点糙。可能等opencode.el更成熟吧。
备注
中间折腾的细节和代码都是实测过的,但Emacs配置这东西,换环境可能就要调整,仅供参考。