Metadata-Version: 2.4
Name: canvasctl
Version: 0.3.1
Summary: Canvas in your terminal: a Canvas LMS companion CLI with a local vault, a Claude-powered chat agent, and an MCP server.
Author: Vivek
License: MIT
Keywords: canvas,lms,cli,education
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Education
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Education
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.31
Provides-Extra: agent
Requires-Dist: anthropic>=0.117; extra == "agent"
Provides-Extra: mcp
Requires-Dist: mcp>=1.28; extra == "mcp"
Provides-Extra: all
Requires-Dist: canvasctl[agent,mcp]; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-cov>=4.1; extra == "dev"
Requires-Dist: responses>=0.24; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Dynamic: license-file

<div align="center">

```
                           _         _   _
  ___ __ _ _ ___ ____ _ __| |_ __ __| |_| |
 / __/ _` | ' \ V / _` (_-<  _/ _/ _|  _| |
 \___\__,_|_||_\_/\__,_/__/\__\__\__|\__|_|
```

# canvasctl

**Canvas in your terminal.** A read-focused Canvas LMS companion that mirrors your
courses into a local vault, answers questions with a Claude-powered chat agent, and
plugs into Claude Code / Claude Desktop over MCP.

[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
[![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/vivekp-05/canvasctl/pulls)

</div>

<!-- TODO: demo.gif -- record a short asciinema/gif of `canvasctl setup` -> `today` -> `ask` and drop it here -->

## What is this

`canvasctl` mirrors your Canvas courses — assignments, quizzes, modules, files, and
announcements — into a plain-JSON vault at `~/canvas-vault/`, then gives you fast
terminal answers to "what's due today?", "what changed since yesterday?", and
"export the next 60 days as a calendar." A built-in **chat agent** (`canvasctl ask` /
`canvasctl chat`) uses your own Anthropic API key to answer natural-language
questions by calling read-only tools over that vault. And an **MCP server**
(`canvasctl mcp`) exposes the same operations to Claude Code and Claude Desktop, so
you can ask Claude about your coursework from anywhere.

Everything runs on your machine. The only network calls it makes are to your own
school's Canvas instance and — if you opt into the agent — directly to
`api.anthropic.com` with your own key. It is read-focused by design: there is no
assignment submission and no mutation of Canvas.

## Quick start

```bash
# 1. Install (see alternatives below if you'd rather use pipx or a venv)
curl -fsSL https://raw.githubusercontent.com/vivekp-05/canvasctl/main/install.sh | sh

# 2. Onboard — asks for your Canvas URL + access token, optionally an Anthropic key
canvasctl setup

# 3. Pull your courses into the local vault, then ask away
canvasctl sync
canvasctl today
canvasctl ask "what's due this week?"

# or skip the ceremony entirely:
canvasctl                      # bare command drops into chat
canvasctl what's due friday    # unrecognized text is treated as a question
```

## Install alternatives

**pipx from PyPI** (isolated, on your `$PATH`):

```bash
pipx install "canvasctl[agent,mcp]"
```

**Bleeding edge, straight from git:**

```bash
pipx install "canvasctl[agent,mcp] @ git+https://github.com/vivekp-05/canvasctl.git"
```

Drop the `[agent,mcp]` extras if you only want the core read-only CLI without the
Claude chat agent or MCP server.

**From source, with a virtualenv:**

```bash
git clone https://github.com/vivekp-05/canvasctl.git
cd canvasctl
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[agent,mcp,dev]"
canvasctl --version
```

## Features

Every read command supports `--json` for piping into `jq`, and `--course <id|alias>`
to scope to one course.

| Command | What it does |
| --- | --- |
| `setup` | **New.** Interactive onboarding wizard: prompts for your Canvas base URL and personal access token, optionally an Anthropic API key for the chat agent, and writes them to `~/.config/canvasctl/config.json` (`chmod 600`). |
| `sync` | Pull all course metadata (assignments, quizzes, modules, files, announcements) into `~/canvas-vault/` in parallel, with retry/backoff for 429/5xx and graceful auth-error handling. |
| `today` | List items due today. |
| `due` | List items due in any future window — `24h`, `7d`, `2w`, or an ISO `start..end` range. |
| `announcements` (`ann`) | Show recent announcements, with optional message previews. |
| `changes` | Replay the local change-log — snapshots are SHA-256 hashed per course/kind so you see exactly what shifted between syncs. |
| `status` (`dashboard`, `dash`) | One-screen overview: due today, recent announcements, recent changes, last sync time. |
| `list` | Tabular listing of courses, assignments, quizzes, files, modules, or announcements. |
| `show` | Print the full Canvas JSON for a single item. |
| `pull` | Download course files into `<vault>/content/<course_id>/`. |
| `ics` | Export upcoming due dates as an `.ics` calendar you can subscribe to in Apple/Google Calendar. |
| `ask` | **New.** One-shot question to the Claude chat agent. Uses your own Anthropic API key (model `claude-opus-4-8`) and answers by making read-only tool calls over the local vault — never touching Canvas write endpoints. Example: `canvasctl ask "what's the biggest thing due before Friday?"` |
| `chat` | **New.** Interactive REPL version of `ask` — a running conversation with the same tool-calling agent over your vault. |
| `mcp` | **New.** Run the MCP server (stdio) so Claude Code and Claude Desktop can call the same read-only operations. Registers one MCP tool per vault operation. |

> The chat agent and MCP server are **bring-your-own-key**: nothing is proxied
> through anyone else. Your Anthropic key is read from `~/.config/canvasctl/config.json`
> (written by `canvasctl setup`) or the `ANTHROPIC_API_KEY` environment variable.

## Use it from Claude Code / Claude Desktop

`canvasctl mcp` speaks the Model Context Protocol over stdio, exposing your vault's
read operations as tools.

**Claude Code** — register it in one line:

```bash
claude mcp add canvasctl -- canvasctl mcp
```

**Claude Desktop** — add an entry to the `mcpServers` block of your
`claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "canvasctl": {
      "command": "canvasctl",
      "args": ["mcp"]
    }
  }
}
```

Then ask Claude things like *"what canvasctl says is due this week?"* or *"summarize
the latest announcements in my courses."* See [docs/mcp.md](docs/mcp.md) for the full
tool list, setup details, and example prompts.

## Layout of the local vault

```
~/canvas-vault/
├── state/
│   ├── config.json           # vault path, aliases, last_sync_at
│   ├── metadata/
│   │   ├── _courses/courses.json
│   │   └── <course_id>/{assignments,quizzes,modules,module_items,files,announcements}.json
│   └── logs/
│       ├── last_hashes.json  # for diffing between syncs
│       └── changes.jsonl     # append-only change-log
├── content/                  # downloaded files (canvasctl pull)
└── outputs/                  # generated artifacts (canvasctl ics)
```

The vault is plain JSON on disk — feel free to `grep`, `jq`, or back it up directly.

> Your Canvas token and Anthropic key are **not** stored in the vault. They live in a
> separate credentials file at `~/.config/canvasctl/config.json` (mode `600`).

## Security & privacy

- **Bring your own keys (BYOK).** You supply your own Canvas personal access token and
  your own Anthropic API key. Nothing is proxied through a third party.
- **Treat your Canvas token like a password.** A Canvas personal access token is a
  full-access student credential — anyone holding it can act as you across every
  course and setting your account can reach. Generate it under
  **Account → Settings → New Access Token**, give it a short expiry, and revoke it the
  moment you suspect exposure.
- **Credentials live in one file, locked down.** `canvasctl setup` writes your Canvas
  URL, token, and optional Anthropic key to `~/.config/canvasctl/config.json` and
  sets its permissions to `600` (owner read/write only). It is never committed and
  never copied into the vault.
- **Nothing leaves your machine except direct calls to Canvas and Anthropic.** The
  only outbound traffic is to your school's Canvas host (to sync) and, if you use the
  agent, to `api.anthropic.com` with your own key. There is no telemetry and no
  middleman server.
- **Read-focused by design.** `canvasctl` only reads from Canvas. There is no
  assignment submission, no grade posting, and no mutation of any Canvas resource —
  the MCP server and chat agent expose read/local operations only.

For the full threat model, see [docs/security.md](docs/security.md).

## Requirements

- **Python 3.10 or newer.** The core CLI runs on 3.10+; the `ask`/`chat` agent and the
  `mcp` server additionally require the optional `anthropic` and `mcp` packages
  (installed via the `[agent]` / `[mcp]` extras).
- A Canvas LMS account and a personal access token.
- An Anthropic API key, only if you want the `ask` / `chat` agent.

## Development

```bash
git clone https://github.com/vivekp-05/canvasctl.git
cd canvasctl
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[agent,mcp,dev]"

# Run the test suite
pytest -q
```

Package layout:

```
canvasctl/
├── cli.py            # argparse + dispatch
├── client.py         # Canvas REST client (retry, backoff, pagination)
├── store.py          # vault layout + atomic JSON I/O + change log
├── diff.py           # by-id diffing
├── records.py        # normalize Canvas dicts -> due-date records
├── timeutil.py       # window parsing + safe datetime handling
├── formatting.py     # colors, tables, byte formatting
├── ops/              # operation registry shared by the CLI, agent, and MCP server
└── commands/         # one module per subcommand
```

The `ops/` registry is the single source of truth for every Canvas operation: each op
is defined once (name, description, JSON schema, handler, kind) and consumed by three
surfaces — the argparse CLI, the MCP server, and the chat agent.

## License

[MIT](LICENSE) © 2026 Vivek Patel.

## Disclaimer

`canvasctl` is an independent, unofficial tool for use with Canvas LMS. It is not
affiliated with, endorsed by, or sponsored by Instructure, Inc. "Canvas" and
"Instructure" are trademarks of Instructure, Inc.
