canvas

Fetch Canvas LMS courses and assignments via API token.

  • Canvas
  • LMS
  • Education
  • Courses
  • Assignments

Declared platforms: linux · macos · windows

Install
npx skills add 'https://github.com/NousResearch/hermes-agent/tree/main/optional-skills/productivity/canvas'
Download bundle ↓
main · 24fd22bScanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

scripts/canvas_api.py

scripts/canvas_api.pyBrowse 2 files
View on GitHub
← Back to SKILL.md
#!/usr/bin/env python3"""Canvas LMS API CLI for Hermes Agent. A thin CLI wrapper around the Canvas REST API.Authenticates using a personal access token from environment variables. Usage:  python canvas_api.py list_courses [--per-page N] [--enrollment-state STATE]  python canvas_api.py list_assignments COURSE_ID [--per-page N] [--order-by FIELD]""" import argparseimport jsonimport osimport sys import requests CANVAS_API_TOKEN = os.environ.get("CANVAS_API_TOKEN", "")CANVAS_BASE_URL = os.environ.get("CANVAS_BASE_URL", "").rstrip("/")  def _check_config():    """Validate required environment variables are set."""    missing = []    if not CANVAS_API_TOKEN:        missing.append("CANVAS_API_TOKEN")    if not CANVAS_BASE_URL:        missing.append("CANVAS_BASE_URL")    if missing:        hermes_env = os.path.join(            os.environ.get("HERMES_HOME", os.path.expanduser("~/.hermes")), ".env"        )        print(            f"Missing required environment variables: {', '.join(missing)}\n"            f"Set them in {hermes_env} or export them in your shell.\n"            "See the canvas skill SKILL.md for setup instructions.",            file=sys.stderr,        )        sys.exit(1)  def _headers():    return {"Authorization": f"Bearer {CANVAS_API_TOKEN}"}  def _paginated_get(url, params=None, max_items=200):    """Fetch all pages up to max_items, following Canvas Link headers."""    results = []    while url and len(results) < max_items:        resp = requests.get(url, headers=_headers(), params=params, timeout=30)        resp.raise_for_status()        results.extend(resp.json())        params = None  # params are included in the Link URL for subsequent pages        url = None        link = resp.headers.get("Link", "")        for part in link.split(","):            if 'rel="next"' in part:                url = part.split(";")[0].strip().strip("<>")    return results[:max_items]  # =========================================================================# Commands# =========================================================================  def list_courses(args):    """List enrolled courses."""    _check_config()    url = f"{CANVAS_BASE_URL}/api/v1/courses"    params = {"per_page": args.per_page}    if args.enrollment_state:        params["enrollment_state"] = args.enrollment_state    try:        courses = _paginated_get(url, params)    except requests.HTTPError as e:        print(f"API error: {e.response.status_code} {e.response.text}", file=sys.stderr)        sys.exit(1)    output = [        {            "id": c["id"],            "name": c.get("name", ""),            "course_code": c.get("course_code", ""),            "enrollment_term_id": c.get("enrollment_term_id"),            "start_at": c.get("start_at"),            "end_at": c.get("end_at"),            "workflow_state": c.get("workflow_state", ""),        }        for c in courses    ]    print(json.dumps(output, indent=2))  def list_assignments(args):    """List assignments for a course."""    _check_config()    url = f"{CANVAS_BASE_URL}/api/v1/courses/{args.course_id}/assignments"    params = {"per_page": args.per_page}    if args.order_by:        params["order_by"] = args.order_by    try:        assignments = _paginated_get(url, params)    except requests.HTTPError as e:        print(f"API error: {e.response.status_code} {e.response.text}", file=sys.stderr)        sys.exit(1)    output = [        {            "id": a["id"],            "name": a.get("name", ""),            "description": (a.get("description") or "")[:500],            "due_at": a.get("due_at"),            "points_possible": a.get("points_possible"),            "submission_types": a.get("submission_types", []),            "html_url": a.get("html_url", ""),            "course_id": a.get("course_id"),        }        for a in assignments    ]    print(json.dumps(output, indent=2))  # =========================================================================# CLI parser# =========================================================================  def main():    parser = argparse.ArgumentParser(        description="Canvas LMS API CLI for Hermes Agent"    )    sub = parser.add_subparsers(dest="command", required=True)     # --- list_courses ---    p = sub.add_parser("list_courses", help="List enrolled courses")    p.add_argument("--per-page", type=int, default=50, help="Results per page (default 50)")    p.add_argument(        "--enrollment-state",        default="",        help="Filter by enrollment state (active, invited_or_pending, completed)",    )    p.set_defaults(func=list_courses)     # --- list_assignments ---    p = sub.add_parser("list_assignments", help="List assignments for a course")    p.add_argument("course_id", help="Canvas course ID")    p.add_argument("--per-page", type=int, default=50, help="Results per page (default 50)")    p.add_argument(        "--order-by",        default="",        help="Order by field (due_at, name, position)",    )    p.set_defaults(func=list_assignments)     args = parser.parse_args()    args.func(args)  if __name__ == "__main__":    main() 
Referenced from SKILL.md