@ivanbard/dd2l 0.0.0-stage → 0.1.1

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +122 -3
  3. package/THIRD_PARTY_NOTICES.md +50 -0
  4. package/bin/dd2l.mjs +7 -0
  5. package/docs/README.md +227 -0
  6. package/package.json +56 -6
  7. package/src/app-runner.mjs +58 -0
  8. package/src/assignment-workspace.mjs +128 -0
  9. package/src/canvas.mjs +17 -0
  10. package/src/cli-banner.mjs +58 -0
  11. package/src/cli.mjs +142 -0
  12. package/src/client.mjs +150 -0
  13. package/src/connector.mjs +587 -0
  14. package/src/discovery.mjs +45 -0
  15. package/src/documents.mjs +154 -0
  16. package/src/links.mjs +99 -0
  17. package/src/llm.mjs +243 -0
  18. package/src/media.mjs +54 -0
  19. package/src/ocr.mjs +24 -0
  20. package/src/office.mjs +61 -0
  21. package/src/pdf-worker.mjs +26 -0
  22. package/src/research.mjs +70 -0
  23. package/src/session.mjs +142 -0
  24. package/src/store.mjs +108 -0
  25. package/src/syllabus.mjs +191 -0
  26. package/src/task-workspace.mjs +138 -0
  27. package/src/ui-data.mjs +123 -0
  28. package/src/ui-demo.mjs +36 -0
  29. package/src/ui-server.mjs +161 -0
  30. package/src/windows-ocr.ps1 +32 -0
  31. package/ui/answer-renderer.js +88 -0
  32. package/ui/answer-view.js +47 -0
  33. package/ui/app.js +385 -0
  34. package/ui/assets/LUCIDE-LICENSE.txt +15 -0
  35. package/ui/assets/avatar.svg +3 -0
  36. package/ui/assets/chevron.svg +5 -0
  37. package/ui/assets/courses.svg +14 -0
  38. package/ui/assets/logo.svg +13 -0
  39. package/ui/assets/new-chat.svg +14 -0
  40. package/ui/assets/search.svg +14 -0
  41. package/ui/assets/send.svg +8 -0
  42. package/ui/assets/settings.svg +14 -0
  43. package/ui/assets/widget-announcements.svg +4 -0
  44. package/ui/assets/widget-shortcuts.svg +5 -0
  45. package/ui/assignment-workspace.js +106 -0
  46. package/ui/canvas-contract.js +41 -0
  47. package/ui/canvas-runtime.js +158 -0
  48. package/ui/canvas.js +96 -0
  49. package/ui/index.html +84 -0
  50. package/ui/loading.js +33 -0
  51. package/ui/style.css +241 -0
  52. package/ui/widget-layout.js +39 -0
  53. package/ui/widgets.js +252 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ivanbard
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.
package/README.md CHANGED
@@ -1,3 +1,122 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # dd2l
2
+
3
+ i am not the biggest fan of D2L Brightspace, and sometimes wish i could get what im looking for without digging through 14 menus that differ from course to course. to solve that i made dd2l, a dynamic companion app to D2L Brightspace that does what you need, and adapts to your needs with time.
4
+
5
+ dd2l is a local student companion for Queen's onQ. Sync your courses, search
6
+ your materials, and ask questions with sources.
7
+
8
+ ## How it works
9
+
10
+ - sign in to onQ through Chrome, including your school's MFA
11
+ - collect course materials, assignments, grades, announcements and syllabus text
12
+ - search the local cache, browse sources, and customize your home screen widgets
13
+ - connect ChatGPT to compose adaptive workspaces around your goals
14
+
15
+ Connect ChatGPT in Settings to use your existing plan, then describe what you
16
+ want to do: **“Help me get ready for tomorrow's seminar”**, **“What should I
17
+ prioritize across my classes?”**, or **“I'm stuck on the filter design report.”**
18
+ The connected model interprets the goal, selects relevant records across
19
+ courses, and generates an interactive workspace: a planner, practice session,
20
+ rubric explorer, calculator, or another layout suited to the task. There is no required prompt format or fixed set of
21
+ supported task names. If it needs clarification, answer in the same composer.
22
+
23
+ Follow-ups carry the workspace's goal, generated interface and local progress, so
24
+ you can change the layout while retaining notes and checked items. Use **Sources**
25
+ to inspect verified coursework, or **Restart progress** to reset the current canvas. A connected model is selected by default;
26
+ requests run only when you send a prompt, with the sharing notice visible.
27
+ Choose **Local search** for keyword search and deterministic assignment views
28
+ without sending data to AI. New search clears the goal; changing course clears
29
+ context and sets a hard scope for the next request.
30
+
31
+ The model can discover course areas, read uncached documents, and follow supported
32
+ document links through the connector before composing the workspace. Retrieved
33
+ materials enter the same cache used by search and sync. This covers accessible
34
+ course content, assignment instructions/rubrics/attachments, your submission or
35
+ quiz history, discussions, grades, announcements and calendar information.
36
+
37
+ Adaptive requests use planning followed by interface generation from selected
38
+ excerpts. Live research adds planning turns, bounded to four read rounds and eight
39
+ connector operations per request. Generated HTML/CSS
40
+ renders inside an isolated frame; JavaScript runs in a network-blocked worker
41
+ through a small state/event interface. No frontend framework or external
42
+ inference service is required. Local progress lasts for the current visit. Missing, inaccessible,
43
+ and stale records remain explicit. Cached submissions do not confirm current
44
+ submission status, and generated guidance should be checked against its sources.
45
+
46
+ The connector is read-only. It does not submit work or change grades, posts,
47
+ quiz attempts or completion states. Coverage depends on what onQ makes accessible.
48
+ Live research uses approved API and document readers; it cannot click through
49
+ arbitrary pages or sign into private external course tools.
50
+
51
+ ## School not listed?
52
+ Choose **My school isn't listed** during setup and paste your school's
53
+ Brightspace address; `https://` is added if omitted. dd2l uses that site for sign-in, syncing, and source links, and
54
+ remembers it for later launches. Other schools have not been verified; their
55
+ authentication or API access may differ. You can also [report a missing school](https://github.com/ivanbard/dd2l/issues/new?template=missing-url.yml)
56
+ using the guided issue form. Include your school's name and Brightspace URL if
57
+ known to help add official support.
58
+ Stop dd2l and run `dd2l logout` before switching schools; this deletes its saved
59
+ school session and coursework cache.
60
+
61
+ ## Run
62
+
63
+ Requires Windows, Node.js 24 or newer, and Google Chrome. This repository includes
64
+ an npm CLI; the commands below work once the package is published to npm:
65
+
66
+ ```powershell
67
+ npx @ivanbard/dd2l setup
68
+ ```
69
+
70
+ Setup explains local storage, asks before connecting onQ, opens Chrome for school
71
+ sign-in and MFA, then opens the app and starts collecting coursework. Keep that
72
+ one terminal open; Ctrl+C stops both the app and sync. The initial view may be
73
+ empty while collection runs. Collection advances through up to five courses per
74
+ pass every 15 minutes, so the first pass is not a complete download.
75
+
76
+ Next time, run `npx @ivanbard/dd2l start`. If the school session expires, stop the app and
77
+ run `npx @ivanbard/dd2l setup` again. Optional ChatGPT sign-in is in Settings.
78
+
79
+ Alternatively, install globally with `npm install -g @ivanbard/dd2l`, then use `dd2l setup`
80
+ and `dd2l start`. Installation itself does not open browsers or connect accounts;
81
+ setup runs explicitly in an interactive terminal.
82
+
83
+ To run the current source checkout before publication:
84
+
85
+ ```powershell
86
+ npm ci
87
+ npm run setup
88
+ ```
89
+
90
+ On later launches use `npm start -- start`. The individual `login`, `audit`,
91
+ `sync`, and `npm run ui` commands remain available for manual operation.
92
+
93
+ Or try fictional data without a school login:
94
+
95
+ ```powershell
96
+ npm run ui -- --demo
97
+ ```
98
+
99
+ ## Your data
100
+
101
+ School sessions stay on your computer, encrypted with Windows DPAPI. The course
102
+ cache lives in `%LOCALAPPDATA%\dd2l` and is **not encrypted**. Local search does
103
+ not send your question to an AI provider.
104
+
105
+ For AI answers, connect ChatGPT in Settings and choose a model below the prompt.
106
+ Submitting sends your goal, previous workspace context, course previews and selected excerpts to OpenAI and uses
107
+ your plan's limits. Check generated answers against the supplied sources.
108
+
109
+ Stop the app, then run `dd2l logout` (or `npm start -- logout` from source) to
110
+ remove the saved school session and inventory. Disconnect
111
+ ChatGPT separately in Settings; clear the site's browser data to remove saved
112
+ widget preferences and shortcut titles.
113
+
114
+ More docs are in [`docs/`](docs/README.md), including commands, ChatGPT setup,
115
+ collection limits and cache behavior.
116
+
117
+ Run `npm test` for tests and `npm run ui:check` for browser checks.
118
+ Third-party attribution is in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
119
+
120
+ Original dd2l code is [MIT licensed](LICENSE); third-party assets retain their
121
+ own licenses. Maintainers: see [PUBLISHING.md](https://github.com/ivanbard/dd2l/blob/main/PUBLISHING.md)
122
+ for release setup.
@@ -0,0 +1,50 @@
1
+ # UI icons
2
+
3
+ The four sidebar action icons are from [Lucide](https://lucide.dev), version 0.468.0 (square-pen, search, book-open, settings), with their stroke color set to match the UI. The upstream ISC license notice is retained in [ui/assets/LUCIDE-LICENSE.txt](ui/assets/LUCIDE-LICENSE.txt). The SVGs are bundled locally; no icon runtime or external requests are required.
4
+
5
+ # Dot-matrix loading indicator
6
+
7
+ The app's 5×5 Origin Wave indicator is adapted from `DotmSquare12` (`@dotmatrix/dotm-square-12`), dot-matrix v1.5.0 by [zzzzshawn/matrix](https://github.com/zzzzshawn/matrix), as published at [Dot Matrix](https://dotmatrix.zzzzshawn.cloud/). `ui/loading.js` and the `dot-origin-wave` CSS preserve its origin, Manhattan ring delays, opacity curve and reduced-motion appearance in plain JavaScript/CSS. This adaptation is bundled for use inside dd2l, not offered as a standalone component or library. No React, Tailwind, registry CLI or external runtime is bundled.
8
+
9
+ The upstream license is retained below:
10
+
11
+ Custom Proprietary License
12
+
13
+ Copyright (c) 2026 dot-matrix contributors
14
+ All rights reserved.
15
+
16
+ Permission is granted to use this software, including in commercial and non-commercial products.
17
+
18
+ The following are strictly prohibited without prior written permission from the copyright holder:
19
+ 1. Publishing, distributing, or presenting any component from this project as a standalone reusable component, or claiming it as part of another component library or similar reusable component collection.
20
+ 2. Selling, sublicensing, renting, leasing, or otherwise commercializing any component from this project, whether modified or unmodified, as a standalone component offering.
21
+
22
+ Any use not expressly permitted by this license is prohibited.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ # Reference implementation
27
+
28
+ Authentication behavior and endpoint discovery were informed by [brightspace-mcp-server](https://github.com/RohanMuppa/brightspace-mcp-server), version 5.1.27, by Rohan Muppa. dd2l does not import or run that server. The applicable notice is retained here for attribution of the reference work:
29
+
30
+ MIT License
31
+
32
+ Copyright (c) 2026 Rohan Muppa
33
+
34
+ Permission is hereby granted, free of charge, to any person obtaining a copy
35
+ of this software and associated documentation files (the "Software"), to deal
36
+ in the Software without restriction, including without limitation the rights
37
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
38
+ copies of the Software, and to permit persons to whom the Software is
39
+ furnished to do so, subject to the following conditions:
40
+
41
+ The above copyright notice and this permission notice shall be included in all
42
+ copies or substantial portions of the Software.
43
+
44
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
45
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
46
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
47
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
48
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
49
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
50
+ SOFTWARE.
package/bin/dd2l.mjs ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ if (Number(process.versions.node.split('.')[0]) < 24) {
3
+ console.error('dd2l requires Node.js 24 or newer. Install it from https://nodejs.org, then retry.');
4
+ process.exitCode = 1;
5
+ } else {
6
+ await import('../src/cli.mjs');
7
+ }
package/docs/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # dd2l guide
2
+
3
+ A direct, read-only Brightspace connector for a customizable, promptable student companion. The first target is one Queen's onQ account on Windows.
4
+
5
+ The connector signs in using your installed Google Chrome browser, reuses a protected school session, inventories course data, and can refresh a selected course or rotate through all accessible courses. It runs directly against Brightspace; no MCP server or model provider is involved.
6
+
7
+ ## Run
8
+
9
+ Setup lists Queen's onQ and lets you enter another school's HTTPS Brightspace
10
+ URL. The `https://` prefix is optional and added automatically. Pasted course URLs are reduced to their site origin; credentials embedded
11
+ in a URL are rejected. The selected site is saved after successful sign-in and
12
+ reused by subsequent connector commands and cached source links. Existing
13
+ Queen's installations keep working without setup changes. Other schools remain
14
+ unverified; use the issue link shown during setup to request official support.
15
+ Stop the worker and run `logout` before switching accounts or schools.
16
+
17
+ The guided source-checkout path is `npm ci`, then `npm run setup`. Setup asks
18
+ before connecting onQ, completes school sign-in, opens the UI, and runs sync in
19
+ the same terminal. Use `npm start -- start` on later launches. Keep the terminal
20
+ open; Ctrl+C stops both. If sign-in expires, rerun setup. Collection remains
21
+ incremental, with the same batching and 15-minute cadence described below.
22
+
23
+ The npm package exposes `dd2l setup` and `dd2l start` (or `npx @ivanbard/dd2l setup` and
24
+ `npx @ivanbard/dd2l start` without a global install) once published. It has no interactive
25
+ install hook. The repository has not been published by this setup change.
26
+
27
+ For manual operation:
28
+
29
+ Requires Windows, Node.js 24 or newer, and Google Chrome. From this directory:
30
+
31
+ ```powershell
32
+ npm ci
33
+ npm run login
34
+ npm start -- courses
35
+ npm run audit
36
+ npm start -- syllabus
37
+ npm run status
38
+ ```
39
+
40
+ Complete school sign-in and MFA in Chrome when requested. No school password is collected or saved. The default audit prefers the most recently accessed active, accessible course, falling back to another accessible enrollment if necessary. To select a particular course, use its ID from `courses`:
41
+
42
+ ```powershell
43
+ npm run audit -- --course COURSE_ID --from 2026-09-01T00:00:00Z --to 2027-01-01T00:00:00Z
44
+ npm run sync -- --course COURSE_ID
45
+ npm start -- syllabus --course COURSE_ID
46
+ npm run audit -- --all
47
+ npm run sync -- --all
48
+ ```
49
+
50
+ Replace `COURSE_ID` with the numeric ID. `sync` keeps running in the terminal until Ctrl+C; shutdown waits for the current bounded audit to finish. Without `--course`, sync reuses the last selected course and pins that choice for subsequent polls. Keep the computer and terminal running. If the school session expires, the worker stops with `needs_login`; run `login`, then restart `sync`. Temporary failures back off, and server retry guidance is respected. Concurrent mutating commands are prevented by a process lock; `status` can inspect a running worker.
51
+
52
+ `--all` rotates through up to five accessible course offerings per pass, including historical courses still accessible to this account. It preserves the pinned single-course selection. Each course's assignment, document and discussion cursors are saved after its audit commits; another pass resumes the remaining objects. Sync waits 15 minutes between passes, so a full sweep can take multiple polling intervals. A rate-limit response stops further reads in that pass and honors the server's retry delay.
53
+
54
+ For a sustained foreground check, run `npm run soak -- --minutes 180`. This uses the real sync cadence, writes structural results to the ignored `.dd2l/soak-summary.json`, and stops after the observation window and any in-progress audit finish. It does not force session expiry or bypass MFA. A one-minute smoke run has passed; a multi-hour run remains to be performed.
55
+
56
+ Run `npm start -- --help` for commands and `npm test` for the focused checks.
57
+
58
+ ## UI prototype
59
+
60
+ ```powershell
61
+ npm run ui
62
+ ```
63
+
64
+ Open the printed [local URL](http://127.0.0.1:4173). The home screen implements the [Figma Home design](https://www.figma.com/design/ASr0dnwupPLbXmeT1AiPGg/dd2l?node-id=1-3): prompt, course selector, sidebar and profile. Search finds keyword matches in cached course materials and displays source excerpts with retrieval dates and stale-cache labels. Courses opens a material browser; Settings and Profile show connection status and refresh instructions. The source link opens the course in onQ using your normal browser sign-in.
65
+
66
+ Local search reads the existing inventory without changing it or loading school credentials. Run `npm run audit -- --all` or keep `npm run sync -- --all` running separately to collect data. Each search reads the current cache. Only courses confirmed accessible in cached enrollments are shown; unavailable material stays unavailable, and coverage is limited to what the connector has collected. The mode dropdown uses an available ChatGPT model when connected, or **Local search** otherwise. Local search matches keywords; submitting with a connected model builds a workspace around your goal.
67
+
68
+ The homescreen includes upcoming deadlines, recent class announcements, and shortcuts to classes or cached materials. **Customize** enables a four-column, eight-row grid: drag a widget to move it live, or drag any edge or corner to resize it. A cell preview marks the proposed placement; overlapping or out-of-bounds drops return to the original position. Escape cancels the gesture. Focus a widget and use arrow keys to move or Shift + arrows to resize without a pointer. Remove widgets or add them back with **Add widget**. Narrow screens stack widgets normally and show a horizontally scrollable grid while customizing. **Pin shortcut** opens a searchable class/material picker; selecting a shortcut browses its cached sources. New search returns to the homescreen.
69
+
70
+ Waiting states use the Dot Matrix Square 12 / Origin Wave indicator: search and AI requests, course/model loading, widget refreshes, panels and account actions. Its 5×5 ripple is adapted to plain JavaScript/CSS, bundled locally and static when reduced motion is preferred. Loading states clear after success, failure or cancellation; concurrent loads share an indicator until all finish. Attribution and the upstream usage license are in [THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md).
71
+
72
+ Layouts and up to 20 shortcut references save in browser local storage, separately for each school account and demo mode. They do not sync across browsers or ports. Shortcut references include course-material titles and remain after CLI logout; clear this site's browser data to remove saved preferences. Inaccessible material is not rendered even if its reference remains pinned. **Refresh cache view** rereads the local inventory, without running a school sync. Deadlines require explicit due dates, announcements use publication dates when available, and stale cache entries remain labeled; an empty widget does not establish that onQ has no updates.
73
+
74
+ ### Use your ChatGPT plan
75
+
76
+ In the live UI, open Settings or Profile and choose **Continue with ChatGPT**.
77
+ Complete registration and plan-use consent on OpenAI's website, then return to
78
+ dd2l. No API key is needed. An available connected model is selected by default;
79
+ the sharing notice identifies AI mode. Sending a prompt starts a request;
80
+ loading the app, typing and changing models never send coursework. Choose
81
+ **Local search** for local keyword retrieval and assignment views. New search
82
+ clears workspace context and restores the connected model default.
83
+
84
+ Adaptive mode has no assignment-specific prompt gate or fixed intent list.
85
+ The first model call receives the request, prior workspace goal and selected
86
+ source references, accessible course names, and up to 400 catalogue previews
87
+ (maximum 400 characters of source text each). Catalogue selection includes
88
+ keyword matches, previous references, and diverse course/category records.
89
+ When evidence is missing, stale, or needs a current check, the model can request
90
+ read-only connector operations and plan again. It can discover courses and course
91
+ areas, read discovered content files, assignment details/rubrics/own status and
92
+ attachments, quiz metadata/own attempts, discussion forums/topics/posts, overview
93
+ attachments, grades, announcements and calendar events. Supported document links
94
+ can be followed from those resources. Discovery returns request-local handles;
95
+ the model cannot supply arbitrary network URLs. New observations update the same
96
+ inventory, without changing the sync course or traversal cursors. Freshly retrieved
97
+ records receive priority in the next catalogue.
98
+
99
+ Research allows at most two operations per planning turn, four read rounds and
100
+ eight connector operations. A final planning turn must finish with available
101
+ evidence or explain what is missing. Requests have a five-minute overall deadline;
102
+ each operation may involve several bounded API/document reads. Calendar discovery
103
+ covers the previous 30 and next 120 days. Live reads share the connector queue with
104
+ sync, so an in-progress course pass can delay them. Use the UI opened by `dd2l start`
105
+ to share its connector; standalone `npm run ui` acquires the inventory lock for each
106
+ operation and reports if another command owns it.
107
+
108
+ The model chooses source IDs and purpose-specific groups in relevance order,
109
+ or asks for clarification. The server validates the plan, enforces the selected
110
+ course scope, and rechecks current access before assembling sources.
111
+
112
+ After planning, another model call generates a workspace program: title, short answer, HTML
113
+ fragment (up to 60,000 characters), CSS (20,000), and a JavaScript function body
114
+ (60,000), grounded in up to 24 selected excerpts of 8,000 characters each. The
115
+ interface can be a planner, practice session, rubric explorer, form, diagram or
116
+ calculator. It is not limited to a catalogue of prebuilt task widgets. All calls
117
+ use the selected model and plan limits: normally two calls, up to five planning
118
+ calls plus one canvas call when researching. Clarifications use only planning. Failed
119
+ generation retains the verified source view; no retries or fallback models are
120
+ billed automatically. Sources and cache coverage remain available from **Sources**.
121
+
122
+ The implementation uses browser primitives rather than introducing React and a
123
+ second agent runtime. The dedicated frame has an opaque origin and restrictive
124
+ CSP. Only the trusted frame runtime touches the DOM. Generated JavaScript runs
125
+ in a Blob worker with network access blocked, no DOM or frame navigation, opaque-origin storage restrictions,
126
+ and a two-second response watchdog. Generated HTML is sanitized to semantic HTML,
127
+ form controls and SVG geometry; scripts, embedded frames, event handlers and
128
+ navigation/resource URLs are removed. CSS remains confined to the frame. This
129
+ follows the isolated generative canvas pattern while keeping the current server
130
+ and ChatGPT authentication path.
131
+
132
+ The generated function receives `state`, `event`, and selected `sources`, and
133
+ returns `{html, state}`. Events include click actions (`data-action`), named input
134
+ changes and submissions. Named controls are saved/restored automatically in
135
+ `state.inputs`; text controls accept up to 4,000 characters each. State must be
136
+ JSON data under 32,000 characters and twelve levels deep. The app waits for
137
+ pending worker updates before collecting progress for a follow-up. Generated
138
+ programs can also keep tabs, revealed explanations, scenario values and other
139
+ local state. Rendering applies the newest queued input so older results do not
140
+ overwrite typing.
141
+
142
+ A `data-source` button requests inspection of a selected source. The host
143
+ validates that source ID, re-reads current access, then displays a trusted source
144
+ panel and a Brightspace link. The generated program receives no credential,
145
+ arbitrary fetch, file, submission, or host navigation capability. Source URLs
146
+ come from dd2l, not generated HTML. API source references stop resolving after
147
+ access is revoked.
148
+
149
+ Follow-ups send the previous goal, selected IDs, bounded guidance summary, prior
150
+ program, current progress and any runtime error to the selected model. Prior
151
+ context and coursework are untrusted data in model instructions. **New search**
152
+ and course changes clear the canvas and context; **Restart progress** reruns the
153
+ current program with empty state. Progress is held in browser memory and clears
154
+ on reload. Editing a prompt cancels generation without discarding the current
155
+ canvas. The app enforces bounded JSON input and output, but a valid program may
156
+ still contain factual, calculation or usability errors. Check its linked
157
+ coursework and identify what to fix in a follow-up.
158
+
159
+ Each explicit submission with a model selected authorizes live read-only discovery
160
+ and sharing its results, those previews, excerpts, the previous program and local
161
+ progress with OpenAI. Local mode sends
162
+ none of them. Brightspace remains read-only. The model cannot invent missing
163
+ course records or submit work. Catalogue coverage remains bounded and is shown
164
+ in the source view when incomplete.
165
+
166
+ School identity and current enrollment are checked for each operation; hidden or
167
+ removed parents cannot authorize child reads. Login expiry and rate limiting stop
168
+ further research, with the result shown in Sources; cached evidence may still be
169
+ used with its freshness labels. This is an API/document read graph, not arbitrary
170
+ browser clicking. It cannot start quizzes, submit assignments, enumerate class
171
+ rosters, or sign into private LTI providers. Unsupported links remain gaps.
172
+
173
+ This uses OpenAI's documented [local-app Sign in with ChatGPT flow](https://developers.openai.com/siwc/token-sharing-open-source), with PKCE, state/nonce and signed identity validation. A stable host ID and separate account registrations are saved under `%LOCALAPPDATA%\dd2l\llm\accounts.dpapi`, encrypted with Windows DPAPI. Refreshes are serialized across processes using a separate lock; the school sync can run alongside AI. Tokens stay on the server and never enter browser storage. `jose` handles JWT signature and claim verification. Remote Responses requests set `store: false` and `stream: true`; partial/failed streams do not become successful answers.
174
+
175
+ Settings lists saved account/workspace registrations and supports switching, reauthorization and sign-out of the active account. Sign-out revokes its renewable session when possible and removes its local tokens; a failed remote revocation is reported. The non-secret registration and host ID remain for later sign-in. School CLI logout affects only school data; disconnect ChatGPT separately in the UI. Manage plan/app limits in [ChatGPT Settings → Usage](https://chatgpt.com/settings/usage). Usage shares the account's plan limits and may use credits according to settings you authorize there; dd2l never falls back to API-key billing. Eligibility, available models and limits are controlled by OpenAI.
176
+
177
+ ChatGPT is the first supported subscription provider. There is no universal subscription login: other providers need separately verified authentication and plan-use support. Demo mode disables provider sign-in and AI requests and never loads AI credentials. Commercial or remotely hosted deployments need a separate access review against [OpenAI's integration requirements](https://developers.openai.com/siwc/request-client-id).
178
+
179
+ For an isolated walkthrough with fictional data and no school login:
180
+
181
+ ```powershell
182
+ npm run ui -- --demo
183
+ ```
184
+
185
+ Demo mode is visibly labeled and never reads the real inventory. Try `recursion`, `memory`, or `syllabus`, filter by course, or browse Courses. Use `--port 4174` to run another instance. The server binds only to `127.0.0.1`, rejects foreign browser origins, and serves private responses without caching. Stop it with Ctrl+C when finished.
186
+
187
+ `npm test` covers retrieval, cache access, widget layout validation, deadline/announcement projection and HTTP boundaries. `npm run ui:check` uses installed Chrome headlessly to check assets, core interactions, widget drag/move/resize/pin/removal and persistence, mobile overflow and error recovery; screenshots go in ignored `.dd2l/ui-*.png`. SF Pro uses the system font when available, with Segoe UI as the Windows fallback.
188
+
189
+ ## What is collected
190
+
191
+ The audit enumerates course offerings, then reads the chosen course's content tree, assignment folders, quiz list, own grade values, announcements, discussion forums, own groups/sections, and calendar events. It traverses assignments and accessible content files in resumable batches, including own submissions, released feedback, rubric tables and attachments. Discussion forum/topic/post reads and file attachments have independent parent-specific cursors. `traversal` reports the batch limits; an unfinished batch is not reported as complete.
192
+
193
+ Assignment traversal prioritizes graded and rubric-bearing folders, then continues through the others, up to 25 per pass. Feedback targets your verified user identity or your own resolved group; an unresolved group remains unknown. Full embedded rubric tables are retained, with a dedicated association lookup used when needed. Feedback denials are not interpreted as ungraded or unsubmitted work.
194
+
195
+ Quiz discovery combines list entries, explicit content references, gradebook candidates and permitted grade/tool associations. A grade item named “Quiz” is only a candidate until a real quiz identifier/association is verified. Quiz metadata and attempts are read separately; attempts are explicitly filtered to the verified current user. Denied or unread attempts remain unknown. For events marked recurring, the connector requests the server's occurrence list rather than guessing dates or timezone rules; this route still needs a real accessible recurring-event example on onQ.
196
+
197
+ Syllabus retrieval is part of normal audit/sync and can also run alone with `syllabus`. It reads the optional overview and its attachment, relevant module/topic descriptions, and file/HTML topics under syllabus or course-information modules. It preserves each source's location, parent context, retrieval time and current access/text status. An overview-specific 404 means `not_configured`; unrelated 404s retain their original meaning. An empty overview is not counted as a readable syllabus. Different documents remain separate instead of being silently merged into one authoritative text.
198
+
199
+ Original permitted JSON fields are retained, with authentication/password fields and credential-bearing URL parameters redacted. Modern DOCX/PPTX/XLSX, PDF, HTML and plain text support local extraction; Office macros and formulas are not executed. Images and scanned PDF pages can use Windows OCR with an installed user-profile OCR language. OCR text is marked for review. Legacy binary Office files, arbitrary archives and encrypted documents are not supported by these readers.
200
+
201
+ Documents have a 20 MiB read limit; raw bytes are retained only up to 2 MiB, while larger successfully parsed documents retain text and metadata. PDF workers have a 30-second deadline, 256 MiB JavaScript heap limit and 250-page limit. At most ten empty-text PDF pages per document are rendered for OCR, with a 16-million-pixel image limit. Text is capped at one million characters. Office expansion, HTML complexity and document/link counts are bounded; limits stay explicit as partial coverage. Syllabus retrieval processes up to 100 candidate sections and 20 document attempts per course. Ordinary traversal handles 20 content files per pass, five forums, five topics per selected forum, and two attachments per selected assignment/topic. API collections follow pagination with existing loop/page limits.
202
+
203
+ Linked school documents use same-school session cookies only on approved content paths, or use their verified API topic IDs. Public reference documents are read only from explicitly supported hosts without school credentials. Source links are deduplicated; referenced websites are not recursively crawled through their citations/navigation. Surveys, self-enrollment and other activity actions are never activated by the document reader. Public YouTube caption retrieval preserves timestamps when the provider returns a caption track; sign-in/bot checks or absent captions remain unavailable. Private SharePoint, Teams, publisher/LTI content and other providers need separate readers/authorization. No assignments, grades, posts, quiz attempts, or completion states are written. Browser navigation may update normal school access timestamps.
204
+
205
+ ## Local data and app integration
206
+
207
+ All runtime data lives outside this repository in `%LOCALAPPDATA%\dd2l`:
208
+
209
+ | File | Purpose |
210
+ | --- | --- |
211
+ | `session.dpapi` | Session cookies, XSRF material, API token and verified identity, encrypted with Windows DPAPI for the current Windows user. |
212
+ | `inventory.sqlite` | Private source payloads, extracted text/small files, traversal cursors, per-resource coverage, successful retrieval times and current outcomes. This database contains personal coursework and grades and is **not encrypted**. |
213
+ | `summary.json` | Latest structural audit summary: counts, field names and coverage without course names, source IDs, grades or document bodies. Review before sharing. |
214
+ | `syllabus-summary.json` | Latest syllabus-only command summary with readability, formats, coverage and unresolved counts. |
215
+ | `worker.lock` | Prevents competing login/audit/sync processes; a dead process's lock is recovered on the next run. |
216
+
217
+ `npm start -- logout` removes dd2l's saved session and local inventory. It does not sign out of other school applications. This is ordinary local deletion, not guaranteed forensic erasure or deletion of any copies you made.
218
+
219
+ The importable core is `BrightspaceClient` in `src/client.mjs`, `Session` in `src/session.mjs`, `Connector`/`sync` in `src/connector.mjs`, and `Store` in `src/store.mjs`. `Connector.syllabus({ course })` uses the same retrieval logic as `audit` without changing the selected sync course. The CLI uses that same core. SQLite's `resources` table separates the latest observation from the last successful `data`, `last_success`, and `last_source`. Failed or partial reads preserve that data with its original source/window; a confirmed denial invalidates the affected cached resource. A complete enrollment read also invalidates cached courses no longer accessible. Data from different accounts cannot mix; run logout before switching accounts.
220
+
221
+ Syllabus content lives in individual `syllabus_source` resource rows; the `syllabus` row is a catalog of source keys and current outcomes, not a duplicate of every document's text. A failed fetch or extraction retains the source's prior successful data as stale. Explicit access restrictions invalidate that source's cached data, and a documented missing overview clears the overview/attachment cache. Catalog readability means some source text was retrieved, while `textCoverageComplete` also accounts for unsupported formats, restrictions, read limits and unexpanded links. Neither field establishes that every possible syllabus location was discovered.
222
+
223
+ An audit can complete successfully as a command while reporting `complete: false`: resource-level denials, unavailable routes, or partial collections remain visible. A successful collection read establishes coverage of that endpoint, not of every activity the website or an external tool may contain.
224
+
225
+ Persistent semantic indexing, additional model providers, private course-content provider integrations and unattended Windows startup remain later milestones.
226
+
227
+ Do not commit credentials, session cookies, access tokens, downloaded coursework, grades, or classmates' personal information. Discovery evidence should contain access outcomes and field names rather than private payloads.
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
- {
2
- "name": "@ivanbard/dd2l",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "@ivanbard/dd2l",
3
+ "version": "0.1.1",
4
+ "description": "A local, read-only student companion for Queen's onQ: sync coursework, search sources, and optionally connect ChatGPT.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ivanbard/dd2l.git"
9
+ },
10
+ "homepage": "https://github.com/ivanbard/dd2l#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/ivanbard/dd2l/issues"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "registry": "https://registry.npmjs.org/"
17
+ },
18
+ "type": "module",
19
+ "bin": {
20
+ "dd2l": "bin/dd2l.mjs"
21
+ },
22
+ "files": [
23
+ "bin/",
24
+ "src/",
25
+ "ui/",
26
+ "docs/README.md",
27
+ "THIRD_PARTY_NOTICES.md"
28
+ ],
29
+ "engines": {
30
+ "node": ">=24"
31
+ },
32
+ "scripts": {
33
+ "start": "node src/cli.mjs",
34
+ "setup": "node src/cli.mjs setup",
35
+ "ui": "node src/ui-server.mjs",
36
+ "ui:check": "node scripts/check-ui.mjs && node scripts/check-canvas.mjs",
37
+ "test": "node --test",
38
+ "package:check": "node scripts/check-package.mjs",
39
+ "release:check": "npm test && npm run ui:check && npm run package:check",
40
+ "prepublishOnly": "npm run release:check",
41
+ "login": "node src/cli.mjs login",
42
+ "audit": "node src/cli.mjs audit",
43
+ "sync": "node src/cli.mjs sync",
44
+ "status": "node src/cli.mjs status",
45
+ "soak": "node scripts/soak-sync.mjs"
46
+ },
47
+ "dependencies": {
48
+ "@clack/prompts": "^1.8.1",
49
+ "@napi-rs/canvas": "^1.0.13",
50
+ "fflate": "^0.8.3",
51
+ "htmlparser2": "12.0.0",
52
+ "jose": "^6.2.15",
53
+ "playwright-core": "1.58.2",
54
+ "unpdf": "2.0.0"
55
+ }
56
+ }
@@ -0,0 +1,58 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { createUiServer } from './ui-server.mjs';
3
+ import { sync } from './connector.mjs';
4
+ import { log as terminalLog, spinner } from '@clack/prompts';
5
+
6
+ function openBrowser(url) {
7
+ return new Promise((resolve, reject) => {
8
+ execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command',
9
+ `Start-Process -FilePath '${url}' -WindowStyle Hidden`], { windowsHide: true, timeout: 15_000 },
10
+ error => error ? reject(error) : resolve());
11
+ });
12
+ }
13
+
14
+ export async function runApp(connector, { onUpdate, server,
15
+ open = openBrowser, signals = process, port = 4173, log = terminalLog.info } = {}) {
16
+ const controller = new AbortController();
17
+ server ??= createUiServer({ connector, shutdownSignal: controller.signal });
18
+ const progress = process.stdout.isTTY ? spinner({ signal: controller.signal }) : null;
19
+ let firstPass = true;
20
+ const stop = () => controller.abort();
21
+ signals.on('SIGINT', stop);
22
+ signals.on('SIGTERM', stop);
23
+ try {
24
+ await new Promise((resolve, reject) => {
25
+ server.once('error', reject);
26
+ server.listen(port, '127.0.0.1', resolve);
27
+ });
28
+ const url = `http://127.0.0.1:${server.address().port}`;
29
+ log(`Your workspace → ${url}\nKeep this terminal open; Ctrl+C stops the app and sync.`);
30
+ try { await open(url); }
31
+ catch { log(`Could not open your browser. Open ${url} manually.`); }
32
+ log('Collecting up to 5 courses per pass, every 15 minutes.\nThe app may be empty until the first batch is ready.');
33
+ progress?.start('Collecting your first batch of coursework');
34
+ await sync(connector, { all: true }, { signal: controller.signal, onUpdate: async summary => {
35
+ await onUpdate?.(summary);
36
+ const message = summary.state === 'retry_wait'
37
+ ? 'Collection delayed · will retry automatically'
38
+ : 'Coursework saved · reopen Courses or search to see updates';
39
+ if (progress && firstPass) {
40
+ if (summary.state === 'retry_wait') { progress.clear(); terminalLog.warn(message); }
41
+ else progress.stop(message);
42
+ }
43
+ else log(message);
44
+ if (firstPass && summary.state !== 'retry_wait') {
45
+ log('Local search is ready. Connect ChatGPT in Settings for AI answers.');
46
+ }
47
+ firstPass = false;
48
+ } });
49
+ } catch (error) {
50
+ progress?.error('Collection stopped');
51
+ throw error;
52
+ } finally {
53
+ progress?.clear();
54
+ signals.off('SIGINT', stop);
55
+ signals.off('SIGTERM', stop);
56
+ await new Promise(resolve => { server.close(resolve); server.closeAllConnections(); });
57
+ }
58
+ }