Communitygithub.com

ASNNetworks/floh-skills

由 FLOH 提供的可攜式 SKILL.md 代理技能,適用於 Claude Code、Codex、Gemini CLI、Cursor 及其他代理客戶端。

floh-skills 是什麼?

此技能提供一個名為 `pharos` 的 CLI 工具,讓開發者能直接從命令列操作 Azure DevOps,無需瀏覽器登入。它透過環境變數進行驗證,並涵蓋了從查詢工作項目、建立/更新任務、管理附件與內嵌圖片,到操作維基頁面、處理服務鉤子與實作計劃等完整的 DevOps 工作流程。支援 Claude Code、Codex、Gemini CLI、Cursor 等多種 AI 代理平台,其核心價值在於將原本需要多次 API 呼叫的複雜操作(例如查詢工作項目後再逐一擷取詳細資料)簡化為單一指令,大幅提升效率。此技能的所有功能都已詳列於文件中,明確指示代理不應執行 `pharos --help`,因為這會浪費多次 API 呼叫。

相容平台✓Claude Code✓Codex CLI✓Cursor✓Gemini CLI
npx skills add ASNNetworks/floh-skills

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

預覽

來自技能 README

floh-skills preview
chart
diagram

說明文件

使用 pharos 操作 Azure DevOps

pharos 是一個 CLI 工具,讓您可以從 shell 操作 Azure DevOps,透過環境變數而非瀏覽器登入進行驗證。

下面列出了它能做的所有事情。請勿執行 pharos --help 來查詢 —— 那會耗費多次 API 呼叫,而本節已包含相同資訊。

whoami                         查詢 ADO_PAT 屬於誰。@Me 會解析成這個使用者
types                          查詢 --state 和 --type 會接受哪些值,依類型區分
query                          查詢哪些工作項目 —— 指派給您、在衝刺 (sprint) 中、
                               特定類型、仍為開啟狀態。回傳的是已充實的項目,而非 ID。
task <id>                      一個完整的工作項目:欄位、註解、附件、
                               包含標題的關聯,以及每個連結維基頁面的內容與討論
                               --children  一個 EPIC 及其底下的所有項目,僅需
                               一次呼叫。--depth <n> 用於更深層(最多 5 層)
                               --compact   扁平化身份資訊,移除面板按鍵
create <type> --title "…"      建立一個工作項目。--parent 會在同一個 PATCH 中處理
update <id>                    變更某個欄位:--state --priority --assignee
                               --title,或使用 --field Name=value 處理其他欄位
link <id> --parent <id>        將兩個項目建立關聯。也可用 --child --related
unlink <id> --parent <id>      取消關聯。也可用 --predecessor --successor --duplicate
link <id> --wiki-page <path>   將維基頁面連結到工作項目 —— 這是工作項目端
wiki links <path>              查詢哪些工作項目連結到某個頁面(ADO 沒有 API)
wiki link <path> --item <id>   這是維基端;--item 可重複
wiki unlink <path> --item <id>
history <id>                   顯示哪些項目變更了,逐欄位查看,誰變更的以及何時
iterations | areas             查看衝刺及其日期,以及區域
links                          查看此組織擁有的每種關聯類型,以及哪個
                               --flag 可以觸發它(約十八種中的六種)
fields [--type T]              查看某個欄位會接受哪些值 —— 允許值
attach <id> <file>             將檔案放到工作項目上(附件列表)
detach <id> <url-or-guid>      移除一個附件。需要 --yes。或使用 --name <file>
download <item-id|guid|url>    讀取一個附件。如果是工作項目 ID,搭配 --name <file>,
                               或使用 --all --out <dir>。指定 --out 否則不會寫入檔案
image <file>                   上傳圖片以供在文字中使用。輸出可貼上的 markdown。
                               與 attach 不同
people [query]                 查詢可以 @提及的使用者,使用 @<guid> 格式
delete <id> --yes              移到資源回收筒(刻意不提供永久刪除)
restore <id>                   從資源回收筒還原
deleted                        查看資源回收筒中的內容,包含名稱。
                               --top <n> —— 預設回傳 50 筆,並在 "count" 中報告實際總數
wiki list | tree | read <path> | write <path> | delete <path>
wiki move <path> <new path>    移動頁面;子頁面會一起移動
wiki rename <path> <new name>  與移動相同呼叫,但僅限葉節點。兩者都需要 --yes
wiki duplicate <path> [to]     一個 Azure DevOps 缺乏的動詞。"<path> - 副本 N"
wiki image <file>              用於頁面或頁面註解的圖片。與 `image` 的端點不同,且名稱是唯一化的
wiki import <file...>          .md .txt .docx .pdf .rtf .html 轉換為頁面。
                               .pptx 會被拒絕,這是故意的——請見下方說明
                               --under <path> --as <name>
comment list | add | edit | delete   目標可以是工作項目 ID 或維基路徑
comment react | unreact | reactors   支援 like, dislike, heart, hooray, smile, confused
hooks list | check | create | repoint | delete    用於即時更新的服務鉤子
                               `check` 需要 --hub <url>;`list` 會顯示已使用的 URL
plan <file>                    實作計劃 → 工作項目樹狀結構
setup                          組織、專案、權杖 → 鑰匙圈 + shell 設定檔

文字輸入:--text / --file / --stdin。全域選項:--pretty 用於人類可讀輸出, --yes 用於破壞性動詞,--dry-run 用於預覽。

ADO_ORG、ADO_PROJECT 和 ADO_PAT 在設定後已存在於環境中。在開始工作前不需檢查它們;缺少時會以退出碼 2 回傳 "kind": "config",這是唯一需要注意的時候。

尋找工作項目:pharos query

請不要使用 curl 和 WIQL 端點。 此技能之前會提供您一個完全針對此需求的做法,因為當時沒有 query 動詞。現在有了,而且它解決了那個做法做不到的部分:WIQL 只回傳 ID,所以那個做法只會給您十六個裸露的數字,然後需要為每個項目分別呼叫一次才有意義。

pharos query --mine                        # 指派給您且仍為開啟狀態
pharos query --sprint "Sprint 1"           # --sprint current 用於 @currentIteration
pharos query --type Epic --state Doing     # 兩者皆可重複
pharos query --assignee "[email protected]"
pharos query --tag api --all                # --all 包含已完成的工作
pharos query --wiql "SELECT [System.Id] FROM WorkItems WHERE …"   # 逃生口

多個旗標會進行 AND 運算。輸出是已充實的項目 —— id、類型、標題、狀態、代理人、迭代、標籤、優先權、變更日期——所以 --mine 是一個指令,而不是先查詢再逐筆擷取結果。只需對您實際要處理的少數項目執行 pharos task <id>。

在報告數量之前,請先讀取 openness 欄位。 "12 個開啟" 在您知道哪些狀態被視為已完成之前是沒有意義的,因為 Azure DevOps 允許流程範本重新命名每個狀態。query 會讀取專案自己的狀態類別,並告訴您它將哪些狀態視為終端狀態;如果無法讀取,它會說明並退而採用作業,這時您就應該明確傳遞 --state。assignedTo 會顯示 @Me 實際解析成的使用者名稱 —— 共用的或服務權杖可能讓「指派給我」悄悄地變成其他人。

--all 可以與任何其他篩選條件組合,所以 --mine --all 是您判斷「沒有任何東西指派給您」與「沒有任何開啟的項目」的方式。單獨執行 pharos query --all 是合法的,會回傳整個專案。

--sprint 接受純粹的名稱或 fieldPath,並在兩者之間進行標準化 —— fieldPath 規則是關於 System.IterationPath 作為 欄位值。但指定的衝刺名稱會使用 UNDER(包含子迭代)進行比對,而 --sprint current 則使用 =(精確)進行比對,所以它們不是相同的查詢。

--wiql 也接受 FROM WorkItemLinks … MODE (MustContain) 查詢,並以相同方式充實 ID,這是您用一次呼叫找到所有帶有某種關聯類型的項目的方式。

如果您使用 --wiql,請勿撰寫 [System.State] NOT IN GROUP 'Completed'。它會解析成功、回傳 200,但會比對到 所有項目 —— IN GROUP 只涵蓋工作項目類型的類別,而未知的群組會解析為空集合且不會報錯。已於 2026-08-05 實測。

變更工作項目

pharos update 225 --state Doing              # 工作進度移動時更新狀態
pharos update 225 --priority 1 --assignee "[email protected]"
pharos update 225 --field Microsoft.VSTS.Scheduling.RemainingWork=3
pharos attach 225 ./bestsellers.xlsx --comment "這些數字"

update 會讀取項目,並在 /rev 上透過 test 操作套用變更,因此如果在您讀取和寫入之間有人修改,您會收到 conflict 錯誤,而不是讓他們的編輯遺失。將值設定為與現有值相同不會寫入任何東西並會告知您 —— 一個無意義的 PATCH 仍然會增加 System.Rev 並使該項目上所有其他快取修訂版本失效。--dry-run 會顯示變更前 → 變更後,且不會寫入任何內容。沒有 --yes:欄位編輯是一般的編輯,Azure DevOps 會保留每個修訂版本。

attach 會上傳位元組,然後將它們連結為 AttachedFile 關聯 —— 兩個呼叫,一個指令。附件是不可變的:兩次附加相同檔案會產生兩個附件,沒有取代或版本控制。detach 會移除一個;它需要 --yes,透過身份而非位置來尋找附件,並將位元組留在 Azure DevOps 中,因此重新附加該 URL 會將其放回。使用 --name <file> 指定檔案名稱或傳遞 GUID —— 如同 download,兩個名稱相同的檔案會拒絕操作,而不是移除排序中先出現的那個。

create 會將 --parent 放在與欄位相同的 PATCH 中,因此子項目永遠不會短暫地成為孤兒,而且它接受與 update 相同的旗標名稱。

unlink 會拒絕而不是猜測,這點在遇到之前值得知道。 Azure DevOps 透過關聯在陣列中的位置來移除關聯,因此來自過時讀取的索引會切斷不同的連結,而且請求仍然會成功。unlink 透過身份尋找關聯,並在其讀取的修訂版本上透過 test 操作將其移除。如果該關聯不存在,您會得到退出碼 3 —— 這表示沒有發生任何事,而不是呼叫失敗。

delete 會移至資源回收筒,且需要 --yes;拒絕時會先顯示標題。restore <id> 會將其還原,且不需要任何旗標。

這裡故意不提供永久刪除。 這是 Azure DevOps 中唯一不可逆的動詞,而 --yes 是您已經學會傳遞的旗標。Web UI 擁有它。如果有人真的需要清除,請將他們送到那裡,而不是尋找某個旗標。

文字中的圖片,以及旁邊的檔案

這是兩件不同的事情,選錯是值得避免的錯誤。

pharos attach 225 ./bestsellers.xlsx    # 一個檔案,在附件列表中
pharos image ./screenshot.png           # 一張圖片,用來放在文字內部

附件會建立 AttachedFile 關聯,並出現在工作項目的附件列表中。內嵌圖片則不會建立任何關聯 —— 在 #333 上實測,貼入描述中的兩張螢幕截圖產生了 relations: 0 和 attachments: []。它只是一個 ![](…) 在欄位文字中,別無其他,這就是為什麼附件列表正確地顯示沒有項目。

因此,image 會輸出一行 markdown,並將寫入動作留給您:

pharos image ./chart.png --pretty
# ![chart](https://dev.azure.com/…/wit/attachments/<guid>?fileName=chart.png)

pharos update 225 --field System.Description="$(cat <<'EOF'
Revenue is up. See the chart:

![chart](https://dev.azure.com/…/wit/attachments/<guid>?fileName=chart.png)
EOF
)"

維基圖片使用的是不同的端點,需要 wiki image。 維基是一個 git 儲存庫,因此那裡的附件是一個檔案,其名稱就是它的身份:上傳第二個 image.png 會覆蓋第一個,而指向 /.attachments/image.png 的每個頁面都會默默地更換圖片。wiki image 會在傳送前使名稱唯一化,並輸出一個儲存庫相對路徑的連結:

pharos wiki image ./diagram.png --pretty
# ![diagram](/.attachments/diagram-1786046773042.png)

請使用該路徑,不要使用絕對 URL —— 連結到 wit/attachments URL 的頁面,對有 session 的人來說可以正常顯示,但對其他人則會中斷。相同的 markdown 在頁面和頁面註解中都適用。

從工作項目取得附件

pharos task <id> 會列出附加的內容,每個檔案都有其 GUID —— 在 JSON 和 --pretty 輸出中都是如此。然後透過名稱一次呼叫取得:

pharos download 41 --name Skills.pptx --out ./Skills.pptx
pharos download 41 --all --out ./attachments      # 項目上的所有附件
pharos download <guid-or-url> --out ./file.bin    # 當您已經持有某個附件時

--out 才是真正寫入的參數。 沒有它,您只會得到檔案大小而不會寫入檔案 —— stdout 上的原始位元組會破壞其他每個動詞輸出的 JSON。

單獨執行 pharos download 41 是一個用法錯誤,但會輸出附件清單 —— 名稱、GUID 和大小 —— 因此選擇正確的檔案永遠不需要第二次呼叫。兩個同名的附件會拒絕而非猜測:附件是不可變的,因此附加同一個檔案兩次會產生兩個,名稱不是身份。

detach 接受相同的 --name <file>,因此在閱讀 task --pretty 後您會使用的動詞,接受它顯示給您的內容:

pharos detach 41 --name Skills.pptx --yes      # --dry-run 會先預覽

讀取其內容是您的責任,使用您的工具

download 會將位元組寫入磁碟並就此打住。將 .docx、.pptx、.xlsx 或 .pdf 轉換為可讀內容是您環境的工作。

請勿使用 pharos-convert 來做這件事。 它是 wiki import 的轉換器,與 macOS 應用程式共享,以便兩者從相同檔案產生相同的頁面 —— 其輸出是為了成為維基頁面而設計的,在這裡使用它會將您讀取的內容與應用程式的匯入路徑耦合。它也完全無法讀取 .pptx。

有兩種方式會靜默失敗,都是在真實的工作項目上實測的:

  • 您在錯誤的地方檢查工具是否可用。 在系統 PATH 上執行 which markitdown 以及對系統的 python3 執行 import docx,在一個那些工具已安裝的機器上都會回傳空值 —— 因為它們安裝在每個技能專用的 venv 中。執行這兩項檢查的代理會得出「這裡沒有東西」的結論,並繞過從頭到尾都存在的工具。在得出結論之前,請先檢查您的代理將其工具放在哪裡。
  • 非空的文字並不能證明您已讀取文件。 一個真實的 .pptx 提取出七個片段,約 90 個字元;整個規格文件都在兩個內嵌的 PNG 中。每個純文字閱讀器都會為那個簡報回傳一些內容,而且看起來像是運作正常。對於投影片,或掃描的 PDF,有效載荷通常是圖片 —— 將它們提取出來並實際查看。

首先問問您是否能夠原生閱讀 PDF。如果可以,這只是一個指令。

許多代理程式 —— 包括 Claude Code —— 可以視覺化地閱讀 PDF,逐頁閱讀,就像人眼觀看一樣。這涵蓋了完全沒有文字的掃描文件,無需 OCR 步驟。如果這是您的情況,整個問題就簡化為一次轉換:

soffice --headless --convert-to pdf f.docx --outdir ./out    # .pptx、.xlsx 也行
# …

相關技能