diff --git a/.gitignore b/.gitignore index 1b50400..8a42ca5 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ credentials/ *.pid memory.bak/ .use_openrouter +.gstack/ diff --git a/dashboard/index.html b/dashboard/index.html index 7bb9967..a28ca3f 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -155,6 +155,43 @@ color: #818cf8; } + .status-section-icon.services { + background: rgba(59, 130, 246, 0.15); + color: #3b82f6; + } + + .status-section-icon.sessions { + background: rgba(168, 85, 247, 0.15); + color: #a855f7; + } + + .status-section-icon.logs { + background: rgba(156, 163, 175, 0.15); + color: #9ca3af; + } + + .status-section-icon.doctor { + background: rgba(20, 184, 166, 0.15); + color: #14b8a6; + } + + .status-section-actions { + display: flex; + gap: var(--space-2); + align-items: center; + margin-left: auto; + } + + .status-section-actions .btn { + font-size: var(--text-xs); + padding: var(--space-1) var(--space-2); + } + + /* Heavy content (services grid, sessions list, logs, doctor) — full width, no left indent */ + .status-section-details.full-width { + padding: 0 var(--space-4) var(--space-3); + } + .status-section-info { flex: 1; min-width: 0; @@ -904,44 +941,9 @@ } /* ───────────────────────────────────────────────────────── - Eco panels (merged from eco.html) + Eco panels (merged from eco.html — now nested under Status) ───────────────────────────────────────────────────────── */ - /* Collapsible section (used by Services/Git/Sessions/Logs/Doctor) */ - .section { - margin-bottom: var(--space-4); - } - - .section-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: var(--space-3); - cursor: pointer; - user-select: none; - } - - .section.collapsed .section-body { display: none; } - .section.collapsed .section-header { margin-bottom: 0; } - .section.collapsed .sec-chev { transform: rotate(180deg); } - - .sec-chev { - width: 16px; - height: 16px; - color: var(--text-muted); - transition: transform var(--transition-fast); - flex-shrink: 0; - } - - .section-title { - font-size: var(--text-lg); - font-weight: 600; - color: var(--text-primary); - display: flex; - align-items: center; - gap: var(--space-2); - } - /* Service cards */ .services-grid { display: grid; @@ -1309,100 +1311,6 @@ font-size: var(--text-xs); } - /* Git panel (top-level) */ - .git-card { - background: var(--bg-surface); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - overflow: hidden; - } - - .git-header { - display: flex; - align-items: center; - gap: var(--space-3); - padding: var(--space-3) var(--space-4); - cursor: pointer; - user-select: none; - } - - .git-header:hover { background: var(--bg-elevated); } - - .git-icon { - width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-md); - background: rgba(249, 115, 22, 0.15); - color: #f97316; - flex-shrink: 0; - } - - .git-icon svg { width: 18px; height: 18px; } - - .git-info { flex: 1; min-width: 0; } - - .git-title { - font-size: var(--text-sm); - font-weight: 600; - color: var(--text-primary); - display: flex; - align-items: center; - gap: var(--space-2); - } - - .git-subtitle { - font-size: 13px; - color: var(--text-secondary); - margin-top: 2px; - } - - .git-badge { - padding: 3px 10px; - border-radius: var(--radius-sm); - font-size: 12px; - font-weight: 600; - } - - .git-badge.ok { background: rgba(34, 197, 94, 0.15); color: #22c55e; } - .git-badge.warning { background: rgba(249, 115, 22, 0.15); color: #f97316; } - .git-badge.error { background: rgba(239, 68, 68, 0.15); color: #ef4444; } - - .git-actions { - display: flex; - gap: var(--space-2); - margin-left: auto; - } - - .git-toggle { - width: 16px; - height: 16px; - color: var(--text-muted); - transition: transform var(--transition-fast); - } - - .git-card.collapsed .git-toggle { transform: rotate(-90deg); } - .git-card.collapsed .git-details { display: none; } - - .git-details { - padding: 0 var(--space-4) var(--space-3); - padding-left: calc(var(--space-4) + 32px + var(--space-3)); - } - - .git-detail-item { - display: flex; - align-items: center; - gap: var(--space-2); - font-size: 13px; - color: var(--text-primary); - padding: 2px 0; - } - - .git-detail-item svg { width: 14px; height: 14px; color: var(--text-secondary); } - .git-detail-item.uncommitted { color: #f97316; } - /* Spinner */ .spinner { display: inline-block; @@ -1536,6 +1444,99 @@
+ +
+
+
+ +
+ +
+ + +
+ +
+
+
+
+
+
+ + + + + + + -
- - - -
-
-

- - Git -

- -
-
-
-
- - - -
-
-

- - Sessions -

-
- - - -
-
-
-
-
-
-
-
-
-
-
-
-
- - - - - - @@ -1999,9 +1914,10 @@ // Status sections collapse state function getCollapsedStatusSections() { + const DEFAULTS = ['servicesSection', 'sessionsSection', 'anafSection', 'logsSection', 'doctorSection']; try { - return JSON.parse(localStorage.getItem('collapsedStatusSections') || '["anafSection"]'); - } catch { return ['anafSection']; } + return JSON.parse(localStorage.getItem('collapsedStatusSections') || JSON.stringify(DEFAULTS)); + } catch { return DEFAULTS; } } function setCollapsedStatusSections(sections) { @@ -2009,19 +1925,29 @@ } function initStatusSections() { - const collapsed = getCollapsedStatusSections(); - collapsed.forEach(id => { - const el = document.getElementById(id); - if (el) el.classList.add('collapsed'); + const collapsed = new Set(getCollapsedStatusSections()); + document.querySelectorAll('#statusBar .status-section').forEach(el => { + if (collapsed.has(el.id)) { + el.classList.add('collapsed'); + } else { + el.classList.remove('collapsed'); + } }); } function toggleStatusSection(id) { const el = document.getElementById(id); if (!el) return; - + + const wasCollapsed = el.classList.contains('collapsed'); el.classList.toggle('collapsed'); - + + // Lazy-load logs the first time the section opens. + if (id === 'logsSection' && wasCollapsed && el.dataset.loaded !== '1') { + el.dataset.loaded = '1'; + loadLogs(); + } + const collapsed = getCollapsedStatusSections(); const idx = collapsed.indexOf(id); if (el.classList.contains('collapsed')) { @@ -2092,17 +2018,17 @@ if (git.clean) { badge.textContent = 'curat'; - badge.className = 'git-badge ok'; + badge.className = 'status-badge ok'; } else { badge.textContent = git.uncommittedCount + ' modificări'; - badge.className = 'git-badge warning'; + badge.className = 'status-badge warning'; } subtitle.textContent = `${git.branch} · ${git.lastCommit.time}`; const GITEA_URL = 'https://gitea.romfast.ro/romfast/echo-core'; let html = ` -
+
${git.lastCommit.hash} ${git.lastCommit.message.substring(0, 50)}${git.lastCommit.message.length > 50 ? '...' : ''} (${git.lastCommit.time})
@@ -2110,15 +2036,15 @@ if (git.uncommittedCount > 0) { const files = (git.uncommittedParsed || []).slice(0, 5).map(f => f.path).join(', '); const more = git.uncommittedCount > 5 ? ` +${git.uncommittedCount - 5}` : ''; - html += `
+ html += ``; } if (git.diffStat) { - html += `
${escapeHtml(git.diffStat)}
`; + html += `
${escapeHtml(git.diffStat)}
`; } - html += `
+ html += ``; @@ -2129,7 +2055,7 @@ } catch (e) { console.error('Git status error:', e); badge.textContent = 'eroare'; - badge.className = 'git-badge error'; + badge.className = 'status-badge error'; subtitle.textContent = 'nu se poate încărca status-ul git'; details.innerHTML = ``; lucide.createIcons(); @@ -2882,22 +2808,6 @@ return div.innerHTML; } - function toggleSec(id) { - const sec = document.getElementById(id); - if (!sec) return; - const wasCollapsed = sec.classList.contains('collapsed'); - sec.classList.toggle('collapsed'); - // Lazy-load logs when expanded for the first time. - if (id === 'sec-logs' && wasCollapsed && sec.dataset.loaded !== '1') { - sec.dataset.loaded = '1'; - loadLogs(); - } - } - - function toggleGitDetails() { - document.getElementById('gitCard').classList.toggle('collapsed'); - } - function formatUptime(seconds) { if (!seconds && seconds !== 0) return '-'; const d = Math.floor(seconds / 86400); @@ -2934,6 +2844,21 @@ function renderServices(services) { const grid = document.getElementById('servicesGrid'); + + // Update the Status sub-section header (badge + subtitle). + const badge = document.getElementById('servicesBadge'); + const subtitle = document.getElementById('servicesSubtitle'); + if (badge && subtitle) { + const total = services.length; + const active = services.filter(s => s.active).length; + badge.textContent = `${active}/${total}`; + badge.className = active === total ? 'status-badge ok' : 'status-badge warning'; + const stopped = services.filter(s => !s.active).map(s => svcLabel(s.name)); + subtitle.textContent = stopped.length === 0 + ? 'Toate servicile rulează' + : `Oprite: ${stopped.join(', ')}`; + } + grid.innerHTML = services.map(svc => { const isTaskboard = svc.name === 'echo-taskboard'; const canControl = !isTaskboard; @@ -3099,6 +3024,21 @@ const container = document.getElementById('sessionsContent'); const clearBtn = document.getElementById('clearAllBtn'); + // Update the Status sub-section header (badge + subtitle). + const badge = document.getElementById('sessionsBadge'); + const subtitle = document.getElementById('sessionsSubtitle'); + if (badge && subtitle) { + const count = sessions ? sessions.length : 0; + badge.textContent = String(count); + badge.className = 'status-badge ok'; + if (count === 0) { + subtitle.textContent = 'Nicio sesiune activă'; + } else { + const platforms = [...new Set(sessions.map(s => s.platform || 'unknown'))]; + subtitle.textContent = platforms.join(', '); + } + } + if (!sessions || sessions.length === 0) { container.innerHTML = '
Nicio sesiune activă. Un DM către Echo pornește una.
'; clearBtn.style.display = 'none'; @@ -3218,7 +3158,12 @@ // ── Doctor ────────────────────────────────────────────── async function runDoctor() { - document.getElementById('sec-doctor').classList.remove('collapsed'); + // Expand both the parent Status bar and the Doctor sub-section so the user sees results. + document.getElementById('statusBar').classList.remove('collapsed'); + const doctorSection = document.getElementById('doctorSection'); + if (doctorSection.classList.contains('collapsed')) { + toggleStatusSection('doctorSection'); + } const list = document.getElementById('doctorChecks'); const btn = document.getElementById('doctorBtn'); diff --git a/memory/kb/index.json b/memory/kb/index.json index e859a36..b7e08ab 100644 --- a/memory/kb/index.json +++ b/memory/kb/index.json @@ -53,6 +53,21 @@ "video": "", "tldr": "| Yann Tiersen | Comptine d'un autre été (Extended 1h) | https://www.youtube.com/watch?v=nJQV1jCM0gk |" }, + { + "file": "notes-data/youtube/2026-04-25_claude-code-playwright-automates-anything.md", + "title": "Claude Code + Playwright Automates Literally Anything", + "date": "2026-04-25", + "tags": [], + "domains": [ + "work" + ], + "types": [], + "category": "youtube", + "project": null, + "subdir": null, + "video": "", + "tldr": "Claude Code + Playwright CLI = automatizezi orice în browser, inclusiv în conturi unde ești logat. Se scrie un script Playwright, se testează, agentul învață din erori și îmbunătățește scriptul iterat..." + }, { "file": "notes-data/tools/claude-agent-projects.md", "title": "Proiecte pe LXC 171 — claude-agent", @@ -8505,8 +8520,8 @@ "title": "Proiect: Vending Master - Integrare Website → ROA", "date": "2026-01-30", "tags": [ - "integrare", - "vending-master" + "vending-master", + "integrare" ], "domains": [ "work" @@ -8986,9 +9001,9 @@ } ], "stats": { - "total": 516, + "total": 517, "by_domain": { - "work": 162, + "work": 163, "health": 97, "growth": 233, "sprijin": 39, @@ -9006,7 +9021,7 @@ "reflectii": 3, "retete": 1, "tools": 7, - "youtube": 104, + "youtube": 105, "memory": 43 } }, diff --git a/memory/kb/youtube/2026-04-25_claude-code-playwright-automates-anything.md b/memory/kb/youtube/2026-04-25_claude-code-playwright-automates-anything.md new file mode 100644 index 0000000..4207929 --- /dev/null +++ b/memory/kb/youtube/2026-04-25_claude-code-playwright-automates-anything.md @@ -0,0 +1,72 @@ +# Claude Code + Playwright Automates Literally Anything + +**Sursa:** https://www.youtube.com/watch?v=J-6pnl5DQg8 +**Data:** 2026-04-25 +**Durata:** 18:50 +**Tags:** @work @automation @tools + +--- + +## TL;DR + +Claude Code + Playwright CLI = automatizezi orice în browser, inclusiv în conturi unde ești logat. Se scrie un script Playwright, se testează, agentul învață din erori și îmbunătățește scriptul iterativ. Use case-urile demo: QA automat pentru web app, scraping contacte (telefoane dentisti), și interacțiuni în platforme cu login (School). Se pot programa ca sarcini recurente headless. + +--- + +## Puncte Cheie + +1. **Playwright CLI vs MCP** — CLI consumă mult mai puțini tokeni decât MCP-ul Chrome DevTools (important pentru proiecte mari). Preferabil CLI. + +2. **Flow de bază:** + - Instalezi Playwright CLI în proiect + - Claude Code scrie un script JS pentru automatizare + - Rulezi script → găsești buguri → Claude îl îmbunătățește → repeat + - Fiecare rulare face scriptul mai bun (agent învață din erori) + +3. **QA automat:** + - Poți cere lui Claude să construiască un app și apoi să-l testeze singur + - Găsește buguri (ex: textarea cu Enter nu avansa), le fixează, rerulează + - Se poate transforma într-un **skill** → QA consistent și repetabil + +4. **Sesiuni autentificate:** + - Opțiunea 1: Persistent browser profile (folosește datele Chrome existente) + - Prima rulare: login manual → sesiunea se salvează → rulările ulterioare = logged in automat + - Funcționează pe platforme "anti-automation" (ex: School/Skool) + +5. **Skills recurente (autonome):** + - Se pot programa ca scheduled tasks pe desktop app + - Agentul AIS rulează automat: daily news roundup, likes pe wins, răspunsuri la notificări + - Headless = rulează fără să se vadă tab-ul + - Iterativ: când dă de un task nou (ex: vot pe poll) → creează un script nou → actualizează skill-ul + +6. **Iterarea e normală:** + - Nu te aștepți să funcționeze din prima + - Course-correct cu instrucțiuni clare (ex: "thumbs up galben = deja likat, sari-l") + - 4-5 iterații → rezultat stabil + +--- + +## Citate Notabile + +> "When you give a tool as powerful as Claude Code access to literally control a browser, you can actually automate anything." + +> "Every time that you use the script, it's going to get better." + +> "The fact that you could automate QA by having multiple different headed or headless browsers spin up and running and fixing is a complete game changer." + +--- + +## Idei & Aplicabilitate (Echo/Marius) + +- **roa2web QA:** Claude Code + Playwright ar putea testa automat interfața web ROA — detectează buguri înainte să ajungă la clienți +- **ANAF Monitor:** Dacă pagina ANAF se schimbă UI și hash-ul nu mai e suficient, Playwright poate extrage conținut vizual +- **Automatizare rapoarte:** Descărcare automată de rapoarte din portale fără API (ex: bancare, fiscale) +- **Skill recurent:** Orice flux repetitiv în browser (login + download + procesare) se poate transforma în scheduled task + +--- + +## Resurse Menționate + +- Playwright CLI (alternativa la MCP Chrome DevTools) +- Alte CLI-uri: Forcell Agent Browser, Open CLI +- Video următor (din canal): Scheduled Tasks cu Claude Code