<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>claude on Astroicers Blog</title><link>https://astroicers.link/tags/claude.html</link><description>Recent content in claude on Astroicers Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 08 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://astroicers.link/tags/claude/index.xml" rel="self" type="application/rss+xml"/><item><title>Vibe Remote — 手機上的 AI Coding 助手</title><link>https://astroicers.link/p/vibe-remote-ai-coding-gateway.html</link><pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate><guid>https://astroicers.link/p/vibe-remote-ai-coding-gateway.html</guid><description>&lt;h2 id="專案簡介">專案簡介&lt;/h2>
&lt;p>通勤時用手機語音或文字下指令，AI 幫你讀 codebase、寫程式碼，你審 diff、一鍵 commit push。這就是 &lt;a class="link" href="https://github.com/astroicers/vibe-remote" target="_blank" rel="noopener"
>Vibe Remote&lt;/a> 在做的事。&lt;/p>
&lt;p>手機上開不了 IDE，但 AI 可以替你操作整個 codebase。Vibe Remote 把 Claude Agent SDK 包裝成 mobile-first 的介面，專門為「對話 → review diff → approve → commit」的工作流設計。&lt;/p>
&lt;p>跟 ChatGPT 或 Cursor 不一樣，Vibe Remote 不是聊天機器人。它是一個有完整 codebase 存取權的 AI Agent——可以讀檔、改檔、跑指令、自動修 bug，最後把改動交給你審查。&lt;/p>
&lt;h2 id="主要功能">主要功能&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>AI 對話&lt;/strong> — Claude Agent SDK 串流回應，即時看到 AI 的思考過程和 tool 呼叫&lt;/li>
&lt;li>&lt;strong>Diff 審查&lt;/strong> — AI 改完程式碼後逐檔檢視差異，可以批准、退回、或留言要求修改&lt;/li>
&lt;li>&lt;strong>Git 操作&lt;/strong> — commit、push、pull、切分支，全部在手機上完成&lt;/li>
&lt;li>&lt;strong>多 Workspace 並行&lt;/strong> — 最多 3 個 AI Runner 同時跑，左滑右滑切換不同專案&lt;/li>
&lt;li>&lt;strong>非同步任務佇列&lt;/strong> — Kanban 看板管理待辦任務，排隊讓 AI 依序處理&lt;/li>
&lt;li>&lt;strong>PWA 安裝 + Push 通知&lt;/strong> — 安裝到主畫面，AI 完成任務後收到推播&lt;/li>
&lt;li>&lt;strong>Prompt 模板&lt;/strong> — 常用指令存成模板，一鍵套用&lt;/li>
&lt;/ul>
&lt;h2 id="架構概覽">架構概覽&lt;/h2>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">手機 (PWA) ──HTTPS──→ Tailscale ──→ Docker Container
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── Express API + WebSocket (port 8080)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── Claude Agent SDK Runner (max 3 並行)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── SQLite (WAL mode)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── /workspace (volume mount → ~/projects)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>Express API + WebSocket&lt;/strong> — 同一個 port 處理 REST API 和 WebSocket。WebSocket 負責 AI 串流回應和即時通知，REST 處理 CRUD 操作。&lt;/p>
&lt;p>&lt;strong>Claude Agent SDK Runner&lt;/strong> — 封裝 SDK 的 &lt;code>query()&lt;/code> 函式，設定 &lt;code>cwd&lt;/code> 為 workspace 路徑後，SDK 自動讀取專案的 CLAUDE.md，內建 Read / Edit / Write / Bash / Grep / Glob 等 tools。每個 runner 獨立追蹤 modified files，完成後觸發 diff review。&lt;/p>
&lt;p>&lt;strong>SQLite&lt;/strong> — 儲存對話歷史、diff reviews、裝置資訊、push subscriptions。開啟 WAL mode + busy_timeout，讓多個 runner 可以並行讀寫。&lt;/p>
&lt;p>&lt;strong>/workspace&lt;/strong> — Docker volume mount，把 host 的專案目錄映射進容器。手機上 approve 的改動，回到電腦 &lt;code>git pull&lt;/code> 就能同步。&lt;/p>
&lt;h2 id="技術選型">技術選型&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>選擇&lt;/th>
&lt;th>原因&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Claude Agent SDK&lt;/td>
&lt;td>內建 Read/Edit/Write/Bash 等 tools，不用自己定義 tool schema，SDK 自動處理多輪 tool call&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>SQLite (better-sqlite3)&lt;/td>
&lt;td>單容器部署、同步 API、零維運，WAL mode 支援並行讀寫&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Tailscale&lt;/td>
&lt;td>零信任網路，WireGuard 加密隧道，免設防火牆和 port forwarding&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PWA (vite-plugin-pwa)&lt;/td>
&lt;td>免上架 App Store，跨平台，離線快取，支援 Push Notification&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Zustand&lt;/td>
&lt;td>輕量 state management，per-workspace 狀態分割，boilerplate 最少&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>React 19 + Vite 6&lt;/td>
&lt;td>生態最大、AI 產出品質最穩定、開發體驗好&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="使用流程">使用流程&lt;/h2>
&lt;p>實際場景：通勤 10 分鐘完成一個 feature。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">1. 手機打開 PWA → 選 workspace（左右滑動切換專案）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 語音或文字輸入需求：「幫 auth service 加上 rate limiting middleware」
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. AI 讀取 codebase、理解架構、寫程式碼（串流顯示進度）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 收到 Push 通知 → 切到 Diff tab
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">5. 逐檔審查 → 批准 → Commit + Push
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">6. 回到電腦 git pull，改動已同步
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>多 Workspace 的好處是可以同時丟幾個任務給不同專案的 AI，等通勤到站時一次 review。&lt;/p>
&lt;h2 id="部署方式">部署方式&lt;/h2>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># docker-compose.yml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">vibe-remote&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">context&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">dockerfile&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">server/Dockerfile&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;8080:8080&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">${WORKSPACE_HOST_PATH:-/home/ubuntu}:/workspace:rw&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">vibe-data:/app/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">${HOME}/.claude:/home/node/.claude:rw&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">NODE_ENV=production&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">PORT=8080&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">JWT_SECRET=${JWT_SECRET}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">CLAUDE_PERMISSION_MODE=bypassPermissions&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">WORKSPACE_HOST_PATH=${WORKSPACE_HOST_PATH:-/home/ubuntu}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">WORKSPACE_CONTAINER_PATH=/workspace&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">unless-stopped&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">vibe-data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>啟動步驟：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git clone https://github.com/astroicers/vibe-remote.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> vibe-remote
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cp .env.example .env
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 編輯 .env：填入 CLAUDE_CODE_OAUTH_TOKEN 和 WORKSPACE_HOST_PATH&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker compose up -d
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Claude 認證有兩種方式：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>OAuth Token&lt;/strong>（推薦）：&lt;code>claude setup-token&lt;/code> 取得，使用 Max/Pro 訂閱額度&lt;/li>
&lt;li>&lt;strong>API Key&lt;/strong>：從 &lt;a class="link" href="https://console.anthropic.com" target="_blank" rel="noopener"
>console.anthropic.com&lt;/a> 取得，按用量付費&lt;/li>
&lt;/ul>
&lt;p>手機連線：確保手機和 server 都在 Tailscale 網路中，瀏覽器打開 &lt;code>http://&amp;lt;TAILSCALE_IP&amp;gt;:8080&lt;/code>，Safari/Chrome「加到主畫面」安裝 PWA。&lt;/p>
&lt;h2 id="開發心得">開發心得&lt;/h2>
&lt;h3 id="token-最佳化">Token 最佳化&lt;/h3>
&lt;p>AI 對話最大的成本是 token。Vibe Remote 做了幾層截斷：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>歷史訊息&lt;/strong>：最多保留最近 5 則，每則上限 2000 字元&lt;/li>
&lt;li>&lt;strong>Context files&lt;/strong>：每個檔案上限 10,000 字元，超過 1MB 直接跳過&lt;/li>
&lt;li>&lt;strong>File tree&lt;/strong>：只展開到第 2 層，package.json 只保留套件名稱不保留版本號&lt;/li>
&lt;li>&lt;strong>Recent commits&lt;/strong>：只帶最近 3 筆&lt;/li>
&lt;/ul>
&lt;p>這些限制讓單次 AI 呼叫的 context 維持在合理範圍，又不會漏掉關鍵資訊。&lt;/p>
&lt;h3 id="多-workspace-狀態隔離">多 Workspace 狀態隔離&lt;/h3>
&lt;p>所有 Zustand store 的 action 都接收明確的 &lt;code>workspaceId&lt;/code> 參數，不依賴隱式全域狀態。Server 端用 &lt;code>Map&amp;lt;string, RunnerState&amp;gt;&lt;/code> 以 &lt;code>workspaceId:conversationId&lt;/code> 為 key 管理 runner，確保同一個 conversation 不會有兩個並行的 AI 查詢。&lt;/p>
&lt;p>WebSocket 事件也帶 &lt;code>workspaceId&lt;/code>，client 端依此路由到正確的 workspace partition。非當前 workspace 的完成通知會增加 unread badge + toast，不會干擾正在看的內容。&lt;/p>
&lt;h3 id="為什麼沒用-session-resume">為什麼沒用 Session Resume&lt;/h3>
&lt;p>Claude Agent SDK 支援 &lt;code>persistSession&lt;/code> / &lt;code>resumeSessionId&lt;/code> 跨請求復用 conversation context，理論上可以省下大量重複的 context token。但在 Docker 環境中，session files 跨 spawned process 不穩定，最後改成 inline history（把歷史訊息截斷後直接附加到 prompt）。&lt;/p>
&lt;p>等 SDK 的 session resume 在容器環境更穩定後會重新啟用。&lt;/p>
&lt;h2 id="相關連結">相關連結&lt;/h2>
&lt;ul>
&lt;li>&lt;a class="link" href="https://github.com/astroicers/vibe-remote" target="_blank" rel="noopener"
>GitHub: astroicers/vibe-remote&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://docs.anthropic.com/en/docs/claude-code/sdk" target="_blank" rel="noopener"
>Claude Agent SDK 文件&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>