Add canvas-lms and nextdns-api Community skills#53
Add canvas-lms and nextdns-api Community skills#53ShadowsDistant wants to merge 2 commits intozocomputer:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds two new Community skills (“canvas-lms” and “nextdns-api”) by introducing Python CLI scripts plus SKILL.md documentation (and a Canvas API endpoints reference) so Zo can query/manage Canvas LMS and NextDNS via their respective APIs.
Changes:
- Added a NextDNS API CLI (
nextdns.py) supporting profiles, settings, lists, blocklists, logs, and analytics. - Added a Canvas LMS CLI (
canvas_cli.py) plus a Canvas REST API endpoints reference document. - Added SKILL.md documentation for both skills with setup + usage examples.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| Community/nextdns-api/scripts/nextdns.py | Implements the NextDNS CLI commands and API calls. |
| Community/nextdns-api/SKILL.md | Documents NextDNS skill setup and CLI usage. |
| Community/canvas-lms/scripts/canvas_cli.py | Implements the Canvas CLI commands, HTTP helpers, and pagination. |
| Community/canvas-lms/references/api_endpoints.md | Provides Canvas REST API endpoint reference. |
| Community/canvas-lms/SKILL.md | Documents Canvas skill setup, usage patterns, and examples. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def get(path: str, params: dict | None = None) -> list | dict: | ||
| url = api_url(path) | ||
| if params: | ||
| qs = "&".join(f"{k}={v}" for k, v in params.items() if v is not None) | ||
| if qs: | ||
| url = f"{url}?{qs}" |
There was a problem hiding this comment.
Query-string construction is not URL-encoded and doesn’t support list/array params. For example --include (a list) will be rendered as a Python list string, and keys like context_codes[] won’t be encoded correctly. Use urllib.parse.urlencode(..., doseq=True) (or equivalent) to build the query string safely.
There was a problem hiding this comment.
Fixed. Replaced the manual string-formatting query builder with urllib.parse.urlencode(..., doseq=True). This properly encodes special characters and correctly handles list params like --include (action='append') and keys like 'context_codes[]'.
| if args.student_id and args.assignment_id: | ||
| r = get(f"/courses/{args.course_id}/students/{args.student_id}/submissions/{args.assignment_id}") | ||
| elif args.student_id: | ||
| r = get(f"/courses/{args.course_id}/students/{args.student_id}/submissions", {"assignment_ids": args.assignment_id, " graded": args.graded}) |
There was a problem hiding this comment.
There’s a typo in the query params dict: the key is " graded" (leading space), so the intended graded filter won’t be sent to the API.
| r = get(f"/courses/{args.course_id}/students/{args.student_id}/submissions", {"assignment_ids": args.assignment_id, " graded": args.graded}) | |
| r = get(f"/courses/{args.course_id}/students/{args.student_id}/submissions", {"assignment_ids": args.assignment_id, "graded": args.graded}) |
There was a problem hiding this comment.
Fixed. Changed ' graded' (with leading space) to 'graded' in the cmd_submissions query params dict. The space was a typo that would have silently dropped the graded filter from the API request.
| import sys | ||
| import argparse | ||
| import json | ||
| import requests | ||
| from datetime import datetime, timezone |
There was a problem hiding this comment.
Several imports are unused (sys, and datetime/timezone). Removing them avoids lint noise and makes it clearer what the script depends on.
| import sys | |
| import argparse | |
| import json | |
| import requests | |
| from datetime import datetime, timezone | |
| import argparse | |
| import json | |
| import requests |
There was a problem hiding this comment.
Fixed. Removed sys, datetime, and timezone imports. sys was redundant since errors are handled via requests exceptions and SystemExit. datetime/timezone were completely unused.
| url = f"{BASE_URL}/profiles/{profile_id}/logs/download" | ||
| if not args.no_redirect: | ||
| params = {"redirect": "0"} | ||
| resp = requests.get(url, headers=get_headers(), params=params) | ||
| resp.raise_for_status() | ||
| data = resp.json() | ||
| download_url = data.get("url") | ||
| else: | ||
| download_url = url |
There was a problem hiding this comment.
The --no-redirect flag behavior is inverted: when --no-redirect is NOT set, the code requests redirect=0 (no redirect) and returns a JSON URL; when --no-redirect IS set, it uses the redirecting endpoint. Swap this logic so the flag matches its help text.
There was a problem hiding this comment.
Fixed. Swapped the if/else logic so that: --no-redirect (flag is set) → returns redirecting URL directly for caller to follow; default (flag not set) → requests redirect=0 and returns JSON download URL. Flag behavior now matches its help text.
…ed imports, no-redirect logic
This PR adds two new Community skills:
canvas-lms
Fetch, create, and manage Canvas LMS data including courses, assignments, grades, enrollments, announcements, modules, discussions, quizzes, pages, files, and calendar events.
nextdns-api
Query and manage NextDNS via API - full feature set including logs, analytics, profile management, blocklists, and settings.
Both skills follow the Agent Skills specification with proper SKILL.md frontmatter and metadata.author field.