Initial release v0.0.1 — Cursor Agents Manager.
Export/import Cursor agent transcripts between workstations. YAML per-workstation config, Markdown archive, MIT license. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python3
|
||||
"""CAM CLI — Cursor Agents Manager."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
CAM_ROOT = Path(__file__).resolve().parent
|
||||
if str(CAM_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(CAM_ROOT))
|
||||
|
||||
from __version__ import __status__, __version__ # noqa: E402
|
||||
from cam.config import load_config, resolve_config_path # noqa: E402
|
||||
from cam.export import export_transcripts # noqa: E402
|
||||
from cam.import_transcripts import import_transcripts, list_archive_sessions # noqa: E402
|
||||
|
||||
|
||||
def _default_cam_command(config_path: Path | None) -> str:
|
||||
if config_path is not None:
|
||||
rel = config_path
|
||||
try:
|
||||
rel = config_path.relative_to(CAM_ROOT)
|
||||
except ValueError:
|
||||
rel = config_path
|
||||
return f"python tools/cam/cam.py -c {rel} export"
|
||||
return "python tools/cam/cam.py export"
|
||||
|
||||
|
||||
def cmd_paths(config_path: Path) -> int:
|
||||
config = load_config(config_path)
|
||||
print(f"workstation_id: {config.workstation_id}")
|
||||
print(f"workstation_label: {config.workstation_label}")
|
||||
print(f"project_root: {config.project_root}")
|
||||
print(f"cursor_project_slug:{config.cursor_project_slug}")
|
||||
print(f"transcripts_dir: {config.transcripts_dir}")
|
||||
print(f"export_dir: {config.export_dir}")
|
||||
print(f"index_language: {config.index_language}")
|
||||
print(f"config_file: {config.source_path}")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_export(config_path: Path) -> int:
|
||||
config = load_config(config_path)
|
||||
main_count, sub_count = export_transcripts(config, _default_cam_command(config_path))
|
||||
print(
|
||||
f"Exported {main_count} main sessions and {sub_count} subagents "
|
||||
f"to {config.export_dir}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_import(config_path: Path, dry_run: bool, source: Path | None) -> int:
|
||||
config = load_config(config_path)
|
||||
result = import_transcripts(config, dry_run=dry_run, source=source)
|
||||
mode = "Dry-run:" if dry_run else "Imported:"
|
||||
print(
|
||||
f"{mode} {result.copied} file(s) to {config.transcripts_dir}; "
|
||||
f"skipped {result.skipped} existing"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_list(config_path: Path) -> int:
|
||||
config = load_config(config_path)
|
||||
sessions = list_archive_sessions(config.export_dir)
|
||||
if not sessions:
|
||||
print(f"No sessions in {config.export_dir / 'raw'}")
|
||||
return 0
|
||||
for session_id, parent_id, path in sessions:
|
||||
parent = parent_id or "-"
|
||||
print(f"{session_id}\tparent={parent}\t{path}")
|
||||
print(f"Total: {len(sessions)}")
|
||||
return 0
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="cam",
|
||||
description="Cursor Agents Manager — export and import agent transcripts",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-V",
|
||||
"--version",
|
||||
action="version",
|
||||
version=f"%(prog)s {__version__} ({__status__})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
type=Path,
|
||||
help="Path to workstation YAML config (default: auto-detect)",
|
||||
)
|
||||
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
sub.add_parser("paths", help="Show resolved paths from config").set_defaults(
|
||||
handler="paths"
|
||||
)
|
||||
sub.add_parser("export", help="Export Cursor transcripts to project archive").set_defaults(
|
||||
handler="export"
|
||||
)
|
||||
sub.add_parser("list", help="List sessions in export archive").set_defaults(handler="list")
|
||||
|
||||
import_parser = sub.add_parser(
|
||||
"import",
|
||||
help="Import archived transcripts into Cursor agent-transcripts folder",
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would be copied without writing files",
|
||||
)
|
||||
import_parser.add_argument(
|
||||
"--source",
|
||||
type=Path,
|
||||
help="Archive root (default: export.output_dir from config)",
|
||||
)
|
||||
import_parser.set_defaults(handler="import")
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
try:
|
||||
config_path = resolve_config_path(CAM_ROOT, args.config)
|
||||
except FileNotFoundError as exc:
|
||||
print(f"Error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
try:
|
||||
if args.handler == "paths":
|
||||
return cmd_paths(config_path)
|
||||
if args.handler == "export":
|
||||
return cmd_export(config_path)
|
||||
if args.handler == "list":
|
||||
return cmd_list(config_path)
|
||||
if args.handler == "import":
|
||||
return cmd_import(config_path, args.dry_run, args.source)
|
||||
except (FileNotFoundError, ValueError) as exc:
|
||||
print(f"Error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
parser.error(f"Unknown command: {args.handler}")
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user