tahuti 0.4.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 (83) hide show
  1. tahuti-0.4.0/.gitignore +181 -0
  2. tahuti-0.4.0/CHANGELOG.md +299 -0
  3. tahuti-0.4.0/LICENSE +21 -0
  4. tahuti-0.4.0/PKG-INFO +509 -0
  5. tahuti-0.4.0/README.md +477 -0
  6. tahuti-0.4.0/SECURITY.md +190 -0
  7. tahuti-0.4.0/docs/downstream-notifier-guide.md +362 -0
  8. tahuti-0.4.0/docs/events.md +912 -0
  9. tahuti-0.4.0/docs/library.md +548 -0
  10. tahuti-0.4.0/docs/realtime-transport-findings.md +310 -0
  11. tahuti-0.4.0/extras/mb-notifier/README.md +18 -0
  12. tahuti-0.4.0/extras/mb-notifier/bark_webhook_receiver.py +907 -0
  13. tahuti-0.4.0/extras/mb-notifier/test_bark_webhook.py +702 -0
  14. tahuti-0.4.0/extras/probe_actioncable.py +221 -0
  15. tahuti-0.4.0/extras/probe_frontend.py +134 -0
  16. tahuti-0.4.0/extras/probe_latency.py +87 -0
  17. tahuti-0.4.0/extras/probe_push.py +178 -0
  18. tahuti-0.4.0/extras/probe_ws.py +103 -0
  19. tahuti-0.4.0/pyproject.toml +107 -0
  20. tahuti-0.4.0/src/mb_cli/__init__.py +10 -0
  21. tahuti-0.4.0/src/mb_cli/__main__.py +2489 -0
  22. tahuti-0.4.0/src/mb_cli/auth.py +273 -0
  23. tahuti-0.4.0/src/mb_cli/cache.py +176 -0
  24. tahuti-0.4.0/src/mb_cli/client.py +2441 -0
  25. tahuti-0.4.0/src/mb_cli/config.py +367 -0
  26. tahuti-0.4.0/src/mb_cli/daemon/__init__.py +566 -0
  27. tahuti-0.4.0/src/mb_cli/daemon/events.py +404 -0
  28. tahuti-0.4.0/src/mb_cli/daemon/provider.py +350 -0
  29. tahuti-0.4.0/src/mb_cli/daemon/scheduler.py +160 -0
  30. tahuti-0.4.0/src/mb_cli/daemon/service.py +477 -0
  31. tahuti-0.4.0/src/mb_cli/daemon/state.py +205 -0
  32. tahuti-0.4.0/src/mb_cli/daemon/stealth.py +171 -0
  33. tahuti-0.4.0/src/mb_cli/daemon/stream.py +116 -0
  34. tahuti-0.4.0/src/mb_cli/daemon/system.py +331 -0
  35. tahuti-0.4.0/src/mb_cli/daemon/webhook.py +647 -0
  36. tahuti-0.4.0/src/mb_cli/exceptions.py +15 -0
  37. tahuti-0.4.0/src/mb_cli/filters.py +288 -0
  38. tahuti-0.4.0/src/mb_cli/formatters.py +532 -0
  39. tahuti-0.4.0/src/mb_cli/keychain.py +374 -0
  40. tahuti-0.4.0/src/mb_cli/mcp_server.py +1088 -0
  41. tahuti-0.4.0/src/mb_cli/notifications.py +182 -0
  42. tahuti-0.4.0/src/mb_cli/task_status.py +278 -0
  43. tahuti-0.4.0/tests/__init__.py +0 -0
  44. tahuti-0.4.0/tests/conftest.py +410 -0
  45. tahuti-0.4.0/tests/test_auth.py +631 -0
  46. tahuti-0.4.0/tests/test_cache.py +935 -0
  47. tahuti-0.4.0/tests/test_cli_fixes.py +367 -0
  48. tahuti-0.4.0/tests/test_cli_handler_defects.py +930 -0
  49. tahuti-0.4.0/tests/test_client.py +1069 -0
  50. tahuti-0.4.0/tests/test_client_scrape_fixes.py +882 -0
  51. tahuti-0.4.0/tests/test_config.py +271 -0
  52. tahuti-0.4.0/tests/test_credential_security.py +1386 -0
  53. tahuti-0.4.0/tests/test_daemon.py +534 -0
  54. tahuti-0.4.0/tests/test_daemon_cli.py +90 -0
  55. tahuti-0.4.0/tests/test_daemon_cli_flags.py +501 -0
  56. tahuti-0.4.0/tests/test_daemon_e2e_integration.py +402 -0
  57. tahuti-0.4.0/tests/test_daemon_events.py +232 -0
  58. tahuti-0.4.0/tests/test_daemon_provider.py +81 -0
  59. tahuti-0.4.0/tests/test_daemon_provider_normalization.py +537 -0
  60. tahuti-0.4.0/tests/test_daemon_scheduler.py +183 -0
  61. tahuti-0.4.0/tests/test_daemon_service.py +496 -0
  62. tahuti-0.4.0/tests/test_daemon_state.py +28 -0
  63. tahuti-0.4.0/tests/test_daemon_stealth.py +165 -0
  64. tahuti-0.4.0/tests/test_daemon_stream.py +273 -0
  65. tahuti-0.4.0/tests/test_daemon_system.py +81 -0
  66. tahuti-0.4.0/tests/test_daemon_webhook.py +796 -0
  67. tahuti-0.4.0/tests/test_download.py +383 -0
  68. tahuti-0.4.0/tests/test_env_isolation.py +206 -0
  69. tahuti-0.4.0/tests/test_exceptions.py +24 -0
  70. tahuti-0.4.0/tests/test_exit_codes.py +399 -0
  71. tahuti-0.4.0/tests/test_feedback.py +350 -0
  72. tahuti-0.4.0/tests/test_filters.py +498 -0
  73. tahuti-0.4.0/tests/test_formatters.py +745 -0
  74. tahuti-0.4.0/tests/test_main.py +643 -0
  75. tahuti-0.4.0/tests/test_mcp_server.py +711 -0
  76. tahuti-0.4.0/tests/test_new_features.py +198 -0
  77. tahuti-0.4.0/tests/test_notification_suppression.py +334 -0
  78. tahuti-0.4.0/tests/test_notifications.py +189 -0
  79. tahuti-0.4.0/tests/test_notifications_transport.py +251 -0
  80. tahuti-0.4.0/tests/test_security_fixes.py +263 -0
  81. tahuti-0.4.0/tests/test_submissions.py +302 -0
  82. tahuti-0.4.0/tests/test_submit_refresh.py +322 -0
  83. tahuti-0.4.0/tests/test_task_status.py +317 -0
@@ -0,0 +1,181 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *.cover
47
+ *.py,cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+ cover/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Django stuff:
57
+ *.log
58
+ local_settings.py
59
+ db.sqlite3
60
+ db.sqlite3-journal
61
+
62
+ # Flask stuff:
63
+ instance/
64
+ .webassets-cache
65
+
66
+ # Scrapy stuff:
67
+ .scrapy
68
+
69
+ # Sphinx documentation
70
+ docs/_build/
71
+
72
+ # PyBuilder
73
+ .pybuilder/
74
+ target/
75
+
76
+ # Jupyter Notebook
77
+ .ipynb_checkpoints
78
+
79
+ # IPython
80
+ profile_default/
81
+ ipython_config.py
82
+
83
+ # pyenv
84
+ .python-version
85
+
86
+ # pipenv
87
+ Pipfile.lock
88
+
89
+ # poetry
90
+ poetry.lock
91
+
92
+ # pdm
93
+ .pdm.toml
94
+ .pdm-python
95
+ .pdm-build/
96
+
97
+ # PEP 582
98
+ __pypackages__/
99
+
100
+ # Celery stuff
101
+ celerybeat-schedule
102
+ celerybeat.pid
103
+
104
+ # SageMath parsed files
105
+ *.sage.py
106
+
107
+ # Environments
108
+ .env
109
+ .venv
110
+ env/
111
+ venv/
112
+ ENV/
113
+ env.bak/
114
+ venv.bak/
115
+
116
+ # Spyder project settings
117
+ .spyderproject
118
+ .spyproject
119
+
120
+ # Rope project settings
121
+ .ropeproject
122
+
123
+ # mkdocs documentation
124
+ /site
125
+
126
+ # mypy
127
+ .mypy_cache/
128
+ .dmypy.json
129
+ dmypy.json
130
+
131
+ # Pyre type checker
132
+ .pyre/
133
+
134
+ # pytype static type analyzer
135
+ .pytype/
136
+
137
+ # Cython debug symbols
138
+ cython_debug/
139
+
140
+ # Ruff
141
+ .ruff_cache/
142
+
143
+ # macOS
144
+ .DS_Store
145
+
146
+ # Project-specific
147
+ *.json
148
+ !pyproject.toml
149
+ .worktrees/
150
+
151
+ # Claude Code agent workspace. .claude/worktrees/ holds complete clones of
152
+ # this repo (each with its own .git), so it must never be committed, and must
153
+ # never reach the sdist — hatchling's default sdist only skips what git ignores.
154
+ .claude/
155
+ .claude/worktrees/
156
+
157
+ # Deployments and operational notes
158
+ docs/deployments/
159
+
160
+ # Local diagnostic, data, and sensitive files
161
+ backup.txt
162
+ test_login.py
163
+ fetch_attachments.py
164
+ open_classes.py
165
+ freqs.txt
166
+ downloaded_attachments/
167
+ /course_aliases.json
168
+
169
+
170
+
171
+ # Personal data / infrastructure details — never commit these.
172
+ DEPLOYMENT.md
173
+ docs/deployments/
174
+ course_aliases.json
175
+ snapshot.json
176
+ daemon_state.json
177
+
178
+ # `mb download` default output dir (task_<id>_<title_slug>) when a test or a
179
+ # manual run is invoked without --output-dir.
180
+ /task_*
181
+
@@ -0,0 +1,299 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Release dates below are the date of the `vX.Y.Z` git tag; `0.4.0` is the first
9
+ release to carry one.
10
+
11
+ ## [Unreleased]
12
+
13
+ Nothing yet.
14
+
15
+ ## [0.4.0] - 2026-09-19
16
+
17
+ Contains a **breaking webhook signature change** — see `### Changed` below. Any
18
+ deployed receiver rejects every payload until it adds `X-MB-Timestamp` to its
19
+ signed material.
20
+
21
+ > Renamed from `mb-cli` to **`tahuti`**. The distribution, the CLI command and
22
+ > the repository are all `tahuti` now; the Python import path stays `mb_cli`, so
23
+ > `import mb_cli` and `python -m mb_cli` are unchanged. Environment variables
24
+ > stay `MB_CRAWLER_*` and the state directory stays `~/.config/tahuti/`.
25
+ > (`mb-cli`/`mb_cli` on PyPI is an unrelated project by another author, so
26
+ > nothing here overwrites it.)
27
+
28
+
29
+ ### Added
30
+ - **Per-endpoint delivery outcomes.** `WebhookDispatcher.dispatch()` returns a
31
+ machine-readable `outcome` per endpoint — `success`, `permanent_failure` or
32
+ `transient_failure` — alongside the existing `success` boolean, plus
33
+ `retryable`, `signed`, `attempts` and `url_display` fields. Previously the
34
+ only signal was a bare boolean, which collapsed "delivered", "will never
35
+ work" and "try again later" into one value.
36
+ - `WebhookDispatcher.retry_failed(event, results)` re-attempts only the
37
+ endpoints that still owe the event, skipping the ones that already delivered
38
+ and the ones that failed permanently. Module-level `retryable_results()`
39
+ (what is still owed) and `all_delivered()` (is anything owed) give a caller
40
+ everything needed to implement per-endpoint at-least-once.
41
+ - `daemon test-webhook` output now carries `url_display`, `outcome`,
42
+ `retryable`, `signed` and `attempts`.
43
+
44
+ ### Fixed
45
+ - **A webhook with no secret no longer ships unsigned payloads silently.**
46
+ `if webhook.secret:` treated `""` as "no signing", so an empty secret sent
47
+ unsigned payloads with no warning anywhere. The dispatcher now logs an ERROR
48
+ once per endpoint explaining that every payload is UNSIGNED and that a
49
+ verifying receiver will reject it, and every result carries `signed: false`.
50
+ - **`daemon test-webhook` now validates the URL.** `test_ping` bypassed
51
+ `_validate_webhook_url`, so the scheme/host guard applied on every real
52
+ dispatch was skipped on the one command where a user is most likely to paste
53
+ a wrong URL — `file://` and friends reached `requests` and surfaced as a
54
+ confusing network error. An invalid URL is now rejected before any network
55
+ call with `invalid_webhook_url:<reason>`.
56
+ - **Webhook URLs are no longer logged verbatim.** `log.info`/`log.warning`/
57
+ `log.error` on every delivery and retry printed the full configured URL, so
58
+ providers that carry the credential in the path or query (Slack
59
+ `hooks.slack.com/services/T…/B…/<token>`, Bark `api.day.app/<key>/…`, WeCom
60
+ `…/send?key=…`) wrote a live token into `daemon.log`. URLs are now redacted
61
+ before logging — scheme, host, port and path shape are kept, credential-
62
+ bearing path segments and query values are masked, and any `user:password@`
63
+ userinfo is dropped. `daemon test-webhook` output additionally carries a
64
+ `url_display` field.
65
+ - **The webhook retry "hard ceiling" now bounds request time, not just
66
+ sleeps.** `MAX_TOTAL_RETRY_SECONDS` was checked only before `time.sleep`, so
67
+ each attempt's `requests.post(timeout=10)` was unbounded by it and the final
68
+ attempt always ran its full timeout. With the default `max_retries=3` one
69
+ hanging endpoint cost ~33s of blocked polling, and the cost was linear in the
70
+ number of configured endpoints (2 endpoints ≈ 66s against a 30s poll
71
+ interval). Each attempt's timeout is now clamped to the remaining budget and
72
+ the backoff sleep is clamped to it too, so one event can never exceed the
73
+ ceiling.
74
+ - **Permanent 4xx are no longer retried.** 400/401/403/404/410/422 were retried
75
+ three times with backoff even though no backoff can fix them, which both
76
+ stalled the poll loop and delayed the diagnosis. Only genuinely transient
77
+ statuses are retried now: 5xx, 408, 429 (and 425).
78
+ - **Webhook redirects are no longer followed.** `requests.post` followed
79
+ redirects with the signed body and the signature headers intact, so a 307
80
+ re-sent them to a *different* host and a 301/302 could downgrade https to
81
+ http — defeating the point of signing. Dispatch now passes
82
+ `allow_redirects=False`; a 3xx is reported as a permanent failure naming the
83
+ `Location` so the configured URL can be corrected.
84
+
85
+ ### Changed
86
+ - **BREAKING PROTOCOL CHANGE — webhook signatures.** `X-MB-Signature` now covers
87
+ `X-MB-Timestamp` as well as the body:
88
+ `sha256=` + HMAC-SHA256(secret, `f"{X-MB-Timestamp}.".encode() + body`). It
89
+ previously covered the body alone, which left `X-MB-Timestamp`
90
+ unauthenticated — anyone who captured a single POST could replay it
91
+ indefinitely by rewriting that header, because the original digest still
92
+ validated and the receiver's freshness check (`MAX_TIMESTAMP_SKEW_SECONDS`)
93
+ waved the replay through. **Any deployed receiver rejects every payload until
94
+ it adds the timestamp to its signed material.** `extras/mb-notifier/bark_webhook_receiver.py`
95
+ and the FastAPI recipe in `docs/events.md` are updated in the same change;
96
+ `verify_signature` now fails closed on a missing `X-MB-Timestamp` and checks
97
+ the digest before freshness, so a restamped payload reports
98
+ `signature_mismatch` rather than merely `stale_timestamp`.
99
+
100
+ ### Added
101
+ - `CHANGELOG.md`, `SECURITY.md`, and GitHub Actions CI (`.github/workflows/ci.yml`).
102
+ - `[dependency-groups]` `dev` group in `pyproject.toml` declaring the test
103
+ dependencies (`pytest`, `requests-mock`, `mcp`) that the suite always needed
104
+ but that were never declared, so a plain `uv sync` could not run the tests.
105
+
106
+ ### Fixed
107
+ - **Piped output is now the documented JSON.** `resolve_format` returned `pretty`
108
+ for any unset `--format`, with no TTY check, so `mb list | jq .` failed with a
109
+ parse error and every JSON consumer had to remember `--format json`. It now
110
+ picks `pretty` for an interactive terminal and `json` otherwise (matching the
111
+ `--format` help text and the README), with `MB_CLI_FORMAT=json|pretty` as an
112
+ escape hatch for scripts that run with and without a terminal.
113
+ - **MCP `list_tasks` no longer answers "no homework" for a misspelled `view`.**
114
+ An unrecognised value matched none of the three section checks, so all three
115
+ lists stayed empty and the tool returned `total_count: 0` — a valid-looking
116
+ wrong answer. `view` is now validated against one canonical vocabulary
117
+ (case-insensitive, with aliases like `Upcoming` / `upcoming tasks`) shared with
118
+ `filters.result_views`, and an unknown value returns a structured error instead
119
+ of crawling anything.
120
+ - **`--grade` / `grade=` now match `+`/`-` modifiers.** The extraction regex
121
+ `^([A-F][+-]?)\b` could never capture the modifier — there is no word boundary
122
+ between `+`/`-` and a following space, so it always backtracked to empty and
123
+ `"A+ (95/100)"` was read as `"A"`. A task whose card carries only
124
+ `grade_score: "A+ (95/100)"` was therefore invisible to `--grade A+`.
125
+ - **Mixed naive/aware due dates can no longer crash the pretty renderer.**
126
+ `parse_due_date` returns an aware datetime for ISO input with an offset and a
127
+ naive one for every HTML format; sorting a section that contained both raised
128
+ `TypeError: can't compare offset-naive and offset-aware datetimes` out of
129
+ `render_pretty`, which `main()` does not catch — the user got a traceback and
130
+ no payload. Both `task_sort_key` and `classify_task_view` now normalise through
131
+ one shared helper.
132
+ - **MCP tools validate their inputs.** `view_task` derived the task id with
133
+ `target.split("core_tasks/")[-1].split("/")[0]`, so a URL without
134
+ `/core_tasks/` made the entire string the id; `get_class_grades` interpolated
135
+ `class_id` straight into a ManageBac URL path; `get_calendar_events` /
136
+ `get_timetable` passed arbitrary strings into query params; and `submit_file`
137
+ handed an unchecked path to the filesystem. Each now returns a structured,
138
+ actionable error for a malformed argument instead of a 404 or a raw
139
+ `FileNotFoundError`.
140
+ - **Packaging:** the sdist no longer leaks a nested copy of the repository. It
141
+ previously shipped 76 entries under `.claude/`, including
142
+ `.claude/worktrees/finish-security-audit/` — a complete clone of the repo with
143
+ its own `.git` (~75 MB). Fixed in both belts: `.claude/` added to
144
+ `.gitignore`, and an explicit `[tool.hatch.build.targets.sdist]` include/exclude
145
+ list in `pyproject.toml` that pins exactly what ships. The sdist went from 153
146
+ entries to 63 files; no `.pyc`, no `.git`, no `.venv`, and no
147
+ `docs/superpowers/` internal design docs ship.
148
+ - `mb --config` / `--session-file` help text no longer claims the format is
149
+ TOML; it is JSON (`config.json` / `session.json`).
150
+ - `docs/library.md` no longer points at a non-existent
151
+ `~/.config/mb-crawler/config.toml`.
152
+ - **The `mcp` extra is now bounded (`mcp>=1.20,<2`).** It was unbounded, so a
153
+ fresh resolve installed mcp 2.x, which removed `mcp.server.fastmcp` — every
154
+ `mb-mcp` invocation died with `ModuleNotFoundError` on import, even when the
155
+ extra was installed correctly.
156
+
157
+ ### Changed
158
+ - README Installation now documents the `mcp` extra
159
+ (`pip install "mb-cli[mcp]"`) — the documented `mb-mcp` command imports `mcp`
160
+ unguarded and failed with `ModuleNotFoundError` on a plain install.
161
+ - README now documents `mb daemon start -b` / `--background`. `daemon start`
162
+ runs in the **foreground** by default and dies with the terminal; only `-b`
163
+ detaches it. `--pid-file` / `--log-file` documented per subcommand.
164
+ - README now documents the previously undocumented `mb submissions`,
165
+ `mb download`, `mb feedback`, and `mb daemon install` / `uninstall` /
166
+ `configure-channel` subcommands.
167
+ - README now documents the `MB_CRAWLER_PASSWORD` and `MB_CRAWLER_COOKIE`
168
+ environment variables alongside `MB_WEBHOOK_SECRET`.
169
+ - README now flags the `--poll-interval` (`mb daemon run`) vs `--interval`
170
+ (`mb daemon start`) flag-name asymmetry, and qualifies the claim that
171
+ `--config` / `--session-file` are universal — the daemon subcommands expose
172
+ only `--daemon-config` and/or `--pid-file` / `--log-file`, varying by
173
+ subcommand.
174
+
175
+ ## [0.3.0] - 2026-09-17
176
+
177
+ ### Added
178
+ - Complete library + downstream security audit.
179
+ - Comprehensive Python SDK and library reference (`docs/library.md`).
180
+
181
+ ### Changed
182
+ - Bumped version to 0.3.0 and documented remote separated deployment.
183
+ - `ManageBacClient.from_config()` implemented; `docs/events.md` updated.
184
+
185
+ ### Fixed
186
+ - CLI command flags, the systemd unit, and Python SDK snippets corrected in the
187
+ README and the notifier guide.
188
+ - `student_name` fallback polished and covered by a test for the default
189
+ `from_config` argument.
190
+
191
+ ## [0.2.5] - 2026-09-14
192
+
193
+ ### Added
194
+ - Real-time event streaming: `ManageBacDaemon.stream()` async event generator.
195
+ - Comprehensive event stream specification and integration guide (`docs/events.md`).
196
+ - Design specs and implementation plan for modular event stream and notifier
197
+ separation.
198
+
199
+ ### Changed
200
+ - Standardized the `MBEvent` payload schema.
201
+ - Extracted the Bark webhook receiver and personal config into
202
+ `extras/mb-notifier`.
203
+ - Refreshed the README and added the downstream notifier guide.
204
+
205
+ ### Fixed
206
+ - Stream lifecycle and queue-safety issues raised in review.
207
+ - Defensive fallback URL resolution in `service.py`.
208
+
209
+ ## [0.2.4] - 2026-09-13
210
+
211
+ ### Added
212
+ - `mb submissions` CLI for submission lifecycle management (`--list`, `--add`,
213
+ `--delete`, `--check-feedback`), with a matching MCP tool.
214
+
215
+ ### Changed
216
+ - Bumped version; grade-release alert suppression and webhook improvements.
217
+
218
+ ## [0.2.3] - 2026-09-05
219
+
220
+ ### Added
221
+ - Unified task status and lifecycle domain model.
222
+ - Teacher feedback fetching, including support for modern task detail page
223
+ submissions and the PSPDFKit preview modal.
224
+ - `on_start` lifecycle callback for a full upcoming-task refresh.
225
+ - Just-in-time submission verification, eliminating unnecessary full recrawls.
226
+ - Lightweight Bark webhook receiver adapter.
227
+ - Real-time notification daemon and webhook engine with the MNN Hub provider and
228
+ a DDL scheduler, plus a complete E2E integration test suite.
229
+ - Compact 3-field notification layout with exact course alias resolution.
230
+ - Design specs for the daemon, the compact Bark layout, the on-start refresh
231
+ callback, unified task status, and submissions lifecycle management.
232
+
233
+ ### Fixed
234
+ - Daemon no longer notifies for submitted or graded tasks.
235
+ - `_check_is_task_submitted` now checks the task page Submitted badge.
236
+ - Past milestones suppressed on task discovery; DDL reminders formatted with the
237
+ exact remaining time.
238
+ - `new_task` event mapped; class and task names cleaned; richer 4-line Bark
239
+ notification.
240
+ - Bark notification format enforced as a compact 4-line layout bounded to the
241
+ date line width.
242
+ - `run_forever` alias restored on `DaemonService`.
243
+ - Resolved code review issues in the daemon.
244
+
245
+ ### Changed
246
+ - Deduplicated task URL parsing (`parse_task_url`), task classification logic
247
+ across main/client/formatters, snapshot IO and diffing in the daemon package,
248
+ and submission/completion/classification logic in `filters.py`; the stealth
249
+ crawler now reuses `client.get_submissions`.
250
+ - `.worktrees/` ignored; `course_aliases.json` ignore scoped to root level.
251
+
252
+ ## [0.2.2] - 2026-06-13
253
+
254
+ ### Added
255
+ - Auth health check and silent re-login on an expired cookie.
256
+ - `load_creds()` for external credential files.
257
+
258
+ ### Fixed
259
+ - Cache namespaced by email hash; `list_tasks` crawl optimized.
260
+ - `submit_file` task ID resolution optimized.
261
+ - Cache no longer sleeps on cache hits inside crawler loops.
262
+ - Hub jitter adjusted to 1–3s for reads and removed from mutations.
263
+
264
+ ## [0.2.0] - 2026-04-30
265
+
266
+ ### Added
267
+ - Stealth daemon with active windows and index-only diffing.
268
+ - Daemon active hours and randomized polling interval.
269
+ - `channel_send` delivery mode.
270
+ - Remember-me login support (30-day sessions by default).
271
+ - Referer header and random jitter between requests.
272
+
273
+ ### Fixed
274
+ - Pagination fixed; retry with backoff and grade frequency counting added.
275
+
276
+ ## [0.1.0] - 2026-04-29
277
+
278
+ ### Added
279
+ - Initial release: `mb` CLI, `ManageBacClient` SDK, disk-based response cache
280
+ with configurable TTL.
281
+ - Status and grade filters, auto-aggregated grades, task grade parsing.
282
+ - Pretty-printed task listing table with CJK/double-width alignment padding.
283
+ - `mb list --tag / -t` filtering and tag filtering in the MCP `list_tasks` tool.
284
+ - `mb list --deleted` to include tasks deleted from the server; instant
285
+ snapshot caching; grade-status rendering (Complete / Incomplete) and a
286
+ combined letter + score grade column.
287
+ - Pretty table formatter for `count-grade-freq`; attachment downloads;
288
+ student name captured and updated on dashboard loads.
289
+ - Legal disclaimer regarding the Faria/ManageBac terms of service.
290
+ - Project renamed from `mb-crawler` to `mb-cli` (command: `mb`).
291
+
292
+ [Unreleased]: https://github.com/allen/mb-crawler/compare/v0.3.0...HEAD
293
+ [0.3.0]: https://github.com/allen/mb-crawler/compare/v0.2.5...v0.3.0
294
+ [0.2.5]: https://github.com/allen/mb-crawler/compare/v0.2.4...v0.2.5
295
+ [0.2.4]: https://github.com/allen/mb-crawler/compare/v0.2.3...v0.2.4
296
+ [0.2.3]: https://github.com/allen/mb-crawler/compare/v0.2.2...v0.2.3
297
+ [0.2.2]: https://github.com/allen/mb-crawler/compare/v0.2.0...v0.2.2
298
+ [0.2.0]: https://github.com/allen/mb-crawler/compare/v0.1.0...v0.2.0
299
+ [0.1.0]: https://github.com/allen/mb-crawler/releases/tag/v0.1.0
tahuti-0.4.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Allen Sun
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.