[{"data":1,"prerenderedAt":3260},["ShallowReactive",2],{"blog-ptt-flex-two-pies":3,"all-blog-posts":290},{"id":4,"title":5,"body":6,"cover":275,"date":276,"description":277,"extension":278,"meta":279,"navigation":280,"path":281,"seo":282,"stem":283,"tags":284,"__hash__":289},"blog\u002Fblog\u002Fptt-flex-two-pies.md","兩塊餅都崩了：PTT Flex 的踩雷紀實",{"type":7,"value":8,"toc":266},"minimark",[9,13,25,28,31,34,38,41,48,51,54,57,60,64,71,74,77,94,97,100,104,116,121,128,131,134,137,140,143,146,149,156,179,182,188,191,198,204,207,210,213,216,219,222,225,228,234,237,240,243,246,252,255,263],[10,11,12],"p",{},"那段時間我畫了兩塊餅",[10,14,15,16,20,21,24],{},"一塊是想做完整的瀏覽器版 PTT 客戶端\nNuxt 3 配 Vue 配一個自己刻的 ",[17,18,19],"code",{},"\u003CPttBoard>"," 元件\n直連 ",[17,22,23],{},"wss:\u002F\u002Fws.ptt.cc"," 那種",[10,26,27],{},"另一塊是把 client 做成 npm package 上架\nlogin 自動化 文章列表 推文 parsing\n連 InteractionEngine 狀態機都從 PyPtt port 過來",[10,29,30],{},"兩個月後\n兩塊都崩了",[10,32,33],{},"崩的方式還不一樣\n崩的原因卻是同一個 Orz",[35,36,37],"h2",{"id":37},"先講沒崩的部分",[10,39,40],{},"不然這篇看起來只剩抱怨",[10,42,43,44,47],{},"PTT Flex 真正的主角是字型\n一個從 Sarasa Gothic Mono TC 改造出來的 Variable Font\n帶一個叫 ",[17,45,46],{},"wdth"," 的軸 從 70 一路拉到 100",[10,49,50],{},"意思是同一個字級下\n我可以叫瀏覽器把字「橫向壓窄」\n不縮字級也能在手機上塞下完整 80 欄的 BBS 版面",[10,52,53],{},"這塊 Phase 1 加 2 早就做完了\nfontTools 編譯 brotli 壓縮 BIG5 subset 框線字元手調\nWOFF2 不到 500KB\n測試覆蓋 91%",[10,55,56],{},"從頭到尾沒出過一次包",[10,58,59],{},"崩的是其他地方",[35,61,63],{"id":62},"第一塊餅完整版-ptt-api","第一塊餅：完整版 PTT API",[10,65,66,67,70],{},"我想做一個正式的 npm package — ",[17,68,69],{},"@ptt-flex\u002Fclient","\n完整的 PTT structured API\n誰要做 PTT 第三方應用都能拉去用",[10,72,73],{},"完整是什麼意思\n就是 login 自動化 看板列表 文章內容 推文 parsing 全包\n連 PyPtt 那邊的 InteractionEngine 狀態機都認真想 port 過來",[10,75,76],{},"做下去才知道\nPTT 的 TUI 不是隨便對 prompt 就好的東西\n換頁時的 ANSI escape 看起來像有規則 細看又破例\n推文夾在文章內容裡 切片的邊界一換行就漂走\n狀態機要嘛寫得超複雜 要嘛漏掉一半 case",[10,78,79,80,83,84,87,88,87,91],{},"撐到 v1.1.0 我打 tag 把它整個收進 ",[17,81,82],{},"archive\u002Fstructured-api"," 分支\n主線砍到只剩三個方法\n",[17,85,86],{},"connect"," \u002F ",[17,89,90],{},"send",[17,92,93],{},"onRawData",[10,95,96],{},"從「PTT API 套件」縮成「一個會 decode Big5-UAO 的 WebSocket connector」",[10,98,99],{},"第一塊餅\n縮了一半多",[35,101,103],{"id":102},"第二塊餅瀏覽器版-ptt-客戶端","第二塊餅：瀏覽器版 PTT 客戶端",[10,105,106,107,109,110,112,115],{},"想法很美\nNuxt 3 加 Vue 3 加一個自己刻的 ",[17,108,19],{}," 元件\nResizeObserver 監聽容器寬度即時算 ",[17,111,46],{},[17,113,114],{},"font-variation-settings"," 一拉\n畫面上 80 欄字一邊縮窄一邊保持原本字級\n打開瀏覽器就能用 PTT\n而且字壓得超漂亮",[10,117,118,119],{},"這塊最關鍵的假設是\n瀏覽器要能直連 ",[17,120,23],{},[10,122,123,124,127],{},"第一輪 Spike 我用 Node 配 ",[17,125,126],{},"ptt-client"," v0.9.0\n登入 跑得過\n熱門看板 128 個全拉到\n文章列表 標題作者日期推文數都吐出來\n連文章內容雖然有點怪但也讀得到大部分",[10,129,130],{},"我看完結論寫了一句「套件可用」就把這個 Spike 收掉",[10,132,133],{},"覺得這部分穩了",[10,135,136],{},"兩個月後 我才知道這句結論寫得多隨便",[35,138,139],{"id":139},"撞牆與實證",[10,141,142],{},"事情是做 web 才爆出來",[10,144,145],{},"我把 client 接上 Nuxt 跑起來\nWebSocket 一直 1006\n連 client 都還沒登入就斷",[10,147,148],{},"換瀏覽器 換網路 關防毒 隱身模式 通通一樣",[10,150,151,152,155],{},"最後寫了一份 5 行的 HTML\n就一個 ",[17,153,154],{},"new WebSocket(\"wss:\u002F\u002Fws.ptt.cc\u002Fbbs\")"," 加四個 lifecycle handler",[10,157,158,159,162,163,166,167,170,171,174,175,178],{},"A 組 從 ",[17,160,161],{},"localhost:8080"," 開 → ",[17,164,165],{},"ERROR"," → ",[17,168,169],{},"CLOSE code=1006","\nB 組 直接到 ",[17,172,173],{},"https:\u002F\u002Fterm.ptt.cc"," 開 DevTools console 貼同樣那行 → ",[17,176,177],{},"OPEN"," → 收 1024 bytes 的 PTT splash 漂亮地飛進來",[10,180,181],{},"唯一變項是頁面 origin",[10,183,184,185,187],{},"PTT 在 WebSocket handshake 用 Origin header 白名單\n只放行 ",[17,186,173],{},"\n其他來源 TCP RST 直接打掉",[10,189,190],{},"而 Origin 在 W3C 規範裡是 forbidden header\n瀏覽器 JS 沒辦法改",[10,192,193,194,197],{},"最賭爛的是\nclient 自己的 ",[17,195,196],{},"PttClient.ts"," 註解早就寫白",[199,200,201],"blockquote",{},[10,202,203],{},"Use in Node.js \u002F testing to inject a WebSocket implementation that can set the Origin header",[10,205,206],{},"只有 Node 能偽造 Origin\n瀏覽器不行",[10,208,209],{},"這段註解在 repo 裡躺了兩個月\n我看不懂",[10,211,212],{},"因為我的 Spike 是 Node 跑的",[35,214,215],{"id":215},"收尾",[10,217,218],{},"兩塊餅崩的方式不太一樣",[10,220,221],{},"client 是內部複雜度頂不住 自己縮回 thin connector\nweb 是外部限制硬擋 瀏覽器能不能連 wss 這件事\n從第一天就被 Origin 鎖死了\n我只是兩個月後才知道",[10,223,224],{},"但崩的根本原因是同一個",[10,226,227],{},"餅可以畫大\n餅底下最危險的那塊磚要先用最便宜的實驗敲過",[10,229,230,231,233],{},"我那兩個月的工\n其實只需要 10 分鐘\n打開瀏覽器 DevTools 貼一行 ",[17,232,154],{},"\n就能省掉",[10,235,236],{},"這個磚我兩個月後才敲",[10,238,239],{},"下次別這樣 Orz",[241,242],"hr",{},[10,244,245],{},"不過字型沒崩",[10,247,248,249,251],{},"主線最後 pivot 成一個純靜態 demo\n模擬 PTT splash 畫面 直接秀 ",[17,250,46],{}," 軸即時壓縮字寬的能力",[10,253,254],{},"拉滑桿可以玩\n直接拖瀏覽器寬度看字怎麼變窄也行",[10,256,257],{},[258,259,260],"a",{"href":260,"rel":261},"https:\u002F\u002Fharry18456.github.io\u002Fptt-flex\u002F",[262],"nofollow",[10,264,265],{},"兩塊餅崩了\n這塊磚是好的",{"title":267,"searchDepth":268,"depth":268,"links":269},"",2,[270,271,272,273,274],{"id":37,"depth":268,"text":37},{"id":62,"depth":268,"text":63},{"id":102,"depth":268,"text":103},{"id":139,"depth":268,"text":139},{"id":215,"depth":268,"text":215},null,"2026-05-10T14:00:00.000Z","PTT Flex 專案兩塊原本畫好的餅怎麼崩的，又是怎麼從崩餅裡保住一塊磚。","md",{},true,"\u002Fblog\u002Fptt-flex-two-pies",{"title":5,"description":277},"blog\u002Fptt-flex-two-pies",[285,286,287,288],"開發日記","PTT","Variable Font","WebSocket","ub0yeigZN4F9avkIfKjSSIWBnp-QoTYVhFh588tYLt4",[291,463,771,1167,1967,2909,3035,3167],{"id":4,"title":5,"body":292,"cover":275,"date":276,"description":277,"extension":278,"meta":460,"navigation":280,"path":281,"seo":461,"stem":283,"tags":462,"__hash__":289},{"type":7,"value":293,"toc":453},[294,296,302,304,306,308,310,312,316,318,320,322,324,326,330,332,334,344,346,348,350,358,362,366,368,370,372,374,376,378,380,384,396,398,402,404,408,412,414,416,418,420,422,424,426,428,432,434,436,438,440,444,446,451],[10,295,12],{},[10,297,15,298,20,300,24],{},[17,299,19],{},[17,301,23],{},[10,303,27],{},[10,305,30],{},[10,307,33],{},[35,309,37],{"id":37},[10,311,40],{},[10,313,43,314,47],{},[17,315,46],{},[10,317,50],{},[10,319,53],{},[10,321,56],{},[10,323,59],{},[35,325,63],{"id":62},[10,327,66,328,70],{},[17,329,69],{},[10,331,73],{},[10,333,76],{},[10,335,79,336,83,338,87,340,87,342],{},[17,337,82],{},[17,339,86],{},[17,341,90],{},[17,343,93],{},[10,345,96],{},[10,347,99],{},[35,349,103],{"id":102},[10,351,106,352,109,354,356,115],{},[17,353,19],{},[17,355,46],{},[17,357,114],{},[10,359,118,360],{},[17,361,23],{},[10,363,123,364,127],{},[17,365,126],{},[10,367,130],{},[10,369,133],{},[10,371,136],{},[35,373,139],{"id":139},[10,375,142],{},[10,377,145],{},[10,379,148],{},[10,381,151,382,155],{},[17,383,154],{},[10,385,158,386,162,388,166,390,170,392,174,394,178],{},[17,387,161],{},[17,389,165],{},[17,391,169],{},[17,393,173],{},[17,395,177],{},[10,397,181],{},[10,399,184,400,187],{},[17,401,173],{},[10,403,190],{},[10,405,193,406,197],{},[17,407,196],{},[199,409,410],{},[10,411,203],{},[10,413,206],{},[10,415,209],{},[10,417,212],{},[35,419,215],{"id":215},[10,421,218],{},[10,423,221],{},[10,425,224],{},[10,427,227],{},[10,429,230,430,233],{},[17,431,154],{},[10,433,236],{},[10,435,239],{},[241,437],{},[10,439,245],{},[10,441,248,442,251],{},[17,443,46],{},[10,445,254],{},[10,447,448],{},[258,449,260],{"href":260,"rel":450},[262],[10,452,265],{},{"title":267,"searchDepth":268,"depth":268,"links":454},[455,456,457,458,459],{"id":37,"depth":268,"text":37},{"id":62,"depth":268,"text":63},{"id":102,"depth":268,"text":103},{"id":139,"depth":268,"text":139},{"id":215,"depth":268,"text":215},{},{"title":5,"description":277},[285,286,287,288],{"id":464,"title":465,"body":466,"cover":275,"date":760,"description":761,"extension":278,"meta":762,"navigation":280,"path":763,"seo":764,"stem":765,"tags":766,"__hash__":770},"blog\u002Fblog\u002Fai-concepts-explained.md","AI 概念大解密：從 LLM 到 Agent，再一次搞懂 MCP 與 Skills",{"type":7,"value":467,"toc":753},[468,479,482,486,498,501,504,526,530,537,548,553,556,570,574,577,585,591,620,624,627,630,712,718,722,725,739,746],[199,469,470],{},[10,471,472,473,478],{},"本文整理自 YouTube 影片：",[258,474,477],{"href":475,"rel":476},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=O9b8tLXCTYU",[262],"【AI科普】一期视频讲透所有唬人概念：LLM, Agent, RAG, MCP, Skills...","\n原作者透過清晰的脈絡，解釋了這些名詞的演進關係。",[10,480,481],{},"你是否也被 GenAI、LLM、Agent、RAG、MCP、Skills 這些不斷冒出的新名詞搞得暈頭轉向？\n這篇文章整理自相關影片內容，帶你從最核心的概念出發，一步步拆解這些術語的關聯，讓你一次看懂 AI 發展的脈絡。",[35,483,485],{"id":484},"_1-一切的起點大語言模型-llm-與-prompt","1. 一切的起點：大語言模型 (LLM) 與 Prompt",[10,487,488,489,493,494,497],{},"故事要從最基礎的 ",[490,491,492],"strong",{},"語言模型 (Language Model)"," 說起。\n早期的語言模型能力有限，但隨著參數量的指數級增長，模型在某個臨界點突然「湧現」出了智慧，為了區分，我們加了一個「大」字，這就是 ",[490,495,496],{},"大語言模型 (Large Language Model, LLM)","。",[10,499,500],{},"LLM 本質上在做什麼？其實它就是在玩「文字接龍」。它根據前面的內容，不斷預測下一個字是什麼。\n單純的文字接龍看起來並不像有智慧，但如果我們把場景設定為「一問一答」，雖然底層機制沒變，但人類感覺像是在對話了。",[10,502,503],{},"在這個過程中，產生了幾個關鍵概念：",[505,506,507,514,520],"ul",{},[508,509,510,513],"li",{},[490,511,512],{},"Prompt (提示詞)","：你對 LLM 說的話，也就是你給它的「上文」。",[508,515,516,519],{},[490,517,518],{},"Context (上下文\u002F語境)","：Prompt 中包含的背景資訊。為了讓 LLM 回答得更準確，我們通常會在 Prompt 裡塞入一些相關的背景知識，這部分就是 Context。",[508,521,522,525],{},[490,523,524],{},"Memory (記憶)","：LLM 本身不記得你昨天說過什麼。為了讓對話能延續，我們把「過往的對話紀錄」作為 Context 的一部分再次傳給 LLM，讓它「假裝」有了記憶。",[35,527,529],{"id":528},"_2-給大腦裝上雙手agent-代理人-與-rag","2. 給大腦裝上雙手：Agent (代理人) 與 RAG",[10,531,532,533,536],{},"LLM 很強，但它有個致命傷：",[490,534,535],{},"它是靜態的，且活在過去","。它無法上網獲取最新資訊，也無法執行真正的操作（比如寫檔案、寄信）。",[10,538,539,540,543,544,547],{},"為了解決這個問題，我們引入了 ",[490,541,542],{},"Agent (代理人)"," 的概念。\n這裡有個很不客氣但精準的定義：",[490,545,546],{},"所謂 Agent，就是整個系統中「不需要智慧」的程式碼部分","。\nAgent 是一個包裝在 LLM 外層的程式，負責處理邏輯、呼叫工具、聯網搜尋。",[505,549,550],{},[508,551,552],{},"你問問題 -> Agent 接收 -> Agent 決定是否需要上網 -> Agent 執行搜尋程式 -> Agent 把搜尋結果塞回 Context -> LLM 根據 Context 生成回答。",[10,554,555],{},"在這個階段，我們又發明了幾個詞：",[505,557,558,564],{},[508,559,560,563],{},[490,561,562],{},"RAG (Retrieval-Augmented Generation, 檢索增強生成)","：Agent 去向量資料庫 (Vector DB) 搜尋相關文件，把找到的內容「增強」到 Context 裡，讓 LLM 基於事實回答，減少幻覺。",[508,565,566,569],{},[490,567,568],{},"Web Search","：其實就是聯網版的 RAG，把搜尋結果餵給 LLM。",[35,571,573],{"id":572},"_3-溝通的標準化function-calling-與-mcp","3. 溝通的標準化：Function Calling 與 MCP",[10,575,576],{},"當 Agent 想要呼叫工具（比如計算機、搜尋、API）時，直接用自然語言跟程式溝通很不穩定。工程師希望 LLM 能輸出「結構化」的資料。",[505,578,579],{},[508,580,581,584],{},[490,582,583],{},"Function Calling","：這是 LLM 與 Agent 之間的一種「約定」。LLM 按照約定的格式（例如 JSON）輸出它想呼叫的函式和參數，Agent 讀懂 JSON 後去執行真正的程式碼。",[10,586,587,588,497],{},"但工具五花八門，Agent 要怎麼知道有哪些工具可用？如何呼叫？\n這就需要一個統一的介面標準，於是有了 ",[490,589,590],{},"MCP (Model Context Protocol)",[505,592,593],{},[508,594,595,598,599,602,603],{},[490,596,597],{},"MCP","：你可以把它想像成 AI 時代的 ",[490,600,601],{},"USB 介面協定","。\n",[505,604,605,611,617],{},[508,606,607,610],{},[490,608,609],{},"Server 端"," (工具提供者)：告訴 Agent \"我有這些工具，參數是這樣那樣\"。",[508,612,613,616],{},[490,614,615],{},"Client 端"," (Agent\u002FIDE)：透過 MCP 協定發現並呼叫這些工具。",[508,618,619],{},"這讓 Agent 可以像插拔 USB 一樣，輕鬆連接各種不同的數據源和工具，而不需要為每個工具寫特定的適配程式碼。",[35,621,623],{"id":622},"_4-自動化的演進從-workflow-到-skills","4. 自動化的演進：從 Workflow 到 Skills",[10,625,626],{},"Agent 能夠呼叫工具後，我們希望它能自動完成複雜任務（例如：讀取 PDF -> 翻譯 -> 存成 Word）。這就涉及到「流程控制」。",[10,628,629],{},"這裡經歷了三個階段的演進，從「剛性」到「柔性」：",[631,632,633,659,681],"ol",{},[508,634,635,638,639],{},[490,636,637],{},"LangChain (程式碼編排)","：",[505,640,641,647,653],{},[508,642,643,646],{},[490,644,645],{},"概念","：工程師用 Python\u002FJS 寫死整套流程 (If this then that)。",[508,648,649,652],{},[490,650,651],{},"優點","：極度穩定，可控。",[508,654,655,658],{},[490,656,657],{},"缺點","：開發門檻高，缺乏彈性。",[508,660,661,638,664],{},[490,662,663],{},"Workflow (工作流)",[505,665,666,671,676],{},[508,667,668,670],{},[490,669,645],{},"：低程式碼 (Low-code) 的拖拉介面 (如 Dify, Coze)。",[508,672,673,675],{},[490,674,651],{},"：上手容易，邏輯可視化。",[508,677,678,680],{},[490,679,657],{},"：本質還是寫死的流程，遇到意料之外的情況容易卡死。",[508,682,683,638,686],{},[490,684,685],{},"Skills (技能 \u002F Agent 自主)",[505,687,688,696,702,707],{},[508,689,690,692,693,497],{},[490,691,645],{},"：這是目前最接近「通用代理人」的型態。我們不再寫死流程，而是寫一份 ",[490,694,695],{},"「說明書」 (Skill.md) + 工具腳本",[508,697,698,701],{},[490,699,700],{},"運作方式","：Agent 閱讀說明書，自己決定在什麼時候、用什麼順序去呼叫這些工具。",[508,703,704,706],{},[490,705,651],{},"：極度靈活。Agent 可以根據當下情況動態調整策略。",[508,708,709,711],{},[490,710,657],{},"：不可控性增加（它可能會自己決定做一些你沒想到的事），且 Token 消耗較大。",[10,713,714,717],{},[490,715,716],{},"Sub-agent (子代理人)","：為了避免一個 Agent 的 Context 太長太亂，我們把複雜任務拆解，交給專門的 Sub-agent 處理（例如一個專門寫程式，一個專門寫文案），處理完只回報結果，這樣能保持主 Agent 的思緒清晰。",[35,719,721],{"id":720},"_5-未來展望super-agent-與便利性的勝利","5. 未來展望：Super Agent 與便利性的勝利",[10,723,724],{},"目前的 AI 發展就像當年的程式語言演進，從底層組合語言 (Prompt Tuning) 走向高階語言 (Agentic Workflow)。",[505,726,727,733],{},[508,728,729,732],{},[490,730,731],{},"LangChain"," 像組合語言，精確但繁瑣。",[508,734,735,738],{},[490,736,737],{},"Skills"," 像高階語言，讓 AI 自主決策。",[10,740,741,742,745],{},"未來，我們可能不再需要手動配置 MCP 或撰寫複雜的 Workflow。\n就像 SpringBoot 封裝了複雜的 Java 配置一樣，未來會出現 ",[490,743,744],{},"Super Agent","，它內建了各種常用 Skills 和 MCP 連接能力。用戶不需要懂什麼是 RAG、什麼是 Vector DB，只需要說「幫我處理這份文件」，Agent 就會自動呼叫內建的技能（閱讀、搜尋、總結、寫檔）來完成任務。",[10,747,748,749,752],{},"在這個趨勢下，",[490,750,751],{},"便利性 (Convenience)"," 將會戰勝一切。也就是說，誰能把 AI 封裝得越簡單、越無感，誰就是贏家。",{"title":267,"searchDepth":268,"depth":268,"links":754},[755,756,757,758,759],{"id":484,"depth":268,"text":485},{"id":528,"depth":268,"text":529},{"id":572,"depth":268,"text":573},{"id":622,"depth":268,"text":623},{"id":720,"depth":268,"text":721},"2026-02-19T00:40:00.000Z","整理自 YouTube 影片，從最核心的概念出發，一步步拆解 LLM, Agent, RAG, MCP, Skills 這些術語的關聯，讓你一次看懂 AI 發展的脈絡。",{},"\u002Fblog\u002Fai-concepts-explained",{"title":465,"description":761},"blog\u002Fai-concepts-explained",[767,768,769,597],"AI","LLM","Agent","1ifWR9YyIQVBShBokFHV2XC2sB25lpqN1-wIqsE9Qn8",{"id":772,"title":773,"body":774,"cover":275,"date":1159,"description":1160,"extension":278,"meta":1161,"navigation":280,"path":1162,"seo":1163,"stem":1164,"tags":1165,"__hash__":1166},"blog\u002Fblog\u002Fopenclaw-local-agent-experience.md","OpenClaw 初體驗：在 NVIDIA Jetson Thor 上跑 Local Agent 的踩坑紀錄",{"type":7,"value":775,"toc":1139},[776,785,788,792,803,806,810,818,844,847,850,861,890,893,895,898,901,905,919,925,929,940,943,947,957,964,968,981,985,992,996,1007,1011,1024,1028,1039,1046,1050,1057,1060,1062,1065,1068,1071,1097,1100,1107,1109,1114],[10,777,778,779,784],{},"最近嘗試了 ",[258,780,783],{"href":781,"rel":782},"https:\u002F\u002Fopenclaw.ai\u002F",[262],"OpenClaw","——一個開源的本地 AI Agent 框架，想體驗看看「24 小時自動幫你做事」的感覺。硬體選擇了 NVIDIA Jetson Thor 開發板，搭配 OpenAI 開源的 gpt-oss-120b 模型。過程中踩了不少坑，在這邊記錄一下。",[35,786,787],{"id":787},"工具簡介",[789,790,783],"h3",{"id":791},"openclaw",[10,793,794,798,799,802],{},[258,795,783],{"href":796,"rel":797},"https:\u002F\u002Fgithub.com\u002Fopenclaw\u002Fopenclaw",[262]," 是一個開源的 AI Agent 平台，前身為 Clawdbot \u002F Moltbot，由 Peter Steinberger 開發。它的定位不只是聊天機器人，而是一個能",[490,800,801],{},"自主執行任務","的 AI 代理——可以瀏覽網頁、操作檔案、執行終端指令、排程任務，甚至整合 Telegram、Signal、Discord 等通訊平台。",[10,804,805],{},"最吸引人的是它支援本地部署，所有資料都留在自己的機器上，也支持外部 LLM（Claude、GPT）和本地模型（Ollama、vLLM）。",[789,807,809],{"id":808},"nvidia-jetson-thor","NVIDIA Jetson Thor",[10,811,812,817],{},[258,813,816],{"href":814,"rel":815},"https:\u002F\u002Fwww.nvidia.com\u002Fen-us\u002Fautonomous-machines\u002Fembedded-systems\u002Fjetson-thor\u002F",[262],"NVIDIA Jetson Thor™"," 是 NVIDIA 最新一代的邊緣 AI 超級模組，核心規格：",[505,819,820,826,835,841],{},[508,821,822,825],{},[490,823,824],{},"GPU","：基於 NVIDIA Blackwell 架構，2560-core GPU + 第五代 Tensor Core",[508,827,828,831,832],{},[490,829,830],{},"AI 算力","：最高 ",[490,833,834],{},"2,070 FP4 TFLOPS",[508,836,837,840],{},[490,838,839],{},"比 Jetson AGX Orin 快 7.5 倍","，電源效率提升 3.5 倍",[508,842,843],{},"主打人形機器人、自動駕駛、智慧影像分析等場景",[10,845,846],{},"簡單來說，它是目前 Jetson 系列中最強的邊緣運算平台。",[789,848,849],{"id":849},"gpt-oss-120b",[10,851,852,856,857,860],{},[258,853,849],{"href":854,"rel":855},"https:\u002F\u002Fhuggingface.co\u002Fopenai\u002Fgpt-oss-120b",[262]," 是 OpenAI 釋出的開源 LLM，基於 ",[490,858,859],{},"Mixture-of-Experts（MoE）"," 架構：",[505,862,863,869,875,878,881],{},[508,864,865,868],{},[490,866,867],{},"總參數量","：117B（1,170 億）",[508,870,871,874],{},[490,872,873],{},"活躍參數量","：5.1B（51 億）",[508,876,877],{},"支援 MXFP4 量化，可在單張 H100 上運行",[508,879,880],{},"Apache 2.0 授權，支援 function calling、結構化輸出",[508,882,883,884,889],{},"可透過 ",[258,885,888],{"href":886,"rel":887},"https:\u002F\u002Fbuild.nvidia.com\u002F",[262],"NVIDIA NIM"," 快速部署",[10,891,892],{},"選擇它的原因很單純——開源、夠大、而且 Jetson Thor 的 Blackwell 架構原生支援 FP4。",[241,894],{},[35,896,897],{"id":897},"踩坑紀錄",[10,899,900],{},"以下是實際運行過程中遇到的各種問題，依序記錄。",[789,902,904],{"id":903},"_1-jetson-thor-gpu-太新vllm-容器不認-fp4","1. Jetson Thor GPU 太新，vLLM 容器不認 FP4",[10,906,907,908,911,912,915,916,497],{},"Jetson Thor 使用 Blackwell 架構（",[17,909,910],{},"sm_110","），算是相當新的 GPU。我一開始使用 ",[17,913,914],{},"nvcr.io\u002Fnvidia\u002Fvllm:26.01-py3"," 這個 NVIDIA 官方容器來跑模型，結果發現",[490,917,918],{},"容器認不得這張 GPU，直接拒絕使用 FP4 模式運行",[10,920,921,922,497],{},"這是個版本支援的問題——早期的 vLLM 容器還沒加入對 Blackwell 架構（Thor）的完整支援，需要等更新版本的容器或自行編譯 vLLM 並設定 ",[17,923,924],{},"TORCH_CUDA_ARCH_LIST=\"11.0;11.1;12.0\"",[789,926,928],{"id":927},"_2-contextwindow-設太小會無聲無息地失敗","2. contextWindow 設太小會無聲無息地失敗",[10,930,931,932,935,936,939],{},"OpenClaw 在配置模型時有一個 ",[17,933,934],{},"contextWindow"," 參數。如果這個值設太小，",[490,937,938],{},"OpenClaw 不會在 UI 上告訴你原因，只是默默地跑不起來","。你必須自己去翻 log 才會發現是 context window 不足的問題，且在設定模型的流程上也不會設定該值。",[10,941,942],{},"這個 UX 上的小缺陷讓我花了不少時間 debug。",[789,944,946],{"id":945},"_3-模型設定必須填-apikey即使是本地模型","3. 模型設定必須填 apiKey，即使是本地模型",[10,948,949,950,956],{},"即使你用的是本地 vLLM，OpenClaw 的模型設定中 ",[490,951,952,955],{},[17,953,954],{},"apiKey"," 欄位是必填的","。如果留空，OpenClaw 自己會無法送出 request。",[10,958,959,960,963],{},"解法很簡單——隨便填一個假的就好，例如 ",[17,961,962],{},"sk-dummy","。但這個行為確實不直覺，本地模型根本不需要 key。",[789,965,967],{"id":966},"_4-透過-tailscale-遠端存取-dashboard-需要額外設定","4. 透過 Tailscale 遠端存取 Dashboard 需要額外設定",[10,969,970,971,976,977,980],{},"如果你像我一樣透過 ",[258,972,975],{"href":973,"rel":974},"https:\u002F\u002Ftailscale.com\u002F",[262],"Tailscale"," 來遠端存取 OpenClaw 的 Dashboard，記得在 ",[490,978,979],{},"OpenClaw 所在的裝置上允許來自 Tailscale 的連線","。不然你看得到設備在線，卻怎麼都連不到 Dashboard。",[789,982,984],{"id":983},"_5-telegram-整合需要在-openclaw-內完成配對","5. Telegram 整合需要在 OpenClaw 內完成配對",[10,986,987,988,991],{},"想用 Telegram 跟你的 Agent 互動？除了設定 bot token 之外，還需要",[490,989,990],{},"在 OpenClaw 的設定介面中完成配對（pairing）流程","。這一步在文件裡不太顯眼，容易被忽略。",[789,993,995],{"id":994},"_6-arm64-上的-snap-版-chrome-不支援-cdp","6. ARM64 上的 Snap 版 Chrome 不支援 CDP",[10,997,998,999,1002,1003,1006],{},"Jetson Thor 是 ARM64 架構，Linux 上預設的 Chrome 是透過 ",[490,1000,1001],{},"Snap"," 安裝的。問題是 Snap 版的 Chrome 在使用 ",[490,1004,1005],{},"CDP（Chrome DevTools Protocol）"," 控制時有諸多限制和問題，OpenClaw 要用瀏覽器自動化時完全跑不動。",[789,1008,1010],{"id":1009},"_7-docker-跑-browserless-會讓-cdp-連線中斷","7. Docker 跑 Browserless 會讓 CDP 連線中斷",[10,1012,1013,1014,1019,1020,1023],{},"既然系統的 Chrome 不行，我轉而嘗試用 Docker 跑 ",[258,1015,1018],{"href":1016,"rel":1017},"https:\u002F\u002Fwww.browserless.io\u002F",[262],"Browserless"," 來提供可被 CDP 控制的瀏覽器環境。結果發現操作流程中，例如第一步透過 CDP 開啟網頁後，",[490,1021,1022],{},"連線就會斷掉、網頁直接關閉","，導致後續的操作（如查看 tabs）自然也跟著失敗。整個瀏覽器自動化流程根本無法串接下去。",[789,1025,1027],{"id":1026},"_8-docker-chrome-可以跑但-openclaw-堅持用預設-profile","8. Docker Chrome 可以跑但 OpenClaw 堅持用預設 Profile",[10,1029,1030,1031,1034,1035,1038],{},"再試了 ",[17,1032,1033],{},"zenika\u002Falpine-chrome:with-puppeteer"," 這個 Docker image，倒是成功啟動了一般瀏覽器。但 OpenClaw 本身",[490,1036,1037],{},"會一直嘗試用預設的 Chrome profile 去操作","，而不是連接到 Docker 裡的瀏覽器實體。",[10,1040,1041,1042,1045],{},"設定 ",[17,1043,1044],{},"defaultProfile"," 也沒有效果。也有可能是模型能力不夠，在 Agent 的決策過程中無法正確判斷該走哪條路徑。",[789,1047,1049],{"id":1048},"_9-300b-以下的模型有強烈建議使用沙盒模式","9. 300B 以下的模型有強烈建議使用沙盒模式",[10,1051,1052,1053,1056],{},"OpenClaw 在偵測到使用的模型參數量低於 300B 時，會",[490,1054,1055],{},"跳出警告建議你開啟沙盒（sandbox）模式","運行。言下之意就是模型太小，它信不過模型的判斷能力，怕它做出危險操作。",[10,1058,1059],{},"gpt-oss-120b 的 117B 參數量自然觸發了這個警告。實際體驗下來，模型確實在複雜的 Agent 任務上表現得力不從心。",[241,1061],{},[35,1063,1064],{"id":1064},"結語",[10,1066,1067],{},"「一個 AI Agent 24 小時幫你服務」的概念確實很吸引人，但以目前的狀況來看，要在本地端順暢跑起來還有一段距離。",[10,1069,1070],{},"回顧這次的經歷，問題大致可以歸納為：",[505,1072,1073,1079,1085,1091],{},[508,1074,1075,1078],{},[490,1076,1077],{},"硬體相容性","：新架構的 GPU 需要等軟體跟上",[508,1080,1081,1084],{},[490,1082,1083],{},"軟體成熟度","：OpenClaw 的設定體驗還有許多可以改善的地方",[508,1086,1087,1090],{},[490,1088,1089],{},"模型能力","：Agent 任務對模型的推理能力要求很高，中小型模型力有未逮",[508,1092,1093,1096],{},[490,1094,1095],{},"瀏覽器整合","：ARM64 環境下的瀏覽器控制仍是個大坑",[10,1098,1099],{},"網路上看到能順暢運行 OpenClaw 的案例，基本上都是有砸錢下去的——用 Claude 或 GPT-4 等級的雲端模型。",[10,1101,1102,1103,1106],{},"我的結論是：",[490,1104,1105],{},"等過段時間 local agent 生態更成熟，或者有更新的技術突破再來嘗試看看吧 Orz"," 現階段，這類工具更適合搭配強大的雲端模型使用，純靠本地模型要達到可用的程度，還需要一些時間。",[241,1108],{},[10,1110,1111],{},[490,1112,1113],{},"相關連結：",[505,1115,1116,1122,1128,1133],{},[508,1117,1118],{},[258,1119,1121],{"href":781,"rel":1120},[262],"OpenClaw 官方網站",[508,1123,1124],{},[258,1125,1127],{"href":796,"rel":1126},[262],"OpenClaw GitHub",[508,1129,1130],{},[258,1131,809],{"href":814,"rel":1132},[262],[508,1134,1135],{},[258,1136,1138],{"href":854,"rel":1137},[262],"gpt-oss-120b（Hugging Face）",{"title":267,"searchDepth":268,"depth":268,"links":1140},[1141,1147,1158],{"id":787,"depth":268,"text":787,"children":1142},[1143,1145,1146],{"id":791,"depth":1144,"text":783},3,{"id":808,"depth":1144,"text":809},{"id":849,"depth":1144,"text":849},{"id":897,"depth":268,"text":897,"children":1148},[1149,1150,1151,1152,1153,1154,1155,1156,1157],{"id":903,"depth":1144,"text":904},{"id":927,"depth":1144,"text":928},{"id":945,"depth":1144,"text":946},{"id":966,"depth":1144,"text":967},{"id":983,"depth":1144,"text":984},{"id":994,"depth":1144,"text":995},{"id":1009,"depth":1144,"text":1010},{"id":1026,"depth":1144,"text":1027},{"id":1048,"depth":1144,"text":1049},{"id":1064,"depth":268,"text":1064},"2026-02-16T01:12:14.000Z","記錄在 NVIDIA Jetson Thor 上搭配 gpt-oss-120b 運行 OpenClaw 本地 AI Agent 的完整經歷，包含 FP4 相容性、瀏覽器控制、Telegram 整合等各種踩坑心得。",{},"\u002Fblog\u002Fopenclaw-local-agent-experience",{"title":773,"description":1160},"blog\u002Fopenclaw-local-agent-experience",[285,767,783],"Fk5JmXE3YxXZxzVpqyGIzYbHiiTzMB95ysf8gsNv27Y",{"id":1168,"title":1169,"body":1170,"cover":275,"date":1956,"description":1957,"extension":278,"meta":1958,"navigation":280,"path":1959,"seo":1960,"stem":1961,"tags":1962,"__hash__":1966},"blog\u002Fblog\u002Fvscode-tunnel-remote-development.md","VSCode Tunnel：不需要 SSH、不需要 VPN，打開瀏覽器就能遠端開發",{"type":7,"value":1171,"toc":1931},[1172,1175,1178,1181,1201,1204,1207,1210,1229,1232,1234,1238,1242,1248,1252,1255,1260,1301,1306,1332,1337,1349,1359,1363,1375,1382,1390,1397,1400,1406,1409,1415,1418,1469,1472,1493,1497,1503,1506,1523,1526,1548,1552,1563,1583,1586,1605,1609,1620,1623,1668,1672,1730,1734,1737,1741,1744,1750,1756,1759,1766,1770,1773,1807,1814,1821,1824,1827,1857,1860,1863,1889,1892,1895,1898,1900,1905,1927],[10,1173,1174],{},"今天試了一下 VSCode 的 Remote Tunnel 功能，體驗出乎意料地好，在這邊記錄一下心得跟完整的設定流程。",[35,1176,1177],{"id":1177},"為什麼我會注意到這個功能",[10,1179,1180],{},"工作上偶爾會遇到幾種尷尬的情境：",[505,1182,1183,1189,1195],{},[508,1184,1185,1188],{},[490,1186,1187],{},"公司內網環境","：機器在內網，SSH 要通過跳板機層層轉發，設定起來繁瑣又容易斷線",[508,1190,1191,1194],{},[490,1192,1193],{},"雲端環境的金鑰問題","：有些服務的金鑰或憑證只存放在 server 上，不方便也不應該拉到本機",[508,1196,1197,1200],{},[490,1198,1199],{},"臨時要改 code","：人在外面，手邊只有一台平板或借來的電腦，要開個 SSH client 還得先裝軟體",[10,1202,1203],{},"VSCode Tunnel 的運作方式恰好解決了這些問題——它不依賴 SSH，也不需要設定 VPN 或打開任何 port。只要遠端機器能連上網路，你就能從任何有瀏覽器的裝置連進去寫 code。",[35,1205,1206],{"id":1206},"原理簡介",[10,1208,1209],{},"VSCode Tunnel 的原理其實不複雜：",[631,1211,1212,1223,1226],{},[508,1213,1214,1215,1218,1219,1222],{},"遠端機器上執行 ",[17,1216,1217],{},"code tunnel","，它會啟動一個 ",[490,1220,1221],{},"VS Code Server"," 並透過 Microsoft 的 dev tunnels 服務建立一條加密通道",[508,1224,1225],{},"你用 GitHub 或 Microsoft 帳號登入做身份驗證",[508,1227,1228],{},"從任何地方——瀏覽器的 vscode.dev 或桌面版 VSCode——用同一個帳號連進去",[10,1230,1231],{},"整個過程不需要打開 port、不需要固定 IP、不需要 SSH key。",[241,1233],{},[35,1235,1237],{"id":1236},"步驟一取得-code-cli","步驟一：取得 Code CLI",[789,1239,1241],{"id":1240},"方法-a已安裝桌面版-vscode","方法 A：已安裝桌面版 VSCode",[10,1243,1244,1245,1247],{},"如果你的遠端機器已經裝了 VSCode Desktop，那 ",[17,1246,17],{}," CLI 已經內建，直接使用即可。",[789,1249,1251],{"id":1250},"方法-b獨立安裝-cli推薦用於-server","方法 B：獨立安裝 CLI（推薦用於 server）",[10,1253,1254],{},"大多數 server 環境不會安裝桌面版 VSCode，這時候可以只下載 CLI：",[10,1256,1257],{},[490,1258,1259],{},"Linux（x64）：",[1261,1262,1266],"pre",{"className":1263,"code":1264,"language":1265,"meta":267,"style":267},"language-bash shiki shiki-themes github-light github-dark","curl -Lk 'https:\u002F\u002Fcode.visualstudio.com\u002Fsha\u002Fdownload?build=stable&os=cli-linux-x64' --output vscode_cli.tar.gz\ntar -xf vscode_cli.tar.gz\n","bash",[17,1267,1268,1291],{"__ignoreMap":267},[1269,1270,1273,1277,1281,1285,1288],"span",{"class":1271,"line":1272},"line",1,[1269,1274,1276],{"class":1275},"sScJk","curl",[1269,1278,1280],{"class":1279},"sj4cs"," -Lk",[1269,1282,1284],{"class":1283},"sZZnC"," 'https:\u002F\u002Fcode.visualstudio.com\u002Fsha\u002Fdownload?build=stable&os=cli-linux-x64'",[1269,1286,1287],{"class":1279}," --output",[1269,1289,1290],{"class":1283}," vscode_cli.tar.gz\n",[1269,1292,1293,1296,1299],{"class":1271,"line":268},[1269,1294,1295],{"class":1275},"tar",[1269,1297,1298],{"class":1279}," -xf",[1269,1300,1290],{"class":1283},[10,1302,1303],{},[490,1304,1305],{},"Linux（ARM64）：",[1261,1307,1309],{"className":1263,"code":1308,"language":1265,"meta":267,"style":267},"curl -Lk 'https:\u002F\u002Fcode.visualstudio.com\u002Fsha\u002Fdownload?build=stable&os=cli-linux-arm64' --output vscode_cli.tar.gz\ntar -xf vscode_cli.tar.gz\n",[17,1310,1311,1324],{"__ignoreMap":267},[1269,1312,1313,1315,1317,1320,1322],{"class":1271,"line":1272},[1269,1314,1276],{"class":1275},[1269,1316,1280],{"class":1279},[1269,1318,1319],{"class":1283}," 'https:\u002F\u002Fcode.visualstudio.com\u002Fsha\u002Fdownload?build=stable&os=cli-linux-arm64'",[1269,1321,1287],{"class":1279},[1269,1323,1290],{"class":1283},[1269,1325,1326,1328,1330],{"class":1271,"line":268},[1269,1327,1295],{"class":1275},[1269,1329,1298],{"class":1279},[1269,1331,1290],{"class":1283},[10,1333,1334],{},[490,1335,1336],{},"Windows：",[10,1338,1339,1340,1345,1346,1348],{},"可以直接到 ",[258,1341,1344],{"href":1342,"rel":1343},"https:\u002F\u002Fcode.visualstudio.com\u002F#alt-downloads",[262],"VSCode 下載頁面"," 下載 CLI 版本，或者如果已安裝桌面版則直接使用 ",[17,1347,17],{}," 指令。",[10,1350,1351,1352,1354,1355,1358],{},"解壓後會得到一個 ",[17,1353,17],{}," 執行檔（獨立安裝的話，使用 ",[17,1356,1357],{},".\u002Fcode"," 來執行）。",[35,1360,1362],{"id":1361},"步驟二建立-tunnel","步驟二：建立 Tunnel",[1261,1364,1366],{"className":1263,"code":1365,"language":1265,"meta":267,"style":267},"code tunnel\n",[17,1367,1368],{"__ignoreMap":267},[1269,1369,1370,1372],{"class":1271,"line":1272},[1269,1371,17],{"class":1275},[1269,1373,1374],{"class":1283}," tunnel\n",[10,1376,1377,1378,1381],{},"第一次執行時，會出現授權提示。輸入 ",[17,1379,1380],{},"y"," 接受 server license 之後，終端機會顯示類似這樣的訊息：",[1261,1383,1388],{"className":1384,"code":1386,"language":1387},[1385],"language-text","* Visual Studio Code Server\n*\n* By using the software, you agree to\n* the Visual Studio Code Server License Terms (https:\u002F\u002Faka.ms\u002Fvscode-server-license)...\n*\n* To grant access to the server, please log into\n* https:\u002F\u002Fgithub.com\u002Flogin\u002Fdevice and use code XXXX-XXXX\n","text",[17,1389,1386],{"__ignoreMap":267},[10,1391,1392,1393,1396],{},"到瀏覽器打開 ",[17,1394,1395],{},"https:\u002F\u002Fgithub.com\u002Flogin\u002Fdevice","，輸入畫面上的 device code 完成 GitHub 帳號驗證。",[10,1398,1399],{},"驗證成功後，CLI 會提示你為這台機器命名：",[1261,1401,1404],{"className":1402,"code":1403,"language":1387},[1385],"What would you like to call this machine?\n> my-work-server\n",[17,1405,1403],{"__ignoreMap":267},[10,1407,1408],{},"接著 tunnel 就建立完成了，終端機會輸出一個 vscode.dev 的連結：",[1261,1410,1413],{"className":1411,"code":1412,"language":1387},[1385],"Open this link in your browser https:\u002F\u002Fvscode.dev\u002Ftunnel\u002Fmy-work-server\n",[17,1414,1412],{"__ignoreMap":267},[789,1416,1417],{"id":1417},"常用參數",[1419,1420,1421,1435],"table",{},[1422,1423,1424],"thead",{},[1425,1426,1427,1432],"tr",{},[1428,1429,1431],"th",{"align":1430},"left","參數",[1428,1433,1434],{"align":1430},"說明",[1436,1437,1438,1449,1459],"tbody",{},[1425,1439,1440,1446],{},[1441,1442,1443],"td",{"align":1430},[17,1444,1445],{},"--accept-server-license-terms",[1441,1447,1448],{"align":1430},"跳過 license 確認提示",[1425,1450,1451,1456],{},[1441,1452,1453],{"align":1430},[17,1454,1455],{},"--name \u003Cname>",[1441,1457,1458],{"align":1430},"直接指定機器名稱，跳過互動式命名",[1425,1460,1461,1466],{},[1441,1462,1463],{"align":1430},[17,1464,1465],{},"--no-sleep",[1441,1467,1468],{"align":1430},"防止遠端機器進入睡眠模式",[10,1470,1471],{},"一行搞定的寫法：",[1261,1473,1475],{"className":1263,"code":1474,"language":1265,"meta":267,"style":267},"code tunnel --accept-server-license-terms --name my-work-server\n",[17,1476,1477],{"__ignoreMap":267},[1269,1478,1479,1481,1484,1487,1490],{"class":1271,"line":1272},[1269,1480,17],{"class":1275},[1269,1482,1483],{"class":1283}," tunnel",[1269,1485,1486],{"class":1279}," --accept-server-license-terms",[1269,1488,1489],{"class":1279}," --name",[1269,1491,1492],{"class":1283}," my-work-server\n",[35,1494,1496],{"id":1495},"步驟三設定為系統服務開機自動啟動","步驟三：設定為系統服務（開機自動啟動）",[10,1498,1499,1500,1502],{},"手動執行 ",[17,1501,1217],{}," 的問題是——你關掉終端機或登出 session，tunnel 就斷了。設定為系統服務可以讓 tunnel 在背景持續運行，甚至開機就自動啟動。",[789,1504,1505],{"id":1505},"安裝服務",[1261,1507,1509],{"className":1263,"code":1508,"language":1265,"meta":267,"style":267},"code tunnel service install\n",[17,1510,1511],{"__ignoreMap":267},[1269,1512,1513,1515,1517,1520],{"class":1271,"line":1272},[1269,1514,17],{"class":1275},[1269,1516,1483],{"class":1283},[1269,1518,1519],{"class":1283}," service",[1269,1521,1522],{"class":1283}," install\n",[10,1524,1525],{},"同樣可以帶參數：",[1261,1527,1529],{"className":1263,"code":1528,"language":1265,"meta":267,"style":267},"code tunnel service install --accept-server-license-terms --name my-work-server\n",[17,1530,1531],{"__ignoreMap":267},[1269,1532,1533,1535,1537,1539,1542,1544,1546],{"class":1271,"line":1272},[1269,1534,17],{"class":1275},[1269,1536,1483],{"class":1283},[1269,1538,1519],{"class":1283},[1269,1540,1541],{"class":1283}," install",[1269,1543,1486],{"class":1279},[1269,1545,1489],{"class":1279},[1269,1547,1492],{"class":1283},[789,1549,1551],{"id":1550},"linux-補充設定","Linux 補充設定",[10,1553,1554,1555,1558,1559,1562],{},"在 Linux 上，tunnel 會以 ",[490,1556,1557],{},"systemd user service"," 的形式執行（服務名稱為 ",[17,1560,1561],{},"code-tunnel.service","）。為了讓你的使用者 session 在登出後仍然保持運作，需要額外執行：",[1261,1564,1566],{"className":1263,"code":1565,"language":1265,"meta":267,"style":267},"sudo loginctl enable-linger $USER\n",[17,1567,1568],{"__ignoreMap":267},[1269,1569,1570,1573,1576,1579],{"class":1271,"line":1272},[1269,1571,1572],{"class":1275},"sudo",[1269,1574,1575],{"class":1283}," loginctl",[1269,1577,1578],{"class":1283}," enable-linger",[1269,1580,1582],{"class":1581},"sVt8B"," $USER\n",[10,1584,1585],{},"驗證服務狀態：",[1261,1587,1589],{"className":1263,"code":1588,"language":1265,"meta":267,"style":267},"systemctl --user status code-tunnel.service\n",[17,1590,1591],{"__ignoreMap":267},[1269,1592,1593,1596,1599,1602],{"class":1271,"line":1272},[1269,1594,1595],{"class":1275},"systemctl",[1269,1597,1598],{"class":1279}," --user",[1269,1600,1601],{"class":1283}," status",[1269,1603,1604],{"class":1283}," code-tunnel.service\n",[789,1606,1608],{"id":1607},"windows-的行為差異","Windows 的行為差異",[10,1610,1611,1612,1615,1616,1619],{},"在 Windows 上，",[17,1613,1614],{},"service install"," 會建立一個",[490,1617,1618],{},"使用者登入時啟動","的項目（類似開機啟動項），而非像 Linux 那樣的系統級 daemon。這表示 Windows 上需要使用者有登入桌面的 session，tunnel 才會自動啟動。",[789,1621,1622],{"id":1622},"管理服務",[1261,1624,1626],{"className":1263,"code":1625,"language":1265,"meta":267,"style":267},"# 查看服務日誌\ncode tunnel service log\n\n# 解除安裝服務\ncode tunnel service uninstall\n",[17,1627,1628,1634,1645,1650,1656],{"__ignoreMap":267},[1269,1629,1630],{"class":1271,"line":1272},[1269,1631,1633],{"class":1632},"sJ8bj","# 查看服務日誌\n",[1269,1635,1636,1638,1640,1642],{"class":1271,"line":268},[1269,1637,17],{"class":1275},[1269,1639,1483],{"class":1283},[1269,1641,1519],{"class":1283},[1269,1643,1644],{"class":1283}," log\n",[1269,1646,1647],{"class":1271,"line":1144},[1269,1648,1649],{"emptyLinePlaceholder":280},"\n",[1269,1651,1653],{"class":1271,"line":1652},4,[1269,1654,1655],{"class":1632},"# 解除安裝服務\n",[1269,1657,1659,1661,1663,1665],{"class":1271,"line":1658},5,[1269,1660,17],{"class":1275},[1269,1662,1483],{"class":1283},[1269,1664,1519],{"class":1283},[1269,1666,1667],{"class":1283}," uninstall\n",[789,1669,1671],{"id":1670},"其他-tunnel-管理指令","其他 Tunnel 管理指令",[1261,1673,1675],{"className":1263,"code":1674,"language":1265,"meta":267,"style":267},"# 查看目前 tunnel 狀態\ncode tunnel status\n\n# 取消這台機器的 tunnel 註冊\ncode tunnel unregister\n\n# 查看所有可用指令\ncode tunnel --help\n",[17,1676,1677,1682,1691,1695,1700,1709,1714,1720],{"__ignoreMap":267},[1269,1678,1679],{"class":1271,"line":1272},[1269,1680,1681],{"class":1632},"# 查看目前 tunnel 狀態\n",[1269,1683,1684,1686,1688],{"class":1271,"line":268},[1269,1685,17],{"class":1275},[1269,1687,1483],{"class":1283},[1269,1689,1690],{"class":1283}," status\n",[1269,1692,1693],{"class":1271,"line":1144},[1269,1694,1649],{"emptyLinePlaceholder":280},[1269,1696,1697],{"class":1271,"line":1652},[1269,1698,1699],{"class":1632},"# 取消這台機器的 tunnel 註冊\n",[1269,1701,1702,1704,1706],{"class":1271,"line":1658},[1269,1703,17],{"class":1275},[1269,1705,1483],{"class":1283},[1269,1707,1708],{"class":1283}," unregister\n",[1269,1710,1712],{"class":1271,"line":1711},6,[1269,1713,1649],{"emptyLinePlaceholder":280},[1269,1715,1717],{"class":1271,"line":1716},7,[1269,1718,1719],{"class":1632},"# 查看所有可用指令\n",[1269,1721,1723,1725,1727],{"class":1271,"line":1722},8,[1269,1724,17],{"class":1275},[1269,1726,1483],{"class":1283},[1269,1728,1729],{"class":1279}," --help\n",[35,1731,1733],{"id":1732},"步驟四從另一端連線","步驟四：從另一端連線",[10,1735,1736],{},"Tunnel 建立好之後，連線方式有兩種：",[789,1738,1740],{"id":1739},"方法-a透過瀏覽器vscodedev","方法 A：透過瀏覽器（vscode.dev）",[10,1742,1743],{},"最簡單的方式——打開瀏覽器，直接訪問：",[1261,1745,1748],{"className":1746,"code":1747,"language":1387},[1385],"https:\u002F\u002Fvscode.dev\u002Ftunnel\u002F\u003C你的機器名稱>\n",[17,1749,1747],{"__ignoreMap":267},[10,1751,1752,1753,497],{},"例如 ",[17,1754,1755],{},"https:\u002F\u002Fvscode.dev\u002Ftunnel\u002Fmy-work-server",[10,1757,1758],{},"登入與 tunnel 端相同的 GitHub 帳號後，瀏覽器裡就會出現完整的 VSCode 介面，操作的都是遠端機器上的檔案。Terminal、extension、debug 都可以使用。",[10,1760,1761,1762,1765],{},"這個方式的好處是",[490,1763,1764],{},"零安裝","——只要有瀏覽器就行，平板、借來的電腦、甚至手機都能用。",[789,1767,1769],{"id":1768},"方法-b透過桌面版-vscode","方法 B：透過桌面版 VSCode",[10,1771,1772],{},"如果偏好桌面版的完整體驗：",[631,1774,1775,1784,1795,1801,1804],{},[508,1776,1777,1778,1783],{},"安裝 ",[258,1779,1782],{"href":1780,"rel":1781},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=ms-vscode.remote-server",[262],"Remote - Tunnels"," 擴充套件",[508,1785,1786,1787,1790,1791,1794],{},"打開 Command Palette（",[17,1788,1789],{},"F1"," 或 ",[17,1792,1793],{},"Ctrl+Shift+P","）",[508,1796,1797,1798],{},"搜尋並執行 ",[490,1799,1800],{},"Remote Tunnels: Connect to Tunnel...",[508,1802,1803],{},"登入相同的 GitHub 帳號",[508,1805,1806],{},"從清單中選擇你的機器",[10,1808,1809,1810,1813],{},"或者也可以透過左側的 ",[490,1811,1812],{},"Remote Explorer"," 面板，在 Tunnels 區塊中找到已註冊的機器並連線。",[10,1815,1816,1817,1820],{},"桌面版的好處是可以使用",[490,1818,1819],{},"完整的 extension 生態系","，包括一些不支援 web 版的 extension。",[35,1822,1823],{"id":1823},"使用限制",[10,1825,1826],{},"在使用之前，有幾個限制需要知道：",[505,1828,1829,1839,1845,1851],{},[508,1830,1831,1834,1835,1838],{},[490,1832,1833],{},"帳號上限 10 條 tunnel","：每個 GitHub\u002FMicrosoft 帳號最多可以註冊 10 個 tunnel，超過的話需要先 ",[17,1836,1837],{},"unregister"," 不用的機器",[508,1840,1841,1844],{},[490,1842,1843],{},"頻寬限制","：Microsoft dev tunnels 服務有流量限制，不適合傳輸大量檔案",[508,1846,1847,1850],{},[490,1848,1849],{},"單人使用","：每個 VS Code Server instance 設計上只供一位使用者使用",[508,1852,1853,1856],{},[490,1854,1855],{},"需要持續連線","：遠端機器必須能連上網路，tunnel 才能運作（所以建議設成 service）",[35,1858,1859],{"id":1859},"適合的使用場景",[10,1861,1862],{},"回到最開始的感想，我覺得 VSCode Tunnel 特別適合這些情境：",[505,1864,1865,1871,1877,1883],{},[508,1866,1867,1870],{},[490,1868,1869],{},"公司內網開發","：機器在防火牆後面，不方便設定 port forwarding 或 VPN，只要遠端機器能訪問外網就行",[508,1872,1873,1876],{},[490,1874,1875],{},"雲端 server","：SSH key、API key、憑證等敏感資訊留在 server 上，不用為了開發方便而拉到本機",[508,1878,1879,1882],{},[490,1880,1881],{},"臨時存取","：出門在外臨時需要改 code，只需要一個瀏覽器",[508,1884,1885,1888],{},[490,1886,1887],{},"效能較弱的本機","：編譯、建置等重活都在遠端跑，本機只負責顯示 UI",[10,1890,1891],{},"不適合的情境大概就是需要多人同時連線同一台機器、或者是網路極不穩定的環境（畢竟 tunnel 還是需要即時通訊的）。",[35,1893,1894],{"id":1894},"總結",[10,1896,1897],{},"VSCode Tunnel 算是一個被低估的功能——設定簡單（五分鐘搞定）、不需要額外的 infrastructure，就能得到近乎本機的開發體驗。如果你有類似的遠端開發需求，推薦試試看。",[241,1899],{},[10,1901,1902],{},[490,1903,1904],{},"參考資源：",[505,1906,1907,1914,1921],{},[508,1908,1909],{},[258,1910,1913],{"href":1911,"rel":1912},"https:\u002F\u002Fcode.visualstudio.com\u002Fdocs\u002Fremote\u002Ftunnels",[262],"VSCode Remote Tunnels 官方文件",[508,1915,1916],{},[258,1917,1920],{"href":1918,"rel":1919},"https:\u002F\u002Fcode.visualstudio.com\u002Fdocs\u002Fremote\u002Fvscode-server",[262],"VS Code Server 文件",[508,1922,1923],{},[258,1924,1926],{"href":1780,"rel":1925},[262],"Remote - Tunnels Extension",[1928,1929,1930],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":267,"searchDepth":268,"depth":268,"links":1932},[1933,1934,1935,1939,1942,1949,1953,1954,1955],{"id":1177,"depth":268,"text":1177},{"id":1206,"depth":268,"text":1206},{"id":1236,"depth":268,"text":1237,"children":1936},[1937,1938],{"id":1240,"depth":1144,"text":1241},{"id":1250,"depth":1144,"text":1251},{"id":1361,"depth":268,"text":1362,"children":1940},[1941],{"id":1417,"depth":1144,"text":1417},{"id":1495,"depth":268,"text":1496,"children":1943},[1944,1945,1946,1947,1948],{"id":1505,"depth":1144,"text":1505},{"id":1550,"depth":1144,"text":1551},{"id":1607,"depth":1144,"text":1608},{"id":1622,"depth":1144,"text":1622},{"id":1670,"depth":1144,"text":1671},{"id":1732,"depth":268,"text":1733,"children":1950},[1951,1952],{"id":1739,"depth":1144,"text":1740},{"id":1768,"depth":1144,"text":1769},{"id":1823,"depth":268,"text":1823},{"id":1859,"depth":268,"text":1859},{"id":1894,"depth":268,"text":1894},"2026-02-11T22:00:00.000Z","深入介紹 VSCode Remote Tunnels 功能，從 code CLI 建立 tunnel、設定為系統服務，到透過桌面版 VSCode 或 vscode.dev 連線，完整涵蓋遠端開發的實戰體驗。",{},"\u002Fblog\u002Fvscode-tunnel-remote-development",{"title":1169,"description":1957},"blog\u002Fvscode-tunnel-remote-development",[1963,1964,1965],"VSCode","工具介紹","遠端開發","e5ydJYsX-DcG0GMF4A6G9fkSj9NOUxktYNDhRA23cU4",{"id":1968,"title":1969,"body":1970,"cover":275,"date":2900,"description":2901,"extension":278,"meta":2902,"navigation":280,"path":2903,"seo":2904,"stem":2905,"tags":2906,"__hash__":2908},"blog\u002Fblog\u002Fuv-python-package-manager.md","uv：新一代 Python 套件管理神器",{"type":7,"value":1971,"toc":2866},[1972,1979,1986,1988,1995,2007,2010,2030,2033,2036,2063,2069,2086,2090,2094,2097,2101,2108,2112,2123,2127,2130,2133,2137,2191,2194,2208,2211,2216,2236,2243,2280,2284,2306,2309,2409,2413,2474,2477,2489,2492,2495,2510,2514,2518,2528,2556,2560,2623,2627,2630,2635,2638,2641,2645,2653,2657,2725,2728,2745,2751,2754,2832,2836,2850,2853,2856,2863],[10,1973,1974,1975,1978],{},"這篇文章整理自我準備在部門週會上分享的內容。會主動想介紹 uv 其實是被幾個工作中遇到的問題搞到感覺推廣一下也不錯：忘了 ",[17,1976,1977],{},"activate"," 結果把套件裝到 global、用 pip 建了太多虛擬環境把公司 OA 的 D 槽塞爆、還有 Ubuntu 24.04 直接禁止用系統 pip 安裝套件等等。",[10,1980,1981,1982,1985],{},"在準備分享的過程中，也意外釐清了一些之前的誤解——例如跨分區其實是用複製而非硬連結，所以省空間的效果會打折扣。同時也學到了一些新東西，像是 ",[17,1983,1984],{},"uv format","、Docker BuildKit 快取、PEP 723 腳本依賴宣告等，這些或許未來工作中都能派上用場。",[241,1987],{},[10,1989,1990,1991,1994],{},"uv 是由 ",[490,1992,1993],{},"Astral"," 公司推出的 Python 工具，也就是開發知名 Linter 工具 Ruff 的團隊。它的目標是成為 Python 生態系中最快、最可靠的套件管理解決方案。",[505,1996,1997],{},[508,1998,1999,638,2002],{},[490,2000,2001],{},"GitHub 專案位址",[258,2003,2006],{"href":2004,"rel":2005},"https:\u002F\u002Fgithub.com\u002Fastral-sh\u002Fuv",[262],"astral-sh\u002Fuv",[35,2008,2009],{"id":2009},"核心特性",[505,2011,2012,2018,2024],{},[508,2013,2014,2017],{},[490,2015,2016],{},"極致的速度","：使用 Rust 編寫，快取啟動情境下速度可比 pip 快上 10 到 100 倍",[508,2019,2020,2023],{},[490,2021,2022],{},"先進的快取機制","：採用內容定址 (Content-addressed) 技術，加速環境建立且不重複佔用空間",[508,2025,2026,2029],{},[490,2027,2028],{},"全能工具箱","：整合了 pip、pip-tools、virtualenv、pyenv、pipx 以及 Poetry 的核心功能",[35,2031,2032],{"id":2032},"安裝方式",[10,2034,2035],{},"uv 提供多種安裝路徑，建議使用獨立二進位檔安裝，以獲得完整功能：",[1261,2037,2039],{"className":1263,"code":2038,"language":1265,"meta":267,"style":267},"# macOS \u002F Linux\ncurl -LsSf https:\u002F\u002Fastral.sh\u002Fuv\u002Finstall.sh | sh\n",[17,2040,2041,2046],{"__ignoreMap":267},[1269,2042,2043],{"class":1271,"line":1272},[1269,2044,2045],{"class":1632},"# macOS \u002F Linux\n",[1269,2047,2048,2050,2053,2056,2060],{"class":1271,"line":268},[1269,2049,1276],{"class":1275},[1269,2051,2052],{"class":1279}," -LsSf",[1269,2054,2055],{"class":1283}," https:\u002F\u002Fastral.sh\u002Fuv\u002Finstall.sh",[1269,2057,2059],{"class":2058},"szBVR"," |",[1269,2061,2062],{"class":1275}," sh\n",[1261,2064,2067],{"className":2065,"code":2066,"language":1387},[1385],"# Windows\npowershell -ExecutionPolicy ByPass -c \"irm https:\u002F\u002Fastral.sh\u002Fuv\u002Finstall.ps1 | iex\"\n",[17,2068,2066],{"__ignoreMap":267},[199,2070,2071],{},[10,2072,2073,2074,2077,2078,2081,2082,2085],{},"透過 pip 安裝 (",[17,2075,2076],{},"pip install uv",") ",[490,2079,2080],{},"不建議","，因為該版本不支援 ",[17,2083,2084],{},"uv self update"," 一鍵更新功能。",[35,2087,2089],{"id":2088},"為什麼選擇-uv","為什麼選擇 uv？",[789,2091,2093],{"id":2092},"_1-解決系統環境限制-pep-668","1. 解決系統環境限制 (PEP 668)",[10,2095,2096],{},"在 Ubuntu 24.04 等現代 Linux 系統中，uv 透過自動管理獨立 Python 版本，完美繞過 pip 被阻擋的問題。",[789,2098,2100],{"id":2099},"_2-高度的向下相容性","2. 高度的向下相容性",[10,2102,2103,2104,2107],{},"內建 ",[17,2105,2106],{},"uv pip"," 介面，是傳統 pip 的「掉包替代品」，完全不需改變使用參數。",[789,2109,2111],{"id":2110},"_3-免手動啟動環境","3. 免手動啟動環境",[10,2113,2114,2115,2118,2119,2122],{},"傳統流程需要 ",[17,2116,2117],{},"source .venv\u002Fbin\u002Factivate","，而 uv 透過 ",[17,2120,2121],{},"uv run"," 直接在環境內執行指令，省去了啟動與切換環境的繁瑣步驟。",[789,2124,2126],{"id":2125},"_4-全生命週期專案管理","4. 全生命週期專案管理",[10,2128,2129],{},"uv 不僅管理依賴，連打包、發佈、甚至格式化程式碼都能一機搞定。",[35,2131,2132],{"id":2132},"核心功能概覽",[789,2134,2136],{"id":2135},"專案管理-取代-poetry-pdm","專案管理 (取代 Poetry \u002F PDM)",[1261,2138,2140],{"className":1263,"code":2139,"language":1265,"meta":267,"style":267},"# 初始化新專案\nuv init my-project\ncd my-project\n\n# 加入依賴並同步\nuv add requests\nuv sync\n",[17,2141,2142,2147,2158,2165,2169,2174,2184],{"__ignoreMap":267},[1269,2143,2144],{"class":1271,"line":1272},[1269,2145,2146],{"class":1632},"# 初始化新專案\n",[1269,2148,2149,2152,2155],{"class":1271,"line":268},[1269,2150,2151],{"class":1275},"uv",[1269,2153,2154],{"class":1283}," init",[1269,2156,2157],{"class":1283}," my-project\n",[1269,2159,2160,2163],{"class":1271,"line":1144},[1269,2161,2162],{"class":1279},"cd",[1269,2164,2157],{"class":1283},[1269,2166,2167],{"class":1271,"line":1652},[1269,2168,1649],{"emptyLinePlaceholder":280},[1269,2170,2171],{"class":1271,"line":1658},[1269,2172,2173],{"class":1632},"# 加入依賴並同步\n",[1269,2175,2176,2178,2181],{"class":1271,"line":1711},[1269,2177,2151],{"class":1275},[1269,2179,2180],{"class":1283}," add",[1269,2182,2183],{"class":1283}," requests\n",[1269,2185,2186,2188],{"class":1271,"line":1716},[1269,2187,2151],{"class":1275},[1269,2189,2190],{"class":1283}," sync\n",[10,2192,2193],{},"💡 uv init 會產生哪些檔案？",[505,2195,2196,2199,2202,2205],{},[508,2197,2198],{},"pyproject.toml：核心設定檔，包含專案資訊與依賴。",[508,2200,2201],{},".python-version：紀錄專案建議使用的 Python 版本。",[508,2203,2204],{},".gitignore：預設排除 .venv 等檔案。",[508,2206,2207],{},"uv.lock：精確紀錄所有套件版本，確保環境一致性（類似於 poetry.lock 或 - package-lock.json）。",[789,2209,2210],{"id":2210},"執行程式與自動環境管理",[10,2212,2213,2215],{},[17,2214,2121],{}," 會確保程式在正確的虛擬環境中執行，若環境尚未建立或依賴有變動，它會自動進行同步。",[1261,2217,2219],{"className":1263,"code":2218,"language":1265,"meta":267,"style":267},"# 在專案中執行\nuv run main.py\n",[17,2220,2221,2226],{"__ignoreMap":267},[1269,2222,2223],{"class":1271,"line":1272},[1269,2224,2225],{"class":1632},"# 在專案中執行\n",[1269,2227,2228,2230,2233],{"class":1271,"line":268},[1269,2229,2151],{"class":1275},[1269,2231,2232],{"class":1283}," run",[1269,2234,2235],{"class":1283}," main.py\n",[10,2237,2238,2239,2242],{},"如果你只有一個腳本，甚至不需要 ",[17,2240,2241],{},"uv init","。只要在腳本開頭加上 PEP 723 格式的依賴宣告：",[1261,2244,2248],{"className":2245,"code":2246,"language":2247,"meta":267,"style":267},"language-python shiki shiki-themes github-light github-dark","# \u002F\u002F\u002F script\n# dependencies = [\"requests\"]\n# \u002F\u002F\u002F\nimport requests\nprint(requests.get(\"https:\u002F\u002Fgoogle.com\").status_code)\n","python",[17,2249,2250,2258,2263,2270,2275],{"__ignoreMap":267},[1269,2251,2252,2255],{"class":1271,"line":1272},[1269,2253,2254],{},"#",[1269,2256,2257],{}," \u002F\u002F\u002F script\n",[1269,2259,2260],{"class":1271,"line":268},[1269,2261,2262],{},"# dependencies = [\"requests\"]\n",[1269,2264,2265,2267],{"class":1271,"line":1144},[1269,2266,2254],{},[1269,2268,2269],{}," \u002F\u002F\u002F\n",[1269,2271,2272],{"class":1271,"line":1652},[1269,2273,2274],{},"import requests\n",[1269,2276,2277],{"class":1271,"line":1658},[1269,2278,2279],{},"print(requests.get(\"https:\u002F\u002Fgoogle.com\").status_code)\n",[789,2281,2283],{"id":2282},"全域工具管理-uv-tool-uvx","全域工具管理 (uv tool & uvx)",[505,2285,2286,2292],{},[508,2287,2288,2291],{},[490,2289,2290],{},"臨時執行 (uvx \u002F uv tool run)","：類似 npx，適合只想「用一下就丟」的情境",[508,2293,2294,2297,2298],{},[490,2295,2296],{},"永久安裝 (uv tool install)","：將工具安裝在隔離的環境中，並自動加入系統 PATH\n",[505,2299,2300,2303],{},[508,2301,2302],{},"例如：uv tool install ruff ，執行：ruff --version",[508,2304,2305],{},"各工具會有各自的獨立環境，不會互相干擾。",[789,2307,2308],{"id":2308},"鎖定依賴與同步環境",[505,2310,2311,2317],{},[508,2312,2313,2316],{},[490,2314,2315],{},"uv lock","：計算專案的依賴關係並更新 uv.lock 檔案",[508,2318,2319,2322,2323],{},[490,2320,2321],{},"uv sync","：確保 .venv 虛擬環境與 uv.lock 完全同步\n",[505,2324,2325],{},[508,2326,2327,2329,2330],{},[17,2328,2321],{}," 常用參數比較：\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",[1419,2331,2332,2344],{},[1422,2333,2334],{},[1425,2335,2336,2338,2341],{},[1428,2337,1431],{"align":1430},[1428,2339,2340],{"align":1430},"行為",[1428,2342,2343],{"align":1430},"適用情境",[1436,2345,2346,2357,2370,2383,2396],{},[1425,2347,2348,2351,2354],{},[1441,2349,2350],{"align":1430},"(無參數)",[1441,2352,2353],{"align":1430},"若 lock 檔過期則重新解析，再同步環境",[1441,2355,2356],{"align":1430},"一般開發，開發機上常用",[1425,2358,2359,2364,2367],{},[1441,2360,2361],{"align":1430},[17,2362,2363],{},"--locked",[1441,2365,2366],{"align":1430},"驗證 lock 檔與 pyproject.toml 一致才安裝，不一致則報錯",[1441,2368,2369],{"align":1430},"CI 環境，確保沒人忘了更新 lock",[1425,2371,2372,2377,2380],{},[1441,2373,2374],{"align":1430},[17,2375,2376],{},"--frozen",[1441,2378,2379],{"align":1430},"跳過驗證，直接依照 lock 檔安裝",[1441,2381,2382],{"align":1430},"Docker build，追求最快速度",[1425,2384,2385,2390,2393],{},[1441,2386,2387],{"align":1430},[17,2388,2389],{},"--exclude-newer 2024-12-01T00:00:00Z",[1441,2391,2392],{"align":1430},"只解析該時間點前發佈的版本",[1441,2394,2395],{"align":1430},"重現舊版環境、除錯",[1425,2397,2398,2403,2406],{},[1441,2399,2400],{"align":1430},[17,2401,2402],{},"--resolution lowest",[1441,2404,2405],{"align":1430},"解析時選擇最低相容版本",[1441,2407,2408],{"align":1430},"測試套件的最低版本相容性",[789,2410,2412],{"id":2411},"python-版本管理","Python 版本管理",[1261,2414,2416],{"className":1263,"code":2415,"language":1265,"meta":267,"style":267},"# 查看可用版本\nuv python list\n\n# 安裝指定版本\nuv python install 3.12\n\n# 鎖定專案版本（建立 .python-version 檔案）\nuv python pin 3.11\n",[17,2417,2418,2423,2433,2437,2442,2453,2457,2462],{"__ignoreMap":267},[1269,2419,2420],{"class":1271,"line":1272},[1269,2421,2422],{"class":1632},"# 查看可用版本\n",[1269,2424,2425,2427,2430],{"class":1271,"line":268},[1269,2426,2151],{"class":1275},[1269,2428,2429],{"class":1283}," python",[1269,2431,2432],{"class":1283}," list\n",[1269,2434,2435],{"class":1271,"line":1144},[1269,2436,1649],{"emptyLinePlaceholder":280},[1269,2438,2439],{"class":1271,"line":1652},[1269,2440,2441],{"class":1632},"# 安裝指定版本\n",[1269,2443,2444,2446,2448,2450],{"class":1271,"line":1658},[1269,2445,2151],{"class":1275},[1269,2447,2429],{"class":1283},[1269,2449,1541],{"class":1283},[1269,2451,2452],{"class":1279}," 3.12\n",[1269,2454,2455],{"class":1271,"line":1711},[1269,2456,1649],{"emptyLinePlaceholder":280},[1269,2458,2459],{"class":1271,"line":1716},[1269,2460,2461],{"class":1632},"# 鎖定專案版本（建立 .python-version 檔案）\n",[1269,2463,2464,2466,2468,2471],{"class":1271,"line":1722},[1269,2465,2151],{"class":1275},[1269,2467,2429],{"class":1283},[1269,2469,2470],{"class":1283}," pin",[1269,2472,2473],{"class":1279}," 3.11\n",[789,2475,2476],{"id":2476},"檢視依賴樹",[1261,2478,2480],{"className":1263,"code":2479,"language":1265,"meta":267,"style":267},"uv tree\n",[17,2481,2482],{"__ignoreMap":267},[1269,2483,2484,2486],{"class":1271,"line":1272},[1269,2485,2151],{"class":1275},[1269,2487,2488],{"class":1283}," tree\n",[789,2490,2491],{"id":2491},"極速代碼格式化",[10,2493,2494],{},"基於 Ruff 的格式化功能，速度比傳統工具快上數十倍：",[1261,2496,2498],{"className":1263,"code":2497,"language":1265,"meta":267,"style":267},"uv format .\n",[17,2499,2500],{"__ignoreMap":267},[1269,2501,2502,2504,2507],{"class":1271,"line":1272},[1269,2503,2151],{"class":1275},[1269,2505,2506],{"class":1283}," format",[1269,2508,2509],{"class":1283}," .\n",[35,2511,2513],{"id":2512},"與傳統-pip-專案的整合","與傳統 Pip 專案的整合",[789,2515,2517],{"id":2516},"作為-pip-的替代品","作為 Pip 的替代品",[10,2519,2520,2521,2524,2525,2527],{},"不需要改變專案結構，只需將 ",[17,2522,2523],{},"pip"," 改為 ",[17,2526,2106],{}," 即可：",[1261,2529,2531],{"className":1263,"code":2530,"language":1265,"meta":267,"style":267},"uv pip install -r requirements.txt\nuv pip list\n",[17,2532,2533,2548],{"__ignoreMap":267},[1269,2534,2535,2537,2540,2542,2545],{"class":1271,"line":1272},[1269,2536,2151],{"class":1275},[1269,2538,2539],{"class":1283}," pip",[1269,2541,1541],{"class":1283},[1269,2543,2544],{"class":1279}," -r",[1269,2546,2547],{"class":1283}," requirements.txt\n",[1269,2549,2550,2552,2554],{"class":1271,"line":268},[1269,2551,2151],{"class":1275},[1269,2553,2539],{"class":1283},[1269,2555,2432],{"class":1283},[789,2557,2559],{"id":2558},"正式遷移至-uv-架構","正式遷移至 uv 架構",[1261,2561,2563],{"className":1263,"code":2562,"language":1265,"meta":267,"style":267},"# 初始化 uv 專案\nuv init\n\n# 匯入現有依賴\nuv add -r requirements.txt\n\n# 反向輸出 (相容性)\nuv export --format requirements-txt > requirements.txt\n",[17,2564,2565,2570,2577,2581,2586,2596,2600,2605],{"__ignoreMap":267},[1269,2566,2567],{"class":1271,"line":1272},[1269,2568,2569],{"class":1632},"# 初始化 uv 專案\n",[1269,2571,2572,2574],{"class":1271,"line":268},[1269,2573,2151],{"class":1275},[1269,2575,2576],{"class":1283}," init\n",[1269,2578,2579],{"class":1271,"line":1144},[1269,2580,1649],{"emptyLinePlaceholder":280},[1269,2582,2583],{"class":1271,"line":1652},[1269,2584,2585],{"class":1632},"# 匯入現有依賴\n",[1269,2587,2588,2590,2592,2594],{"class":1271,"line":1658},[1269,2589,2151],{"class":1275},[1269,2591,2180],{"class":1283},[1269,2593,2544],{"class":1279},[1269,2595,2547],{"class":1283},[1269,2597,2598],{"class":1271,"line":1711},[1269,2599,1649],{"emptyLinePlaceholder":280},[1269,2601,2602],{"class":1271,"line":1716},[1269,2603,2604],{"class":1632},"# 反向輸出 (相容性)\n",[1269,2606,2607,2609,2612,2615,2618,2621],{"class":1271,"line":1722},[1269,2608,2151],{"class":1275},[1269,2610,2611],{"class":1283}," export",[1269,2613,2614],{"class":1279}," --format",[1269,2616,2617],{"class":1283}," requirements-txt",[1269,2619,2620],{"class":2058}," >",[1269,2622,2547],{"class":1283},[35,2624,2626],{"id":2625},"uv-vs-poetry","uv vs. Poetry",[789,2628,2629],{"id":2629},"依賴同步更直接",[10,2631,2632,2634],{},[17,2633,2321],{}," 會自動偵測虛擬環境中多餘的套件並解安裝，確保開發環境永遠是最簡潔一致的狀態。",[789,2636,2637],{"id":2637},"解析速度的代差",[10,2639,2640],{},"uv 通常只需幾秒鐘即可完成數百個依賴的解析。",[789,2642,2644],{"id":2643},"poetry-仍有優勢之處","Poetry 仍有優勢之處",[505,2646,2647,2650],{},[508,2648,2649],{},"外掛生態系 (Plugin System)",[508,2651,2652],{},"成熟度與發佈流程",[35,2654,2656],{"id":2655},"docker-最佳實踐","Docker 最佳實踐",[1261,2658,2662],{"className":2659,"code":2660,"language":2661,"meta":267,"style":267},"language-dockerfile shiki shiki-themes github-light github-dark","FROM python:3.12-slim-bookworm\nCOPY --from=ghcr.io\u002Fastral-sh\u002Fuv:latest \u002Fuv \u002Fuvx \u002Fbin\u002F\n\nWORKDIR \u002Fapp\nCOPY pyproject.toml uv.lock .\u002F\n\n# 使用 Docker BuildKit 快取來加速安裝\nRUN --mount=type=cache,target=\u002Froot\u002F.cache\u002Fuv \\\n    uv sync --frozen --no-dev\n\nCOPY . .\nCMD [\"uv\", \"run\", \"hello.py\"]\n","dockerfile",[17,2663,2664,2669,2674,2678,2683,2688,2692,2697,2702,2708,2713,2719],{"__ignoreMap":267},[1269,2665,2666],{"class":1271,"line":1272},[1269,2667,2668],{},"FROM python:3.12-slim-bookworm\n",[1269,2670,2671],{"class":1271,"line":268},[1269,2672,2673],{},"COPY --from=ghcr.io\u002Fastral-sh\u002Fuv:latest \u002Fuv \u002Fuvx \u002Fbin\u002F\n",[1269,2675,2676],{"class":1271,"line":1144},[1269,2677,1649],{"emptyLinePlaceholder":280},[1269,2679,2680],{"class":1271,"line":1652},[1269,2681,2682],{},"WORKDIR \u002Fapp\n",[1269,2684,2685],{"class":1271,"line":1658},[1269,2686,2687],{},"COPY pyproject.toml uv.lock .\u002F\n",[1269,2689,2690],{"class":1271,"line":1711},[1269,2691,1649],{"emptyLinePlaceholder":280},[1269,2693,2694],{"class":1271,"line":1716},[1269,2695,2696],{},"# 使用 Docker BuildKit 快取來加速安裝\n",[1269,2698,2699],{"class":1271,"line":1722},[1269,2700,2701],{},"RUN --mount=type=cache,target=\u002Froot\u002F.cache\u002Fuv \\\n",[1269,2703,2705],{"class":1271,"line":2704},9,[1269,2706,2707],{},"    uv sync --frozen --no-dev\n",[1269,2709,2711],{"class":1271,"line":2710},10,[1269,2712,1649],{"emptyLinePlaceholder":280},[1269,2714,2716],{"class":1271,"line":2715},11,[1269,2717,2718],{},"COPY . .\n",[1269,2720,2722],{"class":1271,"line":2721},12,[1269,2723,2724],{},"CMD [\"uv\", \"run\", \"hello.py\"]\n",[35,2726,2727],{"id":2727},"快取管理",[1261,2729,2733],{"className":2730,"code":2731,"language":2732,"meta":267,"style":267},"language-powershell shiki shiki-themes github-light github-dark","# Windows\n$env:UV_CACHE_DIR = \"D:\\.uv_cache\"\n","powershell",[17,2734,2735,2740],{"__ignoreMap":267},[1269,2736,2737],{"class":1271,"line":1272},[1269,2738,2739],{},"# Windows\n",[1269,2741,2742],{"class":1271,"line":268},[1269,2743,2744],{},"$env:UV_CACHE_DIR = \"D:\\.uv_cache\"\n",[1261,2746,2749],{"className":2747,"code":2748,"language":1387},[1385],"# Linux\nexport UV_CACHE_DIR=\"\u002Fpath\u002Fto\u002Fcache\"\n",[17,2750,2748],{"__ignoreMap":267},[35,2752,2753],{"id":2753},"效能實測數據",[1419,2755,2756,2775],{},[1422,2757,2758],{},[1425,2759,2760,2763,2766,2769,2772],{},[1428,2761,2762],{"align":1430},"測試場景",[1428,2764,2765],{"align":1430},"Pip 平均耗時",[1428,2767,2768],{"align":1430},"uv 冷啟動",[1428,2770,2771],{"align":1430},"uv 熱啟動",[1428,2773,2774],{"align":1430},"安裝模式",[1436,2776,2777,2796,2815],{},[1425,2778,2779,2782,2785,2788,2793],{},[1441,2780,2781],{"align":1430},"Ubuntu 24.04 (ext4)",[1441,2783,2784],{"align":1430},"18.41s",[1441,2786,2787],{"align":1430},"19.47s",[1441,2789,2790],{"align":1430},[490,2791,2792],{},"0.18s",[1441,2794,2795],{"align":1430},"Hardlink",[1425,2797,2798,2801,2804,2807,2812],{},[1441,2799,2800],{"align":1430},"Win 10 (跨磁區 C → D)",[1441,2802,2803],{"align":1430},"95.40s",[1441,2805,2806],{"align":1430},"20.43s",[1441,2808,2809],{"align":1430},[490,2810,2811],{},"3.52s",[1441,2813,2814],{"align":1430},"Copy",[1425,2816,2817,2820,2822,2825,2830],{},[1441,2818,2819],{"align":1430},"Win 10 (同磁區 D → D)",[1441,2821,2803],{"align":1430},[1441,2823,2824],{"align":1430},"19.78s",[1441,2826,2827],{"align":1430},[490,2828,2829],{},"1.70s",[1441,2831,2795],{"align":1430},[789,2833,2835],{"id":2834},"空間佔用對比-5-個環境","空間佔用對比 (5 個環境)",[505,2837,2838,2844],{},[508,2839,2840,2843],{},[490,2841,2842],{},"傳統 Pip (Ubuntu)",": 1.5 GB",[508,2845,2846,2849],{},[490,2847,2848],{},"uv (同分區連結)",": 235 MB（5 個環境僅佔一份空間）",[35,2851,2852],{"id":2852},"結論",[10,2854,2855],{},"自 2025 年開始，uv 迅速崛起並成為目前 Python 生態系中最炙手可熱的管理工具。它不只是一次效能提升，更是對底層檔案系統特性的極致運用。",[10,2857,2858,2859,2862],{},"從 0.18 秒的安裝神蹟到 Windows 上 27~56 倍的速度飛躍，uv 徹底解決了開發者長久以來的速度與空間焦慮。只要正確設定 ",[17,2860,2861],{},"UV_CACHE_DIR","，這款神器將為你的開發流程帶來跨時代的效率變革。",[1928,2864,2865],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":267,"searchDepth":268,"depth":268,"links":2867},[2868,2869,2870,2876,2885,2889,2894,2895,2896,2899],{"id":2009,"depth":268,"text":2009},{"id":2032,"depth":268,"text":2032},{"id":2088,"depth":268,"text":2089,"children":2871},[2872,2873,2874,2875],{"id":2092,"depth":1144,"text":2093},{"id":2099,"depth":1144,"text":2100},{"id":2110,"depth":1144,"text":2111},{"id":2125,"depth":1144,"text":2126},{"id":2132,"depth":268,"text":2132,"children":2877},[2878,2879,2880,2881,2882,2883,2884],{"id":2135,"depth":1144,"text":2136},{"id":2210,"depth":1144,"text":2210},{"id":2282,"depth":1144,"text":2283},{"id":2308,"depth":1144,"text":2308},{"id":2411,"depth":1144,"text":2412},{"id":2476,"depth":1144,"text":2476},{"id":2491,"depth":1144,"text":2491},{"id":2512,"depth":268,"text":2513,"children":2886},[2887,2888],{"id":2516,"depth":1144,"text":2517},{"id":2558,"depth":1144,"text":2559},{"id":2625,"depth":268,"text":2626,"children":2890},[2891,2892,2893],{"id":2629,"depth":1144,"text":2629},{"id":2637,"depth":1144,"text":2637},{"id":2643,"depth":1144,"text":2644},{"id":2655,"depth":268,"text":2656},{"id":2727,"depth":268,"text":2727},{"id":2753,"depth":268,"text":2753,"children":2897},[2898],{"id":2834,"depth":1144,"text":2835},{"id":2852,"depth":268,"text":2852},"2026-01-28T22:30:00.000Z","深入介紹 Astral 團隊推出的 uv 工具，探討其如何以 Rust 實現的極致效能，重新定義 Python 套件管理的開發體驗。",{},"\u002Fblog\u002Fuv-python-package-manager",{"title":1969,"description":2901},"blog\u002Fuv-python-package-manager",[2907,1964],"Python","0pwQrJhIXTHtgDt6Ws0EtheTbTALZLElD03SXB6cCVE",{"id":2910,"title":2911,"body":2912,"cover":275,"date":3026,"description":3027,"extension":278,"meta":3028,"navigation":280,"path":3029,"seo":3030,"stem":3031,"tags":3032,"__hash__":3034},"blog\u002Fblog\u002Fbossy-radar-refactor.md","Bossy Radar：從 Vibe Coding 到工程化重構的旅程",{"type":7,"value":2913,"toc":3017},[2914,2917,2920,2923,2945,2948,2952,2955,2958,2961,2965,2968,2972,2975,2979,2982,2985,2991,2994,3014],[10,2915,2916],{},"Bossy Radar 是一個我構思已久的專案。",[35,2918,2919],{"id":2919},"專案初衷",[10,2921,2922],{},"這個專案的核心目標是整合兩個公開資料來源：",[631,2924,2925,2936],{},[508,2926,2927,2930,2931,2935],{},[490,2928,2929],{},"違反勞動法令事業單位（雇主）查詢系統"," (",[258,2932,2933],{"href":2933,"rel":2934},"https:\u002F\u002Fannouncement.mol.gov.tw\u002F",[262],")",[508,2937,2938,2930,2941,2935],{},[490,2939,2940],{},"公開資訊觀測站",[258,2942,2943],{"href":2943,"rel":2944},"https:\u002F\u002Fmopsov.twse.com.tw\u002F",[262],[10,2946,2947],{},"希望能將「勞工權益相關的揭露資訊」與「公司基本資料」進行對照，讓求職者、勞工甚至公司能更直觀地檢視各家公司的實際狀況，而非僅看表面的財務數據。",[35,2949,2951],{"id":2950},"vibe-coding-的嘗試與侷限","Vibe Coding 的嘗試與侷限",[10,2953,2954],{},"在專案初期，我嘗試採用 Vibe Coding 的模式，快速堆疊出一個看起來有模有樣的成果。這種開發方式在初期確實帶來了很高的滿足感，透過 AI 輔助迅速產出介面與功能雛形。",[10,2956,2957],{},"然而，隨著功能堆疊，隱藏的技術債也開始浮現，最終導致成品在細節處理上顯得支離破碎，甚至讓我打消了將其公開的念頭。",[10,2959,2960],{},"主要遇到的問題如下：",[789,2962,2964],{"id":2963},"_1-資料庫膨脹與低效","1. 資料庫膨脹與低效",[10,2966,2967],{},"為了快速求成，在資料處理上缺乏規劃。最終導致資料庫塞入了大量非必要的資料，體積膨脹至近 500MB，但其中約 95% 都是實際應用中用不到的冗餘數據。這不僅浪費儲存空間，也嚴重拖慢了查詢效能。",[789,2969,2971],{"id":2970},"_2-核心邏輯的不穩定","2. 核心邏輯的不穩定",[10,2973,2974],{},"專案的關鍵功能在於將「違法雇主」與「公開發行公司」進行名稱比對。在 Vibe Coding 階段，我過度依賴 AI 進行模糊比對，但結果卻時好時壞，缺乏一個穩定且可驗證的邏輯標準。這種反反覆覆的準確度，使得核心功能的可靠性大打折扣。",[789,2976,2978],{"id":2977},"_3-建置與部署的缺失","3. 建置與部署的缺失",[10,2980,2981],{},"在最後嘗試將專案匯出為靜態網站 (Static Web) 時，才發現由於前期缺乏模組化與依賴管理的規劃，導致匯出結果缺東缺西，無法正常運作。",[35,2983,2984],{"id":2984},"重構的決心",[10,2986,2987,2988],{},"面對這些無法忽視的問題，我做了一個艱難但必要的決定：",[490,2989,2990],{},"斷然捨棄過去一週的開發成果，從頭開始。",[10,2992,2993],{},"這一次，我打算回歸工程本質，放慢腳步。不追求表面的快速產出，而是紮實地處理每一個環節：",[505,2995,2996,3002,3008],{},[508,2997,2998,3001],{},[490,2999,3000],{},"精簡資料結構","：只儲存必要的欄位，優化資料庫設計。",[508,3003,3004,3007],{},[490,3005,3006],{},"演算法優化","：用更嚴謹的規則取代黑箱式的 AI 比對，確保資料整合的準確性。",[508,3009,3010,3013],{},[490,3011,3012],{},"自動化流程","：建立穩定的建置與部署流程。",[10,3015,3016],{},"與其追求一個搖搖欲墜的快速原型，不如一步一腳印，打磨出一個真正穩固且值得信賴的產品。這篇 Blog 也是為了記錄這個重新出發的起點。",{"title":267,"searchDepth":268,"depth":268,"links":3018},[3019,3020,3025],{"id":2919,"depth":268,"text":2919},{"id":2950,"depth":268,"text":2951,"children":3021},[3022,3023,3024],{"id":2963,"depth":1144,"text":2964},{"id":2970,"depth":1144,"text":2971},{"id":2977,"depth":1144,"text":2978},{"id":2984,"depth":268,"text":2984},"2026-01-27T20:15:20.000Z","紀錄 Bossy Radar 專案從快速原型到決定重構的過程，探討 Vibe Coding 的侷限與工程化實踐的重要性。",{},"\u002Fblog\u002Fbossy-radar-refactor",{"title":2911,"description":3027},"blog\u002Fbossy-radar-refactor",[285,3033],"重構","LDpCRs94O3y3vL6K8XmntCzwB0XQfrLUZPSxPfB4dnQ",{"id":3036,"title":3037,"body":3038,"cover":275,"date":3159,"description":3160,"extension":278,"meta":3161,"navigation":280,"path":3162,"seo":3163,"stem":3164,"tags":3165,"__hash__":3166},"blog\u002Fblog\u002Fupdownserver-vibe-coding.md","透過 Vibe Coding 改良 UpDownServer：重塑 Python HTTP Server 體驗",{"type":7,"value":3039,"toc":3154},[3040,3047,3060,3068,3082,3085,3088,3120,3124,3127,3141,3144,3151],[10,3041,3042,3043,3046],{},"在 2022 年時，我曾在部門內部介紹 ",[17,3044,3045],{},"python3 -m http.server"," 來快速在開發板（當時部門正在開發 ASUS Tinker Board 系列）與主機間進行檔案傳輸。這在需要臨時搬移檔案時非常方便，省去了安裝額外傳輸套件的麻煩。",[10,3048,3049,3050,3053,3054,3059],{},"最近在協助架設一台 Windows Demo 主機時，再次遇到了檔案傳輸的痛點。由於環境限制，往往需要拿著隨身碟在兩台電腦間來回奔波。\n回頭尋找 ",[17,3051,3052],{},"http.server"," 時，偶然發現了 ",[258,3055,3058],{"href":3056,"rel":3057},"https:\u002F\u002Fgithub.com\u002FDensaugeo\u002Fuploadserver",[262],"uploadserver"," 這個專案。",[10,3061,3062,3064,3065,3067],{},[17,3063,3058],{}," 實作了許多不錯的功能，例如認證機制，解決了原生 ",[17,3066,3052],{}," 只能下載不能上傳的問題。然而在實際使用上，仍有一些不便之處：",[631,3069,3070,3076],{},[508,3071,3072,3075],{},[490,3073,3074],{},"介面分離","：原生的下載畫面與上傳畫面是分開的，操作時需要切換。",[508,3077,3078,3081],{},[490,3079,3080],{},"缺乏拖曳支援","：不支援 Drag & Drop 檔案上傳，操作效率較低。",[10,3083,3084],{},"為了解決這些問題，我決定透過 Vibe Coding 的方式（即透過 AI 輔助編碼）進行改良。\n我 Fork 了該專案並基於實際需求，請 AI 協助實作了以下功能改進：",[35,3086,3087],{"id":3087},"功能改進重點",[631,3089,3090,3096,3102,3108,3114],{},[508,3091,3092,3095],{},[490,3093,3094],{},"頁面整合","\n將上傳與下載介面整合至單一頁面，不再需要切換視圖即可同時進行檔案管理。",[508,3097,3098,3101],{},[490,3099,3100],{},"支援 Drag & Drop 上傳","\n新增了現代化的檔案拖曳上傳功能，提升使用者體驗。",[508,3103,3104,3107],{},[490,3105,3106],{},"增強路徑與目錄支援","\n支援上傳檔案至任意路徑，並具備建立資料夾的功能，不再侷限於根目錄。",[508,3109,3110,3113],{},[490,3111,3112],{},"檔案資訊顯示","\n在列表中顯示所有檔案的實際大小，方便管理磁碟空間。",[508,3115,3116,3119],{},[490,3117,3118],{},"Timeout 機制","\n新增連線逾時強制斷線功能，避免閒置連線佔用資源。",[35,3121,3123],{"id":3122},"cicd-自動化與開源實踐","CI\u002FCD 自動化與開源實踐",[10,3125,3126],{},"除了功能改良，我也趁機玩了 GitHub Actions 的 CI\u002FCD 流程，讓專案維護更輕鬆：",[631,3128,3129,3135],{},[508,3130,3131,3134],{},[490,3132,3133],{},"自動化測試","\n設定了自動測試流程，每次 Push 或 Pull Request 都會自動執行單元測試，確保改動不會破壞既有功能。",[508,3136,3137,3140],{},[490,3138,3139],{},"自動編譯與發布","\n建立了 Release Workflow，只要 Push 新的 Tag，GitHub Actions 就會自動進行編譯，並將新版本上傳至 PyPI，大大簡化了發布流程。",[35,3142,3143],{"id":3143},"專案連結",[10,3145,3146,3147],{},"目前改良後的專案已開源，相關程式碼可參考：\n",[258,3148,3149],{"href":3149,"rel":3150},"https:\u002F\u002Fgithub.com\u002Fharry18456\u002Fupdownserver",[262],[10,3152,3153],{},"Vide coding 真的是越來越方便了，但雖然方便我仍不太敢用在嚴謹的專案上，畢竟 AI 有時會出錯。",{"title":267,"searchDepth":268,"depth":268,"links":3155},[3156,3157,3158],{"id":3087,"depth":268,"text":3087},{"id":3122,"depth":268,"text":3123},{"id":3143,"depth":268,"text":3143},"2026-01-24T19:34:14.000Z","分享如何透過 AI 輔助，將簡易的 Python HTTP Server 改造為支援拖曳上傳、多目錄管理與介面整合的實用工具。",{},"\u002Fblog\u002Fupdownserver-vibe-coding",{"title":3037,"description":3160},"blog\u002Fupdownserver-vibe-coding",[285,2907,767],"GITcW86HDRk9ZOdMKnF9N7wVU7Z7FAuaNUmp7jp2EA8",{"id":3168,"title":3169,"body":3170,"cover":275,"date":3251,"description":3252,"extension":278,"meta":3253,"navigation":280,"path":3254,"seo":3255,"stem":3256,"tags":3257,"__hash__":3259},"blog\u002Fblog\u002Fhello-world.md","歡迎來到橡皮擦部落格",{"type":7,"value":3171,"toc":3246},[3172,3176,3179,3182,3190,3220,3224,3229,3232,3235,3238,3240],[3173,3174,3175],"h1",{"id":3169},"歡迎來到橡皮擦部落格！",[10,3177,3178],{},"很高興你能來到這裡。這是我們的第一篇文章，也是一個新的開始。",[35,3180,3181],{"id":3181},"關於這個部落格",[10,3183,3184,3185,3189],{},"這個部落格是 ",[258,3186,3188],{"href":3187},"\u002F","橡皮擦"," 的一部分，但和工具區不同，這裡更像是一個自由的空間，可以隨心所欲地分享各種內容：",[505,3191,3192,3199,3206,3213],{},[508,3193,3194,3195,3198],{},"🛠️ ",[490,3196,3197],{},"技術分享","：開發心得、程式技巧、問題解決方案",[508,3200,3201,3202,3205],{},"💡 ",[490,3203,3204],{},"想法與靈感","：對產品、設計、生活的思考",[508,3207,3208,3209,3212],{},"📝 ",[490,3210,3211],{},"學習筆記","：記錄學習過程中的收穫",[508,3214,3215,3216,3219],{},"🎯 ",[490,3217,3218],{},"專案更新","：橡皮擦的最新動態",[35,3221,3223],{"id":3222},"為什麼叫橡皮擦","為什麼叫橡皮擦？",[199,3225,3226],{},[10,3227,3228],{},"不留痕跡，不需帳號。我們致力於打造最純粹的數位體驗。",[10,3230,3231],{},"這是我們的核心理念。在這個數據被過度收集的時代，我們相信有些工具可以更純粹、更簡單。",[35,3233,3234],{"id":3234},"開始探索吧",[10,3236,3237],{},"歡迎隨時回來看看，我們會不定期更新內容。",[241,3239],{},[10,3241,3242],{},[3243,3244,3245],"em",{},"感謝你的閱讀！",{"title":267,"searchDepth":268,"depth":268,"links":3247},[3248,3249,3250],{"id":3181,"depth":268,"text":3181},{"id":3222,"depth":268,"text":3223},{"id":3234,"depth":268,"text":3234},"2026-01-21T12:00:00.000Z","這是我們的第一篇文章，歡迎來到橡皮擦部落格！在這裡，我們會分享各種有趣的想法、技術心得和生活點滴。",{},"\u002Fblog\u002Fhello-world",{"title":3169,"description":3252},"blog\u002Fhello-world",[3258],"公告","NTq8oKZOCSk3BbmlMel5YPtEsRjV3C9_wDVUnbvRmOA",1780124055103]