canvasctl 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- canvasctl-0.3.0/LICENSE +21 -0
- canvasctl-0.3.0/PKG-INFO +254 -0
- canvasctl-0.3.0/README.md +224 -0
- canvasctl-0.3.0/canvasctl/__init__.py +3 -0
- canvasctl-0.3.0/canvasctl/__main__.py +4 -0
- canvasctl-0.3.0/canvasctl/agent/__init__.py +39 -0
- canvasctl-0.3.0/canvasctl/agent/loop.py +245 -0
- canvasctl-0.3.0/canvasctl/agent/repl.py +76 -0
- canvasctl-0.3.0/canvasctl/cli.py +197 -0
- canvasctl-0.3.0/canvasctl/client.py +181 -0
- canvasctl-0.3.0/canvasctl/commands/__init__.py +1 -0
- canvasctl-0.3.0/canvasctl/commands/announcements.py +74 -0
- canvasctl-0.3.0/canvasctl/commands/changes.py +59 -0
- canvasctl-0.3.0/canvasctl/commands/config_cmd.py +42 -0
- canvasctl-0.3.0/canvasctl/commands/due.py +79 -0
- canvasctl-0.3.0/canvasctl/commands/ics.py +89 -0
- canvasctl-0.3.0/canvasctl/commands/list_cmd.py +142 -0
- canvasctl-0.3.0/canvasctl/commands/pull.py +114 -0
- canvasctl-0.3.0/canvasctl/commands/show.py +43 -0
- canvasctl-0.3.0/canvasctl/commands/status.py +133 -0
- canvasctl-0.3.0/canvasctl/commands/sync.py +141 -0
- canvasctl-0.3.0/canvasctl/commands/today.py +70 -0
- canvasctl-0.3.0/canvasctl/credentials.py +96 -0
- canvasctl-0.3.0/canvasctl/diff.py +29 -0
- canvasctl-0.3.0/canvasctl/formatting.py +66 -0
- canvasctl-0.3.0/canvasctl/mcp_server.py +137 -0
- canvasctl-0.3.0/canvasctl/ops/__init__.py +4 -0
- canvasctl-0.3.0/canvasctl/ops/catalog.py +659 -0
- canvasctl-0.3.0/canvasctl/ops/registry.py +97 -0
- canvasctl-0.3.0/canvasctl/records.py +45 -0
- canvasctl-0.3.0/canvasctl/setup_wizard.py +130 -0
- canvasctl-0.3.0/canvasctl/store.py +141 -0
- canvasctl-0.3.0/canvasctl/timeutil.py +117 -0
- canvasctl-0.3.0/canvasctl.egg-info/PKG-INFO +254 -0
- canvasctl-0.3.0/canvasctl.egg-info/SOURCES.txt +48 -0
- canvasctl-0.3.0/canvasctl.egg-info/dependency_links.txt +1 -0
- canvasctl-0.3.0/canvasctl.egg-info/entry_points.txt +2 -0
- canvasctl-0.3.0/canvasctl.egg-info/requires.txt +16 -0
- canvasctl-0.3.0/canvasctl.egg-info/top_level.txt +1 -0
- canvasctl-0.3.0/pyproject.toml +60 -0
- canvasctl-0.3.0/setup.cfg +4 -0
- canvasctl-0.3.0/tests/test_agent_loop.py +268 -0
- canvasctl-0.3.0/tests/test_client.py +80 -0
- canvasctl-0.3.0/tests/test_credentials.py +97 -0
- canvasctl-0.3.0/tests/test_diff.py +32 -0
- canvasctl-0.3.0/tests/test_mcp_server.py +92 -0
- canvasctl-0.3.0/tests/test_ops_catalog.py +271 -0
- canvasctl-0.3.0/tests/test_records_and_ics.py +45 -0
- canvasctl-0.3.0/tests/test_store.py +54 -0
- canvasctl-0.3.0/tests/test_timeutil.py +75 -0
canvasctl-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vivek Patel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
canvasctl-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: canvasctl
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Canvas in your terminal: a Canvas LMS companion CLI with a local vault, a Claude-powered chat agent, and an MCP server.
|
|
5
|
+
Author: Vivek
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: canvas,lms,cli,education
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Education
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Topic :: Education
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: requests>=2.31
|
|
18
|
+
Provides-Extra: agent
|
|
19
|
+
Requires-Dist: anthropic>=0.117; extra == "agent"
|
|
20
|
+
Provides-Extra: mcp
|
|
21
|
+
Requires-Dist: mcp>=1.28; extra == "mcp"
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: canvasctl[agent,mcp]; extra == "all"
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
27
|
+
Requires-Dist: responses>=0.24; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
<div align="center">
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
_ _ _
|
|
35
|
+
___ __ _ _ ___ ____ _ __| |_ __ __| |_| |
|
|
36
|
+
/ __/ _` | ' \ V / _` (_-< _/ _/ _| _| |
|
|
37
|
+
\___\__,_|_||_\_/\__,_/__/\__\__\__|\__|_|
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
# canvasctl
|
|
41
|
+
|
|
42
|
+
**Canvas in your terminal.** A read-focused Canvas LMS companion that mirrors your
|
|
43
|
+
courses into a local vault, answers questions with a Claude-powered chat agent, and
|
|
44
|
+
plugs into Claude Code / Claude Desktop over MCP.
|
|
45
|
+
|
|
46
|
+
[](https://www.python.org/downloads/)
|
|
47
|
+
[](LICENSE)
|
|
48
|
+
[](https://github.com/vivekp-05/canvasctl/pulls)
|
|
49
|
+
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<!-- TODO: demo.gif -- record a short asciinema/gif of `canvasctl setup` -> `today` -> `ask` and drop it here -->
|
|
53
|
+
|
|
54
|
+
## What is this
|
|
55
|
+
|
|
56
|
+
`canvasctl` mirrors your Canvas courses — assignments, quizzes, modules, files, and
|
|
57
|
+
announcements — into a plain-JSON vault at `~/canvas-vault/`, then gives you fast
|
|
58
|
+
terminal answers to "what's due today?", "what changed since yesterday?", and
|
|
59
|
+
"export the next 60 days as a calendar." A built-in **chat agent** (`canvasctl ask` /
|
|
60
|
+
`canvasctl chat`) uses your own Anthropic API key to answer natural-language
|
|
61
|
+
questions by calling read-only tools over that vault. And an **MCP server**
|
|
62
|
+
(`canvasctl mcp`) exposes the same operations to Claude Code and Claude Desktop, so
|
|
63
|
+
you can ask Claude about your coursework from anywhere.
|
|
64
|
+
|
|
65
|
+
Everything runs on your machine. The only network calls it makes are to your own
|
|
66
|
+
school's Canvas instance and — if you opt into the agent — directly to
|
|
67
|
+
`api.anthropic.com` with your own key. It is read-focused by design: there is no
|
|
68
|
+
assignment submission and no mutation of Canvas.
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# 1. Install (see alternatives below if you'd rather use pipx or a venv)
|
|
74
|
+
curl -fsSL https://raw.githubusercontent.com/vivekp-05/canvasctl/main/install.sh | sh
|
|
75
|
+
|
|
76
|
+
# 2. Onboard — asks for your Canvas URL + access token, optionally an Anthropic key
|
|
77
|
+
canvasctl setup
|
|
78
|
+
|
|
79
|
+
# 3. Pull your courses into the local vault, then ask away
|
|
80
|
+
canvasctl sync
|
|
81
|
+
canvasctl today
|
|
82
|
+
canvasctl ask "what's due this week?"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Install alternatives
|
|
86
|
+
|
|
87
|
+
**pipx, straight from git** (isolated, on your `$PATH`):
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pipx install "canvasctl[agent,mcp] @ git+https://github.com/vivekp-05/canvasctl.git"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Drop the `[agent,mcp]` extras if you only want the core read-only CLI without the
|
|
94
|
+
Claude chat agent or MCP server.
|
|
95
|
+
|
|
96
|
+
**From source, with a virtualenv:**
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
git clone https://github.com/vivekp-05/canvasctl.git
|
|
100
|
+
cd canvasctl
|
|
101
|
+
python3 -m venv .venv
|
|
102
|
+
source .venv/bin/activate
|
|
103
|
+
pip install -e ".[agent,mcp,dev]"
|
|
104
|
+
canvasctl --version
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Features
|
|
108
|
+
|
|
109
|
+
Every read command supports `--json` for piping into `jq`, and `--course <id|alias>`
|
|
110
|
+
to scope to one course.
|
|
111
|
+
|
|
112
|
+
| Command | What it does |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| `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`). |
|
|
115
|
+
| `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. |
|
|
116
|
+
| `today` | List items due today. |
|
|
117
|
+
| `due` | List items due in any future window — `24h`, `7d`, `2w`, or an ISO `start..end` range. |
|
|
118
|
+
| `announcements` (`ann`) | Show recent announcements, with optional message previews. |
|
|
119
|
+
| `changes` | Replay the local change-log — snapshots are SHA-256 hashed per course/kind so you see exactly what shifted between syncs. |
|
|
120
|
+
| `status` (`dashboard`, `dash`) | One-screen overview: due today, recent announcements, recent changes, last sync time. |
|
|
121
|
+
| `list` | Tabular listing of courses, assignments, quizzes, files, modules, or announcements. |
|
|
122
|
+
| `show` | Print the full Canvas JSON for a single item. |
|
|
123
|
+
| `pull` | Download course files into `<vault>/content/<course_id>/`. |
|
|
124
|
+
| `ics` | Export upcoming due dates as an `.ics` calendar you can subscribe to in Apple/Google Calendar. |
|
|
125
|
+
| `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?"` |
|
|
126
|
+
| `chat` | **New.** Interactive REPL version of `ask` — a running conversation with the same tool-calling agent over your vault. |
|
|
127
|
+
| `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. |
|
|
128
|
+
|
|
129
|
+
> The chat agent and MCP server are **bring-your-own-key**: nothing is proxied
|
|
130
|
+
> through anyone else. Your Anthropic key is read from `~/.config/canvasctl/config.json`
|
|
131
|
+
> (written by `canvasctl setup`) or the `ANTHROPIC_API_KEY` environment variable.
|
|
132
|
+
|
|
133
|
+
## Use it from Claude Code / Claude Desktop
|
|
134
|
+
|
|
135
|
+
`canvasctl mcp` speaks the Model Context Protocol over stdio, exposing your vault's
|
|
136
|
+
read operations as tools.
|
|
137
|
+
|
|
138
|
+
**Claude Code** — register it in one line:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
claude mcp add canvasctl -- canvasctl mcp
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Claude Desktop** — add an entry to the `mcpServers` block of your
|
|
145
|
+
`claude_desktop_config.json`:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"mcpServers": {
|
|
150
|
+
"canvasctl": {
|
|
151
|
+
"command": "canvasctl",
|
|
152
|
+
"args": ["mcp"]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Then ask Claude things like *"what canvasctl says is due this week?"* or *"summarize
|
|
159
|
+
the latest announcements in my courses."* See [docs/mcp.md](docs/mcp.md) for the full
|
|
160
|
+
tool list, setup details, and example prompts.
|
|
161
|
+
|
|
162
|
+
## Layout of the local vault
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
~/canvas-vault/
|
|
166
|
+
├── state/
|
|
167
|
+
│ ├── config.json # vault path, aliases, last_sync_at
|
|
168
|
+
│ ├── metadata/
|
|
169
|
+
│ │ ├── _courses/courses.json
|
|
170
|
+
│ │ └── <course_id>/{assignments,quizzes,modules,module_items,files,announcements}.json
|
|
171
|
+
│ └── logs/
|
|
172
|
+
│ ├── last_hashes.json # for diffing between syncs
|
|
173
|
+
│ └── changes.jsonl # append-only change-log
|
|
174
|
+
├── content/ # downloaded files (canvasctl pull)
|
|
175
|
+
└── outputs/ # generated artifacts (canvasctl ics)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The vault is plain JSON on disk — feel free to `grep`, `jq`, or back it up directly.
|
|
179
|
+
|
|
180
|
+
> Your Canvas token and Anthropic key are **not** stored in the vault. They live in a
|
|
181
|
+
> separate credentials file at `~/.config/canvasctl/config.json` (mode `600`).
|
|
182
|
+
|
|
183
|
+
## Security & privacy
|
|
184
|
+
|
|
185
|
+
- **Bring your own keys (BYOK).** You supply your own Canvas personal access token and
|
|
186
|
+
your own Anthropic API key. Nothing is proxied through a third party.
|
|
187
|
+
- **Treat your Canvas token like a password.** A Canvas personal access token is a
|
|
188
|
+
full-access student credential — anyone holding it can act as you across every
|
|
189
|
+
course and setting your account can reach. Generate it under
|
|
190
|
+
**Account → Settings → New Access Token**, give it a short expiry, and revoke it the
|
|
191
|
+
moment you suspect exposure.
|
|
192
|
+
- **Credentials live in one file, locked down.** `canvasctl setup` writes your Canvas
|
|
193
|
+
URL, token, and optional Anthropic key to `~/.config/canvasctl/config.json` and
|
|
194
|
+
sets its permissions to `600` (owner read/write only). It is never committed and
|
|
195
|
+
never copied into the vault.
|
|
196
|
+
- **Nothing leaves your machine except direct calls to Canvas and Anthropic.** The
|
|
197
|
+
only outbound traffic is to your school's Canvas host (to sync) and, if you use the
|
|
198
|
+
agent, to `api.anthropic.com` with your own key. There is no telemetry and no
|
|
199
|
+
middleman server.
|
|
200
|
+
- **Read-focused by design.** `canvasctl` only reads from Canvas. There is no
|
|
201
|
+
assignment submission, no grade posting, and no mutation of any Canvas resource —
|
|
202
|
+
the MCP server and chat agent expose read/local operations only.
|
|
203
|
+
|
|
204
|
+
For the full threat model, see [docs/security.md](docs/security.md).
|
|
205
|
+
|
|
206
|
+
## Requirements
|
|
207
|
+
|
|
208
|
+
- **Python 3.10 or newer.** The core CLI runs on 3.10+; the `ask`/`chat` agent and the
|
|
209
|
+
`mcp` server additionally require the optional `anthropic` and `mcp` packages
|
|
210
|
+
(installed via the `[agent]` / `[mcp]` extras).
|
|
211
|
+
- A Canvas LMS account and a personal access token.
|
|
212
|
+
- An Anthropic API key, only if you want the `ask` / `chat` agent.
|
|
213
|
+
|
|
214
|
+
## Development
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
git clone https://github.com/vivekp-05/canvasctl.git
|
|
218
|
+
cd canvasctl
|
|
219
|
+
python3 -m venv .venv
|
|
220
|
+
source .venv/bin/activate
|
|
221
|
+
pip install -e ".[agent,mcp,dev]"
|
|
222
|
+
|
|
223
|
+
# Run the test suite
|
|
224
|
+
pytest -q
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Package layout:
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
canvasctl/
|
|
231
|
+
├── cli.py # argparse + dispatch
|
|
232
|
+
├── client.py # Canvas REST client (retry, backoff, pagination)
|
|
233
|
+
├── store.py # vault layout + atomic JSON I/O + change log
|
|
234
|
+
├── diff.py # by-id diffing
|
|
235
|
+
├── records.py # normalize Canvas dicts -> due-date records
|
|
236
|
+
├── timeutil.py # window parsing + safe datetime handling
|
|
237
|
+
├── formatting.py # colors, tables, byte formatting
|
|
238
|
+
├── ops/ # operation registry shared by the CLI, agent, and MCP server
|
|
239
|
+
└── commands/ # one module per subcommand
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The `ops/` registry is the single source of truth for every Canvas operation: each op
|
|
243
|
+
is defined once (name, description, JSON schema, handler, kind) and consumed by three
|
|
244
|
+
surfaces — the argparse CLI, the MCP server, and the chat agent.
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
[MIT](LICENSE) © 2026 Vivek Patel.
|
|
249
|
+
|
|
250
|
+
## Disclaimer
|
|
251
|
+
|
|
252
|
+
`canvasctl` is an independent, unofficial tool for use with Canvas LMS. It is not
|
|
253
|
+
affiliated with, endorsed by, or sponsored by Instructure, Inc. "Canvas" and
|
|
254
|
+
"Instructure" are trademarks of Instructure, Inc.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
_ _ _
|
|
5
|
+
___ __ _ _ ___ ____ _ __| |_ __ __| |_| |
|
|
6
|
+
/ __/ _` | ' \ V / _` (_-< _/ _/ _| _| |
|
|
7
|
+
\___\__,_|_||_\_/\__,_/__/\__\__\__|\__|_|
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
# canvasctl
|
|
11
|
+
|
|
12
|
+
**Canvas in your terminal.** A read-focused Canvas LMS companion that mirrors your
|
|
13
|
+
courses into a local vault, answers questions with a Claude-powered chat agent, and
|
|
14
|
+
plugs into Claude Code / Claude Desktop over MCP.
|
|
15
|
+
|
|
16
|
+
[](https://www.python.org/downloads/)
|
|
17
|
+
[](LICENSE)
|
|
18
|
+
[](https://github.com/vivekp-05/canvasctl/pulls)
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- TODO: demo.gif -- record a short asciinema/gif of `canvasctl setup` -> `today` -> `ask` and drop it here -->
|
|
23
|
+
|
|
24
|
+
## What is this
|
|
25
|
+
|
|
26
|
+
`canvasctl` mirrors your Canvas courses — assignments, quizzes, modules, files, and
|
|
27
|
+
announcements — into a plain-JSON vault at `~/canvas-vault/`, then gives you fast
|
|
28
|
+
terminal answers to "what's due today?", "what changed since yesterday?", and
|
|
29
|
+
"export the next 60 days as a calendar." A built-in **chat agent** (`canvasctl ask` /
|
|
30
|
+
`canvasctl chat`) uses your own Anthropic API key to answer natural-language
|
|
31
|
+
questions by calling read-only tools over that vault. And an **MCP server**
|
|
32
|
+
(`canvasctl mcp`) exposes the same operations to Claude Code and Claude Desktop, so
|
|
33
|
+
you can ask Claude about your coursework from anywhere.
|
|
34
|
+
|
|
35
|
+
Everything runs on your machine. The only network calls it makes are to your own
|
|
36
|
+
school's Canvas instance and — if you opt into the agent — directly to
|
|
37
|
+
`api.anthropic.com` with your own key. It is read-focused by design: there is no
|
|
38
|
+
assignment submission and no mutation of Canvas.
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 1. Install (see alternatives below if you'd rather use pipx or a venv)
|
|
44
|
+
curl -fsSL https://raw.githubusercontent.com/vivekp-05/canvasctl/main/install.sh | sh
|
|
45
|
+
|
|
46
|
+
# 2. Onboard — asks for your Canvas URL + access token, optionally an Anthropic key
|
|
47
|
+
canvasctl setup
|
|
48
|
+
|
|
49
|
+
# 3. Pull your courses into the local vault, then ask away
|
|
50
|
+
canvasctl sync
|
|
51
|
+
canvasctl today
|
|
52
|
+
canvasctl ask "what's due this week?"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Install alternatives
|
|
56
|
+
|
|
57
|
+
**pipx, straight from git** (isolated, on your `$PATH`):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pipx install "canvasctl[agent,mcp] @ git+https://github.com/vivekp-05/canvasctl.git"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Drop the `[agent,mcp]` extras if you only want the core read-only CLI without the
|
|
64
|
+
Claude chat agent or MCP server.
|
|
65
|
+
|
|
66
|
+
**From source, with a virtualenv:**
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/vivekp-05/canvasctl.git
|
|
70
|
+
cd canvasctl
|
|
71
|
+
python3 -m venv .venv
|
|
72
|
+
source .venv/bin/activate
|
|
73
|
+
pip install -e ".[agent,mcp,dev]"
|
|
74
|
+
canvasctl --version
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Features
|
|
78
|
+
|
|
79
|
+
Every read command supports `--json` for piping into `jq`, and `--course <id|alias>`
|
|
80
|
+
to scope to one course.
|
|
81
|
+
|
|
82
|
+
| Command | What it does |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `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`). |
|
|
85
|
+
| `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. |
|
|
86
|
+
| `today` | List items due today. |
|
|
87
|
+
| `due` | List items due in any future window — `24h`, `7d`, `2w`, or an ISO `start..end` range. |
|
|
88
|
+
| `announcements` (`ann`) | Show recent announcements, with optional message previews. |
|
|
89
|
+
| `changes` | Replay the local change-log — snapshots are SHA-256 hashed per course/kind so you see exactly what shifted between syncs. |
|
|
90
|
+
| `status` (`dashboard`, `dash`) | One-screen overview: due today, recent announcements, recent changes, last sync time. |
|
|
91
|
+
| `list` | Tabular listing of courses, assignments, quizzes, files, modules, or announcements. |
|
|
92
|
+
| `show` | Print the full Canvas JSON for a single item. |
|
|
93
|
+
| `pull` | Download course files into `<vault>/content/<course_id>/`. |
|
|
94
|
+
| `ics` | Export upcoming due dates as an `.ics` calendar you can subscribe to in Apple/Google Calendar. |
|
|
95
|
+
| `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?"` |
|
|
96
|
+
| `chat` | **New.** Interactive REPL version of `ask` — a running conversation with the same tool-calling agent over your vault. |
|
|
97
|
+
| `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. |
|
|
98
|
+
|
|
99
|
+
> The chat agent and MCP server are **bring-your-own-key**: nothing is proxied
|
|
100
|
+
> through anyone else. Your Anthropic key is read from `~/.config/canvasctl/config.json`
|
|
101
|
+
> (written by `canvasctl setup`) or the `ANTHROPIC_API_KEY` environment variable.
|
|
102
|
+
|
|
103
|
+
## Use it from Claude Code / Claude Desktop
|
|
104
|
+
|
|
105
|
+
`canvasctl mcp` speaks the Model Context Protocol over stdio, exposing your vault's
|
|
106
|
+
read operations as tools.
|
|
107
|
+
|
|
108
|
+
**Claude Code** — register it in one line:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
claude mcp add canvasctl -- canvasctl mcp
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Claude Desktop** — add an entry to the `mcpServers` block of your
|
|
115
|
+
`claude_desktop_config.json`:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"mcpServers": {
|
|
120
|
+
"canvasctl": {
|
|
121
|
+
"command": "canvasctl",
|
|
122
|
+
"args": ["mcp"]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Then ask Claude things like *"what canvasctl says is due this week?"* or *"summarize
|
|
129
|
+
the latest announcements in my courses."* See [docs/mcp.md](docs/mcp.md) for the full
|
|
130
|
+
tool list, setup details, and example prompts.
|
|
131
|
+
|
|
132
|
+
## Layout of the local vault
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
~/canvas-vault/
|
|
136
|
+
├── state/
|
|
137
|
+
│ ├── config.json # vault path, aliases, last_sync_at
|
|
138
|
+
│ ├── metadata/
|
|
139
|
+
│ │ ├── _courses/courses.json
|
|
140
|
+
│ │ └── <course_id>/{assignments,quizzes,modules,module_items,files,announcements}.json
|
|
141
|
+
│ └── logs/
|
|
142
|
+
│ ├── last_hashes.json # for diffing between syncs
|
|
143
|
+
│ └── changes.jsonl # append-only change-log
|
|
144
|
+
├── content/ # downloaded files (canvasctl pull)
|
|
145
|
+
└── outputs/ # generated artifacts (canvasctl ics)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The vault is plain JSON on disk — feel free to `grep`, `jq`, or back it up directly.
|
|
149
|
+
|
|
150
|
+
> Your Canvas token and Anthropic key are **not** stored in the vault. They live in a
|
|
151
|
+
> separate credentials file at `~/.config/canvasctl/config.json` (mode `600`).
|
|
152
|
+
|
|
153
|
+
## Security & privacy
|
|
154
|
+
|
|
155
|
+
- **Bring your own keys (BYOK).** You supply your own Canvas personal access token and
|
|
156
|
+
your own Anthropic API key. Nothing is proxied through a third party.
|
|
157
|
+
- **Treat your Canvas token like a password.** A Canvas personal access token is a
|
|
158
|
+
full-access student credential — anyone holding it can act as you across every
|
|
159
|
+
course and setting your account can reach. Generate it under
|
|
160
|
+
**Account → Settings → New Access Token**, give it a short expiry, and revoke it the
|
|
161
|
+
moment you suspect exposure.
|
|
162
|
+
- **Credentials live in one file, locked down.** `canvasctl setup` writes your Canvas
|
|
163
|
+
URL, token, and optional Anthropic key to `~/.config/canvasctl/config.json` and
|
|
164
|
+
sets its permissions to `600` (owner read/write only). It is never committed and
|
|
165
|
+
never copied into the vault.
|
|
166
|
+
- **Nothing leaves your machine except direct calls to Canvas and Anthropic.** The
|
|
167
|
+
only outbound traffic is to your school's Canvas host (to sync) and, if you use the
|
|
168
|
+
agent, to `api.anthropic.com` with your own key. There is no telemetry and no
|
|
169
|
+
middleman server.
|
|
170
|
+
- **Read-focused by design.** `canvasctl` only reads from Canvas. There is no
|
|
171
|
+
assignment submission, no grade posting, and no mutation of any Canvas resource —
|
|
172
|
+
the MCP server and chat agent expose read/local operations only.
|
|
173
|
+
|
|
174
|
+
For the full threat model, see [docs/security.md](docs/security.md).
|
|
175
|
+
|
|
176
|
+
## Requirements
|
|
177
|
+
|
|
178
|
+
- **Python 3.10 or newer.** The core CLI runs on 3.10+; the `ask`/`chat` agent and the
|
|
179
|
+
`mcp` server additionally require the optional `anthropic` and `mcp` packages
|
|
180
|
+
(installed via the `[agent]` / `[mcp]` extras).
|
|
181
|
+
- A Canvas LMS account and a personal access token.
|
|
182
|
+
- An Anthropic API key, only if you want the `ask` / `chat` agent.
|
|
183
|
+
|
|
184
|
+
## Development
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
git clone https://github.com/vivekp-05/canvasctl.git
|
|
188
|
+
cd canvasctl
|
|
189
|
+
python3 -m venv .venv
|
|
190
|
+
source .venv/bin/activate
|
|
191
|
+
pip install -e ".[agent,mcp,dev]"
|
|
192
|
+
|
|
193
|
+
# Run the test suite
|
|
194
|
+
pytest -q
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Package layout:
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
canvasctl/
|
|
201
|
+
├── cli.py # argparse + dispatch
|
|
202
|
+
├── client.py # Canvas REST client (retry, backoff, pagination)
|
|
203
|
+
├── store.py # vault layout + atomic JSON I/O + change log
|
|
204
|
+
├── diff.py # by-id diffing
|
|
205
|
+
├── records.py # normalize Canvas dicts -> due-date records
|
|
206
|
+
├── timeutil.py # window parsing + safe datetime handling
|
|
207
|
+
├── formatting.py # colors, tables, byte formatting
|
|
208
|
+
├── ops/ # operation registry shared by the CLI, agent, and MCP server
|
|
209
|
+
└── commands/ # one module per subcommand
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The `ops/` registry is the single source of truth for every Canvas operation: each op
|
|
213
|
+
is defined once (name, description, JSON schema, handler, kind) and consumed by three
|
|
214
|
+
surfaces — the argparse CLI, the MCP server, and the chat agent.
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
[MIT](LICENSE) © 2026 Vivek Patel.
|
|
219
|
+
|
|
220
|
+
## Disclaimer
|
|
221
|
+
|
|
222
|
+
`canvasctl` is an independent, unofficial tool for use with Canvas LMS. It is not
|
|
223
|
+
affiliated with, endorsed by, or sponsored by Instructure, Inc. "Canvas" and
|
|
224
|
+
"Instructure" are trademarks of Instructure, Inc.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""canvasctl chat agent — a Claude-powered assistant over the ops registry.
|
|
2
|
+
|
|
3
|
+
Public surface: ``run_ask`` (single prompt) and ``run_chat`` (interactive REPL).
|
|
4
|
+
Both return an integer exit code and are safe to wire straight into the CLI.
|
|
5
|
+
``anthropic`` is imported lazily inside ``loop``/``repl`` so importing this
|
|
6
|
+
package never requires the optional dependency.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from ..formatting import Colors, colorize
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run_ask(prompt: str, model: str | None = None) -> int:
|
|
16
|
+
"""Answer a single prompt (``canvasctl ask``). Returns an exit code."""
|
|
17
|
+
from .loop import AgentError, ask_once
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
text = ask_once(prompt, model=model)
|
|
21
|
+
except AgentError as exc:
|
|
22
|
+
print(colorize(str(exc), Colors.RED), file=sys.stderr)
|
|
23
|
+
return 1
|
|
24
|
+
except KeyboardInterrupt:
|
|
25
|
+
print(colorize("\nInterrupted.", Colors.YELLOW), file=sys.stderr)
|
|
26
|
+
return 130
|
|
27
|
+
if text:
|
|
28
|
+
print(text)
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run_chat(model: str | None = None) -> int:
|
|
33
|
+
"""Start the interactive chat REPL (``canvasctl chat``). Returns an exit code."""
|
|
34
|
+
from .repl import run_chat as _run_chat
|
|
35
|
+
|
|
36
|
+
return _run_chat(model=model)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = ["run_ask", "run_chat"]
|