Was macht obs-f?

obs-f ist ein Kommandozeilen-Tool, das du im Terminal tippst. Es macht 5 Dinge nacheinander:

obs-f | +-- 1. Rezept-Menue (FZF) -> WELCHE Dateien aus deinem Obsidian Vault? | +-- 2. Pattern-Menue (FZF) -> WAS soll Fabric damit machen? | +-- 3. Fabric laeuft -> AI analysiert die Dateien (Ausgabe: Deutsch) | +-- 4. Output anzeigen -> Du liest das Ergebnis im Terminal | +-- 5. Claude Handoff [j/N] -> Optional: Claude Code uebernimmt

Gesamtfluss als Diagramm

flowchart TD
    A["obs-f im Terminal tippen"] --> B["Rezept-Menue (FZF)"]
    B --> C{"Welcher Modus?"}
    C -->|"HEUTE/WOCHE/BATCH"| D["_obs_f_collect\nDateien sammeln"]
    C -->|"EINZELN"| E["_obs_f_pick_file\nDatei waehlen"]
    C -->|"__CMD__"| F["Shell-Befehl\nausfuehren"]
    D --> G["_obs_f_pick_fabric_pattern\nPattern-Menue (FZF)"]
    E --> G
    G --> H["Fabric AI\nAnalyse auf Deutsch"]
    H --> I["Output im Terminal\nanzeigen"]
    I --> J{"Weiter mit\nClaude Code?"}
    J -->|"j"| K["_obs_f_claude_handoff\nKontext-Datei schreiben\nClaude Code starten"]
    J -->|"N"| L["Fertig"]
            

Abb. 1: Gesamtfluss — Vom Terminal-Aufruf zum Ergebnis

Grundlagen für Anfänger

Was ist eine Shell-Funktion?

Eine Shell-Funktion ist ein wiederverwendbarer Code-Block in deinem Terminal. Statt lange Befehle zu tippen, gibst du nur den Funktionsnamen ein:

# Definition (in deiner .zshrc oder einer extra Datei)
meine_funktion() {
    echo "Hallo Welt"
}

# Aufruf
meine_funktion
# Ausgabe: Hallo Welt

Funktionen, die mit _ beginnen (z.B. _obs_f_run), sind Helper – sie werden nicht direkt aufgerufen, sondern von anderen Funktionen benutzt.

Was ist FZF?

FZF (Fuzzy Finder) ist ein Terminal-Tool, das interaktive Auswahl-Menues erzeugt. Du kennst Dropdown-Menues aus Websites – FZF ist das Gleiche, aber im Terminal.

# Installation
brew install fzf

# Einfachstes Beispiel: Datei waehlen
ls | fzf

# Mit Preview (zeigt Dateiinhalt rechts)
ls | fzf --preview="cat {}" --preview-window=right:50%

Wichtige FZF-Flags, die obs-f nutzt:

FlagWas es tutBeispiel
--promptText vor dem Suchfeld--prompt="waehle > "
--headerUeberschrift oben im Menue--header="Fabric Patterns"
--previewShell-Befehl, der rechts ausgefuehrt wird--preview="cat {}"
--preview-windowWo/wie gross die Preview ist--preview-window=right:50%:wrap
--heightWie viel Terminal-Hoehe FZF nutzt--height=80%
--borderRahmen um das Menue--border=rounded
--no-multiNur 1 Auswahl erlaubt--no-multi
--reverseListe von oben nach unten--reverse
--ansiFarbcodes in der Liste erlauben--ansi
--delimiterTrennzeichen fuer Spalten--delimiter='|'
--with-nthNur bestimmte Spalten anzeigen--with-nth=2
--no-infoKeine Zaehler-Zeile unten--no-info

Was ist Fabric?

Fabric ist ein CLI-Tool von Daniel Miessler. Es hat ueber 200 vordefinierte AI-Analyse-Patterns. Jedes Pattern ist ein Ordner mit einer system.md Datei – einem System-Prompt.

# Installation
go install github.com/danielmiessler/fabric@latest
# oder auf macOS:
brew install fabric-ai

# Einfachstes Beispiel: Text zusammenfassen
echo "Langer Text..." | fabric -p summarize

# Pattern-Liste anzeigen
ls ~/.config/fabric/patterns/

Wie ein Pattern aufgebaut ist:

~/.config/fabric/patterns/
  extract_wisdom/
    system.md          ← Das ist der System-Prompt
  summarize/
    system.md
  analyze_prose/
    system.md
  ... (239 weitere)

Die system.md enthaelt Anweisungen fuer die AI, z.B.:

# IDENTITY and PURPOSE
You are a wisdom extraction service...

# STEPS
1. Extract surprising insights
2. Find the most important ideas
...

# OUTPUT FORMAT
- Use markdown
- Section: IDEAS, INSIGHTS, QUOTES...

Was ist ein Heredoc?

Ein Heredoc (<<EOF ... EOF) schreibt mehrzeiligen Text in eine Datei oder Variable. obs-f nutzt es fuer die Kontext-Datei:

cat > datei.md <<EOF
# Ueberschrift
Inhalt mit $variablen die ersetzt werden.
EOF

Was ist eine Pipe?

Die Pipe (|) schickt die Ausgabe eines Befehls als Eingabe an den naechsten:

# Dateien sammeln -> an Fabric schicken
cat datei1.md datei2.md | fabric -p summarize
#     ^ Output              ^ wird hier Input

Was ist Obsidian CLI?

Obsidian CLI ist eine Kommandozeilen-Schnittstelle, die ab Obsidian v1.12 verfuegbar ist. Sie erlaubt dir, JavaScript direkt in Obsidians Metadata Cache auszufuehren – also Dateien, Tags und Frontmatter abzufragen, ohne das Dateisystem direkt durchsuchen zu muessen.

Warum ist das wichtig fuer obs-f? Obsidian hat einen internen Cache aller Dateien mit Metadaten (Tags, Frontmatter, Aenderungsdatum). Ueber die CLI kann obs-f diesen Cache in Millisekunden abfragen, statt tausende Dateien per find zu durchsuchen.

Installation

Voraussetzung: Obsidian v1.12 oder neuer.

macOS (einmalig):

# Obsidian-Binary zum PATH hinzufuegen
echo 'export PATH="/Applications/Obsidian.app/Contents/MacOS:$PATH"' >> ~/.zprofile

# Terminal neu starten oder:
source ~/.zprofile

# Testen
obsidian --version

Linux:

# AppImage: Obsidian binary ist im AppImage enthalten
# Flatpak: flatpak run md.obsidian.Obsidian --help
echo 'alias obsidian="/pfad/zu/obsidian"' >> ~/.bashrc

Wie obs-f die CLI nutzt

obs-f verwendet den eval-Befehl, um JavaScript in Obsidian auszufuehren:

# Grundsyntax
obsidian eval --vault "MeinVault" --code 'JavaScript-Code'

# Beispiel: Alle heutigen Markdown-Dateien auflisten
obsidian eval --vault "Akademie" --code 'JSON.stringify(
    app.vault.getFiles()
        .filter(f => f.extension === "md" && f.stat.mtime > Date.now() - 86400000)
        .map(f => f.path)
)'

Wichtige Objekte im eval-Kontext:

ObjektZugriff aufBeispiel
app.vaultAlle Dateien im Vaultapp.vault.getFiles()
app.metadataCacheTags, Frontmatter, Linksapp.metadataCache.getTags()
f.stat.mtimeAenderungszeitpunkt (Unix-ms)f.stat.mtime > 1708300800000
f.extensionDateiendungf.extension === 'md'
f.pathRelativer Pfad im Vaultf.path.includes('Marketing')

Der _obs_eval Helper:

_obs_eval() {
    local vault="$1" code="$2"
    obsidian eval --vault "$(basename "$vault")" --code "$code" 2>/dev/null
}

Wenn Obsidian CLI nicht verfuegbar ist, faellt obs-f automatisch auf Filesystem-Suche mit find zurueck (siehe Funktion 2).

Obsidian CLI Diagramm

flowchart LR
    A["obs-f Funktion"] --> B["_obs_eval Helper"]
    B --> C["obsidian eval --vault --code"]
    C --> D["Obsidian Metadata Cache"]
    D --> E["JSON-Array mit Dateipfaden"]
    E --> F["obs-f verarbeitet Ergebnis"]

    style D fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
            

Abb. 2: Obsidian CLI — Vom Funktionsaufruf zum Metadata Cache

Alle Shell-Funktionen im Überblick

obs-f besteht aus 8 Funktionen. Hier ist die Aufruf-Hierarchie:

obs-f() ← Hauptfunktion (du tippst das) | +-- _obs_f_pick_tag() ← Tag/Ordner-Filter per FZF | +-- _obs_f_single() ← Einzeldatei-Modus | +-- _obs_f_pick_file() ← Datei waehlen per FZF | +-- _obs_f_pick_fabric_pattern() ← Pattern waehlen per FZF | +-- _obs_f_claude_handoff() ← Claude Code Uebergabe | +-- _obs_f_run() ← Batch-Modus (mehrere Dateien) +-- _obs_f_collect() ← Dateien nach Zeit/Filter sammeln +-- _obs_f_pick_fabric_pattern() ← Pattern waehlen per FZF +-- _obs_f_claude_handoff() ← Claude Code Uebergabe

Funktions-Hierarchie als Diagramm

flowchart TD
    OBS["obs-f\nHauptfunktion"] --> TAG["_obs_f_pick_tag\nTag/Ordner-Filter"]
    OBS --> SINGLE["_obs_f_single\nEinzeldatei-Modus"]
    OBS --> RUN["_obs_f_run\nBatch-Modus"]

    SINGLE --> PICK["_obs_f_pick_file\nDatei waehlen"]
    SINGLE --> PAT1["_obs_f_pick_fabric_pattern\nPattern waehlen"]
    SINGLE --> HAND1["_obs_f_claude_handoff\nClaude Uebergabe"]

    RUN --> COLL["_obs_f_collect\nDateien sammeln"]
    RUN --> PAT2["_obs_f_pick_fabric_pattern\nPattern waehlen"]
    RUN --> HAND2["_obs_f_claude_handoff\nClaude Uebergabe"]

    COLL --> EVAL["_obs_eval\nObsidian CLI"]

    style OBS fill:#4CAF50,stroke:#2E7D32,color:#fff,stroke-width:3px
    style PAT1 fill:#FF9800,stroke:#E65100,color:#fff
    style PAT2 fill:#FF9800,stroke:#E65100,color:#fff
    style HAND1 fill:#2196F3,stroke:#0D47A1,color:#fff
    style HAND2 fill:#2196F3,stroke:#0D47A1,color:#fff
            

Abb. 3: Funktions-Hierarchie — 8 Funktionen und ihre Abhängigkeiten

Funktion 1: obs-f – Hauptfunktion

Was sie tut: Zeigt das Rezept-Menue, parst die Auswahl und ruft den richtigen Helper auf.

Aufgerufen durch: Dich im Terminal.

Das Rezept-Array

Das Herzstuck ist ein Array aus Pipe-getrennten Strings. Jeder String ist ein Rezept:

local recipes=(
    "HEUTE|Zusammenfassung|Alle heutigen Dateien zusammenfassen|today||summarize"
    "HEUTE|Learnings|Key Insights von heute extrahieren|today||extract_wisdom"
    "HEUTE|Nur Marketing|Heutige Marketing-Dateien|today|marketing|summarize"
    "HEUTE|Nach Tag...|Tag waehlen -> heutige Dateien|today|__TAG__|summarize"
    "WOCHE|Zusammenfassung|7-Tage Ueberblick|week||summarize"
    "BATCH|Letzte 10|Die 10 neuesten zusammenfassen|recent||summarize|10"
    "EINZELN|Datei -> Pattern|Datei waehlen, dann Pattern|__SINGLE__|||"
    "CLAUDE|Heute|Claude Conversations von heute|today||summarize"
    "VAULT|Conversations aufraeumen|obs-ai-digest --batch 10|__CMD__|obs-ai-digest --batch 10||"
)

Format: KATEGORIE|NAME|BESCHREIBUNG|MODUS|FILTER|PATTERN[|LIMIT]

FeldBedeutungBeispielwerte
KATEGORIEGruppe im FZF-MenueHEUTE, WOCHE, BATCH, EINZELN, CLAUDE, VAULT
NAMEAnzeigenameZusammenfassung, Learnings
BESCHREIBUNGErklaerungstextAlle heutigen Dateien zusammenfassen
MODUSZeitfilter fuer _obs_f_collecttoday, week, recent, __SINGLE__, __CMD__
FILTEROrdner- oder Tag-Filterleer, marketing, kurse, __TAG__
PATTERNFabric-Patternsummarize, extract_wisdom
LIMITMax. Dateien (optional)10, 20

Spezial-Modi:

ModusVerhalten
__SINGLE__Ruft _obs_f_single() auf – Einzeldatei-Modus
__CMD__Fuehrt den FILTER-Wert als Shell-Befehl aus
__TAG__Oeffnet _obs_f_pick_tag() zur Tag-Auswahl

Das Display bauen

# Feste Spaltenbreiten mit printf
printf '  %-10s  %-24s  %s' "$cat" "$name" "$desc"
# Ergibt z.B.: "  HEUTE       Zusammenfassung           Alle heutigen Dateien"

Kategorien werden mit Trennlinien gruppiert:

if [[ "$cat" != "$last_cat" ]]; then
    display_lines+=("--- ${cat} ---")
    last_cat="$cat"
fi

Rezept parsen

Nach der FZF-Auswahl wird das Rezept in seine Bestandteile zerlegt:

# Zsh parameter expansion: ${var%%|*} = alles VOR dem ersten |
#                          ${var#*|}  = alles NACH dem ersten |

local _r="$found_recipe"
local _skip="${_r%%|*}"; _r="${_r#*|}"   # CAT (uebersprungen)
_skip="${_r%%|*}"; _r="${_r#*|}"          # NAME (uebersprungen)
_skip="${_r%%|*}"; _r="${_r#*|}"          # DESC (uebersprungen)
local r_mode="${_r%%|*}"; _r="${_r#*|}"   # today/week/recent
local r_filter="${_r%%|*}"; _r="${_r#*|}" # marketing/kurse/...
local r_pattern="${_r%%|*}"               # summarize/extract_wisdom
Warum nicht einfach IFS='|' read? In Zsh funktioniert IFS='|' beim Splitting anders als in Bash. Die ${var%%|*} / ${var#*|} Methode ist zuverlaessiger.

FZF Spalten-Extraktion

# printf '  %-10s  %-24s  %s' erzeugt feste Spalten:
# Zeichen  3-12 = Kategorie (10 Zeichen)
# Zeichen 15-38 = Name (24 Zeichen)

local sel_cat=$(echo "$selection" | cut -c3-12 | sed 's/[[:space:]]*$//')
local sel_name=$(echo "$selection" | cut -c15-38 | sed 's/[[:space:]]*$//')

Funktion 2: _obs_f_collect – Dateien sammeln

Was sie tut: Liefert Dateipfade basierend auf Zeitraum und Filter.

Aufgerufen durch: _obs_f_run()

#NameWerteBeispiel
$1modetoday, week, recenttoday
$2filterOrdnername oder Tagmarketing
$3limitMax. Dateien10
$4include_claude0/1/only0

Primaerer Weg: Obsidian CLI eval

local js_code="JSON.stringify(
    app.vault.getFiles()
        .filter(f =>
            f.extension === 'md'
            && !f.path.includes('_Archive')
            && !f.path.includes('_templates')
            && !f.path.includes('.obsidian')
            ${js_claude_filter}      # Conversations ein/aus
            ${js_time_filter}        # Heute/Woche/alle
            ${js_path_filter}        # Ordner-Filter
        )
        .sort((a,b) => b.stat.mtime - a.stat.mtime)
        ${js_limit}                  # .slice(0, N)
        .map(f => f.path)
)"

local result=$(_obs_eval "$vault" "$js_code")

Zeitfilter (JavaScript):

case "$mode" in
    today)
        local today_start_ms=$(date -j -f "%Y-%m-%d %H:%M:%S" \
            "$(date +%Y-%m-%d) 00:00:00" "+%s" 2>/dev/null)000
        js_time_filter="&&f.stat.mtime>${today_start_ms}"
        ;;
    week)
        local week_ms=$(( $(date +%s) - 7 * 86400 ))000
        js_time_filter="&&f.stat.mtime>${week_ms}"
        ;;
esac

Ordner-Filter (JavaScript):

case "$filter" in
    marketing)  js_path_filter="&&f.path.includes('20_Marketing')" ;;
    kurse)      js_path_filter="&&f.path.includes('10_Kurse')" ;;
    wissen)     js_path_filter="&&f.path.includes('30_Wissen')" ;;
    strategie)  js_path_filter="&&f.path.includes('50_strategie')" ;;
    *)
        # Tag-basierter Filter via Metadata Cache
        js_path_filter="&&(()=>{
            const c=app.metadataCache.getFileCache(f);
            const t=c?.frontmatter?.tags;
            return Array.isArray(t)
                ? t.some(x=>x.toLowerCase().includes('${filter}'))
                : typeof t==='string' && t.includes('${filter}')
        })()"
        ;;
esac

Fallback: Filesystem

case "$mode" in
    today)
        find "$vault" -name '*.md' -type f | while IFS= read -r ff; do
            [[ "$(stat -f '%Sm' -t '%Y-%m-%d' "$ff")" == "$today" ]] && echo "$ff"
        done
        ;;
    week)
        find "$vault" -name '*.md' -type f -mtime -7
        ;;
    recent)
        find "$vault" -name '*.md' -type f \
            -exec stat -f '%m|%N' {} \; | sort -rn | head -${limit} | cut -d'|' -f2
        ;;
esac

Entscheidungsbaum

flowchart TD
    START["_obs_f_collect aufgerufen"] --> CLI{"Obsidian CLI\nverfuegbar?"}
    CLI -->|"Ja"| EVAL["_obs_eval: JavaScript\nim Metadata Cache"]
    CLI -->|"Nein"| FS["Fallback:\nFilesystem mit find"]

    EVAL --> MODE{"mode Parameter?"}
    FS --> MODE2{"mode Parameter?"}

    MODE -->|"today"| T1["mtime > heute 00:00\nals Unix-ms"]
    MODE -->|"week"| W1["mtime > vor 7 Tagen\nals Unix-ms"]
    MODE -->|"recent"| R1["Alle Dateien\n.slice 0 bis limit"]

    MODE2 -->|"today"| T2["stat Datum == heute"]
    MODE2 -->|"week"| W2["find -mtime -7"]
    MODE2 -->|"recent"| R2["stat + sort + head"]

    T1 --> FILTER{"filter\ngesetzt?"}
    W1 --> FILTER
    R1 --> FILTER

    FILTER -->|"Ordner"| PATH["path.includes\nz.B. 20_Marketing"]
    FILTER -->|"Tag"| META["metadataCache\nfrontmatter.tags"]
    FILTER -->|"Leer"| ALL["Alle passenden\nDateien"]

    PATH --> OUT["JSON-Array\nmit Pfaden"]
    META --> OUT
    ALL --> OUT

    style EVAL fill:#4CAF50,stroke:#2E7D32,color:#fff
    style FS fill:#FF9800,stroke:#E65100,color:#fff
            

Abb. 4: Entscheidungsbaum — Obsidian CLI vs. Filesystem Fallback

Funktion 3: _obs_f_pick_fabric_pattern – Pattern wählen

Was sie tut: Zeigt ein FZF-Menue mit 28 kuratierten Fabric-Patterns in 7 Kategorien.

Gibt zurueck: Den Pattern-Namen als String (z.B. extract_wisdom)

Vollstaendiger Code

_obs_f_pick_fabric_pattern() {
    local PATTERNS_DIR="$HOME/.config/fabric/patterns"

    local -a CURATED=(
        "-- ANALYSIEREN & ERKLAEREN ---|"
        "extract_wisdom               | Weisheit, Ideen, Zitate extrahieren"
        "extract_insights             | Die 10 ueberraschendsten Kernerkenntnisse"
        "analyze_prose                | Schreibqualitaet bewerten"
        "analyze_claims               | Behauptungen pruefen (A-F Rating)"
        "analyze_paper                | Wissenschaftliche Rigor-Analyse"
        "analyze_presentation         | Praesentation kritisch bewerten"
        "analyze_tech_impact          | Technologie-Impact analysieren"
        "find_logical_fallacies       | Logikfehler finden"
        "explain_docs                 | Dokumentation erklaeren"
        "explain_terms                | Glossar erstellen"
        "rate_content                 | Qualitaets-Rating (1-10)"
        "-- ZUSAMMENFASSEN ------------|"
        "summarize                    | 1-Satz-Summary + 10 Hauptpunkte"
        "create_summary               | Strukturierte Zusammenfassung"
        "create_5_sentence_summary    | 5-Satz-Zusammenfassung"
        "extract_core_message         | Kernbotschaft auf den Punkt"
        "-- VERGLEICHEN ---------------|"
        "compare_and_contrast         | Gemeinsamkeiten & Unterschiede"
        "-- CONTENT ERSTELLEN ---------|"
        "write_essay                  | Essay im Paul-Graham-Stil"
        "create_keynote               | Keynote erstellen"
        "write_micro_essay            | Micro-Essay (< 300 Woerter)"
        "improve_writing              | Text ueberarbeiten"
        "enrich_blog_post             | Blog-Post verbessern"
        "create_newsletter_entry      | Newsletter-Abschnitt"
        "-- EXTRAHIEREN ---------------|"
        "extract_ideas                | 20-50 ueberraschende Ideen"
        "extract_recommendations      | Handlungsempfehlungen"
        "extract_questions            | Offene Fragen identifizieren"
        "create_tags                  | Tags generieren"
        "-- LERNEN --------------------|"
        "create_flash_cards           | Lernkarten erstellen"
        "create_quiz                  | Quiz-Fragen generieren"
        "-- ALLE ----------------------|"
        "ALLE_PATTERNS                | Alle Fabric Patterns durchsuchen"
    )

    local choice
    choice=$(printf '%s\n' "${CURATED[@]}" | fzf \
        --prompt="Pattern waehlen > " \
        --header="Fabric Pattern fuer die Analyse" \
        --preview='p=$(echo {} | awk "{print \$1}");
                  f="'"$PATTERNS_DIR"'/$p/system.md";
                  if [[ -f "$f" ]]; then
                    printf "\033[1;33m-- %s --\033[0m\n\n" "$p"
                    head -30 "$f"
                  else
                    echo "Kategorie-Header"
                  fi' \
        --preview-window=right:50%:wrap \
        --height=80% --border=rounded --no-multi)

    [[ -z "$choice" ]] && return 1
    local selected=$(echo "$choice" | awk '{print $1}')
    [[ "$selected" == "--" ]] && return 1

    if [[ "$selected" == "ALLE_PATTERNS" ]]; then
        selected=$(ls "$PATTERNS_DIR" | fzf \
            --prompt="Pattern suchen > " \
            --header="Alle Fabric Patterns durchsuchen" \
            --preview='...' \
            --preview-window=right:50%:wrap \
            --height=80% --border=rounded --no-multi)
        [[ -z "$selected" ]] && return 1
    fi

    echo "$selected"
}

Die 7 Pattern-Kategorien

mindmap
  root((Fabric Patterns))
    Analysieren
      extract_wisdom
      extract_insights
      analyze_prose
      analyze_claims
      find_logical_fallacies
      rate_content
    Zusammenfassen
      summarize
      create_summary
      create_5_sentence_summary
      extract_core_message
    Vergleichen
      compare_and_contrast
    Content Erstellen
      write_essay
      create_keynote
      improve_writing
      enrich_blog_post
    Extrahieren
      extract_ideas
      extract_recommendations
      extract_questions
      create_tags
    Lernen
      create_flash_cards
      create_quiz
    Alle
      ALLE_PATTERNS
            

Abb. 5: Die 7 Pattern-Kategorien mit allen 28 kuratierten Patterns

Funktion 4: _obs_f_run – Batch-Modus

Was sie tut: Sammelt Dateien, oeffnet Pattern-FZF, laesst Fabric laufen, bietet Claude Handoff an.

Vollstaendiger Code

_obs_f_run() {
    local mode="$1" filter="$2" pattern="$3" limit="${4:-0}" include_claude="${5:-0}"

    # --- Dateien sammeln ---
    local files=()
    while IFS= read -r f; do
        [[ -n "$f" ]] && files+=("$f")
    done < <(_obs_f_collect "$mode" "$filter" "$limit" "$include_claude")

    if [[ ${#files[@]} -eq 0 ]]; then
        echo "Keine Dateien gefunden."
        return 1
    fi

    echo "${#files[@]} Dateien gesammelt"

    # --- Pattern waehlen via FZF ---
    pattern=$(_obs_f_pick_fabric_pattern)
    [[ $? -ne 0 || -z "$pattern" ]] && { echo "Abgebrochen."; return 0; }

    echo "Fabric: ${pattern} (deutsch) ..."

    # --- Alle Dateien durch Fabric schicken ---
    local fabric_output
    fabric_output=$({
        echo "WICHTIG: Antworte komplett auf Deutsch."
        echo ""
        for f in "${files[@]}"; do
            printf '\n\n--- %s ---\n\n' "$(basename "$f" .md)"
            cat "$f" 2>/dev/null
        done
    } | "$_OBS_FABRIC_BIN" -p "$pattern")

    echo "$fabric_output"
    _obs_f_claude_handoff "$fabric_output" "$pattern" "${#files[@]}"
}

Datenfluss durch die Fabric-Pipe

flowchart LR
    DE["Deutsch-Instruktion\nWICHTIG: Antworte\nauf Deutsch"] --> PIPE["Pipe"]
    F1["Datei 1\n--- name1 ---\nInhalt"] --> PIPE
    F2["Datei 2\n--- name2 ---\nInhalt"] --> PIPE
    F3["Datei N\n--- nameN ---\nInhalt"] --> PIPE

    PIPE --> FAB["fabric -p pattern\nz.B. extract_wisdom"]
    FAB --> OUT["Fabric Output\nStrukturierte Analyse\nauf Deutsch"]
    OUT --> TERM["Terminal-Ausgabe"]
    OUT --> CTX["Kontext-Datei\nobs-f-context.md"]
    CTX --> CC["Claude Code"]

    style FAB fill:#9C27B0,stroke:#4A148C,color:#fff,stroke-width:3px
    style CC fill:#2196F3,stroke:#0D47A1,color:#fff
            

Abb. 6: Datenfluss — Von der Deutsch-Instruktion über Fabric bis Claude Code

Funktion 5: _obs_f_claude_handoff – Claude Code Übergabe

Was sie tut: Fragt "Weiter mit Claude Code? [j/N]". Bei ja: schreibt Output in eine Datei und startet Claude Code.

_obs_f_claude_handoff() {
    local output="$1" pattern="$2" file_count="$3"

    echo ""
    printf "  -> Weiter mit Claude Code? [j/N] "
    read -k1 answer       # Zsh: genau 1 Zeichen lesen
    echo ""

    [[ "$answer" != [jJyY] ]] && return 0

    # Kontext-Datei schreiben
    local ctx_file="$HOME/PAI/PAI_DIRECTORY/.state/obs-f-context.md"
    mkdir -p "$(dirname "$ctx_file")"
    cat > "$ctx_file" <<CTXEOF
# Fabric-Analyse: $pattern
**Dateien:** $file_count | **Zeitpunkt:** $(date '+%H:%M %d.%m.%Y')

---

$output
CTXEOF

    echo "  Claude Code startet..."
    claude "Lies die Datei $ctx_file - das ist eine Fabric-Analyse \
($pattern) meines Obsidian Vaults. Hilf mir basierend auf dieser Analyse weiter."
}
Warum Kontext-Datei statt Inline? Shell-Argumente haben eine Laengenbegrenzung (~262.144 Zeichen auf macOS). Die Kontext-Datei hat keine Groessenbegrenzung.

Claude Handoff Ablauf

flowchart TD
    Q["Weiter mit Claude Code?\nread -k1 answer"] --> D{"Antwort?"}
    D -->|"j/J/y/Y"| WRITE["Kontext-Datei schreiben\nobs-f-context.md"]
    D -->|"Alles andere"| STOP["return 0\nFertig"]

    WRITE --> CONTENT["Heredoc mit\nPattern + Dateianzahl\n+ Zeitstempel\n+ Fabric Output"]
    CONTENT --> START["claude Befehl\nmit Verweis auf\nKontext-Datei"]
    START --> CC["Claude Code\nliest Datei\nund hilft weiter"]

    style Q fill:#FF9800,stroke:#E65100,color:#fff
    style CC fill:#2196F3,stroke:#0D47A1,color:#fff
            

Abb. 7: Claude Handoff — Vom User-Prompt zur Claude Code Session

Funktion 6: _obs_f_single – Einzeldatei-Modus

Aufgerufen durch: obs-f() bei __SINGLE__-Rezepten.

_obs_f_single() {
    local pattern="$1"
    local file=$(_obs_f_pick_file)
    [[ -z "$file" ]] && return 0

    pattern=$(_obs_f_pick_fabric_pattern)
    [[ $? -ne 0 || -z "$pattern" ]] && return 0

    local fabric_output
    fabric_output=$({
        echo "WICHTIG: Antworte komplett auf Deutsch."
        echo ""
        cat "$file"
    } | "$_OBS_FABRIC_BIN" -p "$pattern")

    echo "$fabric_output"
    _obs_f_claude_handoff "$fabric_output" "$pattern" "1"
}

Funktion 7: _obs_f_pick_file – Datei wählen

Was sie tut: Zeigt die 100 neuesten Obsidian-Dateien in einem FZF-Menue.

_obs_f_pick_file() {
    local vault="$VAULT_AKADEMIE"

    local files_json=$(_obs_eval "$vault" "JSON.stringify(
        app.vault.getFiles()
            .filter(f => f.extension==='md'
                && !f.path.includes('_Archive')
                && !f.path.includes('_templates')
                && !f.path.includes('.obsidian'))
            .sort((a,b) => b.stat.mtime - a.stat.mtime)
            .slice(0,100)
            .map(f => f.path)
    )")

    # JSON-Array -> "pfad|ordner/name" Format fuer FZF
    local file_lines=""
    file_lines=$(echo "$files_json" | python3 -c "
import json, sys, os
vault = '$vault'
for p in json.load(sys.stdin):
    full = os.path.join(vault, p)
    name = os.path.splitext(os.path.basename(p))[0]
    folder = os.path.basename(os.path.dirname(p))
    print(f'{full}|{folder}/{name}')
")

    echo "$file_lines" | fzf --height=30 --reverse --border \
        --border-label=" Datei waehlen (100 neueste) " \
        --prompt="datei > " \
        --delimiter='|' \
        --with-nth=2 | cut -d'|' -f1
}
Trick: --delimiter='|' und --with-nth=2 zeigen nur den schoenen Namen, geben aber den vollen Pfad zurueck.

Funktion 8: _obs_f_pick_tag – Tag/Ordner-Filter

_obs_f_pick_tag() {
    local vault="$VAULT_AKADEMIE"
    {
        echo "marketing"
        echo "kurse"
        echo "wissen"
        echo "strategie"
        echo "content"
        echo "workflows"
        echo "tools"
        echo "daily"
        echo "--------------"

        local tags_json=$(_obs_eval "$vault" "JSON.stringify(
            Object.entries(app.metadataCache.getTags())
                .sort((a,b) => b[1] - a[1])
                .slice(0,30)
                .map(e => e[0].replace('#',''))
        )")

        if [[ -n "$tags_json" ]]; then
            echo "$tags_json" | python3 -c \
                "import json,sys;[print(t) for t in json.load(sys.stdin)]"
        fi
    } | fzf --height=25 --reverse --border \
        --border-label=" Tag/Ordner Filter " \
        --prompt="filter > "
}

Eigene Patterns hinzufügen

Ins kuratierte Menue

Fuege eine Zeile ins CURATED-Array ein:

"-- MEINE KATEGORIE ----------|"
"mein_custom_pattern         | Meine eigene Analyse"

Ein neues Fabric Pattern erstellen

# Ordner anlegen
mkdir -p ~/.config/fabric/patterns/mein_custom_pattern

# system.md schreiben
cat > ~/.config/fabric/patterns/mein_custom_pattern/system.md <<'EOF'
# IDENTITY and PURPOSE
Du bist ein Experte fuer [dein Thema].

# STEPS
1. Lies den gesamten Inhalt
2. Identifiziere die 5 wichtigsten Punkte

# OUTPUT FORMAT
- Ausgabe als Markdown
- Deutsche Ueberschriften
- Maximal 500 Woerter
EOF

Globale Variablen

# Vault-Pfad
VAULT_AKADEMIE="$HOME/obsidian/Claude/Akademie"

# Fabric Binary (brew-Version, nicht Anaconda)
_OBS_FABRIC_BIN="/opt/homebrew/bin/fabric-ai"

# Fabric Patterns Verzeichnis
_OBS_FABRIC_PATTERNS="$HOME/.config/fabric/patterns"

# Claude-Conversations-Schalter (Session-persistent)
_OBS_F_CLAUDE=0

Minimal-Setup zum Nachbauen

1. Datei anlegen

touch ~/.zsh/functions/obs-f.zsh

2. Minimaler Code

Kopiere _obs_f_pick_fabric_pattern, _obs_f_claude_handoff und diese vereinfachte Hauptfunktion:

obs-f() {
    local vault="$HOME/obsidian/MeinVault"

    local files=()
    while IFS= read -r f; do
        [[ -n "$f" ]] && files+=("$f")
    done < <(find "$vault" -name "*.md" -mtime -1 -type f 2>/dev/null)

    echo "${#files[@]} Dateien gefunden"
    [[ ${#files[@]} -eq 0 ]] && return 1

    local pattern=$(_obs_f_pick_fabric_pattern)
    [[ -z "$pattern" ]] && return 0

    local output
    output=$({
        echo "WICHTIG: Antworte auf Deutsch."
        for f in "${files[@]}"; do
            printf '\n--- %s ---\n' "$(basename "$f" .md)"
            cat "$f"
        done
    } | fabric -p "$pattern")

    echo "$output"
    _obs_f_claude_handoff "$output" "$pattern" "${#files[@]}"
}

3. Laden

echo 'source ~/.zsh/functions/obs-f.zsh' >> ~/.zshrc
source ~/.zshrc
obs-f

Datei-Übersicht

DateiZweck
~/.zsh/functions/obsidian-vaults.zshAlle 8 Funktionen (Quelldatei)
~/.config/fabric/patterns/*/system.md239 Fabric Pattern-Definitionen
~/.state/obs-f-context.mdTemporaere Kontext-Datei fuer Claude Code
~/.zshrcHier wird die Funktionsdatei geladen