@bis-code/study-dash 0.2.0

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 (89) hide show
  1. package/.claude-plugin/marketplace.json +18 -0
  2. package/.claude-plugin/plugin.json +19 -0
  3. package/.mcp.json +8 -0
  4. package/LICENSE +21 -0
  5. package/commands/dashboard.md +8 -0
  6. package/commands/import.md +12 -0
  7. package/commands/learn.md +13 -0
  8. package/hooks/hooks.json +27 -0
  9. package/package.json +36 -0
  10. package/rules/tutor-mode.md +38 -0
  11. package/server/dist/bundle.mjs +1240 -0
  12. package/server/dist/dashboard/api.d.ts +16 -0
  13. package/server/dist/dashboard/api.js +150 -0
  14. package/server/dist/dashboard/api.js.map +1 -0
  15. package/server/dist/dashboard/server.d.ts +21 -0
  16. package/server/dist/dashboard/server.js +171 -0
  17. package/server/dist/dashboard/server.js.map +1 -0
  18. package/server/dist/index.d.ts +2 -0
  19. package/server/dist/index.js +40 -0
  20. package/server/dist/index.js.map +1 -0
  21. package/server/dist/services/curriculum.d.ts +25 -0
  22. package/server/dist/services/curriculum.js +110 -0
  23. package/server/dist/services/curriculum.js.map +1 -0
  24. package/server/dist/services/exercises.d.ts +35 -0
  25. package/server/dist/services/exercises.js +215 -0
  26. package/server/dist/services/exercises.js.map +1 -0
  27. package/server/dist/services/qa.d.ts +15 -0
  28. package/server/dist/services/qa.js +30 -0
  29. package/server/dist/services/qa.js.map +1 -0
  30. package/server/dist/services/viz.d.ts +8 -0
  31. package/server/dist/services/viz.js +21 -0
  32. package/server/dist/services/viz.js.map +1 -0
  33. package/server/dist/storage/db.d.ts +11 -0
  34. package/server/dist/storage/db.js +51 -0
  35. package/server/dist/storage/db.js.map +1 -0
  36. package/server/dist/storage/files.d.ts +10 -0
  37. package/server/dist/storage/files.js +34 -0
  38. package/server/dist/storage/files.js.map +1 -0
  39. package/server/dist/storage/schema.d.ts +3 -0
  40. package/server/dist/storage/schema.js +126 -0
  41. package/server/dist/storage/schema.js.map +1 -0
  42. package/server/dist/tools/curriculum.d.ts +4 -0
  43. package/server/dist/tools/curriculum.js +137 -0
  44. package/server/dist/tools/curriculum.js.map +1 -0
  45. package/server/dist/tools/exercises.d.ts +4 -0
  46. package/server/dist/tools/exercises.js +76 -0
  47. package/server/dist/tools/exercises.js.map +1 -0
  48. package/server/dist/tools/qa.d.ts +4 -0
  49. package/server/dist/tools/qa.js +56 -0
  50. package/server/dist/tools/qa.js.map +1 -0
  51. package/server/dist/tools/viz.d.ts +4 -0
  52. package/server/dist/tools/viz.js +54 -0
  53. package/server/dist/tools/viz.js.map +1 -0
  54. package/server/dist/types.d.ts +103 -0
  55. package/server/dist/types.js +2 -0
  56. package/server/dist/types.js.map +1 -0
  57. package/server/node_modules/better-sqlite3/LICENSE +21 -0
  58. package/server/node_modules/better-sqlite3/README.md +99 -0
  59. package/server/node_modules/better-sqlite3/binding.gyp +38 -0
  60. package/server/node_modules/better-sqlite3/build/Release/better_sqlite3.node +0 -0
  61. package/server/node_modules/better-sqlite3/deps/common.gypi +68 -0
  62. package/server/node_modules/better-sqlite3/deps/copy.js +31 -0
  63. package/server/node_modules/better-sqlite3/deps/defines.gypi +41 -0
  64. package/server/node_modules/better-sqlite3/deps/download.sh +122 -0
  65. package/server/node_modules/better-sqlite3/deps/patches/1208.patch +15 -0
  66. package/server/node_modules/better-sqlite3/deps/sqlite3/sqlite3.c +261480 -0
  67. package/server/node_modules/better-sqlite3/deps/sqlite3/sqlite3.h +13715 -0
  68. package/server/node_modules/better-sqlite3/deps/sqlite3/sqlite3ext.h +719 -0
  69. package/server/node_modules/better-sqlite3/deps/sqlite3.gyp +80 -0
  70. package/server/node_modules/better-sqlite3/deps/test_extension.c +21 -0
  71. package/server/node_modules/better-sqlite3/lib/database.js +90 -0
  72. package/server/node_modules/better-sqlite3/lib/index.js +3 -0
  73. package/server/node_modules/better-sqlite3/lib/methods/aggregate.js +43 -0
  74. package/server/node_modules/better-sqlite3/lib/methods/backup.js +67 -0
  75. package/server/node_modules/better-sqlite3/lib/methods/function.js +31 -0
  76. package/server/node_modules/better-sqlite3/lib/methods/inspect.js +7 -0
  77. package/server/node_modules/better-sqlite3/lib/methods/pragma.js +12 -0
  78. package/server/node_modules/better-sqlite3/lib/methods/serialize.js +16 -0
  79. package/server/node_modules/better-sqlite3/lib/methods/table.js +189 -0
  80. package/server/node_modules/better-sqlite3/lib/methods/transaction.js +78 -0
  81. package/server/node_modules/better-sqlite3/lib/methods/wrappers.js +54 -0
  82. package/server/node_modules/better-sqlite3/lib/sqlite-error.js +20 -0
  83. package/server/node_modules/better-sqlite3/lib/util.js +12 -0
  84. package/server/node_modules/better-sqlite3/package.json +54 -0
  85. package/server/node_modules/better-sqlite3/src/better_sqlite3.cpp +2186 -0
  86. package/server/node_modules/better-sqlite3/src/better_sqlite3.hpp +1036 -0
  87. package/server/package.json +31 -0
  88. package/skills/import/SKILL.md +19 -0
  89. package/skills/learn/SKILL.md +17 -0
@@ -0,0 +1,16 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { CurriculumService } from '../services/curriculum.js';
3
+ import type { QAService } from '../services/qa.js';
4
+ import type { VizService } from '../services/viz.js';
5
+ import type { ExerciseService } from '../services/exercises.js';
6
+ export declare function writeJSON(res: ServerResponse, data: unknown, status?: number): void;
7
+ export declare function parseBody(req: IncomingMessage): Promise<unknown>;
8
+ export declare function extractId(url: string, prefix: string): number | null;
9
+ export declare function handleSubjects(curriculumSvc: CurriculumService): (_req: IncomingMessage, res: ServerResponse) => void;
10
+ export declare function handlePhases(curriculumSvc: CurriculumService): (req: IncomingMessage, res: ServerResponse) => void;
11
+ export declare function handleTopic(curriculumSvc: CurriculumService, qaSvc: QAService): (req: IncomingMessage, res: ServerResponse) => void;
12
+ export declare function handleTopicViz(vizSvc: VizService): (req: IncomingMessage, res: ServerResponse) => void;
13
+ export declare function handleTopicExercises(exerciseSvc: ExerciseService): (req: IncomingMessage, res: ServerResponse) => void;
14
+ export declare function handleRunTests(exerciseSvc: ExerciseService): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
15
+ export declare function handleSubmitQuiz(exerciseSvc: ExerciseService): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
16
+ export declare function handleSearch(qaSvc: QAService): (req: IncomingMessage, res: ServerResponse) => void;
@@ -0,0 +1,150 @@
1
+ // ── Helpers ────────────────────────────────────────────────────────────────
2
+ export function writeJSON(res, data, status = 200) {
3
+ res.writeHead(status, { 'Content-Type': 'application/json' });
4
+ res.end(JSON.stringify(data));
5
+ }
6
+ function writeError(res, status, message) {
7
+ writeJSON(res, { error: message }, status);
8
+ }
9
+ export function parseBody(req) {
10
+ return new Promise((resolve, reject) => {
11
+ const chunks = [];
12
+ req.on('data', (chunk) => chunks.push(chunk));
13
+ req.on('end', () => {
14
+ try {
15
+ resolve(JSON.parse(Buffer.concat(chunks).toString()));
16
+ }
17
+ catch (err) {
18
+ reject(new Error('Invalid JSON body'));
19
+ }
20
+ });
21
+ req.on('error', reject);
22
+ });
23
+ }
24
+ export function extractId(url, prefix) {
25
+ // e.g. prefix = "/api/topics/" -> extract "42" from "/api/topics/42" or "/api/topics/42/viz"
26
+ if (!url.startsWith(prefix))
27
+ return null;
28
+ const rest = url.slice(prefix.length);
29
+ const segment = rest.split('/')[0];
30
+ const num = Number(segment);
31
+ return Number.isFinite(num) && num > 0 ? num : null;
32
+ }
33
+ // ── Route handlers ─────────────────────────────────────────────────────────
34
+ export function handleSubjects(curriculumSvc) {
35
+ return (_req, res) => {
36
+ const subjects = curriculumSvc.listSubjects();
37
+ const result = subjects.map((s) => ({
38
+ ...s,
39
+ progress: curriculumSvc.getProgress(s.id),
40
+ }));
41
+ writeJSON(res, result);
42
+ };
43
+ }
44
+ export function handlePhases(curriculumSvc) {
45
+ return (req, res) => {
46
+ const id = extractId(req.url ?? '', '/api/subjects/');
47
+ if (id === null) {
48
+ writeError(res, 400, 'Invalid subject ID');
49
+ return;
50
+ }
51
+ const phases = curriculumSvc.getCurriculum(id);
52
+ writeJSON(res, phases);
53
+ };
54
+ }
55
+ export function handleTopic(curriculumSvc, qaSvc) {
56
+ return (req, res) => {
57
+ const id = extractId(req.url ?? '', '/api/topics/');
58
+ if (id === null) {
59
+ writeError(res, 400, 'Invalid topic ID');
60
+ return;
61
+ }
62
+ const topic = curriculumSvc.getTopic(id);
63
+ if (!topic) {
64
+ writeError(res, 404, 'Topic not found');
65
+ return;
66
+ }
67
+ const entries = qaSvc.listEntries(id);
68
+ writeJSON(res, { ...topic, entries });
69
+ };
70
+ }
71
+ export function handleTopicViz(vizSvc) {
72
+ return (req, res) => {
73
+ const id = extractId(req.url ?? '', '/api/topics/');
74
+ if (id === null) {
75
+ writeError(res, 400, 'Invalid topic ID');
76
+ return;
77
+ }
78
+ const vizList = vizSvc.listForTopic(id);
79
+ writeJSON(res, vizList);
80
+ };
81
+ }
82
+ export function handleTopicExercises(exerciseSvc) {
83
+ return (req, res) => {
84
+ const id = extractId(req.url ?? '', '/api/topics/');
85
+ if (id === null) {
86
+ writeError(res, 400, 'Invalid topic ID');
87
+ return;
88
+ }
89
+ const exercises = exerciseSvc.listForTopic(id);
90
+ writeJSON(res, exercises);
91
+ };
92
+ }
93
+ export function handleRunTests(exerciseSvc) {
94
+ return async (req, res) => {
95
+ const id = extractId(req.url ?? '', '/api/exercises/');
96
+ if (id === null) {
97
+ writeError(res, 400, 'Invalid exercise ID');
98
+ return;
99
+ }
100
+ try {
101
+ const results = await exerciseSvc.runTests(id);
102
+ writeJSON(res, results);
103
+ }
104
+ catch (err) {
105
+ const msg = err instanceof Error ? err.message : String(err);
106
+ writeError(res, 500, msg);
107
+ }
108
+ };
109
+ }
110
+ export function handleSubmitQuiz(exerciseSvc) {
111
+ return async (req, res) => {
112
+ const id = extractId(req.url ?? '', '/api/exercises/');
113
+ if (id === null) {
114
+ writeError(res, 400, 'Invalid exercise ID');
115
+ return;
116
+ }
117
+ try {
118
+ const body = (await parseBody(req));
119
+ if (!Array.isArray(body?.answers)) {
120
+ writeError(res, 400, 'Request body must have an "answers" array');
121
+ return;
122
+ }
123
+ const result = exerciseSvc.submitQuiz(id, body.answers);
124
+ writeJSON(res, result);
125
+ }
126
+ catch (err) {
127
+ const msg = err instanceof Error ? err.message : String(err);
128
+ writeError(res, 500, msg);
129
+ }
130
+ };
131
+ }
132
+ export function handleSearch(qaSvc) {
133
+ return (req, res) => {
134
+ const url = new URL(req.url ?? '', 'http://localhost');
135
+ const query = url.searchParams.get('q') ?? '';
136
+ if (!query) {
137
+ writeJSON(res, []);
138
+ return;
139
+ }
140
+ try {
141
+ const results = qaSvc.search(query);
142
+ writeJSON(res, results);
143
+ }
144
+ catch (err) {
145
+ const msg = err instanceof Error ? err.message : String(err);
146
+ writeError(res, 500, msg);
147
+ }
148
+ };
149
+ }
150
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/dashboard/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { CurriculumService } from '../services/curriculum.js';
2
+ import type { QAService } from '../services/qa.js';
3
+ import type { VizService } from '../services/viz.js';
4
+ import type { ExerciseService } from '../services/exercises.js';
5
+ export declare class DashboardServer {
6
+ private curriculumSvc;
7
+ private qaSvc;
8
+ private vizSvc;
9
+ private exerciseSvc;
10
+ private port;
11
+ private sseClients;
12
+ private httpServer;
13
+ constructor(curriculumSvc: CurriculumService, qaSvc: QAService, vizSvc: VizService, exerciseSvc: ExerciseService, port: number);
14
+ start(): void;
15
+ stop(): void;
16
+ notify(): void;
17
+ private handleRequest;
18
+ private handleSSE;
19
+ private routeAPI;
20
+ private serveStatic;
21
+ }
@@ -0,0 +1,171 @@
1
+ import http from 'node:http';
2
+ import { readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, join } from 'node:path';
5
+ import { handleSubjects, handlePhases, handleTopic, handleTopicViz, handleTopicExercises, handleRunTests, handleSubmitQuiz, handleSearch, writeJSON, } from './api.js';
6
+ // ── Embedded static content ──
7
+ // At runtime (tsc output), read from disk.
8
+ // In bundle mode, esbuild --loader:.html=text replaces these with inlined strings.
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ const staticDir = join(__dirname, 'static');
12
+ function loadStatic(name) {
13
+ try {
14
+ return readFileSync(join(staticDir, name), 'utf-8');
15
+ }
16
+ catch {
17
+ return '';
18
+ }
19
+ }
20
+ const indexHtml = loadStatic('index.html');
21
+ const appJs = loadStatic('app.js');
22
+ const stylesCss = loadStatic('styles.css');
23
+ const STATIC_FILES = {
24
+ '/': { content: indexHtml, contentType: 'text/html; charset=utf-8' },
25
+ '/index.html': { content: indexHtml, contentType: 'text/html; charset=utf-8' },
26
+ '/app.js': { content: appJs, contentType: 'application/javascript; charset=utf-8' },
27
+ '/styles.css': { content: stylesCss, contentType: 'text/css; charset=utf-8' },
28
+ };
29
+ export class DashboardServer {
30
+ curriculumSvc;
31
+ qaSvc;
32
+ vizSvc;
33
+ exerciseSvc;
34
+ port;
35
+ sseClients = new Set();
36
+ httpServer = null;
37
+ constructor(curriculumSvc, qaSvc, vizSvc, exerciseSvc, port) {
38
+ this.curriculumSvc = curriculumSvc;
39
+ this.qaSvc = qaSvc;
40
+ this.vizSvc = vizSvc;
41
+ this.exerciseSvc = exerciseSvc;
42
+ this.port = port;
43
+ }
44
+ start() {
45
+ this.httpServer = http.createServer((req, res) => this.handleRequest(req, res));
46
+ this.httpServer.listen(this.port, '127.0.0.1', () => {
47
+ console.error(`Dashboard running at http://127.0.0.1:${this.port}`);
48
+ });
49
+ }
50
+ stop() {
51
+ if (this.httpServer) {
52
+ this.httpServer.close();
53
+ this.httpServer = null;
54
+ }
55
+ }
56
+ notify() {
57
+ const data = JSON.stringify({ type: 'update', ts: new Date().toISOString() });
58
+ const message = `data: ${data}\n\n`;
59
+ for (const client of this.sseClients) {
60
+ client.write(message);
61
+ }
62
+ }
63
+ // ── Request routing ────────────────────────────────────────────────────
64
+ handleRequest(req, res) {
65
+ const url = req.url ?? '/';
66
+ const method = req.method ?? 'GET';
67
+ // CSRF check for POST requests
68
+ if (method === 'POST') {
69
+ const origin = req.headers.origin ?? '';
70
+ if (origin && !origin.startsWith('http://localhost') && !origin.startsWith('http://127.0.0.1')) {
71
+ res.writeHead(403, { 'Content-Type': 'application/json' });
72
+ res.end(JSON.stringify({ error: 'Forbidden: invalid origin' }));
73
+ return;
74
+ }
75
+ }
76
+ // SSE endpoint
77
+ if (url === '/api/events' && method === 'GET') {
78
+ this.handleSSE(req, res);
79
+ return;
80
+ }
81
+ // API routes
82
+ if (url.startsWith('/api/')) {
83
+ this.routeAPI(method, url, req, res);
84
+ return;
85
+ }
86
+ // Static files
87
+ this.serveStatic(url, res);
88
+ }
89
+ // ── SSE ────────────────────────────────────────────────────────────────
90
+ handleSSE(_req, res) {
91
+ res.writeHead(200, {
92
+ 'Content-Type': 'text/event-stream',
93
+ 'Cache-Control': 'no-cache',
94
+ 'Connection': 'keep-alive',
95
+ 'Access-Control-Allow-Origin': '*',
96
+ });
97
+ // Send initial connected event
98
+ const connected = JSON.stringify({ type: 'connected', ts: new Date().toISOString() });
99
+ res.write(`data: ${connected}\n\n`);
100
+ this.sseClients.add(res);
101
+ res.on('close', () => {
102
+ this.sseClients.delete(res);
103
+ });
104
+ }
105
+ // ── API router ─────────────────────────────────────────────────────────
106
+ routeAPI(method, url, req, res) {
107
+ // Strip query string for pattern matching
108
+ const path = url.split('?')[0];
109
+ // GET /api/subjects
110
+ if (method === 'GET' && path === '/api/subjects') {
111
+ handleSubjects(this.curriculumSvc)(req, res);
112
+ return;
113
+ }
114
+ // GET /api/subjects/:id/phases
115
+ if (method === 'GET' && /^\/api\/subjects\/\d+\/phases$/.test(path)) {
116
+ handlePhases(this.curriculumSvc)(req, res);
117
+ return;
118
+ }
119
+ // GET /api/topics/:id/viz
120
+ if (method === 'GET' && /^\/api\/topics\/\d+\/viz$/.test(path)) {
121
+ handleTopicViz(this.vizSvc)(req, res);
122
+ return;
123
+ }
124
+ // GET /api/topics/:id/exercises
125
+ if (method === 'GET' && /^\/api\/topics\/\d+\/exercises$/.test(path)) {
126
+ handleTopicExercises(this.exerciseSvc)(req, res);
127
+ return;
128
+ }
129
+ // GET /api/topics/:id
130
+ if (method === 'GET' && /^\/api\/topics\/\d+$/.test(path)) {
131
+ handleTopic(this.curriculumSvc, this.qaSvc)(req, res);
132
+ return;
133
+ }
134
+ // POST /api/exercises/:id/run
135
+ if (method === 'POST' && /^\/api\/exercises\/\d+\/run$/.test(path)) {
136
+ handleRunTests(this.exerciseSvc)(req, res);
137
+ return;
138
+ }
139
+ // POST /api/exercises/:id/submit
140
+ if (method === 'POST' && /^\/api\/exercises\/\d+\/submit$/.test(path)) {
141
+ handleSubmitQuiz(this.exerciseSvc)(req, res);
142
+ return;
143
+ }
144
+ // GET /api/search?q=...
145
+ if (method === 'GET' && path === '/api/search') {
146
+ handleSearch(this.qaSvc)(req, res);
147
+ return;
148
+ }
149
+ // 404 for unknown API routes
150
+ writeJSON(res, { error: 'Not found' }, 404);
151
+ }
152
+ // ── Static file serving ────────────────────────────────────────────────
153
+ serveStatic(url, res) {
154
+ const file = STATIC_FILES[url];
155
+ if (file) {
156
+ res.writeHead(200, { 'Content-Type': file.contentType });
157
+ res.end(file.content);
158
+ return;
159
+ }
160
+ // Fallback: serve index.html for SPA routing
161
+ const index = STATIC_FILES['/'];
162
+ if (index) {
163
+ res.writeHead(200, { 'Content-Type': index.contentType });
164
+ res.end(index.content);
165
+ return;
166
+ }
167
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
168
+ res.end('Not found');
169
+ }
170
+ }
171
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAK1C,OAAO,EACL,cAAc,EACd,YAAY,EACZ,WAAW,EACX,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,SAAS,GACV,MAAM,UAAU,CAAC;AAElB,gCAAgC;AAChC,2CAA2C;AAC3C,mFAAmF;AAEnF,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AACtC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAE5C,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,SAAS,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;AAC3C,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;AACnC,MAAM,SAAS,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;AAE3C,MAAM,YAAY,GAA6D;IAC7E,GAAG,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,0BAA0B,EAAE;IACpE,aAAa,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,0BAA0B,EAAE;IAC9E,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,uCAAuC,EAAE;IACnF,aAAa,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,yBAAyB,EAAE;CAC9E,CAAC;AAEF,MAAM,OAAO,eAAe;IAKhB;IACA;IACA;IACA;IACA;IARF,UAAU,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC5C,UAAU,GAAuB,IAAI,CAAC;IAE9C,YACU,aAAgC,EAChC,KAAgB,EAChB,MAAkB,EAClB,WAA4B,EAC5B,IAAY;QAJZ,kBAAa,GAAb,aAAa,CAAmB;QAChC,UAAK,GAAL,KAAK,CAAW;QAChB,WAAM,GAAN,MAAM,CAAY;QAClB,gBAAW,GAAX,WAAW,CAAiB;QAC5B,SAAI,GAAJ,IAAI,CAAQ;IACnB,CAAC;IAEJ,KAAK;QACH,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;YAClD,OAAO,CAAC,KAAK,CAAC,yCAAyC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACtE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YACxB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzB,CAAC;IACH,CAAC;IAED,MAAM;QACJ,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC9E,MAAM,OAAO,GAAG,SAAS,IAAI,MAAM,CAAC;QACpC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACrC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,0EAA0E;IAElE,aAAa,CAAC,GAAyB,EAAE,GAAwB;QACvE,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC;QAC3B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;QAEnC,+BAA+B;QAC/B,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;YACxC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;gBAC/F,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,2BAA2B,EAAE,CAAC,CAAC,CAAC;gBAChE,OAAO;YACT,CAAC;QACH,CAAC;QAED,eAAe;QACf,IAAI,GAAG,KAAK,aAAa,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;YAC9C,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACzB,OAAO;QACT,CAAC;QAED,aAAa;QACb,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YACrC,OAAO;QACT,CAAC;QAED,eAAe;QACf,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,0EAA0E;IAElE,SAAS,CAAC,IAA0B,EAAE,GAAwB;QACpE,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;YACjB,cAAc,EAAE,mBAAmB;YACnC,eAAe,EAAE,UAAU;YAC3B,YAAY,EAAE,YAAY;YAC1B,6BAA6B,EAAE,GAAG;SACnC,CAAC,CAAC;QAEH,+BAA+B;QAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACtF,GAAG,CAAC,KAAK,CAAC,SAAS,SAAS,MAAM,CAAC,CAAC;QAEpC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACnB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,0EAA0E;IAElE,QAAQ,CAAC,MAAc,EAAE,GAAW,EAAE,GAAyB,EAAE,GAAwB;QAC/F,0CAA0C;QAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAE/B,oBAAoB;QACpB,IAAI,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,eAAe,EAAE,CAAC;YACjD,cAAc,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAC7C,OAAO;QACT,CAAC;QAED,+BAA+B;QAC/B,IAAI,MAAM,KAAK,KAAK,IAAI,gCAAgC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACpE,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,0BAA0B;QAC1B,IAAI,MAAM,KAAK,KAAK,IAAI,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/D,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACtC,OAAO;QACT,CAAC;QAED,gCAAgC;QAChC,IAAI,MAAM,KAAK,KAAK,IAAI,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACrE,oBAAoB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACjD,OAAO;QACT,CAAC;QAED,sBAAsB;QACtB,IAAI,MAAM,KAAK,KAAK,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1D,WAAW,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACtD,OAAO;QACT,CAAC;QAED,8BAA8B;QAC9B,IAAI,MAAM,KAAK,MAAM,IAAI,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACnE,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,iCAAiC;QACjC,IAAI,MAAM,KAAK,MAAM,IAAI,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACtE,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAC7C,OAAO;QACT,CAAC;QAED,wBAAwB;QACxB,IAAI,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;YAC/C,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;QAED,6BAA6B;QAC7B,SAAS,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED,0EAA0E;IAElE,WAAW,CAAC,GAAW,EAAE,GAAwB;QACvD,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,IAAI,EAAE,CAAC;YACT,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;YACzD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACtB,OAAO;QACT,CAAC;QAED,6CAA6C;QAC7C,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,KAAK,EAAE,CAAC;YACV,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;YAC1D,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACvB,OAAO;QACT,CAAC;QAED,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;QACrD,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACvB,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { Database } from './storage/db.js';
5
+ import { FileStore } from './storage/files.js';
6
+ import { CurriculumService } from './services/curriculum.js';
7
+ import { QAService } from './services/qa.js';
8
+ import { VizService } from './services/viz.js';
9
+ import { ExerciseService } from './services/exercises.js';
10
+ import { registerCurriculumTools } from './tools/curriculum.js';
11
+ import { registerQATools } from './tools/qa.js';
12
+ import { registerVizTools } from './tools/viz.js';
13
+ import { registerExerciseTools } from './tools/exercises.js';
14
+ import { DashboardServer } from './dashboard/server.js';
15
+ const fileStore = new FileStore();
16
+ const db = new Database(fileStore.dbPath);
17
+ const sessions = new Map();
18
+ const curriculumSvc = new CurriculumService(db);
19
+ const qaSvc = new QAService(db);
20
+ const vizSvc = new VizService(db);
21
+ const exerciseSvc = new ExerciseService(db, fileStore);
22
+ const port = Number(db.getSetting('dashboard_port') ?? '19282');
23
+ const dashboard = new DashboardServer(curriculumSvc, qaSvc, vizSvc, exerciseSvc, port);
24
+ const notify = () => dashboard.notify();
25
+ const server = new McpServer({ name: 'study-dash', version: '0.1.0' });
26
+ registerCurriculumTools(server, curriculumSvc, sessions, notify);
27
+ registerQATools(server, qaSvc, sessions, notify);
28
+ registerVizTools(server, vizSvc, sessions, notify);
29
+ registerExerciseTools(server, exerciseSvc, sessions, notify);
30
+ dashboard.start();
31
+ async function run() {
32
+ const transport = new StdioServerTransport();
33
+ await server.connect(transport);
34
+ console.error(`study-dash MCP server running, dashboard at http://127.0.0.1:${port}`);
35
+ }
36
+ run().catch((err) => {
37
+ console.error('Fatal:', err);
38
+ process.exit(1);
39
+ });
40
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,MAAM,SAAS,GAAG,IAAI,SAAS,EAAE,CAAC;AAClC,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AAC1C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC;AAEjD,MAAM,aAAa,GAAG,IAAI,iBAAiB,CAAC,EAAE,CAAC,CAAC;AAChD,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC;AAChC,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;AAClC,MAAM,WAAW,GAAG,IAAI,eAAe,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;AAEvD,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,IAAI,OAAO,CAAC,CAAC;AAChE,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;AACvF,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;AAExC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAEvE,uBAAuB,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AACjE,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AACjD,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AACnD,qBAAqB,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAE7D,SAAS,CAAC,KAAK,EAAE,CAAC;AAElB,KAAK,UAAU,GAAG;IAChB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,gEAAgE,IAAI,EAAE,CAAC,CAAC;AACxF,CAAC;AAED,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAClB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { Database } from '../storage/db.js';
2
+ import type { Subject, Topic, PhaseWithTopics, ProgressStats } from '../types.js';
3
+ interface PhaseInput {
4
+ name: string;
5
+ description: string;
6
+ topics: Array<{
7
+ name: string;
8
+ description: string;
9
+ }>;
10
+ }
11
+ export declare class CurriculumService {
12
+ private db;
13
+ constructor(db: Database);
14
+ createSubject(name: string, language?: string, source?: Subject['source']): Subject;
15
+ listSubjects(): Subject[];
16
+ getSubject(id: number): Subject | undefined;
17
+ findSubjectByName(name: string): Subject | undefined;
18
+ importCurriculum(subjectId: number, phases: PhaseInput[]): void;
19
+ getCurriculum(subjectId: number): PhaseWithTopics[];
20
+ getProgress(subjectId: number): ProgressStats;
21
+ setTopicStatus(topicId: number, status: Topic['status']): void;
22
+ getTopic(id: number): Topic | undefined;
23
+ findTopic(subjectId: number, name: string): Topic | undefined;
24
+ }
25
+ export {};
@@ -0,0 +1,110 @@
1
+ function slugify(name) {
2
+ return name
3
+ .toLowerCase()
4
+ .replace(/[^a-z0-9]+/g, '-')
5
+ .replace(/^-+|-+$/g, '');
6
+ }
7
+ export class CurriculumService {
8
+ db;
9
+ constructor(db) {
10
+ this.db = db;
11
+ }
12
+ // ── Subjects ───────────────────────────────────────────────────────────────
13
+ createSubject(name, language = '', source = 'manual') {
14
+ const slug = language && !language.includes(' ') && name.toLowerCase() === language.toLowerCase()
15
+ ? language.toLowerCase()
16
+ : slugify(name);
17
+ const result = this.db.raw
18
+ .prepare('INSERT INTO subjects (name, slug, language, source) VALUES (?, ?, ?, ?) RETURNING id')
19
+ .get(name, slug, language, source);
20
+ return this.getSubject(result.id);
21
+ }
22
+ listSubjects() {
23
+ return this.db.raw
24
+ .prepare('SELECT * FROM subjects ORDER BY created_at DESC')
25
+ .all();
26
+ }
27
+ getSubject(id) {
28
+ return this.db.raw
29
+ .prepare('SELECT * FROM subjects WHERE id = ?')
30
+ .get(id);
31
+ }
32
+ findSubjectByName(name) {
33
+ return this.db.raw
34
+ .prepare('SELECT * FROM subjects WHERE lower(name) = lower(?)')
35
+ .get(name);
36
+ }
37
+ // ── Curriculum import ──────────────────────────────────────────────────────
38
+ importCurriculum(subjectId, phases) {
39
+ const insertPhase = this.db.raw.prepare('INSERT INTO phases (subject_id, name, description, sort_order) VALUES (?, ?, ?, ?) RETURNING id');
40
+ const insertTopic = this.db.raw.prepare('INSERT INTO topics (phase_id, name, description, sort_order) VALUES (?, ?, ?, ?)');
41
+ const run = this.db.raw.transaction(() => {
42
+ phases.forEach((phase, phaseIdx) => {
43
+ const phaseRow = insertPhase.get(subjectId, phase.name, phase.description, phaseIdx);
44
+ phase.topics.forEach((topic, topicIdx) => {
45
+ insertTopic.run(phaseRow.id, topic.name, topic.description, topicIdx);
46
+ });
47
+ });
48
+ });
49
+ run();
50
+ }
51
+ getCurriculum(subjectId) {
52
+ const phases = this.db.raw
53
+ .prepare('SELECT * FROM phases WHERE subject_id = ? ORDER BY sort_order')
54
+ .all(subjectId);
55
+ const getTopics = this.db.raw.prepare('SELECT * FROM topics WHERE phase_id = ? ORDER BY sort_order');
56
+ return phases.map((phase) => ({
57
+ ...phase,
58
+ topics: getTopics.all(phase.id),
59
+ }));
60
+ }
61
+ // ── Progress ───────────────────────────────────────────────────────────────
62
+ getProgress(subjectId) {
63
+ const row = this.db.raw
64
+ .prepare(`WITH subject_topics AS (
65
+ SELECT t.id, t.status
66
+ FROM topics t
67
+ JOIN phases p ON p.id = t.phase_id
68
+ WHERE p.subject_id = :sid
69
+ )
70
+ SELECT
71
+ COUNT(*) AS total_topics,
72
+ SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) AS done,
73
+ SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) AS in_progress,
74
+ SUM(CASE WHEN status = 'todo' THEN 1 ELSE 0 END) AS todo,
75
+ (SELECT COUNT(*) FROM entries WHERE topic_id IN (SELECT id FROM subject_topics)) AS total_entries,
76
+ (SELECT COUNT(*) FROM exercises WHERE topic_id IN (SELECT id FROM subject_topics)) AS total_exercises,
77
+ (SELECT COUNT(*) FROM visualizations WHERE topic_id IN (SELECT id FROM subject_topics)) AS total_viz
78
+ FROM subject_topics`)
79
+ .get({ sid: subjectId });
80
+ return {
81
+ total_topics: row?.total_topics ?? 0,
82
+ done: row?.done ?? 0,
83
+ in_progress: row?.in_progress ?? 0,
84
+ todo: row?.todo ?? 0,
85
+ total_entries: row?.total_entries ?? 0,
86
+ total_exercises: row?.total_exercises ?? 0,
87
+ total_viz: row?.total_viz ?? 0,
88
+ };
89
+ }
90
+ // ── Topics ─────────────────────────────────────────────────────────────────
91
+ setTopicStatus(topicId, status) {
92
+ this.db.raw
93
+ .prepare("UPDATE topics SET status = ?, updated_at = datetime('now') WHERE id = ?")
94
+ .run(status, topicId);
95
+ }
96
+ getTopic(id) {
97
+ return this.db.raw
98
+ .prepare('SELECT * FROM topics WHERE id = ?')
99
+ .get(id);
100
+ }
101
+ findTopic(subjectId, name) {
102
+ return this.db.raw
103
+ .prepare(`SELECT t.* FROM topics t
104
+ JOIN phases p ON p.id = t.phase_id
105
+ WHERE p.subject_id = ? AND lower(t.name) = lower(?)
106
+ LIMIT 1`)
107
+ .get(subjectId, name);
108
+ }
109
+ }
110
+ //# sourceMappingURL=curriculum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curriculum.js","sourceRoot":"","sources":["../../src/services/curriculum.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import { Database } from '../storage/db.js';
2
+ import { FileStore } from '../storage/files.js';
3
+ import type { Exercise, ExerciseResult } from '../types.js';
4
+ interface CreateExerciseData {
5
+ title: string;
6
+ type: Exercise['type'];
7
+ description: string;
8
+ difficulty?: Exercise['difficulty'];
9
+ est_minutes?: number;
10
+ source?: Exercise['source'];
11
+ starter_code?: string;
12
+ test_content?: string;
13
+ quiz_json?: string;
14
+ }
15
+ interface QuizSubmitResult {
16
+ score: number;
17
+ total: number;
18
+ passed: boolean;
19
+ results: Array<{
20
+ test_name: string;
21
+ passed: boolean;
22
+ output: string;
23
+ }>;
24
+ }
25
+ export declare class ExerciseService {
26
+ private db;
27
+ private fileStore;
28
+ constructor(db: Database, fileStore: FileStore);
29
+ createExercise(topicId: number, data: CreateExerciseData): Exercise;
30
+ runTests(exerciseId: number): Promise<ExerciseResult[]>;
31
+ submitQuiz(exerciseId: number, answers: (number | boolean | string)[]): QuizSubmitResult;
32
+ listForTopic(topicId: number): Exercise[];
33
+ private getSubjectForTopic;
34
+ }
35
+ export {};