Compare commits

15 Commits

Author SHA1 Message Date
b53e134ecf fix: docs.html auto-detect API path — fonctionne local ET derriere proxy 2026-03-21 21:14:32 +01:00
9a35ddf45f fix: docs.html API path vide — fonctionne en local sans proxy Apache 2026-03-21 21:13:37 +01:00
15eab804e2 fix: VITE_BRAIN_API vide par defaut — requetes relatives au meme serveur
localhost:7700 ne marche que si le navigateur est sur la meme machine.
Avec un VITE_BRAIN_API vide, les requetes sont relatives — fonctionne
que brain-engine soit sur localhost, LAN, ou derriere un proxy.
2026-03-21 20:55:46 +01:00
45b7e0455c fix: setup.sh cree .env.local automatiquement — brain-ui pointe vers localhost:7700
Sans ce fichier, VITE_BRAIN_API est vide et les docs ne chargent pas
en local (requetes relatives = 404 sans proxy Apache).
2026-03-21 20:50:19 +01:00
73ebc50069 fix: setup.sh — build brain-ui avec npm directement (plus de build.sh) 2026-03-21 20:27:30 +01:00
e0087794c8 fix: supprimer symlinks public/docs/ — cassent le build sur tout fork
Les docs sont servies live par brain-engine depuis docs/.
Les symlinks dans public/docs/ pointaient vers des chemins absolus
de la machine de dev — crash garanti au build sur un autre poste.
2026-03-21 20:20:24 +01:00
7e4986e8c6 sync: kernel v0.8.0 → template 2026-03-21 20:03:39 +01:00
e40e22c949 sync: kernel v0.8.0 → template 2026-03-21 19:57:40 +01:00
667e84aa30 feat: template UI — onglets owner disabled avec label 'soon' + tooltip 2026-03-21 17:56:16 +01:00
a043fd0285 fix: template UI — retirer onglets owner (workflows, secrets, infra, builder) 2026-03-21 17:54:33 +01:00
1eada64913 fix: docs lien externe vers docs.html — aligné avec prod (pas de DocsView SPA) 2026-03-21 17:38:07 +01:00
0b066f729a fix: restaurer docs.html + symlinks public/docs/ (page docs séparée)
- docs.html: page HTML autonome (marked.js, sidebar, live/static mode)
- public/docs/: symlinks relatifs vers docs/*.md (portables entre machines)
- Revient sur la suppression de la PR Cortex — la page séparée est nécessaire
2026-03-21 17:31:14 +01:00
71b2be5ea9 fix: sync Cosmos zones (instance+satellite colors) depuis brain prod 2026-03-21 17:29:36 +01:00
Tetardtek-Cortex
5c060dcc1c fix: start.sh auto-build brain-ui + /health tolerant sans Ollama + docs source unique brain-engine
- start.sh: detecte brain-ui/dist absent → build auto si Node dispo, warning sinon
- start.sh: lien docs pointe vers /ui/docs (page rendue) au lieu de /docs (JSON)
- server.py /health: tolere absence table embeddings (pas d'Ollama = indexed:0, pas crash)
- server.py /docs/view: redirect 302 → /ui/docs pour navigateurs
- public/docs/ supprime: source unique = docs/ servi par brain-engine API
2026-03-21 17:16:19 +01:00
02e19fcd7c feat: brain-engine start/stop/status — lifecycle visible pour les forks
- start.sh: background par défaut, PID tracké, --foreground pour debug
- stop.sh: arrêt propre avec grace period 5s + force kill
- status.sh: running/stopped, vérification port, exit code scriptable
- getting-started: docs mis à jour (plus de nohup/kill manuels)
- .gitignore: .brain-engine.pid
2026-03-21 16:49:18 +01:00
46 changed files with 6550 additions and 107 deletions

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ brain-engine/.venv/
brain-engine/__pycache__/ brain-engine/__pycache__/
brain-engine/*.log brain-engine/*.log
brain-engine/viz_cache.json brain-engine/viz_cache.json
.brain-engine.pid
# Brain-ui build + deps # Brain-ui build + deps
brain-ui/node_modules/ brain-ui/node_modules/

View File

@@ -67,7 +67,7 @@ from pathlib import Path
import subprocess import subprocess
import asyncio import asyncio
from fastapi import FastAPI, Header, HTTPException, Query, Body, WebSocket, Request from fastapi import FastAPI, Header, HTTPException, Query, Body, WebSocket, Request
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.websockets import WebSocketDisconnect from fastapi.websockets import WebSocketDisconnect
try: try:
@@ -268,6 +268,12 @@ def health():
import sqlite3 import sqlite3
from search import DB_PATH from search import DB_PATH
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
# embeddings table is created by embed.py (requires Ollama) — optional
has_embeddings = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='embeddings'"
).fetchone()
count = 0
if has_embeddings:
count = conn.execute("SELECT COUNT(*) FROM embeddings WHERE indexed=1").fetchone()[0] count = conn.execute("SELECT COUNT(*) FROM embeddings WHERE indexed=1").fetchone()[0]
conn.close() conn.close()
return {'status': 'ok', 'indexed': count, 'uptime': uptime} return {'status': 'ok', 'indexed': count, 'uptime': uptime}
@@ -360,6 +366,12 @@ def brain_compose_tiers():
# ── Docs live — sert docs/*.md depuis le filesystem ──────────────────────────── # ── Docs live — sert docs/*.md depuis le filesystem ────────────────────────────
@app.get('/docs/view')
def docs_redirect():
"""Redirige /docs/view vers le dashboard docs (pour les navigateurs)."""
return RedirectResponse(url='/ui/docs', status_code=302)
@app.get('/docs') @app.get('/docs')
def docs_list(): def docs_list():
"""Liste les fichiers docs/*.md avec métadonnées (frontmatter group/label).""" """Liste les fichiers docs/*.md avec métadonnées (frontmatter group/label)."""

View File

@@ -61,14 +61,63 @@ else
echo " Le serveur démarre quand même (BSI, docs, endpoints basiques)." echo " Le serveur démarre quand même (BSI, docs, endpoints basiques)."
fi fi
# 5. Lancer le serveur # 5. Vérifier brain-ui (dashboard + docs)
UI_DIST="$BRAIN_ROOT/brain-ui/dist"
if [ ! -d "$UI_DIST" ]; then
echo ""
echo "⚠️ brain-ui pas buildé — le dashboard ne sera pas disponible."
if command -v node &>/dev/null && command -v npm &>/dev/null; then
echo "→ Build automatique de brain-ui..."
bash "$BRAIN_ROOT/brain-ui/build.sh"
else
echo " Node.js/npm requis pour le dashboard."
echo " Installe Node.js 18+ puis lance : bash brain-ui/build.sh"
fi
fi
# 6. Vérifier si déjà en cours (re-check après build éventuel)
PIDFILE="$BRAIN_ROOT/.brain-engine.pid"
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
echo ""
echo "⚠️ brain-engine tourne déjà (PID $(cat "$PIDFILE"))"
echo " Arrêter : bash brain-engine/stop.sh"
echo " Statut : bash brain-engine/status.sh"
exit 0
fi
# 7. Lancer le serveur
PORT="${BRAIN_PORT:-7700}" PORT="${BRAIN_PORT:-7700}"
LOGFILE="$BRAIN_ROOT/brain-engine/brain-engine.log"
echo "" echo ""
echo "=== Lancement brain-engine sur port $PORT ===" echo "=== Lancement brain-engine sur port $PORT ==="
echo " Health : http://localhost:$PORT/health" echo " Health : http://localhost:$PORT/health"
echo " Search : http://localhost:$PORT/search?q=comment+ca+marche" if [ -d "$UI_DIST" ]; then
echo " Dashboard : http://localhost:$PORT/ui/"
fi
echo " Docs : http://localhost:$PORT/ui/docs"
echo " Agents : http://localhost:$PORT/agents" echo " Agents : http://localhost:$PORT/agents"
echo "" echo ""
cd "$BRAIN_ROOT" cd "$BRAIN_ROOT"
python3 "$SCRIPT_DIR/server.py"
if [ "${1:-}" = "--foreground" ]; then
# Mode foreground (debug) — Ctrl+C pour arrêter
echo "Mode foreground — Ctrl+C pour arrêter"
python3 "$SCRIPT_DIR/server.py"
else
# Mode background (défaut) — PID tracké, log rotatif
python3 "$SCRIPT_DIR/server.py" > "$LOGFILE" 2>&1 &
ENGINE_PID=$!
echo "$ENGINE_PID" > "$PIDFILE"
sleep 1
if kill -0 "$ENGINE_PID" 2>/dev/null; then
echo "✅ brain-engine démarré (PID $ENGINE_PID)"
echo " Logs : tail -f brain-engine/brain-engine.log"
echo " Arrêter : bash brain-engine/stop.sh"
else
echo "❌ brain-engine n'a pas démarré — voir brain-engine/brain-engine.log"
rm -f "$PIDFILE"
exit 1
fi
fi

29
brain-engine/status.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
# brain-engine/status.sh — Statut rapide
# Usage : bash brain-engine/status.sh
# Exit 0 si running, 1 si stopped — utilisable dans des scripts/briefings
BRAIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
PIDFILE="$BRAIN_ROOT/.brain-engine.pid"
PORT="${BRAIN_PORT:-7700}"
if [ ! -f "$PIDFILE" ]; then
echo "brain-engine: stopped"
exit 1
fi
PID=$(cat "$PIDFILE")
if kill -0 "$PID" 2>/dev/null; then
# Vérifier que le port répond
if curl -s --max-time 2 "http://localhost:$PORT/health" > /dev/null 2>&1; then
echo "brain-engine: running (PID $PID, port $PORT)"
else
echo "brain-engine: starting (PID $PID, port $PORT pas encore prêt)"
fi
exit 0
else
rm -f "$PIDFILE"
echo "brain-engine: stopped (PID $PID stale — nettoyé)"
exit 1
fi

33
brain-engine/stop.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/bin/bash
# brain-engine/stop.sh — Arrêt propre
# Usage : bash brain-engine/stop.sh
BRAIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
PIDFILE="$BRAIN_ROOT/.brain-engine.pid"
if [ ! -f "$PIDFILE" ]; then
echo "brain-engine n'est pas démarré (pas de PID tracké)"
exit 0
fi
PID=$(cat "$PIDFILE")
if kill -0 "$PID" 2>/dev/null; then
kill "$PID"
# Attendre l'arrêt propre (max 5s)
for i in $(seq 1 10); do
kill -0 "$PID" 2>/dev/null || break
sleep 0.5
done
if kill -0 "$PID" 2>/dev/null; then
echo "⚠️ brain-engine ne répond pas — force kill"
kill -9 "$PID" 2>/dev/null
fi
rm -f "$PIDFILE"
echo "✅ brain-engine arrêté (PID $PID)"
else
rm -f "$PIDFILE"
echo "brain-engine n'était plus actif (PID $PID stale — nettoyé)"
fi

14
brain-ui/.env.example Normal file
View File

@@ -0,0 +1,14 @@
# brain-ui — variables d'environnement
# Copier vers .env.local et adapter
# Mode mock — true = pas de VPS nécessaire (laptop dev)
VITE_USE_MOCK=true
# URL de base de l'API brain-engine
# Vide = relatif à l'hôte Apache (/api proxy)
# http://localhost:7700 = brain-engine local
VITE_BRAIN_API=
# Tier actif — owner (toutes features) | pro | free
# Géré par brain-engine, pas directement ici
# BRAIN_TIER est une variable d'environnement du process brain-engine (MYSECRETS ou export)

5
brain-ui/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules/
dist/
.env.production
.env.local
.env.production

View File

@@ -1,33 +0,0 @@
#!/bin/bash
# brain-ui/build.sh — Build le dashboard pour servir via brain-engine
# Usage : bash brain-ui/build.sh
# Prérequis : Node.js 18+, npm
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "=== brain-ui — build ==="
# 1. Vérifier Node
if ! command -v node &>/dev/null; then
echo "❌ Node.js requis (18+). Installe-le : https://nodejs.org/"
exit 1
fi
# 2. Install deps
cd "$SCRIPT_DIR"
if [ ! -d "node_modules" ]; then
echo "→ Installation des dépendances..."
npm install
fi
# 3. Build (skip type check — erreurs TS pré-existantes non bloquantes)
echo "→ Build en cours..."
npx vite build
echo ""
echo "✅ brain-ui build dans dist/"
echo " Servi automatiquement par brain-engine sur /ui/"
echo " Lance : bash brain-engine/start.sh"
echo " Puis ouvre : http://localhost:7700/ui/"

5353
brain-ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

243
brain-ui/public/docs.html Normal file
View File

@@ -0,0 +1,243 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Brain — Documentation</title>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0d0d0d; color: #e5e7eb;
display: flex; height: 100vh; overflow: hidden;
}
/* Sidebar */
.sidebar {
width: 240px; flex-shrink: 0;
background: #141414; border-right: 1px solid #2a2a2a;
display: flex; flex-direction: column; overflow-y: auto;
}
.sidebar-header {
padding: 16px; border-bottom: 1px solid #2a2a2a;
display: flex; align-items: center; justify-content: space-between;
}
.sidebar-header h1 {
font-size: 14px; font-weight: 600; color: #fff;
font-family: monospace; letter-spacing: 0.05em;
}
.sidebar-header .badge {
font-size: 10px; padding: 2px 6px; border-radius: 4px;
background: #22c55e20; color: #22c55e; font-family: monospace;
}
.group-label {
font-size: 10px; font-family: monospace; color: #4b5563;
padding: 12px 16px 4px; letter-spacing: 0.1em; text-transform: uppercase;
}
.doc-link {
display: block; padding: 6px 16px; font-size: 13px; color: #9ca3af;
cursor: pointer; border: none; background: none; text-align: left;
width: 100%; transition: color 0.15s;
}
.doc-link:hover { color: #e5e7eb; }
.doc-link.active { color: #818cf8; background: rgba(99,102,241,0.12); }
/* Back link */
.back-link {
margin-top: auto; padding: 12px 16px; border-top: 1px solid #2a2a2a;
font-size: 11px; font-family: monospace;
}
.back-link a { color: #4b5563; text-decoration: none; }
.back-link a:hover { color: #6366f1; }
/* Content */
.content {
flex: 1; overflow-y: auto; padding: 2rem 3rem;
}
.content .loading { color: #4b5563; font-family: monospace; font-size: 13px; }
/* Markdown styles */
.md h1 { font-size: 1.8rem; font-weight: 700; color: #fff; margin: 0 0 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid #2a2a2a; }
.md h2 { font-size: 1.3rem; font-weight: 600; color: #e5e7eb; margin: 2rem 0 0.8rem; }
.md h3 { font-size: 1.1rem; font-weight: 600; color: #d1d5db; margin: 1.5rem 0 0.5rem; }
.md p { line-height: 1.7; margin: 0.5rem 0; color: #d1d5db; }
.md a { color: #818cf8; text-decoration: none; }
.md a:hover { text-decoration: underline; }
.md code {
font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 0.85em;
background: #1e1e1e; padding: 2px 6px; border-radius: 4px; color: #e5e7eb;
}
.md pre {
background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 8px;
padding: 1rem; overflow-x: auto; margin: 1rem 0;
}
.md pre code { background: none; padding: 0; font-size: 0.85rem; }
.md ul, .md ol { padding-left: 1.5rem; margin: 0.5rem 0; }
.md li { line-height: 1.7; color: #d1d5db; margin: 0.2rem 0; }
.md blockquote {
border-left: 3px solid #2a2a2a; padding: 0.5rem 1rem; margin: 1rem 0;
background: #1a1a1a; border-radius: 0 6px 6px 0;
}
.md blockquote p { color: #9ca3af; }
.md table { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.9rem; }
.md th { text-align: left; padding: 8px 12px; border-bottom: 2px solid #2a2a2a; color: #9ca3af; font-weight: 600; }
.md td { padding: 8px 12px; border-bottom: 1px solid #1e1e1e; color: #d1d5db; }
.md tr:hover td { background: #1a1a1a; }
.md img { max-width: 100%; border-radius: 8px; margin: 1rem 0; }
.md hr { border: none; border-top: 1px solid #2a2a2a; margin: 2rem 0; }
/* Tier blockquotes */
.md blockquote:has(p:first-child) { }
.tier-free { border-left-color: #22c55e; }
.tier-featured { border-left-color: #3b82f6; }
.tier-pro { border-left-color: #f59e0b; }
.tier-full { border-left-color: #a855f7; }
/* Responsive */
@media (max-width: 768px) {
.sidebar { width: 200px; }
.content { padding: 1rem; }
}
</style>
</head>
<body>
<aside class="sidebar">
<div class="sidebar-header">
<h1>brain docs</h1>
<span class="badge" id="mode-badge">...</span>
</div>
<nav id="nav"></nav>
<div class="back-link">
<a href="/ui/">← brain-ui</a>
</div>
</aside>
<main class="content">
<div class="md" id="content">
<p class="loading">Chargement...</p>
</div>
</main>
<script>
let API = ''
const GROUP_ORDER = ['Guides', 'Agents', 'Vues']
const TIER_MARKERS = { '\u{1F7E2}': 'tier-free', '\u{1F535}': 'tier-featured', '\u{1F7E0}': 'tier-pro', '\u{1F7E3}': 'tier-full' }
let docs = []
let activeDoc = null
let liveMode = false
// Static fallback
const STATIC_DOCS = [
{ name: 'getting-started', label: 'Demarrer', group: 'Guides' },
{ name: 'architecture', label: 'Architecture', group: 'Guides' },
{ name: 'sessions', label: 'Sessions', group: 'Guides' },
{ name: 'workflows', label: 'Workflows', group: 'Guides' },
{ name: 'satellites', label: 'Satellites', group: 'Guides' },
{ name: 'brain-engine-guide', label: 'Brain-engine', group: 'Guides' },
{ name: 'agents', label: "Vue d'ensemble", group: 'Agents' },
{ name: 'agents-code', label: 'Code & Qualite', group: 'Agents' },
{ name: 'agents-infra', label: 'Infra & Deploy', group: 'Agents' },
{ name: 'agents-brain', label: 'Brain & Systeme', group: 'Agents' },
{ name: 'vue-tiers', label: 'Comparatif', group: 'Vues' },
{ name: 'vue-free', label: 'free', group: 'Vues' },
{ name: 'vue-featured', label: 'featured', group: 'Vues' },
{ name: 'vue-pro', label: 'pro', group: 'Vues' },
{ name: 'vue-full', label: 'full', group: 'Vues' },
]
async function init() {
// Auto-detect API path : /api (proxy Apache) ou direct (local)
for (const prefix of ['', '/api']) {
try {
const res = await fetch(`${prefix}/docs`)
if (!res.ok) continue
const data = await res.json()
if (data.docs?.length) {
API = prefix
docs = data.docs
liveMode = true
break
}
} catch { /* next */ }
}
if (!liveMode) docs = STATIC_DOCS
document.getElementById('mode-badge').textContent = liveMode ? 'live' : 'static'
renderNav()
// Check URL hash
const hash = location.hash.replace('#', '')
const target = hash && docs.find(d => d.name === hash) ? hash : 'getting-started'
loadDoc(target)
}
function renderNav() {
const nav = document.getElementById('nav')
const groups = {}
docs.forEach(d => {
const g = d.group || 'Autres'
if (!groups[g]) groups[g] = []
groups[g].push(d)
})
const sorted = GROUP_ORDER.filter(g => groups[g]).map(g => [g, groups[g]])
Object.entries(groups).forEach(([g, d]) => {
if (!GROUP_ORDER.includes(g)) sorted.push([g, d])
})
nav.innerHTML = sorted.map(([group, groupDocs]) => `
<div class="group-label">${group}</div>
${groupDocs.map(d => `
<button class="doc-link" data-name="${d.name}" onclick="loadDoc('${d.name}')">${d.label}</button>
`).join('')}
`).join('')
}
async function loadDoc(name) {
activeDoc = name
location.hash = name
// Update active state
document.querySelectorAll('.doc-link').forEach(el => {
el.classList.toggle('active', el.dataset.name === name)
})
const el = document.getElementById('content')
el.innerHTML = '<p class="loading">Chargement...</p>'
try {
let md
if (liveMode) {
const res = await fetch(`${API}/docs/${name}.md`)
if (!res.ok) throw new Error(res.status)
const data = await res.json()
md = data.content
} else {
const res = await fetch(`/ui/docs/${name}.md`)
if (!res.ok) throw new Error(res.status)
md = await res.text()
md = md.replace(/^---[\s\S]*?---\n*/, '')
}
el.innerHTML = marked.parse(md)
// Apply tier colors to blockquotes
el.querySelectorAll('blockquote').forEach(bq => {
const text = bq.textContent
for (const [marker, cls] of Object.entries(TIER_MARKERS)) {
if (text.includes(marker)) {
bq.classList.add(cls)
break
}
}
})
} catch (err) {
el.innerHTML = `<p style="color:#ef4444">Impossible de charger ${name}: ${err.message}</p>`
}
}
init()
</script>
</body>
</html>

View File

@@ -1 +0,0 @@
../../../docs/README.md

View File

@@ -1 +0,0 @@
../../../docs/agents-brain.md

View File

@@ -1 +0,0 @@
../../../docs/agents-code.md

View File

@@ -1 +0,0 @@
../../../docs/agents-infra.md

View File

@@ -1 +0,0 @@
../../../docs/agents.md

View File

@@ -1 +0,0 @@
../../../docs/architecture.md

View File

@@ -1 +0,0 @@
../../../docs/brain-engine-guide.md

View File

@@ -1 +0,0 @@
../../../docs/getting-started.md

View File

@@ -1 +0,0 @@
../../../docs/satellites.md

View File

@@ -1 +0,0 @@
../../../docs/sessions.md

View File

@@ -1 +0,0 @@
../../../docs/vue-featured.md

View File

@@ -1 +0,0 @@
../../../docs/vue-free.md

View File

@@ -1 +0,0 @@
../../../docs/vue-full.md

View File

@@ -1 +0,0 @@
../../../docs/vue-pro.md

View File

@@ -1 +0,0 @@
../../../docs/vue-tiers.md

View File

@@ -1 +0,0 @@
../../../docs/workflows.md

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, Suspense, lazy } from 'react' import { useState, useEffect, Suspense, lazy } from 'react'
import Dashboard from './components/Dashboard'
import WorkflowBoard from './components/WorkflowBoard' import WorkflowBoard from './components/WorkflowBoard'
import SecretsZone, { MOCK_SECTIONS } from './components/SecretsZone' import SecretsZone, { MOCK_SECTIONS } from './components/SecretsZone'
import WorkflowBuilder from './components/WorkflowBuilder'
import GatesDrawer from './components/GatesDrawer' import GatesDrawer from './components/GatesDrawer'
import GateDrawer from './components/GateDrawer' import GateDrawer from './components/GateDrawer'
import LogDrawer from './components/LogDrawer' import LogDrawer from './components/LogDrawer'
@@ -18,7 +18,7 @@ const CosmosView = lazy(() => import('./components/cosmos/CosmosView'))
const WorkspaceView = lazy(() => import('./components/workspace/WorkspaceView')) const WorkspaceView = lazy(() => import('./components/workspace/WorkspaceView'))
const DocsView = lazy(() => import('./components/DocsView')) const DocsView = lazy(() => import('./components/DocsView'))
type ActiveView = 'workflows' | 'builder' | 'secrets' | 'infra' | 'cosmos' | 'workspace' | 'docs' type ActiveView = 'dashboard' | 'cosmos' | 'workflows' | 'secrets' | 'infra' | 'workspace'
interface NavItem { interface NavItem {
id: ActiveView id: ActiveView
@@ -34,12 +34,11 @@ interface PendingGate {
} }
const NAV_ITEMS: NavItem[] = [ const NAV_ITEMS: NavItem[] = [
{ id: 'workflows', icon: '🔀', label: 'Workflows' }, { id: 'dashboard', icon: '', label: 'Dashboard' },
{ id: 'builder', icon: '', label: 'Nouveau' }, { id: 'cosmos', icon: '🌌', label: 'Cosmos' },
{ id: 'secrets', icon: '🔑', label: 'Secrets' }, { id: 'workflows', icon: '🔀', label: 'Workflows', separator: true },
{ id: 'infra', icon: '🖥️', label: 'Infra' }, { id: 'infra', icon: '🖥️', label: 'Infra' },
{ id: 'cosmos', icon: '🌌', label: 'Cosmos', separator: true }, { id: 'secrets', icon: '🔑', label: 'Secrets' },
{ id: 'docs', icon: '📖', label: 'Docs' },
] ]
function AppInner() { function AppInner() {
@@ -48,10 +47,9 @@ function AppInner() {
// Detect URL path for direct routing (/ui/docs → docs view) // Detect URL path for direct routing (/ui/docs → docs view)
const initialView = (): ActiveView => { const initialView = (): ActiveView => {
const path = window.location.pathname const path = window.location.pathname
if (path.includes('/docs')) return 'docs'
if (path.includes('/cosmos')) return 'cosmos' if (path.includes('/cosmos')) return 'cosmos'
if (path.includes('/workspace')) return 'workspace' if (path.includes('/workspace')) return 'workspace'
return 'workflows' return 'dashboard'
} }
const [activeView, setActiveView] = useState<ActiveView>(initialView) const [activeView, setActiveView] = useState<ActiveView>(initialView)
const [pendingGate, setPendingGate] = useState<PendingGate | null>(null) const [pendingGate, setPendingGate] = useState<PendingGate | null>(null)
@@ -173,6 +171,21 @@ function AppInner() {
})} })}
</nav> </nav>
{/* Docs — lien externe standalone */}
<div className="px-2 mt-2">
<a
href="/ui/docs.html"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-3 px-3 py-2 rounded text-sm font-medium text-left w-full transition-colors"
style={{ color: '#9ca3af', borderLeft: '2px solid transparent', paddingLeft: 10, textDecoration: 'none' }}
>
<span className="text-base leading-none">📖</span>
<span>Docs</span>
<span style={{ marginLeft: 'auto', fontSize: 9, color: '#4b5563' }}></span>
</a>
</div>
{/* Bouton Logs */} {/* Bouton Logs */}
<div className="px-2 mt-2"> <div className="px-2 mt-2">
<button <button
@@ -219,6 +232,9 @@ function AppInner() {
{/* Main content */} {/* Main content */}
<main className="flex-1 overflow-hidden flex flex-col"> <main className="flex-1 overflow-hidden flex flex-col">
{activeView === 'dashboard' && (
<Dashboard />
)}
{activeView === 'workflows' && ( {activeView === 'workflows' && (
<WorkflowBoard <WorkflowBoard
workflows={workflows} workflows={workflows}
@@ -226,9 +242,6 @@ function AppInner() {
onWorkflowClick={(wfId) => setLogsProject(wfId)} onWorkflowClick={(wfId) => setLogsProject(wfId)}
/> />
)} )}
{activeView === 'builder' && (
<WorkflowBuilder />
)}
{activeView === 'secrets' && ( {activeView === 'secrets' && (
<TierGate feature="secrets" hasFeature={hasFeature}> <TierGate feature="secrets" hasFeature={hasFeature}>
<SecretsZone sections={MOCK_SECTIONS} onSecretSave={handleSecretSave} /> <SecretsZone sections={MOCK_SECTIONS} onSecretSave={handleSecretSave} />
@@ -250,15 +263,6 @@ function AppInner() {
</Suspense> </Suspense>
</div> </div>
)} )}
{activeView === 'docs' && (
<Suspense fallback={
<div className="flex items-center justify-center h-full" style={{ color: '#4b5563' }}>
<span className="text-sm font-mono">Chargement Docs...</span>
</div>
}>
<DocsView />
</Suspense>
)}
{activeView === 'workspace' && ( {activeView === 'workspace' && (
<Suspense fallback={ <Suspense fallback={
<div className="flex items-center justify-center h-full" style={{ color: '#4b5563' }}> <div className="flex items-center justify-center h-full" style={{ color: '#4b5563' }}>

View File

@@ -0,0 +1,404 @@
import { useState, useEffect, useCallback } from 'react'
const API = import.meta.env.VITE_BRAIN_API ?? ''
interface SearchResult {
score: number
title: string
filepath: string
excerpt: string
}
interface HealthData {
status: string
indexed: number
uptime: number
}
interface ClaimData {
sess_id: string
type: string
scope: string
status: string
opened_at: string
closed_at: string | null
}
interface AgentData {
id: string
label: string
tier: string
status: string
scope: string
}
interface DocData {
name: string
label: string
group: string
}
function formatUptime(seconds: number): string {
if (seconds < 60) return `${seconds}s`
if (seconds < 3600) return `${Math.floor(seconds / 60)}min`
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}min`
return `${Math.floor(seconds / 86400)}j ${Math.floor((seconds % 86400) / 3600)}h`
}
function formatTimeAgo(dateStr: string): string {
const diff = Date.now() - new Date(dateStr).getTime()
const mins = Math.floor(diff / 60000)
if (mins < 1) return "à l'instant"
if (mins < 60) return `il y a ${mins}min`
const hours = Math.floor(mins / 60)
if (hours < 24) return `il y a ${hours}h`
const days = Math.floor(hours / 24)
return `il y a ${days}j`
}
function StatCard({ label, value, sub, color }: { label: string; value: string | number; sub?: string; color?: string }) {
return (
<div style={{
background: '#141414', border: '1px solid #2a2a2a', borderRadius: 8,
padding: '16px 20px', flex: '1 1 0', minWidth: 140,
}}>
<div style={{ fontSize: 11, color: '#6b7280', fontFamily: 'monospace', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
{label}
</div>
<div style={{ fontSize: 28, fontWeight: 700, color: color ?? '#e5e7eb', marginTop: 4 }}>
{value}
</div>
{sub && (
<div style={{ fontSize: 11, color: '#4b5563', marginTop: 2, fontFamily: 'monospace' }}>
{sub}
</div>
)}
</div>
)
}
function SessionRow({ claim }: { claim: ClaimData }) {
const isOpen = claim.status === 'open'
return (
<div style={{
display: 'flex', alignItems: 'center', gap: 12,
padding: '10px 16px', borderBottom: '1px solid #1e1e1e',
}}>
<span style={{
width: 8, height: 8, borderRadius: '50%', flexShrink: 0,
background: isOpen ? '#22c55e' : '#4b5563',
}} />
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 13, color: '#e5e7eb', fontFamily: 'monospace' }}>
{claim.sess_id}
</div>
<div style={{ fontSize: 11, color: '#6b7280', marginTop: 2 }}>
{claim.type} {claim.scope}
</div>
</div>
<div style={{ fontSize: 11, color: '#4b5563', fontFamily: 'monospace', flexShrink: 0 }}>
{formatTimeAgo(claim.opened_at)}
</div>
</div>
)
}
function FileViewer({ path, onClose }: { path: string; onClose: () => void }) {
const [content, setContent] = useState<string | null>(null)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
fetch(`${API}/brain/${path}`)
.then(r => { if (!r.ok) throw new Error(`${r.status}`); return r.json() })
.then(d => setContent(d.content))
.catch(e => setError(`Impossible de charger ${path}: ${e.message}`))
}, [path])
return (
<div style={{
position: 'fixed', inset: 0, zIndex: 50,
background: 'rgba(0,0,0,0.7)', display: 'flex', alignItems: 'center', justifyContent: 'center',
}} onClick={onClose}>
<div
style={{
background: '#141414', border: '1px solid #2a2a2a', borderRadius: 12,
width: '70%', maxWidth: 800, maxHeight: '80vh',
display: 'flex', flexDirection: 'column', overflow: 'hidden',
}}
onClick={e => e.stopPropagation()}
>
<div style={{
padding: '12px 20px', borderBottom: '1px solid #2a2a2a',
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
}}>
<span style={{ fontSize: 13, fontFamily: 'monospace', color: '#818cf8' }}>{path}</span>
<button
onClick={onClose}
style={{ background: 'none', border: 'none', color: '#6b7280', cursor: 'pointer', fontSize: 18 }}
>
×
</button>
</div>
<div style={{ padding: '16px 20px', overflowY: 'auto', flex: 1 }}>
{error && <div style={{ color: '#ef4444', fontSize: 13 }}>{error}</div>}
{!content && !error && <div style={{ color: '#4b5563', fontSize: 13, fontFamily: 'monospace' }}>Chargement...</div>}
{content && (
<pre style={{
fontSize: 13, lineHeight: 1.6, color: '#d1d5db',
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
whiteSpace: 'pre-wrap', wordBreak: 'break-word', margin: 0,
}}>
{content}
</pre>
)}
</div>
</div>
</div>
)
}
function SearchBar() {
const [query, setQuery] = useState('')
const [results, setResults] = useState<SearchResult[]>([])
const [searching, setSearching] = useState(false)
const [searched, setSearched] = useState(false)
const [viewingFile, setViewingFile] = useState<string | null>(null)
const search = useCallback(async (q: string) => {
if (q.trim().length < 2) { setResults([]); setSearched(false); return }
setSearching(true)
try {
const res = await fetch(`${API}/search?q=${encodeURIComponent(q)}&top=6`)
if (!res.ok) throw new Error()
const data = await res.json()
setResults(data.results ?? [])
setSearched(true)
} catch {
setResults([])
} finally {
setSearching(false)
}
}, [])
useEffect(() => {
const timer = setTimeout(() => { if (query.trim().length >= 2) search(query) }, 400)
return () => clearTimeout(timer)
}, [query, search])
return (
<div style={{ marginBottom: 24 }}>
<div style={{
display: 'flex', alignItems: 'center', gap: 8,
background: '#141414', border: '1px solid #2a2a2a', borderRadius: 8,
padding: '8px 16px',
}}>
<span style={{ color: '#4b5563', fontSize: 16 }}>🔍</span>
<input
type="text"
placeholder="Rechercher dans le brain..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') search(query) }}
style={{
flex: 1, background: 'transparent', border: 'none', outline: 'none',
color: '#e5e7eb', fontSize: 14, fontFamily: 'inherit',
}}
/>
{searching && <span style={{ color: '#4b5563', fontSize: 12, fontFamily: 'monospace' }}>...</span>}
</div>
{searched && results.length > 0 && (
<div style={{
marginTop: 8, background: '#141414', border: '1px solid #2a2a2a',
borderRadius: 8, overflow: 'hidden',
}}>
{results.map((r, i) => (
<div key={i} style={{
padding: '12px 16px', borderBottom: i < results.length - 1 ? '1px solid #1e1e1e' : 'none',
cursor: 'pointer', transition: 'background 0.15s',
}}
onClick={() => setViewingFile(r.filepath)}
onMouseEnter={e => (e.currentTarget.style.background = '#1a1a1a')}
onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
<span style={{ fontSize: 12, fontFamily: 'monospace', color: '#818cf8' }}>
{r.filepath}
</span>
<span style={{
fontSize: 10, fontFamily: 'monospace', color: '#4b5563',
marginLeft: 'auto',
}}>
{(r.score * 100).toFixed(0)}%
</span>
</div>
<div style={{ fontSize: 13, color: '#9ca3af', lineHeight: 1.5 }}>
{r.excerpt.slice(0, 200)}{r.excerpt.length > 200 ? '...' : ''}
</div>
</div>
))}
</div>
)}
{searched && results.length === 0 && !searching && (
<div style={{ marginTop: 8, fontSize: 13, color: '#4b5563', fontFamily: 'monospace', padding: '8px 16px' }}>
Aucun résultat pour "{query}"
</div>
)}
{viewingFile && <FileViewer path={viewingFile} onClose={() => setViewingFile(null)} />}
</div>
)
}
export default function Dashboard() {
const [health, setHealth] = useState<HealthData | null>(null)
const [claims, setClaims] = useState<ClaimData[]>([])
const [agents, setAgents] = useState<AgentData[]>([])
const [docs, setDocs] = useState<DocData[]>([])
const [error, setError] = useState<string | null>(null)
useEffect(() => {
Promise.allSettled([
fetch(`${API}/health`).then(r => r.json()),
fetch(`${API}/bsi/claims`).then(r => r.ok ? r.json() : []),
fetch(`${API}/agents`).then(r => r.ok ? r.json() : []),
fetch(`${API}/docs`).then(r => r.ok ? r.json() : { docs: [] }),
]).then(([h, c, a, d]) => {
if (h.status === 'fulfilled') setHealth(h.value)
if (c.status === 'fulfilled') setClaims(Array.isArray(c.value) ? c.value : [])
if (a.status === 'fulfilled') setAgents(Array.isArray(a.value) ? a.value : [])
if (d.status === 'fulfilled') setDocs(d.value?.docs ?? [])
}).catch(() => setError('Impossible de charger les données'))
}, [])
const openClaims = claims.filter(c => c.status === 'open')
const recentClaims = claims.slice(0, 8)
const agentsByTier = agents.reduce<Record<string, number>>((acc, a) => {
acc[a.tier] = (acc[a.tier] || 0) + 1
return acc
}, {})
return (
<div style={{ padding: '2rem 3rem', overflowY: 'auto', height: '100%' }}>
{/* Header */}
<div style={{ marginBottom: 24 }}>
<h1 style={{ fontSize: 20, fontWeight: 700, color: '#fff', margin: 0 }}>
Dashboard
</h1>
<p style={{ fontSize: 12, color: '#4b5563', fontFamily: 'monospace', marginTop: 4 }}>
{health ? `brain-engine up — ${formatUptime(health.uptime)}` : 'connexion...'}
</p>
</div>
{error && (
<div style={{ color: '#ef4444', fontSize: 13, marginBottom: 16 }}>{error}</div>
)}
{/* Search */}
<SearchBar />
{/* Stats row */}
<div style={{ display: 'flex', gap: 12, marginBottom: 24, flexWrap: 'wrap' }}>
<StatCard
label="Embeddings"
value={health?.indexed?.toLocaleString() ?? '—'}
sub="chunks indexés"
color="#818cf8"
/>
<StatCard
label="Agents"
value={agents.length || '—'}
sub={Object.entries(agentsByTier).map(([t, n]) => `${n} ${t}`).join(' · ') || undefined}
color="#22c55e"
/>
<StatCard
label="Docs"
value={docs.length || '—'}
sub="pages live"
color="#f59e0b"
/>
<StatCard
label="Sessions"
value={openClaims.length}
sub={openClaims.length > 0 ? 'actives' : 'aucune active'}
color={openClaims.length > 0 ? '#22c55e' : '#6b7280'}
/>
</div>
{/* Two columns */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
{/* Recent sessions */}
<div style={{ background: '#141414', border: '1px solid #2a2a2a', borderRadius: 8, overflow: 'hidden' }}>
<div style={{
padding: '12px 16px', borderBottom: '1px solid #2a2a2a',
fontSize: 12, fontFamily: 'monospace', color: '#6b7280',
textTransform: 'uppercase', letterSpacing: '0.05em',
}}>
Sessions récentes
</div>
{recentClaims.length === 0 ? (
<div style={{ padding: 16, fontSize: 13, color: '#4b5563' }}>
Aucune session enregistrée
</div>
) : (
recentClaims.map(c => <SessionRow key={c.sess_id} claim={c} />)
)}
</div>
{/* Quick links */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{/* Agents by scope */}
<div style={{ background: '#141414', border: '1px solid #2a2a2a', borderRadius: 8, overflow: 'hidden' }}>
<div style={{
padding: '12px 16px', borderBottom: '1px solid #2a2a2a',
fontSize: 12, fontFamily: 'monospace', color: '#6b7280',
textTransform: 'uppercase', letterSpacing: '0.05em',
}}>
Agents par scope
</div>
<div style={{ padding: 16, display: 'flex', gap: 16, flexWrap: 'wrap' }}>
{Object.entries(
agents.reduce<Record<string, number>>((acc, a) => {
acc[a.scope || 'unknown'] = (acc[a.scope || 'unknown'] || 0) + 1
return acc
}, {})
).map(([scope, count]) => (
<div key={scope} style={{ textAlign: 'center' }}>
<div style={{ fontSize: 20, fontWeight: 700, color: '#e5e7eb' }}>{count}</div>
<div style={{ fontSize: 10, color: '#6b7280', fontFamily: 'monospace' }}>{scope}</div>
</div>
))}
</div>
</div>
{/* Docs groups */}
<div style={{ background: '#141414', border: '1px solid #2a2a2a', borderRadius: 8, overflow: 'hidden' }}>
<div style={{
padding: '12px 16px', borderBottom: '1px solid #2a2a2a',
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
}}>
<span style={{ fontSize: 12, fontFamily: 'monospace', color: '#6b7280', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
Documentation
</span>
<a href="/docs" target="_blank" rel="noopener noreferrer"
style={{ fontSize: 11, color: '#818cf8', textDecoration: 'none', fontFamily: 'monospace' }}>
Ouvrir
</a>
</div>
<div style={{ padding: 16, display: 'flex', gap: 16, flexWrap: 'wrap' }}>
{Object.entries(
docs.reduce<Record<string, number>>((acc, d) => {
acc[d.group || 'Autres'] = (acc[d.group || 'Autres'] || 0) + 1
return acc
}, {})
).map(([group, count]) => (
<div key={group} style={{ textAlign: 'center' }}>
<div style={{ fontSize: 20, fontWeight: 700, color: '#e5e7eb' }}>{count}</div>
<div style={{ fontSize: 10, color: '#6b7280', fontFamily: 'monospace' }}>{group}</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
)
}

View File

@@ -212,7 +212,14 @@ export default function DocsView() {
return <article className="docs-markdown"><TierSingle tierName={tierName} /></article> return <article className="docs-markdown"><TierSingle tierName={tierName} /></article>
} }
if (liveMode && activeDoc === 'agents') { if (liveMode && activeDoc === 'agents') {
return <article className="docs-markdown"><AgentCatalog /></article> return (
<article className="docs-markdown">
{!loading && content && (
<ReactMarkdown components={mdComponents}>{content}</ReactMarkdown>
)}
<AgentCatalog />
</article>
)
} }
// Mode standard — markdown // Mode standard — markdown

View File

@@ -5,6 +5,8 @@ const ZONE_BADGE_COLORS: Record<ZoneKey, { bg: string; text: string }> = {
public: { bg: 'rgba(229,231,235,0.1)', text: '#e5e7eb' }, public: { bg: 'rgba(229,231,235,0.1)', text: '#e5e7eb' },
work: { bg: 'rgba(99,102,241,0.15)', text: '#6366f1' }, work: { bg: 'rgba(99,102,241,0.15)', text: '#6366f1' },
kernel: { bg: 'rgba(239,68,68,0.15)', text: '#ef4444' }, kernel: { bg: 'rgba(239,68,68,0.15)', text: '#ef4444' },
instance: { bg: 'rgba(168,85,247,0.15)', text: '#a855f7' },
satellite: { bg: 'rgba(34,197,94,0.15)', text: '#22c55e' },
unknown: { bg: 'rgba(75,85,99,0.2)', text: '#6b7280' }, unknown: { bg: 'rgba(75,85,99,0.2)', text: '#6b7280' },
} }

View File

@@ -6,6 +6,8 @@ const ZONE_COLORS: Record<ZoneKey, string> = {
public: '#6366f1', public: '#6366f1',
work: '#22c55e', work: '#22c55e',
kernel: '#f59e0b', kernel: '#f59e0b',
instance: '#a855f7',
satellite: '#3b82f6',
unknown: '#6b7280', unknown: '#6b7280',
} }

View File

@@ -0,0 +1,45 @@
# session-capital.yml — Contexte BHP pour sessions capital professionnel
# Trigger : "brain boot mode capital"
# Focus : bilan, objectifs, CV, capital professionnel, progression long terme
# Cible : ~23% contexte max au boot
session_type: capital
description: "Session capital — bilan, objectifs, CV, capital professionnel"
tier_required: featured # coach.md complet + capital-scribe = tier featured (RAG + progression)
# L0 — Invariant (~5%)
L0:
- PATHS.md
- brain-compose.local.yml
- KERNEL.md
# L1 — Session type (~18%)
L1:
- now.md # bridge session précédente
- agents/coach.md # coach complet — bilan et cap stratégique (pas boot-summary)
- profil/objectifs.md # objectifs actifs + prochaines étapes
- profil/capital.md # preuves CV, milestones, capital accumulé
- progression/README.md # état métabolique + ratio sessions + tendances
# L2 — non applicable (capital = session introspective, pas de scope projet)
L2:
template: null
extras: []
fallback: null
# L3 — On demand
# progression/ détaillée, sessions passées, capital-scribe pour mise à jour capital.md
L3:
hint: "Charger à la demande : progression/ détaillée, sessions passées, capital-scribe"
# --- Note capital ---
# capital-scribe actif automatiquement si modification capital.md détectée en session.
# Pas de projets/ en L2 — le coaching capital est orthogonal aux projets actifs.
# session-coach = réflexion stratégique ; session-capital = capital pro + CV + milestones.
# --- Métriques cibles ---
context_target:
L0: "~5%"
L1: "~18%"
L2: "0%"
total_boot: "~23%"

View File

@@ -4,7 +4,7 @@
session_type: coach session_type: coach
description: "Session coaching — progression, réflexion, cap stratégique, clarté" description: "Session coaching — progression, réflexion, cap stratégique, clarté"
tier_required: pro # coaching complet (coach.md 365 lignes) = feature pro tier_required: featured # coaching complet (coach.md) = tier featured (RAG + progression)
# L0 — Invariant (~5%) # L0 — Invariant (~5%)
L0: L0:

View File

@@ -0,0 +1,46 @@
# session-deploy.yml — Contexte BHP pour sessions de déploiement
# Trigger : "brain boot mode deploy[/<project>]"
# Cible : ~25% contexte max au boot
session_type: deploy
description: "Session de déploiement — VPS, Docker, SSL, CI/CD, infra"
tier_required: pro # deploy = agents vps/ci-cd/monitoring (tous pro)
# L0 — Invariant (~5%)
L0:
- PATHS.md
- brain-compose.local.yml
- KERNEL.md
# L1 — Session type (~15%)
L1:
- now.md # état dernière session — ce qui a changé, ce qui est en attente
- focus.md # projets actifs — savoir quoi déployer
- agents/vps.md # VPS, Apache, Docker, SSL, vhost, certbot # tier: pro
- agents/ci-cd.md # pipelines, GitHub Actions, Gitea CI # tier: pro
- agents/monitoring.md # Kuma, alertes, logs post-déploiement # tier: pro
# L2 — Project scope (~8%) — optionnel sur projet déclaré
L2:
template: "projets/{project}.md"
extras:
- "todo/{project}.md"
fallback: null
# L3 — On demand
# Exemples : agents/security.md (audit pré-deploy), agents/mail.md (DNS/DKIM),
# agents/pm2.md, agents/migration.md, config spécifique
L3:
hint: "Charger à la demande : security pre-deploy, mail/DNS, pm2, migration schema"
# --- Règle deploy ---
# agents/security.md NOT en L1 par défaut — deploy ≠ audit sécurité.
# Charger si audit pré-déploiement explicitement demandé (→ session-audit).
# agents/monitoring.md en L1 : toujours vérifier les alertes après deploy.
# --- Métriques cibles ---
context_target:
L0: "~5%"
L1: "~12%"
L2: "~8%"
total_boot: "~25%"

View File

@@ -0,0 +1,52 @@
# session-handoff.yml — Contexte BHP pour sessions HANDOFF
# Trigger : "brain boot mode HANDOFF[/<handoff-id>]"
# Cible : ~15% contexte max au boot — minimum viable pour reprendre
session_type: HANDOFF
description: "Reprise d'une session via handoff — contexte minimal + fichier handoff cible"
tier_required: free # handoff = protocole BSI de base — disponible pour tous
# L0 — Invariant (~5%)
L0:
- PATHS.md
- brain-compose.local.yml
- KERNEL.md
# L1 — Session type (~8%) — chirurgical : seulement ce qu'il faut pour reprendre
L1:
- BRAIN-INDEX.md # trouver le handoff actif
# L2 — Handoff scope (~5%) — fichier handoff si déclaré dans le signal
# Signal : "HANDOFF/<handoff-id>" → charger le fichier handoff directement
L2:
template: "handoffs/{handoff_id}.md"
extras: []
fallback:
- handoffs/LATEST.md # si pas d'ID déclaré → dernier handoff
# L3 — On demand
# Exemples : context complet projet, agents métier, focus.md
# Principe : le fichier handoff CONTIENT les pointeurs vers L3.
# La nouvelle session lit le handoff → décide elle-même quoi charger.
L3:
hint: "Lire le handoff d'abord. Il indique quoi charger en L3."
# --- Règle HANDOFF ---
# Le handoff est la source de vérité pour la reprise.
# Ne pas charger focus.md, metabolism, agents au boot — le handoff décide.
# Après lecture du handoff → promouvoir le contexte nécessaire en L2 implicite.
# Le claim ouvert doit référencer le handoff : parent_satellite ou story_angle.
# --- Format handoff attendu ---
# handoffs/<id>.md doit contenir :
# - Contexte de la session précédente (état, décisions, bloquants)
# - Fichiers à charger (L3 → L2 pour cette session)
# - Todos ouverts prioritaires
# - Signal de session recommandé pour la suite
# --- Métriques cibles ---
context_target:
L0: "~5%"
L1: "~5%"
L2: "~5%"
total_boot: "~15%"

View File

@@ -0,0 +1,45 @@
# session-infra.yml — Contexte BHP pour sessions infrastructure
# Trigger : "brain boot mode infra"
# Focus : ops quotidien — monitoring, maintenance VPS, config, santé système
# Distinct de session-deploy (deploy = ship code ; infra = ops/health/maintenance)
# Cible : ~22% contexte max au boot
session_type: infra
description: "Session infrastructure — ops quotidien, maintenance VPS, santé système"
tier_required: pro # vps, pm2, monitoring = agents pro
# L0 — Invariant (~5%)
L0:
- PATHS.md
- brain-compose.local.yml
- KERNEL.md
# L1 — Session type (~14%)
L1:
- now.md # bridge session précédente
- agents/coach-boot.md # présence légère — pas de bilan complet en infra
- agents/vps.md # VPS, Apache, Docker, SSL, vhost, certbot # tier: pro
- agents/pm2.md # process manager, restart, logs # tier: pro
- focus.md # projets actifs — contexte des services en cours
# L2 — Project scope — optionnel si scope projet déclaré
L2:
template: "projets/{project}.md"
extras: []
fallback: null
# L3 — On demand
# agents/monitoring.md, agents/mail.md (DNS/DKIM), agents/ci-cd.md, agents/migration.md
L3:
hint: "Charger à la demande : monitoring, mail/DNS, ci-cd, migration schema, security pre-audit"
# --- Distinction infra / deploy ---
# infra = état du système, maintenance, vérifications, config — pas de CI/CD au boot
# deploy = livraison code, CI/CD, releases, pipeline — agents/ci-cd.md en L1
# --- Métriques cibles ---
context_target:
L0: "~5%"
L1: "~14%"
L2: "~3%"
total_boot: "~22%"

View File

@@ -0,0 +1,45 @@
# session-urgence.yml — Contexte BHP pour sessions incident/urgence
# Trigger : "brain boot mode urgence"
# Focus : incident prod, production down, hotfix critique
# Mode conserve : automatique — cible context < 40%
# Cible : ~15% contexte max au boot (mode conserve)
session_type: urgence
description: "Session urgence — incident prod, production down, hotfix critique"
tier_required: pro # vps, pm2, debug = agents pro
# L0 — Invariant (~5%)
L0:
- PATHS.md
- brain-compose.local.yml
- KERNEL.md
# L1 — Session type (~10%) — minimal, on va vite
# Coach absent : urgence avant bilan pédagogique — invocation explicite si besoin
L1:
- agents/vps.md # VPS, Apache — infra down # tier: pro
- agents/pm2.md # process manager, restart, logs # tier: pro
- agents/debug.md # diagnostic, crash, comportement inattendu
# L2 — non applicable (urgence = focus immédiat, pas de scope projet)
L2:
template: null
extras: []
fallback: null
# L3 — On demand — déclenché après résolution
L3:
hint: "Après résolution : scribe (post-mortem), security (si faille), monitoring (état alertes)"
# --- Règles urgence ---
conserve: auto # mode conserve activé automatiquement — cible context < 40%
coach: silent # coach silencieux sauf invocation explicite après résolution
# post-mortem : scribe déclenché sur signal "résolution confirmée" ou "c'est résolu"
# secrets-guardian : actif en passif — rotation clés si compromis suspects
# --- Métriques cibles ---
context_target:
L0: "~5%"
L1: "~10%"
L2: "0%"
total_boot: "~15%"

View File

@@ -16,10 +16,14 @@ L0:
L1: L1:
- now.md # bridge session précédente - now.md # bridge session précédente
- focus.md # focus actuel, todos prioritaires - focus.md # focus actuel, todos prioritaires
- agents/coach.md # coach complet byTask — observe le projet en cours - agents/coach-boot.md # coach boot-summary — observe le projet en cours (free)
- agents/debug.md # bug, crash, comportement inattendu - agents/debug.md # bug, crash, comportement inattendu
- metabolism/README.md # état métabolique, énergie session - metabolism/README.md # état métabolique, énergie session
# L1_featured — chargé si tier: featured+ — coach complet remplace coach-boot
L1_featured:
- agents/coach.md # coach complet byTask — observe le projet en cours
# L1_pro — Session type (~5%) — chargé uniquement si tier: pro déclaré # L1_pro — Session type (~5%) — chargé uniquement si tier: pro déclaré
# Pas de code-review ni security pour tier free — chargement explicite sur demande sinon # Pas de code-review ni security pour tier free — chargement explicite sur demande sinon
L1_pro: L1_pro:

View File

@@ -1,6 +1,7 @@
# docs/ — Documentation humaine # docs/ — Documentation humaine
> Guides lisibles sans contexte brain. Pour forks, onboarding, ou quand tu veux comprendre comment ca marche. > Guides lisibles sans contexte brain. Pour forks, onboarding, ou quand tu veux comprendre comment ca marche.
> L'histoire du projet → [story.tetardtek.com](https://story.tetardtek.com)
--- ---

View File

@@ -1,8 +1,12 @@
# Le brain en 30 secondes # Le brain en 30 secondes
Un brain, c'est un systeme de **specialistes IA** qui travaillent ensemble. Chaque specialiste (agent) fait une chose bien : debugger, reviewer du code, deployer, ecrire des tests. Tu n'en charges jamais plus de 5 a la fois — le brain sait lesquels activer selon ce que tu fais. ## Pourquoi un brain plutot que Claude seul ?
Tu forkes le brain, tu codes. Les agents se chargent automatiquement. Claude est puissant. Mais a chaque session, il repart de zero. Tu re-expliques ton projet, ta stack, tes conventions. Tu repetes les memes consignes. Tu perds du contexte a chaque compaction.
Le brain resout ca : **un systeme de specialistes IA qui persistent entre sessions.** Chaque specialiste (agent) fait une chose bien — debugger, reviewer du code, deployer, ecrire des tests. Ils connaissent tes regles, ta stack, tes decisions passees. Tu n'en charges jamais plus de 5 a la fois — le brain sait lesquels activer selon ce que tu fais.
Tu forkes le brain, tu codes. Les agents se chargent automatiquement. Ton contexte survit aux sessions.
--- ---
@@ -10,25 +14,25 @@ Tu forkes le brain, tu codes. Les agents se chargent automatiquement.
> 🟢 **free — Tu forkes, ca marche** > 🟢 **free — Tu forkes, ca marche**
> >
> **14 agents + 8 systeme. 6 sessions.** Pas de cle API, pas de config. > **17 agents + 9 systeme. 6 sessions.** Pas de cle API, pas de config.
> >
> Debug, brainstorm, scribes automatiques, protection secrets, creation d'agents custom. Le coach observe en arriere-plan. > Debug, brainstorm, scribes automatiques, protection secrets, creation d'agents custom. 3 agents d'onboarding (guide, catalogist, pathfinder) pour t'orienter. Le coach observe en arriere-plan.
> 🔵 **featured — Le brain te connait** > 🔵 **featured — Le brain te connait**
> >
> **18 agents + systeme. 8 sessions.** Le coach se reveille. > **21 agents + systeme. 8 sessions.** Le coach se reveille.
> >
> Bilans de session, objectifs concrets, progression tracee. Le brain se souvient de tes acquis entre sessions grace a la distillation RAG. > Bilans de session, objectifs concrets, progression tracee. Le brain se souvient de tes acquis entre sessions grace a la distillation RAG.
> 🟠 **pro — L'atelier complet** > 🟠 **pro — L'atelier complet**
> >
> **40 agents + systeme. 12 sessions.** Tu ship en prod. > **42 agents + systeme. 14 sessions.** Tu ship en prod.
> >
> Code review (7 priorites), audit securite (8 priorites OWASP), tests automatises, 3 optimiseurs perf, deploy VPS + CI/CD + SSL, sessions urgence et infra. > Code review (7 priorites), audit securite (8 priorites OWASP), tests automatises, 3 optimiseurs perf, deploy VPS + CI/CD + SSL, sessions urgence et infra.
> 🟣 **full — Ton brain, tes regles** > 🟣 **full — Ton brain, tes regles**
> >
> **75 agents (tous). 15 sessions.** Tu es owner. > **81 agents (tous). 15 sessions.** Tu es owner.
> >
> Modification du kernel, copilotage long (mode pilote), supervision multi-phase (hypervisor), coach proactif qui anticipe. > Modification du kernel, copilotage long (mode pilote), supervision multi-phase (hypervisor), coach proactif qui anticipe.
@@ -66,6 +70,11 @@ Charge les agents security et code-review
**Ils ne chargent que l'essentiel.** Un agent de 200 lignes → ~25 lignes au boot. Le reste se charge quand tu en as besoin. **Ils ne chargent que l'essentiel.** Un agent de 200 lignes → ~25 lignes au boot. Le reste se charge quand tu en as besoin.
**Premier fork ? 3 agents t'orientent.**
- `guide` — presente le systeme, repond a "c'est quoi ce truc ?"
- `catalogist` — explore ce qui est disponible (agents, tiers, features)
- `pathfinder` — t'oriente vers la bonne session selon ce que tu veux faire
--- ---
## Explore les agents par famille ## Explore les agents par famille
@@ -80,10 +89,19 @@ Charge les agents security et code-review
--- ---
## Pour aller plus loin
**L'histoire du projet** — [story.tetardtek.com](https://story.tetardtek.com) raconte le pourquoi, le parcours, les decisions. Si tu veux comprendre la vision avant de fork.
---
## Nouveautes ## Nouveautes
| Date | Quoi de neuf | | Date | Quoi de neuf |
|------|-------------| |------|-------------|
| 2026-03-21 | 3 agents onboarding (guide, catalogist, pathfinder) — le brain accueille les nouveaux |
| 2026-03-21 | Docs live — git pull = docs a jour, zero rebuild |
| 2026-03-21 | VPS scission — vitrine template publique separee du brain prod |
| 2026-03-20 | Agents 87% plus legers au boot | | 2026-03-20 | Agents 87% plus legers au boot |
| 2026-03-20 | Coach adaptatif — 5 comportements selon la session | | 2026-03-20 | Coach adaptatif — 5 comportements selon la session |
| 2026-03-20 | Fermeture fiable — sequence deterministe | | 2026-03-20 | Fermeture fiable — sequence deterministe |

View File

@@ -10,7 +10,7 @@ Le brain c'est 3 couches :
**1. Le kernel** — l'identite **1. Le kernel** — l'identite
- Les regles qui ne changent pas (KERNEL.md, constitution, PATHS.md) - Les regles qui ne changent pas (KERNEL.md, constitution, PATHS.md)
- Les agents specialises (~75 fichiers .md) - Les agents specialises (~81 fichiers .md)
- Le profil de collaboration - Le profil de collaboration
- Le brain-compose.yml (config, tiers, modes) - Le brain-compose.yml (config, tiers, modes)

View File

@@ -1,6 +1,7 @@
# Demarrer avec le brain — le vrai tuto # Demarrer avec le brain — le vrai tuto
> Du fork au premier `brain boot`. 10 minutes. > Du fork au premier `brain boot`. 10 minutes.
> Envie de comprendre le projet avant de fork ? → [story.tetardtek.com](https://story.tetardtek.com)
--- ---
@@ -55,7 +56,7 @@ bash setup.sh
Le script fait tout automatiquement : Le script fait tout automatiquement :
1. **Cree `brain-compose.local.yml`** — ta config machine (chemins auto-detectes) 1. **Cree `brain-compose.local.yml`** — ta config machine (chemins auto-detectes)
2. **Cree les dossiers satellites** — todo/, progression/, toolkit/, reviews/, workspace/ ([pourquoi ?](satellites.md)) 2. **Cree les dossiers satellites** — todo/, progression/, toolkit/, reviews/, workspace/
3. **Copie `profil/collaboration.md`** — regles de travail 3. **Copie `profil/collaboration.md`** — regles de travail
4. **Build le dashboard**`brain-ui/` (npm install + vite build) 4. **Build le dashboard**`brain-ui/` (npm install + vite build)
5. **Init brain-engine** — cree l'environnement Python + brain.db 5. **Init brain-engine** — cree l'environnement Python + brain.db
@@ -243,6 +244,20 @@ git fetch upstream
git merge upstream/main git merge upstream/main
``` ```
### J'utilise Gitea self-hosted et git clone echoue ?
Gitea en Docker ecoute souvent sur un port SSH non standard (2222 au lieu de 22). Ajoute dans `~/.ssh/config` :
```
Host git.example.com
HostName git.example.com
Port 2222
User git
IdentityFile ~/.ssh/id_ed25519
```
Puis ajoute la host key : `ssh-keyscan -p 2222 git.example.com >> ~/.ssh/known_hosts`
### Ou est la documentation complete ? ### Ou est la documentation complete ?
- Dashboard : `http://localhost:7700/ui/` → onglet Docs - Dashboard : `http://localhost:7700/ui/` → onglet Docs

View File

@@ -1,6 +1,6 @@
# 🟢 free — Ce que tu as # 🟢 free — Ce que tu as
> 🟢 **14 agents invocables + 8 systeme. 6 sessions. Pas de cle, pas de config.** > 🟢 **17 agents invocables + 9 systeme. 6 sessions. Pas de cle, pas de config.**
--- ---
@@ -36,6 +36,12 @@
- `pattern-scribe` — detection patterns recurrents - `pattern-scribe` — detection patterns recurrents
- `time-anchor` — conscience temporelle, recontextualisation - `time-anchor` — conscience temporelle, recontextualisation
**S'orienter**
- `guide` — presentation du systeme, accueil fresh fork
- `catalogist` — exploration des registres (agents, tiers, features)
- `pathfinder` — routage intentionnel, oriente vers la bonne session
--- ---
## Agents systeme ## Agents systeme

View File

@@ -7,7 +7,7 @@
## 🟢 free — Ce que tu as ## 🟢 free — Ce que tu as
> 🟢 **14 agents invocables + 8 systeme. 6 sessions.** > 🟢 **17 agents invocables + 9 systeme. 6 sessions.**
### Sessions ### Sessions
@@ -37,6 +37,11 @@
- `pattern-scribe` — detection patterns recurrents - `pattern-scribe` — detection patterns recurrents
- `time-anchor` — conscience temporelle, recontextualisation - `time-anchor` — conscience temporelle, recontextualisation
**S'orienter**
- `guide` — presentation du systeme, accueil fresh fork
- `catalogist` — exploration des registres (agents, tiers, features)
- `pathfinder` — routage intentionnel, oriente vers la bonne session
### Agents systeme (tournent a chaque boot) ### Agents systeme (tournent a chaque boot)
- `helloWorld` — briefing, claim BSI - `helloWorld` — briefing, claim BSI

View File

@@ -92,6 +92,42 @@ if [ -d "$BRAIN_ROOT/wiki" ]; then
fi fi
fi fi
# --- Docs ---
echo ""
echo "── docs/ ───────────────────────────────────────"
if [ -d "$BRAIN_ROOT/docs" ]; then
if [ -z "$DRY" ]; then
mkdir -p "$TEMPLATE_DIR/docs"
rsync -a --delete "$BRAIN_ROOT/docs/" "$TEMPLATE_DIR/docs/"
fi
doc_count=$(ls "$BRAIN_ROOT/docs/"*.md 2>/dev/null | wc -l | tr -d ' ')
echo "$doc_count docs"
fi
# --- Contexts ---
echo ""
echo "── contexts/ ──────────────────────────────────"
if [ -d "$BRAIN_ROOT/contexts" ]; then
if [ -z "$DRY" ]; then
mkdir -p "$TEMPLATE_DIR/contexts"
rsync -a --delete "$BRAIN_ROOT/contexts/" "$TEMPLATE_DIR/contexts/"
fi
ctx_count=$(ls "$BRAIN_ROOT/contexts/"*.yml 2>/dev/null | wc -l | tr -d ' ')
echo "$ctx_count contexts"
fi
# --- Brain-UI (source + dist) ---
echo ""
echo "── brain-ui/ ──────────────────────────────────"
if [ -d "$BRAIN_ROOT/brain-ui" ]; then
if [ -z "$DRY" ]; then
rsync -a --delete \
--exclude='node_modules/' \
"$BRAIN_ROOT/brain-ui/" "$TEMPLATE_DIR/brain-ui/"
fi
echo " ✅ brain-ui (src + dist)"
fi
# --- Gitkeep --- # --- Gitkeep ---
[ -z "$DRY" ] && mkdir -p "$TEMPLATE_DIR/locks" && \ [ -z "$DRY" ] && mkdir -p "$TEMPLATE_DIR/locks" && \
touch "$TEMPLATE_DIR/locks/.gitkeep" touch "$TEMPLATE_DIR/locks/.gitkeep"

View File

@@ -66,15 +66,31 @@ echo " Ils fonctionnent sans Git. Pour les versionner : docs/satellites.md"
# 3. Build dashboard # 3. Build dashboard
echo "" echo ""
echo "=== Dashboard ===" echo "=== Dashboard ==="
if [ -d "$BRAIN_ROOT/brain-ui/dist" ]; then if [ -d "$BRAIN_ROOT/brain-ui" ]; then
# Creer .env.local si absent — pointe vers brain-engine local
if [ ! -f "$BRAIN_ROOT/brain-ui/.env.local" ]; then
cat > "$BRAIN_ROOT/brain-ui/.env.local" << 'ENVEOF'
# VITE_BRAIN_API vide = requetes relatives (meme serveur)
# brain-engine sert l'UI ET l'API sur le meme port
VITE_BRAIN_API=
VITE_USE_MOCK=false
ENVEOF
echo "✅ brain-ui/.env.local cree"
fi
if [ -d "$BRAIN_ROOT/brain-ui/dist" ]; then
echo "✅ brain-ui deja build" echo "✅ brain-ui deja build"
else else
if command -v node &>/dev/null && command -v npm &>/dev/null; then if command -v node &>/dev/null && command -v npm &>/dev/null; then
bash "$BRAIN_ROOT/brain-ui/build.sh" echo "→ Build brain-ui..."
cd "$BRAIN_ROOT/brain-ui" && npm install --silent && npm run build && cd "$BRAIN_ROOT"
echo "✅ brain-ui build"
else else
echo "⚠️ Node.js/npm absent — le dashboard ne sera pas disponible." echo "⚠️ Node.js/npm absent — le dashboard ne sera pas disponible."
echo " Installe Node.js 18+ puis relance : bash brain-ui/build.sh" echo " Installe Node.js 18+ puis relance setup.sh"
fi fi
fi
else
echo "⚠️ brain-ui/ absent — dashboard non disponible."
fi fi
# 3. Init brain-engine # 3. Init brain-engine