<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>
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
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'
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
#!/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"
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>