Update dashboard, memory, root (+7 ~14)
This commit is contained in:
17
TOOLS.md
17
TOOLS.md
@@ -85,6 +85,23 @@
|
||||
**Ollama:** LXC 104 - 10.0.20.161:11434 (all-minilm embeddings)
|
||||
**Detalii:** memory/kb/tools/infrastructure.md
|
||||
|
||||
### Content Discovery
|
||||
- **Script:** `python3 tools/content_discovery.py`
|
||||
- **Utilizare:** Căutare automată de conținut bazată pe interese (YouTube, articole)
|
||||
- **Job:** content-discovery (02:00 București)
|
||||
|
||||
### Email Check
|
||||
- **Script:** `python3 tools/email_check.py`
|
||||
- **Utilizare:** IMAP inbox checker pentru moltbot@romfast.ro, returnează emailuri necitite ca JSON
|
||||
|
||||
### Generate PDF
|
||||
- **Script:** `python3 tools/generate_pdf.py`
|
||||
- **Utilizare:** Generează PDF din markdown content (output binary la stdout)
|
||||
|
||||
### Calendar Auth
|
||||
- **Script:** `python3 tools/calendar_auth.py`
|
||||
- **Utilizare:** OAuth2 authorization one-time setup pentru Google Calendar token
|
||||
|
||||
## Unelte per domeniu
|
||||
|
||||
### Sprijin (grup joi)
|
||||
|
||||
281
dashboard/api.py
281
dashboard/api.py
@@ -28,6 +28,12 @@ KANBAN_DIR = BASE_DIR / 'dashboard'
|
||||
WORKSPACE_DIR = Path('/home/moltbot/workspace')
|
||||
HABITS_FILE = KANBAN_DIR / 'habits.json'
|
||||
|
||||
# Eco (echo-core) constants
|
||||
ECO_SERVICES = ['echo-core', 'echo-whatsapp-bridge', 'echo-taskboard']
|
||||
ECHO_CORE_DIR = Path('/home/moltbot/echo-core')
|
||||
ECHO_LOG_FILE = ECHO_CORE_DIR / 'logs' / 'echo-core.log'
|
||||
ECHO_SESSIONS_FILE = ECHO_CORE_DIR / 'sessions' / 'active.json'
|
||||
|
||||
# Load .env file if present
|
||||
_env_file = Path(__file__).parent / '.env'
|
||||
if _env_file.exists():
|
||||
@@ -70,6 +76,12 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
self.handle_workspace_git_push()
|
||||
elif self.path == '/api/workspace/delete':
|
||||
self.handle_workspace_delete()
|
||||
elif self.path == '/api/eco/restart':
|
||||
self.handle_eco_restart()
|
||||
elif self.path == '/api/eco/stop':
|
||||
self.handle_eco_stop()
|
||||
elif self.path == '/api/eco/sessions/clear':
|
||||
self.handle_eco_sessions_clear()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
@@ -289,6 +301,12 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
self.handle_workspace_git_diff()
|
||||
elif self.path.startswith('/api/workspace/logs'):
|
||||
self.handle_workspace_logs()
|
||||
elif self.path == '/api/eco/status' or self.path.startswith('/api/eco/status?'):
|
||||
self.handle_eco_status()
|
||||
elif self.path.startswith('/api/eco/logs'):
|
||||
self.handle_eco_logs()
|
||||
elif self.path == '/api/eco/doctor':
|
||||
self.handle_eco_doctor()
|
||||
elif self.path.startswith('/api/'):
|
||||
self.send_error(404)
|
||||
else:
|
||||
@@ -1949,6 +1967,269 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
# ── Eco (echo-core) handlers ──────────────────────────────────────
|
||||
|
||||
def handle_eco_status(self):
|
||||
"""Get status of echo-core services + active sessions."""
|
||||
try:
|
||||
services = []
|
||||
for svc in ECO_SERVICES:
|
||||
info = {'name': svc, 'active': False, 'pid': None, 'uptime': None, 'memory': None}
|
||||
|
||||
result = subprocess.run(
|
||||
['systemctl', '--user', 'is-active', svc],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
info['active'] = result.stdout.strip() == 'active'
|
||||
|
||||
if info['active']:
|
||||
# PID
|
||||
result = subprocess.run(
|
||||
['systemctl', '--user', 'show', '-p', 'MainPID', '--value', svc],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
pid = result.stdout.strip()
|
||||
if pid and pid != '0':
|
||||
info['pid'] = int(pid)
|
||||
|
||||
# Uptime via systemctl timestamp
|
||||
try:
|
||||
r = subprocess.run(
|
||||
['systemctl', '--user', 'show', '-p', 'ActiveEnterTimestamp', '--value', svc],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
ts = r.stdout.strip()
|
||||
if ts:
|
||||
start = datetime.strptime(ts, '%a %Y-%m-%d %H:%M:%S %Z')
|
||||
info['uptime'] = int((datetime.utcnow() - start).total_seconds())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Memory (VmRSS from /proc)
|
||||
try:
|
||||
for line in Path(f'/proc/{pid}/status').read_text().splitlines():
|
||||
if line.startswith('VmRSS:'):
|
||||
info['memory'] = line.split(':')[1].strip()
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
services.append(info)
|
||||
|
||||
# Active sessions
|
||||
sessions = []
|
||||
if ECHO_SESSIONS_FILE.exists():
|
||||
try:
|
||||
sessions = json.loads(ECHO_SESSIONS_FILE.read_text())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.send_json({'services': services, 'sessions': sessions})
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def handle_eco_restart(self):
|
||||
"""Restart an echo-core service (not taskboard)."""
|
||||
try:
|
||||
data = self._read_post_json()
|
||||
svc = data.get('service', '')
|
||||
|
||||
if svc not in ECO_SERVICES:
|
||||
self.send_json({'success': False, 'error': f'Unknown service: {svc}'}, 400)
|
||||
return
|
||||
if svc == 'echo-taskboard':
|
||||
self.send_json({'success': False, 'error': 'Cannot restart taskboard from itself'}, 400)
|
||||
return
|
||||
|
||||
result = subprocess.run(
|
||||
['systemctl', '--user', 'restart', svc],
|
||||
capture_output=True, text=True, timeout=30
|
||||
)
|
||||
if result.returncode == 0:
|
||||
self.send_json({'success': True, 'message': f'{svc} restarted'})
|
||||
else:
|
||||
self.send_json({'success': False, 'error': result.stderr.strip()}, 500)
|
||||
except Exception as e:
|
||||
self.send_json({'success': False, 'error': str(e)}, 500)
|
||||
|
||||
def handle_eco_stop(self):
|
||||
"""Stop an echo-core service (not taskboard)."""
|
||||
try:
|
||||
data = self._read_post_json()
|
||||
svc = data.get('service', '')
|
||||
|
||||
if svc not in ECO_SERVICES:
|
||||
self.send_json({'success': False, 'error': f'Unknown service: {svc}'}, 400)
|
||||
return
|
||||
if svc == 'echo-taskboard':
|
||||
self.send_json({'success': False, 'error': 'Cannot stop taskboard from itself'}, 400)
|
||||
return
|
||||
|
||||
result = subprocess.run(
|
||||
['systemctl', '--user', 'stop', svc],
|
||||
capture_output=True, text=True, timeout=30
|
||||
)
|
||||
if result.returncode == 0:
|
||||
self.send_json({'success': True, 'message': f'{svc} stopped'})
|
||||
else:
|
||||
self.send_json({'success': False, 'error': result.stderr.strip()}, 500)
|
||||
except Exception as e:
|
||||
self.send_json({'success': False, 'error': str(e)}, 500)
|
||||
|
||||
def handle_eco_logs(self):
|
||||
"""Return last N lines from echo-core.log."""
|
||||
try:
|
||||
params = parse_qs(urlparse(self.path).query)
|
||||
lines = min(int(params.get('lines', ['100'])[0]), 500)
|
||||
|
||||
if not ECHO_LOG_FILE.exists():
|
||||
self.send_json({'lines': ['(log file not found)']})
|
||||
return
|
||||
|
||||
result = subprocess.run(
|
||||
['tail', '-n', str(lines), str(ECHO_LOG_FILE)],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
self.send_json({'lines': result.stdout.splitlines()})
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def handle_eco_doctor(self):
|
||||
"""Run health checks on echo-core ecosystem."""
|
||||
checks = []
|
||||
|
||||
# 1. Services
|
||||
for svc in ECO_SERVICES:
|
||||
try:
|
||||
r = subprocess.run(
|
||||
['systemctl', '--user', 'is-active', svc],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
active = r.stdout.strip() == 'active'
|
||||
checks.append({
|
||||
'name': f'Service: {svc}',
|
||||
'pass': active,
|
||||
'detail': 'active' if active else r.stdout.strip()
|
||||
})
|
||||
except Exception as e:
|
||||
checks.append({'name': f'Service: {svc}', 'pass': False, 'detail': str(e)})
|
||||
|
||||
# 2. Disk space
|
||||
try:
|
||||
st = shutil.disk_usage('/')
|
||||
pct_free = (st.free / st.total) * 100
|
||||
checks.append({
|
||||
'name': 'Disk space',
|
||||
'pass': pct_free > 5,
|
||||
'detail': f'{pct_free:.1f}% free ({st.free // (1024**3)} GB)'
|
||||
})
|
||||
except Exception as e:
|
||||
checks.append({'name': 'Disk space', 'pass': False, 'detail': str(e)})
|
||||
|
||||
# 3. Log file
|
||||
try:
|
||||
if ECHO_LOG_FILE.exists():
|
||||
size = ECHO_LOG_FILE.stat().st_size
|
||||
size_mb = size / (1024 * 1024)
|
||||
checks.append({
|
||||
'name': 'Log file',
|
||||
'pass': size_mb < 100,
|
||||
'detail': f'{size_mb:.1f} MB'
|
||||
})
|
||||
else:
|
||||
checks.append({'name': 'Log file', 'pass': False, 'detail': 'Not found'})
|
||||
except Exception as e:
|
||||
checks.append({'name': 'Log file', 'pass': False, 'detail': str(e)})
|
||||
|
||||
# 4. Sessions file
|
||||
try:
|
||||
if ECHO_SESSIONS_FILE.exists():
|
||||
data = json.loads(ECHO_SESSIONS_FILE.read_text())
|
||||
count = len(data) if isinstance(data, list) else len(data.keys()) if isinstance(data, dict) else 0
|
||||
checks.append({'name': 'Sessions file', 'pass': True, 'detail': f'{count} active'})
|
||||
else:
|
||||
checks.append({'name': 'Sessions file', 'pass': False, 'detail': 'Not found'})
|
||||
except Exception as e:
|
||||
checks.append({'name': 'Sessions file', 'pass': False, 'detail': str(e)})
|
||||
|
||||
# 5. Config
|
||||
config_file = ECHO_CORE_DIR / 'config.json'
|
||||
try:
|
||||
if config_file.exists():
|
||||
json.loads(config_file.read_text())
|
||||
checks.append({'name': 'Config', 'pass': True, 'detail': 'Valid JSON'})
|
||||
else:
|
||||
checks.append({'name': 'Config', 'pass': False, 'detail': 'Not found'})
|
||||
except Exception as e:
|
||||
checks.append({'name': 'Config', 'pass': False, 'detail': str(e)})
|
||||
|
||||
# 6. WhatsApp bridge log
|
||||
wa_log = ECHO_CORE_DIR / 'logs' / 'whatsapp-bridge.log'
|
||||
try:
|
||||
if wa_log.exists():
|
||||
# Check last line for errors
|
||||
r = subprocess.run(
|
||||
['tail', '-1', str(wa_log)],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
last = r.stdout.strip()
|
||||
has_error = 'error' in last.lower() or 'fatal' in last.lower()
|
||||
checks.append({
|
||||
'name': 'WhatsApp bridge log',
|
||||
'pass': not has_error,
|
||||
'detail': last[:80] if last else 'Empty'
|
||||
})
|
||||
else:
|
||||
checks.append({'name': 'WhatsApp bridge log', 'pass': False, 'detail': 'Not found'})
|
||||
except Exception as e:
|
||||
checks.append({'name': 'WhatsApp bridge log', 'pass': False, 'detail': str(e)})
|
||||
|
||||
# 7. Claude CLI
|
||||
try:
|
||||
r = subprocess.run(
|
||||
['which', 'claude'],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
found = r.returncode == 0
|
||||
checks.append({
|
||||
'name': 'Claude CLI',
|
||||
'pass': found,
|
||||
'detail': r.stdout.strip() if found else 'Not in PATH'
|
||||
})
|
||||
except Exception as e:
|
||||
checks.append({'name': 'Claude CLI', 'pass': False, 'detail': str(e)})
|
||||
|
||||
self.send_json({'checks': checks})
|
||||
|
||||
def handle_eco_sessions_clear(self):
|
||||
"""Clear active sessions (all or specific channel)."""
|
||||
try:
|
||||
data = self._read_post_json()
|
||||
channel = data.get('channel', None)
|
||||
|
||||
if not ECHO_SESSIONS_FILE.exists():
|
||||
self.send_json({'success': True, 'message': 'No sessions file'})
|
||||
return
|
||||
|
||||
if channel:
|
||||
# Remove specific channel
|
||||
sessions = json.loads(ECHO_SESSIONS_FILE.read_text())
|
||||
if isinstance(sessions, list):
|
||||
sessions = [s for s in sessions if s.get('channel') != channel]
|
||||
elif isinstance(sessions, dict):
|
||||
sessions.pop(channel, None)
|
||||
ECHO_SESSIONS_FILE.write_text(json.dumps(sessions, indent=2))
|
||||
self.send_json({'success': True, 'message': f'Cleared session: {channel}'})
|
||||
else:
|
||||
# Clear all
|
||||
if isinstance(json.loads(ECHO_SESSIONS_FILE.read_text()), list):
|
||||
ECHO_SESSIONS_FILE.write_text('[]')
|
||||
else:
|
||||
ECHO_SESSIONS_FILE.write_text('{}')
|
||||
self.send_json({'success': True, 'message': 'All sessions cleared'})
|
||||
except Exception as e:
|
||||
self.send_json({'success': False, 'error': str(e)}, 500)
|
||||
|
||||
def send_json(self, data, code=200):
|
||||
self.send_response(code)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
|
||||
821
dashboard/eco.html
Normal file
821
dashboard/eco.html
Normal file
@@ -0,0 +1,821 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ro">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/svg+xml" href="/echo/favicon.svg">
|
||||
<title>Echo · Eco</title>
|
||||
<link rel="stylesheet" href="/echo/common.css">
|
||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
||||
<script src="/echo/swipe-nav.js"></script>
|
||||
<style>
|
||||
.main {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-muted);
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.section {
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
/* Service cards */
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.service-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-4);
|
||||
transition: border-color var(--transition-base);
|
||||
}
|
||||
|
||||
.service-card:hover {
|
||||
border-color: var(--border-focus);
|
||||
}
|
||||
|
||||
.service-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.service-name {
|
||||
font-size: var(--text-base);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-dot.active {
|
||||
background: var(--success);
|
||||
box-shadow: 0 0 6px rgba(34, 197, 94, 0.5);
|
||||
}
|
||||
|
||||
.status-dot.inactive {
|
||||
background: var(--error);
|
||||
}
|
||||
|
||||
.service-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.service-meta span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.service-actions {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.service-actions .btn {
|
||||
font-size: var(--text-xs);
|
||||
padding: var(--space-1) var(--space-3);
|
||||
}
|
||||
|
||||
/* Sessions table */
|
||||
.sessions-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.sessions-table th,
|
||||
.sessions-table td {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.sessions-table th {
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
font-size: var(--text-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.sessions-table td {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.sessions-empty {
|
||||
text-align: center;
|
||||
padding: var(--space-5);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.sessions-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Log viewer */
|
||||
.log-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.log-toolbar label {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.log-toolbar select {
|
||||
background: var(--bg-elevated);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-primary);
|
||||
font-size: var(--text-xs);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
}
|
||||
|
||||
.log-content {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-secondary);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
padding: var(--space-4);
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
background: var(--bg-elevated);
|
||||
}
|
||||
|
||||
/* Auto-refresh toggle */
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: var(--bg-surface-active);
|
||||
border-radius: 20px;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.toggle-slider:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background: var(--text-muted);
|
||||
border-radius: 50%;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.toggle-switch input:checked + .toggle-slider {
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.toggle-switch input:checked + .toggle-slider:before {
|
||||
transform: translateX(16px);
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Doctor checks */
|
||||
.doctor-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.check-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.check-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.check-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.check-icon.pass { color: var(--success); }
|
||||
.check-icon.fail { color: var(--error); }
|
||||
|
||||
.check-name {
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.check-detail {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
/* Spinner */
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid var(--text-muted);
|
||||
border-top-color: var(--accent);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Button variants */
|
||||
.btn-danger {
|
||||
border-color: var(--error);
|
||||
color: var(--error);
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background: var(--error);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
border-color: var(--warning);
|
||||
color: var(--warning);
|
||||
}
|
||||
.btn-warning:hover {
|
||||
background: var(--warning);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.services-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.service-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
.check-item {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.check-name {
|
||||
min-width: auto;
|
||||
}
|
||||
.sessions-table {
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
.sessions-table th:nth-child(3),
|
||||
.sessions-table td:nth-child(3) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<a href="/echo/index.html" class="logo">
|
||||
<i data-lucide="circle-dot"></i>
|
||||
Echo
|
||||
</a>
|
||||
<nav class="nav">
|
||||
<a href="/echo/index.html" class="nav-item">
|
||||
<i data-lucide="layout-dashboard"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a href="/echo/workspace.html" class="nav-item">
|
||||
<i data-lucide="code"></i>
|
||||
<span>Workspace</span>
|
||||
</a>
|
||||
<a href="/echo/notes.html" class="nav-item">
|
||||
<i data-lucide="file-text"></i>
|
||||
<span>KB</span>
|
||||
</a>
|
||||
<a href="/echo/habits.html" class="nav-item">
|
||||
<i data-lucide="dumbbell"></i>
|
||||
<span>Habits</span>
|
||||
</a>
|
||||
<a href="/echo/files.html" class="nav-item">
|
||||
<i data-lucide="folder"></i>
|
||||
<span>Files</span>
|
||||
</a>
|
||||
<a href="/echo/eco.html" class="nav-item active">
|
||||
<i data-lucide="cpu"></i>
|
||||
<span>Eco</span>
|
||||
</a>
|
||||
<button class="theme-toggle" onclick="toggleTheme()" title="Schimba tema">
|
||||
<i data-lucide="sun" id="themeIcon"></i>
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="main">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Eco Control Panel</h1>
|
||||
<div class="page-subtitle" id="statusSummary">Loading...</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary" onclick="refreshAll()">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Services -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<i data-lucide="server" style="width:18px;height:18px;"></i>
|
||||
Services
|
||||
</h2>
|
||||
</div>
|
||||
<div class="services-grid" id="servicesGrid">
|
||||
<div style="color:var(--text-muted);">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sessions -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<i data-lucide="message-square" style="width:18px;height:18px;"></i>
|
||||
Sessions
|
||||
</h2>
|
||||
<button class="btn btn-secondary btn-danger" onclick="clearAllSessions()" id="clearAllBtn" style="display:none;">
|
||||
<i data-lucide="trash-2"></i>
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
<div class="sessions-card">
|
||||
<div id="sessionsContent">
|
||||
<div class="sessions-empty">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<i data-lucide="scroll-text" style="width:18px;height:18px;"></i>
|
||||
Logs
|
||||
</h2>
|
||||
</div>
|
||||
<div class="log-card">
|
||||
<div class="log-toolbar">
|
||||
<label>Lines:</label>
|
||||
<select id="logLines" onchange="loadLogs()">
|
||||
<option value="50">50</option>
|
||||
<option value="100" selected>100</option>
|
||||
<option value="200">200</option>
|
||||
</select>
|
||||
<button class="btn btn-secondary" onclick="loadLogs()" style="font-size:var(--text-xs);padding:var(--space-1) var(--space-2);">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
Refresh
|
||||
</button>
|
||||
<div style="margin-left:auto;display:flex;align-items:center;gap:var(--space-2);">
|
||||
<label style="font-size:var(--text-xs);color:var(--text-muted);">Auto</label>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="autoRefreshLogs" onchange="toggleAutoRefreshLogs()">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-content" id="logContent">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Doctor -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<i data-lucide="stethoscope" style="width:18px;height:18px;"></i>
|
||||
Doctor
|
||||
</h2>
|
||||
<button class="btn btn-primary" onclick="runDoctor()" id="doctorBtn">
|
||||
<i data-lucide="play"></i>
|
||||
Run Doctor
|
||||
</button>
|
||||
</div>
|
||||
<div class="doctor-card" id="doctorCard" style="display:none;">
|
||||
<ul class="check-list" id="doctorChecks"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
let statusRefreshInterval = null;
|
||||
let logRefreshInterval = null;
|
||||
|
||||
// Theme
|
||||
function toggleTheme() {
|
||||
const current = document.documentElement.getAttribute('data-theme');
|
||||
const next = current === 'light' ? 'dark' : 'light';
|
||||
document.documentElement.setAttribute('data-theme', next);
|
||||
localStorage.setItem('theme', next);
|
||||
}
|
||||
(function() {
|
||||
const saved = localStorage.getItem('theme');
|
||||
if (saved) document.documentElement.setAttribute('data-theme', saved);
|
||||
})();
|
||||
|
||||
function formatUptime(seconds) {
|
||||
if (!seconds && seconds !== 0) return '-';
|
||||
const d = Math.floor(seconds / 86400);
|
||||
const h = Math.floor((seconds % 86400) / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
if (d > 0) return `${d}d ${h}h`;
|
||||
if (h > 0) return `${h}h ${m}m`;
|
||||
return `${m}m`;
|
||||
}
|
||||
|
||||
function svcLabel(name) {
|
||||
return name.replace('echo-', '').replace('-', ' ');
|
||||
}
|
||||
|
||||
// ── Status ──────────────────────────────────────────────
|
||||
|
||||
async function loadStatus() {
|
||||
try {
|
||||
const res = await fetch('/echo/api/eco/status');
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
document.getElementById('servicesGrid').innerHTML =
|
||||
`<div style="color:var(--error);">${data.error}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
renderServices(data.services);
|
||||
renderSessions(data.sessions);
|
||||
|
||||
const active = data.services.filter(s => s.active).length;
|
||||
document.getElementById('statusSummary').textContent =
|
||||
`${active}/${data.services.length} services active`;
|
||||
} catch (e) {
|
||||
document.getElementById('servicesGrid').innerHTML =
|
||||
`<div style="color:var(--error);">Error: ${e.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderServices(services) {
|
||||
const grid = document.getElementById('servicesGrid');
|
||||
grid.innerHTML = services.map(svc => {
|
||||
const isTaskboard = svc.name === 'echo-taskboard';
|
||||
const canControl = !isTaskboard;
|
||||
|
||||
let actionsHtml = '';
|
||||
if (canControl) {
|
||||
if (svc.active) {
|
||||
actionsHtml = `
|
||||
<button class="btn btn-secondary" onclick="restartService('${svc.name}')">
|
||||
<i data-lucide="rotate-cw"></i> Restart
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-danger" onclick="stopService('${svc.name}')">
|
||||
<i data-lucide="square"></i> Stop
|
||||
</button>
|
||||
`;
|
||||
} else {
|
||||
actionsHtml = `
|
||||
<button class="btn btn-primary" onclick="restartService('${svc.name}')">
|
||||
<i data-lucide="play"></i> Start
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="service-card">
|
||||
<div class="service-header">
|
||||
<div class="service-name">
|
||||
<span class="status-dot ${svc.active ? 'active' : 'inactive'}"></span>
|
||||
${svcLabel(svc.name)}
|
||||
</div>
|
||||
<span style="font-size:var(--text-xs);color:${svc.active ? 'var(--success)' : 'var(--error)'};">
|
||||
${svc.active ? 'running' : 'stopped'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="service-meta">
|
||||
<span><i data-lucide="hash" style="width:12px;height:12px;"></i> PID: ${svc.pid || '-'}</span>
|
||||
<span><i data-lucide="clock" style="width:12px;height:12px;"></i> Uptime: ${formatUptime(svc.uptime)}</span>
|
||||
<span><i data-lucide="memory-stick" style="width:12px;height:12px;"></i> Memory: ${svc.memory || '-'}</span>
|
||||
</div>
|
||||
<div class="service-actions">
|
||||
${actionsHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
async function restartService(name) {
|
||||
try {
|
||||
const res = await fetch('/echo/api/eco/restart', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({service: name})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
setTimeout(loadStatus, 1500);
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Failed: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function stopService(name) {
|
||||
if (!confirm(`Stop ${svcLabel(name)}?`)) return;
|
||||
try {
|
||||
const res = await fetch('/echo/api/eco/stop', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({service: name})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
setTimeout(loadStatus, 1000);
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Failed: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Sessions ────────────────────────────────────────────
|
||||
|
||||
function renderSessions(sessions) {
|
||||
const container = document.getElementById('sessionsContent');
|
||||
const clearBtn = document.getElementById('clearAllBtn');
|
||||
|
||||
if (!sessions || sessions.length === 0) {
|
||||
container.innerHTML = '<div class="sessions-empty">No active sessions</div>';
|
||||
clearBtn.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
clearBtn.style.display = '';
|
||||
|
||||
// Handle both array and object formats
|
||||
let rows = [];
|
||||
if (Array.isArray(sessions)) {
|
||||
rows = sessions;
|
||||
} else if (typeof sessions === 'object') {
|
||||
rows = Object.entries(sessions).map(([k, v]) => ({
|
||||
channel: k,
|
||||
...(typeof v === 'object' ? v : {value: v})
|
||||
}));
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
container.innerHTML = '<div class="sessions-empty">No active sessions</div>';
|
||||
clearBtn.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<table class="sessions-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Channel</th>
|
||||
<th>Platform</th>
|
||||
<th>Started</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${rows.map(s => `
|
||||
<tr>
|
||||
<td>${escapeHtml(s.channel || s.id || '-')}</td>
|
||||
<td>${escapeHtml(s.platform || s.type || '-')}</td>
|
||||
<td>${s.started || s.timestamp || '-'}</td>
|
||||
<td>
|
||||
<button class="btn btn-secondary btn-danger" style="font-size:var(--text-xs);padding:2px 8px;"
|
||||
onclick="clearSession('${escapeHtml(s.channel || s.id || '')}')">
|
||||
<i data-lucide="x"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
async function clearSession(channel) {
|
||||
try {
|
||||
const res = await fetch('/echo/api/eco/sessions/clear', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({channel: channel})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) loadStatus();
|
||||
else alert('Error: ' + data.error);
|
||||
} catch (e) {
|
||||
alert('Failed: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function clearAllSessions() {
|
||||
if (!confirm('Clear all active sessions?')) return;
|
||||
try {
|
||||
const res = await fetch('/echo/api/eco/sessions/clear', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) loadStatus();
|
||||
else alert('Error: ' + data.error);
|
||||
} catch (e) {
|
||||
alert('Failed: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Logs ────────────────────────────────────────────────
|
||||
|
||||
async function loadLogs() {
|
||||
const lines = document.getElementById('logLines').value;
|
||||
const content = document.getElementById('logContent');
|
||||
|
||||
try {
|
||||
const res = await fetch(`/echo/api/eco/logs?lines=${lines}`);
|
||||
const data = await res.json();
|
||||
content.textContent = (data.lines || []).join('\n') || '(empty)';
|
||||
content.scrollTop = content.scrollHeight;
|
||||
} catch (e) {
|
||||
content.textContent = 'Error: ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAutoRefreshLogs() {
|
||||
const enabled = document.getElementById('autoRefreshLogs').checked;
|
||||
if (logRefreshInterval) {
|
||||
clearInterval(logRefreshInterval);
|
||||
logRefreshInterval = null;
|
||||
}
|
||||
if (enabled) {
|
||||
logRefreshInterval = setInterval(loadLogs, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Doctor ──────────────────────────────────────────────
|
||||
|
||||
async function runDoctor() {
|
||||
const card = document.getElementById('doctorCard');
|
||||
const list = document.getElementById('doctorChecks');
|
||||
const btn = document.getElementById('doctorBtn');
|
||||
|
||||
card.style.display = '';
|
||||
list.innerHTML = '<li class="check-item"><span class="spinner"></span> Running checks...</li>';
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const res = await fetch('/echo/api/eco/doctor');
|
||||
const data = await res.json();
|
||||
|
||||
list.innerHTML = data.checks.map(c => `
|
||||
<li class="check-item">
|
||||
<span class="check-icon ${c.pass ? 'pass' : 'fail'}">
|
||||
${c.pass ? '✓' : '✗'}
|
||||
</span>
|
||||
<span class="check-name">${escapeHtml(c.name)}</span>
|
||||
<span class="check-detail">${escapeHtml(c.detail)}</span>
|
||||
</li>
|
||||
`).join('');
|
||||
} catch (e) {
|
||||
list.innerHTML = `<li class="check-item" style="color:var(--error);">Error: ${escapeHtml(e.message)}</li>`;
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function refreshAll() {
|
||||
loadStatus();
|
||||
loadLogs();
|
||||
}
|
||||
|
||||
// ── Init ────────────────────────────────────────────────
|
||||
|
||||
loadStatus();
|
||||
loadLogs();
|
||||
lucide.createIcons();
|
||||
|
||||
// Auto-refresh status every 10s
|
||||
statusRefreshInterval = setInterval(loadStatus, 10000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -854,6 +854,10 @@
|
||||
<i data-lucide="folder"></i>
|
||||
<span>Files</span>
|
||||
</a>
|
||||
<a href="/echo/eco.html" class="nav-item">
|
||||
<i data-lucide="cpu"></i>
|
||||
<span>Eco</span>
|
||||
</a>
|
||||
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
|
||||
<i data-lucide="sun" id="themeIcon"></i>
|
||||
</button>
|
||||
|
||||
@@ -1458,6 +1458,10 @@
|
||||
<i data-lucide="folder"></i>
|
||||
<span>Files</span>
|
||||
</a>
|
||||
<a href="/echo/eco.html" class="nav-item">
|
||||
<i data-lucide="cpu"></i>
|
||||
<span>Eco</span>
|
||||
</a>
|
||||
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
|
||||
<i data-lucide="sun" id="themeIcon"></i>
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"lastUpdated": "2026-02-12T10:54:25.858148",
|
||||
"lastUpdated": "2026-02-14T06:36:41.910996",
|
||||
"habits": [
|
||||
{
|
||||
"id": "95c15eef-3a14-4985-a61e-0b64b72851b0",
|
||||
@@ -15,19 +15,27 @@
|
||||
"count": 5
|
||||
},
|
||||
"streak": {
|
||||
"current": 0,
|
||||
"current": 2,
|
||||
"best": 2,
|
||||
"lastCheckIn": "2026-02-12"
|
||||
"lastCheckIn": "2026-02-14"
|
||||
},
|
||||
"lives": 0,
|
||||
"completions": [
|
||||
{
|
||||
"date": "2026-02-11",
|
||||
"type": "check"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-13",
|
||||
"type": "check"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-14",
|
||||
"type": "check"
|
||||
}
|
||||
],
|
||||
"createdAt": "2026-02-11T00:54:03.447063",
|
||||
"updatedAt": "2026-02-12T10:54:25.858148"
|
||||
"updatedAt": "2026-02-14T06:36:41.910996"
|
||||
},
|
||||
{
|
||||
"id": "ceddaa7e-caf9-4038-94bb-da486c586bf8",
|
||||
@@ -43,19 +51,27 @@
|
||||
"count": 3
|
||||
},
|
||||
"streak": {
|
||||
"current": 1,
|
||||
"best": 1,
|
||||
"lastCheckIn": "2026-02-11"
|
||||
"current": 2,
|
||||
"best": 2,
|
||||
"lastCheckIn": "2026-02-14"
|
||||
},
|
||||
"lives": 2,
|
||||
"completions": [
|
||||
{
|
||||
"date": "2026-02-11",
|
||||
"type": "check"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-13",
|
||||
"type": "check"
|
||||
},
|
||||
{
|
||||
"date": "2026-02-14",
|
||||
"type": "check"
|
||||
}
|
||||
],
|
||||
"createdAt": "2026-02-11T01:58:44.779904",
|
||||
"updatedAt": "2026-02-11T22:07:48.431522"
|
||||
"updatedAt": "2026-02-14T06:36:28.866187"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1073,6 +1073,10 @@
|
||||
<i data-lucide="folder"></i>
|
||||
<span>Files</span>
|
||||
</a>
|
||||
<a href="/echo/eco.html" class="nav-item">
|
||||
<i data-lucide="cpu"></i>
|
||||
<span>Eco</span>
|
||||
</a>
|
||||
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
|
||||
<i data-lucide="sun" id="themeIcon"></i>
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"lastUpdated": "2026-02-13T13:03:45.357Z",
|
||||
"lastUpdated": "2026-02-13T23:06:16.571Z",
|
||||
"programs": [
|
||||
"ROACONT",
|
||||
"ROAGEST",
|
||||
@@ -21,9 +21,10 @@
|
||||
"program": "ROACONT",
|
||||
"owner": "robert",
|
||||
"priority": "important",
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"created": "2026-02-12T13:19:01.786Z",
|
||||
"deadline": null
|
||||
"deadline": null,
|
||||
"completed": "2026-02-13T23:06:16.567Z"
|
||||
},
|
||||
{
|
||||
"id": "ROA-002",
|
||||
|
||||
@@ -704,6 +704,10 @@
|
||||
<i data-lucide="folder"></i>
|
||||
<span>Files</span>
|
||||
</a>
|
||||
<a href="/echo/eco.html" class="nav-item">
|
||||
<i data-lucide="cpu"></i>
|
||||
<span>Eco</span>
|
||||
</a>
|
||||
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
|
||||
<i data-lucide="sun" id="themeIcon"></i>
|
||||
</button>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Swipe left/right to navigate between pages
|
||||
*/
|
||||
(function() {
|
||||
const pages = ['index.html', 'notes.html', 'habits.html', 'files.html', 'workspace.html'];
|
||||
const pages = ['index.html', 'eco.html', 'notes.html', 'habits.html', 'files.html', 'workspace.html'];
|
||||
|
||||
// Get current page index
|
||||
function getCurrentIndex() {
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
{
|
||||
"lastUpdated": "2026-02-13T13:03:30.654Z",
|
||||
"lastUpdated": "2026-02-14T07:00:00.000Z",
|
||||
"items": [
|
||||
{
|
||||
"id": "prov-2026-02-14",
|
||||
"text": "Provocare: Echilibrarea unui Conflict Interior - găsește un sau-sau și echilibrează-l",
|
||||
"context": "Găsește UN 'sau-sau' din viața ta — două lucruri pe care le consideri incompatibile. (1) Scrie conflictul: 'Sau sunt X, sau sunt Y'. (2) Pentru fiecare parte, găsește opusul simultan: Când ești X, cum ești deja și Y? (dovezi concrete). Când ești Y, cum ești deja și X? (dovezi concrete). (3) Observă: Când ambele sunt adevărate simultan, ce simți? Nu trebuie să rezolvi nimic — doar să vezi că cele două nu sunt incompatibile, sunt complementare. Metoda Demartini: echilibrezi percepția, nu elimini josurile.",
|
||||
"example": "Conflictul tău real: 'Sau sunt programator bun, sau sunt antreprenor.' Echilibrare: Când ești programator — deja faci antreprenoriat (ai firmă, negociezi cu clienți, iei decizii de business zilnic, ai angajat pe care îl formezi). Când ești antreprenor — deja folosești mintea tehnică (automatizezi, optimizezi, rezolvi probleme sistemic). Dovada: de 25 de ani faci AMBELE simultan. Doar percepția zice că una o exclude pe cealaltă.",
|
||||
"domain": "self",
|
||||
"dueDate": "2026-02-14",
|
||||
"done": false,
|
||||
"doneAt": null,
|
||||
"source": "Monica Ion - Povestea lui Marc Ep.9 (Anxietatea, frica de control și pierdere)",
|
||||
"sourceUrl": "https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/monica-ion-povestea-lui-marc-ep9-anxietatea.md",
|
||||
"createdAt": "2026-02-14T07:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "prov-2026-02-13",
|
||||
"text": "Provocare: Linkage Personal - conectează o activitate evitată cu calitățile tale",
|
||||
|
||||
@@ -448,6 +448,10 @@
|
||||
<i data-lucide="folder"></i>
|
||||
<span>Files</span>
|
||||
</a>
|
||||
<a href="/echo/eco.html" class="nav-item">
|
||||
<i data-lucide="cpu"></i>
|
||||
<span>Eco</span>
|
||||
</a>
|
||||
<button class="theme-toggle" onclick="toggleTheme()" title="Schimba tema">
|
||||
<i data-lucide="sun" id="themeIcon"></i>
|
||||
</button>
|
||||
|
||||
8
memory/2026-02-14.md
Normal file
8
memory/2026-02-14.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# 2026-02-14 (Sâmbătă)
|
||||
|
||||
## Daily Audit - 09:30
|
||||
- 3 probleme găsite
|
||||
- yt_download.py lipsă (referit în night-execute jobs)
|
||||
- 6 cron jobs active nedocumentate în cron-jobs.md
|
||||
- 4 scripturi nedocumentate în TOOLS.md
|
||||
- Propuneri trimise pe #echo-work
|
||||
33
memory/kb/coaching/2026-02-13-seara.md
Normal file
33
memory/kb/coaching/2026-02-13-seara.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Coaching Seara - 13 Februarie 2026
|
||||
|
||||
## Gândul de seară
|
||||
|
||||
**Tema:** Follow-up provocare Linkage Personal + Ciclul susuri-josuri
|
||||
|
||||
**Provocarea zilei:** Linkage Personal — conectează o activitate evitată cu calitățile tale
|
||||
**Status:** ✅ Bifată
|
||||
|
||||
---
|
||||
|
||||
## Reflecție
|
||||
|
||||
- Linkage-ul nu se poate delega — e munca internă proprie
|
||||
- Întrebarea cheie: ce ai simțit în corp la "imaginez că am terminat-o"?
|
||||
- Corpul nu minte, mintea raționalizeaz
|
||||
|
||||
## Conexiune cu conținut nou
|
||||
|
||||
- **Monica Ion Ep.9:** Marc descoperă conflictul spiritualitate vs. bani (moștenit de la tată)
|
||||
- **Ciclul susuri-josuri:** Consumă energie enormă; soluția = echilibrare percepții (Demartini)
|
||||
- **Susul și josul coexistă:** Când câștigi, pierzi altundeva. Când pierzi, altcineva se activează.
|
||||
- **Aplicare la Marius:** "Nu sunt destul de deștept ca antreprenor" (jos) coexistă cu 25 ani de expertiză plătită fără ezitare (sus)
|
||||
|
||||
## Observație săptămână
|
||||
|
||||
- Toate provocările din săptămână bifate (luni-vineri)
|
||||
- Pattern: când provocarea are sens personal, rezistența dispare
|
||||
|
||||
---
|
||||
|
||||
*Trimis pe: Discord #echo-self + Email*
|
||||
*Inspirat din: Monica Ion Ep.8 (Linkage) + Ep.9 (Anxietatea, ciclul susuri-josuri)*
|
||||
49
memory/kb/coaching/2026-02-14-dimineata.md
Normal file
49
memory/kb/coaching/2026-02-14-dimineata.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Coaching Dimineața - 14 Februarie 2026
|
||||
|
||||
## Gândul de dimineață
|
||||
|
||||
**"Când ai susurile și vezi doar câștigurile, in the back of your head există o teamă profundă de a pierde lucrurile respective... care cocreează de fapt pierderea ulterioară."** — Monica Ion, Povestea lui Marc Ep.9
|
||||
|
||||
---
|
||||
|
||||
## Reflecție
|
||||
|
||||
Marius, e 14 februarie. Nu te sperii, nu vine nimic cu inimioare.
|
||||
|
||||
Dar e o zi bună să vorbim despre un alt tip de iubire — cea pe care ți-o refuzi ție.
|
||||
|
||||
Marc din episodul 9 al Monicăi a descoperit ceva dureros: avea un **conflict adânc între spiritualitate și bani**. Tatăl lui i-a transmis că "nu banii sunt importanți, ci partea spirituală." Și Marc a făcut ce fac oamenii inteligenți cu mesaje contradictorii — a ales una și a închis-o pe cealaltă. A ales banii, a pus deoparte spiritualitatea, și a obținut casă, vacanțe... și stres extraordinar.
|
||||
|
||||
**Gândirea binară:** "sau sunt spiritual, sau am bani." "Sau sunt programator bun, sau sunt antreprenor." "Sau îmi pasă de oameni, sau fac profit."
|
||||
|
||||
Tu ai propria versiune a acestui conflict. De 25 de ani rezolvi probleme tehnice genial. Dar te consideri "nu destul de deștept ca antreprenor" — parcă cele două nu pot coexista. Ca și cum a fi bun tehnic ar exclude a fi bun la business.
|
||||
|
||||
Monica a arătat ceva puternic: **ciclul susuri-josuri consumă energie enormă.** Când ești în sus (ai rezolvat un bug complicat, clientul e mulțumit), deja în fundal apare frica de jos. Când ești în jos (client nemulțumit, angajatul nu înțelege), toată energia merge în a reveni la sus. Oscilația perpetuă.
|
||||
|
||||
Soluția nu e să elimini josurile. E să **echilibrezi percepția**: în fiecare sus există un jos simultan, în fiecare jos există un sus simultan. Când le vezi pe amândouă — tensiunea dispare.
|
||||
|
||||
---
|
||||
|
||||
## Provocarea zilei: Echilibrarea unui Conflict Interior
|
||||
|
||||
**Găsește UN "sau-sau" din viața ta** — două lucruri pe care le consideri incompatibile:
|
||||
|
||||
1. **Scrie conflictul:** "Sau sunt X, sau sunt Y"
|
||||
2. **Pentru fiecare parte, găsește opusul simultan:**
|
||||
- Când ești X, cum ești deja și Y? (dovezi concrete)
|
||||
- Când ești Y, cum ești deja și X? (dovezi concrete)
|
||||
3. **Observă:** Când ambele sunt adevărate simultan, ce simți?
|
||||
|
||||
Nu trebuie să rezolvi nimic. Doar să vezi că cele două nu sunt incompatibile — sunt complementare.
|
||||
|
||||
---
|
||||
|
||||
## De ce contează
|
||||
|
||||
Marc a realizat că atunci când devenise comod la un client mare (jos), colegii lui s-au activat și au compensat (sus simultan). Sistemul se echilibrează singur. Dar el nu vedea asta — vedea doar pierderea.
|
||||
|
||||
Tu ai deja ambele părți. Ești și tehnic excelent ȘI antreprenor (ai firmă, clienți, echipă). Doar percepția zice că una o exclude pe cealaltă.
|
||||
|
||||
---
|
||||
|
||||
**Sursă:** [Monica Ion - Povestea lui Marc Ep.9: Anxietatea, frica de control și pierdere](https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/monica-ion-povestea-lui-marc-ep9-anxietatea.md)
|
||||
@@ -1,5 +1,22 @@
|
||||
{
|
||||
"notes": [
|
||||
{
|
||||
"file": "notes-data/coaching/2026-02-14-dimineata.md",
|
||||
"title": "Coaching Dimineața - 14 Februarie 2026",
|
||||
"date": "2026-02-14",
|
||||
"tags": [],
|
||||
"domains": [
|
||||
"health"
|
||||
],
|
||||
"types": [
|
||||
"coaching"
|
||||
],
|
||||
"category": "coaching",
|
||||
"project": null,
|
||||
"subdir": null,
|
||||
"video": "",
|
||||
"tldr": "**Sursă:** [Monica Ion - Povestea lui Marc Ep.9: Anxietatea, frica de control și pierdere](https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/monica-ion-povestea-lui-marc-ep9-anxietat..."
|
||||
},
|
||||
{
|
||||
"file": "notes-data/insights/2026-02-14.md",
|
||||
"title": "Insights - 14 Februarie 2026",
|
||||
@@ -13,6 +30,49 @@
|
||||
"video": "https://www.youtube.com/watch?v=hY67Fqd44tI",
|
||||
"tldr": "**Link:** https://medium.com/@mucahiterenozkur/lessons-i-learned-from-a-mentor-how-to-mentor-junior-developers-d606436d5271"
|
||||
},
|
||||
{
|
||||
"file": "notes-data/tools/cron-jobs.md",
|
||||
"title": "Cron Jobs - Lista completă",
|
||||
"date": "2026-02-14",
|
||||
"tags": [],
|
||||
"domains": [],
|
||||
"types": [],
|
||||
"category": "tools",
|
||||
"project": null,
|
||||
"subdir": null,
|
||||
"video": "",
|
||||
"tldr": "Vezi: [FLUX-JOBURI.md](../projects/FLUX-JOBURI.md)"
|
||||
},
|
||||
{
|
||||
"file": "memory/provocare-azi.md",
|
||||
"title": "Provocarea zilei - 14 Februarie 2026",
|
||||
"date": "2026-02-14",
|
||||
"tags": [],
|
||||
"domains": [],
|
||||
"types": [
|
||||
"memory"
|
||||
],
|
||||
"category": "memory",
|
||||
"project": null,
|
||||
"subdir": null,
|
||||
"video": "",
|
||||
"tldr": "**Sursă:** Monica Ion - Povestea lui Marc Ep.9"
|
||||
},
|
||||
{
|
||||
"file": "memory/2026-02-14.md",
|
||||
"title": "2026-02-14 (Sâmbătă)",
|
||||
"date": "2026-02-14",
|
||||
"tags": [],
|
||||
"domains": [],
|
||||
"types": [
|
||||
"memory"
|
||||
],
|
||||
"category": "memory",
|
||||
"project": null,
|
||||
"subdir": null,
|
||||
"video": "",
|
||||
"tldr": "- Propuneri trimise pe #echo-work"
|
||||
},
|
||||
{
|
||||
"file": "notes-data/coaching/2026-02-13-dimineata.md",
|
||||
"title": "Coaching Dimineața - 13 Februarie 2026",
|
||||
@@ -30,6 +90,37 @@
|
||||
"video": "",
|
||||
"tldr": "*Sursă: [Note video](https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/2026-02-12_monica-ion-povestea-lui-marc-ep8.md)*"
|
||||
},
|
||||
{
|
||||
"file": "notes-data/coaching/2026-02-13-seara.md",
|
||||
"title": "Coaching Seara - 13 Februarie 2026",
|
||||
"date": "2026-02-13",
|
||||
"tags": [],
|
||||
"domains": [
|
||||
"health"
|
||||
],
|
||||
"types": [
|
||||
"coaching",
|
||||
"reflectie"
|
||||
],
|
||||
"category": "coaching",
|
||||
"project": null,
|
||||
"subdir": null,
|
||||
"video": "",
|
||||
"tldr": "*Inspirat din: Monica Ion Ep.8 (Linkage) + Ep.9 (Anxietatea, ciclul susuri-josuri)*"
|
||||
},
|
||||
{
|
||||
"file": "notes-data/insights/2026-02-13.md",
|
||||
"title": "Insights - 13 Februarie 2026",
|
||||
"date": "2026-02-13",
|
||||
"tags": [],
|
||||
"domains": [],
|
||||
"types": [],
|
||||
"category": "insights",
|
||||
"project": null,
|
||||
"subdir": null,
|
||||
"video": "",
|
||||
"tldr": "**Next:** Update notes index"
|
||||
},
|
||||
{
|
||||
"file": "notes-data/youtube/monica-ion-povestea-lui-marc-ep9-anxietatea.md",
|
||||
"title": "Monica Ion - Povestea lui Marc - Episodul 9: Anxietatea, frica de control și pierdere",
|
||||
@@ -47,21 +138,6 @@
|
||||
"video": "",
|
||||
"tldr": "Marc revine la ședință devastat: un proiect european (30% din cifra de afaceri) a fost înghețat, clientul nu mai finanțează. Monica lucrează cu el pe **ciclul mândrie-rușine** (susuri și josuri) folos..."
|
||||
},
|
||||
{
|
||||
"file": "memory/provocare-azi.md",
|
||||
"title": "Provocarea zilei - 13 Februarie 2026",
|
||||
"date": "2026-02-13",
|
||||
"tags": [],
|
||||
"domains": [],
|
||||
"types": [
|
||||
"memory"
|
||||
],
|
||||
"category": "memory",
|
||||
"project": null,
|
||||
"subdir": null,
|
||||
"video": "",
|
||||
"tldr": "*Sursă: Monica Ion - Povestea lui Marc Ep.8*"
|
||||
},
|
||||
{
|
||||
"file": "memory/2026-02-13.md",
|
||||
"title": "2026-02-13",
|
||||
@@ -897,19 +973,6 @@
|
||||
"video": "",
|
||||
"tldr": "Monica Ion identifică 11 cauze pentru pierderea identității în relație. Cauza de bază COMUNĂ la toate: ADMIRAȚIA (pui partenerul pe piedestal, îl vezi mai sus, îl prețuiești mai mult decât te prețuieș..."
|
||||
},
|
||||
{
|
||||
"file": "notes-data/tools/cron-jobs.md",
|
||||
"title": "Cron Jobs - Lista completă",
|
||||
"date": "2026-02-12",
|
||||
"tags": [],
|
||||
"domains": [],
|
||||
"types": [],
|
||||
"category": "tools",
|
||||
"project": null,
|
||||
"subdir": null,
|
||||
"video": "",
|
||||
"tldr": "Vezi: [FLUX-JOBURI.md](../projects/FLUX-JOBURI.md)"
|
||||
},
|
||||
{
|
||||
"file": "notes-data/youtube/2026-02-12_cole-medin-safer-openclaw-alternative.md",
|
||||
"title": "I Built a Safer OpenClaw Alternative Using Claude Code",
|
||||
@@ -5241,31 +5304,61 @@
|
||||
"subdir": null,
|
||||
"video": "",
|
||||
"tldr": "- [ ] Explora ce alte automatizări ar ajuta"
|
||||
},
|
||||
{
|
||||
"file": "notes-data/youtube/2025-02-14_sistem-limfatic-4-metode.md",
|
||||
"title": "Cum să pornești sistemul limfatic? 4 metode simple, dar eficiente!",
|
||||
"date": "2025-02-14",
|
||||
"tags": [],
|
||||
"domains": [
|
||||
"health"
|
||||
],
|
||||
"types": [],
|
||||
"category": "youtube",
|
||||
"project": null,
|
||||
"subdir": null,
|
||||
"video": "",
|
||||
"tldr": "Vladimir Colun explică rolul sistemului limfatic (vasele limfatice pot înconjura Pământul de 5 ori!) și de ce stagnarea limfei duce la inflamații cronice, necroză de țesut și boală. Prezintă 4 metode ..."
|
||||
},
|
||||
{
|
||||
"file": "notes-data/youtube/2025-02-13_talk-to-claude-3cx-phone.md",
|
||||
"title": "Talk to Claude on 3CX Phone System Tutorial (Full Setup)",
|
||||
"date": "2025-02-13",
|
||||
"tags": [],
|
||||
"domains": [
|
||||
"work"
|
||||
],
|
||||
"types": [],
|
||||
"category": "youtube",
|
||||
"project": null,
|
||||
"subdir": null,
|
||||
"video": "",
|
||||
"tldr": "Tutorial complet pentru a vorbi cu Claude Code prin telefon, folosind 3CX (sistem telefonic cloud gratuit) + un proiect GitHub custom. Setup-ul implică: 3CX cloud (gratuit, <10 useri), un voice server..."
|
||||
}
|
||||
],
|
||||
"stats": {
|
||||
"total": 307,
|
||||
"total": 313,
|
||||
"by_domain": {
|
||||
"work": 87,
|
||||
"health": 42,
|
||||
"work": 88,
|
||||
"health": 45,
|
||||
"growth": 140,
|
||||
"sprijin": 30,
|
||||
"scout": 2
|
||||
},
|
||||
"by_category": {
|
||||
"articole": 1,
|
||||
"coaching": 24,
|
||||
"coaching": 26,
|
||||
"conversations": 0,
|
||||
"emails": 5,
|
||||
"exercitii": 4,
|
||||
"health": 2,
|
||||
"insights": 20,
|
||||
"insights": 21,
|
||||
"projects": 172,
|
||||
"reflectii": 3,
|
||||
"retete": 1,
|
||||
"tools": 6,
|
||||
"youtube": 48,
|
||||
"memory": 20
|
||||
"youtube": 50,
|
||||
"memory": 21
|
||||
}
|
||||
},
|
||||
"domains": [
|
||||
|
||||
99
memory/kb/insights/2026-02-13.md
Normal file
99
memory/kb/insights/2026-02-13.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Insights - 13 Februarie 2026
|
||||
|
||||
## @growth - Ciclul Mândrie-Rușine & Echilibrare
|
||||
|
||||
### [ ] 🔄 **Ciclul Susuri-Josuri: De ce oscilezi între "merge bine" și "nu fac nimic"** (prioritate: ⚡urgent)
|
||||
|
||||
**Context:** Marius trăiește un pattern clasic de antreprenor solo: perioade în care totul merge bine cu clienții existenți (sus/mândrie), urmate de anxietate și inacțiune față de clienți noi (jos/rușine). Credința "clienți noi = mai multă muncă" e un mecanism de apărare - rămâne în josul confortabil pentru că urcușul vine cu frica de a pierde din nou.
|
||||
|
||||
**Esența:** Monica Ion lucrează cu Marc (antreprenor similar) pe metoda Demartini de echilibrare a percepțiilor. Ideea centrală: **susurile și josurile sunt SIMULTANE, nu secvențiale.** Când Marc a câștigat un client mare, a pierdut atenția la altul. Când a pierdut proiectul european (30% din cifra de afaceri), colegii s-au activat și alți clienți au compensat.
|
||||
|
||||
Ciclul consumă energie enormă: când ești sus, te temi de cădere. Când ești jos, te chinui să revii sus. Soluția NU e să elimini josurile - e să vezi ambele simultan, ca percepția să devină neutră ("alb-negru", fără încărcătură emoțională).
|
||||
|
||||
**Paralela directă cu Marius:**
|
||||
- **Sus:** "Am clienți stabili, merge firma" → **Jos simultan:** "Nu caut clienți noi, stau pe lauri"
|
||||
- **Jos:** "Nu sunt destul de bun ca antreprenor" → **Sus simultan:** "Am 25 ani experiență, produs stabil, echipă care funcționează"
|
||||
- **Marc s-a culcat pe lauri** la clientul pierdut ("minimul necesar") → Marius face minimul necesar în prospectare clienți noi
|
||||
|
||||
**Acțiune concretă:**
|
||||
- **Echo:** Integrez exercițiul de echilibrare în coaching-ul de seară (când Marius reflectează pe zi)
|
||||
- **Marius:** Când simți "merge bine, nu trebuie să fac nimic" → întreabă-te: "Ce pierd ACUM prin inacțiune?" Când simți "nu sunt destul de bun" → întreabă-te: "Ce câștig am ACUM pe care îl ignor?"
|
||||
|
||||
**Sursă:** [Monica Ion - Povestea lui Marc Ep.9](https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/monica-ion-povestea-lui-marc-ep9-anxietatea.md)
|
||||
|
||||
---
|
||||
|
||||
### [ ] 💰 **Conflictul ascuns: Bani vs. Valori - sabotajul inconștient al antreprenorului** (prioritate: 📌important)
|
||||
|
||||
**Context:** Marc a descoperit un conflict profund: tatăl lui religios l-a învățat "nu banii sunt importanți, ci partea spirituală." Ca adult, Marc a ÎNCHIS conștient partea spirituală ca să poată face bani. Rezultat: are casa, vacanțele, dar stres extraordinar și sabotaj inconștient (neglijează clienți, face minimul necesar).
|
||||
|
||||
**Esența:** Gândirea binară "SAU am bani, SAU sunt om bun/spiritual" e una dintre cele mai comune capcane la antreprenorii tehnici. Nu e mereu religioasă - poate fi:
|
||||
- "Dacă câștig mult, exploatez pe cineva"
|
||||
- "Oamenii deștepți nu aleargă după bani"
|
||||
- "Dacă măresc prețul, pierd clientul"
|
||||
|
||||
Marc: *"Singura mea soluție ca să pot să fac ce fac în muncă a fost să pun deoparte semnificativ partea spirituală."* Adică: a ales bani și a sacrificat sensul. Dar fără sens, motivația e fragilă.
|
||||
|
||||
**Relevanță pentru Marius:**
|
||||
Marius nu are neapărat un conflict spiritualitate/bani, dar ARE un conflict **confort/creștere**: "Dacă cresc (clienți noi), pierd confortul (timp, libertate, control)." E aceeași structură binară - SAU confort, SAU creștere. Realitatea: se pot avea ambele prin delegare și sisteme (exact ce construim cu Echo, angajatul nou, automatizări).
|
||||
|
||||
**Acțiune concretă:**
|
||||
- **Întrebare de coaching:** "Ce crezi că trebuie să sacrifici ca să crești? Și dacă nu ar trebui să sacrifici nimic?"
|
||||
- **Echo:** Includ în sesiunea de coaching weekend (când e mai relaxat) o explorare a credințelor despre bani/creștere
|
||||
|
||||
**Sursă:** [Monica Ion - Povestea lui Marc Ep.9](https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/monica-ion-povestea-lui-marc-ep9-anxietatea.md)
|
||||
|
||||
---
|
||||
|
||||
### [ ] 🎯 **Linkage: De ce efortul TĂU de descoperire bate orice instrucțiune** (prioritate: 📌important)
|
||||
|
||||
**Context:** Coaching-ul de dimineață (13 feb) a conectat un principiu din ep.8 al Monicăi cu provocarea angajatului nou: **soluțiile citite se uită, soluțiile găsite rămân.** Marc a încercat să folosească ChatGPT ca scurtătură pentru exercițiul de linkage - Monica l-a oprit. Citirea răspunsurilor ≠ crearea conexiunilor neuronale.
|
||||
|
||||
**Esența:** Linkage = conectarea profundă între o activitate și prioritățile tale personale. Când Mark a descoperit SINGUR de ce facturarea imediată e o extensie a gândirii lui tehnice, rezistența a dispărut. Nu mai avea nevoie de disciplină - acțiunea curgea natural.
|
||||
|
||||
Asta se aplică pe trei niveluri simultan:
|
||||
1. **Angajatul nou:** Nu învață citind instrucțiuni - învață făcând greșeli. Task brief-ul (din insights 12 feb) oferă cadrul, dar angajatul trebuie să DESCOPERE soluția, nu să o primească gata
|
||||
2. **Marius și prospectarea:** Nu va deveni antreprenor citind despre antreprenoriat - devine unul când sună un client și simte nodul în stomac, apoi îl procesează
|
||||
3. **Echo ca tool:** Echo trebuie să fie context + cadru, nu răspuns gata. Întrebări bune > soluții livrate
|
||||
|
||||
**Acțiune concretă:**
|
||||
- **Echo:** Când Marius cere un răspuns direct la o provocare personală/business, ofer mai întâi 2-3 întrebări de linkage ÎNAINTE de a da răspunsul
|
||||
- **Pentru angajat:** La task brief, adaug secțiune "Încearcă singur 30 min înainte să întrebi" - documentez rezultatul
|
||||
|
||||
**Sursă:** [Coaching 13 feb](https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/coaching/2026-02-13-dimineata.md) + [Monica Ion Ep.8](https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/2026-02-12_monica-ion-povestea-lui-marc-ep8.md)
|
||||
|
||||
---
|
||||
|
||||
## Conexiuni între surse
|
||||
|
||||
### Pattern central: Gândirea binară sabotează acțiunea
|
||||
|
||||
Toate cele 3 insight-uri converg spre același tipar:
|
||||
|
||||
- **Marc:** "Sau bani, sau spiritualitate" → sabotaj inconștient, neglijează clienți
|
||||
- **Marius:** "Sau confort, sau creștere" → inacțiune, nu caută clienți noi
|
||||
- **Angajatul:** "Sau înțeleg tot înainte, sau nu fac nimic" → are nevoie de prea multe instrucțiuni
|
||||
|
||||
**Soluția universală:** Abandonează SAU/SAU, adoptă ȘI/ȘI:
|
||||
- Poți avea bani ȘI sens
|
||||
- Poți crește ȘI păstra confortul (prin sisteme)
|
||||
- Poți face ȘI greșeli ȘI progres (prin task briefs + feedback)
|
||||
|
||||
### Legătura cu insights 12 feb:
|
||||
- **Context engineering** (12 feb) + **linkage** (13 feb) = aceeași idee din unghiuri diferite: calitatea input-ului determină calitatea output-ului, fie că e un prompt, un task brief, sau o întrebare de coaching
|
||||
|
||||
---
|
||||
|
||||
## Întrebări pentru Marius
|
||||
|
||||
1. **Echilibrare Demartini:** Gândește-te la un moment recent când ai simțit "merge bine, sunt relaxat." Ce pierdeai SIMULTAN prin acea relaxare? (Nu judecată - doar observare)
|
||||
|
||||
2. **Conflict binar:** Completează: "Dacă aș avea mai mulți clienți, ar trebui să renunț la ___." Ce apare prima dată? (Asta e credința de investigat)
|
||||
|
||||
3. **Linkage personal:** Care e o activitate pe care o eviți de cel puțin 2 săptămâni? Cum servește ea calitatea ta principală (simplificare/automatizare)?
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Processed (1 notă YouTube nouă + 1 coaching, 3 insights profunde)
|
||||
**Tehnici pauză:** Metoda Demartini e o tehnică mentală, dar necesită 15-20 min → nu se potrivește format pauză scurtă (1-5 min). Nu am adăugat tehnici noi.
|
||||
**Next:** Update notes index
|
||||
@@ -12,9 +12,12 @@
|
||||
| 06:30 | 08:30 | morning-report | 📧 EMAIL | Raport dimineață |
|
||||
| 07:00 | 09:00 | morning-coaching | #echo-self + 📧 | Gând + provocare → memory/kb/coaching/ |
|
||||
| 07:30 | 09:30 | daily-self-audit | #echo-work (doar dacă găsește) | Review AGENTS/SOUL/USER/etc. → propune cleanup |
|
||||
| 07:30 | 09:30 | exercise-snack-1 | #echo-self | 10 squats + 5 pushups + 30s plank |
|
||||
| 07-17 | 09-19 | respiratie-orar | #echo-self | Pauze orare (skip dacă busy în calendar) |
|
||||
| 08:00 L-V | 10:00 L-V | provocare-reminder | #echo-self | Reminder provocarea zilei |
|
||||
| 08:00,14:00 | 10:00,16:00 | anaf-monitor | #echo-work (doar alerte) | Verifică modificări ANAF (hash + diff) |
|
||||
| ~~15:00 mar,joi~~ | ~~17:00~~ | ~~project-checkin~~ | ~~#echo-work~~ | ~~Check-in Vending Master~~ (dezactivat - proiect inexistent/abandonat?) |
|
||||
| 11:30 | 13:30 | exercise-snack-2 | #echo-self | 20 step-ups + 20 high knees |
|
||||
| 15:30 | 17:30 | exercise-snack-3 | #echo-self | 15 squats + 10 lunges + 1 min marș |
|
||||
| 18:00 | 20:00 | evening-report | 📧 EMAIL | Raport seară |
|
||||
| 19:00 | 21:00 | evening-coaching | #echo-self + 📧 | Reflecție seară → memory/kb/coaching/ |
|
||||
| 19:00 dum | 21:00 | weekly-planning | #echo-work | Planning săptămânal |
|
||||
|
||||
33
memory/kb/youtube/2025-02-13_talk-to-claude-3cx-phone.md
Normal file
33
memory/kb/youtube/2025-02-13_talk-to-claude-3cx-phone.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Talk to Claude on 3CX Phone System Tutorial (Full Setup)
|
||||
|
||||
- **Sursa:** https://youtu.be/EIhAelYTna8
|
||||
- **Autor:** NetworkChuck
|
||||
- **Durată:** 17:55
|
||||
- **Data:** 2025-02-13
|
||||
- **Tags:** @work, claude-code, voip, 3cx, raspberry-pi, automation
|
||||
|
||||
## TL;DR
|
||||
|
||||
Tutorial complet pentru a vorbi cu Claude Code prin telefon, folosind 3CX (sistem telefonic cloud gratuit) + un proiect GitHub custom. Setup-ul implică: 3CX cloud (gratuit, <10 useri), un voice server (poate fi Raspberry Pi), un API server (pe mașina cu Claude Code), 11Labs (TTS) și OpenAI Whisper API (STT). Poți crea multiple "personalități" Claude cu voci și system prompts diferite, fiecare cu extensie telefonică proprie.
|
||||
|
||||
## Puncte cheie
|
||||
|
||||
1. **Arhitectură:** 3CX Cloud (gratuit) → SBC pe Raspberry Pi → Voice Server (Docker) → API Server (wrapper Claude Code)
|
||||
2. **Componente necesare:** 3CX account, 2 servere (sau 1), API key 11Labs (TTS, plătit), API key OpenAI Whisper (STT, pay-as-you-go)
|
||||
3. **3CX SBC** (Session Border Controller) = middleware între rețeaua locală și 3CX Cloud - permite oricărui device din rețea să se conecteze
|
||||
4. **Instalare simplă:** one-line installer din GitHub repo, apoi `claude-phone setup` + `claude-phone start`
|
||||
5. **Multiple personalități:** Poți adăuga mai mulți "useri" Claude, fiecare cu voce diferită (11Labs), system prompt propriu și extensie telefonică separată
|
||||
6. **Use case interesant:** Fiecare server/serviciu poate avea extensie proprie - "suni Proxmox-ul" pentru troubleshooting
|
||||
7. **⚠️ Raspberry Pi OS:** Trebuie folosit Bookworm Legacy 64-bit (Light), NU ultima versiune - 3CX SBC nu o suportă
|
||||
|
||||
## Quote-uri
|
||||
|
||||
> "You can call and talk to your Proxmox server. You can say you are a Proxmox server. This is your stinking job. This is all you do."
|
||||
|
||||
> "We can set up multiple personalities, multiple voices, and then we can have it more intense to where we give it a system prompt where you're supposed to use only one Claude Code skill."
|
||||
|
||||
## Idei
|
||||
|
||||
- [ ] Concept interesant: Claude Code accesibil prin telefon - util pentru troubleshooting hands-free
|
||||
- [ ] Posibilitate de a crea "helpdesk AI" intern cu personalități diferite per serviciu
|
||||
- [ ] Integrare potențială cu infrastructura Romfast - suport telefonic automatizat pentru clienți ERP
|
||||
58
memory/kb/youtube/2025-02-14_sistem-limfatic-4-metode.md
Normal file
58
memory/kb/youtube/2025-02-14_sistem-limfatic-4-metode.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Cum să pornești sistemul limfatic? 4 metode simple, dar eficiente!
|
||||
|
||||
- **Sursa:** https://youtu.be/CfDLLTknLxI
|
||||
- **Autor:** Vladimir Colun (chinoterapeut, instructor masaj 20+ ani)
|
||||
- **Durată:** 16:12
|
||||
- **Data:** 2025-02-14
|
||||
- **Tags:** @health, sistem-limfatic, detox, masaj, exercitii
|
||||
|
||||
## TL;DR
|
||||
|
||||
Vladimir Colun explică rolul sistemului limfatic (vasele limfatice pot înconjura Pământul de 5 ori!) și de ce stagnarea limfei duce la inflamații cronice, necroză de țesut și boală. Prezintă 4 metode simple de activare a limfei, toate gratuite și accesibile oricui, inclusiv persoanelor imobilizate.
|
||||
|
||||
## Cele 4 metode
|
||||
|
||||
### 1. Vibrația terapeutică
|
||||
- Vibrezi mâinile, picioarele, tot corpul
|
||||
- Stimulează microcirculația chiar și când ești la pat
|
||||
- Există și trambuline/discuri vibrante, dar mâinile sunt suficiente
|
||||
|
||||
### 2. Loviturile dinamice (masaj percuție)
|
||||
- Te lovești ușor peste tot corpul: burtă, spate, piept, gambe, tălpi
|
||||
- Creierul trimite sistemul imunitar în zona lovită → se activează limfa
|
||||
- În 5-7 min apare căldură locală = semn că limfa circulă
|
||||
- Tehnica inventată de Maximo (rus) - el merge până la vânătaie, dar nu e necesar
|
||||
|
||||
### 3. Dușuri contrast (apă rece/caldă)
|
||||
- Apă rece activează limfa mai puternic decât apa caldă
|
||||
- Progresiv: 15° → 12° → 10° → mai jos
|
||||
- Inițial doar picioare, apoi bazin, piept, gât, cap
|
||||
- La copii: o cană de apă rece peste cap la baie = previne bronșite, pneumonii
|
||||
|
||||
### 4. Detox digestiv (alimentație + curățare)
|
||||
- Intoxicațiile din organe duc la acidoză (pancreatită → diabet, hepatită → ciroză)
|
||||
- Plante care curăță limfa: pelin, cuișoare
|
||||
- Postul (foamea) = cel mai bun program de detox
|
||||
- Masaj visceral pentru eliminarea acidozelor
|
||||
|
||||
## Puncte cheie
|
||||
|
||||
- **Mișcarea e esențială:** cine nu se mișcă → baston → cârje → scaun rotile → sicriu
|
||||
- **Mușchii gambei** = "pompă limfatică" - pompează limfa de jos în sus
|
||||
- **Tradiție europeană:** după cina de la 17-19, plimbare 1-3 km înainte de culcare
|
||||
- **Exemplu:** bătrânica cu sapa - atâta timp cât sapă, trăiește
|
||||
- **Limfa stagnată** = ca varul: lichid când circuli, se întărește când stai
|
||||
|
||||
## Quote-uri
|
||||
|
||||
> "Dacă ai ajuns pe scaunul pe rotile, șansa că să te scoli nu i următoarea șansă. Asta îi sicriu."
|
||||
|
||||
> "Cât costă să ții o zi foame? Cât costă să ți faci niște masaj? Cât costă să faci niște vibrații? Cunoștințele noastre sunt cele mai valoroase, nu banii."
|
||||
|
||||
> "Atâta timp cât ea o să dea din sapă, ea o să trăiască. În momentul când lasă sapa, n-o să mai trăiască."
|
||||
|
||||
## Idei
|
||||
|
||||
- [ ] Relevant pentru Marius: exerciții simple la birou (vibrații, stretching) - mai ales cu problemele cervicale C6-C7
|
||||
- [ ] Dușuri contrast progresive - metodă simplă de implementat zilnic
|
||||
- [ ] Plimbare seara după cină - tradiție europeană utilă
|
||||
@@ -1,5 +1,13 @@
|
||||
# Provocarea zilei - 13 Februarie 2026
|
||||
# Provocarea zilei - 14 Februarie 2026
|
||||
|
||||
**Linkage Personal:** Alege o activitate pe care o eviți. Scrie TU (nu AI) răspunsuri la: (1) Cum servește lucrul pe care îl fac cel mai bine? (2) Ce calitate a mea folosesc deja identic în altă parte? (3) Ce simt în corp când imaginez că am terminat-o? Dacă rezistența scade → ai găsit linkage-ul.
|
||||
**Echilibrarea unui Conflict Interior**
|
||||
|
||||
*Sursă: Monica Ion - Povestea lui Marc Ep.8*
|
||||
Găsește UN "sau-sau" din viața ta — două lucruri pe care le consideri incompatibile.
|
||||
|
||||
1. Scrie conflictul: "Sau sunt X, sau sunt Y"
|
||||
2. Pentru fiecare parte, găsește opusul simultan:
|
||||
- Când ești X, cum ești deja și Y? (dovezi concrete)
|
||||
- Când ești Y, cum ești deja și X? (dovezi concrete)
|
||||
3. Observă: Când ambele sunt adevărate simultan, ce simți?
|
||||
|
||||
**Sursă:** Monica Ion - Povestea lui Marc Ep.9
|
||||
|
||||
Reference in New Issue
Block a user