studyctl 2.0.0__py3-none-any.whl

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 (58) hide show
  1. studyctl/__init__.py +3 -0
  2. studyctl/calendar.py +140 -0
  3. studyctl/cli/__init__.py +56 -0
  4. studyctl/cli/_config.py +128 -0
  5. studyctl/cli/_content.py +462 -0
  6. studyctl/cli/_lazy.py +35 -0
  7. studyctl/cli/_review.py +491 -0
  8. studyctl/cli/_schedule.py +125 -0
  9. studyctl/cli/_setup.py +164 -0
  10. studyctl/cli/_shared.py +83 -0
  11. studyctl/cli/_state.py +69 -0
  12. studyctl/cli/_sync.py +156 -0
  13. studyctl/cli/_web.py +228 -0
  14. studyctl/content/__init__.py +5 -0
  15. studyctl/content/markdown_converter.py +271 -0
  16. studyctl/content/models.py +31 -0
  17. studyctl/content/notebooklm_client.py +434 -0
  18. studyctl/content/splitter.py +159 -0
  19. studyctl/content/storage.py +105 -0
  20. studyctl/content/syllabus.py +416 -0
  21. studyctl/history.py +982 -0
  22. studyctl/maintenance.py +69 -0
  23. studyctl/mcp/__init__.py +1 -0
  24. studyctl/mcp/server.py +58 -0
  25. studyctl/mcp/tools.py +234 -0
  26. studyctl/pdf.py +89 -0
  27. studyctl/review_db.py +277 -0
  28. studyctl/review_loader.py +375 -0
  29. studyctl/scheduler.py +242 -0
  30. studyctl/services/__init__.py +6 -0
  31. studyctl/services/content.py +39 -0
  32. studyctl/services/review.py +127 -0
  33. studyctl/settings.py +367 -0
  34. studyctl/shared.py +425 -0
  35. studyctl/state.py +120 -0
  36. studyctl/sync.py +229 -0
  37. studyctl/tui/__main__.py +33 -0
  38. studyctl/tui/app.py +395 -0
  39. studyctl/tui/study_cards.py +396 -0
  40. studyctl/web/__init__.py +1 -0
  41. studyctl/web/app.py +68 -0
  42. studyctl/web/routes/__init__.py +1 -0
  43. studyctl/web/routes/artefacts.py +57 -0
  44. studyctl/web/routes/cards.py +86 -0
  45. studyctl/web/routes/courses.py +91 -0
  46. studyctl/web/routes/history.py +69 -0
  47. studyctl/web/server.py +260 -0
  48. studyctl/web/static/app.js +853 -0
  49. studyctl/web/static/icon-192.svg +4 -0
  50. studyctl/web/static/icon-512.svg +4 -0
  51. studyctl/web/static/index.html +50 -0
  52. studyctl/web/static/manifest.json +21 -0
  53. studyctl/web/static/style.css +657 -0
  54. studyctl/web/static/sw.js +14 -0
  55. studyctl-2.0.0.dist-info/METADATA +49 -0
  56. studyctl-2.0.0.dist-info/RECORD +58 -0
  57. studyctl-2.0.0.dist-info/WHEEL +4 -0
  58. studyctl-2.0.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,657 @@
1
+ @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap");
2
+ @import url("https://cdn.jsdelivr.net/npm/@fontsource/opendyslexic@5/400.css");
3
+
4
+ :root {
5
+ --bg: #1a1b26;
6
+ --bg-card: #24283b;
7
+ --bg-hover: #292e42;
8
+ --text: #c0caf5;
9
+ --text-muted: #565f89;
10
+ --accent: #7aa2f7;
11
+ --green: #9ece6a;
12
+ --red: #f7768e;
13
+ --yellow: #e0af68;
14
+ --orange: #ff9e64;
15
+ --border: #3b4261;
16
+ --radius: 16px;
17
+ --font: "Inter", system-ui, sans-serif;
18
+ --font-dyslexic: "OpenDyslexic", "Inter", system-ui, sans-serif;
19
+ }
20
+
21
+ /* Light theme */
22
+ body.light {
23
+ --bg: #f0f0f5;
24
+ --bg-card: #ffffff;
25
+ --bg-hover: #e8e8f0;
26
+ --text: #1a1b26;
27
+ --text-muted: #6b7294;
28
+ --accent: #4a6cf7;
29
+ --green: #2d8a4e;
30
+ --red: #d93a5b;
31
+ --yellow: #b88a2a;
32
+ --orange: #d97033;
33
+ --border: #d0d4e4;
34
+ }
35
+
36
+ * { box-sizing: border-box; margin: 0; padding: 0; }
37
+
38
+ body {
39
+ font-family: var(--font);
40
+ background: var(--bg);
41
+ color: var(--text);
42
+ min-height: 100dvh;
43
+ display: flex;
44
+ flex-direction: column;
45
+ -webkit-font-smoothing: antialiased;
46
+ }
47
+
48
+ body.dyslexic {
49
+ font-family: var(--font-dyslexic);
50
+ letter-spacing: 0.05em;
51
+ word-spacing: 0.1em;
52
+ line-height: 1.8;
53
+ }
54
+
55
+ /* --- Header --- */
56
+ header {
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: space-between;
60
+ padding: 16px 24px;
61
+ border-bottom: 1px solid var(--border);
62
+ flex-shrink: 0;
63
+ }
64
+
65
+ header h1 {
66
+ font-size: 1.3rem;
67
+ font-weight: 700;
68
+ color: var(--accent);
69
+ }
70
+
71
+ .header-controls {
72
+ display: flex;
73
+ gap: 8px;
74
+ }
75
+
76
+ .toggle-btn {
77
+ background: var(--bg-card);
78
+ border: 1px solid var(--border);
79
+ color: var(--text-muted);
80
+ padding: 6px 12px;
81
+ border-radius: 8px;
82
+ cursor: pointer;
83
+ font-size: 0.85rem;
84
+ transition: all 0.2s;
85
+ }
86
+
87
+ .toggle-btn:hover { border-color: var(--accent); color: var(--text); }
88
+ .toggle-btn.active { border-color: var(--accent); color: var(--accent); background: var(--bg-hover); }
89
+ .toggle-btn svg { vertical-align: middle; }
90
+
91
+ .voice-select {
92
+ background: var(--bg-card);
93
+ border: 1px solid var(--border);
94
+ border-radius: 8px;
95
+ color: var(--text-dim);
96
+ font-size: 12px;
97
+ padding: 4px 6px;
98
+ max-width: 140px;
99
+ cursor: pointer;
100
+ }
101
+ .voice-select:focus { border-color: var(--accent); outline: none; }
102
+
103
+ /* --- Nav bar (in study view) --- */
104
+ .nav-bar {
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: space-between;
108
+ margin-bottom: 12px;
109
+ }
110
+
111
+ .nav-course {
112
+ font-size: 0.9rem;
113
+ color: var(--text-muted);
114
+ font-weight: 600;
115
+ }
116
+
117
+ .nav-btn {
118
+ background: var(--bg-card);
119
+ border: 1px solid var(--border);
120
+ color: var(--text-muted);
121
+ width: 36px;
122
+ height: 36px;
123
+ border-radius: 8px;
124
+ cursor: pointer;
125
+ display: flex;
126
+ align-items: center;
127
+ justify-content: center;
128
+ transition: all 0.2s;
129
+ }
130
+
131
+ .nav-btn:hover { border-color: var(--accent); color: var(--text); }
132
+
133
+ /* --- Main content --- */
134
+ main {
135
+ flex: 1;
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ padding: 24px;
140
+ }
141
+
142
+ /* --- Course picker --- */
143
+ .courses {
144
+ display: grid;
145
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
146
+ gap: 16px;
147
+ width: 100%;
148
+ max-width: 800px;
149
+ }
150
+
151
+ .course-card {
152
+ background: var(--bg-card);
153
+ border: 1px solid var(--border);
154
+ border-radius: var(--radius);
155
+ padding: 24px;
156
+ cursor: pointer;
157
+ transition: all 0.2s;
158
+ }
159
+
160
+ .course-card:hover {
161
+ border-color: var(--accent);
162
+ transform: translateY(-2px);
163
+ box-shadow: 0 8px 24px rgba(0,0,0,0.3);
164
+ }
165
+
166
+ .course-card h2 { font-size: 1.2rem; margin-bottom: 8px; }
167
+ .course-card .counts { color: var(--text-muted); font-size: 0.9rem; }
168
+ .course-card .counts span { margin-right: 16px; }
169
+
170
+ .mode-buttons {
171
+ display: flex;
172
+ gap: 8px;
173
+ margin-top: 16px;
174
+ }
175
+
176
+ .mode-btn {
177
+ flex: 1;
178
+ padding: 10px;
179
+ border: 1px solid var(--border);
180
+ border-radius: 8px;
181
+ background: var(--bg);
182
+ color: var(--text);
183
+ cursor: pointer;
184
+ font-size: 0.9rem;
185
+ font-family: inherit;
186
+ transition: all 0.2s;
187
+ }
188
+
189
+ .mode-btn:hover { border-color: var(--accent); }
190
+ .mode-btn.flashcard:hover { border-color: var(--accent); background: rgba(122,162,247,0.1); }
191
+ .mode-btn.quiz:hover { border-color: var(--yellow); background: rgba(224,175,104,0.1); }
192
+
193
+ /* --- Study card --- */
194
+ .study-view {
195
+ width: 100%;
196
+ max-width: 700px;
197
+ display: flex;
198
+ flex-direction: column;
199
+ gap: 20px;
200
+ }
201
+
202
+ .progress-bar {
203
+ display: flex;
204
+ align-items: center;
205
+ gap: 12px;
206
+ color: var(--text-muted);
207
+ font-size: 0.9rem;
208
+ }
209
+
210
+ .progress-track {
211
+ flex: 1;
212
+ height: 6px;
213
+ background: var(--bg-card);
214
+ border-radius: 3px;
215
+ overflow: hidden;
216
+ }
217
+
218
+ .progress-fill {
219
+ height: 100%;
220
+ background: var(--accent);
221
+ border-radius: 3px;
222
+ transition: width 0.3s ease;
223
+ }
224
+
225
+ .card {
226
+ background: var(--bg-card);
227
+ border: 2px solid var(--border);
228
+ border-radius: var(--radius);
229
+ padding: 32px;
230
+ min-height: 200px;
231
+ display: flex;
232
+ flex-direction: column;
233
+ justify-content: center;
234
+ cursor: pointer;
235
+ transition: all 0.3s;
236
+ position: relative;
237
+ overflow: hidden;
238
+ box-sizing: border-box;
239
+ }
240
+
241
+ .card:hover { border-color: var(--accent); }
242
+ .card.revealed { border-color: var(--green); }
243
+
244
+ .card-label {
245
+ font-size: 0.8rem;
246
+ font-weight: 600;
247
+ text-transform: uppercase;
248
+ letter-spacing: 0.1em;
249
+ color: var(--accent);
250
+ margin-bottom: 12px;
251
+ }
252
+
253
+ .card-header {
254
+ display: flex;
255
+ align-items: center;
256
+ justify-content: space-between;
257
+ }
258
+
259
+ .speak-btn {
260
+ background: none;
261
+ border: 1px solid var(--border);
262
+ color: var(--text-muted);
263
+ width: 32px;
264
+ height: 32px;
265
+ border-radius: 8px;
266
+ cursor: pointer;
267
+ display: flex;
268
+ align-items: center;
269
+ justify-content: center;
270
+ transition: all 0.2s;
271
+ flex-shrink: 0;
272
+ }
273
+
274
+ .speak-btn:hover { border-color: var(--accent); color: var(--accent); }
275
+ .speak-btn:active { transform: scale(0.9); }
276
+
277
+ .card.revealed .card-label { color: var(--green); }
278
+
279
+ .card-content {
280
+ font-size: 1.15rem;
281
+ line-height: 1.7;
282
+ }
283
+
284
+ body.dyslexic .card-content {
285
+ font-size: 1.3rem;
286
+ line-height: 2;
287
+ }
288
+
289
+ .card-hint {
290
+ color: var(--text-muted);
291
+ font-size: 0.85rem;
292
+ margin-top: 16px;
293
+ text-align: center;
294
+ }
295
+
296
+ /* Quiz options */
297
+ .quiz-options {
298
+ display: flex;
299
+ flex-direction: column;
300
+ gap: 8px;
301
+ margin-top: 16px;
302
+ }
303
+
304
+ .quiz-option {
305
+ display: flex;
306
+ align-items: center;
307
+ gap: 12px;
308
+ padding: 12px 16px;
309
+ background: var(--bg);
310
+ border: 1px solid var(--border);
311
+ border-radius: 10px;
312
+ cursor: pointer;
313
+ transition: all 0.2s;
314
+ font-family: inherit;
315
+ font-size: 1rem;
316
+ color: var(--text);
317
+ text-align: left;
318
+ width: 100%;
319
+ }
320
+
321
+ .quiz-option:hover { border-color: var(--accent); background: var(--bg-hover); }
322
+ .quiz-option.correct { border-color: var(--green); background: rgba(158,206,106,0.1); }
323
+ .quiz-option.incorrect { border-color: var(--red); background: rgba(247,118,142,0.1); }
324
+ .quiz-option.was-correct { border-color: var(--green); opacity: 0.7; }
325
+
326
+ .option-letter {
327
+ width: 28px;
328
+ height: 28px;
329
+ border-radius: 50%;
330
+ background: var(--bg-card);
331
+ display: flex;
332
+ align-items: center;
333
+ justify-content: center;
334
+ font-weight: 600;
335
+ font-size: 0.85rem;
336
+ flex-shrink: 0;
337
+ }
338
+
339
+ .rationale {
340
+ margin-top: 12px;
341
+ padding: 12px;
342
+ background: rgba(158,206,106,0.08);
343
+ border-radius: 8px;
344
+ color: var(--text-muted);
345
+ font-size: 0.9rem;
346
+ line-height: 1.6;
347
+ }
348
+
349
+ /* --- Action buttons --- */
350
+ .actions {
351
+ display: flex;
352
+ gap: 12px;
353
+ }
354
+
355
+ .action-btn {
356
+ flex: 1;
357
+ padding: 14px;
358
+ border: none;
359
+ border-radius: 12px;
360
+ font-size: 1rem;
361
+ font-weight: 600;
362
+ cursor: pointer;
363
+ transition: all 0.2s;
364
+ font-family: inherit;
365
+ }
366
+
367
+ .action-btn:active { transform: scale(0.97); }
368
+
369
+ .btn-correct { background: var(--green); color: var(--bg); }
370
+ .btn-correct:hover { filter: brightness(1.1); }
371
+ .btn-incorrect { background: var(--red); color: white; }
372
+ .btn-incorrect:hover { filter: brightness(1.1); }
373
+ .btn-skip { background: var(--bg-card); color: var(--text-muted); border: 1px solid var(--border); }
374
+ .btn-skip:hover { border-color: var(--text-muted); }
375
+ .btn-flip { background: var(--accent); color: var(--bg); }
376
+ .btn-flip:hover { filter: brightness(1.1); }
377
+
378
+ /* --- Summary --- */
379
+ .summary {
380
+ text-align: center;
381
+ width: 100%;
382
+ max-width: 500px;
383
+ }
384
+
385
+ .summary h2 { font-size: 1.8rem; margin-bottom: 8px; }
386
+ .summary .grade { font-size: 1.2rem; margin-bottom: 24px; }
387
+ .grade.excellent { color: var(--green); }
388
+ .grade.good { color: var(--yellow); }
389
+ .grade.review { color: var(--red); }
390
+
391
+ .score-ring {
392
+ width: 140px;
393
+ height: 140px;
394
+ margin: 0 auto 24px;
395
+ position: relative;
396
+ }
397
+
398
+ .score-ring svg { transform: rotate(-90deg); }
399
+ .score-ring circle {
400
+ fill: none;
401
+ stroke-width: 8;
402
+ stroke-linecap: round;
403
+ }
404
+ .score-ring .track { stroke: var(--bg-card); }
405
+ .score-ring .fill { stroke: var(--green); transition: stroke-dashoffset 1s ease; }
406
+ .score-ring .fill.good { stroke: var(--yellow); }
407
+ .score-ring .fill.review { stroke: var(--red); }
408
+
409
+ .score-text {
410
+ position: absolute;
411
+ top: 50%;
412
+ left: 50%;
413
+ transform: translate(-50%, -50%);
414
+ font-size: 2rem;
415
+ font-weight: 700;
416
+ }
417
+
418
+ .summary-stats {
419
+ display: flex;
420
+ justify-content: center;
421
+ gap: 32px;
422
+ margin-bottom: 24px;
423
+ color: var(--text-muted);
424
+ }
425
+
426
+ .summary-actions {
427
+ display: flex;
428
+ gap: 12px;
429
+ justify-content: center;
430
+ }
431
+
432
+ .summary-btn {
433
+ padding: 12px 24px;
434
+ border-radius: 10px;
435
+ font-size: 1rem;
436
+ font-weight: 600;
437
+ cursor: pointer;
438
+ font-family: inherit;
439
+ border: 1px solid var(--border);
440
+ transition: all 0.2s;
441
+ }
442
+
443
+ .btn-retry { background: var(--orange); color: var(--bg); border: none; }
444
+ .btn-retry:hover { filter: brightness(1.1); }
445
+ .btn-restart { background: var(--accent); color: var(--bg); border: none; }
446
+ .btn-restart:hover { filter: brightness(1.1); }
447
+ .btn-back { background: var(--bg-card); color: var(--text); }
448
+ .btn-back:hover { border-color: var(--accent); }
449
+
450
+ /* --- Keyboard shortcuts --- */
451
+ .shortcuts {
452
+ position: fixed;
453
+ bottom: 16px;
454
+ left: 50%;
455
+ transform: translateX(-50%);
456
+ display: flex;
457
+ gap: 8px;
458
+ color: var(--text-muted);
459
+ font-size: 0.8rem;
460
+ }
461
+
462
+ .shortcuts kbd {
463
+ background: var(--bg-card);
464
+ border: 1px solid var(--border);
465
+ border-radius: 4px;
466
+ padding: 2px 6px;
467
+ font-family: inherit;
468
+ font-size: 0.75rem;
469
+ }
470
+
471
+ /* --- Due badge --- */
472
+ .due-badge {
473
+ display: inline-block;
474
+ background: var(--orange);
475
+ color: var(--bg);
476
+ font-size: 0.75rem;
477
+ font-weight: 700;
478
+ padding: 2px 8px;
479
+ border-radius: 10px;
480
+ margin-left: 8px;
481
+ }
482
+
483
+ .stats-row {
484
+ display: flex;
485
+ gap: 12px;
486
+ margin-top: 8px;
487
+ font-size: 0.8rem;
488
+ color: var(--text-muted);
489
+ }
490
+
491
+ /* --- Session config bar --- */
492
+ .config-bar {
493
+ display: flex;
494
+ gap: 12px;
495
+ align-items: center;
496
+ flex-wrap: wrap;
497
+ margin-bottom: 8px;
498
+ width: 100%;
499
+ box-sizing: border-box;
500
+ }
501
+
502
+ .config-bar select, .config-bar input {
503
+ background: var(--bg);
504
+ border: 1px solid var(--border);
505
+ color: var(--text);
506
+ padding: 6px 10px;
507
+ border-radius: 8px;
508
+ font-family: inherit;
509
+ font-size: 0.85rem;
510
+ min-width: 0;
511
+ }
512
+
513
+ .config-bar label {
514
+ color: var(--text-muted);
515
+ font-size: 0.85rem;
516
+ min-width: 0;
517
+ flex: 1 1 0;
518
+ }
519
+
520
+ .config-bar label select {
521
+ width: 100%;
522
+ text-overflow: ellipsis;
523
+ }
524
+
525
+ /* --- History section --- */
526
+ .history-section {
527
+ margin-top: 32px;
528
+ width: 100%;
529
+ max-width: 800px;
530
+ }
531
+
532
+ .history-section h3 {
533
+ font-size: 1rem;
534
+ color: var(--text-muted);
535
+ margin-bottom: 12px;
536
+ }
537
+
538
+ .history-list {
539
+ display: flex;
540
+ flex-direction: column;
541
+ gap: 6px;
542
+ }
543
+
544
+ .history-item {
545
+ display: flex;
546
+ justify-content: space-between;
547
+ align-items: center;
548
+ padding: 10px 16px;
549
+ background: var(--bg-card);
550
+ border-radius: 8px;
551
+ font-size: 0.85rem;
552
+ }
553
+
554
+ .history-item .hi-course { font-weight: 600; }
555
+ .history-item .hi-score { color: var(--green); }
556
+ .history-item .hi-date { color: var(--text-muted); }
557
+
558
+ /* --- Pomodoro timer --- */
559
+ .pomodoro {
560
+ position: fixed;
561
+ top: 60px;
562
+ right: 16px;
563
+ background: var(--bg-card);
564
+ border: 1px solid var(--border);
565
+ border-radius: 12px;
566
+ padding: 12px 16px;
567
+ display: flex;
568
+ align-items: center;
569
+ gap: 10px;
570
+ z-index: 100;
571
+ box-shadow: 0 4px 16px rgba(0,0,0,0.3);
572
+ transition: all 0.3s;
573
+ }
574
+
575
+ .pomodoro.hidden { display: none; }
576
+ .pomodoro.break { border-color: var(--green); }
577
+
578
+ .pomo-time {
579
+ font-size: 1.4rem;
580
+ font-weight: 700;
581
+ font-variant-numeric: tabular-nums;
582
+ min-width: 60px;
583
+ text-align: center;
584
+ }
585
+
586
+ .pomo-label {
587
+ font-size: 0.75rem;
588
+ color: var(--text-muted);
589
+ text-transform: uppercase;
590
+ letter-spacing: 0.05em;
591
+ }
592
+
593
+ .pomo-btn {
594
+ background: none;
595
+ border: 1px solid var(--border);
596
+ color: var(--text-muted);
597
+ width: 28px;
598
+ height: 28px;
599
+ border-radius: 6px;
600
+ cursor: pointer;
601
+ display: flex;
602
+ align-items: center;
603
+ justify-content: center;
604
+ font-size: 0.9rem;
605
+ transition: all 0.2s;
606
+ }
607
+
608
+ .pomo-btn:hover { border-color: var(--accent); color: var(--text); }
609
+
610
+ .pomodoro .pomo-ring {
611
+ width: 44px;
612
+ height: 44px;
613
+ flex-shrink: 0;
614
+ }
615
+
616
+ .pomo-ring svg { transform: rotate(-90deg); }
617
+ .pomo-ring circle { fill: none; stroke-width: 3; stroke-linecap: round; }
618
+ .pomo-ring .track { stroke: var(--bg); }
619
+ .pomo-ring .fill { stroke: var(--accent); transition: stroke-dashoffset 1s linear; }
620
+ .pomodoro.break .pomo-ring .fill { stroke: var(--green); }
621
+
622
+ /* --- Heatmap --- */
623
+ .heatmap-section {
624
+ margin-top: 24px;
625
+ width: 100%;
626
+ max-width: 800px;
627
+ }
628
+
629
+ .heatmap {
630
+ display: flex;
631
+ gap: 3px;
632
+ flex-wrap: wrap;
633
+ justify-content: flex-end;
634
+ }
635
+
636
+ .heatmap-day {
637
+ width: 14px;
638
+ height: 14px;
639
+ border-radius: 3px;
640
+ background: var(--bg-card);
641
+ }
642
+
643
+ .heatmap-day.l1 { background: rgba(122,162,247,0.25); }
644
+ .heatmap-day.l2 { background: rgba(122,162,247,0.5); }
645
+ .heatmap-day.l3 { background: rgba(122,162,247,0.75); }
646
+ .heatmap-day.l4 { background: var(--accent); }
647
+
648
+ /* --- Responsive --- */
649
+ @media (max-width: 600px) {
650
+ header { padding: 12px 16px; }
651
+ main { padding: 16px; }
652
+ .card { padding: 24px; min-height: 160px; }
653
+ .actions { flex-direction: column; }
654
+ .summary-stats { gap: 16px; }
655
+ .summary-actions { flex-direction: column; }
656
+ .shortcuts { display: none; }
657
+ }
@@ -0,0 +1,14 @@
1
+ const CACHE = "studyctl-v1";
2
+ const ASSETS = ["/", "/style.css", "/app.js", "/manifest.json"];
3
+
4
+ self.addEventListener("install", (e) => {
5
+ e.waitUntil(caches.open(CACHE).then((c) => c.addAll(ASSETS)));
6
+ self.skipWaiting();
7
+ });
8
+
9
+ self.addEventListener("fetch", (e) => {
10
+ if (e.request.url.includes("/api/")) return;
11
+ e.respondWith(
12
+ caches.match(e.request).then((r) => r || fetch(e.request))
13
+ );
14
+ });
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: studyctl
3
+ Version: 2.0.0
4
+ Summary: AuDHD-aware study tool with AI Socratic mentoring, spaced repetition, and content pipeline
5
+ Project-URL: Homepage, https://github.com/NetDevAutomate/Socratic-Study-Mentor
6
+ Project-URL: Repository, https://github.com/NetDevAutomate/Socratic-Study-Mentor
7
+ Project-URL: Issues, https://github.com/NetDevAutomate/Socratic-Study-Mentor/issues
8
+ Project-URL: Documentation, https://github.com/NetDevAutomate/Socratic-Study-Mentor/tree/main/docs
9
+ Author: Andy Taylor
10
+ License-Expression: MIT
11
+ Keywords: adhd,audhd,flashcards,notebooklm,socratic,spaced-repetition,study
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Intended Audience :: Education
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Education
19
+ Requires-Python: >=3.12
20
+ Requires-Dist: click>=8.1
21
+ Requires-Dist: pyyaml>=6.0
22
+ Requires-Dist: rich>=13.0
23
+ Provides-Extra: all
24
+ Requires-Dist: fastapi>=0.115; extra == 'all'
25
+ Requires-Dist: httpx; extra == 'all'
26
+ Requires-Dist: mcp[cli]>=1.0.0; extra == 'all'
27
+ Requires-Dist: notebooklm-py>=0.3.4; extra == 'all'
28
+ Requires-Dist: pymupdf>=1.25; extra == 'all'
29
+ Requires-Dist: textual>=0.80; extra == 'all'
30
+ Requires-Dist: uvicorn[standard]>=0.34; extra == 'all'
31
+ Provides-Extra: content
32
+ Requires-Dist: httpx; extra == 'content'
33
+ Requires-Dist: pymupdf>=1.25; extra == 'content'
34
+ Provides-Extra: mcp
35
+ Requires-Dist: mcp[cli]>=1.0.0; extra == 'mcp'
36
+ Provides-Extra: notebooklm
37
+ Requires-Dist: notebooklm-py>=0.3.4; extra == 'notebooklm'
38
+ Provides-Extra: tui
39
+ Requires-Dist: textual>=0.80; extra == 'tui'
40
+ Provides-Extra: web
41
+ Requires-Dist: fastapi>=0.115; extra == 'web'
42
+ Requires-Dist: uvicorn[standard]>=0.34; extra == 'web'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # studyctl
46
+
47
+ Study pipeline management CLI — sync Obsidian notes, spaced repetition scheduling, and cross-machine state sync.
48
+
49
+ Part of [socratic-study-mentor](https://github.com/NetDevAutomate/Socratic-Study-Mentor).