fix: am start <agent> passait le token run en argument (binaire enregistre existant) — picoclaw rejetait son propre nom, aichat recevait 'aichat' en prompt

This commit is contained in:
2026-08-21 10:32:26 -04:00
parent 7fb65c257d
commit e71cffb3c0
7 changed files with 798 additions and 2 deletions
Generated
+1 -1
View File
@@ -21,7 +21,7 @@ dependencies = [
[[package]]
name = "agent-manager"
version = "1.1.2"
version = "1.1.3"
dependencies = [
"anyhow",
"base64",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "agent-manager"
version = "1.1.2"
version = "1.1.3"
edition = "2021"
description = "Manage local AI coding agents: list, install, start, stop, update — with automatic dependency handling and a YAML-driven catalog."
license = "MIT"
+10
View File
@@ -82,6 +82,16 @@
> l'app — l'import écrasait la config réelle de la machine (et la suite
> de tests détruisait la config utilisateur à chaque `cargo test`).
> L'import respecte désormais `app.paths.config_dir` (--config/isolated).
>
> 🔧 **Maintenance v1.1.3 (2026-08-21)** : fix `resolve_exec` — pour un
> agent INSTALLÉ avec un binaire enregistré, le premier token de `run:`
> (ex. `picoclaw`) n'était jamais consommé : `am start <agent>` lançait
> `<bin> <token-run>` (picoclaw rejetait son propre nom comme
> sous-commande inconnue ; aichat recevait « aichat » comme prompt
> one-shot). Le token est désormais consommé dans la branche « binaire
> enregistré existant » (les branches non-installé/binaire manquant
> étaient déjà correctes). Test de régression
> `resolve_exec_installed_agent_does_not_leak_run_token`.
---
+298
View File
@@ -0,0 +1,298 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>am web — Cockpit sombre (mockup)</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg:#0b0f14; --panel:#121821; --panel2:#0f151c; --border:#1d2733;
--fg:#e6edf3; --muted:#8b98a5; --accent:#3ddc97; --accent2:#38bdf8;
--ok:#34d399; --warn:#f59e0b; --err:#f87171; --purple:#a78bfa;
}
body {
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
background:var(--bg); color:var(--fg); line-height:1.5; font-size:14px;
}
.layout { display:flex; min-height:100vh; }
/* ---------- Sidebar ---------- */
.side {
width:225px; flex-shrink:0; background:var(--panel2);
border-right:1px solid var(--border); padding:18px 12px;
display:flex; flex-direction:column; gap:4px; position:sticky; top:0; height:100vh;
}
.logo { display:flex; align-items:center; gap:10px; padding:4px 8px 18px; }
.logo-badge {
width:34px; height:34px; border-radius:9px; background:linear-gradient(135deg,#3ddc97,#38bdf8);
display:flex; align-items:center; justify-content:center; font-weight:800; font-size:16px; color:#0b0f14;
}
.logo b { font-size:16px; letter-spacing:.5px; }
.logo small { display:block; color:var(--muted); font-size:11px; font-weight:400; }
.nav-item {
display:flex; align-items:center; gap:10px; padding:9px 10px; border-radius:8px;
color:var(--muted); cursor:pointer; border:1px solid transparent; font-size:13.5px;
}
.nav-item:hover { background:rgba(61,220,151,.07); color:var(--fg); }
.nav-item.active { background:rgba(61,220,151,.12); color:var(--accent); border-color:rgba(61,220,151,.25); font-weight:600; }
.nav-item .ico { width:18px; text-align:center; }
.side-foot { margin-top:auto; padding:12px 8px 4px; border-top:1px solid var(--border); color:var(--muted); font-size:11.5px; }
.side-foot .dot { display:inline-block; width:8px; height:8px; border-radius:50%; background:var(--ok); margin-right:6px; }
/* ---------- Main ---------- */
.main { flex:1; padding:22px 28px 40px; max-width:1200px; }
.topbar { display:flex; align-items:center; justify-content:space-between; margin-bottom:22px; gap:12px; flex-wrap:wrap; }
.topbar h1 { font-size:20px; font-weight:700; }
.topbar .sub { color:var(--muted); font-size:12.5px; margin-top:2px; }
.top-actions { display:flex; align-items:center; gap:10px; }
.badge {
font-size:11.5px; padding:4px 10px; border-radius:99px; border:1px solid var(--border);
color:var(--muted); background:var(--panel); font-family:ui-monospace,Consolas,monospace;
}
.badge.live { color:var(--ok); border-color:rgba(52,211,153,.4); }
.badge.live::before { content:"● "; }
.btn {
background:var(--accent); color:#0b0f14; border:none; border-radius:8px; padding:7px 14px;
font-weight:700; font-size:13px; cursor:pointer;
}
.btn:hover { filter:brightness(1.1); }
.btn.ghost { background:transparent; color:var(--muted); border:1px solid var(--border); font-weight:500; }
.btn.ghost:hover { color:var(--fg); border-color:var(--muted); }
/* ---------- KPI cards ---------- */
.kpis { display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-bottom:18px; }
.kpi { background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:16px 18px; }
.kpi .label { color:var(--muted); font-size:12px; text-transform:uppercase; letter-spacing:.6px; }
.kpi .value { font-size:28px; font-weight:800; margin-top:6px; }
.kpi .delta { font-size:11.5px; margin-top:4px; }
.kpi .delta.up { color:var(--ok); } .kpi .delta.down { color:var(--err); }
.kpi .emoji { float:right; font-size:20px; opacity:.9; }
/* ---------- Panels ---------- */
.grid2 { display:grid; grid-template-columns:1fr 1fr; gap:14px; margin-bottom:18px; }
.panel { background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:16px 18px; }
.panel h3 { font-size:13px; text-transform:uppercase; letter-spacing:.6px; color:var(--muted); margin-bottom:14px; }
/* bars */
.bar-row { display:flex; align-items:center; gap:10px; margin-bottom:9px; font-size:12.5px; }
.bar-row .name { width:110px; color:var(--fg); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.bar-row .track { flex:1; height:14px; background:var(--panel2); border-radius:7px; overflow:hidden; }
.bar-row .fill { height:100%; border-radius:7px; background:linear-gradient(90deg,var(--accent),var(--accent2)); }
.bar-row .val { width:34px; text-align:right; color:var(--muted); font-family:ui-monospace,monospace; }
/* trend svg */
.trend-wrap { position:relative; }
.trend-axis { display:flex; justify-content:space-between; color:var(--muted); font-size:10.5px; margin-top:4px; font-family:ui-monospace,monospace; }
/* tables */
table { width:100%; border-collapse:collapse; font-size:12.5px; }
th { text-align:left; color:var(--muted); font-weight:600; font-size:11px; text-transform:uppercase; letter-spacing:.5px; padding:7px 10px; border-bottom:1px solid var(--border); }
td { padding:8px 10px; border-bottom:1px solid rgba(29,39,51,.5); }
tbody tr:hover { background:rgba(61,220,151,.05); }
.pill { font-size:10.5px; padding:2px 8px; border-radius:99px; font-weight:600; }
.pill.ok { background:rgba(52,211,153,.14); color:var(--ok); }
.pill.run { background:rgba(56,189,248,.14); color:var(--accent2); }
.pill.fail { background:rgba(248,113,113,.14); color:var(--err); }
.pill.mut { background:rgba(167,139,250,.14); color:var(--purple); }
.mono { font-family:ui-monospace,Consolas,monospace; font-size:11.5px; color:var(--muted); }
.search { width:100%; background:var(--panel2); border:1px solid var(--border); border-radius:8px; color:var(--fg); padding:8px 12px; font-size:12.5px; margin-bottom:10px; }
.search:focus { outline:none; border-color:var(--accent); }
/* timeline */
.tl { list-style:none; }
.tl li { display:flex; gap:12px; padding:8px 0; border-bottom:1px solid rgba(29,39,51,.5); align-items:baseline; }
.tl .dot { width:8px; height:8px; border-radius:50%; margin-top:6px; flex-shrink:0; }
.tl .t { color:var(--fg); } .tl .a { color:var(--accent); font-weight:600; }
.tl .m { color:var(--muted); font-size:11.5px; margin-left:auto; white-space:nowrap; font-family:ui-monospace,monospace; }
.empty-note { color:var(--muted); font-size:12px; text-align:center; padding:18px 0; }
.panel.hidden, .view { display:none; } .view.active { display:block; }
@media (max-width:900px){ .kpis{grid-template-columns:repeat(2,1fr);} .grid2{grid-template-columns:1fr;} .side{width:64px;} .side .lbl,.logo small,.side-foot span{display:none;} }
</style>
</head>
<body>
<div class="layout">
<aside class="side">
<div class="logo">
<div class="logo-badge">am</div>
<div><b>agent-manager</b><small>cockpit des agents</small></div>
</div>
<div class="nav-item active" data-view="overview"><span class="ico">▦</span><span class="lbl">Vue d'ensemble</span></div>
<div class="nav-item" data-view="stats"><span class="ico">📊</span><span class="lbl">Statistiques</span></div>
<div class="nav-item" data-view="sessions"><span class="ico">⏱</span><span class="lbl">Sessions</span></div>
<div class="nav-item" data-view="events"><span class="ico">📜</span><span class="lbl">Événements</span></div>
<div class="nav-item" data-view="projects"><span class="ico">🗂</span><span class="lbl">Projets</span></div>
<div class="side-foot">
<span><span class="dot"></span>v0.6.0 · lang FR ·<br>127.0.0.1:7878</span>
</div>
</aside>
<main class="main">
<div class="topbar">
<div>
<h1 id="view-title">Vue d'ensemble</h1>
<div class="sub" id="last-update">Dernière mise à jour : il y a 5 s</div>
</div>
<div class="top-actions">
<span class="badge live">serveur local</span>
<span class="badge">127.0.0.1:7878</span>
<button class="btn ghost" onclick="fakeRefresh()">↻ Actualiser</button>
</div>
</div>
<!-- ===== OVERVIEW ===== -->
<section class="view active" id="overview">
<div class="kpis">
<div class="kpi"><span class="emoji">🤖</span><div class="label">Agents installés</div><div class="value">9</div><div class="delta up">▲ +1 cette semaine</div></div>
<div class="kpi"><span class="emoji">⚡</span><div class="label">En cours</div><div class="value">2</div><div class="delta up">● claude-code · codex</div></div>
<div class="kpi"><span class="emoji">⏱</span><div class="label">Sessions (7 j)</div><div class="value">47</div><div class="delta up">▲ +12 % vs semaine préc.</div></div>
<div class="kpi"><span class="emoji">📜</span><div class="label">Événements (24 h)</div><div class="value">132</div><div class="delta down">▼ 8 % vs hier</div></div>
</div>
<div class="grid2">
<div class="panel">
<h3>Top agents — sessions (7 j)</h3>
<div class="bar-row"><span class="name">claude-code</span><div class="track"><div class="fill" style="width:100%"></div></div><span class="val">18</span></div>
<div class="bar-row"><span class="name">aider</span><div class="track"><div class="fill" style="width:50%"></div></div><span class="val">9</span></div>
<div class="bar-row"><span class="name">codex</span><div class="track"><div class="fill" style="width:44%"></div></div><span class="val">8</span></div>
<div class="bar-row"><span class="name">gemini-cli</span><div class="track"><div class="fill" style="width:28%"></div></div><span class="val">5</span></div>
<div class="bar-row"><span class="name">jcode</span><div class="track"><div class="fill" style="width:22%"></div></div><span class="val">4</span></div>
<div class="bar-row"><span class="name">opencode</span><div class="track"><div class="fill" style="width:17%"></div></div><span class="val">3</span></div>
</div>
<div class="panel">
<h3>Activité — sessions par jour (14 j)</h3>
<div class="trend-wrap">
<svg viewBox="0 0 480 150" width="100%" height="150">
<defs><linearGradient id="g" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#3ddc97" stop-opacity=".35"/><stop offset="1" stop-color="#3ddc97" stop-opacity="0"/></linearGradient></defs>
<path d="M0,120 L37,108 L74,116 L111,96 L148,104 L185,84 L222,92 L259,72 L296,80 L333,62 L370,70 L407,48 L444,58 L480,34 L480,150 L0,150 Z" fill="url(#g)"/>
<polyline points="0,120 37,108 74,116 111,96 148,104 185,84 222,92 259,72 296,80 333,62 370,70 407,48 444,58 480,34" fill="none" stroke="#3ddc97" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="480" cy="34" r="4" fill="#38bdf8"/>
</svg>
<div class="trend-axis"><span>04 août</span><span>11 août</span><span>aujourd'hui</span></div>
</div>
</div>
</div>
<div class="grid2">
<div class="panel">
<h3>Activité récente</h3>
<ul class="tl">
<li><span class="dot" style="background:var(--ok)"></span><span class="t"><span class="a">claude-code</span> terminé — session 3 h 12 min</span><span class="m">il y a 2 min</span></li>
<li><span class="dot" style="background:var(--accent2)"></span><span class="t"><span class="a">codex</span> démarré — projet flowdeck</span><span class="m">il y a 14 min</span></li>
<li><span class="dot" style="background:var(--purple)"></span><span class="t"><span class="a">jcode</span> mis à jour 0.76.0 → 0.78.1</span><span class="m">il y a 1 h</span></li>
<li><span class="dot" style="background:var(--err)"></span><span class="t"><span class="a">aider</span> échec — exit code 1</span><span class="m">il y a 3 h</span></li>
<li><span class="dot" style="background:var(--ok)"></span><span class="t"><span class="a">gemini-cli</span> terminé — session 42 min</span><span class="m">il y a 5 h</span></li>
</ul>
</div>
<div class="panel">
<h3>Sessions en cours</h3>
<table>
<thead><tr><th>Agent</th><th>Projet</th><th>Durée</th><th>Démarrée</th></tr></thead>
<tbody>
<tr><td>claude-code</td><td>agent-manager</td><td class="mono">3 h 12 min</td><td class="mono">14:26</td></tr>
<tr><td>codex</td><td>flowdeck</td><td class="mono">14 min</td><td class="mono">17:18</td></tr>
</tbody>
</table>
<div class="empty-note" id="running-empty" style="display:none">Aucune session en cours</div>
</div>
</div>
</section>
<!-- ===== STATS ===== -->
<section class="view" id="stats">
<div class="panel">
<h3>Statistiques par agent — période 30 j</h3>
<table>
<thead><tr><th>Agent</th><th>Sessions</th><th>Durée totale</th><th>Taux de succès</th><th>Coût estimé</th><th>Dernière activité</th></tr></thead>
<tbody>
<tr><td>claude-code</td><td>54</td><td class="mono">38 h 12 m</td><td><span class="pill ok">94 %</span></td><td class="mono">12,40 €</td><td class="mono">il y a 2 min</td></tr>
<tr><td>aider</td><td>31</td><td class="mono">21 h 48 m</td><td><span class="pill ok">87 %</span></td><td class="mono">4,10 €</td><td class="mono">il y a 3 h</td></tr>
<tr><td>codex</td><td>27</td><td class="mono">19 h 05 m</td><td><span class="pill ok">89 %</span></td><td class="mono">8,75 €</td><td class="mono">il y a 14 min</td></tr>
<tr><td>gemini-cli</td><td>14</td><td class="mono">6 h 33 m</td><td><span class="pill ok">79 %</span></td><td class="mono">0 € (local)</td><td class="mono">il y a 5 h</td></tr>
<tr><td>jcode</td><td>12</td><td class="mono">5 h 02 m</td><td><span class="pill run">67 %</span></td><td class="mono">1,20 €</td><td class="mono">hier</td></tr>
<tr><td>opencode</td><td>9</td><td class="mono">4 h 40 m</td><td><span class="pill fail">55 %</span></td><td class="mono">0,90 €</td><td class="mono">il y a 2 j</td></tr>
</tbody>
</table>
</div>
</section>
<!-- ===== SESSIONS ===== -->
<section class="view" id="sessions">
<div class="panel">
<h3>Sessions — 7 derniers jours</h3>
<input class="search" id="sess-search" placeholder="Filtrer par agent, projet ou id…" oninput="filterSessions(this.value)">
<table>
<thead><tr><th>Session</th><th>Agent</th><th>Projet</th><th>Statut</th><th>Durée</th><th>Fin</th></tr></thead>
<tbody id="sess-body">
<tr><td class="mono">20260818_172631_b4e2f1</td><td>claude-code</td><td>agent-manager</td><td><span class="pill run">en cours</span></td><td class="mono">3 h 12 min</td><td class="mono">—</td></tr>
<tr><td class="mono">20260818_171812_c9d0a4</td><td>codex</td><td>flowdeck</td><td><span class="pill run">en cours</span></td><td class="mono">14 min</td><td class="mono">—</td></tr>
<tr><td class="mono">20260818_153402_a1b2c3</td><td>claude-code</td><td>agent-manager</td><td><span class="pill ok">terminée</span></td><td class="mono">1 h 05 min</td><td class="mono">16:40</td></tr>
<tr><td class="mono">20260818_142118_d4e5f6</td><td>aider</td><td>obsigate</td><td><span class="pill fail">échec</span></td><td class="mono">12 min</td><td class="mono">14:33</td></tr>
<tr><td class="mono">20260818_113047_e7f8a9</td><td>gemini-cli</td><td>imago</td><td><span class="pill ok">terminée</span></td><td class="mono">42 min</td><td class="mono">12:12</td></tr>
<tr><td class="mono">20260817_220933_b0c1d2</td><td>claude-code</td><td>agent-manager</td><td><span class="pill ok">terminée</span></td><td class="mono">2 h 47 min</td><td class="mono">23:50</td></tr>
<tr><td class="mono">20260817_183011_e2f3a4</td><td>jcode</td><td>agent-manager</td><td><span class="pill ok">terminée</span></td><td class="mono">1 h 22 min</td><td class="mono">19:35</td></tr>
</tbody>
</table>
<div class="empty-note" id="sess-empty" style="display:none">Aucune session ne correspond au filtre</div>
</div>
</section>
<!-- ===== EVENTS ===== -->
<section class="view" id="events">
<div class="panel">
<h3>Journal d'événements — aujourd'hui</h3>
<ul class="tl">
<li><span class="dot" style="background:var(--accent2)"></span><span class="t"><span class="a">start</span> codex — projet flowdeck</span><span class="m">17:18</span></li>
<li><span class="dot" style="background:var(--ok)"></span><span class="t"><span class="a">stop</span> claude-code — session 1 h 05 min</span><span class="m">16:40</span></li>
<li><span class="dot" style="background:var(--accent2)"></span><span class="t"><span class="a">start</span> claude-code — projet agent-manager</span><span class="m">15:34</span></li>
<li><span class="dot" style="background:var(--err)"></span><span class="t"><span class="a">run</span> aider — exit code 1 (fichier manquant)</span><span class="m">14:33</span></li>
<li><span class="dot" style="background:var(--purple)"></span><span class="t"><span class="a">update</span> jcode 0.76.0 → 0.78.1</span><span class="m">13:05</span></li>
<li><span class="dot" style="background:var(--ok)"></span><span class="t"><span class="a">stop</span> gemini-cli — session 42 min</span><span class="m">12:12</span></li>
<li><span class="dot" style="background:var(--accent2)"></span><span class="t"><span class="a">start</span> gemini-cli — projet imago</span><span class="m">11:30</span></li>
<li><span class="dot" style="background:var(--purple)"></span><span class="t"><span class="a">install</span> opencode 0.14.2 — méthode binary</span><span class="m">10:02</span></li>
</ul>
</div>
</section>
<!-- ===== PROJECTS ===== -->
<section class="view" id="projects">
<div class="kpis" style="grid-template-columns:repeat(3,1fr)">
<div class="kpi"><div class="label">Projets suivis</div><div class="value">4</div><div class="delta up">▲ 1 nouveau cette semaine</div></div>
<div class="kpi"><div class="label">Sessions (7 j)</div><div class="value">47</div><div class="delta up">▲ +12 %</div></div>
<div class="kpi"><div class="label">Temps total</div><div class="value">96 h</div><div class="delta up">▲ +8 h vs semaine préc.</div></div>
</div>
<div class="panel">
<h3>Projets</h3>
<table>
<thead><tr><th>Projet</th><th>Agents</th><th>Sessions (7 j)</th><th>Temps</th><th>Dernière activité</th></tr></thead>
<tbody>
<tr><td><b>agent-manager</b> <span class="mono">· Rust</span></td><td>claude-code · jcode · codex</td><td>24</td><td class="mono">41 h</td><td class="mono">il y a 2 min</td></tr>
<tr><td><b>flowdeck</b> <span class="mono">· Python</span></td><td>codex · aider</td><td>11</td><td class="mono">19 h</td><td class="mono">il y a 14 min</td></tr>
<tr><td><b>obsigate</b> <span class="mono">· Python</span></td><td>aider · gemini-cli</td><td>8</td><td class="mono">22 h</td><td class="mono">il y a 3 h</td></tr>
<tr><td><b>imago</b> <span class="mono">· Python</span></td><td>gemini-cli</td><td>4</td><td class="mono">14 h</td><td class="mono">il y a 5 h</td></tr>
</tbody>
</table>
</div>
</section>
</main>
</div>
<script>
const titles = { overview:"Vue d'ensemble", stats:"Statistiques", sessions:"Sessions", events:"Événements", projects:"Projets" };
document.querySelectorAll('.nav-item').forEach(el => {
el.addEventListener('click', () => {
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
el.classList.add('active');
const v = el.dataset.view;
document.getElementById(v).classList.add('active');
document.getElementById('view-title').textContent = titles[v];
});
});
function fakeRefresh(){
const t = new Date();
document.getElementById('last-update').textContent = 'Dernière mise à jour : ' + t.toLocaleTimeString('fr-FR');
}
function filterSessions(q){
q = q.toLowerCase();
let shown = 0;
document.querySelectorAll('#sess-body tr').forEach(tr => {
const ok = tr.textContent.toLowerCase().includes(q);
tr.style.display = ok ? '' : 'none';
if (ok) shown++;
});
document.getElementById('sess-empty').style.display = shown ? 'none' : 'block';
}
</script>
</body>
</html>
+216
View File
@@ -0,0 +1,216 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>am web — Admin clair (mockup)</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg:#f6f7f9; --panel:#ffffff; --border:#e3e7ec; --fg:#1c2430; --muted:#64748b;
--accent:#2563eb; --ok:#16a34a; --warn:#d97706; --err:#dc2626; --purple:#7c3aed;
}
body {
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
background:var(--bg); color:var(--fg); line-height:1.5; font-size:14px;
}
/* header bar */
.head {
background:var(--panel); border-bottom:1px solid var(--border);
padding:0 28px; height:56px; display:flex; align-items:center; gap:24px; position:sticky; top:0; z-index:5;
}
.head .brand { font-weight:800; font-size:16px; display:flex; align-items:center; gap:9px; }
.brand .sq { width:26px; height:26px; border-radius:7px; background:var(--accent); color:#fff; display:flex; align-items:center; justify-content:center; font-size:13px; }
.tabs { display:flex; gap:4px; height:100%; }
.tab { display:flex; align-items:center; padding:0 16px; color:var(--muted); cursor:pointer; font-size:13.5px; font-weight:500; border-bottom:2px solid transparent; }
.tab:hover { color:var(--fg); }
.tab.active { color:var(--accent); border-bottom-color:var(--accent); font-weight:600; }
.head .spacer { flex:1; }
.hbadge { font-size:11.5px; color:var(--muted); background:var(--bg); border:1px solid var(--border); padding:4px 10px; border-radius:99px; font-family:ui-monospace,Consolas,monospace; }
.hbadge.ok { color:var(--ok); }
.btn { background:var(--accent); color:#fff; border:none; border-radius:7px; padding:7px 14px; font-weight:600; font-size:13px; cursor:pointer; }
.btn:hover { background:#1d4ed8; }
.btn.ghost { background:transparent; color:var(--muted); border:1px solid var(--border); font-weight:500; }
.btn.ghost:hover { color:var(--fg); }
.wrap { max-width:1150px; margin:26px auto; padding:0 28px; }
.page-title { font-size:19px; font-weight:700; margin-bottom:4px; }
.page-sub { color:var(--muted); font-size:12.5px; margin-bottom:20px; }
.cards { display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-bottom:18px; }
.card { background:var(--panel); border:1px solid var(--border); border-radius:10px; padding:15px 17px; box-shadow:0 1px 2px rgba(16,24,40,.04); }
.card .l { color:var(--muted); font-size:11.5px; text-transform:uppercase; letter-spacing:.5px; }
.card .v { font-size:26px; font-weight:800; margin-top:5px; color:#111827; }
.card .d { font-size:11.5px; margin-top:3px; } .card .d.up{color:var(--ok);} .card .d.down{color:var(--err);}
.grid2 { display:grid; grid-template-columns:1fr 1fr; gap:14px; margin-bottom:18px; }
.panel { background:var(--panel); border:1px solid var(--border); border-radius:10px; padding:16px 18px; box-shadow:0 1px 2px rgba(16,24,40,.04); }
.panel h3 { font-size:12.5px; text-transform:uppercase; letter-spacing:.5px; color:var(--muted); margin-bottom:14px; }
table { width:100%; border-collapse:collapse; font-size:13px; }
th { text-align:left; color:var(--muted); font-weight:600; font-size:11px; text-transform:uppercase; letter-spacing:.4px; padding:7px 10px; border-bottom:1px solid var(--border); background:var(--bg); }
td { padding:9px 10px; border-bottom:1px solid var(--border); }
tbody tr:hover { background:#fafbfc; }
.pill { font-size:10.5px; padding:2px 9px; border-radius:99px; font-weight:600; }
.pill.ok { background:#dcfce7; color:var(--ok); } .pill.run { background:#dbeafe; color:#1d4ed8; }
.pill.fail { background:#fee2e2; color:var(--err); } .pill.mut { background:#ede9fe; color:var(--purple); }
.mono { font-family:ui-monospace,Consolas,monospace; font-size:11.5px; color:var(--muted); }
.bar-row { display:flex; align-items:center; gap:10px; margin-bottom:9px; font-size:12.5px; }
.bar-row .name { width:110px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.bar-row .track { flex:1; height:12px; background:#eef1f5; border-radius:6px; overflow:hidden; }
.bar-row .fill { height:100%; background:var(--accent); border-radius:6px; }
.bar-row .val { width:32px; text-align:right; color:var(--muted); font-family:ui-monospace,monospace; }
.search { width:100%; border:1px solid var(--border); border-radius:7px; padding:8px 12px; font-size:13px; margin-bottom:10px; background:#fff; }
.search:focus { outline:none; border-color:var(--accent); }
.view { display:none; } .view.active { display:block; }
@media (max-width:900px){ .cards{grid-template-columns:repeat(2,1fr);} .grid2{grid-template-columns:1fr;} }
</style>
</head>
<body>
<div class="head">
<div class="brand"><span class="sq">am</span> agent-manager</div>
<div class="tabs">
<div class="tab active" data-view="overview">Vue d'ensemble</div>
<div class="tab" data-view="stats">Statistiques</div>
<div class="tab" data-view="sessions">Sessions</div>
<div class="tab" data-view="events">Événements</div>
<div class="tab" data-view="projects">Projets</div>
</div>
<div class="spacer"></div>
<span class="hbadge ok">● local</span>
<span class="hbadge">127.0.0.1:7878 · v0.6.0</span>
<button class="btn ghost" onclick="fakeRefresh()">↻ Actualiser</button>
</div>
<div class="wrap">
<div class="page-title" id="view-title">Vue d'ensemble</div>
<div class="page-sub" id="last-update">Dernière mise à jour : il y a 5 s · lang FR</div>
<section class="view active" id="overview">
<div class="cards">
<div class="card"><div class="l">Agents installés</div><div class="v">9</div><div class="d up">▲ +1 cette semaine</div></div>
<div class="card"><div class="l">En cours</div><div class="v">2</div><div class="d up">● claude-code · codex</div></div>
<div class="card"><div class="l">Sessions (7 j)</div><div class="v">47</div><div class="d up">▲ +12 %</div></div>
<div class="card"><div class="l">Événements (24 h)</div><div class="v">132</div><div class="d down">▼ 8 % vs hier</div></div>
</div>
<div class="grid2">
<div class="panel">
<h3>Top agents — sessions (7 j)</h3>
<div class="bar-row"><span class="name">claude-code</span><div class="track"><div class="fill" style="width:100%"></div></div><span class="val">18</span></div>
<div class="bar-row"><span class="name">aider</span><div class="track"><div class="fill" style="width:50%"></div></div><span class="val">9</span></div>
<div class="bar-row"><span class="name">codex</span><div class="track"><div class="fill" style="width:44%"></div></div><span class="val">8</span></div>
<div class="bar-row"><span class="name">gemini-cli</span><div class="track"><div class="fill" style="width:28%"></div></div><span class="val">5</span></div>
<div class="bar-row"><span class="name">jcode</span><div class="track"><div class="fill" style="width:22%"></div></div><span class="val">4</span></div>
<div class="bar-row"><span class="name">opencode</span><div class="track"><div class="fill" style="width:17%"></div></div><span class="val">3</span></div>
</div>
<div class="panel">
<h3>Activité — sessions par jour (14 j)</h3>
<svg viewBox="0 0 480 150" width="100%" height="150">
<defs><linearGradient id="g2" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#2563eb" stop-opacity=".2"/><stop offset="1" stop-color="#2563eb" stop-opacity="0"/></linearGradient></defs>
<path d="M0,120 L37,108 L74,116 L111,96 L148,104 L185,84 L222,92 L259,72 L296,80 L333,62 L370,70 L407,48 L444,58 L480,34 L480,150 L0,150 Z" fill="url(#g2)"/>
<polyline points="0,120 37,108 74,116 111,96 148,104 185,84 222,92 259,72 296,80 333,62 370,70 407,48 444,58 480,34" fill="none" stroke="#2563eb" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<div style="display:flex;justify-content:space-between;color:var(--muted);font-size:10.5px;font-family:ui-monospace,monospace;margin-top:4px"><span>04 août</span><span>11 août</span><span>aujourd'hui</span></div>
</div>
</div>
<div class="panel">
<h3>Sessions — 7 derniers jours</h3>
<input class="search" placeholder="Filtrer par agent, projet ou id…" oninput="filterSessions(this.value)">
<table>
<thead><tr><th>Session</th><th>Agent</th><th>Projet</th><th>Statut</th><th>Durée</th><th>Fin</th></tr></thead>
<tbody id="sess-body-ov">
<tr><td class="mono">20260818_172631_b4e2f1</td><td>claude-code</td><td>agent-manager</td><td><span class="pill run">en cours</span></td><td class="mono">3 h 12 min</td><td class="mono">—</td></tr>
<tr><td class="mono">20260818_171812_c9d0a4</td><td>codex</td><td>flowdeck</td><td><span class="pill run">en cours</span></td><td class="mono">14 min</td><td class="mono">—</td></tr>
<tr><td class="mono">20260818_153402_a1b2c3</td><td>claude-code</td><td>agent-manager</td><td><span class="pill ok">terminée</span></td><td class="mono">1 h 05 min</td><td class="mono">16:40</td></tr>
<tr><td class="mono">20260818_142118_d4e5f6</td><td>aider</td><td>obsigate</td><td><span class="pill fail">échec</span></td><td class="mono">12 min</td><td class="mono">14:33</td></tr>
<tr><td class="mono">20260818_113047_e7f8a9</td><td>gemini-cli</td><td>imago</td><td><span class="pill ok">terminée</span></td><td class="mono">42 min</td><td class="mono">12:12</td></tr>
<tr><td class="mono">20260817_220933_b0c1d2</td><td>claude-code</td><td>agent-manager</td><td><span class="pill ok">terminée</span></td><td class="mono">2 h 47 min</td><td class="mono">23:50</td></tr>
</tbody>
</table>
</div>
</section>
<section class="view" id="stats">
<div class="panel">
<h3>Statistiques par agent — période 30 j</h3>
<table>
<thead><tr><th>Agent</th><th>Sessions</th><th>Durée totale</th><th>Taux de succès</th><th>Coût estimé</th><th>Dernière activité</th></tr></thead>
<tbody>
<tr><td>claude-code</td><td>54</td><td class="mono">38 h 12 m</td><td><span class="pill ok">94 %</span></td><td class="mono">12,40 €</td><td class="mono">il y a 2 min</td></tr>
<tr><td>aider</td><td>31</td><td class="mono">21 h 48 m</td><td><span class="pill ok">87 %</span></td><td class="mono">4,10 €</td><td class="mono">il y a 3 h</td></tr>
<tr><td>codex</td><td>27</td><td class="mono">19 h 05 m</td><td><span class="pill ok">89 %</span></td><td class="mono">8,75 €</td><td class="mono">il y a 14 min</td></tr>
<tr><td>gemini-cli</td><td>14</td><td class="mono">6 h 33 m</td><td><span class="pill ok">79 %</span></td><td class="mono">0 € (local)</td><td class="mono">il y a 5 h</td></tr>
<tr><td>jcode</td><td>12</td><td class="mono">5 h 02 m</td><td><span class="pill run">67 %</span></td><td class="mono">1,20 €</td><td class="mono">hier</td></tr>
</tbody>
</table>
</div>
</section>
<section class="view" id="sessions">
<div class="panel">
<h3>Sessions — 7 derniers jours</h3>
<input class="search" placeholder="Filtrer par agent, projet ou id…" oninput="filterSessions(this.value)">
<table>
<thead><tr><th>Session</th><th>Agent</th><th>Projet</th><th>Statut</th><th>Durée</th><th>Fin</th></tr></thead>
<tbody id="sess-body">
<tr><td class="mono">20260818_172631_b4e2f1</td><td>claude-code</td><td>agent-manager</td><td><span class="pill run">en cours</span></td><td class="mono">3 h 12 min</td><td class="mono">—</td></tr>
<tr><td class="mono">20260818_171812_c9d0a4</td><td>codex</td><td>flowdeck</td><td><span class="pill run">en cours</span></td><td class="mono">14 min</td><td class="mono">—</td></tr>
<tr><td class="mono">20260818_153402_a1b2c3</td><td>claude-code</td><td>agent-manager</td><td><span class="pill ok">terminée</span></td><td class="mono">1 h 05 min</td><td class="mono">16:40</td></tr>
<tr><td class="mono">20260818_142118_d4e5f6</td><td>aider</td><td>obsigate</td><td><span class="pill fail">échec</span></td><td class="mono">12 min</td><td class="mono">14:33</td></tr>
</tbody>
</table>
</div>
</section>
<section class="view" id="events">
<div class="panel">
<h3>Journal d'événements — aujourd'hui</h3>
<table>
<thead><tr><th>Heure</th><th>Type</th><th>Agent</th><th>Détail</th></tr></thead>
<tbody>
<tr><td class="mono">17:18</td><td><span class="pill run">start</span></td><td>codex</td><td>projet flowdeck</td></tr>
<tr><td class="mono">16:40</td><td><span class="pill ok">stop</span></td><td>claude-code</td><td>session 1 h 05 min</td></tr>
<tr><td class="mono">15:34</td><td><span class="pill run">start</span></td><td>claude-code</td><td>projet agent-manager</td></tr>
<tr><td class="mono">14:33</td><td><span class="pill fail">run</span></td><td>aider</td><td>exit code 1</td></tr>
<tr><td class="mono">13:05</td><td><span class="pill mut">update</span></td><td>jcode</td><td>0.76.0 → 0.78.1</td></tr>
<tr><td class="mono">10:02</td><td><span class="pill mut">install</span></td><td>opencode</td><td>0.14.2 — méthode binary</td></tr>
</tbody>
</table>
</div>
</section>
<section class="view" id="projects">
<div class="panel">
<h3>Projets</h3>
<table>
<thead><tr><th>Projet</th><th>Agents</th><th>Sessions (7 j)</th><th>Temps</th><th>Dernière activité</th></tr></thead>
<tbody>
<tr><td><b>agent-manager</b> <span class="mono">· Rust</span></td><td>claude-code · jcode · codex</td><td>24</td><td class="mono">41 h</td><td class="mono">il y a 2 min</td></tr>
<tr><td><b>flowdeck</b> <span class="mono">· Python</span></td><td>codex · aider</td><td>11</td><td class="mono">19 h</td><td class="mono">il y a 14 min</td></tr>
<tr><td><b>obsigate</b> <span class="mono">· Python</span></td><td>aider · gemini-cli</td><td>8</td><td class="mono">22 h</td><td class="mono">il y a 3 h</td></tr>
<tr><td><b>imago</b> <span class="mono">· Python</span></td><td>gemini-cli</td><td>4</td><td class="mono">14 h</td><td class="mono">il y a 5 h</td></tr>
</tbody>
</table>
</div>
</section>
</div>
<script>
const titles = { overview:"Vue d'ensemble", stats:"Statistiques", sessions:"Sessions", events:"Événements", projects:"Projets" };
document.querySelectorAll('.tab').forEach(el => {
el.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(n => n.classList.remove('active'));
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
el.classList.add('active');
document.getElementById(el.dataset.view).classList.add('active');
document.getElementById('view-title').textContent = titles[el.dataset.view];
});
});
function fakeRefresh(){
document.getElementById('last-update').textContent = 'Dernière mise à jour : ' + new Date().toLocaleTimeString('fr-FR') + ' · lang FR';
}
function filterSessions(q){
q = q.toLowerCase();
document.querySelectorAll('#sess-body tr, #sess-body-ov tr').forEach(tr => {
tr.style.display = tr.textContent.toLowerCase().includes(q) ? '' : 'none';
});
}
</script>
</body>
</html>
+222
View File
@@ -0,0 +1,222 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>am web — Terminal dense (mockup)</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg:#0d1117; --panel:#10161f; --border:#212a36; --fg:#c9d4e0; --muted:#6e7b8a;
--accent:#3fb950; --accent2:#58a6ff; --ok:#3fb950; --warn:#d29922; --err:#f85149; --purple:#bc8cff;
}
body {
font-family:ui-monospace,SFMono-Regular,Consolas,"Cascadia Mono",Menlo,monospace;
background:var(--bg); color:var(--fg); font-size:12.5px; line-height:1.5;
}
.top {
border-bottom:1px solid var(--border); padding:9px 16px; display:flex; align-items:center; gap:16px;
background:var(--panel); position:sticky; top:0; z-index:5;
}
.top .prompt { color:var(--accent); font-weight:700; }
.top .brand { color:var(--fg); }
.tabs { display:flex; gap:2px; }
.tab { padding:3px 10px; cursor:pointer; color:var(--muted); border:1px solid transparent; border-radius:5px; }
.tab:hover { color:var(--fg); }
.tab.active { color:var(--accent); background:rgba(63,185,80,.08); border-color:rgba(63,185,80,.3); }
.top .spacer { flex:1; }
.stat { color:var(--muted); font-size:11.5px; }
.stat b { color:var(--ok); font-weight:400; }
.btn { background:transparent; border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:3px 10px; font-family:inherit; font-size:12px; cursor:pointer; }
.btn:hover { border-color:var(--accent); color:var(--accent); }
.wrap { max-width:1180px; margin:0 auto; padding:14px 16px 40px; }
/* overview row */
.kv { display:grid; grid-template-columns:repeat(4,1fr); gap:8px; margin-bottom:14px; }
.kv-item { border:1px solid var(--border); background:var(--panel); border-radius:6px; padding:9px 12px; }
.kv-item .k { color:var(--muted); font-size:10.5px; text-transform:uppercase; letter-spacing:.8px; }
.kv-item .v { font-size:21px; font-weight:700; color:var(--fg); margin-top:2px; }
.kv-item .v small { color:var(--muted); font-size:11px; font-weight:400; }
.kv-item.running .v { color:var(--accent); }
.kv-item.running .v::before { content:"● "; }
.row { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-bottom:14px; }
.box { border:1px solid var(--border); background:var(--panel); border-radius:6px; overflow:hidden; }
.box .h { padding:7px 12px; border-bottom:1px solid var(--border); color:var(--muted); font-size:10.5px; text-transform:uppercase; letter-spacing:.8px; display:flex; justify-content:space-between; }
.box .h .n { color:var(--accent); text-transform:none; letter-spacing:0; }
.box .b { padding:10px 12px; }
/* bars */
.br { display:grid; grid-template-columns:95px 1fr 30px; gap:8px; align-items:center; margin-bottom:6px; }
.br .nm { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.br .tr { height:10px; background:var(--bg); border-radius:3px; overflow:hidden; }
.br .fl { height:100%; background:var(--accent); opacity:.85; }
.br .vl { text-align:right; color:var(--muted); }
/* tables */
table { width:100%; border-collapse:collapse; }
th { text-align:left; color:var(--muted); font-weight:400; font-size:10.5px; text-transform:uppercase; letter-spacing:.6px; padding:5px 10px; border-bottom:1px solid var(--border); }
td { padding:5px 10px; border-bottom:1px solid rgba(33,42,54,.6); white-space:nowrap; }
td.wrap { white-space:normal; }
tbody tr:hover { background:rgba(63,185,80,.05); }
.st { font-size:11px; } .st::before { content:"● "; }
.st.ok { color:var(--ok); } .st.run { color:var(--accent2); } .st.fail { color:var(--err); } .st.mut { color:var(--purple); }
.dim { color:var(--muted); }
.view { display:none; } .view.active { display:block; }
@media (max-width:900px){ .row{grid-template-columns:1fr;} .kv{grid-template-columns:repeat(2,1fr);} }
</style>
</head>
<body>
<div class="top">
<span class="prompt">$</span><span class="brand">am web</span>
<div class="tabs">
<div class="tab active" data-view="overview">overview</div>
<div class="tab" data-view="stats">stats</div>
<div class="tab" data-view="sessions">sessions</div>
<div class="tab" data-view="events">events</div>
<div class="tab" data-view="projects">projects</div>
</div>
<div class="spacer"></div>
<span class="stat">v0.6.0 · <b>●</b> local · 127.0.0.1:7878</span>
<button class="btn" onclick="fakeRefresh()">↻ refresh</button>
</div>
<div class="wrap">
<section class="view active" id="overview">
<div class="kv">
<div class="kv-item"><div class="k">agents installés</div><div class="v">9</div></div>
<div class="kv-item running"><div class="k">en cours</div><div class="v">2</div></div>
<div class="kv-item"><div class="k">sessions 7j</div><div class="v">47 <small>▲12%</small></div></div>
<div class="kv-item"><div class="k">events 24h</div><div class="v">132 <small>▼8%</small></div></div>
</div>
<div class="row">
<div class="box">
<div class="h">top agents · sessions 7j</div>
<div class="b">
<div class="br"><span class="nm">claude-code</span><div class="tr"><div class="fl" style="width:100%"></div></div><span class="vl">18</span></div>
<div class="br"><span class="nm">aider</span><div class="tr"><div class="fl" style="width:50%"></div></div><span class="vl">9</span></div>
<div class="br"><span class="nm">codex</span><div class="tr"><div class="fl" style="width:44%"></div></div><span class="vl">8</span></div>
<div class="br"><span class="nm">gemini-cli</span><div class="tr"><div class="fl" style="width:28%"></div></div><span class="vl">5</span></div>
<div class="br"><span class="nm">jcode</span><div class="tr"><div class="fl" style="width:22%"></div></div><span class="vl">4</span></div>
<div class="br"><span class="nm">opencode</span><div class="tr"><div class="fl" style="width:17%"></div></div><span class="vl">3</span></div>
</div>
</div>
<div class="box">
<div class="h">activité 14j <span class="n">sessions/jour</span></div>
<div class="b">
<svg viewBox="0 0 480 150" width="100%" height="140">
<defs><linearGradient id="g3" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#3fb950" stop-opacity=".3"/><stop offset="1" stop-color="#3fb950" stop-opacity="0"/></linearGradient></defs>
<path d="M0,120 L37,108 L74,116 L111,96 L148,104 L185,84 L222,92 L259,72 L296,80 L333,62 L370,70 L407,48 L444,58 L480,34 L480,150 L0,150 Z" fill="url(#g3)"/>
<polyline points="0,120 37,108 74,116 111,96 148,104 185,84 222,92 259,72 296,80 333,62 370,70 407,48 444,58 480,34" fill="none" stroke="#3fb950" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<div style="display:flex;justify-content:space-between;color:var(--muted);font-size:10px"><span>04/08</span><span>11/08</span><span>18/08</span></div>
</div>
</div>
</div>
<div class="box">
<div class="h">sessions en cours</div>
<div class="b" style="padding:0">
<table>
<thead><tr><th>id</th><th>agent</th><th>projet</th><th>durée</th><th>depuis</th></tr></thead>
<tbody>
<tr><td class="dim">20260818_172631_b4e2f1</td><td>claude-code</td><td>agent-manager</td><td>3h12m</td><td class="dim">14:26</td></tr>
<tr><td class="dim">20260818_171812_c9d0a4</td><td>codex</td><td>flowdeck</td><td>14m</td><td class="dim">17:18</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="view" id="stats">
<div class="box">
<div class="h">stats par agent · 30j</div>
<div class="b" style="padding:0">
<table>
<thead><tr><th>agent</th><th>sessions</th><th>durée</th><th>succès</th><th>coût</th><th>dernière act.</th></tr></thead>
<tbody>
<tr><td>claude-code</td><td>54</td><td>38h12m</td><td><span class="st ok">94%</span></td><td class="dim">12,40 €</td><td class="dim">2 min</td></tr>
<tr><td>aider</td><td>31</td><td>21h48m</td><td><span class="st ok">87%</span></td><td class="dim">4,10 €</td><td class="dim">3 h</td></tr>
<tr><td>codex</td><td>27</td><td>19h05m</td><td><span class="st ok">89%</span></td><td class="dim">8,75 €</td><td class="dim">14 min</td></tr>
<tr><td>gemini-cli</td><td>14</td><td>6h33m</td><td><span class="st ok">79%</span></td><td class="dim">0 € local</td><td class="dim">5 h</td></tr>
<tr><td>jcode</td><td>12</td><td>5h02m</td><td><span class="st run">67%</span></td><td class="dim">1,20 €</td><td class="dim">hier</td></tr>
<tr><td>opencode</td><td>9</td><td>4h40m</td><td><span class="st fail">55%</span></td><td class="dim">0,90 €</td><td class="dim">2 j</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="view" id="sessions">
<div class="box">
<div class="h">sessions · 7j</div>
<div class="b" style="padding:8px 12px 0">
<input id="sess-search" placeholder="$ grep sessions…" style="width:100%;background:var(--bg);border:1px solid var(--border);color:var(--fg);font-family:inherit;font-size:12px;padding:6px 10px;border-radius:4px;margin-bottom:6px" oninput="filterSessions(this.value)">
</div>
<div class="b" style="padding:0">
<table>
<thead><tr><th>id</th><th>agent</th><th>projet</th><th>statut</th><th>durée</th><th>fin</th></tr></thead>
<tbody id="sess-body">
<tr><td class="dim">20260818_172631_b4e2f1</td><td>claude-code</td><td>agent-manager</td><td><span class="st run">running</span></td><td>3h12m</td><td class="dim">—</td></tr>
<tr><td class="dim">20260818_171812_c9d0a4</td><td>codex</td><td>flowdeck</td><td><span class="st run">running</span></td><td>14m</td><td class="dim">—</td></tr>
<tr><td class="dim">20260818_153402_a1b2c3</td><td>claude-code</td><td>agent-manager</td><td><span class="st ok">done</span></td><td>1h05m</td><td class="dim">16:40</td></tr>
<tr><td class="dim">20260818_142118_d4e5f6</td><td>aider</td><td>obsigate</td><td><span class="st fail">failed</span></td><td>12m</td><td class="dim">14:33</td></tr>
<tr><td class="dim">20260818_113047_e7f8a9</td><td>gemini-cli</td><td>imago</td><td><span class="st ok">done</span></td><td>42m</td><td class="dim">12:12</td></tr>
<tr><td class="dim">20260817_220933_b0c1d2</td><td>claude-code</td><td>agent-manager</td><td><span class="st ok">done</span></td><td>2h47m</td><td class="dim">23:50</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="view" id="events">
<div class="box">
<div class="h">events · aujourd'hui</div>
<div class="b" style="padding:0">
<table>
<thead><tr><th>heure</th><th>type</th><th>agent</th><th class="wrap">détail</th></tr></thead>
<tbody>
<tr><td class="dim">17:18</td><td><span class="st run">start</span></td><td>codex</td><td class="wrap">projet flowdeck</td></tr>
<tr><td class="dim">16:40</td><td><span class="st ok">stop</span></td><td>claude-code</td><td class="wrap">session 1h05m</td></tr>
<tr><td class="dim">15:34</td><td><span class="st run">start</span></td><td>claude-code</td><td class="wrap">projet agent-manager</td></tr>
<tr><td class="dim">14:33</td><td><span class="st fail">run</span></td><td>aider</td><td class="wrap">exit code 1 — fichier manquant</td></tr>
<tr><td class="dim">13:05</td><td><span class="st mut">update</span></td><td>jcode</td><td class="wrap">0.76.0 → 0.78.1</td></tr>
<tr><td class="dim">10:02</td><td><span class="st mut">install</span></td><td>opencode</td><td class="wrap">0.14.2 — méthode binary</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="view" id="projects">
<div class="box">
<div class="h">projects</div>
<div class="b" style="padding:0">
<table>
<thead><tr><th>projet</th><th>agents</th><th>sessions 7j</th><th>temps</th><th>dernière act.</th></tr></thead>
<tbody>
<tr><td>agent-manager <span class="dim">· Rust</span></td><td>claude-code · jcode · codex</td><td>24</td><td class="dim">41h</td><td class="dim">2 min</td></tr>
<tr><td>flowdeck <span class="dim">· Python</span></td><td>codex · aider</td><td>11</td><td class="dim">19h</td><td class="dim">14 min</td></tr>
<tr><td>obsigate <span class="dim">· Python</span></td><td>aider · gemini-cli</td><td>8</td><td class="dim">22h</td><td class="dim">3 h</td></tr>
<tr><td>imago <span class="dim">· Python</span></td><td>gemini-cli</td><td>4</td><td class="dim">14h</td><td class="dim">5 h</td></tr>
</tbody>
</table>
</div>
</div>
</section>
</div>
<script>
document.querySelectorAll('.tab').forEach(el => {
el.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(n => n.classList.remove('active'));
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
el.classList.add('active');
document.getElementById(el.dataset.view).classList.add('active');
});
});
function fakeRefresh(){}
function filterSessions(q){
q = q.toLowerCase();
document.querySelectorAll('#sess-body tr').forEach(tr => {
tr.style.display = tr.textContent.toLowerCase().includes(q) ? '' : 'none';
});
}
</script>
</body>
</html>
+50
View File
@@ -319,6 +319,12 @@ pub fn resolve_exec(
Some(e) if !e.bins.is_empty() => {
let p = std::path::PathBuf::from(&e.bins[0]);
if p.exists() || app.dry_run() {
// The recorded bin replaces the run command's first token
// (e.g. "picoclaw"): consume it so it is not appended to
// the argument list. Regression: am start <agent géré>
// lançait "<bin> <token-run>" — picoclaw rejetait son propre
// nom comme sous-commande inconnue.
tokens.remove(0);
p.display().to_string()
} else {
tokens.remove(0)
@@ -473,6 +479,7 @@ pub fn select_method<'a>(
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[test]
fn routes_cmd_scripts_through_comspec() {
@@ -498,4 +505,47 @@ mod tests {
assert_eq!(program, "node");
assert_eq!(args, vec!["-v".to_string()]);
}
#[test]
fn resolve_exec_installed_agent_does_not_leak_run_token() {
let guard = tempfile::tempdir().unwrap();
let dir = guard.path().to_path_buf();
std::mem::forget(guard);
let cfg = dir.join("config.yaml");
std::fs::write(
&cfg,
"version: \"1.0\"\nsettings:\n auto_install_deps: false\n confirm_before_run: false\nagents:\n - name: x\n run: x\n installable: false\n",
)
.unwrap();
let cli = crate::cli::Cli::parse_from(["am", "--config", cfg.to_str().unwrap()]);
let mut app = crate::app::App::from_cli(cli).unwrap();
app.paths.state_file = dir.join("state.json");
app.state = crate::state::StateStore::new(app.paths.state_file.clone());
// A recorded installed bin that exists: the run token "x" must be
// consumed, not appended to the argument list.
let bin = dir.join("x.exe");
std::fs::write(&bin, b"x").unwrap();
app.state
.set(&crate::state::InstalledEntry {
name: "x".to_string(),
version: Some("1.0".to_string()),
method: "binary".to_string(),
install_dir: dir.display().to_string(),
bins: vec![bin.display().to_string()],
run: "x".to_string(),
installed_at: "2026-01-01T00:00:00Z".to_string(),
updated_at: None,
pid: None,
started_at: None,
})
.unwrap();
let agent = app.catalog.resolve("x").unwrap();
let exec = resolve_exec(&app, agent, &[], &BTreeMap::new()).unwrap();
assert_eq!(exec.program, bin.display().to_string());
assert!(
exec.args.is_empty(),
"run token leaked into args: {:?}",
exec.args
);
}
}