Restructurare completă: kanban→dashboard, notes→kb, ANAF→tools/

- Mutare și reorganizare foldere proiecte
- Actualizare path-uri în TOOLS.md
- Sincronizare configurații agenți
- 79 fișiere actualizate
This commit is contained in:
Echo
2026-01-31 09:34:24 +00:00
parent 838c38e82f
commit a44b9ef852
99 changed files with 2096 additions and 623 deletions

View File

@@ -217,6 +217,14 @@ Când primesc un link YouTube:
3. Nota trebuie să conțină: TL;DR, pași concreți, comenzi, puncte cheie 3. Nota trebuie să conțină: TL;DR, pași concreți, comenzi, puncte cheie
4. Nu dau rezumate generice - surprind **esența** și **detaliile acționabile** 4. Nu dau rezumate generice - surprind **esența** și **detaliile acționabile**
5. **DUPĂ SALVARE** rulez: `python3 ~/clawd/tools/update_notes_index.py` (actualizează index.json pentru kb.html) 5. **DUPĂ SALVARE** rulez: `python3 ~/clawd/tools/update_notes_index.py` (actualizează index.json pentru kb.html)
6. **DEEP LINK:** Când dau link spre notă, folosesc formatul:
`https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/YYYY-MM-DD_titlu-slug.md`
(files.html deschide previzualizarea, notes.html nu)
7. **INSIGHTS:** Analizez nota și extrag idei aplicabile în `kb/insights/YYYY-MM-DD.md`:
- Fișier zilnic (ca memory/)
- Categorisez pe domeniu (@work, @health, @growth, @sprijin, @scout)
- Pentru fiecare idee: ce e, cum se aplică, propunere concretă
- **Link la sursă:** fiecare secțiune include deep link la nota originală
### 📦 Git Commits (~/clawd → gitea.romfast.ro/romfast/clawd) ### 📦 Git Commits (~/clawd → gitea.romfast.ro/romfast/clawd)
- **NU face commit automat** - întreabă-l pe Marius când să dau commit - **NU face commit automat** - întreabă-l pe Marius când să dau commit
@@ -229,16 +237,20 @@ Când primesc un link YouTube:
### 📋 Task Tracking (OBLIGATORIU) ### 📋 Task Tracking (OBLIGATORIU)
Când primesc o acțiune/cerere de la Marius: Când primesc o acțiune/cerere de la Marius:
1. **React:** Reacționez cu 👍 la mesaj (WhatsApp/Discord) 1. **React:** Reacționez cu 👍 la mesaj (WhatsApp/Discord)
2. **Start:** Adaug task în kanban (in-progress) cu `python3 kanban/update_task.py add "titlu"` 2. **Start:** Adaug task în kanban (in-progress) cu `python3 dashboard/update_task.py add "titlu"`
3. **Lucrez:** Execut cererea 3. **Lucrez:** Execut cererea
4. **Done:** Marchez task-ul terminat cu `python3 kanban/update_task.py done <task-id>` 4. **Done:** Marchez task-ul terminat cu `python3 dashboard/update_task.py done <task-id>`
Când se execută orice job cron: Când se execută orice job cron:
1. **Start:** Creează task în kanban (Progress) cu numele job-ului 1. **Start:** Creează task în kanban (Progress) cu numele job-ului
2. **Rulează:** Execută task-ul 2. **Rulează:** Execută task-ul
3. **Done:** Mută task-ul în Done cu rezultatul 3. **Done:** Mută task-ul în Done cu rezultatul
Astfel Marius poate vedea în https://moltbot.tailf7372d.ts.net/echo/ ce job-uri au rulat și când. **TOATE acțiunile trebuie notate** - dashboard-ul arată statistici:
- Task-uri completate: azi / săptămâna / luna
- Număr de insights procesate
Astfel Marius poate vedea în https://moltbot.tailf7372d.ts.net/echo/ ce s-a lucrat și când.
**Things to check (rotate through these, 2-4 times per day):** **Things to check (rotate through these, 2-4 times per day):**
- **Emails** - Any urgent unread messages? - **Emails** - Any urgent unread messages?
@@ -281,3 +293,13 @@ The goal: Be helpful without being annoying. Check in a few times a day, do usef
## Make It Yours ## Make It Yours
This is a starting point. Add your own conventions, style, and rules as you figure out what works. This is a starting point. Add your own conventions, style, and rules as you figure out what works.
### 📁 Reguli directoare (verifică .rules.json)
Când salvez în `kb/projects/`, verific dacă există `.rules.json`:
- Citesc `filenameRule` pentru cum să numesc fișierul
- Citesc `validTypes` pentru ce tipuri sunt valide
- Scriptul inferă automat tipul din filename dacă `inferTypeFromFilename: true`
**Exemplu grup-sprijin:**
- Filename: `meditatie-nume-descriptiv.md` → automat @meditatie
- Tipuri valide: meditatie, exercitiu, reflectie, intrebare, fisa

View File

@@ -17,6 +17,11 @@
### 📦 Git status (seara) ### 📦 Git status (seara)
- [ ] Fișiere uncommitted? Dacă da, întreabă dacă fac commit. - [ ] Fișiere uncommitted? Dacă da, întreabă dacă fac commit.
### 📚 KB Index (la fiecare heartbeat)
- [ ] Verifică dacă vreun fișier din kb/ e mai nou decât kb/index.json
- [ ] Dacă da → `python3 tools/update_notes_index.py`
- [ ] Comandă rapidă: `find kb/ -name "*.md" -newer kb/index.json | head -1`
--- ---
## Tracking ultimele verificări ## Tracking ultimele verificări

View File

@@ -24,25 +24,26 @@ python3 tools/email_send.py "dest@email.com" "Subiect" "Corp mesaj"
**Pentru orice altă adresă:** Citesc și raportez, aștept aprobare. **Pentru orice altă adresă:** Citesc și raportez, aștept aprobare.
### Kanban & Web ### Dashboard & Web
- **Task Board:** https://moltbot.tailf7372d.ts.net/echo/ - **Task Board:** https://moltbot.tailf7372d.ts.net/echo/
- **Notes:** https://moltbot.tailf7372d.ts.net/echo/notes.html - **KB (Notes):** https://moltbot.tailf7372d.ts.net/echo/notes.html
- **Files:** https://moltbot.tailf7372d.ts.net/echo/files.html - **Files:** https://moltbot.tailf7372d.ts.net/echo/files.html
- **API:** `kanban/api.py` - **API:** `dashboard/api.py`
- **Update task:** `python3 kanban/update_task.py` - **Update task:** `python3 dashboard/update_task.py`
**Reguli dashboard:** **Reguli dashboard:**
- Tab Activity afișează task-uri din tasks.json, sortate descrescător după timestamp - Tab Activity afișează task-uri din tasks.json, sortate descrescător după timestamp
- Când creez/completez task-uri, să am timestamp complet (ISO format cu oră) - Când creez/completez task-uri, să am timestamp complet (ISO format cu oră)
### Notes (toate tipurile) ### KB - Knowledge Base (toate tipurile de conținut)
- **Folder:** `notes/` (subdirectoare: `youtube/`, `retete/`, etc.) - **Folder:** `kb/` (subdirectoare: `youtube/`, `retete/`, `projects/`)
- **Update index:** `python3 tools/update_notes_index.py` - **Update index:** `python3 tools/update_notes_index.py`
- **Pagina web:** https://moltbot.tailf7372d.ts.net/echo/notes.html - **Pagina web:** https://moltbot.tailf7372d.ts.net/echo/notes.html
- **Tags domeniu:** `@work`, `@health`, `@growth`, `@sprijin`, `@scout` - **Tags domeniu:** `@work`, `@health`, `@growth`, `@sprijin`, `@scout`
- **Tags tip:** `@project`, `@fisa`, `@exercitiu`, `@meditatie`, `@reflectie`
**IMPORTANT:** Când salvez orice notă (rețete, youtube, etc.), trebuie să: **IMPORTANT:** Când salvez orice notă (rețete, youtube, proiecte, etc.), trebuie să:
1. Salvez în subdirectorul potrivit din `notes/` 1. Salvez în subdirectorul potrivit din `kb/`
2. Rulez `python3 tools/update_notes_index.py` pentru a actualiza indexul 2. Rulez `python3 tools/update_notes_index.py` pentru a actualiza indexul
3. Dau link-ul către pagina notes.html 3. Dau link-ul către pagina notes.html
@@ -56,7 +57,7 @@ python3 tools/email_send.py "dest@email.com" "Subiect" "Corp mesaj"
**TOOLS.md:** `agents/echo-work/TOOLS.md` **TOOLS.md:** `agents/echo-work/TOOLS.md`
- **ANAF Monitor:** `anaf-monitor/monitor.py` - verificare la fiecare 6 ore - **ANAF Monitor:** `tools/anaf-monitor/monitor.py` - verificare la fiecare 6 ore
- Monitorizează: D100, D101, D200, D390, D406, situații financiare, E-Factura - Monitorizează: D100, D101, D200, D390, D406, situații financiare, E-Factura
--- ---
@@ -84,9 +85,11 @@ python3 tools/email_send.py "dest@email.com" "Subiect" "Corp mesaj"
**TOOLS.md:** `agents/echo-sprijin/TOOLS.md` **TOOLS.md:** `agents/echo-sprijin/TOOLS.md`
- **Pagină dedicată:** https://moltbot.tailf7372d.ts.net/echo/grup-sprijin.html - **Pagină dedicată:** https://moltbot.tailf7372d.ts.net/echo/notes.html (filtrează @grup-sprijin)
- **Fișe activități:** `kanban/grup-sprijin/` - **Proiect:** `kb/projects/grup-sprijin/`
- **Template:** `kanban/grup-sprijin/template-fisa.md` - **Biblioteca activități:** `kb/projects/grup-sprijin/biblioteca.json`
- **Fișe:** `kb/projects/grup-sprijin/fise/`
- **Template:** `kb/projects/grup-sprijin/template-fisa.md`
--- ---

10
USER.md
View File

@@ -35,6 +35,16 @@
- **Preferă:** Să repare decât să construiască de la zero - **Preferă:** Să repare decât să construiască de la zero
- **Preferă:** Mai multă muncă la preț bun de la clienți existenți - **Preferă:** Mai multă muncă la preț bun de la clienți existenți
## Reguli pentru propuneri (OBLIGATORIU pentru Echo)
- **80/20 STRICT:** Propun DOAR ce aduce impact mare cu efort mic
- **Simplitate:** NU complica fluxul, NU adăuga pași noi fără motiv solid
- **Integrare:** Orice propunere trebuie să se muleze pe fluxul CURENT
- **Judecată critică:** Filtrez EU mai întâi, nu propun tot ce citesc
- **Recomandare explicită:** La fiecare propunere zic: ✅ RECOMAND sau ⚠️ AȘTEPT sau ❌ NU RECOMAND + de ce
- **Context complet:** Timp, efort TU, impact flux, beneficiu concret
- **Anti-complexitate:** Dacă adaugă complexitate > beneficiu, NU propun
## Interese și domenii ## Interese și domenii
- **Coaching & Comunicare:** NLP, Sleight of Mouth, comunicare nonviolentă - **Coaching & Comunicare:** NLP, Sleight of Mouth, comunicare nonviolentă

View File

@@ -1 +1 @@
../../kanban ../../dashboard

View File

@@ -1 +1 @@
../../notes ../../kb

View File

@@ -1 +1 @@
../../projects ../../kb/projects

View File

@@ -3,6 +3,15 @@
- **Name:** Marius - **Name:** Marius
- **Timezone:** Europe/Bucharest (UTC+2/+3) - **Timezone:** Europe/Bucharest (UTC+2/+3)
## Profil
- 25 ani programator, principiul 80/20
- E daltă nu ciocan (rafinează, simplifică)
- Îi place natura, jocul, SF/fantasy
- Lider cercetași
- Pattern: face munca, nu cere bani
- Caută cine vrea să fie (avatarul ideal)
## Sănătate ## Sănătate
### Condiții curente ### Condiții curente
@@ -22,11 +31,37 @@
- **Post negru** - practica, efecte, planificare - **Post negru** - practica, efecte, planificare
- Abordări holistice, legătura minte-corp - Abordări holistice, legătura minte-corp
### Preferințe ## Filosofie & Spiritualitate
- Discuții deschise despre alternative, fără a înlocui medicina convențională ### Stoicism (vrea să aprofundeze)
- Tracking și pattern-uri mai mult decât sfaturi directe - **Marcus Aurelius** - Meditations (reflecție, auto-disciplină)
- **Seneca** - Letters from a Stoic (practică zilnică, moarte, timp)
- **Epictetus** - Enchiridion, Discourses (ce controlăm vs ce nu)
- **Ryan Holiday** - stoicism modern aplicat
### Poeți & Mistici
- **Rumi, Hafiz** - poeți sufi persani (iubire, căutare interioară)
- **Kahlil Gibran** - The Prophet (viață, relații, muncă)
- **Thich Nhat Hanh** - Zen, mindfulness, prezență
- **Lao Tzu** - Tao Te Ching (wu wei, flux natural)
### Gânditori moderni
- **James Clear** - Atomic Habits, sisteme vs obiective
- **Naval Ravikant** - filosofie pentru antreprenori
- **Alan Watts** - filosofie estică pentru occidentali
### Psihologie & Dezvoltare
- **NLP** - reframing, Sleight of Mouth
- **Personocrație** - autenticitate
- **Comunicare nonviolentă** - nevoi, empatie
## Preferințe mesaje
- Varietate din TOATE domeniile, nu doar sănătate fizică
- Perspectiva: sănătate, spiritualitate, igienă mentală/emoțională
- Surse variate, nu doar Rumi/NMG/Martel
- Concret și aplicat, nu abstract
- Validare, nu alarmism - Validare, nu alarmism
--- ---
*Updated: 2026-01-30* *Updated: 2026-01-31*

View File

@@ -1 +1 @@
../../kanban ../../dashboard

View File

@@ -1 +1 @@
../../notes ../../kb

View File

@@ -1 +1 @@
../../projects ../../kb/projects

View File

@@ -1 +1 @@
../../kanban ../../dashboard

View File

@@ -1 +1 @@
../../notes ../../kb

View File

@@ -1 +1 @@
../../projects ../../kb/projects

View File

@@ -1 +1 @@
../../kanban ../../dashboard

View File

@@ -1 +1 @@
../../notes ../../kb

View File

@@ -1 +1 @@
../../projects ../../kb/projects

View File

@@ -1 +1 @@
../../kanban ../../dashboard

View File

@@ -1 +1 @@
../../notes ../../kb

View File

@@ -1 +1 @@
../../projects ../../kb/projects

View File

@@ -16,8 +16,8 @@ from pathlib import Path
BASE_DIR = Path(__file__).parent.parent BASE_DIR = Path(__file__).parent.parent
TOOLS_DIR = BASE_DIR / 'tools' TOOLS_DIR = BASE_DIR / 'tools'
NOTES_DIR = BASE_DIR / 'notes' / 'youtube' NOTES_DIR = BASE_DIR / 'kb' / 'youtube'
KANBAN_DIR = BASE_DIR / 'kanban' KANBAN_DIR = BASE_DIR / 'dashboard'
class TaskBoardHandler(SimpleHTTPRequestHandler): class TaskBoardHandler(SimpleHTTPRequestHandler):
@@ -26,9 +26,44 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
self.handle_youtube() self.handle_youtube()
elif self.path == '/api/files': elif self.path == '/api/files':
self.handle_files_post() self.handle_files_post()
elif self.path == '/api/refresh-index':
self.handle_refresh_index()
else: else:
self.send_error(404) self.send_error(404)
def handle_refresh_index(self):
"""Regenerate kb/index.json"""
try:
script = TOOLS_DIR / 'update_notes_index.py'
result = subprocess.run(
[sys.executable, str(script)],
capture_output=True,
text=True,
timeout=30
)
if result.returncode == 0:
# Parse output for stats
output = result.stdout
total_match = re.search(r'with (\d+) notes', output)
total = int(total_match.group(1)) if total_match else 0
self.send_json({
'success': True,
'message': f'Index regenerat cu {total} notițe',
'total': total,
'output': output
})
else:
self.send_json({
'success': False,
'error': result.stderr or 'Unknown error'
}, 500)
except subprocess.TimeoutExpired:
self.send_json({'success': False, 'error': 'Timeout'}, 500)
except Exception as e:
self.send_json({'success': False, 'error': str(e)}, 500)
def handle_files_post(self): def handle_files_post(self):
"""Save file content.""" """Save file content."""
try: try:
@@ -63,8 +98,10 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
def do_GET(self): def do_GET(self):
if self.path == '/api/status': if self.path == '/api/status':
self.send_json({'status': 'ok', 'time': datetime.now().isoformat()}) self.send_json({'status': 'ok', 'time': datetime.now().isoformat()})
elif self.path == '/api/git': elif self.path == '/api/git' or self.path.startswith('/api/git?'):
self.handle_git_status() self.handle_git_status()
elif self.path == '/api/agents' or self.path.startswith('/api/agents?'):
self.handle_agents_status()
elif self.path.startswith('/api/files'): elif self.path.startswith('/api/files'):
self.handle_files_get() self.handle_files_get()
elif self.path.startswith('/api/'): elif self.path.startswith('/api/'):
@@ -129,6 +166,55 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
except Exception as e: except Exception as e:
self.send_json({'error': str(e)}, 500) self.send_json({'error': str(e)}, 500)
def handle_agents_status(self):
"""Get agents status - fast version reading session files directly."""
try:
# Define known agents
agents_config = [
{'id': 'echo', 'name': 'Echo', 'emoji': '🌀'},
{'id': 'echo-work', 'name': 'Work', 'emoji': ''},
{'id': 'echo-health', 'name': 'Health', 'emoji': '❤️'},
{'id': 'echo-growth', 'name': 'Growth', 'emoji': '🪜'},
{'id': 'echo-sprijin', 'name': 'Sprijin', 'emoji': ''},
{'id': 'echo-scout', 'name': 'Scout', 'emoji': '⚜️'},
]
# Check active sessions by reading session files directly (fast)
active_agents = set()
sessions_base = Path.home() / '.clawdbot' / 'agents'
if sessions_base.exists():
for agent_dir in sessions_base.iterdir():
if agent_dir.is_dir():
sessions_file = agent_dir / 'sessions' / 'sessions.json'
if sessions_file.exists():
try:
data = json.loads(sessions_file.read_text())
# sessions.json is an object with session keys
now = datetime.now().timestamp() * 1000
for key, sess in data.items():
if isinstance(sess, dict):
last_active = sess.get('updatedAt', 0)
if now - last_active < 30 * 60 * 1000: # 30 min
active_agents.add(agent_dir.name)
break
except:
pass
# Build response
agents = []
for cfg in agents_config:
agents.append({
'id': cfg['id'],
'name': cfg['name'],
'emoji': cfg['emoji'],
'active': cfg['id'] in active_agents
})
self.send_json({'agents': agents})
except Exception as e:
self.send_json({'error': str(e)}, 500)
def handle_files_get(self): def handle_files_get(self):
"""List files or get file content.""" """List files or get file content."""
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs

1
dashboard/conversations Symbolic link
View File

@@ -0,0 +1 @@
../conversations

View File

@@ -330,16 +330,12 @@
</a> </a>
<a href="notes.html" class="nav-item"> <a href="notes.html" class="nav-item">
<i data-lucide="file-text"></i> <i data-lucide="file-text"></i>
<span>Notes</span> <span>KB</span>
</a> </a>
<a href="files.html" class="nav-item active"> <a href="files.html" class="nav-item active">
<i data-lucide="folder"></i> <i data-lucide="folder"></i>
<span>Files</span> <span>Files</span>
</a> </a>
<a href="grup-sprijin.html" class="nav-item">
<i data-lucide="heart-handshake"></i>
<span>Grup</span>
</a>
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema"> <button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
<i data-lucide="sun" id="themeIcon"></i> <i data-lucide="sun" id="themeIcon"></i>
</button> </button>
@@ -583,9 +579,16 @@
const isMarkdown = path.endsWith('.md'); const isMarkdown = path.endsWith('.md');
document.getElementById('previewBtn').style.display = isMarkdown ? 'flex' : 'none'; document.getElementById('previewBtn').style.display = isMarkdown ? 'flex' : 'none';
// Reset preview state // Auto-activate preview for markdown files
if (isMarkdown) {
const preview = document.getElementById('markdownPreview');
preview.innerHTML = marked.parse(data.content);
document.getElementById('editorBody').classList.add('preview-active');
document.getElementById('previewBtn').classList.add('active');
} else {
document.getElementById('editorBody').classList.remove('preview-active'); document.getElementById('editorBody').classList.remove('preview-active');
document.getElementById('previewBtn').classList.remove('active'); document.getElementById('previewBtn').classList.remove('active');
}
if (data.truncated) { if (data.truncated) {
setStatus('Fișier trunchiat', 'error'); setStatus('Fișier trunchiat', 'error');

View File

@@ -14,6 +14,61 @@
padding: var(--space-5); padding: var(--space-5);
} }
/* Stats Summary */
.stats-summary {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-3);
margin-bottom: var(--space-4);
}
@media (max-width: 768px) {
.stats-summary {
grid-template-columns: repeat(2, 1fr);
}
}
.stat-card {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-4);
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
}
.stat-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--accent-subtle);
border-radius: var(--radius-md);
color: var(--accent);
}
.stat-icon svg {
width: 20px;
height: 20px;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: var(--text-xl);
font-weight: 700;
color: var(--text-primary);
}
.stat-label {
font-size: var(--text-xs);
color: var(--text-muted);
}
.page-header { .page-header {
margin-bottom: var(--space-4); margin-bottom: var(--space-4);
} }
@@ -154,6 +209,33 @@
color: #818cf8; color: #818cf8;
} }
.status-section-icon.agents {
background: rgba(168, 85, 247, 0.15);
color: #a855f7;
}
.agents-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: var(--space-2);
}
.agent-chip {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
background: var(--bg-elevated);
border-radius: var(--radius-md);
font-size: var(--text-xs);
}
.agent-chip .emoji { font-size: 14px; }
.agent-chip .name { font-weight: 500; color: var(--text-primary); }
.agent-chip .status { color: var(--text-muted); }
.agent-chip.active { background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.3); }
.agent-chip.active .status { color: #22c55e; }
.status-section-info { .status-section-info {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
@@ -794,16 +876,12 @@
</a> </a>
<a href="notes.html" class="nav-item"> <a href="notes.html" class="nav-item">
<i data-lucide="file-text"></i> <i data-lucide="file-text"></i>
<span>Notes</span> <span>KB</span>
</a> </a>
<a href="files.html" class="nav-item"> <a href="files.html" class="nav-item">
<i data-lucide="folder"></i> <i data-lucide="folder"></i>
<span>Files</span> <span>Files</span>
</a> </a>
<a href="grup-sprijin.html" class="nav-item">
<i data-lucide="heart-handshake"></i>
<span>Grup</span>
</a>
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema"> <button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
<i data-lucide="sun" id="themeIcon"></i> <i data-lucide="sun" id="themeIcon"></i>
</button> </button>
@@ -894,6 +972,39 @@
<!-- Populated by JS --> <!-- Populated by JS -->
</div> </div>
</div> </div>
</div>
</div>
<!-- Stats Summary -->
<div class="stats-summary" id="statsSummary">
<div class="stat-card">
<div class="stat-icon"><i data-lucide="check-circle"></i></div>
<div class="stat-content">
<div class="stat-value" id="statToday">0</div>
<div class="stat-label">Azi</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon"><i data-lucide="calendar"></i></div>
<div class="stat-content">
<div class="stat-value" id="statWeek">0</div>
<div class="stat-label">Săptămâna</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon"><i data-lucide="trending-up"></i></div>
<div class="stat-content">
<div class="stat-value" id="statMonth">0</div>
<div class="stat-label">Luna</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon"><i data-lucide="lightbulb"></i></div>
<div class="stat-content">
<div class="stat-value" id="statInsights">0</div>
<div class="stat-label">Insights</div>
</div>
</div> </div>
</div> </div>
@@ -1140,7 +1251,7 @@
async function loadGitStatus() { async function loadGitStatus() {
try { try {
const response = await fetch('/api/git?' + Date.now()); const response = await fetch('./api/git?' + Date.now());
if (!response.ok) throw new Error('API error'); if (!response.ok) throw new Error('API error');
const git = await response.json(); const git = await response.json();
@@ -1270,6 +1381,42 @@
lucide.createIcons(); lucide.createIcons();
} }
async function loadAgentsStatus() {
try {
const response = await fetch('./api/agents?' + Date.now());
if (!response.ok) throw new Error('API error');
const data = await response.json();
const agents = data.agents || [];
const activeCount = agents.filter(a => a.active).length;
// Update badge
const badge = document.getElementById('agentsBadge');
badge.textContent = `${activeCount}/${agents.length}`;
badge.className = 'status-badge ' + (activeCount > 0 ? 'ok' : 'warning');
// Update subtitle
const subtitle = document.getElementById('agentsSubtitle');
const activeNames = agents.filter(a => a.active).map(a => a.name).join(', ');
subtitle.textContent = activeCount > 0 ? `Activi: ${activeNames}` : 'Niciun agent activ';
// Update grid
const grid = document.getElementById('agentsGrid');
grid.innerHTML = agents.map(agent => `
<div class="agent-chip ${agent.active ? 'active' : ''}">
<span class="emoji">${agent.emoji || '🤖'}</span>
<span class="name">${agent.name}</span>
<span class="status">${agent.active ? '●' : '○'}</span>
</div>
`).join('');
} catch (e) {
console.log('Agents status error:', e);
document.getElementById('agentsBadge').textContent = '-';
document.getElementById('agentsSubtitle').textContent = 'Nu se poate încărca';
}
}
function updateStatusSummary() { function updateStatusSummary() {
const gitBadge = document.getElementById('gitBadge'); const gitBadge = document.getElementById('gitBadge');
const anafBadge = document.getElementById('anafBadge'); const anafBadge = document.getElementById('anafBadge');
@@ -1366,9 +1513,51 @@
function refreshActivity() { function refreshActivity() {
loadActivity(); loadActivity();
loadStats();
showToast('Activitate reîmprospătată'); showToast('Activitate reîmprospătată');
} }
async function loadStats() {
try {
// Load tasks
const tasksRes = await fetch('tasks.json');
const tasksData = await tasksRes.json();
// Get done tasks
const doneColumn = tasksData.columns.find(c => c.id === 'done');
const doneTasks = doneColumn ? doneColumn.tasks : [];
const now = new Date();
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const weekStart = new Date(todayStart);
weekStart.setDate(weekStart.getDate() - weekStart.getDay() + 1); // Monday
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
let today = 0, week = 0, month = 0;
doneTasks.forEach(task => {
if (!task.completed) return;
const completed = new Date(task.completed);
if (completed >= todayStart) today++;
if (completed >= weekStart) week++;
if (completed >= monthStart) month++;
});
document.getElementById('statToday').textContent = today;
document.getElementById('statWeek').textContent = week;
document.getElementById('statMonth').textContent = month;
// Count insights
const insightsRes = await fetch('/echo/api/files?path=kb/insights');
if (insightsRes.ok) {
const insightsData = await insightsRes.json();
document.getElementById('statInsights').textContent = insightsData.files ? insightsData.files.length : 0;
}
} catch (e) {
console.log('Stats load error:', e);
}
}
function renderActivity() { function renderActivity() {
const body = document.getElementById('activityBody'); const body = document.getElementById('activityBody');
@@ -1644,6 +1833,7 @@
loadStatus(); loadStatus();
loadIssues(); loadIssues();
loadActivity(); loadActivity();
loadStats();
</script> </script>
</body> </body>
</html> </html>

1
dashboard/memory Symbolic link
View File

@@ -0,0 +1 @@
../memory

1
dashboard/notes-data Symbolic link
View File

@@ -0,0 +1 @@
../kb

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Echo · Notes</title> <title>Echo · KB</title>
<link rel="stylesheet" href="common.css"> <link rel="stylesheet" href="common.css">
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script> <script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
<script src="swipe-nav.js"></script> <script src="swipe-nav.js"></script>
@@ -155,9 +155,39 @@
} }
.note-tags { .note-tags {
display: flex; display: inline;
flex-wrap: wrap; line-height: 1.4;
gap: var(--space-1); }
.note-tag {
font-size: var(--text-sm);
margin-right: 6px;
}
.note-tag.domain {
color: #f97316;
}
.note-tag.type {
color: #8b5cf6;
}
.note-tag.tag {
color: #94a3b8;
}
.note-tags-toggle {
color: #94a3b8;
cursor: pointer;
font-size: var(--text-sm);
}
.note-tags-toggle:hover {
color: var(--text-primary);
}
.note-tags-full {
color: #94a3b8;
} }
/* Empty section */ /* Empty section */
@@ -287,74 +317,197 @@
.markdown-body strong { color: var(--text-primary); } .markdown-body strong { color: var(--text-primary); }
/* Tag filter pills */ /* Filter bar with pills */
.tag-filter { .filter-bar {
margin-bottom: var(--space-5); display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-4);
padding: var(--space-3);
background: var(--bg-surface);
border-radius: var(--radius-lg);
border: 1px solid rgba(255, 255, 255, 0.1);
} }
.tag-filter-header { [data-theme="light"] .filter-bar {
border-color: rgba(0, 0, 0, 0.1);
}
.filter-separator {
color: var(--text-muted);
opacity: 0.3;
margin: 0 var(--space-1);
}
.filter-group {
display: flex;
align-items: center;
gap: var(--space-1);
flex-wrap: wrap;
}
.filter-pill {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-1) var(--space-2);
background: var(--bg-secondary);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: var(--radius-full);
font-size: var(--text-sm);
color: var(--text-secondary);
cursor: pointer;
transition: all var(--transition-fast);
white-space: nowrap;
}
[data-theme="light"] .filter-pill {
border-color: rgba(0, 0, 0, 0.1);
}
.filter-pill:hover {
background: var(--bg-surface-hover);
border-color: var(--accent);
}
.filter-pill.active {
background: var(--accent);
border-color: var(--accent);
color: white;
}
.filter-pill.dimmed {
opacity: 0.4;
}
.filter-pill.dimmed:hover {
opacity: 0.7;
}
/* Category pills - teal */
.filter-pill.category {
background: rgba(20, 184, 166, 0.15);
border-color: rgba(20, 184, 166, 0.4);
color: #14b8a6;
}
.filter-pill.category:hover {
background: rgba(20, 184, 166, 0.25);
}
.filter-pill.category.active {
background: #14b8a6;
color: white;
}
/* Project pills - blue */
.filter-pill.project {
background: rgba(59, 130, 246, 0.15);
border-color: rgba(59, 130, 246, 0.4);
color: #3b82f6;
}
.filter-pill.project:hover {
background: rgba(59, 130, 246, 0.25);
}
.filter-pill.project.active {
background: #3b82f6;
color: white;
}
/* Domain pills - orange */
.filter-pill.domain {
background: rgba(249, 115, 22, 0.15);
border-color: rgba(249, 115, 22, 0.4);
color: #f97316;
}
.filter-pill.domain:hover {
background: rgba(249, 115, 22, 0.25);
}
.filter-pill.domain.active {
background: #f97316;
color: white;
}
/* Type pills - purple */
.filter-pill.type {
background: rgba(139, 92, 246, 0.15);
border-color: rgba(139, 92, 246, 0.4);
color: #8b5cf6;
}
.filter-pill.type:hover {
background: rgba(139, 92, 246, 0.25);
}
.filter-pill.type.active {
background: #8b5cf6;
color: white;
}
/* Tag pills - gray */
.filter-pill.tag {
background: rgba(100, 116, 139, 0.15);
border-color: rgba(100, 116, 139, 0.4);
color: #94a3b8;
}
.filter-pill.tag:hover {
background: rgba(100, 116, 139, 0.25);
}
.filter-pill.tag.active {
background: #64748b;
color: white;
}
.filter-pill-count {
font-size: var(--text-xs);
opacity: 0.7;
}
.filter-actions {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-2); gap: var(--space-2);
cursor: pointer; margin-left: auto;
user-select: none;
margin-bottom: var(--space-2);
} }
.tag-filter-header svg { .filter-btn {
padding: var(--space-2);
background: transparent;
border: none;
color: var(--text-muted);
cursor: pointer;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
}
.filter-btn:hover {
background: var(--bg-surface-hover);
color: var(--text-primary);
}
.filter-btn.clear:hover {
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
}
.filter-btn svg {
width: 16px; width: 16px;
height: 16px; height: 16px;
}
.more-tags-toggle {
color: var(--text-muted); color: var(--text-muted);
transition: transform var(--transition-fast); font-size: var(--text-sm);
cursor: pointer;
padding: var(--space-1) var(--space-2);
} }
.tag-filter.collapsed .tag-filter-header svg { .more-tags-toggle:hover {
transform: rotate(-90deg); color: var(--text-primary);
} }
.tag-pills-more { @keyframes spin {
display: none; from { transform: rotate(0deg); }
margin-top: var(--space-2); to { transform: rotate(360deg); }
}
.tag-pills-more.expanded {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}
/* More tags toggle button - same style as pills */
.tag-pill.more-toggle {
background: rgba(100, 116, 139, 0.2);
border-color: rgba(100, 116, 139, 0.4);
color: var(--text-muted);
}
.tag-pill.more-toggle:hover {
background: rgba(100, 116, 139, 0.3);
}
.tag-pill.more-toggle.expanded {
background: rgba(100, 116, 139, 0.4);
}
/* Dimmed pills - tags not in visible notes */
.tag-pill.dimmed {
opacity: 0.35;
}
.tag-pill.dimmed:hover {
opacity: 0.6;
}
.filter-count {
font-size: var(--text-xs);
color: var(--text-muted);
margin-left: var(--space-1);
}
.tag-filter-label {
font-size: var(--text-xs);
color: var(--text-muted);
margin-bottom: var(--space-2);
display: block;
} }
.tag-pills { .tag-pills {
@@ -497,16 +650,12 @@
</a> </a>
<a href="notes.html" class="nav-item active"> <a href="notes.html" class="nav-item active">
<i data-lucide="file-text"></i> <i data-lucide="file-text"></i>
<span>Notes</span> <span>KB</span>
</a> </a>
<a href="files.html" class="nav-item"> <a href="files.html" class="nav-item">
<i data-lucide="folder"></i> <i data-lucide="folder"></i>
<span>Files</span> <span>Files</span>
</a> </a>
<a href="grup-sprijin.html" class="nav-item">
<i data-lucide="heart-handshake"></i>
<span>Grup</span>
</a>
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema"> <button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
<i data-lucide="sun" id="themeIcon"></i> <i data-lucide="sun" id="themeIcon"></i>
</button> </button>
@@ -515,15 +664,29 @@
<main class="main"> <main class="main">
<div class="page-header"> <div class="page-header">
<h1 class="page-title">Notes</h1> <h1 class="page-title">Knowledge Base</h1>
<div class="search-bar"> <div class="search-bar">
<input type="text" class="input" id="searchInput" placeholder="Caută în notițe..." oninput="filterNotes()"> <input type="text" class="input" id="searchInput" placeholder="Caută în notițe..." oninput="filterNotes()">
</div> </div>
</div> </div>
<div class="tag-filter"> <div class="filter-bar" id="filterBar">
<div class="tag-pills" id="mainPills"></div> <div class="filter-group" id="categoryPills"></div>
<div class="tag-pills-more" id="tagPills"></div> <span class="filter-separator" id="sep1">|</span>
<div class="filter-group" id="projectPills"></div>
<span class="filter-separator" id="sep2">|</span>
<div class="filter-group" id="typePills"></div>
<span class="filter-separator" id="sep3">|</span>
<div class="filter-group" id="tagPills"></div>
<span class="more-tags-toggle" id="moreTagsToggle" onclick="toggleMoreTags()"></span>
<div class="filter-actions">
<button class="filter-btn clear" onclick="clearFilters()" title="Resetează" id="clearBtn" style="display: none;">
<i data-lucide="x"></i>
</button>
<button class="filter-btn" onclick="refreshIndex()" title="Reîncarcă">
<i data-lucide="refresh-cw" id="refreshIcon"></i>
</button>
</div>
</div> </div>
<div id="notesContainer"> <div id="notesContainer">
@@ -594,171 +757,267 @@
notesIndex = []; notesIndex = [];
} }
} }
let selectedTags = new Set(); // Current filter state
let selectedFilters = {
category: null,
project: null,
domain: null,
type: null,
tags: new Set()
};
let showAllTags = false;
// Extract all tags with counts (including domains and categories) // Get counts for each filter value
function getAllTags() { function getFilterCounts(filteredNotes = null) {
const tagCounts = {}; const notes = filteredNotes || notesIndex;
notesIndex.forEach(note => { const counts = {
// Category tags (📁youtube, 📁retete) categories: {},
if (note.category) { projects: {},
const catTag = '📁' + note.category; domains: {},
tagCounts[catTag] = (tagCounts[catTag] || 0) + 1; types: {},
} tags: {}
// Domain tags (@work, @health, etc.) };
if (note.domains) {
note.domains.forEach(domain => { notes.forEach(note => {
const domainTag = '@' + domain; if (note.category) counts.categories[note.category] = (counts.categories[note.category] || 0) + 1;
tagCounts[domainTag] = (tagCounts[domainTag] || 0) + 1; if (note.project) counts.projects[note.project] = (counts.projects[note.project] || 0) + 1;
if (note.domains) note.domains.forEach(d => counts.domains[d] = (counts.domains[d] || 0) + 1);
if (note.types) note.types.forEach(t => counts.types[t] = (counts.types[t] || 0) + 1);
if (note.tags) note.tags.forEach(t => counts.tags[t] = (counts.tags[t] || 0) + 1);
}); });
}
// Regular tags return counts;
note.tags.forEach(tag => {
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
});
});
// Sort: categories first (📁), then domains (@), then by count
return Object.entries(tagCounts)
.sort((a, b) => {
const aIsCat = a[0].startsWith('📁');
const bIsCat = b[0].startsWith('📁');
const aIsDomain = a[0].startsWith('@');
const bIsDomain = b[0].startsWith('@');
if (aIsCat && !bIsCat) return -1;
if (!aIsCat && bIsCat) return 1;
if (aIsDomain && !bIsDomain) return -1;
if (!aIsDomain && bIsDomain) return 1;
return b[1] - a[1];
})
.map(([tag, count]) => ({ tag, count }));
} }
// Render tag pills // Render filter pills
function renderTagPills(visibleNotes = null) { function renderFilterPills(filteredNotes = null) {
const mainContainer = document.getElementById('mainPills'); const counts = getFilterCounts();
const moreContainer = document.getElementById('tagPills'); const visibleCounts = filteredNotes ? getFilterCounts(filteredNotes) : counts;
const tags = getAllTags();
// Calculate which tags appear in visible notes // Categories
const visibleTags = new Set(); const catHtml = Object.entries(counts.categories)
if (visibleNotes && visibleNotes.length > 0) { .sort((a, b) => b[1] - a[1])
visibleNotes.forEach(note => { .map(([cat, count]) => {
note.tags.forEach(t => visibleTags.add(t)); const isActive = selectedFilters.category === cat;
(note.domains || []).forEach(d => visibleTags.add('@' + d)); const isVisible = visibleCounts.categories[cat] > 0;
if (note.category) visibleTags.add('📁' + note.category); const dimmed = filteredNotes && !isVisible && !isActive ? 'dimmed' : '';
}); return `<span class="filter-pill category ${isActive ? 'active' : ''} ${dimmed}" onclick="toggleFilter('category', '${cat}')">
} 📁${cat} <span class="filter-pill-count">${count}</span>
const hasFilter = visibleNotes !== null; </span>`;
}).join('');
document.getElementById('categoryPills').innerHTML = catHtml;
// Separate: categories + domains vs regular tags // Projects (only show if category=projects or no category selected)
const mainTags = tags.filter(({tag}) => tag.startsWith('📁') || tag.startsWith('@')); const showProjects = !selectedFilters.category || selectedFilters.category === 'projects';
const moreTags = tags.filter(({tag}) => !tag.startsWith('📁') && !tag.startsWith('@')); const projHtml = showProjects && Object.keys(counts.projects).length > 0 ? Object.entries(counts.projects)
.sort((a, b) => b[1] - a[1])
.map(([proj, count]) => {
const isActive = selectedFilters.project === proj;
const isVisible = visibleCounts.projects[proj] > 0;
const dimmed = filteredNotes && !isVisible && !isActive ? 'dimmed' : '';
return `<span class="filter-pill project ${isActive ? 'active' : ''} ${dimmed}" onclick="toggleFilter('project', '${proj}')">
📂${proj} <span class="filter-pill-count">${count}</span>
</span>`;
}).join('') : '';
document.getElementById('projectPills').innerHTML = projHtml;
document.getElementById('sep1').style.display = projHtml ? '' : 'none';
// Check if more section is expanded // Domains (@work, @health, etc.) - orange
const isExpanded = moreContainer.classList.contains('expanded'); const domainHtml = Object.entries(counts.domains)
const activeMoreCount = [...selectedTags].filter(t => !t.startsWith('📁') && !t.startsWith('@')).length; .sort((a, b) => b[1] - a[1])
.map(([domain, count]) => {
// Render main pills (categories + domains) const isActive = selectedFilters.domain === domain;
let mainHtml = mainTags.map(({ tag, count }) => { const isVisible = visibleCounts.domains[domain] > 0;
let pillClass = 'tag-pill'; const dimmed = filteredNotes && !isVisible && !isActive ? 'dimmed' : '';
if (tag.startsWith('📁')) pillClass += ' category'; return `<span class="filter-pill domain ${isActive ? 'active' : ''} ${dimmed}" onclick="toggleFilter('domain', '${domain}')">
else if (tag.startsWith('@')) pillClass += ' domain'; @${domain} <span class="filter-pill-count">${count}</span>
if (selectedTags.has(tag)) pillClass += ' active';
// Dim tags not in visible notes (unless it's already selected)
if (hasFilter && !visibleTags.has(tag) && !selectedTags.has(tag)) pillClass += ' dimmed';
return `<span class="${pillClass}" onclick="toggleTag('${tag}')">
${tag} <span class="tag-pill-count">(${count})</span>
</span>`; </span>`;
}).join(''); }).join('');
// Add "more tags" toggle button inline // Types (@meditatie, @exercitiu, etc.)
if (moreTags.length > 0) { const typeHtml = Object.entries(counts.types)
const visibleMoreCount = moreTags.filter(({tag}) => visibleTags.has(tag)).length; .sort((a, b) => b[1] - a[1])
const moreLabel = activeMoreCount > 0 .map(([type, count]) => {
? `+${moreTags.length} tags (${activeMoreCount} active)` const isActive = selectedFilters.type === type;
: (hasFilter && visibleMoreCount > 0 ? `+${visibleMoreCount}/${moreTags.length} tags` : `+${moreTags.length} tags`); const isVisible = visibleCounts.types[type] > 0;
mainHtml += `<span class="tag-pill more-toggle ${isExpanded ? 'expanded' : ''}" onclick="toggleMoreTags()"> const dimmed = filteredNotes && !isVisible && !isActive ? 'dimmed' : '';
${isExpanded ? '' : '+'} ${moreLabel} return `<span class="filter-pill type ${isActive ? 'active' : ''} ${dimmed}" onclick="toggleFilter('type', '${type}')">
</span>`; @${type} <span class="filter-pill-count">${count}</span>
}
if (selectedTags.size > 0) {
mainHtml += `<button class="clear-filters" onclick="clearTagFilters()">✕ Clear</button>`;
}
mainContainer.innerHTML = mainHtml;
// Render more pills (regular tags)
const moreHtml = moreTags.map(({ tag, count }) => {
let pillClass = 'tag-pill';
if (selectedTags.has(tag)) pillClass += ' active';
if (hasFilter && !visibleTags.has(tag) && !selectedTags.has(tag)) pillClass += ' dimmed';
return `<span class="${pillClass}" onclick="toggleTag('${tag}')">
${tag} <span class="tag-pill-count">(${count})</span>
</span>`; </span>`;
}).join(''); }).join('');
moreContainer.innerHTML = moreHtml;
}
// Toggle tag selection // Combine domains and types in typePills
function toggleTag(tag) { document.getElementById('typePills').innerHTML = domainHtml + typeHtml;
if (selectedTags.has(tag)) { document.getElementById('sep2').style.display = (domainHtml || typeHtml) ? '' : 'none';
selectedTags.delete(tag);
// Tags - collapsed by default, show only count
const tagEntries = Object.entries(counts.tags).sort((a, b) => b[1] - a[1]);
const activeTagsCount = selectedFilters.tags.size;
let tagHtml = '';
if (showAllTags) {
// Show all tags
tagHtml = tagEntries.map(([tag, count]) => {
const isActive = selectedFilters.tags.has(tag);
const isVisible = visibleCounts.tags[tag] > 0;
const dimmed = filteredNotes && !isVisible && !isActive ? 'dimmed' : '';
return `<span class="filter-pill tag ${isActive ? 'active' : ''} ${dimmed}" onclick="toggleFilter('tag', '${tag}')">
#${tag} <span class="filter-pill-count">${count}</span>
</span>`;
}).join('');
} else if (activeTagsCount > 0) {
// Show only active tags
tagHtml = [...selectedFilters.tags].map(tag => {
const count = counts.tags[tag] || 0;
return `<span class="filter-pill tag active" onclick="toggleFilter('tag', '${tag}')">
#${tag} <span class="filter-pill-count">${count}</span>
</span>`;
}).join('');
}
document.getElementById('tagPills').innerHTML = tagHtml;
document.getElementById('sep3').style.display = (tagHtml || tagEntries.length > 0) ? '' : 'none';
// More tags toggle
const moreToggle = document.getElementById('moreTagsToggle');
if (tagEntries.length > 0) {
const hiddenCount = activeTagsCount > 0 ? tagEntries.length - activeTagsCount : tagEntries.length;
if (showAllTags) {
moreToggle.textContent = `-${hiddenCount} tags`;
} else { } else {
selectedTags.add(tag); moreToggle.textContent = `+${hiddenCount} tags`;
}
moreToggle.style.display = '';
} else {
moreToggle.style.display = 'none';
}
// Clear button
const hasFilters = selectedFilters.category || selectedFilters.project ||
selectedFilters.domain || selectedFilters.type || selectedFilters.tags.size > 0;
document.getElementById('clearBtn').style.display = hasFilters ? '' : 'none';
}
// Toggle filter
function toggleFilter(filterType, value) {
if (filterType === 'tag') {
if (selectedFilters.tags.has(value)) {
selectedFilters.tags.delete(value);
} else {
selectedFilters.tags.add(value);
}
} else {
selectedFilters[filterType] = selectedFilters[filterType] === value ? null : value;
// Reset child filters when parent changes
if (filterType === 'category' && value !== 'projects') {
selectedFilters.project = null;
}
} }
renderTagPills();
filterNotes(); filterNotes();
} }
// Clear all tag filters // Toggle more tags
function clearTagFilters() {
selectedTags.clear();
renderTagPills();
filterNotes();
}
// Toggle more tags section
function toggleMoreTags() { function toggleMoreTags() {
const moreContainer = document.getElementById('tagPills'); showAllTags = !showAllTags;
moreContainer.classList.toggle('expanded'); renderFilterPills(lastFilteredNotes);
renderTagPills(lastFilteredNotes);
} }
// Filter notes by search and tags // Toggle tags in note card
function toggleNoteTags(noteId) {
const full = document.getElementById('tags_' + noteId);
const toggle = full.parentElement;
const count = toggle.querySelector('.note-tags-count');
if (full.style.display === 'none') {
full.style.display = 'inline';
count.style.display = 'none';
} else {
full.style.display = 'none';
count.style.display = 'inline';
}
}
// Clear all filters
function clearFilters() {
selectedFilters = { category: null, project: null, domain: null, type: null, tags: new Set() };
showAllTags = false;
document.getElementById('searchInput').value = '';
filterNotes();
}
// Refresh index from server
async function refreshIndex() {
const icon = document.getElementById('refreshIcon');
if (icon) icon.style.animation = 'spin 1s linear infinite';
try {
const response = await fetch('api/refresh-index', { method: 'POST' });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (data.success) {
await loadNotesIndex();
filterNotes();
lucide.createIcons();
console.log('Index refreshed:', data.message);
} else {
console.error('Refresh failed:', data.error);
alert('Eroare: ' + (data.error || 'Unknown error'));
}
} catch (e) {
console.error('Refresh error:', e);
alert('Eroare la refresh: ' + e.message);
} finally {
if (icon) icon.style.animation = '';
}
}
// Filter notes
function filterNotes() { function filterNotes() {
const query = document.getElementById('searchInput').value.toLowerCase().trim(); const query = document.getElementById('searchInput').value.toLowerCase().trim();
let filtered = notesIndex; let filtered = notesIndex;
// Filter by selected tags (AND logic) - includes categories and domains // Filter by category
if (selectedTags.size > 0) { if (selectedFilters.category) {
filtered = filtered.filter(note => { filtered = filtered.filter(n => n.category === selectedFilters.category);
const allNoteTags = [ }
...note.tags,
...(note.domains || []).map(d => '@' + d), // Filter by project
note.category ? '📁' + note.category : null if (selectedFilters.project) {
].filter(Boolean); filtered = filtered.filter(n => n.project === selectedFilters.project);
return [...selectedTags].every(tag => allNoteTags.includes(tag)); }
// Filter by domain (@work, @health, etc.)
if (selectedFilters.domain) {
filtered = filtered.filter(n => n.domains && n.domains.includes(selectedFilters.domain));
}
// Filter by type (@meditatie, @exercitiu, etc.)
if (selectedFilters.type) {
filtered = filtered.filter(n => n.types && n.types.includes(selectedFilters.type));
}
// Filter by tags (AND)
if (selectedFilters.tags.size > 0) {
filtered = filtered.filter(n => {
const noteTags = n.tags || [];
return [...selectedFilters.tags].every(t => noteTags.includes(t));
}); });
} }
// Filter by search query // Filter by search
if (query) { if (query) {
filtered = filtered.filter(note => { filtered = filtered.filter(n => {
const titleMatch = note.title.toLowerCase().includes(query); const titleMatch = n.title.toLowerCase().includes(query);
const tagsMatch = note.tags.some(t => t.toLowerCase().includes(query)); const tagsMatch = (n.tags || []).some(t => t.toLowerCase().includes(query));
const contentMatch = (notesCache[note.file] || '').toLowerCase().includes(query); const contentMatch = (notesCache[n.file] || '').toLowerCase().includes(query);
return titleMatch || tagsMatch || contentMatch; return titleMatch || tagsMatch || contentMatch;
}); });
} }
renderNotesAccordion(filtered);
// Save filtered notes for tag pills
lastFilteredNotes = filtered; lastFilteredNotes = filtered;
renderFilterPills(filtered);
// Update tag pills to show which tags are in visible notes renderNotesAccordion(filtered);
renderTagPills(filtered);
} }
// Group notes by date category // Group notes by date category
@@ -847,12 +1106,26 @@
} }
function renderNoteCard(note) { function renderNoteCard(note) {
// Domains (portocaliu), Types (mov), Tags colapsate cu expand
const tags = note.tags || [];
const noteId = note.file.replace(/[^a-zA-Z0-9]/g, '_');
const tagsHtml = tags.length > 0
? `<span class="note-tags-toggle" onclick="event.stopPropagation(); toggleNoteTags('${noteId}')">
<span class="note-tags-count">${tags.length} tags</span>
<span class="note-tags-full" id="tags_${noteId}" style="display:none">${tags.join(' · ')}</span>
</span>`
: '';
const allTags = [
...(note.domains || []).map(d => `<span class="note-tag domain">${d}</span>`),
...(note.types || []).map(t => `<span class="note-tag type">${t}</span>`),
tagsHtml
].filter(Boolean).join('');
return ` return `
<div class="note-card" onclick="openNote('${note.file}')"> <div class="note-card" onclick="openNote('${note.file}')">
<div class="note-title">${note.title}</div> <div class="note-title">${note.title}</div>
<div class="note-tags"> <div class="note-tags">${allTags}</div>
${note.tags.map(t => `<span class="tag">${t}</span>`).join('')}
</div>
</div> </div>
`; `;
} }
@@ -878,7 +1151,7 @@
try { try {
let content = notesCache[file]; let content = notesCache[file];
if (!content) { if (!content) {
const response = await fetch(notesBasePath + file); const response = await fetch(file);
content = await response.text(); content = await response.text();
notesCache[file] = content; notesCache[file] = content;
} }
@@ -897,7 +1170,7 @@
async function preloadNotes() { async function preloadNotes() {
for (const note of notesIndex) { for (const note of notesIndex) {
try { try {
const response = await fetch(notesBasePath + note.file); const response = await fetch(note.file);
notesCache[note.file] = await response.text(); notesCache[note.file] = await response.text();
} catch (e) { } catch (e) {
notesCache[note.file] = ''; notesCache[note.file] = '';
@@ -929,8 +1202,9 @@
// Init - load index first, then render // Init - load index first, then render
async function init() { async function init() {
await loadNotesIndex(); await loadNotesIndex();
renderTagPills(); renderFilterPills();
renderNotesAccordion(); renderNotesAccordion(notesIndex);
lucide.createIcons();
preloadNotes(); preloadNotes();
checkHash(); checkHash();
} }

1
dashboard/youtube-notes Symbolic link
View File

@@ -0,0 +1 @@
../kb/youtube

View File

@@ -1,118 +0,0 @@
[
{
"id": "ancorare-emotii",
"title": "Exercițiu de ancorare a emoțiilor",
"type": "exercitiu",
"tags": [
"NLP",
"ancorare",
"emotii",
"corp"
],
"used": null,
"content": "Să simți o emoție pozitivă pe care ți-o dorești și apoi să faci exercițiul cu ancorarea emoției. Când vrei să simți liniște, satisfacție, bucurie, energie - să revină în corp.\n\nPași:\n1. Gândește-te la un moment în care ai simțit emoția dorită\n2. Simte-o în corp\n3. Ancoreaz-o (gest, cuvânt, imagine)\n4. Testează ancora"
},
{
"id": "meditatie-demnitate",
"title": "Meditația cu demnitatea",
"type": "meditatie",
"tags": [
"meditatie",
"demnitate",
"sine"
],
"used": null,
"content": "Meditație despre demnitate personală.\n\n(De dezvoltat - Marius să adauge textul complet)"
},
{
"id": "meditatie-eu-sunt-mai-mare",
"title": "Eu sunt mai mare decât gândurile și emoțiile mele",
"type": "meditatie",
"tags": [
"meditatie",
"ganduri",
"emotii",
"distantare"
],
"used": null,
"content": "Meditație: Eu sunt mai mare decât gândurile mele, mai mare decât emoțiile mele.\n\nIdee: Creezi distanță între tine și gânduri/emoții. Tu ești observatorul, nu gândul."
},
{
"id": "meditatie-mindfulness",
"title": "Moment de mindfulness",
"type": "meditatie",
"tags": [
"meditatie",
"mindfulness",
"prezent"
],
"used": null,
"content": "Un moment de mindfulness - prezență în aici și acum.\n\n(De dezvoltat)"
},
{
"id": "intrebare-bucurie-azi",
"title": "Ce ai făcut azi care ți-a adus bucurie?",
"type": "intrebare",
"tags": [
"introspectie",
"bucurie",
"energie",
"recunostinta"
],
"used": null,
"content": "Întrebări în secvență:\n1. Ce îți dorești să simți? (bucurie, energie, entuziasm, motivație)\n2. Când ai mai simțit asta?\n3. Ce ai făcut ASTĂZI care să îți aducă acea emoție?"
},
{
"id": "intrebare-copil-interior",
"title": "Ce îți aducea bucurie când erai mic?",
"type": "intrebare",
"tags": [
"introspectie",
"copilarie",
"pasiune",
"bucurie"
],
"used": null,
"content": "Ce îți doreai să faci când erai mic? Ce îți aducea bucurie, entuziasm, satisfacție?\n\nCare era emoția? Ce poți să faci ACUM care să îți dea aceeași emoție?\n\nAdu-ți aminte de copilul care erai (poate mai ești și acum). Ce dorește să facă? Ce simțea? Când ai simțit acele emoții?"
},
{
"id": "reflectie-barbati-energie",
"title": "Credințe despre bărbați și cerut ajutor",
"type": "reflectie",
"tags": [
"credinte",
"masculin",
"ajutor",
"energie",
"vulnerabilitate"
],
"used": null,
"content": "Credințe limitatoare:\n- Bărbații sunt puternici\n- Bărbații trebuie să facă totul singuri\n- Bărbații nu trebuie să ceară ajutor\n\nȘi pentru femei - când trebuie să facă totul singure, nu cer ajutor, se încarcă, trag, împing dintr-o energie masculină.\n\nE OK să faci, dar te oprește de la resursele de energie și creativitate.\n\nÎntrebare: Unde tragi singur când ai putea cere ajutor?"
},
{
"id": "reflectie-oglinda",
"title": "Tot ce văd la tine am și eu în mine",
"type": "reflectie",
"tags": [
"oglinda",
"proiectie",
"emotii",
"autocunoastere"
],
"used": null,
"content": "Tot ce văd la tine am și eu în mine:\n- Sentimentul de vină, de rușine\n- Nu în aceleași situații\n- Furie, frustrare, jenă\n- Dar și bucurie\n\nCeilalți sunt oglinzi pentru noi."
},
{
"id": "beneficiu-grup-siguranta",
"title": "Beneficiul grupului - siguranță",
"type": "reflectie",
"tags": [
"grup",
"siguranta",
"energie",
"vulnerabilitate"
],
"used": null,
"content": "Câtă energie consum să arăt ce trebuie?\n\nÎn grup nu mai este nevoie de energie să mențin scutul sus, să țin sub apă balonul.\n\nÎntr-un grup de sprijin, mă simt în siguranță, nu sunt judecat."
}
]

View File

@@ -1 +0,0 @@
../notes

View File

@@ -1 +0,0 @@
../notes/youtube

62
kb/backlog.md Normal file
View File

@@ -0,0 +1,62 @@
# Backlog - Propuneri pentru viitor
**Tags:** @work @health @growth @sprijin @scout #backlog #propuneri
Propuneri păstrate pentru viitor. Format: `[ ]` de făcut, `[x]` făcut, `[—]` renunțat.
---
## ⚡ Urgent + Important
*Gol momentan - se adaugă din insights când zici "azi prioritar"*
---
## 📌 Important
### @work
- [ ] Verificare securitate Clawdbot (port, trustedProxies)
### @growth
- [ ] Exercițiu Priming în morning-coaching (3 recunoștințe)
### @sprijin
- [ ] Fișă "blocare vs deblocare" cu exercițiu fiziologie
---
## 💡 Nice-to-have
### @work
- [ ] Template spec-driven development în kb/projects/
- [ ] Job proactive coding noaptea (ora 23)
- [ ] Spellbook - prompt templates cu variabile
### @health
- [ ] Checklist post negru (tranziție, clisme, apă)
### @growth
- [ ] Dezvoltă personalitățile agenților (stil, nu doar funcțional)
### @sprijin
- [ ] Întrebare "Ce moment greu s-a dovedit cadou?"
- [ ] Exercițiu "Pentru cine altcineva faci asta?"
### @scout
- [ ] Activitate hero's journey pentru cercetași
---
## ✅ Făcut
*Se mută aici când completez*
---
## ❌ Renunțat
*Se mută aici când nu mai e relevant*
---
*Ultima actualizare: 2026-01-31*

10
kb/coaching/.rules.json Normal file
View File

@@ -0,0 +1,10 @@
{
"defaultDomains": ["health"],
"defaultTypes": ["coaching"],
"defaultTags": [],
"inferTypeFromFilename": true,
"filenameTypeMap": {
"dimineata": "coaching",
"seara": "reflectie"
}
}

573
kb/index.json Normal file
View File

@@ -0,0 +1,573 @@
{
"notes": [
{
"file": "notes-data/insights/2026-01-31.md",
"title": "Insights 2026-01-31",
"date": "2026-01-31",
"tags": [
"insights",
"propuneri"
],
"domains": [
"growth",
"health",
"work",
"sprijin",
"scout"
],
"types": [],
"category": "insights",
"project": null,
"subdir": null,
"video": "",
"tldr": "*Scanare completă: 9 note YouTube | 2026-01-31*"
},
{
"file": "notes-data/youtube/2026-01-31_tony-robbins-secret-extraordinary-life.md",
"title": "The Secret to an Extraordinary Life - Tony & Sage Robbins",
"date": "2026-01-31",
"tags": [],
"domains": [
"growth"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "",
"tldr": "Secretul unei vieți extraordinare nu e banii, poziția sau puterea - **e emoția**. Tony Robbins explică cum starea fizică, focusul și limbajul determină cum ne simțim. Pentru a te debloca: schimbă-ți c..."
},
{
"file": "notes-data/projects/vending-master/README.md",
"title": "Proiect: Vending Master - Integrare Website → ROA",
"date": "2026-01-30",
"tags": [
"vending-master",
"integrare"
],
"domains": [
"work"
],
"types": [],
"category": "projects",
"project": "vending-master",
"subdir": null,
"video": "",
"tldr": "[conversations/2026-01-30-conversatie-completa.md](https://moltbot.tailf7372d.ts.net/echo/files.html#conversations/2026-01-30-conversatie-completa.md)"
},
{
"file": "notes-data/projects/grup-sprijin/README.md",
"title": "Grup de Sprijin - Lideri Cercetași",
"date": "2026-01-30",
"tags": [
"grup-sprijin"
],
"domains": [
"sprijin"
],
"types": [],
"category": "projects",
"project": "grup-sprijin",
"subdir": null,
"video": "",
"tldr": ""
},
{
"file": "notes-data/projects/grup-sprijin/fise/fisa-2026-02-05-ancorare-oglinda.md",
"title": "Fișă Întâlnire Grup Sprijin",
"date": "2026-01-30",
"tags": [
"grup-sprijin"
],
"domains": [
"sprijin"
],
"types": [
"fisa"
],
"category": "projects",
"project": "grup-sprijin",
"subdir": "fise",
"video": "",
"tldr": ""
},
{
"file": "notes-data/projects/grup-sprijin/biblioteca/meditatie-mindfulness.md",
"title": "Moment de mindfulness",
"date": "2026-01-30",
"tags": [
"mindfulness",
"prezent",
"grup-sprijin"
],
"domains": [
"sprijin"
],
"types": [
"meditatie"
],
"category": "projects",
"project": "grup-sprijin",
"subdir": "biblioteca",
"video": "",
"tldr": "(De dezvoltat)"
},
{
"file": "notes-data/projects/grup-sprijin/biblioteca/meditatie-eu-sunt-mai-mare.md",
"title": "Eu sunt mai mare decât gândurile și emoțiile mele",
"date": "2026-01-30",
"tags": [
"ganduri",
"emotii",
"distantare",
"grup-sprijin"
],
"domains": [
"sprijin"
],
"types": [
"meditatie"
],
"category": "projects",
"project": "grup-sprijin",
"subdir": "biblioteca",
"video": "",
"tldr": "Idee: Creezi distanță între tine și gânduri/emoții. Tu ești observatorul, nu gândul."
},
{
"file": "notes-data/projects/grup-sprijin/biblioteca/meditatie-demnitate.md",
"title": "Meditația cu demnitatea",
"date": "2026-01-30",
"tags": [
"demnitate",
"sine",
"grup-sprijin"
],
"domains": [
"sprijin"
],
"types": [
"meditatie"
],
"category": "projects",
"project": "grup-sprijin",
"subdir": "biblioteca",
"video": "",
"tldr": "(De dezvoltat - Marius să adauge textul complet)"
},
{
"file": "notes-data/projects/grup-sprijin/biblioteca/ancorare-emotii.md",
"title": "Exercițiu de ancorare a emoțiilor",
"date": "2026-01-30",
"tags": [
"NLP",
"ancorare",
"emotii",
"corp",
"grup-sprijin"
],
"domains": [
"sprijin"
],
"types": [
"exercitiu"
],
"category": "projects",
"project": "grup-sprijin",
"subdir": "biblioteca",
"video": "",
"tldr": "4. Testează ancora"
},
{
"file": "notes-data/projects/grup-sprijin/biblioteca/intrebare-copil-interior.md",
"title": "Ce îți aducea bucurie când erai mic?",
"date": "2026-01-30",
"tags": [
"introspectie",
"copilarie",
"pasiune",
"bucurie",
"grup-sprijin"
],
"domains": [
"sprijin"
],
"types": [
"intrebare"
],
"category": "projects",
"project": "grup-sprijin",
"subdir": "biblioteca",
"video": "",
"tldr": "Adu-ți aminte de copilul care erai (poate mai ești și acum). Ce dorește să facă? Ce simțea? Când ai simțit acele emoții?"
},
{
"file": "notes-data/projects/grup-sprijin/biblioteca/reflectie-oglinda.md",
"title": "Tot ce văd la tine am și eu în mine",
"date": "2026-01-30",
"tags": [
"oglinda",
"proiectie",
"emotii",
"autocunoastere",
"grup-sprijin"
],
"domains": [
"sprijin"
],
"types": [
"reflectie"
],
"category": "projects",
"project": "grup-sprijin",
"subdir": "biblioteca",
"video": "",
"tldr": "Ceilalți sunt oglinzi pentru noi."
},
{
"file": "notes-data/projects/grup-sprijin/biblioteca/intrebare-bucurie-azi.md",
"title": "Ce ai făcut azi care ți-a adus bucurie?",
"date": "2026-01-30",
"tags": [
"introspectie",
"bucurie",
"energie",
"recunostinta",
"grup-sprijin"
],
"domains": [
"sprijin"
],
"types": [
"intrebare"
],
"category": "projects",
"project": "grup-sprijin",
"subdir": "biblioteca",
"video": "",
"tldr": "3. Ce ai făcut ASTĂZI care să îți aducă acea emoție?"
},
{
"file": "notes-data/projects/grup-sprijin/biblioteca/beneficiu-grup-siguranta.md",
"title": "Beneficiul grupului - siguranță",
"date": "2026-01-30",
"tags": [
"grup",
"siguranta",
"energie",
"vulnerabilitate",
"grup-sprijin"
],
"domains": [
"sprijin"
],
"types": [
"reflectie"
],
"category": "projects",
"project": "grup-sprijin",
"subdir": "biblioteca",
"video": "",
"tldr": "Într-un grup de sprijin, mă simt în siguranță, nu sunt judecat."
},
{
"file": "notes-data/projects/grup-sprijin/biblioteca/reflectie-barbati-energie.md",
"title": "Credințe despre bărbați și cerut ajutor",
"date": "2026-01-30",
"tags": [
"credinte",
"masculin",
"ajutor",
"energie",
"vulnerabilitate",
"grup-sprijin"
],
"domains": [
"sprijin"
],
"types": [
"reflectie"
],
"category": "projects",
"project": "grup-sprijin",
"subdir": "biblioteca",
"video": "",
"tldr": "Întrebare: Unde tragi singur când ai putea cere ajutor?"
},
{
"file": "notes-data/retete/2026-01-30_ciorba-burta-falsa-cu-pui.md",
"title": "Ciorbă de Burtă Falsă cu Pui și Ciuperci Pleurotus",
"date": "2026-01-30",
"tags": [
"ciorba",
"reteta",
"pleurotus",
"pui"
],
"domains": [
"health"
],
"types": [],
"category": "retete",
"project": null,
"subdir": null,
"video": "",
"tldr": "- Se poate face și de post: fără carne, cu lapte vegetal în loc de smântână"
},
{
"file": "notes-data/youtube/2026-01-30_claude-code-do-work-pattern.md",
"title": "The Most Powerful Claude Code Pattern I've Found",
"date": "2026-01-30",
"tags": [
"claude-code",
"skills",
"workflow",
"automation",
"do-work"
],
"domains": [
"work"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "https://youtu.be/I9-tdhxiH7w",
"tldr": "Un pattern puternic pentru Claude Code: **Do Work** - o coadă de task-uri pe care Claude le procesează automat, unul câte unul, în sub-agenți cu context curat. Ideea cheie: **construiește tool-uri pen..."
},
{
"file": "notes-data/youtube/2026-01-30_clawdbot-5-use-cases.md",
"title": "5 Insane ClawdBot Use Cases You Need To Do Immediately",
"date": "2026-01-30",
"tags": [
"clawdbot",
"automation",
"productivity",
"ai-assistant"
],
"domains": [
"work"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "https://www.youtube.com/watch?v=b-l9sGh1-UY",
"tldr": "5 use case-uri pentru ClawdBot care îl transformă dintr-un simplu chatbot într-un asistent proactiv care lucrează pentru tine chiar și când dormi."
},
{
"file": "notes-data/youtube/2026-01-30_clawdbot-personal-os-kitze.md",
"title": "How I Use Clawdbot to Run My Business and Life 24/7",
"date": "2026-01-30",
"tags": [
"clawdbot",
"productivity",
"personas",
"automation"
],
"domains": [
"work",
"growth"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "https://youtu.be/YRhGtHfs1Lw",
"tldr": "Kitze folosește **UN SINGUR gateway Clawdbot** cu **MULTIPLE PERSONAS** pe Telegram/Discord. Fiecare personă are:\n- Personalitate diferită (avatar, stil de vorbit)\n- Skills diferite (acces la tool-uri..."
},
{
"file": "memory/2026-01-30.md",
"title": "2026-01-30",
"date": "2026-01-30",
"tags": [],
"domains": [],
"types": [
"memory"
],
"category": "memory",
"project": null,
"subdir": null,
"video": "",
"tldr": "- **Proactivitate activată**: Marius vrea să fiu proactiv - să propun automatizări, tools, să conectez punctele din discuții. Budget Claude Max $100/lună."
},
{
"file": "conversations/2026-01-30-conversatie-completa.md",
"title": "2026-01-30 - Conversație completă dimineață",
"date": "2026-01-30",
"tags": [],
"domains": [],
"types": [
"conversation"
],
"category": "conversations",
"project": null,
"subdir": null,
"video": "",
"tldr": "6. **Nevoie:** Accountability - check-in-uri regulate ca să nu amâne."
},
{
"file": "notes-data/youtube/2026-01-29_cloudflare-tunnel-localhost-public.md",
"title": "Cloudflare Tunnel: Make Localhost Public Without Port Forwarding",
"date": "2026-01-29",
"tags": [
"cloudflare",
"tunnel",
"localhost",
"networking",
"devops"
],
"domains": [
"work"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "https://youtu.be/etluT8UC-nw",
"tldr": "Cloudflare Tunnel permite expunerea unui server local (localhost) pe internet printr-un domeniu public, fără port forwarding, fără configurare router, fără expunerea IP-ului public. App-ul rămâne pe m..."
},
{
"file": "notes-data/youtube/2026-01-29_gsd-framework-claude-code.md",
"title": "Forget Ralph Loops: The New GSD Framework for Claude Code",
"date": "2026-01-29",
"tags": [
"claude-code",
"gsd",
"framework",
"sub-agents",
"automation"
],
"domains": [
"work"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "https://www.youtube.com/watch?v=l94A53kIUB0",
"tldr": "GSD (Get Shit Done) este un framework open-source pentru Claude Code care orchestrează sub-agenți pentru a completa proiecte urmând spec-driven development. Rezolvă problema \"context bloat\" prin rular..."
},
{
"file": "notes-data/youtube/2026-01-29_clawdbot-security-vulnerabilities.md",
"title": "It Got Worse (Clawdbot) - Security Vulnerabilities",
"date": "2026-01-29",
"tags": [
"clawdbot",
"security",
"vulnerabilities",
"hacking"
],
"domains": [
"work"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "https://youtu.be/rPAKq2oQVBs",
"tldr": "Video critic despre vulnerabilitățile de securitate ale Clawdbot - sute/mii de instanțe au fost compromise. Probleme principale: porturi default, parole lipsă, reverse proxy misconfigurat, skills mali..."
},
{
"file": "notes-data/youtube/2026-01-29_greseli-post-apa.md",
"title": "Greșeli frecvente în timpul postului doar cu apă",
"date": "2026-01-29",
"tags": [
"post",
"water-fasting",
"sănătate",
"detox"
],
"domains": [
"health"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "https://youtu.be/4QjkI0sf64M",
"tldr": "Greșelile frecvente pe care le fac oamenii când țin post terapeutic cu apă și cum să le eviți. Puncte cheie: pregătire corectă, curățarea colonului, calitatea apei, și importanța scopului spiritual."
},
{
"file": "notes-data/youtube/2026-01-29_remotion-skill-claude-code.md",
"title": "How people are generating videos with Claude Code (Remotion Skill)",
"date": "2026-01-29",
"tags": [
"remotion",
"claude-code",
"video",
"automation"
],
"domains": [
"work"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "https://youtu.be/7OR-L0AySn8",
"tldr": "Remotion Skill permite generarea de videouri programatic cu Claude Code. Funcționează prin React components → video export. Demo live: Claude creează animații YouTube (like, subscribe, cursor) doar di..."
},
{
"file": "memory/2026-01-29.md",
"title": "2026-01-29 — Prima zi",
"date": "2026-01-29",
"tags": [],
"domains": [],
"types": [
"memory"
],
"category": "memory",
"project": null,
"subdir": null,
"video": "",
"tldr": "- [ ] Explora ce alte automatizări ar ajuta"
}
],
"stats": {
"total": 26,
"by_domain": {
"work": 9,
"health": 3,
"growth": 3,
"sprijin": 12,
"scout": 1
},
"by_category": {
"coaching": 0,
"insights": 1,
"projects": 12,
"retete": 1,
"youtube": 9,
"memory": 2,
"conversations": 1
}
},
"domains": [
"work",
"health",
"growth",
"sprijin",
"scout"
],
"types": [
"exercitiu",
"meditatie",
"reflectie",
"intrebare",
"fisa",
"project",
"memory",
"conversation",
"coaching"
],
"categories": [
"coaching",
"insights",
"projects",
"retete",
"youtube",
"memory",
"conversations"
]
}

59
kb/insights/2026-01-31.md Normal file
View File

@@ -0,0 +1,59 @@
# Insights 2026-01-31
**Tags:** @growth @health @work @sprijin @scout #insights #propuneri
Idei extrase din note YouTube. Format: `[ ]` neprocesat, `[x]` făcut, `[→]` backlog, `[—]` skip.
---
## @work - Productivitate & Automatizări
- [ ] ⚡ Verificare securitate Clawdbot (port, trustedProxies) - [Security](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-29_clawdbot-security-vulnerabilities.md)
- [ ] 📌 Template spec-driven development în kb/projects/ - [GSD](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-29_gsd-framework-claude-code.md)
- [ ] 💡 Job proactive coding noaptea (ora 23) - [5 Use Cases](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-30_clawdbot-5-use-cases.md)
- [ ] 💡 Spellbook - prompt templates cu variabile - [Kitze](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-30_clawdbot-personal-os-kitze.md)
---
## @health - Sănătate
- [ ] 📌 Reminder "ridică-te, mergi 2 min" în respirații - [Tony Robbins](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-31_tony-robbins-secret-extraordinary-life.md)
- [ ] 💡 Checklist post negru (tranziție, clisme, apă) - [Post Apă](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-29_greseli-post-apa.md)
---
## @growth - Dezvoltare personală
- [ ] ⚡ Exercițiu Priming în morning-coaching (3 recunoștințe) - [Tony Robbins](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-31_tony-robbins-secret-extraordinary-life.md)
- [ ] 📌 Pattern Interrupt tehnica (fiziologie, focus, limbaj) - [Tony Robbins](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-31_tony-robbins-secret-extraordinary-life.md)
- [ ] 💡 Dezvoltă personalitățile agenților (stil, nu doar funcțional) - [Kitze](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-30_clawdbot-personal-os-kitze.md)
---
## @sprijin - Grup sprijin
- [ ] ⚡ Fișă "blocare vs deblocare" cu exercițiu fiziologie - [Tony Robbins](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-31_tony-robbins-secret-extraordinary-life.md)
- [ ] 📌 Întrebare "Ce moment greu s-a dovedit cadou?" - [Tony Robbins](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-31_tony-robbins-secret-extraordinary-life.md)
- [ ] 💡 Exercițiu "Pentru cine altcineva faci asta?" - [Post Apă](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-29_greseli-post-apa.md)
---
## @scout - Cercetași
- [ ] 💡 Activitate hero's journey pentru cercetași - [Tony Robbins](https://moltbot.tailf7372d.ts.net/echo/files.html#kb/youtube/2026-01-31_tony-robbins-secret-extraordinary-life.md)
---
## Legendă
- `[ ]` = neprocesat
- `[x]` = făcut
- `[→]` = mutat în backlog
- `[—]` = renunțat/skip
- ⚡ = urgent+important
- 📌 = important
- 💡 = nice-to-have
---
*Scanare: 9 note YouTube | 2026-01-31*

5
kb/projects/.rules.json Normal file
View File

@@ -0,0 +1,5 @@
{
"description": "Proiecte active",
"filenameRule": "Pentru proiecte cu tipuri, pune tipul în numele fișierului",
"inferTypeFromFilename": true
}

View File

@@ -0,0 +1,15 @@
{
"description": "Proiect grup de sprijin - lideri cercetași",
"defaultDomains": ["sprijin"],
"defaultTags": ["grup-sprijin"],
"filenameRule": "Pune tipul în numele fișierului: meditatie-xxx.md, exercitiu-xxx.md, etc.",
"validTypes": ["meditatie", "exercitiu", "reflectie", "intrebare", "fisa"],
"inferTypeFromFilename": true,
"filenameTypeMap": {
"meditatie": "meditatie",
"exercitiu": "exercitiu",
"intrebare": "intrebare",
"reflectie": "reflectie",
"fisa": "fisa"
}
}

View File

@@ -0,0 +1,13 @@
# Exercițiu de ancorare a emoțiilor
**Tags:** @exercitiu @grup-sprijin #NLP #ancorare #emotii #corp
---
Să simți o emoție pozitivă pe care ți-o dorești și apoi să faci exercițiul cu ancorarea emoției. Când vrei să simți liniște, satisfacție, bucurie, energie - să revină în corp.
Pași:
1. Gândește-te la un moment în care ai simțit emoția dorită
2. Simte-o în corp
3. Ancoreaz-o (gest, cuvânt, imagine)
4. Testează ancora

View File

@@ -0,0 +1,11 @@
# Beneficiul grupului - siguranță
**Tags:** @reflectie @grup-sprijin #grup #siguranta #energie #vulnerabilitate
---
Câtă energie consum să arăt ce trebuie?
În grup nu mai este nevoie de energie să mențin scutul sus, să țin sub apă balonul.
Într-un grup de sprijin, mă simt în siguranță, nu sunt judecat.

View File

@@ -0,0 +1,10 @@
# Ce ai făcut azi care ți-a adus bucurie?
**Tags:** @intrebare @grup-sprijin #introspectie #bucurie #energie #recunostinta
---
Întrebări în secvență:
1. Ce îți dorești să simți? (bucurie, energie, entuziasm, motivație)
2. Când ai mai simțit asta?
3. Ce ai făcut ASTĂZI care să îți aducă acea emoție?

View File

@@ -0,0 +1,11 @@
# Ce îți aducea bucurie când erai mic?
**Tags:** @intrebare @grup-sprijin #introspectie #copilarie #pasiune #bucurie
---
Ce îți doreai să faci când erai mic? Ce îți aducea bucurie, entuziasm, satisfacție?
Care era emoția? Ce poți să faci ACUM care să îți dea aceeași emoție?
Adu-ți aminte de copilul care erai (poate mai ești și acum). Ce dorește să facă? Ce simțea? Când ai simțit acele emoții?

View File

@@ -0,0 +1,9 @@
# Meditația cu demnitatea
**Tags:** @meditatie @grup-sprijin #meditatie #demnitate #sine
---
Meditație despre demnitate personală.
(De dezvoltat - Marius să adauge textul complet)

View File

@@ -0,0 +1,9 @@
# Eu sunt mai mare decât gândurile și emoțiile mele
**Tags:** @meditatie @grup-sprijin #meditatie #ganduri #emotii #distantare
---
Meditație: Eu sunt mai mare decât gândurile mele, mai mare decât emoțiile mele.
Idee: Creezi distanță între tine și gânduri/emoții. Tu ești observatorul, nu gândul.

View File

@@ -0,0 +1,9 @@
# Moment de mindfulness
**Tags:** @meditatie @grup-sprijin #meditatie #mindfulness #prezent
---
Un moment de mindfulness - prezență în aici și acum.
(De dezvoltat)

View File

@@ -0,0 +1,16 @@
# Credințe despre bărbați și cerut ajutor
**Tags:** @reflectie @grup-sprijin #credinte #masculin #ajutor #energie #vulnerabilitate
---
Credințe limitatoare:
- Bărbații sunt puternici
- Bărbații trebuie să facă totul singuri
- Bărbații nu trebuie să ceară ajutor
Și pentru femei - când trebuie să facă totul singure, nu cer ajutor, se încarcă, trag, împing dintr-o energie masculină.
E OK să faci, dar te oprește de la resursele de energie și creativitate.
Întrebare: Unde tragi singur când ai putea cere ajutor?

View File

@@ -0,0 +1,13 @@
# Tot ce văd la tine am și eu în mine
**Tags:** @reflectie @grup-sprijin #oglinda #proiectie #emotii #autocunoastere
---
Tot ce văd la tine am și eu în mine:
- Sentimentul de vină, de rușine
- Nu în aceleași situații
- Furie, frustrare, jenă
- Dar și bucurie
Ceilalți sunt oglinzi pentru noi.

View File

@@ -0,0 +1,5 @@
{
"description": "Proiect integrare Vending Master cu ROA",
"defaultDomains": ["work"],
"defaultTags": ["vending-master", "integrare"]
}

5
kb/retete/.rules.json Normal file
View File

@@ -0,0 +1,5 @@
{
"defaultDomains": ["health"],
"defaultTypes": [],
"defaultTags": []
}

5
kb/youtube/.rules.json Normal file
View File

@@ -0,0 +1,5 @@
{
"defaultDomains": [],
"defaultTypes": [],
"defaultTags": []
}

View File

@@ -0,0 +1,123 @@
# The Secret to an Extraordinary Life - Tony & Sage Robbins
**Sursă:** https://youtu.be/FkcmW0Bbpao
**Cu:** Tony Robbins, Sage Robbins, Evaluna Montaner, Mau y Ricky
**Data notei:** 2026-01-31
**Tags:** @growth, @development, @mindset
---
## TL;DR
Secretul unei vieți extraordinare nu e banii, poziția sau puterea - **e emoția**. Tony Robbins explică cum starea fizică, focusul și limbajul determină cum ne simțim. Pentru a te debloca: schimbă-ți corpul, schimbă-ți focusul, schimbă-ți cuvintele.
---
## 🎯 Puncte Cheie
### 1. Găsirea scopului în momente dificile
> "Everything happens for a reason and a purpose greater than you know, but it's your job to find that reason."
- Viața se întâmplă **pentru noi**, nu **cu noi**
- Hero's journey: viața pare OK → ceva se întâmplă → "call to adventure" (care pare moarte/sfârșit)
- **Nu ai un singur scop** - ai multiple scopuri în diferite contexte
- Caută scopul mai degrabă decât să încerci să "găsești" un scop unic
### 2. Cum să te deblocezi (Pattern Interrupt)
**Cele 3 lucruri care controlează cum te simți:**
1. **Fiziologia** (corpul)
- Depresia are o postură: umeri căzuți, cap în jos, respirație superficială
- Energia/entuziasmul: energie sus, corp deschis
- **Schimbă corpul PRIMUL** - mișcă-te, respiră diferit
2. **Focusul** (ce și cum)
- Nu doar CE vezi, ci CUM: imaginea e mare/mică? Aproape/departe? Color/alb-negru?
- Anxietatea: imagine mare, aproape, se îndepărtează (pierdere)
- Încrederea: imagine mare, se apropie de tine
3. **Limbajul**
- Cuvintele atașate experienței DEVIN experiența
- "Nutritious snacks" vs "Delicious snacks" - percepție diferită
- Ce-ți spui când ești anxios vs când știi că vei reuși?
**Tehnici rapide de deblocare:**
- Schimbare de temperatură (apă rece, saună) - resetează instant
- Mișcare (plimbare) - nu poți rămâne blocat când te miști
- "Can we begin again?" - reset în relații
### 3. Priming (ritual zilnic Tony Robbins)
Proces de 10 minute în fiecare dimineață:
- Intră într-o stare pozitivă
- Simte 3 lucruri pentru care ești recunoscător (nu doar le gândește, LE SIMTE)
- Recunoștința elimină frica și furia
> "Work on gratitude and happiness is automatic."
### 4. Relații și lucrul împreună
- Businessul = misiune, nu doar muncă
- Fă momentele să conteze (hot tub catch-up, priviri, atingeri)
- Ritualuri de conectare (cina fără telefoane)
- "I love you too much to go this place" - oprește escaladarea
- Apreciază puzzle piece-ul fiecăruia - daruri diferite
### 5. Începe de unde ești
Povestea lui Tony:
- La 11 ani, familie săracă, Thanksgiving fără mâncare
- Un străin a adus mâncare → "Strangers care"
- La 17 ani: a hrănit 2 familii
- Apoi 4, 8, apoi 1 milion/an, 42 milioane în 37 ani
- Provocare: 100 miliarde mese în 10 ani (62 miliarde în 3 ani deja)
> "If I won't give a dime out of a dollar, how will I ever give a million out of 10?"
### 6. Cele 5 arii de stăpânit în viață
1. **Corpul** - fără energie, nimic nu merge
2. **Emoțiile** - determină calitatea vieții
3. **Relațiile** - cea mai mare bucurie
4. **Timpul** - nu-l gestiona, CREEAZĂ viața
5. **Finanțele/Cariera + Spiritualitatea** - creștere și contribuție
---
## 💡 Acțiuni Practice
1. **Dimineața:** Priming 10 min - simte 3 lucruri pentru care ești recunoscător
2. **Când ești blocat:** Mișcă-te fizic, schimbă temperatura, mergi la plimbare
3. **În conflicte:** "Can we begin again?" sau "I love you too much for this"
4. **Pentru copii:** Respiră CU ei, nu doar le spune să respire
5. **Goals:** Stabilește obiective nerealiste cu timeline strâns (12-36 luni max)
---
## 📝 Citate Memorabile
> "What's wrong is always available. So is what's right."
> "You don't experience life. You experience the life you focus on."
> "My worst day became my best day."
> "Scarcity is a lie."
> "We're not made to manage life. We're made to create."
> "Religion means to celebrate. You can't celebrate if you're not grateful."
---
## 🔗 Resurse menționate
- **Time to Rise Summit** - timetorisesummit.com (gratis, 3 zile x 3 ore)
- **Unleash the Power Within** - seminar live
- **Life Force** (carte Tony Robbins despre sănătate)
---
*Video: ~1h conversation, familia Montaner cu Tony & Sage Robbins*

30
memory/2026-01-31.md Normal file
View File

@@ -0,0 +1,30 @@
# Memory 2026-01-31
## Decizii
### Dashboard statistici
- Adăugat secțiune statistici în index.html: task-uri azi/săptămâna/luna + insights
- Se calculează automat din tasks.json (coloana done)
### Format insights cu tracking
- Checkboxes: `[ ]` neprocesat, `[x]` făcut, `[→]` backlog, `[—]` skip
- Prioritate: ⚡ urgent, 📌 important, 💡 nice-to-have
- Fiecare propunere cu link la sursă
### Rapoarte actualizate (morning + evening)
- **Pas 0 NOU:** Scanare automată kb/youtube/ ultimele 48h
- Verifică dacă nota apare deja în insights
- Dacă nu → extrage și adaugă în insights/YYYY-MM-DD.md
- Verifică doar `[ ]` neprocesate din insights (AZI + IERI) + backlog
- După răspunsul lui Marius: marchează `[x]`/`[→]`/`[—]`
### Backlog.md restructurat
- Secțiuni: Urgent+Important, Important, Nice-to-have, Făcut, Renunțat
- Același format checkbox ca insights
## De făcut
- Verificare că rapoartele funcționează corect la 08:30 și 20:00
## Învățat
- Marius preferă totul într-un singur loc (scanare în raport, nu job separat)
- Insights trebuie să aibă status clar ca să nu se repete propunerile

View File

@@ -1,6 +1,6 @@
{ {
"lastChecks": { "lastChecks": {
"agents_sync": "2026-01-30", "agents_sync": "2026-01-31",
"email": null, "email": null,
"calendar": null, "calendar": null,
"git": 1738241820 "git": 1738241820

View File

@@ -1,186 +0,0 @@
{
"notes": [
{
"file": "youtube/2026-01-30_clawdbot-personal-os-kitze.md",
"title": "How I Use Clawdbot to Run My Business and Life 24/7",
"date": "2026-01-30",
"tags": [
"clawdbot",
"productivity",
"personas",
"automation"
],
"domains": [
"work",
"growth"
],
"video": "https://youtu.be/YRhGtHfs1Lw",
"tldr": "Kitze folosește **UN SINGUR gateway Clawdbot** cu **MULTIPLE PERSONAS** pe Telegram/Discord. Fiecare personă are:\n- Personalitate diferită (avatar, stil de vorbit)\n- Skills diferite (acces la tool-uri...",
"category": "youtube"
},
{
"file": "retete/2026-01-30_ciorba-burta-falsa-cu-pui.md",
"title": "Ciorbă de Burtă Falsă cu Pui și Ciuperci Pleurotus",
"date": "2026-01-30",
"tags": [
"ciorba",
"reteta",
"pleurotus",
"pui"
],
"domains": [
"health"
],
"video": "",
"tldr": "",
"category": "retete"
},
{
"file": "youtube/2026-01-29_remotion-skill-claude-code.md",
"title": "How people are generating videos with Claude Code (Remotion Skill)",
"date": "2026-01-29",
"tags": [
"remotion",
"claude-code",
"video",
"automation"
],
"domains": [
"work"
],
"video": "https://youtu.be/7OR-L0AySn8",
"tldr": "Remotion Skill permite generarea de videouri programatic cu Claude Code. Funcționează prin React components → video export. Demo live: Claude creează animații YouTube (like, subscribe, cursor) doar di...",
"category": "youtube"
},
{
"file": "youtube/2026-01-29_gsd-framework-claude-code.md",
"title": "Forget Ralph Loops: The New GSD Framework for Claude Code",
"date": "2026-01-29",
"tags": [
"claude-code",
"gsd",
"framework",
"sub-agents",
"automation"
],
"domains": [
"work"
],
"video": "https://www.youtube.com/watch?v=l94A53kIUB0",
"tldr": "GSD (Get Shit Done) este un framework open-source pentru Claude Code care orchestrează sub-agenți pentru a completa proiecte urmând spec-driven development. Rezolvă problema \"context bloat\" prin rular...",
"category": "youtube"
},
{
"file": "youtube/2026-01-29_greseli-post-apa.md",
"title": "Greșeli frecvente în timpul postului doar cu apă",
"date": "2026-01-29",
"tags": [
"post",
"water-fasting",
"sănătate",
"detox"
],
"domains": [
"health"
],
"video": "https://youtu.be/4QjkI0sf64M",
"tldr": "Greșelile frecvente pe care le fac oamenii când țin post terapeutic cu apă și cum să le eviți. Puncte cheie: pregătire corectă, curățarea colonului, calitatea apei, și importanța scopului spiritual.",
"category": "youtube"
},
{
"file": "youtube/2026-01-29_cloudflare-tunnel-localhost-public.md",
"title": "Cloudflare Tunnel: Make Localhost Public Without Port Forwarding",
"date": "2026-01-29",
"tags": [
"cloudflare",
"tunnel",
"localhost",
"networking",
"devops"
],
"domains": [
"work"
],
"video": "https://youtu.be/etluT8UC-nw",
"tldr": "Cloudflare Tunnel permite expunerea unui server local (localhost) pe internet printr-un domeniu public, fără port forwarding, fără configurare router, fără expunerea IP-ului public. App-ul rămâne pe m...",
"category": "youtube"
},
{
"file": "youtube/2026-01-29_clawdbot-security-vulnerabilities.md",
"title": "It Got Worse (Clawdbot) - Security Vulnerabilities",
"date": "2026-01-29",
"tags": [
"clawdbot",
"security",
"vulnerabilities",
"hacking"
],
"domains": [
"work"
],
"video": "https://youtu.be/rPAKq2oQVBs",
"tldr": "Video critic despre vulnerabilitățile de securitate ale Clawdbot - sute/mii de instanțe au fost compromise. Probleme principale: porturi default, parole lipsă, reverse proxy misconfigurat, skills mali...",
"category": "youtube"
},
{
"file": "youtube/2025-01-30_clawdbot-5-use-cases.md",
"title": "5 Insane ClawdBot Use Cases You Need To Do Immediately",
"date": "2025-01-30",
"tags": [
"clawdbot",
"automation",
"productivity",
"ai-assistant"
],
"domains": [
"work"
],
"video": "https://www.youtube.com/watch?v=b-l9sGh1-UY",
"tldr": "5 use case-uri pentru ClawdBot care îl transformă dintr-un simplu chatbot într-un asistent proactiv care lucrează pentru tine chiar și când dormi.",
"category": "youtube"
},
{
"file": "youtube/2025-01-30_claude-code-do-work-pattern.md",
"title": "The Most Powerful Claude Code Pattern I've Found",
"date": "2025-01-30",
"tags": [
"claude-code",
"skills",
"workflow",
"automation",
"do-work"
],
"domains": [
"work"
],
"video": "https://youtu.be/I9-tdhxiH7w",
"tldr": "Un pattern puternic pentru Claude Code: **Do Work** - o coadă de task-uri pe care Claude le procesează automat, unul câte unul, în sub-agenți cu context curat. Ideea cheie: **construiește tool-uri pen...",
"category": "youtube"
}
],
"stats": {
"total": 9,
"by_domain": {
"work": 7,
"health": 2,
"growth": 1,
"sprijin": 0,
"scout": 0
},
"by_category": {
"youtube": 8,
"retete": 1
}
},
"domains": [
"work",
"health",
"growth",
"sprijin",
"scout"
],
"categories": [
"youtube",
"retete"
]
}

View File

@@ -1,25 +1,75 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Generează index.json pentru notes din fișierele .md Generează index.json pentru KB din fișierele .md
Scanează: kb/, memory/, conversations/
Extrage titlu, dată, tags, și domenii (@work, @health, etc.) Extrage titlu, dată, tags, și domenii (@work, @health, etc.)
Scanează TOATE subdirectoarele din notes/ (youtube, retete, etc.)
""" """
import os import os
import re import re
import json import json
from pathlib import Path from pathlib import Path
from datetime import datetime
NOTES_ROOT = Path(__file__).parent.parent / "notes" BASE_DIR = Path(__file__).parent.parent
INDEX_FILE = NOTES_ROOT / "index.json" KB_ROOT = BASE_DIR / "kb"
MEMORY_DIR = BASE_DIR / "memory"
# Subdirectoare de scanat (adaugă altele aici) CONVERSATIONS_DIR = BASE_DIR / "conversations"
SCAN_DIRS = ['youtube', 'retete'] INDEX_FILE = KB_ROOT / "index.json"
# Domenii de agenți # Domenii de agenți
VALID_DOMAINS = ['work', 'health', 'growth', 'sprijin', 'scout'] VALID_DOMAINS = ['work', 'health', 'growth', 'sprijin', 'scout']
def extract_metadata(filepath): # Tipuri speciale (pentru grup-sprijin etc.)
VALID_TYPES = ['exercitiu', 'meditatie', 'reflectie', 'intrebare', 'fisa', 'project', 'memory', 'conversation', 'coaching']
# Cache for rules files
_rules_cache = {}
def load_rules(filepath):
"""Încarcă regulile din .rules.json din directorul fișierului sau părinți"""
dir_path = filepath.parent
# Check cache
if str(dir_path) in _rules_cache:
return _rules_cache[str(dir_path)]
# Look for .rules.json in current dir and parents (up to kb/)
rules = {
"defaultDomains": [],
"defaultTypes": [],
"defaultTags": [],
"inferTypeFromFilename": False,
"filenameTypeMap": {}
}
# Collect rules from all levels (child rules override parent)
rules_chain = []
current = dir_path
while current >= KB_ROOT:
rules_file = current / ".rules.json"
if rules_file.exists():
try:
with open(rules_file, 'r', encoding='utf-8') as f:
rules_chain.insert(0, json.load(f)) # Parent first
except:
pass
current = current.parent
# Merge rules (child overrides parent)
for r in rules_chain:
for key in rules:
if key in r:
if isinstance(rules[key], list):
# Extend lists (don't override)
rules[key] = list(set(rules[key] + r[key]))
else:
rules[key] = r[key]
_rules_cache[str(dir_path)] = rules
return rules
def extract_metadata(filepath, category, subcategory=None):
"""Extrage metadata din fișierul markdown""" """Extrage metadata din fișierul markdown"""
with open(filepath, 'r', encoding='utf-8') as f: with open(filepath, 'r', encoding='utf-8') as f:
content = f.read() content = f.read()
@@ -31,6 +81,7 @@ def extract_metadata(filepath):
# Extrage tags (linia cu **Tags:** sau tags:) # Extrage tags (linia cu **Tags:** sau tags:)
tags = [] tags = []
domains = [] domains = []
types = []
tags_match = re.search(r'\*\*Tags?:\*\*\s*(.+)$|^Tags?:\s*(.+)$', content, re.MULTILINE | re.IGNORECASE) tags_match = re.search(r'\*\*Tags?:\*\*\s*(.+)$|^Tags?:\s*(.+)$', content, re.MULTILINE | re.IGNORECASE)
if tags_match: if tags_match:
tags_str = tags_match.group(1) or tags_match.group(2) tags_str = tags_match.group(1) or tags_match.group(2)
@@ -38,96 +89,199 @@ def extract_metadata(filepath):
# Extrage domenii (@work, @health, etc.) # Extrage domenii (@work, @health, etc.)
domain_matches = re.findall(r'@(\w+)', tags_str) domain_matches = re.findall(r'@(\w+)', tags_str)
domains = [d for d in domain_matches if d in VALID_DOMAINS] domains = [d for d in domain_matches if d in VALID_DOMAINS]
types = [d for d in domain_matches if d in VALID_TYPES]
# Extrage tags normale (#tag) - exclude domeniile # Extrage tags normale (#tag)
all_tags = re.findall(r'#([\w-]+)', tags_str) all_tags = re.findall(r'#([\w-]+)', tags_str)
tags = [t for t in all_tags if t not in VALID_DOMAINS] tags = [t for t in all_tags if t not in VALID_DOMAINS and t not in VALID_TYPES]
# Extrage data din filename (YYYY-MM-DD_slug.md) # Aplică reguli din .rules.json (dacă există)
date_match = re.match(r'(\d{4}-\d{2}-\d{2})_', filepath.name) rules = load_rules(filepath)
# Adaugă domains implicite (dacă nu sunt deja)
for d in rules.get("defaultDomains", []):
if d not in domains:
domains.append(d)
# Adaugă types implicite
for t in rules.get("defaultTypes", []):
if t not in types:
types.append(t)
# Adaugă tags implicite
for t in rules.get("defaultTags", []):
if t not in tags:
tags.append(t)
# Inferă type din filename (dacă e configurat)
if rules.get("inferTypeFromFilename"):
filename_lower = filepath.stem.lower()
for pattern, type_name in rules.get("filenameTypeMap", {}).items():
if pattern in filename_lower and type_name not in types:
types.append(type_name)
break
# Extrage data din filename (YYYY-MM-DD_slug.md sau YYYY-MM-DD.md)
date_match = re.match(r'(\d{4}-\d{2}-\d{2})', filepath.name)
date = date_match.group(1) if date_match else "" date = date_match.group(1) if date_match else ""
# Pentru fișiere fără dată în nume, folosește mtime
if not date:
mtime = filepath.stat().st_mtime
date = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d')
# Extrage video URL # Extrage video URL
video_match = re.search(r'\*\*(?:Video|Link):\*\*\s*(https?://[^\s]+)', content) video_match = re.search(r'\*\*(?:Video|Link):\*\*\s*(https?://[^\s]+)', content)
video_url = video_match.group(1) if video_match else "" video_url = video_match.group(1) if video_match else ""
# Extrage TL;DR (primele 200 caractere) # Extrage TL;DR sau primele 200 caractere de conținut
tldr_match = re.search(r'##\s*📋?\s*TL;DR\s*\n+(.+?)(?=\n##|\n---|\Z)', content, re.DOTALL)
tldr = "" tldr = ""
tldr_match = re.search(r'##\s*📋?\s*TL;DR\s*\n+(.+?)(?=\n##|\n---|\Z)', content, re.DOTALL)
if tldr_match: if tldr_match:
tldr = tldr_match.group(1).strip()[:200] tldr = tldr_match.group(1).strip()[:200]
if len(tldr_match.group(1).strip()) > 200: else:
# Fallback: primul paragraf după titlu
para_match = re.search(r'^#.+\n+(.+?)(?=\n\n|\n#|\Z)', content, re.DOTALL)
if para_match:
tldr = para_match.group(1).strip()[:200]
if len(tldr) >= 200:
tldr += "..." tldr += "..."
# Construiește path-ul relativ pentru web (din dashboard/)
# Dashboard are symlinks: notes-data -> ../kb, memory -> ../memory, conversations -> ../conversations
rel_path = str(filepath.relative_to(BASE_DIR))
# Transformă kb/... în notes-data/... pentru web
if rel_path.startswith('kb/'):
rel_path = 'notes-data/' + rel_path[3:]
return { return {
"file": filepath.name, "file": rel_path,
"title": title, "title": title,
"date": date, "date": date,
"tags": tags, "tags": tags,
"domains": domains, "domains": domains,
"types": types,
"category": category,
"project": subcategory, # primul nivel sub projects/ (grup-sprijin, vending-master)
"subdir": None, # se setează în scan_directory pentru niveluri mai adânci
"video": video_url, "video": video_url,
"tldr": tldr "tldr": tldr
} }
def generate_index(): def scan_directory(dir_path, category, subcategory=None, recursive=False):
"""Generează index.json din toate fișierele .md din toate subdirectoarele""" """Scanează un director pentru fișiere .md"""
notes = [] notes = []
# Stats per domeniu if not dir_path.exists():
domain_stats = {d: 0 for d in VALID_DOMAINS} return notes
# Stats per categorie
category_stats = {}
for subdir in SCAN_DIRS: # Defaults pentru categorii speciale (memory/, conversations/)
notes_dir = NOTES_ROOT / subdir category_defaults = {
if not notes_dir.exists(): "memory": {"types": ["memory"], "domains": []},
print(f" (skipping {subdir}/ - not found)") "conversations": {"types": ["conversation"], "domains": []}
continue }
print(f"Scanning notes/{subdir}/...") if recursive:
category_stats[subdir] = 0 # Scanează recursiv
for filepath in dir_path.rglob("*.md"):
for filepath in sorted(notes_dir.glob("*.md"), reverse=True): if filepath.name.startswith('.') or 'template' in filepath.name.lower():
if filepath.name == 'index.json':
continue continue
try: try:
metadata = extract_metadata(filepath) # Determină project și subdir din path
# Adaugă categoria (subdirectorul) # Ex: projects/grup-sprijin/biblioteca/file.md
metadata['category'] = subdir # project = grup-sprijin, subdir = biblioteca
# Modifică path-ul fișierului să includă subdirectorul rel_to_dir = filepath.relative_to(dir_path)
metadata['file'] = f"{subdir}/{filepath.name}" parts = rel_to_dir.parts[:-1] # exclude filename
notes.append(metadata)
# Update stats project = parts[0] if len(parts) > 0 else None
category_stats[subdir] += 1 subdir = parts[1] if len(parts) > 1 else None
for d in metadata['domains']:
metadata = extract_metadata(filepath, category, project)
metadata['subdir'] = subdir
notes.append(metadata)
except Exception as e:
print(f" ! Error processing {filepath}: {e}")
else:
# Scanează doar fișierele din director (nu subdirectoare)
for filepath in sorted(dir_path.glob("*.md"), reverse=True):
if filepath.name.startswith('.') or 'template' in filepath.name.lower():
continue
try:
metadata = extract_metadata(filepath, category, subcategory)
# Aplică defaults pentru categoria specială
if category in category_defaults:
defaults = category_defaults[category]
for t in defaults.get("types", []):
if t not in metadata["types"]:
metadata["types"].append(t)
for d in defaults.get("domains", []):
if d not in metadata["domains"]:
metadata["domains"].append(d)
notes.append(metadata)
except Exception as e:
print(f" ! Error processing {filepath}: {e}")
return notes
def generate_index():
"""Generează index.json din toate sursele"""
all_notes = []
# Stats
domain_stats = {d: 0 for d in VALID_DOMAINS}
category_stats = {}
# Scanează TOATE subdirectoarele din kb/ recursiv
print("Scanning kb/ (all subdirectories)...")
for subdir in sorted(KB_ROOT.iterdir()):
if subdir.is_dir() and not subdir.name.startswith('.'):
category = subdir.name
print(f" [{category}]")
notes = scan_directory(subdir, category, recursive=True)
all_notes.extend(notes)
category_stats[category] = len(notes)
for n in notes:
sub = f"/{n['subcategory']}" if n.get('subcategory') else ""
print(f" + {n['title'][:42]}...")
for d in n['domains']:
domain_stats[d] += 1 domain_stats[d] += 1
domains_str = ' '.join([f'@{d}' for d in metadata['domains']]) if metadata['domains'] else '' # 4. Scanează memory/
print(f" + {metadata['title'][:40]}... {domains_str}") print("Scanning memory/...")
except Exception as e: memory_notes = scan_directory(MEMORY_DIR, "memory")
print(f" ! Error processing {filepath.name}: {e}") all_notes.extend(memory_notes)
category_stats["memory"] = len(memory_notes)
for n in memory_notes:
print(f" + {n['title'][:45]}...")
# 5. Scanează conversations/
print("Scanning conversations/...")
conv_notes = scan_directory(CONVERSATIONS_DIR, "conversations")
all_notes.extend(conv_notes)
category_stats["conversations"] = len(conv_notes)
for n in conv_notes:
print(f" + {n['title'][:45]}...")
# Sortează după dată descrescător # Sortează după dată descrescător
notes.sort(key=lambda x: x['date'], reverse=True) all_notes.sort(key=lambda x: x['date'], reverse=True)
# Adaugă metadata globală # Adaugă metadata globală
output = { output = {
"notes": notes, "notes": all_notes,
"stats": { "stats": {
"total": len(notes), "total": len(all_notes),
"by_domain": domain_stats, "by_domain": domain_stats,
"by_category": category_stats "by_category": category_stats
}, },
"domains": VALID_DOMAINS, "domains": VALID_DOMAINS,
"categories": SCAN_DIRS "types": VALID_TYPES,
"categories": list(category_stats.keys())
} }
with open(INDEX_FILE, 'w', encoding='utf-8') as f: with open(INDEX_FILE, 'w', encoding='utf-8') as f:
json.dump(output, f, indent=2, ensure_ascii=False) json.dump(output, f, indent=2, ensure_ascii=False)
print(f"\n✅ Generated {INDEX_FILE} with {len(notes)} notes") print(f"\n✅ Generated {INDEX_FILE} with {len(all_notes)} notes")
print(f" Domains: {domain_stats}")
print(f" Categories: {category_stats}") print(f" Categories: {category_stats}")
return output return output