scripts/canvas_api.py
scripts/canvas_api.pyBrowse 2 files
1,179 tokens
5,224 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1#!/usr/bin/env python32"""Canvas LMS API CLI for Hermes Agent.3 4A thin CLI wrapper around the Canvas REST API.5Authenticates using a personal access token from environment variables.6 7Usage:8 python canvas_api.py list_courses [--per-page N] [--enrollment-state STATE]9 python canvas_api.py list_assignments COURSE_ID [--per-page N] [--order-by FIELD]10"""11 12import argparse13import json14import os15import sys16 17import requests18 19CANVAS_API_TOKEN = os.environ.get("CANVAS_API_TOKEN", "")20CANVAS_BASE_URL = os.environ.get("CANVAS_BASE_URL", "").rstrip("/")21 22 23def _check_config():24 """Validate required environment variables are set."""25 missing = []26 if not CANVAS_API_TOKEN:27 missing.append("CANVAS_API_TOKEN")28 if not CANVAS_BASE_URL:29 missing.append("CANVAS_BASE_URL")30 if missing:31 hermes_env = os.path.join(32 os.environ.get("HERMES_HOME", os.path.expanduser("~/.hermes")), ".env"33 )34 print(35 f"Missing required environment variables: {', '.join(missing)}\n"36 f"Set them in {hermes_env} or export them in your shell.\n"37 "See the canvas skill SKILL.md for setup instructions.",38 file=sys.stderr,39 )40 sys.exit(1)41 42 43def _headers():44 return {"Authorization": f"Bearer {CANVAS_API_TOKEN}"}45 46 47def _paginated_get(url, params=None, max_items=200):48 """Fetch all pages up to max_items, following Canvas Link headers."""49 results = []50 while url and len(results) < max_items:51 resp = requests.get(url, headers=_headers(), params=params, timeout=30)52 resp.raise_for_status()53 results.extend(resp.json())54 params = None # params are included in the Link URL for subsequent pages55 url = None56 link = resp.headers.get("Link", "")57 for part in link.split(","):58 if 'rel="next"' in part:59 url = part.split(";")[0].strip().strip("<>")60 return results[:max_items]61 62 63# =========================================================================64# Commands65# =========================================================================66 67 68def list_courses(args):69 """List enrolled courses."""70 _check_config()71 url = f"{CANVAS_BASE_URL}/api/v1/courses"72 params = {"per_page": args.per_page}73 if args.enrollment_state:74 params["enrollment_state"] = args.enrollment_state75 try:76 courses = _paginated_get(url, params)77 except requests.HTTPError as e:78 print(f"API error: {e.response.status_code} {e.response.text}", file=sys.stderr)79 sys.exit(1)80 output = [81 {82 "id": c["id"],83 "name": c.get("name", ""),84 "course_code": c.get("course_code", ""),85 "enrollment_term_id": c.get("enrollment_term_id"),86 "start_at": c.get("start_at"),87 "end_at": c.get("end_at"),88 "workflow_state": c.get("workflow_state", ""),89 }90 for c in courses91 ]92 print(json.dumps(output, indent=2))93 94 95def list_assignments(args):96 """List assignments for a course."""97 _check_config()98 url = f"{CANVAS_BASE_URL}/api/v1/courses/{args.course_id}/assignments"99 params = {"per_page": args.per_page}100 if args.order_by:101 params["order_by"] = args.order_by102 try:103 assignments = _paginated_get(url, params)104 except requests.HTTPError as e:105 print(f"API error: {e.response.status_code} {e.response.text}", file=sys.stderr)106 sys.exit(1)107 output = [108 {109 "id": a["id"],110 "name": a.get("name", ""),111 "description": (a.get("description") or "")[:500],112 "due_at": a.get("due_at"),113 "points_possible": a.get("points_possible"),114 "submission_types": a.get("submission_types", []),115 "html_url": a.get("html_url", ""),116 "course_id": a.get("course_id"),117 }118 for a in assignments119 ]120 print(json.dumps(output, indent=2))121 122 123# =========================================================================124# CLI parser125# =========================================================================126 127 128def main():129 parser = argparse.ArgumentParser(130 description="Canvas LMS API CLI for Hermes Agent"131 )132 sub = parser.add_subparsers(dest="command", required=True)133 134 # --- list_courses ---135 p = sub.add_parser("list_courses", help="List enrolled courses")136 p.add_argument("--per-page", type=int, default=50, help="Results per page (default 50)")137 p.add_argument(138 "--enrollment-state",139 default="",140 help="Filter by enrollment state (active, invited_or_pending, completed)",141 )142 p.set_defaults(func=list_courses)143 144 # --- list_assignments ---145 p = sub.add_parser("list_assignments", help="List assignments for a course")146 p.add_argument("course_id", help="Canvas course ID")147 p.add_argument("--per-page", type=int, default=50, help="Results per page (default 50)")148 p.add_argument(149 "--order-by",150 default="",151 help="Order by field (due_at, name, position)",152 )153 p.set_defaults(func=list_assignments)154 155 args = parser.parse_args()156 args.func(args)157 158 159if __name__ == "__main__":160 main()161