Arbeiten mit Azure DevOps und pharos
pharos ist ein CLI, das Ihnen Azure DevOps aus einer Shell heraus bietet, authentifiziert durch eine Umgebungsvariable anstelle eines Browser-Logins.
Alles, was es kann, ist unten aufgeführt. Führen Sie kein pharos --help aus, um es herauszufinden – das kostet mehrere Aufrufe und dieser Abschnitt enthält die gleichen Informationen.
whoami Wem ADO_PAT gehört. @Me löst sich darauf auf
types Welche --state und --type AKZEPTIEREN, pro Typ
query WELCHE Arbeitselemente – Ihnen zugewiesen, in einem Sprint,
eines Typs, noch offen. Hydrierte Elemente, keine IDs.
task <id> Ein Arbeitselement, vollständig: Felder, Kommentare,
Anhänge, Beziehungen MIT Titeln, sowie der
Inhalt + Diskussion jeder verlinkten Wiki-Seite
--children Ein EPIC und alles darunter, in
EINEM Aufruf. --depth <n> für tiefer (max. 5)
--compact Identitäten abflachen, Board-Schlüssel entfernen
create <type> --title "…" Ein Arbeitselement. --parent kommt im GLEICHEN Patch
update <id> Ein Feld ändern: --state --priority --assignee
--title, oder --field Name=Wert für alles andere
link <id> --parent <id> Zwei Elemente in Beziehung setzen. Auch --child --related
unlink <id> --parent <id> --predecessor --successor --duplicate
link <id> --wiki-page <pfad> Eine WIKI-SEITE mit einem Arbeitselement verknüpfen – die Elementseite
wiki links <pfad> Welche Arbeitselemente verlinken eine Seite (ADO hat keine API)
wiki link <pfad> --item <id> Die WIKI-Seite; --item wiederholbar
wiki unlink <pfad> --item <id>
history <id> Was sich GEÄNDERT hat, Feld für Feld, wer und wann
iterations | areas Die Sprints mit ihren Daten und die Bereiche
links Jeder Beziehungstyp, den diese ORG hat, und welches
--flag ihn erreicht (sechs von ~achtzehn)
fields [--type T] Was ein Feld AKZEPTIERT – zulässige Werte
attach <id> <datei> Eine DATEI an ein Arbeitselement anhängen (Anhänge-Liste)
detach <id> <url-oder-guid> Eine entfernen. --yes. Oder --name <datei>
download <element-id|guid|url> Eine herunterladen. Eine ARBEITSELEMENT-ID mit --name <datei>,
oder --all --out <verz>. --out oder nichts wird geschrieben
image <datei> Ein BILD für die Verwendung in Text hochladen. Gibt das
Markdown zum Einfügen aus. NICHT dasselbe wie attach
people [abfrage] Wer @erwähnt werden kann, mit der @<guid>-Form
delete <id> --yes → Papierkorb (kein dauerhaftes Löschen, absichtlich)
restore <id> Eines zurückholen
deleted Was sich im Papierkorb befindet, mit Namen.
--top <n> – es gibt standardmäßig 50 zurück und meldet
die wahre Gesamtzahl als "count"
wiki list | tree | read <pfad> | write <pfad> | delete <pfad>
wiki move <pfad> <neuer pfad> Eine Seite verschieben; Unterseiten werden mitgenommen
wiki rename <pfad> <neuer name> Der gleiche Aufruf, nur Blatt. BEIDE benötigen --yes
wiki duplicate <pfad> [nach] Ein Verb, das Azure DevOps fehlt. "<pfad> - Kopie N"
wiki image <datei> Ein Bild für eine SEITE oder Seitenkommentar. Anderer
Endpunkt als `image`, und der Name wird eindeutig gemacht
wiki import <datei…> .md .txt .docx .pdf .rtf .html -> Seiten. Eine
.pptx wird abgelehnt, absichtlich – siehe unten
--under <pfad> --as <name>
comment list | add | edit | delete <ziel> ist eine Arbeits-ID ODER ein Wiki-Pfad
comment react | unreact | reactors like dislike heart hooray smile confused
hooks list | check | create | repoint | delete Service-Hooks für Echtzeit.
`check` benötigt --hub <url>; `list` zeigt die URL
bereits in Gebrauch
plan <datei> Ein Implementierungsplan → ein Arbeitselement-Baum
setup Org, Projekt, Token → Schlüsselbund + Shell-Profil
Texteingabe: --text / --file / --stdin. Global: --pretty für Menschen, --yes für destruktive Verben, --dry-run zur Vorschau.
ADO_ORG, ADO_PROJECT und ADO_PAT sind nach der Einrichtung bereits in der Umgebung. Überprüfen Sie sie nicht, bevor Sie arbeiten; eine fehlende Variable kündigt sich als "kind": "config" mit Exit-Code 2 an, was der einzige Zeitpunkt ist, zu dem es wichtig ist.
Die Arbeit finden: pharos query
Greifen Sie nicht zu curl und zum WIQL-Endpunkt. Dieses Skill hat Ihnen früher ein Rezept genau dafür gegeben, weil es kein Query-Verb gab. Jetzt gibt es eines, und es macht den Teil, den das Rezept nicht konnte: WIQL gibt nur IDs zurück, also gab das Rezept Ihnen sechzehn nackte Zahlen und einen Aufruf pro Element, um sie bedeutungsvoll zu machen.
pharos query --mine # Ihnen zugewiesen, noch offen
pharos query --sprint "Sprint 1" # --sprint current für @currentIteration
pharos query --type Epic --state Doing # beide wiederholbar
pharos query --assignee "[email protected]"
pharos query --tag api --all # --all schließt abgeschlossene Arbeit ein
pharos query --wiql "SELECT [System.Id] FROM WorkItems WHERE …" # Notausstieg
Flags werden UND-verknüpft. Die Ausgabe besteht aus hydrierten Elementen – ID, Typ, Titel, Status, Bearbeiter, Iteration, Tags, Priorität, geändert – also ist --mine ein Befehl, keine Abfrage gefolgt von einem Abruf pro Ergebnis. Verwenden Sie pharos task <id> nur für die wenigen, an denen Sie tatsächlich arbeiten werden.
Lesen Sie das Feld openness, bevor Sie eine Anzahl melden. "12 offen" ist bedeutungslos, bis Sie wissen, was als abgeschlossen gezählt wurde, und Azure DevOps erlaubt einer Prozessvorlage, jeden Status umzubenennen. query liest die eigenen Statuskategorien des Projekts und teilt Ihnen mit, welche Status es als terminal behandelt hat; wenn es sie nicht lesen konnte, sagt es das und fällt auf Schätzung zurück, und das ist Ihr Hinweis, explizit --state zu übergeben. assignedTo nennt, wer tatsächlich von @Me aufgelöst wurde – ein gemeinsamer oder Dienst-Token bedeutet, dass "mir zugewiesen" stillschweigend jemand anderen meint.
--all kombiniert mit jedem anderen Filter, also ist --mine --all wie Sie feststellen, ob Ihnen nichts zugewiesen ist oder nichts offen ist. Ein nacktes pharos query --all ist zulässig und gibt das gesamte Projekt zurück.
--sprint akzeptiert entweder den bloßen Namen oder den fieldPath und normalisiert zwischen ihnen – die fieldPath-Regel bezieht sich auf System.IterationPath als Feldwert. Aber ein benannter Sprint stimmt mit UNDER überein (Unter-Iterationen eingeschlossen), während --sprint current mit = übereinstimmt (exakt), also sind sie nicht dieselbe Abfrage.
--wiql akzeptiert auch eine FROM WorkItemLinks … MODE (MustContain)-Abfrage und hydriert die IDs auf die gleiche Weise, womit Sie jedes Element finden, das einen bestimmten Beziehungstyp in einem Aufruf trägt.
Schreiben Sie NICHT [System.State] NOT IN GROUP 'Completed', wenn Sie zu --wiql greifen. Es wird geparst, gibt 200 zurück und stimmt mit allem überein – IN GROUP deckt nur Workitem-TYP-Kategorien ab, und eine unbekannte Gruppe löst sich ohne Fehler in die leere Menge auf. Gemessen, 2026-08-05.
Ein Arbeitselement ändern
pharos update 225 --state Doing # den Status verschieben, wenn die Arbeit sich bewegt
pharos update 225 --priority 1 --assignee "[email protected]"
pharos update 225 --field Microsoft.VSTS.Scheduling.RemainingWork=3
pharos attach 225 ./bestsellers.xlsx --comment "Die Zahlen"
update liest das Element und wendet die Änderung unter einem test-Op auf /rev an, sodass jemand, der zwischen Ihrem Lesen und Ihrem Schreiben geschrieben hat, einen conflict erhält, anstatt dass seine Bearbeitung verloren geht. Das Setzen eines bereits vorhandenen Werts schreibt nichts und sagt das – ein sinnloses PATCH erhöht trotzdem System.Rev und macht jede andere zwischengespeicherte Revision des Elements ungültig. --dry-run zeigt das Vorher → Nachher und schreibt nichts. Es gibt kein --yes: eine Feldbearbeitung ist eine gewöhnliche Bearbeitung und Azure DevOps behält jede Revision.
attach lädt die Bytes hoch und verknüpft sie dann als AttachedFile-Beziehung – zwei Aufrufe, ein Befehl. Anhänge sind unveränderlich: dasselbe Datei zweimal anzuhängen erstellt zwei davon, und es gibt keine Ersetzung und keine Versionierung. detach entfernt einen; es benötigt --yes, findet den Anhang anhand der Identität und nicht anhand der Position und hinterlässt die Bytes in Azure DevOps, sodass das erneute Anhängen der URL ihn zurückbringt. Benennen Sie die Datei mit --name <datei> oder übergeben Sie die GUID – und wie bei download verweigern zwei Dateien mit demselben Namen die Ausführung, anstatt diejenige zu entfernen, die zuerst sortiert wird.
create setzt --parent in denselben Patch wie die Felder, sodass ein Kind nie kurzzeitig ein Waise ist, und es akzeptiert dieselben Flag-Namen wie update.
unlink verweigert, anstatt zu raten, und das ist wissenswert, bevor Sie es sehen. Azure DevOps entfernt eine Beziehung anhand ihrer POSITION im Array, sodass ein Index aus einer veralteten Lektüre eine andere Verknüpfung durchtrennt und die Anfrage trotzdem erfolgreich ist. unlink findet die Beziehung anhand der Identität und entfernt sie unter einem test-Op auf der Revision, die es gelesen hat. Wenn die Beziehung nicht vorhanden ist, erhalten Sie Exit-Code 3 – das bedeutet, dass nichts passiert ist, nicht, dass der Aufruf fehlgeschlagen ist.
delete verschiebt in den Papierkorb und benötigt --yes; die Verweigerung zitiert zuerst den Titel. restore <id> bringt es zurück und benötigt kein Flag.
Es gibt hier absichtlich kein dauerhaftes Löschen. Es ist das einzige irreversible Verb, das Azure DevOps hat, und --yes ist ein Flag, das Sie gelernt haben zu übergeben. Die Weboberfläche besitzt es. Wenn jemand wirklich löschen muss, schicken Sie ihn dorthin, anstatt nach einem Flag zu suchen.
Bilder im Text und Dateien daneben
Dies sind zwei verschiedene Dinge, und das Falsche zu wählen ist der Fehler, den es zu vermeiden gilt.
pharos attach 225 ./bestsellers.xlsx # eine DATEI, in der Anhänge-Liste
pharos image ./screenshot.png # ein BILD, um es in den Text einzufügen
Ein Anhang erstellt eine AttachedFile-Beziehung und erscheint in der Anhänge-Liste des Arbeitselements. Ein Inline-Bild erstellt überhaupt keine Beziehung – gemessen an #333, zwei in eine Beschreibung eingefügte Screenshots ergaben relations: 0 und attachments: []. Es ist ein  im Feldtext und sonst nichts, weshalb die Anhänge-Liste zu Recht nichts dafür anzeigt.
Also gibt image eine Markdown-Zeile aus und überlässt das Schreiben Ihnen:
pharos image ./chart.png --pretty
# 
pharos update 225 --field System.Description="$(cat <<'EOF'
Der Umsatz steigt. Siehe das Diagramm:

EOF
)"
Ein Wiki-Bild ist ein anderer Endpunkt und benötigt wiki image. Ein Wiki ist ein Git-Repository, also ist ein Anhang dort eine DATEI und sein NAME ist seine Identität: Laden Sie ein zweites image.png hoch, und es landet auf dem ersten, und jede Seite, die auf /.attachments/image.png verweist, ändert stillschweigend das Bild. wiki image macht den Namen vor dem Senden eindeutig und gibt einen repository-relativen Link aus:
pharos wiki image ./diagram.png --pretty
# 
Verwenden Sie diesen Pfad, keine absolute URL – eine Seite, die auf eine wit/attachments-URL verlinkt, wird für jeden mit einer Sitzung gerendert und funktioniert für alle anderen nicht. Derselbe Markdown funktioniert in einer Seite und in einem Seitenkommentar.
Einen Anhang von einem Arbeitselement herunterladen
pharos task <id> listet auf, was angehängt ist, mit der GUID jeder Datei – im JSON und in --pretty. Dann holen Sie es in einem Aufruf, nach Namen:
pharos download 41 --name Skills.pptx --out ./Skills.pptx
pharos download 41 --all --out ./attachments # jeden Anhang auf dem Element
pharos download <guid-oder-url> --out ./datei.bin # wenn Sie bereits eine haben
--out ist das, was schreibt. Ohne es erhalten Sie die Größe und keine Datei – rohe Bytes auf stdout würden das JSON verderben, das jedes andere Verb ausgibt.
Ein nacktes pharos download 41 ist ein Nutzungsfehler, der die Liste trägt – Name, GUID und Größe – sodass die Auswahl der richtigen Datei niemals einen zweiten Aufruf kostet. Zwei Anhänge mit demselben Namen verweigern, anstatt zu raten: Anhänge sind unveränderlich, also erstellt das zweimalige Anhängen einer Datei zwei davon, und der Name ist keine Identität.
detach akzeptiert denselben --name <datei>, sodass das Verb, das Sie nach dem Lesen von task --pretty verwenden, akzeptiert, was es Ihnen gezeigt hat:
pharos detach 41 --name Skills.pptx --yes # --dry-run zeigt zuerst eine Vorschau
Das Lesen des Inhalts ist IHRE Aufgabe, mit IHREM Werkzeug
download bringt Ihnen Bytes auf die Festplatte und hört dann auf. Eine .docx, .pptx, .xlsx oder .pdf in etwas zu verwandeln, das Sie lesen können, ist die Aufgabe Ihrer Umgebung.
Verwenden Sie pharos-convert nicht dafür. Es ist der Konverter von wiki import, der mit der macOS-App geteilt wird, sodass beide die gleiche Seite aus derselben Datei erzeugen – seine Ausgabe ist geformt, um eine Wiki-Seite zu werden, und der Zugriff darauf koppelt hier das, was Sie lesen, an den Importpfad der App. Es kann auch .pptx überhaupt nicht lesen.
Zwei Arten, wie dies still fehlschlägt, beide gemessen an einem echten Arbeitselement:
- Sie haben nach dem Werkzeug an der falschen Stelle gesucht.
which markitdowngegen den System-PATH undimport docxgegen das System-python3kommen beide leer zurück auf einem Rechner, auf dem diese Werkzeuge installiert sind – in einer pro-Skill-Venv. Ein Agent, der diese beiden Prüfungen durchführt, schließt "hier ist nichts" und umgeht Werkzeuge, die die ganze Zeit da waren. Schauen Sie, wo Ihr Agent seine Werkzeuge aufbewahrt, bevor Sie zu dem Schluss kommen, dass sie fehlen. - Nicht-leerer Text ist kein Beweis dafür, dass Sie das Dokument gelesen haben. Eine echte
.pptxextrahierte sieben Fragmente, etwa 90 Zeichen; die gesamte Spezifikation befand sich in zwei eingebetteten PNGs. Jeder reine Text-Leser gibt für diese Präsentation etwas zurück und sieht aus, als hätte er funktioniert. Für eine Folienpräsentation oder ein gescanntes PDF ist die Nutzlast normalerweise die Bilder – extrahieren Sie sie und schauen Sie sie tatsächlich an.
Fragen Sie zuerst, ob Sie PDFs nativ lesen. Wenn ja, ist dies ein Befehl.
Viele Agenten – darunter Claude Code – lesen ein PDF visuell, Seite für Seite, so wie ein Mensch es betrachtet. Das deckt einen Scan ab, der überhaupt keinen Text enthält, ohne OCR-Schritt. Wenn das auf Sie zutrifft, reduziert sich das gesamte Problem auf eine Konvertierung:
soffice --headless --convert-to pdf f.docx --outdir ./out # .pptx, .xlsx auch
#
…

