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.
Files changed (50) hide show
  1. canvasctl-0.3.0/LICENSE +21 -0
  2. canvasctl-0.3.0/PKG-INFO +254 -0
  3. canvasctl-0.3.0/README.md +224 -0
  4. canvasctl-0.3.0/canvasctl/__init__.py +3 -0
  5. canvasctl-0.3.0/canvasctl/__main__.py +4 -0
  6. canvasctl-0.3.0/canvasctl/agent/__init__.py +39 -0
  7. canvasctl-0.3.0/canvasctl/agent/loop.py +245 -0
  8. canvasctl-0.3.0/canvasctl/agent/repl.py +76 -0
  9. canvasctl-0.3.0/canvasctl/cli.py +197 -0
  10. canvasctl-0.3.0/canvasctl/client.py +181 -0
  11. canvasctl-0.3.0/canvasctl/commands/__init__.py +1 -0
  12. canvasctl-0.3.0/canvasctl/commands/announcements.py +74 -0
  13. canvasctl-0.3.0/canvasctl/commands/changes.py +59 -0
  14. canvasctl-0.3.0/canvasctl/commands/config_cmd.py +42 -0
  15. canvasctl-0.3.0/canvasctl/commands/due.py +79 -0
  16. canvasctl-0.3.0/canvasctl/commands/ics.py +89 -0
  17. canvasctl-0.3.0/canvasctl/commands/list_cmd.py +142 -0
  18. canvasctl-0.3.0/canvasctl/commands/pull.py +114 -0
  19. canvasctl-0.3.0/canvasctl/commands/show.py +43 -0
  20. canvasctl-0.3.0/canvasctl/commands/status.py +133 -0
  21. canvasctl-0.3.0/canvasctl/commands/sync.py +141 -0
  22. canvasctl-0.3.0/canvasctl/commands/today.py +70 -0
  23. canvasctl-0.3.0/canvasctl/credentials.py +96 -0
  24. canvasctl-0.3.0/canvasctl/diff.py +29 -0
  25. canvasctl-0.3.0/canvasctl/formatting.py +66 -0
  26. canvasctl-0.3.0/canvasctl/mcp_server.py +137 -0
  27. canvasctl-0.3.0/canvasctl/ops/__init__.py +4 -0
  28. canvasctl-0.3.0/canvasctl/ops/catalog.py +659 -0
  29. canvasctl-0.3.0/canvasctl/ops/registry.py +97 -0
  30. canvasctl-0.3.0/canvasctl/records.py +45 -0
  31. canvasctl-0.3.0/canvasctl/setup_wizard.py +130 -0
  32. canvasctl-0.3.0/canvasctl/store.py +141 -0
  33. canvasctl-0.3.0/canvasctl/timeutil.py +117 -0
  34. canvasctl-0.3.0/canvasctl.egg-info/PKG-INFO +254 -0
  35. canvasctl-0.3.0/canvasctl.egg-info/SOURCES.txt +48 -0
  36. canvasctl-0.3.0/canvasctl.egg-info/dependency_links.txt +1 -0
  37. canvasctl-0.3.0/canvasctl.egg-info/entry_points.txt +2 -0
  38. canvasctl-0.3.0/canvasctl.egg-info/requires.txt +16 -0
  39. canvasctl-0.3.0/canvasctl.egg-info/top_level.txt +1 -0
  40. canvasctl-0.3.0/pyproject.toml +60 -0
  41. canvasctl-0.3.0/setup.cfg +4 -0
  42. canvasctl-0.3.0/tests/test_agent_loop.py +268 -0
  43. canvasctl-0.3.0/tests/test_client.py +80 -0
  44. canvasctl-0.3.0/tests/test_credentials.py +97 -0
  45. canvasctl-0.3.0/tests/test_diff.py +32 -0
  46. canvasctl-0.3.0/tests/test_mcp_server.py +92 -0
  47. canvasctl-0.3.0/tests/test_ops_catalog.py +271 -0
  48. canvasctl-0.3.0/tests/test_records_and_ics.py +45 -0
  49. canvasctl-0.3.0/tests/test_store.py +54 -0
  50. canvasctl-0.3.0/tests/test_timeutil.py +75 -0
@@ -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.
@@ -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
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
47
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
48
+ [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](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
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
17
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
18
+ [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](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,3 @@
1
+ """canvasctl — a read-only Canvas LMS CLI."""
2
+
3
+ __version__ = "0.3.0"
@@ -0,0 +1,4 @@
1
+ from canvasctl.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -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"]