Update dashboard, memory, root (~6)

This commit is contained in:
Echo
2026-02-02 12:47:34 +00:00
parent 9d080a099e
commit 170c65f579
6 changed files with 352 additions and 78 deletions

View File

@@ -294,21 +294,28 @@ Dashboard: https://moltbot.tailf7372d.ts.net/echo/
---
## 📊 Tipuri de propuneri în rapoarte
## 📊 Rapoarte cu propuneri acționabile
**Task-uri executabile** (le fac eu):
- Commit/push git
- Actualizare job-uri
- Creare fișiere/documente
- Automatizări
**📖 Documentație completă:** [`memory/kb/projects/FLUX-JOBURI.md`](memory/kb/projects/FLUX-JOBURI.md)
**Categorii propuneri:**
- 🤖 **FAC EU** - Echo singur (git, fișiere, jobs)
- 🤝 **TU+EU** - Marius + Echo (sesiuni, review)
- 👤 **FACI TU** - Doar Marius (decizii, acțiuni externe)
**Timing propuneri:**
- ⚡ **ACUM** - imediat după "1 pentru X"
- 🌙 **NOAPTE** - job 23:00 după "2 pentru X"
- 📅 **PROGRAMAT** - dată specifică
- ⏳ **CÂND POȚI** - fără deadline
**Format obligatoriu:** `A0 - Titlu [categorie] [timing]`
**Întrebări de reflecție** (pentru Marius):
- NU le trimit pe canal
- Le pun în `memory/kb/reflectii/YYYY-MM-DD_titlu.md`
- Marius le citește când vrea
**În raport:** Fii explicit ce tip e fiecare propunere!
---
## 📊 Flux Insights → Approved Tasks (OBLIGATORIU pentru rapoarte)

View File

@@ -107,13 +107,13 @@ memory_get path="memory/file.md" from=1 lines=50
| 01:00 | 03:00 | night-execute-late | #echo-work | Continuă execuția task-uri (run 2) |
| 03:00 | 05:00 | archive-tasks | #echo-work | Arhivează task-uri vechi |
| 06:00,17:00 | 08:00,19:00 | insights-extract | - | Extrage insights din memory/kb/ |
| 06:30 | 08:30 | morning-report | 📧 EMAIL | Raport dimineață HTML + TOATE propunerile cu context |
| 06:30 | 08:30 | morning-report | 📧 EMAIL | Raport dimineață - vezi [FLUX-JOBURI.md](memory/kb/projects/FLUX-JOBURI.md) |
| 07:00 | 09:00 | morning-coaching | #echo-self + 📧 | Gând + provocare → memory/kb/coaching/ |
| 07-17 | 09-19 | respiratie-orar | #echo-self | Pauze orare pattern interrupt |
| 15:00 mar,joi | 17:00 | project-checkin | #echo-work | Check-in Vending Master |
| 15:00 3/feb | 17:00 | grup-sprijin-pregatire | #echo-sprijin | Pregătire fișă grup joi |
| 15:00 5/feb | 17:00 | grup-sprijin-5feb | #echo-sprijin | Reminder grup sprijin |
| 18:00 | 20:00 | evening-report | 📧 EMAIL | Raport seară HTML + TOATE propunerile cu context |
| 18:00 | 20:00 | evening-report | 📧 EMAIL | Raport seară - vezi [FLUX-JOBURI.md](memory/kb/projects/FLUX-JOBURI.md) |
| 19:00 | 21:00 | evening-coaching | #echo-self + 📧 | Reflecție seară → memory/kb/coaching/ |
| 20:00 | 22:00 | seara-merit-reminder | #echo-self | Reminder lista "10 lucruri pentru care merit respect" |
| 19:00 dum | 21:00 | weekly-planning | #echo-work | Planning săptămânal |
@@ -249,6 +249,38 @@ cron action=update jobId=X # modifică job
| 108 | LXC | central-oracle | running |
| 171 | LXC | claude-agent | running |
##### LXC 171 - claude-agent (Development Environment)
- **IP:** 10.0.20.171
- **Resurse:** 4 cores, 16GB RAM, 32GB disk
- **OS:** Ubuntu (unprivileged, nesting enabled)
- **Tailscale:** Da (acces remote)
**Servicii:**
- `code-server@claude` — VS Code în browser
- `ttyd` — Web Terminal
- `ssh` — acces direct
**Claude Code:**
- Instalat și configurat
- Git configurat pentru `gitea.romfast.ro`
- Mod interactiv: `claude` (în terminal)
- Mod programatic: `claude -p "task description"` — pentru sarcini automatizate
**Utilizare:**
```bash
# Acces SSH
ssh user@10.0.20.171
# Sau prin Proxmox
ssh echo@10.0.20.201 "sudo pct exec 171 -- bash"
# Claude Code - interactiv
claude
# Claude Code - task direct
claude -p "descrie sarcina aici"
```
#### pve1 (10.0.20.200)
- **Resurse:** 32GB RAM, 1.3TB disk
- **SSH:** `ssh echo@10.0.20.200`

View File

@@ -192,11 +192,15 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
).stdout.strip()
# Parse uncommitted into structured format
# Format: XY PATH where XY is 2 chars (index + working tree status)
# Examples: "M file" (staged), " M file" (unstaged), "?? file" (untracked)
uncommitted_parsed = []
for line in uncommitted:
if len(line) >= 3:
status = line[:2].strip()
filepath = line[3:].strip()
# Path starts at position 3 for most cases, but we use lstrip
# to handle edge cases where there's no separator space
filepath = line[2:].lstrip().strip()
uncommitted_parsed.append({'status': status, 'path': filepath})
self.send_json({

View File

@@ -970,6 +970,27 @@
margin-bottom: var(--space-3);
opacity: 0.5;
}
/* Danger button */
.btn-danger {
background: #dc2626;
color: white;
border: none;
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-md);
font-size: var(--text-sm);
cursor: pointer;
transition: all var(--transition-fast);
}
.btn-danger:hover {
background: #b91c1c;
}
/* Clickable todo item */
.todo-item {
cursor: pointer;
}
</style>
</head>
<body>
@@ -1181,10 +1202,11 @@
</div>
</div>
<!-- Add Issue Modal -->
<div class="modal-overlay" id="addModal">
<!-- Issue Modal (Add/Edit) -->
<div class="modal-overlay" id="issueModal">
<div class="modal">
<h2 class="modal-title">Issue nou</h2>
<h2 class="modal-title" id="issueModalTitle">Issue nou</h2>
<input type="hidden" id="issueEditId">
<div class="form-group">
<label class="form-label">Titlu *</label>
<input type="text" class="input" id="issueTitle" placeholder="Ce trebuie făcut?">
@@ -1225,8 +1247,59 @@
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideAddModal()">Anulează</button>
<button class="btn btn-primary" onclick="addIssue()">Adaugă</button>
<button class="btn btn-danger" id="issueDeleteBtn" onclick="deleteIssue()" style="margin-right: auto; display: none;">Șterge</button>
<button class="btn btn-secondary" onclick="hideIssueModal()">Anulează</button>
<button class="btn btn-primary" id="issueSaveBtn" onclick="saveIssue()">Adaugă</button>
</div>
</div>
</div>
<!-- Todo Modal (Add/Edit) -->
<div class="modal-overlay" id="todoModal">
<div class="modal">
<h2 class="modal-title" id="todoModalTitle">Todo nou</h2>
<input type="hidden" id="todoEditId">
<div class="form-group">
<label class="form-label">Ce trebuie făcut? *</label>
<input type="text" class="input" id="todoText" placeholder="Ex: Verifică client X">
</div>
<div class="form-group">
<label class="form-label">Context</label>
<textarea class="input" id="todoContext" rows="2" placeholder="De ce? Detalii..."></textarea>
</div>
<div class="form-group">
<label class="form-label">Exemplu</label>
<textarea class="input" id="todoExample" rows="2" placeholder="Un exemplu concret..."></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Domeniu</label>
<select class="input" id="todoDomain">
<option value="work">💼 Work</option>
<option value="self">🧘 Self</option>
<option value="sprijin">💚 Sprijin</option>
<option value="scout">⚜️ Scout</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Data</label>
<input type="date" class="input" id="todoDueDate">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Sursă</label>
<input type="text" class="input" id="todoSource" placeholder="Ex: video, articol...">
</div>
<div class="form-group">
<label class="form-label">Link sursă</label>
<input type="text" class="input" id="todoSourceUrl" placeholder="URL...">
</div>
</div>
<div class="modal-actions">
<button class="btn btn-danger" id="todoDeleteBtn" onclick="deleteTodo()" style="margin-right: auto; display: none;">Șterge</button>
<button class="btn btn-secondary" onclick="hideTodoModal()">Anulează</button>
<button class="btn btn-primary" id="todoSaveBtn" onclick="saveTodoForm()">Adaugă</button>
</div>
</div>
</div>
@@ -1412,7 +1485,8 @@
`;
if (git.uncommittedCount > 0) {
const files = git.uncommitted.slice(0, 3).map(f => f.trim().split(' ').pop()).join(', ');
// Use uncommittedParsed for correctly parsed paths
const files = (git.uncommittedParsed || []).slice(0, 3).map(f => f.path).join(', ');
const more = git.uncommittedCount > 3 ? ` +${git.uncommittedCount - 3}` : '';
html += `<div class="status-detail-item uncommitted">
<i data-lucide="alert-circle"></i>
@@ -1662,8 +1736,8 @@
function renderTodoItem(todo) {
const isOverdue = !todo.done && todo.dueDate < new Date().toISOString().split('T')[0];
return `
<div class="todo-item ${todo.done ? 'done' : ''}" data-id="${todo.id}">
<div class="todo-checkbox ${todo.done ? 'checked' : ''}" onclick="toggleTodo('${todo.id}')">
<div class="todo-item ${todo.done ? 'done' : ''}" data-id="${todo.id}" onclick="editTodo('${todo.id}')">
<div class="todo-checkbox ${todo.done ? 'checked' : ''}" onclick="event.stopPropagation(); toggleTodo('${todo.id}')">
<i data-lucide="check"></i>
</div>
<div class="todo-content">
@@ -1746,36 +1820,102 @@
}
function showAddTodoModal() {
const text = prompt('Todo (ex: @work Verifică client X)');
if (text && text.trim()) {
addTodo(text.trim());
}
document.getElementById('todoModalTitle').textContent = 'Todo nou';
document.getElementById('todoEditId').value = '';
document.getElementById('todoText').value = '';
document.getElementById('todoContext').value = '';
document.getElementById('todoExample').value = '';
document.getElementById('todoDomain').value = 'work';
document.getElementById('todoDueDate').value = new Date().toISOString().split('T')[0];
document.getElementById('todoSource').value = '';
document.getElementById('todoSourceUrl').value = '';
document.getElementById('todoDeleteBtn').style.display = 'none';
document.getElementById('todoSaveBtn').textContent = 'Adaugă';
document.getElementById('todoModal').classList.add('active');
document.getElementById('todoText').focus();
}
async function addTodo(text) {
// Parse domain from text (@work, @self, @sprijin, @scout)
let domain = 'work';
const domainMatch = text.match(/@(work|self|sprijin|scout)/i);
if (domainMatch) {
domain = domainMatch[1].toLowerCase();
text = text.replace(/@(work|self|sprijin|scout)/i, '').trim();
function editTodo(id) {
const todo = todosData.items.find(t => t.id === id);
if (!todo) return;
document.getElementById('todoModalTitle').textContent = 'Editare todo';
document.getElementById('todoEditId').value = id;
document.getElementById('todoText').value = todo.text || '';
document.getElementById('todoContext').value = todo.context || '';
document.getElementById('todoExample').value = todo.example || '';
document.getElementById('todoDomain').value = todo.domain || 'work';
document.getElementById('todoDueDate').value = todo.dueDate || '';
document.getElementById('todoSource').value = todo.source || '';
document.getElementById('todoSourceUrl').value = todo.sourceUrl || '';
document.getElementById('todoDeleteBtn').style.display = 'block';
document.getElementById('todoSaveBtn').textContent = 'Salvează';
document.getElementById('todoModal').classList.add('active');
document.getElementById('todoText').focus();
}
function hideTodoModal() {
document.getElementById('todoModal').classList.remove('active');
}
async function saveTodoForm() {
const text = document.getElementById('todoText').value.trim();
if (!text) {
showToast('Textul este obligatoriu', 'error');
return;
}
const editId = document.getElementById('todoEditId').value;
if (editId) {
// Edit existing
const todo = todosData.items.find(t => t.id === editId);
if (todo) {
todo.text = text;
todo.context = document.getElementById('todoContext').value.trim() || null;
todo.example = document.getElementById('todoExample').value.trim() || null;
todo.domain = document.getElementById('todoDomain').value;
todo.dueDate = document.getElementById('todoDueDate').value || null;
todo.source = document.getElementById('todoSource').value.trim() || null;
todo.sourceUrl = document.getElementById('todoSourceUrl').value.trim() || null;
todo.updatedAt = new Date().toISOString();
}
showToast('Todo actualizat');
} else {
// Add new
const todo = {
id: 'todo-' + Date.now(),
text: text,
domain: domain,
dueDate: new Date().toISOString().split('T')[0],
context: document.getElementById('todoContext').value.trim() || null,
example: document.getElementById('todoExample').value.trim() || null,
domain: document.getElementById('todoDomain').value,
dueDate: document.getElementById('todoDueDate').value || new Date().toISOString().split('T')[0],
done: false,
doneAt: null,
source: 'manual',
source: document.getElementById('todoSource').value.trim() || 'manual',
sourceUrl: document.getElementById('todoSourceUrl').value.trim() || null,
createdAt: new Date().toISOString()
};
todosData.items.push(todo);
showToast('Todo adăugat');
}
hideTodoModal();
await saveTodos();
renderTodos();
showToast('Todo adăugat');
}
async function deleteTodo() {
const editId = document.getElementById('todoEditId').value;
if (!editId) return;
if (!confirm('Sigur ștergi acest todo?')) return;
todosData.items = todosData.items.filter(t => t.id !== editId);
hideTodoModal();
await saveTodos();
renderTodos();
showToast('Todo șters');
}
// ===== ISSUES =====
@@ -2028,13 +2168,6 @@
showToast(issue.status === 'done' ? 'Issue finalizat! ✓' : 'Issue redeschis');
}
function editIssue(id) {
const issue = issuesData.issues.find(i => i.id === id);
if (issue) {
alert(`Edit: ${issue.title}\n\n${issue.description || 'Fără descriere'}`);
}
}
// Filters
document.getElementById('issuesFilters').addEventListener('click', (e) => {
if (e.target.classList.contains('filter-btn')) {
@@ -2045,22 +2178,47 @@
}
});
// Modal
// Issue Modal
function showAddModal() {
document.getElementById('addModal').classList.add('active');
document.getElementById('issueTitle').focus();
}
function hideAddModal() {
document.getElementById('addModal').classList.remove('active');
document.getElementById('issueModalTitle').textContent = 'Issue nou';
document.getElementById('issueEditId').value = '';
document.getElementById('issueTitle').value = '';
document.getElementById('issueDesc').value = '';
document.getElementById('issueProgram').value = '';
document.getElementById('issueOwner').value = 'marius';
document.getElementById('issuePriority').value = 'urgent-important';
document.getElementById('issueDeadline').value = '';
document.getElementById('issueDeleteBtn').style.display = 'none';
document.getElementById('issueSaveBtn').textContent = 'Adaugă';
document.getElementById('issueModal').classList.add('active');
document.getElementById('issueTitle').focus();
}
function editIssue(id) {
const issue = issuesData.issues.find(i => i.id === id);
if (!issue) return;
document.getElementById('issueModalTitle').textContent = 'Editare issue';
document.getElementById('issueEditId').value = id;
document.getElementById('issueTitle').value = issue.title || '';
document.getElementById('issueDesc').value = issue.description || '';
document.getElementById('issueProgram').value = issue.program || '';
document.getElementById('issueOwner').value = issue.owner || 'marius';
document.getElementById('issuePriority').value = issue.priority || 'backlog';
document.getElementById('issueDeadline').value = issue.deadline || '';
document.getElementById('issueDeleteBtn').style.display = 'block';
document.getElementById('issueSaveBtn').textContent = 'Salvează';
document.getElementById('issueModal').classList.add('active');
document.getElementById('issueTitle').focus();
}
function hideIssueModal() {
document.getElementById('issueModal').classList.remove('active');
}
// Legacy alias
function hideAddModal() { hideIssueModal(); }
function populateProgramSelect() {
const select = document.getElementById('issueProgram');
select.innerHTML = '<option value="">— Selectează —</option>';
@@ -2071,13 +2229,30 @@
}
}
async function addIssue() {
async function saveIssue() {
const title = document.getElementById('issueTitle').value.trim();
if (!title) {
showToast('Titlul este obligatoriu', 'error');
return;
}
const editId = document.getElementById('issueEditId').value;
if (editId) {
// Edit existing
const issue = issuesData.issues.find(i => i.id === editId);
if (issue) {
issue.title = title;
issue.description = document.getElementById('issueDesc').value.trim();
issue.program = document.getElementById('issueProgram').value;
issue.owner = document.getElementById('issueOwner').value;
issue.priority = document.getElementById('issuePriority').value;
issue.deadline = document.getElementById('issueDeadline').value || null;
issue.updated = new Date().toISOString();
}
showToast('Issue actualizat!');
} else {
// Add new
const newIssue = {
id: 'ROA-' + String(issuesData.issues.length + 1).padStart(3, '0'),
title: title,
@@ -2089,15 +2264,33 @@
created: new Date().toISOString(),
deadline: document.getElementById('issueDeadline').value || null
};
issuesData.issues.unshift(newIssue);
hideAddModal();
showToast('Issue adăugat!');
}
hideIssueModal();
renderIssues();
updateIssuesCount();
await saveIssues();
showToast('Issue adăugat!');
}
async function deleteIssue() {
const editId = document.getElementById('issueEditId').value;
if (!editId) return;
if (!confirm('Sigur ștergi acest issue?')) return;
issuesData.issues = issuesData.issues.filter(i => i.id !== editId);
hideIssueModal();
renderIssues();
updateIssuesCount();
await saveIssues();
showToast('Issue șters');
}
// Legacy alias
async function addIssue() { await saveIssue(); }
async function saveIssues() {
issuesData.lastUpdated = new Date().toISOString();
try {
@@ -2122,9 +2315,12 @@
setTimeout(() => toast.classList.remove('show'), 3000);
}
// Close modal on outside click
document.getElementById('addModal').addEventListener('click', (e) => {
if (e.target.id === 'addModal') hideAddModal();
// Close modals on outside click
document.getElementById('issueModal').addEventListener('click', (e) => {
if (e.target.id === 'issueModal') hideIssueModal();
});
document.getElementById('todoModal').addEventListener('click', (e) => {
if (e.target.id === 'todoModal') hideTodoModal();
});
// Init

View File

@@ -1,5 +1,5 @@
{
"lastUpdated": "2026-02-02T11:29:00Z",
"lastUpdated": "2026-02-02T12:17:34.010Z",
"items": [
{
"id": "prov-2026-02-02",
@@ -8,8 +8,8 @@
"example": "'Vreau să sun un client vechi' → Eforturi: 5 min pregătire, 10 min apel, posibil să zică nu, disconfort inițial. Îmi asum? Dacă da, sun acum. Dacă nu, aleg altceva mai mic.",
"domain": "self",
"dueDate": "2026-02-02",
"done": false,
"doneAt": null,
"done": true,
"doneAt": "2026-02-02T12:17:34.010Z",
"source": "Zoltan Vereș - Motivația Intrinsecă",
"sourceUrl": "https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/2026-02-02_zoltan-veres-motivatie-intrinseca-complet.md",
"createdAt": "2026-02-02T09:00:00Z"

View File

@@ -58,6 +58,41 @@ Execuție noaptea:
---
## 📊 Propuneri Acționabile în Rapoarte
### Categorii (cine face)
| Tag | Cine | Exemplu |
|-----|------|---------|
| 🤖 FAC EU | Echo singur | Git commit, creare fișier, update job |
| 🤝 TU+EU | Marius + Echo | Sesiune de lucru, review împreună |
| 👤 FACI TU | Doar Marius | Reflecție, decizie, acțiune externă |
### Timing (când)
| Tag | Când | Răspuns Marius |
|-----|------|----------------|
| ⚡ ACUM | Imediat după aprobare | "1 pentru X" |
| 🌙 NOAPTE | Job night-execute (23:00) | "2 pentru X" |
| 📅 PROGRAMAT | Data specifică | Echo setează reminder/job |
| ⏳ CÂND POȚI | Fără deadline | Pentru Marius, nu urgent |
### Format propunere
```
**A0 - [Titlu scurt]** 🤖 FAC EU ⚡ ACUM
Descriere: ce face exact
Rezultat: ce obține Marius
```
### Coduri răspuns rapid
- `1 pentru A0,A3` → Execut ACUM
- `2 pentru A1,A4` → approved-tasks.md pentru noapte
- `3 pentru A2` → Skip (marchez [—] în insights)
- Text liber → Procesez și răspund
---
## 📝 Cine Creează Ce
### 1. Note YouTube (`kb/youtube/`)