feat: Workspace page enhancements - git ops, Gitea auto-create, compact stories
- Add workspace.html with project cards, Ralph status, git info - Backend: git diff/commit/push endpoints, project delete with confirmation - Push auto-creates Gitea repo (romfast org) when no remote configured - GITEA_TOKEN read from dashboard/.env file - Compact collapsible user stories (emoji row + expand on click) - Action buttons: Diff (with count badge), Commit, Push, README, Delete - Fix openPrd/openReadme to use hash navigation for files.html - Add .gitignore template to ralph.sh for new projects - Unify branches: merge main into master, delete main Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
124
AGENTS.md
124
AGENTS.md
@@ -32,118 +32,24 @@ When I receive errors, bugs, or new feature requests:
|
|||||||
1. **Planning → Opus**: Architecture, strategy, design decisions
|
1. **Planning → Opus**: Architecture, strategy, design decisions
|
||||||
2. **Execution → Sonnet**: Implementation, coding, debugging, testing
|
2. **Execution → Sonnet**: Implementation, coding, debugging, testing
|
||||||
|
|
||||||
## Proiecte/Features Workflow (MANDATORY)
|
## Proiecte/Features Workflow
|
||||||
|
|
||||||
**Scop:** Propun și creez programe/proiecte în cod care îl ajută pe Marius (80/20), inspirate din Discovery (YouTube, articole, bloguri).
|
**Scop:** Propun proiecte 80/20 in evening-report, implementez cu Ralph in night-execute.
|
||||||
|
**Tools:** tools/ralph_prd_generator.py, tools/ralph_workflow.py
|
||||||
|
**Workspace:** ~/workspace/ | **Tracking:** memory/approved-tasks.md
|
||||||
|
**Model strategy:** Opus (planning/PRD) → Sonnet (implementare Ralph)
|
||||||
|
|
||||||
### Criterii Propuneri (80/20 STRICT)
|
### Context Proiecte Prioritare
|
||||||
- Impact mare pentru Marius → apoi pentru clienți
|
- **roa2web** (gitea.romfast.ro/romfast/roa2web) - rapoarte, interfata web, notificari ERP ROA
|
||||||
- Proiecte de "joacă" pentru el mai întâi (să vadă cum îl ajută)
|
- Rapoarte ROA noi → feature in roa2web (NU proiect separat)
|
||||||
- Din ce îl interesează (USER.md)
|
- **Chatbot Maria** (Flowise, LXC 104) - documentatie, raspunsuri clienti
|
||||||
- Inspirat din conținut procesat (memory/kb/youtube/, articole/, insights/)
|
- Imbunatatiri chatbot → documentatie + configurare Flowise
|
||||||
- **NU orice** - doar cu valoare concretă
|
- **Proiecte independente** → ~/workspace/ cu Ralph autonom
|
||||||
|
|
||||||
### Workflow Complet
|
### Surse Inspiratie
|
||||||
|
- Intrebari frecvente clienti (validari ANAF D406/D394, facturare valuta, taxare inversa)
|
||||||
**1. SEARA (20:00) - evening-report:**
|
- Note din memory/kb/ (youtube, insights, articole)
|
||||||
- Propun 1-2 proiecte NOI (P1, P2)
|
- Probleme repetitive ale lui Marius
|
||||||
- Propun 2-3 features pentru proiecte EXISTENTE (F1, F2, F3)
|
|
||||||
- Format: context, impact, efort, stack simplu
|
|
||||||
- Marius aprobă: "P pentru P1,P2" sau "F pentru F1,F3"
|
|
||||||
|
|
||||||
**2. NOAPTE (23:00, 03:00) - night-execute:**
|
|
||||||
|
|
||||||
**A. Planning cu OPUS (eu, Echo) - pe moltbot:**
|
|
||||||
```python
|
|
||||||
from tools.ralph_prd_generator import create_prd_and_json
|
|
||||||
from tools.ralph_workflow import run_ralph
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
# Pentru fiecare proiect aprobat
|
|
||||||
prd_file, prd_json = create_prd_and_json(
|
|
||||||
project_name="project-name", # kebab-case
|
|
||||||
description="Descriere completă cu Features:\n- Feature 1\n- Feature 2",
|
|
||||||
workspace_dir=Path.home() / "workspace"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Git init + push
|
|
||||||
project_dir = prd_json.parent.parent.parent
|
|
||||||
subprocess.run(["git", "init"], cwd=project_dir)
|
|
||||||
subprocess.run(["git", "add", "."], cwd=project_dir)
|
|
||||||
subprocess.run(["git", "commit", "-m", "Initial commit with PRD"], cwd=project_dir)
|
|
||||||
subprocess.run(["git", "remote", "add", "origin", f"https://gitea.romfast.ro/romfast/PROJECT"], cwd=project_dir)
|
|
||||||
subprocess.run(["git", "push", "-u", "origin", "main"], cwd=project_dir)
|
|
||||||
|
|
||||||
# Lansează Ralph
|
|
||||||
run_ralph(prd_json, max_iterations=20, background=True)
|
|
||||||
|
|
||||||
# Marchează [x] în approved-tasks.md
|
|
||||||
```
|
|
||||||
|
|
||||||
**B. Implementare cu Ralph (Sonnet) - automat în background:**
|
|
||||||
- ralph.sh rulează autonom:
|
|
||||||
1. Selectează story (priority minimă, passes=false)
|
|
||||||
2. Rulează Claude Code (Sonnet) pentru implementare
|
|
||||||
3. Quality checks: typecheck, lint, test
|
|
||||||
4. Git commit dacă OK → passes: true
|
|
||||||
5. Update progress.txt cu learnings
|
|
||||||
6. Repetă până complete sau max 20 iterații
|
|
||||||
- Git push automat
|
|
||||||
|
|
||||||
**3. DIMINEAȚĂ (08:30) - morning-report:**
|
|
||||||
```python
|
|
||||||
from tools.ralph_workflow import check_status
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
status = check_status(Path.home() / "workspace" / "PROJECT-NAME")
|
|
||||||
# Raportez: stories complete/incomplete, learnings, link gitea
|
|
||||||
```
|
|
||||||
|
|
||||||
- Status per story: ✅ complet / 🔄 în progres / ⚠️ blocat
|
|
||||||
- Learnings din progress.txt
|
|
||||||
- Link gitea: `https://gitea.romfast.ro/romfast/PROJECT-NAME`
|
|
||||||
|
|
||||||
### Mașină Development
|
|
||||||
|
|
||||||
**moltbot (LXC 110) - LOCAL:**
|
|
||||||
- User: moltbot
|
|
||||||
- Workspace: `~/workspace/`
|
|
||||||
- Claude Code: `~/.local/bin/claude` (v2.1.37)
|
|
||||||
- Ralph tools:
|
|
||||||
- `~/clawd/tools/ralph_prd_generator.py` - Generează PRD și prd.json (Python)
|
|
||||||
- `~/clawd/tools/ralph_workflow.py` - Wrapper (lansare Ralph)
|
|
||||||
- `~/clawd/skills/ralph/templates/` - ralph.sh, prompt.md (copiate în proiecte)
|
|
||||||
- Templates copiate automat în fiecare proiect
|
|
||||||
|
|
||||||
### Model Strategy (OBLIGATORIU)
|
|
||||||
- **Opus** → Planning, PRD, stories (eu, Echo în night-execute)
|
|
||||||
- **Sonnet** → Coding, debugging, implementare (Ralph loop)
|
|
||||||
|
|
||||||
### Structură Proiect
|
|
||||||
```
|
|
||||||
/workspace/PROJECT-NAME/
|
|
||||||
├── tasks/
|
|
||||||
│ └── prd-PROJECT-NAME.md # PRD generat de /prd skill
|
|
||||||
├── scripts/
|
|
||||||
│ └── ralph/
|
|
||||||
│ ├── prd.json # Stories pentru Ralph
|
|
||||||
│ ├── progress.txt # Learnings per iterație
|
|
||||||
│ └── ralph.sh # Loop autonom
|
|
||||||
├── src/ # Cod implementat de Ralph
|
|
||||||
└── .git/ # Git repo → gitea
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tracking
|
|
||||||
- `memory/approved-tasks.md` - include proiecte (P1, P2) și features (F1, F2)
|
|
||||||
- Secțiuni: "Noaptea asta" + "Nopțile următoare"
|
|
||||||
- Format: `[ ] P1 - Nume Proiect: descriere scurtă`
|
|
||||||
|
|
||||||
### Exemple Domenii
|
|
||||||
- Automatizări pentru ROA (scripturile lui Marius)
|
|
||||||
- Unelte productivitate (task tracking, reminder-uri)
|
|
||||||
- Mini-tools pentru clienți (rapoarte, validări)
|
|
||||||
- Experimente NLP/coaching (exerciții interactive)
|
|
||||||
- Tracking sănătate (dureri, pauze respirație)
|
|
||||||
|
|
||||||
## Memory
|
## Memory
|
||||||
|
|
||||||
|
|||||||
622
dashboard/api.py
622
dashboard/api.py
@@ -5,10 +5,12 @@ Handles YouTube summarization requests.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -18,6 +20,20 @@ BASE_DIR = Path(__file__).parent.parent
|
|||||||
TOOLS_DIR = BASE_DIR / 'tools'
|
TOOLS_DIR = BASE_DIR / 'tools'
|
||||||
NOTES_DIR = BASE_DIR / 'kb' / 'youtube'
|
NOTES_DIR = BASE_DIR / 'kb' / 'youtube'
|
||||||
KANBAN_DIR = BASE_DIR / 'dashboard'
|
KANBAN_DIR = BASE_DIR / 'dashboard'
|
||||||
|
WORKSPACE_DIR = Path('/home/moltbot/workspace')
|
||||||
|
|
||||||
|
# Load .env file if present
|
||||||
|
_env_file = Path(__file__).parent / '.env'
|
||||||
|
if _env_file.exists():
|
||||||
|
for line in _env_file.read_text().splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('#') and '=' in line:
|
||||||
|
k, v = line.split('=', 1)
|
||||||
|
os.environ.setdefault(k.strip(), v.strip())
|
||||||
|
|
||||||
|
GITEA_URL = os.environ.get('GITEA_URL', 'https://gitea.romfast.ro')
|
||||||
|
GITEA_ORG = os.environ.get('GITEA_ORG', 'romfast')
|
||||||
|
GITEA_TOKEN = os.environ.get('GITEA_TOKEN', '')
|
||||||
|
|
||||||
class TaskBoardHandler(SimpleHTTPRequestHandler):
|
class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||||
|
|
||||||
@@ -32,6 +48,16 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
|||||||
self.handle_git_commit()
|
self.handle_git_commit()
|
||||||
elif self.path == '/api/pdf':
|
elif self.path == '/api/pdf':
|
||||||
self.handle_pdf_post()
|
self.handle_pdf_post()
|
||||||
|
elif self.path == '/api/workspace/run':
|
||||||
|
self.handle_workspace_run()
|
||||||
|
elif self.path == '/api/workspace/stop':
|
||||||
|
self.handle_workspace_stop()
|
||||||
|
elif self.path == '/api/workspace/git/commit':
|
||||||
|
self.handle_workspace_git_commit()
|
||||||
|
elif self.path == '/api/workspace/git/push':
|
||||||
|
self.handle_workspace_git_push()
|
||||||
|
elif self.path == '/api/workspace/delete':
|
||||||
|
self.handle_workspace_delete()
|
||||||
else:
|
else:
|
||||||
self.send_error(404)
|
self.send_error(404)
|
||||||
|
|
||||||
@@ -229,6 +255,12 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
|||||||
self.handle_files_get()
|
self.handle_files_get()
|
||||||
elif self.path.startswith('/api/diff'):
|
elif self.path.startswith('/api/diff'):
|
||||||
self.handle_git_diff()
|
self.handle_git_diff()
|
||||||
|
elif self.path == '/api/workspace' or self.path.startswith('/api/workspace?'):
|
||||||
|
self.handle_workspace_list()
|
||||||
|
elif self.path.startswith('/api/workspace/git/diff'):
|
||||||
|
self.handle_workspace_git_diff()
|
||||||
|
elif self.path.startswith('/api/workspace/logs'):
|
||||||
|
self.handle_workspace_logs()
|
||||||
elif self.path.startswith('/api/'):
|
elif self.path.startswith('/api/'):
|
||||||
self.send_error(404)
|
self.send_error(404)
|
||||||
else:
|
else:
|
||||||
@@ -726,6 +758,596 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
|||||||
else:
|
else:
|
||||||
self.send_json({'error': 'Unknown action'}, 400)
|
self.send_json({'error': 'Unknown action'}, 400)
|
||||||
|
|
||||||
|
def handle_workspace_list(self):
|
||||||
|
"""List projects in ~/workspace/ with Ralph status, git info, etc."""
|
||||||
|
try:
|
||||||
|
projects = []
|
||||||
|
if not WORKSPACE_DIR.exists():
|
||||||
|
self.send_json({'projects': []})
|
||||||
|
return
|
||||||
|
|
||||||
|
for project_dir in sorted(WORKSPACE_DIR.iterdir()):
|
||||||
|
if not project_dir.is_dir() or project_dir.name.startswith('.'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
ralph_dir = project_dir / 'scripts' / 'ralph'
|
||||||
|
prd_json = ralph_dir / 'prd.json'
|
||||||
|
tasks_dir = project_dir / 'tasks'
|
||||||
|
|
||||||
|
proj = {
|
||||||
|
'name': project_dir.name,
|
||||||
|
'path': str(project_dir),
|
||||||
|
'hasRalph': ralph_dir.exists(),
|
||||||
|
'hasPrd': any(tasks_dir.glob('prd-*.md')) if tasks_dir.exists() else False,
|
||||||
|
'hasMain': (project_dir / 'main.py').exists(),
|
||||||
|
'hasVenv': (project_dir / 'venv').exists(),
|
||||||
|
'hasReadme': (project_dir / 'README.md').exists(),
|
||||||
|
'ralph': None,
|
||||||
|
'process': {'running': False, 'pid': None, 'port': None},
|
||||||
|
'git': None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ralph status
|
||||||
|
if prd_json.exists():
|
||||||
|
try:
|
||||||
|
prd = json.loads(prd_json.read_text())
|
||||||
|
stories = prd.get('userStories', [])
|
||||||
|
complete = sum(1 for s in stories if s.get('passes'))
|
||||||
|
|
||||||
|
# Check ralph PID
|
||||||
|
ralph_pid = None
|
||||||
|
ralph_running = False
|
||||||
|
pid_file = ralph_dir / '.ralph.pid'
|
||||||
|
if pid_file.exists():
|
||||||
|
try:
|
||||||
|
pid = int(pid_file.read_text().strip())
|
||||||
|
os.kill(pid, 0) # Check if alive
|
||||||
|
ralph_running = True
|
||||||
|
ralph_pid = pid
|
||||||
|
except (ValueError, ProcessLookupError, PermissionError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Last iteration time from logs
|
||||||
|
last_iter = None
|
||||||
|
logs_dir = ralph_dir / 'logs'
|
||||||
|
if logs_dir.exists():
|
||||||
|
log_files = sorted(logs_dir.glob('iteration-*.log'), key=lambda f: f.stat().st_mtime, reverse=True)
|
||||||
|
if log_files:
|
||||||
|
mtime = log_files[0].stat().st_mtime
|
||||||
|
last_iter = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M')
|
||||||
|
|
||||||
|
tech = prd.get('techStack', {})
|
||||||
|
proj['ralph'] = {
|
||||||
|
'running': ralph_running,
|
||||||
|
'pid': ralph_pid,
|
||||||
|
'storiesTotal': len(stories),
|
||||||
|
'storiesComplete': complete,
|
||||||
|
'lastIteration': last_iter,
|
||||||
|
'stories': [
|
||||||
|
{'id': s.get('id', ''), 'title': s.get('title', ''), 'passes': s.get('passes', False)}
|
||||||
|
for s in stories
|
||||||
|
]
|
||||||
|
}
|
||||||
|
proj['techStack'] = {
|
||||||
|
'type': tech.get('type', ''),
|
||||||
|
'commands': tech.get('commands', {}),
|
||||||
|
'port': tech.get('port'),
|
||||||
|
}
|
||||||
|
except (json.JSONDecodeError, IOError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Check if main.py is running
|
||||||
|
if proj['hasMain']:
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['pgrep', '-f', f'python.*{project_dir.name}/main.py'],
|
||||||
|
capture_output=True, text=True, timeout=3
|
||||||
|
)
|
||||||
|
if result.stdout.strip():
|
||||||
|
pids = result.stdout.strip().split('\n')
|
||||||
|
port = None
|
||||||
|
if prd_json.exists():
|
||||||
|
try:
|
||||||
|
prd_data = json.loads(prd_json.read_text())
|
||||||
|
port = prd_data.get('techStack', {}).get('port')
|
||||||
|
except (json.JSONDecodeError, IOError):
|
||||||
|
pass
|
||||||
|
proj['process'] = {
|
||||||
|
'running': True,
|
||||||
|
'pid': int(pids[0]),
|
||||||
|
'port': port
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Git info
|
||||||
|
if (project_dir / '.git').exists():
|
||||||
|
try:
|
||||||
|
branch = subprocess.run(
|
||||||
|
['git', 'branch', '--show-current'],
|
||||||
|
cwd=project_dir, capture_output=True, text=True, timeout=5
|
||||||
|
).stdout.strip()
|
||||||
|
|
||||||
|
last_commit = subprocess.run(
|
||||||
|
['git', 'log', '-1', '--format=%h - %s'],
|
||||||
|
cwd=project_dir, capture_output=True, text=True, timeout=5
|
||||||
|
).stdout.strip()
|
||||||
|
|
||||||
|
status_out = subprocess.run(
|
||||||
|
['git', 'status', '--short'],
|
||||||
|
cwd=project_dir, capture_output=True, text=True, timeout=5
|
||||||
|
).stdout.strip()
|
||||||
|
uncommitted = len([l for l in status_out.split('\n') if l.strip()]) if status_out else 0
|
||||||
|
|
||||||
|
proj['git'] = {
|
||||||
|
'branch': branch,
|
||||||
|
'lastCommit': last_commit,
|
||||||
|
'uncommitted': uncommitted
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
projects.append(proj)
|
||||||
|
|
||||||
|
self.send_json({'projects': projects})
|
||||||
|
except Exception as e:
|
||||||
|
self.send_json({'error': str(e)}, 500)
|
||||||
|
|
||||||
|
def _read_post_json(self):
|
||||||
|
"""Helper to read JSON POST body."""
|
||||||
|
content_length = int(self.headers['Content-Length'])
|
||||||
|
post_data = self.rfile.read(content_length).decode('utf-8')
|
||||||
|
return json.loads(post_data)
|
||||||
|
|
||||||
|
def _validate_project(self, name):
|
||||||
|
"""Validate project name and return its path, or None."""
|
||||||
|
if not name or '/' in name or '..' in name:
|
||||||
|
return None
|
||||||
|
project_dir = WORKSPACE_DIR / name
|
||||||
|
if not project_dir.exists() or not project_dir.is_dir():
|
||||||
|
return None
|
||||||
|
# Ensure it resolves within workspace
|
||||||
|
if not str(project_dir.resolve()).startswith(str(WORKSPACE_DIR)):
|
||||||
|
return None
|
||||||
|
return project_dir
|
||||||
|
|
||||||
|
def handle_workspace_run(self):
|
||||||
|
"""Start a project process (main.py, ralph.sh, or pytest)."""
|
||||||
|
try:
|
||||||
|
data = self._read_post_json()
|
||||||
|
project_name = data.get('project', '')
|
||||||
|
command = data.get('command', '')
|
||||||
|
|
||||||
|
project_dir = self._validate_project(project_name)
|
||||||
|
if not project_dir:
|
||||||
|
self.send_json({'success': False, 'error': 'Invalid project'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
allowed_commands = {'main', 'ralph', 'test'}
|
||||||
|
if command not in allowed_commands:
|
||||||
|
self.send_json({'success': False, 'error': f'Invalid command. Allowed: {", ".join(allowed_commands)}'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
ralph_dir = project_dir / 'scripts' / 'ralph'
|
||||||
|
|
||||||
|
if command == 'main':
|
||||||
|
main_py = project_dir / 'main.py'
|
||||||
|
if not main_py.exists():
|
||||||
|
self.send_json({'success': False, 'error': 'No main.py found'}, 404)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Use venv python if available
|
||||||
|
venv_python = project_dir / 'venv' / 'bin' / 'python'
|
||||||
|
python_cmd = str(venv_python) if venv_python.exists() else sys.executable
|
||||||
|
|
||||||
|
log_path = ralph_dir / 'logs' / 'main.log' if ralph_dir.exists() else project_dir / 'main.log'
|
||||||
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with open(log_path, 'a') as log_file:
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[python_cmd, 'main.py'],
|
||||||
|
cwd=str(project_dir),
|
||||||
|
stdout=log_file,
|
||||||
|
stderr=log_file,
|
||||||
|
start_new_session=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.send_json({'success': True, 'pid': proc.pid, 'log': str(log_path)})
|
||||||
|
|
||||||
|
elif command == 'ralph':
|
||||||
|
ralph_sh = ralph_dir / 'ralph.sh'
|
||||||
|
if not ralph_sh.exists():
|
||||||
|
self.send_json({'success': False, 'error': 'No ralph.sh found'}, 404)
|
||||||
|
return
|
||||||
|
|
||||||
|
log_path = ralph_dir / 'logs' / 'ralph.log'
|
||||||
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with open(log_path, 'a') as log_file:
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
['bash', str(ralph_sh)],
|
||||||
|
cwd=str(project_dir),
|
||||||
|
stdout=log_file,
|
||||||
|
stderr=log_file,
|
||||||
|
start_new_session=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write PID
|
||||||
|
pid_file = ralph_dir / '.ralph.pid'
|
||||||
|
pid_file.write_text(str(proc.pid))
|
||||||
|
|
||||||
|
self.send_json({'success': True, 'pid': proc.pid, 'log': str(log_path)})
|
||||||
|
|
||||||
|
elif command == 'test':
|
||||||
|
# Run pytest synchronously (with timeout)
|
||||||
|
venv_python = project_dir / 'venv' / 'bin' / 'python'
|
||||||
|
python_cmd = str(venv_python) if venv_python.exists() else sys.executable
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
[python_cmd, '-m', 'pytest', '-v', '--tb=short'],
|
||||||
|
cwd=str(project_dir),
|
||||||
|
capture_output=True, text=True,
|
||||||
|
timeout=120
|
||||||
|
)
|
||||||
|
|
||||||
|
self.send_json({
|
||||||
|
'success': result.returncode == 0,
|
||||||
|
'output': result.stdout + result.stderr,
|
||||||
|
'returncode': result.returncode
|
||||||
|
})
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
self.send_json({'success': False, 'error': 'Test timeout (120s)'}, 500)
|
||||||
|
except Exception as e:
|
||||||
|
self.send_json({'success': False, 'error': str(e)}, 500)
|
||||||
|
|
||||||
|
def handle_workspace_stop(self):
|
||||||
|
"""Stop a project process."""
|
||||||
|
try:
|
||||||
|
data = self._read_post_json()
|
||||||
|
project_name = data.get('project', '')
|
||||||
|
target = data.get('target', '')
|
||||||
|
|
||||||
|
project_dir = self._validate_project(project_name)
|
||||||
|
if not project_dir:
|
||||||
|
self.send_json({'success': False, 'error': 'Invalid project'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
if target not in ('main', 'ralph'):
|
||||||
|
self.send_json({'success': False, 'error': 'Invalid target. Use: main, ralph'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
if target == 'ralph':
|
||||||
|
pid_file = project_dir / 'scripts' / 'ralph' / '.ralph.pid'
|
||||||
|
if pid_file.exists():
|
||||||
|
try:
|
||||||
|
pid = int(pid_file.read_text().strip())
|
||||||
|
# Verify the process belongs to our user and is within workspace
|
||||||
|
proc_cwd = Path(f'/proc/{pid}/cwd').resolve()
|
||||||
|
if str(proc_cwd).startswith(str(WORKSPACE_DIR)):
|
||||||
|
os.killpg(os.getpgid(pid), signal.SIGTERM)
|
||||||
|
self.send_json({'success': True, 'message': f'Ralph stopped (PID {pid})'})
|
||||||
|
else:
|
||||||
|
self.send_json({'success': False, 'error': 'Process not in workspace'}, 403)
|
||||||
|
except ProcessLookupError:
|
||||||
|
self.send_json({'success': True, 'message': 'Process already stopped'})
|
||||||
|
except PermissionError:
|
||||||
|
self.send_json({'success': False, 'error': 'Permission denied'}, 403)
|
||||||
|
else:
|
||||||
|
self.send_json({'success': False, 'error': 'No PID file found'}, 404)
|
||||||
|
|
||||||
|
elif target == 'main':
|
||||||
|
# Find main.py process for this project
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['pgrep', '-f', f'python.*{project_dir.name}/main.py'],
|
||||||
|
capture_output=True, text=True, timeout=3
|
||||||
|
)
|
||||||
|
if result.stdout.strip():
|
||||||
|
pid = int(result.stdout.strip().split('\n')[0])
|
||||||
|
proc_cwd = Path(f'/proc/{pid}/cwd').resolve()
|
||||||
|
if str(proc_cwd).startswith(str(WORKSPACE_DIR)):
|
||||||
|
os.kill(pid, signal.SIGTERM)
|
||||||
|
self.send_json({'success': True, 'message': f'Main stopped (PID {pid})'})
|
||||||
|
else:
|
||||||
|
self.send_json({'success': False, 'error': 'Process not in workspace'}, 403)
|
||||||
|
else:
|
||||||
|
self.send_json({'success': True, 'message': 'No running process found'})
|
||||||
|
except Exception as e:
|
||||||
|
self.send_json({'success': False, 'error': str(e)}, 500)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.send_json({'success': False, 'error': str(e)}, 500)
|
||||||
|
|
||||||
|
def handle_workspace_git_diff(self):
|
||||||
|
"""Get git diff for a workspace project."""
|
||||||
|
try:
|
||||||
|
parsed = urlparse(self.path)
|
||||||
|
params = parse_qs(parsed.query)
|
||||||
|
project_name = params.get('project', [''])[0]
|
||||||
|
|
||||||
|
project_dir = self._validate_project(project_name)
|
||||||
|
if not project_dir:
|
||||||
|
self.send_json({'error': 'Invalid project'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not (project_dir / '.git').exists():
|
||||||
|
self.send_json({'error': 'Not a git repository'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
status = subprocess.run(
|
||||||
|
['git', 'status', '--short'],
|
||||||
|
cwd=str(project_dir), capture_output=True, text=True, timeout=10
|
||||||
|
).stdout.strip()
|
||||||
|
|
||||||
|
diff = subprocess.run(
|
||||||
|
['git', 'diff'],
|
||||||
|
cwd=str(project_dir), capture_output=True, text=True, timeout=10
|
||||||
|
).stdout
|
||||||
|
|
||||||
|
diff_cached = subprocess.run(
|
||||||
|
['git', 'diff', '--cached'],
|
||||||
|
cwd=str(project_dir), capture_output=True, text=True, timeout=10
|
||||||
|
).stdout
|
||||||
|
|
||||||
|
combined_diff = ''
|
||||||
|
if diff_cached:
|
||||||
|
combined_diff += '=== Staged Changes ===\n' + diff_cached
|
||||||
|
if diff:
|
||||||
|
if combined_diff:
|
||||||
|
combined_diff += '\n'
|
||||||
|
combined_diff += '=== Unstaged Changes ===\n' + diff
|
||||||
|
|
||||||
|
self.send_json({
|
||||||
|
'project': project_name,
|
||||||
|
'status': status,
|
||||||
|
'diff': combined_diff,
|
||||||
|
'hasDiff': bool(status)
|
||||||
|
})
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
self.send_json({'error': 'Timeout'}, 500)
|
||||||
|
except Exception as e:
|
||||||
|
self.send_json({'error': str(e)}, 500)
|
||||||
|
|
||||||
|
def handle_workspace_git_commit(self):
|
||||||
|
"""Commit all changes in a workspace project."""
|
||||||
|
try:
|
||||||
|
data = self._read_post_json()
|
||||||
|
project_name = data.get('project', '')
|
||||||
|
message = data.get('message', '').strip()
|
||||||
|
|
||||||
|
project_dir = self._validate_project(project_name)
|
||||||
|
if not project_dir:
|
||||||
|
self.send_json({'success': False, 'error': 'Invalid project'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not (project_dir / '.git').exists():
|
||||||
|
self.send_json({'success': False, 'error': 'Not a git repository'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if there's anything to commit
|
||||||
|
porcelain = subprocess.run(
|
||||||
|
['git', 'status', '--porcelain'],
|
||||||
|
cwd=str(project_dir), capture_output=True, text=True, timeout=10
|
||||||
|
).stdout.strip()
|
||||||
|
|
||||||
|
if not porcelain:
|
||||||
|
self.send_json({'success': False, 'error': 'Nothing to commit'})
|
||||||
|
return
|
||||||
|
|
||||||
|
files_changed = len([l for l in porcelain.split('\n') if l.strip()])
|
||||||
|
|
||||||
|
# Auto-message if empty
|
||||||
|
if not message:
|
||||||
|
now = datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||||
|
message = f'Update: {now} ({files_changed} files)'
|
||||||
|
|
||||||
|
# Stage all and commit
|
||||||
|
subprocess.run(
|
||||||
|
['git', 'add', '-A'],
|
||||||
|
cwd=str(project_dir), capture_output=True, text=True, timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'commit', '-m', message],
|
||||||
|
cwd=str(project_dir), capture_output=True, text=True, timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
output = result.stdout + result.stderr
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
self.send_json({
|
||||||
|
'success': True,
|
||||||
|
'message': message,
|
||||||
|
'output': output,
|
||||||
|
'filesChanged': files_changed
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
self.send_json({'success': False, 'error': output or 'Commit failed'})
|
||||||
|
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 _ensure_gitea_remote(self, project_dir, project_name):
|
||||||
|
"""Create Gitea repo and add remote if no origin exists. Returns (ok, message)."""
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
if not GITEA_TOKEN:
|
||||||
|
return False, 'GITEA_TOKEN not set'
|
||||||
|
|
||||||
|
# Create repo via Gitea API
|
||||||
|
api_url = f'{GITEA_URL}/api/v1/orgs/{GITEA_ORG}/repos'
|
||||||
|
payload = json.dumps({'name': project_name, 'private': True, 'auto_init': False}).encode()
|
||||||
|
req = urllib.request.Request(api_url, data=payload, method='POST', headers={
|
||||||
|
'Authorization': f'token {GITEA_TOKEN}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
try:
|
||||||
|
resp = urllib.request.urlopen(req, timeout=15)
|
||||||
|
resp.read()
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
body = e.read().decode(errors='replace')
|
||||||
|
if e.code == 409:
|
||||||
|
pass # repo already exists, fine
|
||||||
|
else:
|
||||||
|
return False, f'Gitea API error {e.code}: {body}'
|
||||||
|
|
||||||
|
# Add remote with token auth
|
||||||
|
remote_url = f'{GITEA_URL}/{GITEA_ORG}/{project_name}.git'
|
||||||
|
# Insert token into URL for push auth
|
||||||
|
auth_url = remote_url.replace('https://', f'https://gitea:{GITEA_TOKEN}@')
|
||||||
|
subprocess.run(
|
||||||
|
['git', 'remote', 'add', 'origin', auth_url],
|
||||||
|
cwd=str(project_dir), capture_output=True, text=True, timeout=5
|
||||||
|
)
|
||||||
|
return True, f'Created repo {GITEA_ORG}/{project_name}'
|
||||||
|
|
||||||
|
def handle_workspace_git_push(self):
|
||||||
|
"""Push a workspace project to its remote, creating Gitea repo if needed."""
|
||||||
|
try:
|
||||||
|
data = self._read_post_json()
|
||||||
|
project_name = data.get('project', '')
|
||||||
|
|
||||||
|
project_dir = self._validate_project(project_name)
|
||||||
|
if not project_dir:
|
||||||
|
self.send_json({'success': False, 'error': 'Invalid project'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not (project_dir / '.git').exists():
|
||||||
|
self.send_json({'success': False, 'error': 'Not a git repository'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
created_msg = ''
|
||||||
|
# Check remote exists, create if not
|
||||||
|
remote_check = subprocess.run(
|
||||||
|
['git', 'remote', 'get-url', 'origin'],
|
||||||
|
cwd=str(project_dir), capture_output=True, text=True, timeout=10
|
||||||
|
)
|
||||||
|
if remote_check.returncode != 0:
|
||||||
|
ok, msg = self._ensure_gitea_remote(project_dir, project_name)
|
||||||
|
if not ok:
|
||||||
|
self.send_json({'success': False, 'error': msg})
|
||||||
|
return
|
||||||
|
created_msg = msg + '\n'
|
||||||
|
|
||||||
|
# Push (set upstream on first push)
|
||||||
|
result = subprocess.run(
|
||||||
|
['git', 'push', '-u', 'origin', 'HEAD'],
|
||||||
|
cwd=str(project_dir), capture_output=True, text=True, timeout=60
|
||||||
|
)
|
||||||
|
|
||||||
|
output = result.stdout + result.stderr
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
self.send_json({'success': True, 'output': created_msg + (output or 'Pushed successfully')})
|
||||||
|
else:
|
||||||
|
self.send_json({'success': False, 'error': output or 'Push failed'})
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
self.send_json({'success': False, 'error': 'Push timeout (60s)'}, 500)
|
||||||
|
except Exception as e:
|
||||||
|
self.send_json({'success': False, 'error': str(e)}, 500)
|
||||||
|
|
||||||
|
def handle_workspace_delete(self):
|
||||||
|
"""Delete a workspace project."""
|
||||||
|
try:
|
||||||
|
data = self._read_post_json()
|
||||||
|
project_name = data.get('project', '')
|
||||||
|
confirm = data.get('confirm', '')
|
||||||
|
|
||||||
|
project_dir = self._validate_project(project_name)
|
||||||
|
if not project_dir:
|
||||||
|
self.send_json({'success': False, 'error': 'Invalid project'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
if confirm != project_name:
|
||||||
|
self.send_json({'success': False, 'error': 'Confirmation does not match project name'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check for running processes
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['pgrep', '-f', f'{project_dir.name}/(main\\.py|ralph)'],
|
||||||
|
capture_output=True, text=True, timeout=5
|
||||||
|
)
|
||||||
|
if result.stdout.strip():
|
||||||
|
self.send_json({'success': False, 'error': 'Project has running processes. Stop them first.'})
|
||||||
|
return
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
pass
|
||||||
|
|
||||||
|
shutil.rmtree(str(project_dir))
|
||||||
|
|
||||||
|
self.send_json({
|
||||||
|
'success': True,
|
||||||
|
'message': f'Project {project_name} deleted'
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
self.send_json({'success': False, 'error': str(e)}, 500)
|
||||||
|
|
||||||
|
def handle_workspace_logs(self):
|
||||||
|
"""Get last N lines from a project log."""
|
||||||
|
try:
|
||||||
|
parsed = urlparse(self.path)
|
||||||
|
params = parse_qs(parsed.query)
|
||||||
|
|
||||||
|
project_name = params.get('project', [''])[0]
|
||||||
|
log_type = params.get('type', ['ralph'])[0]
|
||||||
|
lines_count = min(int(params.get('lines', ['100'])[0]), 500)
|
||||||
|
|
||||||
|
project_dir = self._validate_project(project_name)
|
||||||
|
if not project_dir:
|
||||||
|
self.send_json({'error': 'Invalid project'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
ralph_dir = project_dir / 'scripts' / 'ralph'
|
||||||
|
|
||||||
|
# Determine log file
|
||||||
|
if log_type == 'ralph':
|
||||||
|
log_file = ralph_dir / 'logs' / 'ralph.log'
|
||||||
|
if not log_file.exists():
|
||||||
|
# Try ralph-test.log
|
||||||
|
log_file = ralph_dir / 'logs' / 'ralph-test.log'
|
||||||
|
elif log_type == 'main':
|
||||||
|
log_file = ralph_dir / 'logs' / 'main.log' if ralph_dir.exists() else project_dir / 'main.log'
|
||||||
|
elif log_type == 'progress':
|
||||||
|
log_file = ralph_dir / 'progress.txt'
|
||||||
|
else:
|
||||||
|
# Try iteration log
|
||||||
|
if log_type.startswith('iteration-'):
|
||||||
|
log_file = ralph_dir / 'logs' / f'{log_type}.log'
|
||||||
|
else:
|
||||||
|
self.send_json({'error': 'Invalid log type'}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not log_file.exists():
|
||||||
|
self.send_json({
|
||||||
|
'project': project_name,
|
||||||
|
'type': log_type,
|
||||||
|
'lines': [],
|
||||||
|
'total': 0
|
||||||
|
})
|
||||||
|
return
|
||||||
|
|
||||||
|
# Security: ensure path is within workspace
|
||||||
|
if not str(log_file.resolve()).startswith(str(WORKSPACE_DIR)):
|
||||||
|
self.send_json({'error': 'Access denied'}, 403)
|
||||||
|
return
|
||||||
|
|
||||||
|
content = log_file.read_text(encoding='utf-8', errors='replace')
|
||||||
|
all_lines = content.split('\n')
|
||||||
|
total = len(all_lines)
|
||||||
|
last_lines = all_lines[-lines_count:] if len(all_lines) > lines_count else all_lines
|
||||||
|
|
||||||
|
self.send_json({
|
||||||
|
'project': project_name,
|
||||||
|
'type': log_type,
|
||||||
|
'lines': last_lines,
|
||||||
|
'total': total
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
self.send_json({'error': str(e)}, 500)
|
||||||
|
|
||||||
def handle_youtube(self):
|
def handle_youtube(self):
|
||||||
try:
|
try:
|
||||||
content_length = int(self.headers['Content-Length'])
|
content_length = int(self.headers['Content-Length'])
|
||||||
|
|||||||
@@ -838,6 +838,10 @@
|
|||||||
<i data-lucide="layout-list"></i>
|
<i data-lucide="layout-list"></i>
|
||||||
<span>Tasks</span>
|
<span>Tasks</span>
|
||||||
</a>
|
</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">
|
<a href="/echo/notes.html" class="nav-item">
|
||||||
<i data-lucide="file-text"></i>
|
<i data-lucide="file-text"></i>
|
||||||
<span>KB</span>
|
<span>KB</span>
|
||||||
|
|||||||
@@ -1063,6 +1063,10 @@
|
|||||||
<i data-lucide="layout-dashboard"></i>
|
<i data-lucide="layout-dashboard"></i>
|
||||||
<span>Dashboard</span>
|
<span>Dashboard</span>
|
||||||
</a>
|
</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">
|
<a href="/echo/notes.html" class="nav-item">
|
||||||
<i data-lucide="file-text"></i>
|
<i data-lucide="file-text"></i>
|
||||||
<span>KB</span>
|
<span>KB</span>
|
||||||
|
|||||||
@@ -688,6 +688,10 @@
|
|||||||
<i data-lucide="layout-list"></i>
|
<i data-lucide="layout-list"></i>
|
||||||
<span>Tasks</span>
|
<span>Tasks</span>
|
||||||
</a>
|
</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 active">
|
<a href="/echo/notes.html" class="nav-item active">
|
||||||
<i data-lucide="file-text"></i>
|
<i data-lucide="file-text"></i>
|
||||||
<span>KB</span>
|
<span>KB</span>
|
||||||
|
|||||||
1021
dashboard/workspace.html
Normal file
1021
dashboard/workspace.html
Normal file
File diff suppressed because it is too large
Load Diff
29
memory/2026-02-09.md
Normal file
29
memory/2026-02-09.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 2026-02-09 (Duminica)
|
||||||
|
|
||||||
|
## Test Ralph Workflow - SUCCES
|
||||||
|
- Creat proiect test `file-monitor` in ~/workspace/
|
||||||
|
- Ralph a completat 4/4 stories in ~12 minute, 4 iteratii
|
||||||
|
- Cost: ~$3.50 (Sonnet pentru implementare)
|
||||||
|
- 8 commits git, 28+ teste pytest
|
||||||
|
- Workflow: PRD generator → prd.json → ralph.sh → Claude Code autonom → commit
|
||||||
|
|
||||||
|
## Simplificare AGENTS.md
|
||||||
|
- Redus sectiunea "Proiecte/Features Workflow" de la ~110 linii la ~15 linii
|
||||||
|
- Detaliile raman in cron jobs (evening-report, night-execute)
|
||||||
|
|
||||||
|
## Actualizare Evening Report
|
||||||
|
- Adaugat context proiecte prioritare: roa2web, Chatbot Maria
|
||||||
|
- Regula: rapoarte ROA → feature roa2web, NU proiect separat
|
||||||
|
- Surse inspiratie: intrebari frecvente clienti, notes, insights
|
||||||
|
- Features existente prioritare fata de proiecte noi
|
||||||
|
|
||||||
|
## Context Marius (interviu proiecte)
|
||||||
|
- **ROA clienti:** rapoarte, interfata web, notificari
|
||||||
|
- **Pain point:** raspunde la intrebari similare de la clienti
|
||||||
|
- **Chatbot Maria:** Flowise LXC 104, document store XML/MD, Groq+Ollama, ngrok→romfast.ro
|
||||||
|
- **Angajat nou:** poate mentine documentatia chatbot (scrie TXT)
|
||||||
|
- **roa2web:** are balanta, facturi, trezorerie; lipsesc validari ANAF, facturare valuta
|
||||||
|
- **Vending Master:** trebuie verificat rapoarte financiare XLSX
|
||||||
|
|
||||||
|
## Prompt creat
|
||||||
|
- `prompts/workspace-panel.md` - panou Workspace Projects pentru dashboard Echo
|
||||||
@@ -21,6 +21,34 @@
|
|||||||
"video": "",
|
"video": "",
|
||||||
"tldr": "Monica Ion lucrează cu Mark pe relația cu angajații și frica de pierdere. Mark vine agitat cu teama de a pierde un angajat tehnic genial. Monica folosește **legea transformării** pentru a vindeca dure..."
|
"tldr": "Monica Ion lucrează cu Mark pe relația cu angajații și frica de pierdere. Mark vine agitat cu teama de a pierde un angajat tehnic genial. Monica folosește **legea transformării** pentru a vindeca dure..."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "notes-data/tools/ralph-workflow.md",
|
||||||
|
"title": "Ralph Workflow - Sistem Complet",
|
||||||
|
"date": "2026-02-09",
|
||||||
|
"tags": [],
|
||||||
|
"domains": [],
|
||||||
|
"types": [],
|
||||||
|
"category": "tools",
|
||||||
|
"project": null,
|
||||||
|
"subdir": null,
|
||||||
|
"video": "",
|
||||||
|
"tldr": "**Next:** Integrare night-execute"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "memory/2026-02-09.md",
|
||||||
|
"title": "2026-02-09 (Duminica)",
|
||||||
|
"date": "2026-02-09",
|
||||||
|
"tags": [],
|
||||||
|
"domains": [],
|
||||||
|
"types": [
|
||||||
|
"memory"
|
||||||
|
],
|
||||||
|
"category": "memory",
|
||||||
|
"project": null,
|
||||||
|
"subdir": null,
|
||||||
|
"video": "",
|
||||||
|
"tldr": "- `prompts/workspace-panel.md` - panou Workspace Projects pentru dashboard Echo"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "notes-data/coaching/2026-02-08-dimineata.md",
|
"file": "notes-data/coaching/2026-02-08-dimineata.md",
|
||||||
"title": "Gândul de dimineață - 2026-02-08",
|
"title": "Gândul de dimineață - 2026-02-08",
|
||||||
@@ -2193,8 +2221,8 @@
|
|||||||
"title": "Proiect: Vending Master - Integrare Website → ROA",
|
"title": "Proiect: Vending Master - Integrare Website → ROA",
|
||||||
"date": "2026-01-30",
|
"date": "2026-01-30",
|
||||||
"tags": [
|
"tags": [
|
||||||
"vending-master",
|
"integrare",
|
||||||
"integrare"
|
"vending-master"
|
||||||
],
|
],
|
||||||
"domains": [
|
"domains": [
|
||||||
"work"
|
"work"
|
||||||
@@ -2644,7 +2672,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stats": {
|
"stats": {
|
||||||
"total": 149,
|
"total": 151,
|
||||||
"by_domain": {
|
"by_domain": {
|
||||||
"work": 41,
|
"work": 41,
|
||||||
"health": 25,
|
"health": 25,
|
||||||
@@ -2663,9 +2691,9 @@
|
|||||||
"projects": 44,
|
"projects": 44,
|
||||||
"reflectii": 3,
|
"reflectii": 3,
|
||||||
"retete": 1,
|
"retete": 1,
|
||||||
"tools": 4,
|
"tools": 5,
|
||||||
"youtube": 42,
|
"youtube": 42,
|
||||||
"memory": 15
|
"memory": 16
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domains": [
|
"domains": [
|
||||||
|
|||||||
@@ -71,9 +71,9 @@
|
|||||||
|
|
||||||
### 3 Întrebări Anti-Blocaj
|
### 3 Întrebări Anti-Blocaj
|
||||||
- Când te simți blocat/victimă, întreabă-te rapid:
|
- Când te simți blocat/victimă, întreabă-te rapid:
|
||||||
1. "Ce depinde de MINE acum?"
|
1. **Ce pot controla eu în situația asta?**
|
||||||
2. "Care e cel mai mic pas pe care pot să-l inițiez?"
|
2. **Ce aleg să fac acum?**
|
||||||
3. "Ce pot face EU chiar acum?"
|
3. **Ce vreau să fie diferit - și ce fac pentru asta?**
|
||||||
- **Rezultat:** Mută atenția de la ce NU poți controla la ce POȚI - scoate din paralizie instant
|
- **Rezultat:** Mută atenția de la ce NU poți controla la ce POȚI - scoate din paralizie instant
|
||||||
- **Sursă:** [Zoltan Vereș - Starea de Victimă](https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/2026-02-02_zoltan-veres-victima-complet.md)
|
- **Sursă:** [Zoltan Vereș - Starea de Victimă](https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/2026-02-02_zoltan-veres-victima-complet.md)
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,42 @@ fi
|
|||||||
# Creează directoare necesare
|
# Creează directoare necesare
|
||||||
mkdir -p "$SCRIPT_DIR/logs" "$SCRIPT_DIR/archive" "$SCRIPT_DIR/screenshots"
|
mkdir -p "$SCRIPT_DIR/logs" "$SCRIPT_DIR/archive" "$SCRIPT_DIR/screenshots"
|
||||||
|
|
||||||
|
# Creează .gitignore dacă nu există
|
||||||
|
if [ ! -f "$PROJECT_DIR/.gitignore" ]; then
|
||||||
|
cat > "$PROJECT_DIR/.gitignore" << 'GITIGNORE'
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.pyo
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Virtual environment
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# Ralph runtime
|
||||||
|
scripts/ralph/.ralph.pid
|
||||||
|
scripts/ralph/.last-branch
|
||||||
|
scripts/ralph/logs/
|
||||||
|
scripts/ralph/screenshots/
|
||||||
|
scripts/ralph/archive/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
GITIGNORE
|
||||||
|
echo "Created .gitignore"
|
||||||
|
fi
|
||||||
|
|
||||||
# Inițializare progress file dacă nu există
|
# Inițializare progress file dacă nu există
|
||||||
if [ ! -f "$PROGRESS_FILE" ]; then
|
if [ ! -f "$PROGRESS_FILE" ]; then
|
||||||
echo "# Ralph Progress Log" > "$PROGRESS_FILE"
|
echo "# Ralph Progress Log" > "$PROGRESS_FILE"
|
||||||
|
|||||||
Reference in New Issue
Block a user