<aside> 🎯

Objectif : Piloter Claude Code (CLI) depuis Notion via n8n. Pipeline complet : Notion page → webhook n8n → claude -p sur VPS → résultat écrit dans Notion.

</aside>


🗺️ ARCHITECTURE CIBLE

graph LR
    A["Notion DB<br>Tasks Dispatch"] -->|"Trigger: page créée<br>Status = TODO"| B["n8n Workflow<br>CoworkDispatch"]
    B -->|"Execute Command<br>ou SSH"| C["/root/dispatch-cc.sh<br>'task'"]
    C -->|"claude -p 'task'<br>--output-format json"| D["Claude Code CLI<br>Sonnet 4.6"]
    D -->|"Exécute actions<br>VPS"| E["Résultat"]
    E -->|"PATCH Notion page<br>Status = DONE"| A

📋 ÉTAPE 1 — Scripts VPS à déployer

1.1 notion-ai.sh (v2 — robuste, caractères spéciaux)

Déployer sur VPS : nano /root/notion-ai.sh puis chmod +x /root/notion-ai.sh

#!/bin/bash
# 🔵 NOTION-2API v2 — Caractères spéciaux + JSON robuste
# Usage: ./notion-ai.sh @neo-opus "Prompt avec caractères: éàü"

API_URL="<https://notion-ai.notreunivers.cloud/v1/chat/completions>"
API_KEY="4s8BOPaKPyKstXUJUQcibe47g7kMHOTK"

declare -A MODELS=(
    ["neo-opus"]="claude-opus-4.1"
    ["neo-sonnet"]="claude-sonnet-4.5"
    ["neo-gpt5"]="gpt-5"
    ["neo-gpt4"]="gpt-4.1"
    ["neo-gemini"]="gemini-2.5-flash"
    ["neo-ollama"]="qwen2.5:7b"
    ["neo-openclaw"]="openclaw"
    ["neo-phenix-gpt"]="phenix-gpt"
    ["neo-phenix-claude"]="phenix-claude"
    ["neo-phenix-gemini"]="phenix-gemini"
    ["neo-phenix-ollama"]="phenix-ollama"
)

MODEL_ALIAS="${1#@}"
PROMPT="$2"
[ -z "$PROMPT" ] && { PROMPT="$1"; MODEL_ALIAS="neo-opus"; }

MODEL="${MODELS[$MODEL_ALIAS]}"
[ -z "$MODEL" ] && { echo "❌ Model: $MODEL_ALIAS"; echo "Use: ${!MODELS[@]}"; exit 1; }

echo "🔵 $MODEL_ALIAS → notion-2api"
echo "---"

jq -n \\
  --arg model "$MODEL" \\
  --arg content "$PROMPT" \\
  '{model: $model, messages: [{role: "user", content: $content}]}' \\
  | curl -s "$API_URL" \\
    -H "Authorization: Bearer $API_KEY" \\
    -H "Content-Type: application/json" \\
    -d @- \\
    | jq -r '.choices[0].message.content // .error.message'

1.2 dispatch-cc.sh — Dispatch Claude Code CLI (async)

Déployer : nano /root/dispatch-cc.sh puis chmod +x /root/dispatch-cc.sh

#!/bin/bash
# 🚀 COWORKDISPATCH — Lance claude -p en async, écrit résultat dans fichier
# Usage: ./dispatch-cc.sh "task_id" "prompt" ["workdir"]
# Résultat : /tmp/cc-result-{task_id}.json

TASK_ID="$1"
PROMPT="$2"
WORKDIR="${3:-/root}"
RESULT_FILE="/tmp/cc-result-${TASK_ID}.json"
LOG_FILE="/tmp/cc-log-${TASK_ID}.txt"

# Écrire status initial
echo '{"status":"running","task_id":"'"$TASK_ID"'","started":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"}' > "$RESULT_FILE"

# Lancer claude en background
(
  cd "$WORKDIR" || exit 1
  # Trouver claude (npm global)
  CLAUDE_BIN=$(which claude 2>/dev/null || ls /root/.npm/bin/claude 2>/dev/null || ls /usr/local/bin/claude 2>/dev/null)
  
  if [ -z "$CLAUDE_BIN" ]; then
    echo '{"status":"error","error":"claude not found in PATH","task_id":"'"$TASK_ID"'"}' > "$RESULT_FILE"
    exit 1
  fi
  
  # Mode print non-interactif
  RESULT=$("$CLAUDE_BIN" -p "$PROMPT" --output-format json 2>"$LOG_FILE")
  EXIT_CODE=$?
  
  if [ $EXIT_CODE -eq 0 ]; then
    echo '{"status":"done","task_id":"'"$TASK_ID"'","result":' > /tmp/cc-tmp.json
    echo "$RESULT" >> /tmp/cc-tmp.json  
    echo '}' >> /tmp/cc-tmp.json
    jq -s '{status:"done",task_id:"'"$TASK_ID"'",result:.[0]}' <<< "$RESULT" > "$RESULT_FILE" 2>/dev/null \\
      || echo '{"status":"done","task_id":"'"$TASK_ID"'","result":"'"$(echo $RESULT | head -c 2000)"'"}' > "$RESULT_FILE"
  else
    echo '{"status":"error","task_id":"'"$TASK_ID"'","error":"'"$(cat $LOG_FILE | head -c 500)"'"}' > "$RESULT_FILE"
  fi
) &

echo "🚀 Task $TASK_ID lancée (PID: $!)"
echo "Poll : cat $RESULT_FILE"
echo "$!"  # Retourne le PID pour tracking

1.3 poll-cc.sh — Poller le résultat d'un dispatch

#!/bin/bash
# Poll résultat dispatch-cc
# Usage: ./poll-cc.sh "task_id" [timeout_seconds]

TASK_ID="$1"
TIMEOUT="${2:-120}"
RESULT_FILE="/tmp/cc-result-${TASK_ID}.json"

echo "⏳ Polling $TASK_ID (max ${TIMEOUT}s)..."

for i in $(seq 1 $TIMEOUT); do
  STATUS=$(jq -r '.status' "$RESULT_FILE" 2>/dev/null)
  if [ "$STATUS" = "done" ] || [ "$STATUS" = "error" ]; then
    echo "✅ Résultat après ${i}s :"
    cat "$RESULT_FILE"
    exit 0
  fi
  sleep 1
done

echo "⚠️ Timeout après ${TIMEOUT}s"
cat "$RESULT_FILE"

📋 ÉTAPE 2 — Vérifier claude CLI sur VPS

Commandes à lancer en SSH :

# Trouver claude
which claude || find /root -name claude -type f 2>/dev/null
npm list -g | grep claude

# Tester mode non-interactif
claude -p "Dis bonjour en 5 mots" --output-format text

# Si ça marche :
echo "✅ claude CLI opérationnel"

<aside> ⚠️

Si claude n'est pas dans PATH lors d'exec SSH non-login : ajouter dans /root/.bashrc et utiliser bash -l -c 'claude -p ...' dans dispatch-cc.sh

</aside>