2cab3c2b02
- registry.yaml maps codes (w1-1, w1.git.001) to sessions and stable paths
- Markdown files named {code}--{slug}.md for Cursor @ picker
- Selective export/import via --context and --mask
- cam code list|set|show for manual code assignment
- Re-export updates same file per context code
Co-authored-by: Cursor <cursoragent@cursor.com>
103 lines
2.8 KiB
Python
103 lines
2.8 KiB
Python
"""Import archived transcripts back into Cursor agent-transcripts folder."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
from cam.config import CamConfig
|
|
from cam.paths import cursor_transcript_path
|
|
from cam.registry import ContextRegistry
|
|
|
|
|
|
@dataclass
|
|
class ImportResult:
|
|
copied: int = 0
|
|
skipped: int = 0
|
|
paths: list[str] | None = None
|
|
|
|
def __post_init__(self) -> None:
|
|
if self.paths is None:
|
|
self.paths = []
|
|
|
|
|
|
def import_transcripts(
|
|
config: CamConfig,
|
|
*,
|
|
dry_run: bool = False,
|
|
source: Path | None = None,
|
|
context: str | None = None,
|
|
context_mask: str | None = None,
|
|
) -> ImportResult:
|
|
archive_root = (source or config.export_dir).resolve()
|
|
raw_root = archive_root / "raw"
|
|
transcripts_root = config.transcripts_dir
|
|
|
|
if not raw_root.is_dir():
|
|
raise FileNotFoundError(f"Archive raw/ folder not found: {raw_root}")
|
|
|
|
registry = ContextRegistry(archive_root, config)
|
|
registry.load()
|
|
|
|
allowed = registry.sessions_matching(pattern=context_mask, explicit=context)
|
|
if context or context_mask:
|
|
if not allowed:
|
|
raise ValueError(f"No contexts match filter: {context or context_mask}")
|
|
allowed_ids = {r.session_id for r in allowed}
|
|
else:
|
|
allowed_ids = {r.session_id for r in registry.contexts.values()}
|
|
|
|
result = ImportResult()
|
|
|
|
for record in registry.contexts.values():
|
|
if record.session_id not in allowed_ids:
|
|
continue
|
|
raw_path = archive_root / record.raw_file
|
|
if not raw_path.is_file():
|
|
continue
|
|
|
|
dest = cursor_transcript_path(
|
|
transcripts_root,
|
|
record.session_id,
|
|
record.parent_session_id,
|
|
)
|
|
result.paths.append(f"{record.code} -> {dest}")
|
|
|
|
if dest.exists() and not config.overwrite_existing:
|
|
result.skipped += 1
|
|
continue
|
|
|
|
if dry_run:
|
|
result.copied += 1
|
|
continue
|
|
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(raw_path, dest)
|
|
result.copied += 1
|
|
|
|
return result
|
|
|
|
|
|
def list_archive_contexts(
|
|
archive_root: Path,
|
|
config: CamConfig,
|
|
*,
|
|
context_mask: str | None = None,
|
|
) -> list[tuple[str, str, str, Path | None]]:
|
|
registry = ContextRegistry(archive_root, config)
|
|
registry.load()
|
|
rows: list[tuple[str, str, str, Path | None]] = []
|
|
for record in registry.sessions_matching(pattern=context_mask):
|
|
raw_path = archive_root / record.raw_file
|
|
rows.append(
|
|
(
|
|
record.code,
|
|
record.session_id,
|
|
record.title,
|
|
raw_path if raw_path.is_file() else None,
|
|
)
|
|
)
|
|
rows.sort(key=lambda r: r[0])
|
|
return rows
|