Files
brain-template/scripts/diagram-init.sh

336 lines
8.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# diagram-init.sh — Génère le fichier .excalidraw initial depuis un workflow.yml
# Usage : bash scripts/diagram-init.sh <workflow-name>
# Exemple : bash scripts/diagram-init.sh superoauth-tier3
# Output : draw/diagrams/<name>.excalidraw
BRAIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WORKFLOW_NAME="${1:-}"
if [[ -z "$WORKFLOW_NAME" ]]; then
echo "Usage : bash scripts/diagram-init.sh <workflow-name>"
echo "Exemple : bash scripts/diagram-init.sh superoauth-tier3"
exit 1
fi
WORKFLOW_FILE="$BRAIN_ROOT/workflows/${WORKFLOW_NAME}.yml"
OUTPUT_DIR="$BRAIN_ROOT/draw/diagrams"
OUTPUT_FILE="$OUTPUT_DIR/${WORKFLOW_NAME}.excalidraw"
if [[ ! -f "$WORKFLOW_FILE" ]]; then
echo "❌ Workflow introuvable : $WORKFLOW_FILE"
exit 1
fi
mkdir -p "$OUTPUT_DIR"
python3 - "$WORKFLOW_FILE" "$OUTPUT_FILE" << 'PYEOF'
import sys
import json
import yaml
import uuid
import time
workflow_path = sys.argv[1]
output_path = sys.argv[2]
with open(workflow_path) as f:
wf = yaml.safe_load(f)
name = wf.get("name", "workflow")
chain = wf.get("chain", [])
# Layout constants
NODE_W = 220
NODE_H = 90
NODE_GAP = 60
START_X = 40
START_Y = 120
ARROW_Y = START_Y + NODE_H // 2
# Colors
COLOR_PENDING = "#868e96" # gris — pending
COLOR_BORDER = "#343a40"
COLOR_BG_PAGE = "#f8f9fa"
elements = []
def make_id():
return str(uuid.uuid4())[:8]
# Title
elements.append({
"id": make_id(),
"type": "text",
"x": START_X,
"y": 40,
"width": len(name) * 12 + 40,
"height": 36,
"text": name,
"fontSize": 24,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top",
"strokeColor": COLOR_BORDER,
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": 1,
"version": 1,
"isDeleted": False,
"groupIds": [],
"boundElements": [],
"updated": int(time.time()),
"link": None,
"locked": False,
})
node_ids = {}
for i, step in enumerate(chain):
n = step.get("step", i + 1)
stype = step.get("type", "")
angle = step.get("story_angle", "")
agents = step.get("agents", [])
gate = step.get("gate", None)
x = START_X + i * (NODE_W + NODE_GAP)
y = START_Y
node_id = f"{name}-step-{n}"
node_ids[n] = {"id": node_id, "x": x, "y": y}
# Gate badge (above node)
if gate:
gate_label = "⚡ gate:human" if gate == "human" else f"⚡ gate:{gate}"
elements.append({
"id": make_id(),
"type": "text",
"x": x,
"y": y - 28,
"width": NODE_W,
"height": 20,
"text": gate_label,
"fontSize": 13,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "top",
"strokeColor": "#f39c12",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": i + 100,
"version": 1,
"isDeleted": False,
"groupIds": [],
"boundElements": [],
"updated": int(time.time()),
"link": None,
"locked": False,
})
# Truncate story_angle
label_angle = (angle[:38] + "…") if len(angle) > 40 else angle
agents_str = " · ".join(agents[:3]) if agents else ""
label_text = f"step {n} [{stype}]\n{label_angle}\n⬜ pending"
elements.append({
"id": node_id,
"type": "rectangle",
"x": x,
"y": y,
"width": NODE_W,
"height": NODE_H,
"backgroundColor": COLOR_PENDING,
"strokeColor": COLOR_BORDER,
"fillStyle": "solid",
"strokeWidth": 2,
"roughness": 0,
"opacity": 80,
"angle": 0,
"seed": i + 10,
"version": 1,
"isDeleted": False,
"groupIds": [],
"boundElements": [],
"updated": int(time.time()),
"link": None,
"locked": False,
})
# Label inside node
elements.append({
"id": make_id(),
"type": "text",
"x": x + 10,
"y": y + 8,
"width": NODE_W - 20,
"height": NODE_H - 16,
"text": label_text,
"fontSize": 12,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top",
"strokeColor": "#ffffff",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": i + 200,
"version": 1,
"isDeleted": False,
"groupIds": [],
"boundElements": [],
"updated": int(time.time()),
"link": None,
"locked": False,
})
# Agents badge (below node)
if agents_str:
elements.append({
"id": make_id(),
"type": "text",
"x": x,
"y": y + NODE_H + 6,
"width": NODE_W,
"height": 18,
"text": agents_str,
"fontSize": 11,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "top",
"strokeColor": "#868e96",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": i + 300,
"version": 1,
"isDeleted": False,
"groupIds": [],
"boundElements": [],
"updated": int(time.time()),
"link": None,
"locked": False,
})
# Arrows between nodes
for i in range(len(chain) - 1):
n_from = chain[i].get("step", i + 1)
n_to = chain[i + 1].get("step", i + 2)
if n_from not in node_ids or n_to not in node_ids:
continue
from_x = node_ids[n_from]["x"] + NODE_W
to_x = node_ids[n_to]["x"]
arr_y = START_Y + NODE_H // 2
# Detect type drift (code→deploy or deploy→code)
type_from = chain[i].get("type", "")
type_to = chain[i + 1].get("type", "")
is_drift = (type_from != type_to)
arrow_color = "#e74c3c" if is_drift else "#495057"
arr_id = make_id()
elements.append({
"id": arr_id,
"type": "arrow",
"x": from_x,
"y": arr_y,
"width": to_x - from_x,
"height": 0,
"points": [[0, 0], [to_x - from_x, 0]],
"strokeColor": arrow_color,
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": is_drift and 3 or 2,
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": i + 400,
"version": 1,
"isDeleted": False,
"groupIds": [],
"boundElements": [],
"updated": int(time.time()),
"link": None,
"locked": False,
"startBinding": None,
"endBinding": None,
"lastCommittedPoint": None,
"startArrowhead": None,
"endArrowhead": "arrow",
})
# Drift label
if is_drift:
mid_x = from_x + (to_x - from_x) // 2 - 40
elements.append({
"id": make_id(),
"type": "text",
"x": mid_x,
"y": arr_y - 22,
"width": 100,
"height": 18,
"text": f"⚠️ {type_from}→{type_to}",
"fontSize": 11,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "top",
"strokeColor": "#e74c3c",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": i + 500,
"version": 1,
"isDeleted": False,
"groupIds": [],
"boundElements": [],
"updated": int(time.time()),
"link": None,
"locked": False,
})
excalidraw = {
"type": "excalidraw",
"version": 2,
"source": "brain/diagram-init.sh",
"elements": elements,
"appState": {
"gridSize": None,
"viewBackgroundColor": COLOR_BG_PAGE,
},
"files": {}
}
with open(output_path, "w") as f:
json.dump(excalidraw, f, indent=2, ensure_ascii=False)
print(f"✅ {output_path}")
print(f" {len(chain)} steps — {len(elements)} éléments générés")
PYEOF
STATUS=$?
if [[ $STATUS -eq 0 ]]; then
echo ""
echo "→ Ouvrir dans draw.tetardtek.com ou commiter :"
echo " git -C $BRAIN_ROOT/draw add diagrams/${WORKFLOW_NAME}.excalidraw"
echo " git -C $BRAIN_ROOT/draw commit -m \"diagram: init ${WORKFLOW_NAME}\""
fi
exit $STATUS