Make KB Capture project-centric
- Sessions now live inside project folders: <Project>/_capture/<session>/ - Project picker dropdown (scans projects folder) - Auto-discovers projects with KB/ folder or _context.md - Windows: D:/ATODrive/Projects - Linux: ~/obsidian-vault/2-Projects This aligns with the KB structure where Mario updates: - KB/dev/gen-XXX.md (session captures) - Images/screenshot-sessions/ (frames)
This commit is contained in:
@@ -116,27 +116,61 @@ class SessionManager:
|
||||
"""
|
||||
Manages recording sessions and clips.
|
||||
|
||||
Directory structure:
|
||||
sessions/
|
||||
├── <session-id>/
|
||||
│ ├── session.json # Session metadata
|
||||
│ ├── clips/
|
||||
│ │ ├── clip-001.mp4
|
||||
│ │ ├── clip-002.mp4
|
||||
│ │ └── ...
|
||||
│ └── export/ # Final export for Clawdbot
|
||||
│ ├── merged.mp4
|
||||
│ ├── transcript.json
|
||||
│ └── metadata.json
|
||||
Project-centric structure:
|
||||
/2-Projects/<ProjectName>/
|
||||
├── KB/
|
||||
│ └── dev/ # gen-XXX.md session captures (Mario creates)
|
||||
├── Images/
|
||||
│ └── screenshot-sessions/ # Frames organized by session
|
||||
└── _capture/ # Session staging
|
||||
└── <session-id>/
|
||||
├── session.json
|
||||
├── clips/
|
||||
│ ├── clip-001.mp4
|
||||
│ └── ...
|
||||
└── clawdbot_export/ # Ready for Mario
|
||||
├── merged.mp4
|
||||
├── transcript.json
|
||||
└── metadata.json
|
||||
"""
|
||||
|
||||
def __init__(self, base_path: Path):
|
||||
self.base_path = Path(base_path)
|
||||
self.sessions_dir = self.base_path / "sessions"
|
||||
self.sessions_dir.mkdir(parents=True, exist_ok=True)
|
||||
def __init__(self, projects_root: Path):
|
||||
"""
|
||||
Initialize session manager.
|
||||
|
||||
Args:
|
||||
projects_root: Path to projects folder (e.g., /2-Projects/ or D:/ATODrive/Projects/)
|
||||
"""
|
||||
self.projects_root = Path(projects_root)
|
||||
self.current_session: Optional[Session] = None
|
||||
self.current_clip: Optional[Clip] = None
|
||||
self._current_project_path: Optional[Path] = None
|
||||
|
||||
def list_projects(self) -> List[str]:
|
||||
"""List available projects (folders in projects_root)."""
|
||||
projects = []
|
||||
if self.projects_root.exists():
|
||||
for p in sorted(self.projects_root.iterdir()):
|
||||
if p.is_dir() and not p.name.startswith((".", "_")):
|
||||
# Check if it looks like a project (has KB folder or _context.md)
|
||||
if (p / "KB").exists() or (p / "_context.md").exists():
|
||||
projects.append(p.name)
|
||||
return projects
|
||||
|
||||
def get_project_path(self, project: str) -> Path:
|
||||
"""Get full path to a project."""
|
||||
return self.projects_root / project
|
||||
|
||||
def get_capture_dir(self, project: str) -> Path:
|
||||
"""Get the _capture directory for a project."""
|
||||
return self.get_project_path(project) / "_capture"
|
||||
|
||||
@property
|
||||
def sessions_dir(self) -> Path:
|
||||
"""Current project's capture directory."""
|
||||
if self._current_project_path:
|
||||
return self._current_project_path / "_capture"
|
||||
raise RuntimeError("No project selected")
|
||||
|
||||
def start_session(
|
||||
self,
|
||||
@@ -144,7 +178,12 @@ class SessionManager:
|
||||
project: str,
|
||||
session_type: SessionType = SessionType.DESIGN,
|
||||
) -> Session:
|
||||
"""Start a new recording session."""
|
||||
"""Start a new recording session within a project."""
|
||||
# Set current project
|
||||
self._current_project_path = self.get_project_path(project)
|
||||
if not self._current_project_path.exists():
|
||||
raise ValueError(f"Project not found: {project}")
|
||||
|
||||
session_id = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
|
||||
session = Session(
|
||||
@@ -155,8 +194,9 @@ class SessionManager:
|
||||
created_at=datetime.now(),
|
||||
)
|
||||
|
||||
# Create session directory
|
||||
session_dir = self.sessions_dir / session_id
|
||||
# Create session directory in project's _capture folder
|
||||
capture_dir = self._current_project_path / "_capture"
|
||||
session_dir = capture_dir / session_id
|
||||
session_dir.mkdir(parents=True, exist_ok=True)
|
||||
(session_dir / "clips").mkdir(exist_ok=True)
|
||||
|
||||
@@ -299,15 +339,26 @@ class SessionManager:
|
||||
return Session.from_dict(json.load(f))
|
||||
return None
|
||||
|
||||
def list_sessions(self) -> List[Session]:
|
||||
"""List all sessions."""
|
||||
def list_sessions(self, project: Optional[str] = None) -> List[Session]:
|
||||
"""List sessions, optionally filtered by project."""
|
||||
sessions = []
|
||||
for session_dir in sorted(self.sessions_dir.iterdir(), reverse=True):
|
||||
if session_dir.is_dir():
|
||||
session_file = session_dir / "session.json"
|
||||
if session_file.exists():
|
||||
with open(session_file) as f:
|
||||
sessions.append(Session.from_dict(json.load(f)))
|
||||
|
||||
if project:
|
||||
# List sessions for specific project
|
||||
capture_dir = self.get_capture_dir(project)
|
||||
if capture_dir.exists():
|
||||
for session_dir in sorted(capture_dir.iterdir(), reverse=True):
|
||||
if session_dir.is_dir():
|
||||
session_file = session_dir / "session.json"
|
||||
if session_file.exists():
|
||||
with open(session_file) as f:
|
||||
sessions.append(Session.from_dict(json.load(f)))
|
||||
else:
|
||||
# List sessions across all projects
|
||||
for proj in self.list_projects():
|
||||
sessions.extend(self.list_sessions(proj))
|
||||
sessions.sort(key=lambda s: s.created_at, reverse=True)
|
||||
|
||||
return sessions
|
||||
|
||||
def get_session_dir(self, session_id: str) -> Path:
|
||||
|
||||
Reference in New Issue
Block a user