Alle Anleitungen
Plugins
Ein Plugin bildet den universellen Kern auf die Sprache und Policy eines
Konsumenten ab. Nichts in der CLI kodiert Vokabular fest — alles ergibt sich aus der aktiven
Plugin-Konfiguration, einmal bei nxf init gewählt. Ein Plugin legt vier Dinge fest:
- Vokabular: die Worte für die beiden Item-Typen und die drei Status.
- Prioritätslabels: die Namen für die Prioritätsstufen 0–4.
- Ranking: wie
nextdie ready-Menge ordnet. - Darstellung: welche Felder
listundshowanzeigen.
Die zwei mitgelieferten Plugins
- issue-tracker — Software-Issue-Tracking: Epics und Issues, P0–P4-Prioritäten, abhängigkeitsbewusstes Ranking.
- personal-todo — Persönliche To-do-Liste: Listen und Todos, now/soon/later-Prioritäten für alltägliche Aufgaben.
Ihre Vokabulare unterscheiden sich:
- issue-tracker: ein Projekt ist ein
epic, ein Task ist einissue; die Status sindopen/in progress/closed; die Prioritäten sindP0–P4;nextrankt nach Priorität, dann Fälligkeitsdatum, dann id. - personal-todo: ein Projekt ist ein
project, ein Task ist eintodo; die Status sindtodo/doing/done; die Prioritäten sindnow/soon/later/someday/icebox;nextrankt nach Priorität, dann id (kein Fälligkeitsdatum als Tiebreak).
Named Variants auflösen: types und priorities
type und priority sind beide named variants — eine kleine, plugin-definierte Menge mit
fester Ordnung. nxf schema --json exponiert beide als gekeyte Map, sodass eine Regel jedes
der beiden Felder auf jedem Item auflöst:
schema.types[item.type] # "task" -> "issue"
schema.priorities[item.priority] # "0" -> "P0"Der auf einem Item gespeicherte Wert ist der Handle, nicht das Label. Für priority ist dieser
Handle ein Ordinal — "0" ist die höchste Priorität, aufsteigend gezählt — bewusst
plugin-unabhängig, damit next numerisch ranken kann und gesyncte Items keine Fremd-Labels
tragen. Die schema.priorities-Map übersetzt das Ordinal zurück in ein menschliches Label.
Ordinal-Stabilitätsvertrag. Innerhalb einer Schema-Version ist die Bindung Ordinal→Bedeutung
stabil und append-only: priority.labels darf am Ende wachsen, aber Umsortieren oder
Einfügen verschiebt die Bedeutung bereits gespeicherter Items und ist daher ein breaking
Schema-Change — ein Schema-Version-Bump mit Migration, keine In-place-Änderung.
Gleiche Daten, nebeneinander
Die Naht ist am klarsten, wenn Sie die gleichen Befehle auf den gleichen Daten unter jedem
Plugin ausführen. Beide Workspaces unten enthalten drei Tasks: zwei mit Priorität 1 (einer fällig
2026-02-01, einer fällig 2026-03-01) und einer mit Priorität 2.
next zeigt zwei Unterschiede auf einmal — das Vokabular und das Ranking. Unter
issue-tracker gewinnt das früher fällige P1-Issue (Book venue) den Tiebreak:
$ nxf next
0002 P1 open [feature] Book venue
0001 P1 open [feature] Draft proposal
0003 P2 open [feature] Order badgesUnter personal-todo stehen dieselben beiden soon-Todos bei der Priorität gleich und fallen auf
die id zurück, sodass das zuerst erstellte (Draft proposal) zuerst kommt — und die Labels sind nun
now/soon/later, todo/doing/done:
$ nxf next
0001 soon todo [todo] Draft proposal
0002 soon todo [todo] Book venue
0003 later todo [todo] Order badgesDennoch ist der gespeicherte Datensatz derselbe — list --json ist id-geordnet und hängt nicht
vom aktiven Plugin ab. Das Plugin legt Darstellung und Ranking darüber; es ändert nie die Daten.
Das eine Feld, das ein Plugin besitzt, ist type: Das Typ-Set ist plugin-deklariert, also speichert
issue-tracker feature, wo personal-todo todo speichert. Lässt man dieses eine Feld weg, sind die
Datensätze byte-für-byte dieselben:
$ nxf list --json
[{"archived":null,"assignee":null,"belongs_to":null,"closed_at":null,"closing_comment":null,"completion_criterion":null,"created_at":"2026-06-23T00:00:00Z","defer_until":null,"deleted":null,"description":"Outline the event proposal","design":null,"due":"2026-03-01","id":"ab12.0001","priority":"1","priority_label":"P1","status":"open","title":"Draft proposal","type":"feature","type_label":"feature","updated_at":"2026-06-23T00:00:00Z"},{"archived":null,"assignee":null,"belongs_to":null,"closed_at":null,"closing_comment":null,"completion_criterion":null,"created_at":"2026-06-23T00:00:00Z","defer_until":null,"deleted":null,"description":"Reserve the event space","design":null,"due":"2026-02-01","id":"ab12.0002","priority":"1","priority_label":"P1","status":"open","title":"Book venue","type":"feature","type_label":"feature","updated_at":"2026-06-23T00:00:00Z"},{"archived":null,"assignee":null,"belongs_to":null,"closed_at":null,"closing_comment":null,"completion_criterion":null,"created_at":"2026-06-23T00:00:00Z","defer_until":null,"deleted":null,"description":"Print attendee name badges","design":null,"due":null,"id":"ab12.0003","priority":"2","priority_label":"P2","status":"open","title":"Order badges","type":"feature","type_label":"feature","updated_at":"2026-06-23T00:00:00Z"}]show macht den Vokabularunterschied an einem einzelnen Item konkret. Issue-tracker:
$ nxf show ab12.0002
0002 P1 Book venue
==================
TYPE: feature
STATUS: open
DESCRIPTION
-----------
Reserve the event space
DEFINITION OF DONE
------------------
DESIGN
------
NOTES
-----personal-todo, dasselbe ab12.0002:
$ nxf show ab12.0002
0002 soon Book venue
====================
TYPE: todo
STATUS: todo
WHY
---
Reserve the event space
DONE WHEN
---------
PLAN
----
LOG
---Diese Verdopplung ist keine Redundanz — sie ist die Erklärung. Das --json ist die Bedeutung;
das Plugin ist, wie ein bestimmtes Publikum sie liest und rankt. Ein Plugin bei init zu wählen
ist daher eine echte Entscheidung; führen Sie nxf init --help aus, um die Optionen zu sehen,
bevor Sie sich festlegen.
Eigene Felder deklarieren
Ein Plugin kann seinen Typen unter [fields.<name>] eigene Felder geben. nxf schema --json
listet sie mit "custom": true und ihrer kind; create und update setzen sie mit
--set <name>=<wert>; show --json und die Listen geben die gesetzten unter custom zurück. Ein
leerer Wert löscht ein Feld.
[fields.stage]
type = "enum"
values = ["backlog", "active", "done"]
on = ["project"] # die Typen, für die es gilt; weglassen heißt: für jeden Typ
[fields.occurred_on]
type = "datetime"
label = "Occurred on"Der type entscheidet, was ein Wert sein darf:
textundlongtext: beliebiger Text.date: ein Datum (2026-10-12) oder ein RFC-3339-Zeitstempel, gespeichert wie angegeben.datetime: ein Zeitpunkt, als RFC-3339-Zeitstempel mit Zeitzone —Zoder ein Offset wie+02:00. Gespeichert wird derselbe Zeitpunkt in UTC auf ganze Sekunden:--set occurred_on=2026-10-12T10:30:00+02:00speichert2026-10-12T08:30:00Z. Ein reines Datum oder eine Uhrzeit ohne Zone wird abgelehnt. Weil jeder Wert dieselbe Form hat, sortierendatetime-Werte überall zeitlich, wo ein Feld gelesen wird, etwa imof = { field = "..." }eines Ranking-Schlüssels. Der eingegebene Offset bleibt nicht erhalten.enum: einer der deklariertenvalues.
Listenfelder
list = true lässt ein Feld mehrere Einträge statt eines Werts halten — etwa die E-Mail-Adressen
einer Person. Jeder Eintrag ist ein Wert des type des Felds (text, date, datetime oder
enum, geprüft wie oben) und kann eine eigene Kennzeichnung tragen, etwa work oder private:
[fields.email]
type = "text"
list = true
on = ["person"]nxf entry add <id> email ada@work.example --label work
nxf entry add <id> email ada@home.example
nxf entry list <id> email
nxf entry remove <id> email ada@home.example
nxf update <id> --set email= # leert die Listeshow --json und die Listen geben die Einträge unter custom zurück, in der Reihenfolge, in der
ihre Werte zuerst hinzugefügt wurden: "email": [{"value": "ada@work.example", "label": "work"}, ...], label nur, wo es eine gibt. nxf schema --json markiert das Feld mit "list": true. Über
MCP heißen die Verben flow_entry_add, flow_entry_remove und flow_entry_list.
Der Wert ist der Eintrag: Wer einen Wert hinzufügt, den die Liste schon hat, gibt diesem Eintrag
die neue Kennzeichnung (oder keine, ohne --label), statt ihn doppelt anzulegen; wer einen Wert
entfernt, entfernt ihn mit jeder Kennzeichnung. Zwei Geräte dürfen gleichzeitig hinzufügen und
entfernen: Eine Adresse, die auf dem einen hinzukommt, übersteht ein Entfernen auf dem anderen, das
sie nie gesehen hat, und beide enden mit derselben Liste. --set leert ein Listenfeld nur; ein
Listenfeld kann weder required noch longtext sein, kein merge haben und nicht ranken.
entry add und entry remove geben den Eintrag so aus, wie er gespeichert ist: Ein
datetime-Eintrag, eingegeben als 10:30:00+02:00, erscheint als 08:30:00Z, und ein
datetime-Eintrag lässt sich mit demselben Zeitpunkt in jedem Offset entfernen. Jeder Eintrag, den
du siehst, lässt sich entfernen: entry remove prüft weder den Wert gegen den Typ des Felds (einen
inzwischen gestrichenen Enum-Wert) noch on (einen Typ, den das Plugin inzwischen ausnimmt). Der
Name eines Listenfelds hat höchstens 64 Zeichen.
Verweisfelder
type = "ref" macht ein Feld zu einem typisierten Verweis auf ein anderes Item — die Organisatorin
eines Meetings, die Firma, bei der eine Person arbeitet. Sein Wert ist eine Item-Id, und targets
nennt die Item-Typen, auf die es zeigen darf. Ein Verweis ist ein einzelner Wert oder, mit
list = true, eine Liste von Einträgen mit optionaler Kennzeichnung:
[fields.organizer]
type = "ref"
targets = ["person"]
on = ["meeting"]
[fields.participants]
type = "ref"
targets = ["person"]
list = true
on = ["meeting"]nxf update <meeting> --set organizer=<person>
nxf entry add <meeting> participants <person> --label host
nxf entry remove <meeting> participants <person>
nxf update <meeting> --set organizer= # leert ihn--set und entry add nehmen eine volle Id (ab12.0007 oder eine fremde cd34.0001) oder eine
nackte lokale (0007), die wie jede eingegebene Id aufgelöst wird. Sie prüfen das Ziel, in dieser
Reihenfolge: Der Wert ist eine Id, das Item existiert und ist nicht gelöscht, sein Typ steht in
targets, und es ist nicht das Item selbst. Ein geschlossenes oder archiviertes Item ist ein
gültiges Ziel. entry remove prüft nichts, sodass sich auch ein Verweis auf ein inzwischen
gelöschtes Item entfernen lässt. Typ und Selbstverweis werden beim Schreiben geprüft, nie danach:
Wird das Ziel später umgetypt oder gelöscht, bleibt der Verweis, wie er ist.
show --json behält den Wert unter custom und ergänzt refs, das jedes Ziel auflöst:
"refs": {"organizer": [{"id": "ab12.0007", "found": true, "type": "person", "title": "Ada", "status": "open"}]}, oder {"id": …, "found": false} für einen Wert, der kein Item auf diesem Board
nennt. Das menschenlesbare show zeigt unter der Überschrift des Felds Id und Titel jedes Ziels,
oder (not on this board). nxf schema --json meldet das Feld als "kind": "ref" mit seinen
"targets".
Ein Verweis kann kein merge = "crdt-text" haben und nicht ranken; sein Name hat höchstens 64
Zeichen. Wer ein Textfeld als Verweis neu deklariert, behält dessen Werte: Die Ids darunter werden zu
Verweisen, der Rest erscheint als nicht auf diesem Board. Ein Verweis wird mit den gewöhnlichen
Feld-Ops geschrieben, sodass eine aus einem Snapshot gestartete Replik gültig bleibt.
Die Verweisenden finden
nxf list --ref <feld>=<id> listet die Items, deren Verweisfeld die Id hält: einen einzelnen Wert
gleich ihr oder einen Eintrag gleich ihr. Es ist ein list-Filter und lässt sich daher mit
--type, --status, --in, --label, --sort, --limit und --paginate kombinieren; eine
nackte lokale Id wird aufgelöst. Mehrfach angegeben sind es Alternativen (höchstens 8). Mit einem
Plugin, das Meetings, Stationen und Beziehungen deklariert:
nxf list --type meeting --ref participants=<person> # die Meetings einer Person
nxf list --type station --ref person=<person> # die Stationen einer Person
nxf list --type relationship --ref a=<person> --ref b=<person> # Beziehungen, von beiden EndenUnter --sort rank grenzt der Filter die Zeilen ein, nie das Ranking: Die Items kommen in der
Reihenfolge, die die ungefilterte gerankte Liste ihnen gibt. Die Id muss kein Item nennen — die
Verweisenden eines gelöschten Items bleiben auffindbar, und eine Id, die nichts hält, listet nichts.
Gelöschte Verweisende fallen weg, geschlossene werden gelistet; archivierte Verweisende werden
nicht gelistet, weil list lebende, nicht archivierte Items listet (nxf archived nimmt kein
--ref). Ein Item, dessen eigene Id länger als 128 Byte ist, ist nie ein Verweisender. Ein Feld,
das kein Verweisfeld des Plugins ist, ein Wert, der keine Id ist, --ref ohne = und mehr als 8
Verweise werden abgelehnt. list --json bleibt unverändert; über MCP nimmt flow_list
refs: [{"field": "participants", "id": "<person>"}].
Ein Ranking deklarieren
Ein Plugin deklariert unter [ranking.next], wie next rankt, und unter [ranking.list], wie
list --sort rank rankt. Beide nehmen dieselben Schlüssel und dieselben Regeln:
order = [...]ist die Standardreihenfolge: eine Liste von Schlüsseln, der Reihe nach verglichen, mit der Id als letztem Entscheider.[ranking.next.<typ>](oder[ranking.list.<typ>]) gibt einem Typ eine eigeneorder.types = [...]sagt, wie die Typen zueinander stehen, sobald einige eine eigene Reihenfolge haben.
Ein Schlüssel ist ein Feldschlüssel, etwa { field = "priority", dir = "asc" }. Mit einer
precedence-Liste rankt ein Feldschlüssel stattdessen nach der Position des Werts in dieser
Liste. In einer Reihenfolge pro Typ, oder in der Standard-order von [ranking.list], kann ein
Schlüssel auch ein abgeleiteter Schlüssel sein, der ein Item nach seinen Nachbarn im Graphen
rankt:
[ranking.next]
types = ["project", "action"]
order = [ { field = "priority" } ]
# related: ein Wert, gelesen an den Nachbarn über eine ausgehende Kante (parent, contributes_to,
# dep, mentions). `of = "rank"` liest die Position des Nachbarn in der Reihenfolge seines eigenen
# Typs; `of = { field = "..." }` liest ein Feld, auch ein eigenes Feld. `pick` ist min (Standard)
# oder max.
[ranking.next.action]
order = [
{ related = "parent", type = "project", of = "rank", nulls = "last" },
{ field = "priority" },
]
# count: wie viele Nachbarn passen. children, contributors, dependents und mentioned_by zählen die
# eingehenden Kanten parent, contributes_to, dep und mentions; eine ausgehende Art zählt in die
# andere Richtung. `type`, `status` (eine Liste) und `label` filtern die gezählten Nachbarn.
[ranking.next.project]
order = [
{ related = "contributes_to", type = "goal", of = { field = "order" }, nulls = "last" },
{ count = "children", type = "action", status = ["open", "in_progress"], dir = "desc" },
]
# sum: mehrere Zählungen, mit gleichem Gewicht addiert. `dir` gehört zur Summe.
[ranking.list]
types = ["person"]
[ranking.list.person]
order = [
{ sum = [ { count = "mentioned_by", type = "project" },
{ count = "mentioned_by", type = "action", label = "waiting" } ], dir = "desc" },
]Abgeleitete Schlüssel zählen nur lebende Nachbarn: ein gelöschter fällt weg, ein geschlossener
oder archivierter zählt. Ein Rang wird über die Kandidaten des Typs unter dem Verb berechnet, das
ihn rankt, und Items, die in jedem Schlüssel gleich sind, teilen sich einen Rang. Für next sind
diese Kandidaten seine umsetzbaren Items, für list die lebenden, nicht archivierten Items. Ein
Nachbar außerhalb dieser Menge, etwa ein geschlossenes Projekt unter next, hat keinen Rang und
liest sich als null. Ein list-Schlüssel darf den Rang eines Typs lesen, den nur next rankt;
die Kandidaten von next werden dann zur aktuellen Zeit genommen (NXF_NOW legt sie fest). Nur
list --sort rank liest diese Zeit.
Das Plugin wird beim Laden abgelehnt, mit dem Schlüssel benannt, wenn:
- ein Schlüssel Arten mischt oder keine nennt;
- ein Schlüssel eine unbekannte Kantenart, einen unbekannten Typ, Status oder ein unbekanntes Feld nennt;
- ein Schlüssel einen Teil trägt, den seine Art nicht nimmt, etwa
pickan einer Zählung odernullsan einer Zählung oder Summe (eine Zählung ist nie null); of = "rank"einen Typ ohne eigene Reihenfolge nennt;- die Typen sich im Kreis gegenseitig ranken.
Ein Schlüssel related ... of = "rank" ohne type liest den Rang jedes Typs mit einer
Reihenfolge, auch des eigenen. Das ist ein Kreis, also nennen Sie den type des Nachbarn; die
Standard-order von [ranking.list] lehnt einen solchen Schlüssel ebenfalls ab. Abgeleitete Schlüssel sind in der Standard-order von [ranking.next] nicht erlaubt, weil diese
Reihenfolge auch blocked und --sort rank der Lanes rankt.