coursecode 0.1.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 (362) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -0
  3. package/THIRD_PARTY_NOTICES.md +22 -0
  4. package/bin/cli.js +331 -0
  5. package/framework/assets/logo-coursecode-black.svg +14 -0
  6. package/framework/assets/logo-coursecode-white.svg +14 -0
  7. package/framework/assets/logo-coursecode.svg +14 -0
  8. package/framework/css/01-base.css +160 -0
  9. package/framework/css/02-layout.css +499 -0
  10. package/framework/css/accessibility.css +834 -0
  11. package/framework/css/components/accordions.css +710 -0
  12. package/framework/css/components/assessments.css +520 -0
  13. package/framework/css/components/audio-player.css +570 -0
  14. package/framework/css/components/badges.css +80 -0
  15. package/framework/css/components/breadcrumbs.css +87 -0
  16. package/framework/css/components/buttons.css +707 -0
  17. package/framework/css/components/callouts.css +1280 -0
  18. package/framework/css/components/cards.css +475 -0
  19. package/framework/css/components/carousel.css +193 -0
  20. package/framework/css/components/checkbox-group.css +123 -0
  21. package/framework/css/components/checklist.css +203 -0
  22. package/framework/css/components/collapse.css +96 -0
  23. package/framework/css/components/comparison.css +33 -0
  24. package/framework/css/components/content-image.css +36 -0
  25. package/framework/css/components/document-gallery.css +425 -0
  26. package/framework/css/components/dropdown.css +115 -0
  27. package/framework/css/components/embed-frame.css +142 -0
  28. package/framework/css/components/engagement.css +412 -0
  29. package/framework/css/components/features.css +35 -0
  30. package/framework/css/components/flip-cards.css +253 -0
  31. package/framework/css/components/footer.css +353 -0
  32. package/framework/css/components/forms.css +294 -0
  33. package/framework/css/components/hero.css +216 -0
  34. package/framework/css/components/images.css +528 -0
  35. package/framework/css/components/interactive-timeline.css +274 -0
  36. package/framework/css/components/intro-cards.css +30 -0
  37. package/framework/css/components/lightbox.css +666 -0
  38. package/framework/css/components/loading.css +65 -0
  39. package/framework/css/components/modals.css +235 -0
  40. package/framework/css/components/notifications.css +107 -0
  41. package/framework/css/components/quote.css +150 -0
  42. package/framework/css/components/sidebar.css +684 -0
  43. package/framework/css/components/slide-header.css +52 -0
  44. package/framework/css/components/spinner.css +62 -0
  45. package/framework/css/components/stats.css +44 -0
  46. package/framework/css/components/steps.css +232 -0
  47. package/framework/css/components/tables.css +90 -0
  48. package/framework/css/components/tabs.css +347 -0
  49. package/framework/css/components/timeline.css +154 -0
  50. package/framework/css/components/toggle.css +95 -0
  51. package/framework/css/components/tooltip.css +226 -0
  52. package/framework/css/components/video-player.css +438 -0
  53. package/framework/css/design-tokens.css +707 -0
  54. package/framework/css/framework.css +86 -0
  55. package/framework/css/interactions/accessibility.css +75 -0
  56. package/framework/css/interactions/base.css +92 -0
  57. package/framework/css/interactions/drag-drop.css +295 -0
  58. package/framework/css/interactions/fill-in-the-blank.css +236 -0
  59. package/framework/css/interactions/hotspots.css +69 -0
  60. package/framework/css/interactions/index.css +45 -0
  61. package/framework/css/interactions/interactive-image.css +359 -0
  62. package/framework/css/interactions/likert.css +126 -0
  63. package/framework/css/interactions/matching.css +354 -0
  64. package/framework/css/interactions/numeric-input.css +78 -0
  65. package/framework/css/interactions/sequencing.css +378 -0
  66. package/framework/css/interactions/true-false.css +177 -0
  67. package/framework/css/layouts/article.css +258 -0
  68. package/framework/css/layouts/base.css +30 -0
  69. package/framework/css/layouts/canvas.css +38 -0
  70. package/framework/css/layouts/focused.css +236 -0
  71. package/framework/css/layouts/index.css +29 -0
  72. package/framework/css/layouts/presentation.css +191 -0
  73. package/framework/css/layouts/traditional.css +52 -0
  74. package/framework/css/responsive.css +439 -0
  75. package/framework/css/utilities/accessibility-utils.css +59 -0
  76. package/framework/css/utilities/animations.css +419 -0
  77. package/framework/css/utilities/borders.css +72 -0
  78. package/framework/css/utilities/colors.css +76 -0
  79. package/framework/css/utilities/container.css +46 -0
  80. package/framework/css/utilities/decorative.css +442 -0
  81. package/framework/css/utilities/display.css +257 -0
  82. package/framework/css/utilities/flexbox.css +80 -0
  83. package/framework/css/utilities/grid.css +69 -0
  84. package/framework/css/utilities/icons.css +534 -0
  85. package/framework/css/utilities/lists.css +190 -0
  86. package/framework/css/utilities/spacing.css +167 -0
  87. package/framework/css/utilities/tables.css +81 -0
  88. package/framework/css/utilities/typography.css +159 -0
  89. package/framework/css/utilities/visibility.css +117 -0
  90. package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
  91. package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
  92. package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
  93. package/framework/docs/DATA_MODEL.md +409 -0
  94. package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
  95. package/framework/docs/USER_GUIDE.md +583 -0
  96. package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
  97. package/framework/docs/examples/cloudflare-data-worker.js +102 -0
  98. package/framework/docs/examples/cloudflare-error-worker.js +228 -0
  99. package/framework/index.html +175 -0
  100. package/framework/js/app/AppActions.js +410 -0
  101. package/framework/js/app/AppState.js +225 -0
  102. package/framework/js/app/AppUI.js +616 -0
  103. package/framework/js/assessment/AssessmentActions.js +615 -0
  104. package/framework/js/assessment/AssessmentFactory.js +471 -0
  105. package/framework/js/assessment/AssessmentState.js +322 -0
  106. package/framework/js/assessment/AssessmentUI.js +451 -0
  107. package/framework/js/automation/api-engagement.js +196 -0
  108. package/framework/js/automation/api-interactions.js +167 -0
  109. package/framework/js/automation/api.js +242 -0
  110. package/framework/js/automation/index.js +41 -0
  111. package/framework/js/components/interactions/drag-drop.js +884 -0
  112. package/framework/js/components/interactions/fill-in.js +535 -0
  113. package/framework/js/components/interactions/hotspot.js +702 -0
  114. package/framework/js/components/interactions/interaction-base.js +511 -0
  115. package/framework/js/components/interactions/likert.js +301 -0
  116. package/framework/js/components/interactions/matching.js +699 -0
  117. package/framework/js/components/interactions/multiple-choice.js +377 -0
  118. package/framework/js/components/interactions/numeric.js +271 -0
  119. package/framework/js/components/interactions/sequencing.js +423 -0
  120. package/framework/js/components/interactions/true-false.js +241 -0
  121. package/framework/js/components/ui-components/accordion.js +442 -0
  122. package/framework/js/components/ui-components/alert.js +88 -0
  123. package/framework/js/components/ui-components/audio-player.js +1193 -0
  124. package/framework/js/components/ui-components/callout.js +121 -0
  125. package/framework/js/components/ui-components/carousel.js +145 -0
  126. package/framework/js/components/ui-components/checkbox-group.js +87 -0
  127. package/framework/js/components/ui-components/checklist.js +40 -0
  128. package/framework/js/components/ui-components/collapse.js +114 -0
  129. package/framework/js/components/ui-components/comparison.js +30 -0
  130. package/framework/js/components/ui-components/conditional-display.js +150 -0
  131. package/framework/js/components/ui-components/content-image.js +41 -0
  132. package/framework/js/components/ui-components/dropdown.js +262 -0
  133. package/framework/js/components/ui-components/embed-frame.js +274 -0
  134. package/framework/js/components/ui-components/features.js +33 -0
  135. package/framework/js/components/ui-components/flip-card.js +230 -0
  136. package/framework/js/components/ui-components/form-validator.js +76 -0
  137. package/framework/js/components/ui-components/hero.js +49 -0
  138. package/framework/js/components/ui-components/index.js +12 -0
  139. package/framework/js/components/ui-components/interactive-image.js +235 -0
  140. package/framework/js/components/ui-components/interactive-timeline.js +285 -0
  141. package/framework/js/components/ui-components/intro-cards.js +35 -0
  142. package/framework/js/components/ui-components/lightbox.js +652 -0
  143. package/framework/js/components/ui-components/modal.js +386 -0
  144. package/framework/js/components/ui-components/notifications.js +145 -0
  145. package/framework/js/components/ui-components/progress.js +88 -0
  146. package/framework/js/components/ui-components/quote.js +41 -0
  147. package/framework/js/components/ui-components/stats.js +33 -0
  148. package/framework/js/components/ui-components/steps.js +41 -0
  149. package/framework/js/components/ui-components/tabs.js +255 -0
  150. package/framework/js/components/ui-components/timeline.js +42 -0
  151. package/framework/js/components/ui-components/toggle-group.js +73 -0
  152. package/framework/js/components/ui-components/tooltip.js +458 -0
  153. package/framework/js/components/ui-components/value-display.js +133 -0
  154. package/framework/js/components/ui-components/video-player.js +686 -0
  155. package/framework/js/core/component-catalog.js +121 -0
  156. package/framework/js/core/event-bus.js +178 -0
  157. package/framework/js/core/interaction-catalog.js +149 -0
  158. package/framework/js/dev/runtime-linter.js +1725 -0
  159. package/framework/js/drivers/cmi5-driver.js +768 -0
  160. package/framework/js/drivers/driver-factory.js +77 -0
  161. package/framework/js/drivers/driver-interface.js +110 -0
  162. package/framework/js/drivers/http-driver-base.js +241 -0
  163. package/framework/js/drivers/lti-driver.js +508 -0
  164. package/framework/js/drivers/proxy-driver.js +444 -0
  165. package/framework/js/drivers/scorm-12-driver.js +560 -0
  166. package/framework/js/drivers/scorm-2004-driver.js +775 -0
  167. package/framework/js/drivers/scorm-driver-base.js +112 -0
  168. package/framework/js/engagement/engagement-manager.js +404 -0
  169. package/framework/js/engagement/engagement-progress.js +191 -0
  170. package/framework/js/engagement/engagement-trackers.js +215 -0
  171. package/framework/js/engagement/requirement-strategies.js +268 -0
  172. package/framework/js/main.js +727 -0
  173. package/framework/js/managers/accessibility-manager.js +499 -0
  174. package/framework/js/managers/assessment-manager.js +230 -0
  175. package/framework/js/managers/audio-manager.js +944 -0
  176. package/framework/js/managers/comment-manager.js +88 -0
  177. package/framework/js/managers/flag-manager.js +86 -0
  178. package/framework/js/managers/interaction-manager.js +254 -0
  179. package/framework/js/managers/interaction-registry.js +96 -0
  180. package/framework/js/managers/objective-manager.js +423 -0
  181. package/framework/js/managers/score-manager.js +441 -0
  182. package/framework/js/managers/video-manager.js +536 -0
  183. package/framework/js/navigation/Breadcrumbs.js +234 -0
  184. package/framework/js/navigation/NavigationActions.js +1132 -0
  185. package/framework/js/navigation/NavigationState.js +276 -0
  186. package/framework/js/navigation/NavigationUI.js +574 -0
  187. package/framework/js/navigation/document-gallery.js +357 -0
  188. package/framework/js/navigation/navigation-helpers.js +175 -0
  189. package/framework/js/navigation/navigation-validators.js +174 -0
  190. package/framework/js/state/index.js +8 -0
  191. package/framework/js/state/lms-connection.js +482 -0
  192. package/framework/js/state/lms-error-utils.js +58 -0
  193. package/framework/js/state/state-commits.js +200 -0
  194. package/framework/js/state/state-domains.js +86 -0
  195. package/framework/js/state/state-manager.js +502 -0
  196. package/framework/js/state/state-validation.js +311 -0
  197. package/framework/js/state/transaction-log.js +41 -0
  198. package/framework/js/state/xapi-statement-service.js +325 -0
  199. package/framework/js/utilities/access-control.js +99 -0
  200. package/framework/js/utilities/breakpoint-manager.js +315 -0
  201. package/framework/js/utilities/canvas-slide.js +35 -0
  202. package/framework/js/utilities/conditional-display.js +388 -0
  203. package/framework/js/utilities/course-channel.js +214 -0
  204. package/framework/js/utilities/course-helpers.js +420 -0
  205. package/framework/js/utilities/data-reporter.js +273 -0
  206. package/framework/js/utilities/error-reporter.js +313 -0
  207. package/framework/js/utilities/hotspot-helper.js +341 -0
  208. package/framework/js/utilities/icons.js +348 -0
  209. package/framework/js/utilities/logger.js +92 -0
  210. package/framework/js/utilities/markdown-renderer.js +45 -0
  211. package/framework/js/utilities/scroll-tracker.js +68 -0
  212. package/framework/js/utilities/ui-initializer.js +146 -0
  213. package/framework/js/utilities/utilities.js +293 -0
  214. package/framework/js/utilities/view-manager.js +227 -0
  215. package/framework/js/validation/html-validators.js +422 -0
  216. package/framework/js/validation/scorm-validators.js +438 -0
  217. package/framework/js/vendor/pipwerks.js +931 -0
  218. package/framework/scripts/generate-narration.js +629 -0
  219. package/framework/scripts/tts-providers/azure-provider.js +178 -0
  220. package/framework/scripts/tts-providers/base-provider.js +81 -0
  221. package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
  222. package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
  223. package/framework/scripts/tts-providers/google-provider.js +272 -0
  224. package/framework/scripts/tts-providers/index.js +158 -0
  225. package/framework/scripts/tts-providers/openai-provider.js +143 -0
  226. package/framework/version.json +63 -0
  227. package/lib/authoring-api.js +919 -0
  228. package/lib/build-linter.js +450 -0
  229. package/lib/build-packaging.js +186 -0
  230. package/lib/build.js +88 -0
  231. package/lib/cloud.js +691 -0
  232. package/lib/convert.js +341 -0
  233. package/lib/course-parser.js +936 -0
  234. package/lib/course-writer.js +258 -0
  235. package/lib/create.js +248 -0
  236. package/lib/css-index.js +237 -0
  237. package/lib/dev.js +51 -0
  238. package/lib/export-content.js +1246 -0
  239. package/lib/headless-browser.js +413 -0
  240. package/lib/import.js +377 -0
  241. package/lib/index.js +80 -0
  242. package/lib/info.js +79 -0
  243. package/lib/interaction-formatters.js +568 -0
  244. package/lib/manifest/cmi5-manifest.js +63 -0
  245. package/lib/manifest/lti-tool-config.js +53 -0
  246. package/lib/manifest/manifest-factory.js +99 -0
  247. package/lib/manifest/scorm-12-manifest.js +61 -0
  248. package/lib/manifest/scorm-2004-manifest.js +94 -0
  249. package/lib/manifest/scorm-proxy-manifest.js +104 -0
  250. package/lib/manifest-parser.js +96 -0
  251. package/lib/mcp-prompts.js +753 -0
  252. package/lib/mcp-server.js +316 -0
  253. package/lib/narration.js +53 -0
  254. package/lib/pdf-structure.js +142 -0
  255. package/lib/preview-export.js +231 -0
  256. package/lib/preview-routes-api.js +662 -0
  257. package/lib/preview-routes-editing.js +159 -0
  258. package/lib/preview-routes-lms.js +230 -0
  259. package/lib/preview-server.js +564 -0
  260. package/lib/project-utils.js +269 -0
  261. package/lib/proxy-templates/proxy.html +68 -0
  262. package/lib/proxy-templates/scorm-bridge.js +112 -0
  263. package/lib/scaffold.js +193 -0
  264. package/lib/schema-extractor.js +361 -0
  265. package/lib/slide-source-editor.js +586 -0
  266. package/lib/stub-player/app-viewer.js +195 -0
  267. package/lib/stub-player/app.js +370 -0
  268. package/lib/stub-player/catalog-panel.js +312 -0
  269. package/lib/stub-player/config-panel.js +1303 -0
  270. package/lib/stub-player/content-generator.js +586 -0
  271. package/lib/stub-player/content-viewer.js +173 -0
  272. package/lib/stub-player/debug-panel.js +420 -0
  273. package/lib/stub-player/edit-mode.js +922 -0
  274. package/lib/stub-player/edit-utils.js +400 -0
  275. package/lib/stub-player/header-bar.js +354 -0
  276. package/lib/stub-player/interaction-editor.js +210 -0
  277. package/lib/stub-player/interactions-panel.js +565 -0
  278. package/lib/stub-player/lms-api.js +1094 -0
  279. package/lib/stub-player/login-screen.js +74 -0
  280. package/lib/stub-player/outline-mode.js +689 -0
  281. package/lib/stub-player/styles/_assessments-panel.css +245 -0
  282. package/lib/stub-player/styles/_base.css +89 -0
  283. package/lib/stub-player/styles/_catalog-icons.css +96 -0
  284. package/lib/stub-player/styles/_catalog-panel.css +291 -0
  285. package/lib/stub-player/styles/_config-panel.css +636 -0
  286. package/lib/stub-player/styles/_content-viewer.css +834 -0
  287. package/lib/stub-player/styles/_debug-panel.css +576 -0
  288. package/lib/stub-player/styles/_edit-mode.css +128 -0
  289. package/lib/stub-player/styles/_header-bar.css +343 -0
  290. package/lib/stub-player/styles/_interaction-editor.css +140 -0
  291. package/lib/stub-player/styles/_interactions-panel.css +1038 -0
  292. package/lib/stub-player/styles/_login-screen.css +102 -0
  293. package/lib/stub-player/styles/_outline-mode.css +752 -0
  294. package/lib/stub-player/styles.css +15 -0
  295. package/lib/stub-player.js +160 -0
  296. package/lib/test-data-reporting.js +176 -0
  297. package/lib/test-error-reporting.js +146 -0
  298. package/lib/token.js +86 -0
  299. package/lib/upgrade.js +257 -0
  300. package/lib/validation-rules.js +517 -0
  301. package/lib/vite-plugin-content-discovery.js +296 -0
  302. package/package.json +108 -0
  303. package/schemas/XMLSchema.dtd +402 -0
  304. package/schemas/adlcp_v1p3.xsd +111 -0
  305. package/schemas/adlnav_v1p3.xsd +61 -0
  306. package/schemas/adlseq_v1p3.xsd +93 -0
  307. package/schemas/common/anyElement.xsd +27 -0
  308. package/schemas/common/dataTypes.xsd +138 -0
  309. package/schemas/common/elementNames.xsd +767 -0
  310. package/schemas/common/elementTypes.xsd +786 -0
  311. package/schemas/common/rootElement.xsd +31 -0
  312. package/schemas/common/vocabTypes.xsd +345 -0
  313. package/schemas/common/vocabValues.xsd +257 -0
  314. package/schemas/datatypes.dtd +203 -0
  315. package/schemas/ims_xml.xsd +35 -0
  316. package/schemas/imscp_v1p1.xsd +368 -0
  317. package/schemas/imsss_v1p0.xsd +67 -0
  318. package/schemas/imsss_v1p0auxresource.xsd +19 -0
  319. package/schemas/imsss_v1p0control.xsd +20 -0
  320. package/schemas/imsss_v1p0delivery.xsd +17 -0
  321. package/schemas/imsss_v1p0limit.xsd +47 -0
  322. package/schemas/imsss_v1p0objective.xsd +67 -0
  323. package/schemas/imsss_v1p0random.xsd +16 -0
  324. package/schemas/imsss_v1p0rollup.xsd +46 -0
  325. package/schemas/imsss_v1p0seqrule.xsd +108 -0
  326. package/schemas/imsss_v1p0util.xsd +94 -0
  327. package/schemas/license.txt +17 -0
  328. package/schemas/lom.xsd +102 -0
  329. package/schemas/lomCustom.xsd +62 -0
  330. package/schemas/lomLoose.xsd +62 -0
  331. package/schemas/lomStrict.xsd +62 -0
  332. package/schemas/xml.xsd +81 -0
  333. package/template/.env.example +92 -0
  334. package/template/course/assets/audio/example-intro.mp3 +0 -0
  335. package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
  336. package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
  337. package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
  338. package/template/course/assets/docs/example_md_1.md +39 -0
  339. package/template/course/assets/docs/example_md_2.md +41 -0
  340. package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
  341. package/template/course/assets/docs/example_pdf_2.pdf +0 -0
  342. package/template/course/assets/images/course-architecture.svg +36 -0
  343. package/template/course/assets/images/logo.svg +14 -0
  344. package/template/course/assets/widgets/counter-demo.html +190 -0
  345. package/template/course/assets/widgets/gravity-painter.html +384 -0
  346. package/template/course/course-config.js +539 -0
  347. package/template/course/icons.js +19 -0
  348. package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
  349. package/template/course/slides/example-course-structure.js +138 -0
  350. package/template/course/slides/example-final-exam.js +144 -0
  351. package/template/course/slides/example-finishing.js +127 -0
  352. package/template/course/slides/example-interactions-showcase.js +615 -0
  353. package/template/course/slides/example-preview-tour.js +129 -0
  354. package/template/course/slides/example-remedial.js +143 -0
  355. package/template/course/slides/example-summary.js +103 -0
  356. package/template/course/slides/example-ui-showcase.js +1805 -0
  357. package/template/course/slides/example-welcome.js +123 -0
  358. package/template/course/slides/example-workflow.js +140 -0
  359. package/template/course/theme.css +165 -0
  360. package/template/eslint.config.js +47 -0
  361. package/template/package.json +28 -0
  362. package/template/vite.config.js +339 -0
@@ -0,0 +1,753 @@
1
+ /**
2
+ * MCP Prompts & Tool Configuration
3
+ *
4
+ * Single source of truth for all MCP tool definitions, descriptions,
5
+ * and dynamic instruction generation. mcp-server.js imports from here.
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import { getWorkflowStatus } from './authoring-api.js';
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+
15
+ // ========================================
16
+ // Tool Definitions
17
+ // ========================================
18
+
19
+ export const TOOLS = [
20
+ // --- Runtime Tools (require headless browser) ---
21
+ {
22
+ name: 'coursecode_state',
23
+ description: `Get course state, runtime errors, and warnings in one call. This is the primary tool for checking errors.
24
+
25
+ Returns:
26
+ - slide: current slide ID (string)
27
+ - toc: course structure [{id, type, title, file?}]
28
+ - interactions: interactions on current slide [{id, type, hasResponse, isChecked}]
29
+ - engagement: slide engagement {complete, percentage, requirements}
30
+ - lmsState: LMS data {score, completion, success, bookmark, format, objectives, state}
31
+ - apiLog: last 20 LMS API calls [{timestamp, method, args, result}]
32
+ - errors: runtime errors/warnings [{type, message, hint, isWarning}]
33
+ - frameworkLogs: structured framework log events [{level, domain, operation, message, stack?, timestamp}]
34
+ - consoleLogs: browser console warnings/errors [{type, text, time}]
35
+
36
+ Use this first to understand the course state before taking actions.
37
+ Requires preview server to be running.`,
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {},
41
+ required: []
42
+ }
43
+ },
44
+ {
45
+ name: 'coursecode_navigate',
46
+ description: `Navigate to a specific slide by ID. Returns:
47
+ - slide: current slide ID
48
+ - interactions: interactions on the new slide [{id, type, hasResponse, isChecked}]
49
+ - engagement: slide engagement {complete, percentage, requirements}
50
+ - accessibility: current accessibility state {theme, highContrast, largeFont, reducedMotion}
51
+
52
+ Optionally set theme or highContrast before navigating. Use this to toggle dark mode for visual inspection.
53
+
54
+ Use coursecode_state first to get the structure and find valid slide IDs.
55
+ Requires preview server to be running.`,
56
+ inputSchema: {
57
+ type: 'object',
58
+ properties: {
59
+ slideId: {
60
+ type: 'string',
61
+ description: 'The slide ID to navigate to'
62
+ },
63
+ theme: {
64
+ type: 'string',
65
+ description: 'Set theme before navigating: "light" or "dark"',
66
+ enum: ['light', 'dark']
67
+ },
68
+ highContrast: {
69
+ type: 'boolean',
70
+ description: 'Enable or disable high contrast mode before navigating'
71
+ }
72
+ },
73
+ required: ['slideId']
74
+ }
75
+ },
76
+ {
77
+ name: 'coursecode_interact',
78
+ description: `Set a response for an interaction AND evaluate it in one call. Returns:
79
+ - correct: boolean
80
+ - score: 0-1
81
+ - feedback: feedback message if any
82
+ - state: updated course state
83
+
84
+ Response format depends on interaction type:
85
+ - multiple-choice: 'a', 'b', 'c', etc.
86
+ - true-false: true or false
87
+ - fill-in-blank: {blankId: 'answer'}
88
+ - drag-drop: {itemId: 'zoneId'}
89
+ - numeric: number
90
+ - sequencing: ['id1', 'id2', 'id3']
91
+
92
+ Requires preview server to be running.`,
93
+ inputSchema: {
94
+ type: 'object',
95
+ properties: {
96
+ interactionId: {
97
+ type: 'string',
98
+ description: 'The interaction ID to answer'
99
+ },
100
+ response: {
101
+ description: 'The response value (format depends on interaction type)'
102
+ }
103
+ },
104
+ required: ['interactionId', 'response']
105
+ }
106
+ },
107
+ {
108
+ name: 'coursecode_reset',
109
+ description: `Clear learner state and restart the course. Use this to test from a fresh state.
110
+
111
+ Clears localStorage and fully reloads the headless browser.
112
+
113
+ Requires preview server to be running.`,
114
+ inputSchema: {
115
+ type: 'object',
116
+ properties: {},
117
+ required: []
118
+ }
119
+ },
120
+ {
121
+ name: 'coursecode_screenshot',
122
+ description: `Take a screenshot of the course preview. Returns a JPEG image.
123
+
124
+ Two modes optimized for token efficiency:
125
+ - normal (default): 800×450 (~30-60KB) — layout checks
126
+ - detailed: 1280×720 (~100-200KB) — close text/element inspection
127
+
128
+ Use scrollY to scroll course content before capturing (useful for long slides).
129
+ fullPage captures the course iframe's full content area.
130
+
131
+ Use to visually inspect slide layout, design, and component rendering.
132
+ Requires preview server to be running.`,
133
+ inputSchema: {
134
+ type: 'object',
135
+ properties: {
136
+ slideId: {
137
+ type: 'string',
138
+ description: 'Navigate to this slide ID before taking screenshot'
139
+ },
140
+ fullPage: {
141
+ type: 'boolean',
142
+ description: 'Capture full scrollable page instead of viewport'
143
+ },
144
+ detailed: {
145
+ type: 'boolean',
146
+ description: 'Use higher-res mode (1280×720) for close inspection'
147
+ },
148
+ scrollY: {
149
+ type: 'number',
150
+ description: 'Scroll course content to this Y position (pixels) before capturing'
151
+ }
152
+ },
153
+ required: []
154
+ }
155
+ },
156
+ // --- Workflow & Build Tools ---
157
+ {
158
+ name: 'coursecode_workflow_status',
159
+ description: `Detect the current authoring stage and get stage-specific instructions.
160
+
161
+ Returns:
162
+ - stage: human-readable stage name
163
+ - stageNumber: 1-5
164
+ - checklist: {hasRawRefs, hasConvertedRefs, hasOutline, hasSlides, hasCourseConfig, previewRunning}
165
+ - nextAction: recommended next step
166
+ - recommendedTool: MCP tool to use next
167
+ - instructions: detailed guidance for the current stage
168
+
169
+ Stages:
170
+ 1. Source Ingestion - create project and convert reference docs
171
+ 2. Outline Creation - build COURSE_OUTLINE.md from references
172
+ 3. Course Building - create slides and course-config.js
173
+ 4. Preview & Polish - iterate on visual quality and correctness
174
+ 5. Export Ready - lint passes, ready for LMS deployment
175
+
176
+ Call this after completing a major milestone to get updated guidance for the next stage.`,
177
+ inputSchema: {
178
+ type: 'object',
179
+ properties: {},
180
+ required: []
181
+ }
182
+ },
183
+ {
184
+ name: 'coursecode_build',
185
+ description: `Build the course for deployment. Runs the Vite production build.
186
+
187
+ Returns:
188
+ - success: boolean
189
+ - format: LMS format used
190
+ - outputDir: path to built output
191
+ - errors: any build errors
192
+ - warnings: any build warnings
193
+ - duration: build time
194
+
195
+ Use in Stage 5 when the course is ready for export.`,
196
+ inputSchema: {
197
+ type: 'object',
198
+ properties: {
199
+ format: {
200
+ type: 'string',
201
+ description: 'LMS format: cmi5, scorm2004, scorm1.2, or lti (default: cmi5)',
202
+ enum: ['cmi5', 'scorm2004', 'scorm1.2', 'lti']
203
+ }
204
+ },
205
+ required: []
206
+ }
207
+ },
208
+ // --- Catalog & Validation Tools (filesystem, no preview needed) ---
209
+ {
210
+ name: 'coursecode_css_catalog',
211
+ description: `Get CSS class information extracted from real CSS source files.
212
+
213
+ Without 'category': returns all classes grouped by category with abbreviated declarations.
214
+ With 'category': returns full detail for that category only.
215
+
216
+ Categories are derived from CSS file paths (e.g., "utilities/borders", "layout", "patterns").
217
+ Use to discover available CSS classes before authoring slides. Lint catches invalid classes.`,
218
+ inputSchema: {
219
+ type: 'object',
220
+ properties: {
221
+ category: {
222
+ type: 'string',
223
+ description: 'Optional category to get full details for (e.g., "utilities/colors", "layout", "patterns")'
224
+ }
225
+ },
226
+ required: []
227
+ }
228
+ },
229
+ {
230
+ name: 'coursecode_component_catalog',
231
+ description: `Get UI component information.
232
+
233
+ Without 'type': returns compact list of all components (name + description + engagement tracking type).
234
+ With 'type': returns full schema, metadata, and HTML usage template for that specific component.
235
+
236
+ Components are declarative HTML (data-component attributes). No imports needed—just use the HTML patterns in your slide template.
237
+ Use to discover available components before authoring slides.`,
238
+ inputSchema: {
239
+ type: 'object',
240
+ properties: {
241
+ type: {
242
+ type: 'string',
243
+ description: 'Optional component type to get full details for (e.g., "tabs", "accordion")'
244
+ }
245
+ },
246
+ required: []
247
+ }
248
+ },
249
+ {
250
+ name: 'coursecode_interaction_catalog',
251
+ description: `Get interaction type information.
252
+
253
+ Without 'type': returns compact list of all interaction types (name + description).
254
+ With 'type': returns full schema, properties, and factory name for that specific interaction.
255
+
256
+ Interactions use factory functions from the global CourseCode object (e.g., const { createMultipleChoiceQuestion } = CourseCode). No import statements needed.
257
+ Use to discover available interactions before creating assessments.`,
258
+ inputSchema: {
259
+ type: 'object',
260
+ properties: {
261
+ type: {
262
+ type: 'string',
263
+ description: 'Optional interaction type to get full details for (e.g., "multiple-choice", "drag-drop")'
264
+ }
265
+ },
266
+ required: []
267
+ }
268
+ },
269
+
270
+ {
271
+ name: 'coursecode_lint',
272
+ description: `Run the build-time linter and get structured results.
273
+
274
+ Returns:
275
+ - errors: [{slideId, rule, message, severity}]
276
+ - warnings: [{slideId, rule, message, severity, class?, suggestion?}]
277
+ - passed: boolean
278
+
279
+ Rules detected:
280
+ - undefined-css-class: hallucinated or stale class names (with fix suggestions)
281
+ - unknown-component: unregistered data-component types
282
+ - requirement-missing-component: engagement requirement without matching component
283
+ - missing-slide-file: slide references non-existent file
284
+ - slide-id-filename-mismatch: slide ID doesn't match component filename
285
+ - assessment-id-mismatch: config ID doesn't match assessment ID
286
+ - invalid-gating: bad gating condition configuration
287
+
288
+ The runtime linter (visible via coursecode_state errors/warnings) also checks:
289
+ - Spacing: flex/grid containers without gap, adjacent elements with no margin, containers with no padding
290
+ - Contrast, touch targets, text proximity to borders, element overlap, styled lists
291
+
292
+ Suppression: Add data-lint-ignore to any HTML element to suppress warnings for it and children.
293
+ data-lint-ignore — suppress all warnings
294
+ data-lint-ignore="spacing" — suppress only spacing warnings
295
+ data-lint-ignore="spacing,contrast" — suppress multiple categories
296
+ Categories: spacing, contrast, target-size, proximity, overlap, list-style, css-class
297
+
298
+ Use AFTER making changes to validate the course.`,
299
+ inputSchema: {
300
+ type: 'object',
301
+ properties: {},
302
+ required: []
303
+ }
304
+ },
305
+ {
306
+ name: 'coursecode_icon_catalog',
307
+ description: `Get icon information.
308
+
309
+ Without 'name': returns all available icon names grouped by category, with counts and usage syntax.
310
+ With 'name': returns the icon's SVG content, category, and usage examples (JS, config, HTML).
311
+
312
+ Use to discover available icons before authoring slides or configuring menus.`,
313
+ inputSchema: {
314
+ type: 'object',
315
+ properties: {
316
+ name: {
317
+ type: 'string',
318
+ description: 'Optional icon name to get full details for (e.g., "check", "book-open")'
319
+ }
320
+ },
321
+ required: []
322
+ }
323
+ },
324
+ {
325
+ name: 'coursecode_export_content',
326
+ description: `Extract course content as structured Markdown or JSON for review.
327
+
328
+ Returns the full text content of the course: slide headers, body text, tabs, accordions, callouts, cards, interactions, assessment questions, narration, config, and structure overview.
329
+
330
+ Use cases:
331
+ - Compare built course against COURSE_OUTLINE.md for accuracy
332
+ - Review all interactions and assessment questions at once
333
+ - Audit content wording and consistency across slides
334
+ - Generate content for localization or SME review
335
+
336
+ Filtering options keep output manageable:
337
+ - slides: scope to specific slide IDs
338
+ - interactionsOnly: just Q&A, no slide content
339
+ - excludeInteractions: content only, no Q&A
340
+ - format: 'md' (default) or 'json' for structured data
341
+
342
+ Does not require preview server.`,
343
+ inputSchema: {
344
+ type: 'object',
345
+ properties: {
346
+ slides: {
347
+ type: 'string',
348
+ description: 'Comma-separated slide IDs to export (default: all slides)'
349
+ },
350
+ interactionsOnly: {
351
+ type: 'boolean',
352
+ description: 'Export only interactions and assessment questions (no slide content)'
353
+ },
354
+ includeNarration: {
355
+ type: 'boolean',
356
+ description: 'Include narration transcripts (default: false)'
357
+ },
358
+ includeAnswers: {
359
+ type: 'boolean',
360
+ description: 'Include correct answers for interactions (default: true)'
361
+ },
362
+ includeFeedback: {
363
+ type: 'boolean',
364
+ description: 'Include feedback text (default: true)'
365
+ },
366
+ excludeInteractions: {
367
+ type: 'boolean',
368
+ description: 'Exclude all interactions from output (default: false)'
369
+ },
370
+ format: {
371
+ type: 'string',
372
+ description: 'Output format: md or json (default: md)',
373
+ enum: ['md', 'json']
374
+ }
375
+ },
376
+ required: []
377
+ }
378
+ },
379
+ ];
380
+
381
+ // ========================================
382
+ // Dynamic Instructions Builder
383
+ // ========================================
384
+
385
+ /**
386
+ * Build a compact directory tree string for a given directory.
387
+ */
388
+ function buildDirTree(dirPath, prefix = '', maxDepth = 3, depth = 0) {
389
+ if (depth >= maxDepth || !fs.existsSync(dirPath)) return '';
390
+
391
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true })
392
+ .filter(e => !e.name.startsWith('.'))
393
+ .sort((a, b) => {
394
+ // directories first, then files
395
+ if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
396
+ return a.name.localeCompare(b.name);
397
+ });
398
+
399
+ const lines = [];
400
+ entries.forEach((entry, i) => {
401
+ const isLast = i === entries.length - 1;
402
+ const connector = isLast ? '└── ' : '├── ';
403
+ const childPrefix = isLast ? ' ' : '│ ';
404
+
405
+ if (entry.isDirectory()) {
406
+ const children = fs.readdirSync(path.join(dirPath, entry.name))
407
+ .filter(e => !e.startsWith('.'));
408
+ lines.push(`${prefix}${connector}${entry.name}/ (${children.length} files)`);
409
+ if (depth < maxDepth - 1) {
410
+ lines.push(buildDirTree(path.join(dirPath, entry.name), prefix + childPrefix, maxDepth, depth + 1));
411
+ }
412
+ } else {
413
+ lines.push(`${prefix}${connector}${entry.name}`);
414
+ }
415
+ });
416
+
417
+ return lines.filter(Boolean).join('\n');
418
+ }
419
+
420
+ /**
421
+ * Try to read course title and description from course-config.js
422
+ */
423
+ function getCourseInfo(courseDir) {
424
+ const configPath = path.join(courseDir, 'course-config.js');
425
+ if (!fs.existsSync(configPath)) return { title: null, description: null };
426
+
427
+ try {
428
+ const content = fs.readFileSync(configPath, 'utf-8');
429
+ const titleMatch = content.match(/title:\s*['"`]([^'"`]+)['"`]/);
430
+ const descMatch = content.match(/description:\s*['"`]([^'"`]+)['"`]/);
431
+ return {
432
+ title: titleMatch ? titleMatch[1] : null,
433
+ description: descMatch ? descMatch[1] : null
434
+ };
435
+ } catch {
436
+ return { title: null, description: null };
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Build stage-specific instructions for the current authoring state.
442
+ * Returns instruction text appropriate for the detected stage.
443
+ */
444
+ function buildStageInstructions(stageNumber, courseDir, _checklist) {
445
+ const frameworkDocs = path.join(__dirname, '..', 'framework', 'docs');
446
+ const outlineGuidePath = path.join(frameworkDocs, 'COURSE_OUTLINE_GUIDE.md');
447
+ const outlineTemplatePath = path.join(frameworkDocs, 'COURSE_OUTLINE_TEMPLATE.md');
448
+ const authoringGuidePath = path.join(frameworkDocs, 'COURSE_AUTHORING_GUIDE.md');
449
+ const refsDir = path.join(courseDir, 'references');
450
+ const convertedDir = path.join(refsDir, 'converted');
451
+
452
+ switch (stageNumber) {
453
+ case 0: // Not initialized
454
+ return `## What to Do
455
+
456
+ No course project found. Help the author create one:
457
+ coursecode create <project-name>
458
+
459
+ This creates a course/ directory with the starter template.
460
+ After creating the project, the author should add reference files (PDF, DOCX, PPTX) to course/references/ for conversion.
461
+
462
+ ## Next Stage
463
+ Once the project exists and reference files are added, Stage 1 (Source Ingestion) begins. Run workflow_status to refresh.`;
464
+
465
+ case 1: { // Source Ingestion
466
+ const rawFiles = listSafe(refsDir, ['.pdf', '.docx', '.doc', '.pptx', '.ppt']);
467
+ const convertedFiles = listSafe(convertedDir, ['.md']);
468
+
469
+ let refStatus = '';
470
+ if (rawFiles.length > 0 && convertedFiles.length === 0) {
471
+ refStatus = `\nFound ${rawFiles.length} unconverted reference file(s):\n${rawFiles.map(f => ` - ${f}`).join('\n')}\n\nConvert them: coursecode convert`;
472
+ } else if (convertedFiles.length > 0) {
473
+ refStatus = `\n${convertedFiles.length} converted reference(s) ready in course/references/converted/`;
474
+ if (rawFiles.length > convertedFiles.length) {
475
+ refStatus += `\n${rawFiles.length - convertedFiles.length} file(s) still need conversion. Run: coursecode convert`;
476
+ }
477
+ } else {
478
+ refStatus = '\nNo reference files found. The author should add source documents (PDF, DOCX, PPTX) to course/references/';
479
+ }
480
+
481
+ return `## What to Do
482
+
483
+ Help the author prepare reference materials for course creation.
484
+ ${refStatus}
485
+
486
+ Reference files go in course/references/ (PDF, DOCX, PPTX).
487
+ Convert to markdown: coursecode convert
488
+ Converted files appear in course/references/converted/
489
+
490
+ ## Next Stage
491
+ Once references are converted to markdown, Stage 2 (Outline Creation) begins. Call workflow_status after conversion to get updated guidance.`;
492
+ }
493
+
494
+ case 2: { // Outline Creation
495
+ const convertedFiles = listSafe(convertedDir, ['.md']);
496
+ const refList = convertedFiles.length <= 10
497
+ ? convertedFiles.map(f => ` - ${path.join(convertedDir, f)}`).join('\n')
498
+ : convertedFiles.slice(0, 10).map(f => ` - ${path.join(convertedDir, f)}`).join('\n') + `\n ... and ${convertedFiles.length - 10} more`;
499
+
500
+ return `## What to Do
501
+
502
+ Create COURSE_OUTLINE.md from the reference materials.
503
+
504
+ 1. Read the outline guide (explains format, rules, section structure):
505
+ ${outlineGuidePath}
506
+
507
+ 2. Copy and modify the template to create course/COURSE_OUTLINE.md:
508
+ ${outlineTemplatePath}
509
+
510
+ 3. Use these reference materials as source content:
511
+ ${refList}
512
+
513
+ The outline is a DESIGN document. Define content, interactions, structure, engagement requirements, and objectives using template terminology. Do NOT include code or config syntax.
514
+
515
+ Pause for author review after creating the outline.
516
+
517
+ ## Next Stage
518
+ Once the outline is approved, Stage 3 (Course Building) begins. Call workflow_status to get updated guidance.`;
519
+ }
520
+
521
+ case 3: { // Course Building
522
+ const convertedFiles = listSafe(convertedDir, ['.md']);
523
+ const refList = convertedFiles.length > 0
524
+ ? convertedFiles.map(f => ` - ${path.join(convertedDir, f)}`).join('\n')
525
+ : ' (none found)';
526
+
527
+ return `## What to Do
528
+
529
+ Build slides (course/slides/*.js) and course-config.js from the outline.
530
+
531
+ 1. READ THESE FIRST (essential for slide format, config, interactions, CSS):
532
+ Authoring Guide: ${authoringGuidePath}
533
+ Outline: ${path.join(courseDir, 'COURSE_OUTLINE.md')}
534
+
535
+ 2. REFERENCE MATERIALS (converted source content for slide writing):
536
+ ${refList}
537
+
538
+ 3. SLIDE FILE FORMAT (course/slides/*.js):
539
+ export const meta = { title: 'Slide Title' };
540
+ export default \`<section class="slide">...</section>\`;
541
+ Each file exports meta (title) + default HTML string.
542
+ ⚠️ NO import statements needed. Components, interactions, CSS classes, and icons are all globally available.
543
+ The only valid import is for local assets: import myImg from '../assets/images/photo.png';
544
+
545
+ 4. RULES:
546
+ - ⚠️ NEVER add import statements for components, interactions, CSS, or icons. They are globally available.
547
+ Only import local assets (images, SVGs): import myImage from '../assets/images/photo.png';
548
+ Interactions: const { createMultipleChoiceQuestion } = CourseCode; (destructure from global, NOT import)
549
+ Components: use data-component="tabs" in HTML (declarative, no JS needed)
550
+ - Use css_catalog to discover available CSS classes by category. Lint catches invalid classes with fix suggestions.
551
+ - Use component_catalog and interaction_catalog to discover available components and interactions.
552
+ - Run lint after each batch of file changes. Fix all errors before proceeding.
553
+ - Never modify files in framework/ — all work goes in course/ only.
554
+ - No em-dashes in sentence structure. Use alternative phrasing.
555
+
556
+ Pause for author review after the initial slide build.
557
+
558
+ ## Next Stage
559
+ Once slides and config are built, Stage 4 (Preview & Polish) begins. Start the preview and call workflow_status for updated guidance.`;
560
+ }
561
+
562
+ case 4: { // Preview & Polish
563
+ const convertedFiles = listSafe(convertedDir, ['.md']);
564
+ const refList = convertedFiles.length > 0
565
+ ? convertedFiles.map(f => ` - ${path.join(convertedDir, f)}`).join('\n')
566
+ : '';
567
+
568
+ // Import-specific guidance
569
+ if (_checklist.source === 'powerpoint-import') {
570
+ return `## Imported from PowerPoint
571
+
572
+ This course was imported from a PowerPoint presentation. Each slide is currently a static image. Your job is to enhance it into an interactive course.
573
+
574
+ 1. Ensure the preview server is running (\`coursecode preview\` in a terminal, or AI uses run_command)
575
+ 2. Review slides: screenshot each to understand the content
576
+ 3. Enhancement priorities:
577
+ - **Replace image slides** with interactive HTML — use the extracted text from references/converted/ as source content
578
+ - **Add engagement tracking** — require interaction before advancing (tabs, accordions, etc.)
579
+ - **Insert assessments** — add knowledge checks with multiple-choice, drag-drop, etc.
580
+ - **Group into sections** — organize slides into logical modules with section headers in course-config.js
581
+ - **Customize theme** — update colors in course/theme.css
582
+ ${refList ? `\n4. REFERENCE MATERIALS (extracted text from presentation):\n${refList}\n` : ''}
583
+ 5. RULES:
584
+ - Use css_catalog, component_catalog, interaction_catalog to discover available tools
585
+ - Run lint after changes. Fix all errors before proceeding.
586
+ - Efficient loop: edit files → lint → fix errors → screenshot to verify
587
+
588
+ ## Next Stage
589
+ Once polished and lint passes, Stage 5 (Export Ready) begins.`;
590
+ }
591
+
592
+ return `## What to Do
593
+
594
+ Visually verify and polish the course using the preview server.
595
+
596
+ 1. Ensure the preview server is running (\`coursecode preview\` in a terminal, or AI uses run_command)
597
+ 2. Do NOT open a browser yourself — the MCP has its own headless Chrome
598
+ 3. Workflow (all tools execute instantly via internal headless browser):
599
+ - state — get course structure, current slide, interactions, engagement
600
+ - navigate — go to any slide by ID (get IDs from state)
601
+ - screenshot — capture visual state (accepts slideId to navigate+capture in one call)
602
+ - interact — test interactions with responses
603
+ - export_content — extract all text content to review or compare against outline
604
+ - lint — validate after file changes
605
+
606
+ 4. Efficient iteration loop:
607
+ Edit files → lint → fix errors → screenshot to verify visual result
608
+ Do NOT screenshot every slide sequentially. Target specific slides.
609
+ ${refList ? `\n5. REFERENCE MATERIALS (for verifying content accuracy):\n${refList}\n` : ''}
610
+ Run lint and ensure zero errors before moving to export.
611
+
612
+ ## Next Stage
613
+ Once the course is polished and lint passes, Stage 5 (Export Ready) begins. Call workflow_status for export guidance.`;
614
+ }
615
+
616
+ case 5: // Export Ready
617
+ return `## What to Do
618
+
619
+ Export the finished course for LMS deployment.
620
+
621
+ 1. Run lint one final time to confirm zero errors
622
+ 2. Build with: coursecode_build (format: cmi5, scorm2004, scorm1.2, or lti)
623
+ - cmi5 (default) for modern LMS
624
+ - scorm1.2 for legacy systems
625
+
626
+ The build produces a ZIP file in dist/ ready for LMS upload.`;
627
+
628
+ default:
629
+ return 'Call workflow_status to determine the current authoring stage.';
630
+ }
631
+ }
632
+
633
+ /**
634
+ * Safely list files in a directory matching given extensions.
635
+ */
636
+ function listSafe(dirPath, extensions) {
637
+ try {
638
+ if (!fs.existsSync(dirPath)) return [];
639
+ return fs.readdirSync(dirPath).filter(f =>
640
+ extensions.some(ext => f.toLowerCase().endsWith(ext))
641
+ );
642
+ } catch {
643
+ return [];
644
+ }
645
+ }
646
+
647
+ /**
648
+ * Build the browser architecture and rules section.
649
+ * Critical for preventing agents from using external browsers.
650
+ */
651
+ function buildBrowserRules() {
652
+ return `## Browser Architecture (CRITICAL)
653
+
654
+ The MCP server runs its OWN headless Chrome internally via puppeteer-core.
655
+ All runtime tools (state, navigate, interact, screenshot, reset) execute instantly inside this headless browser.
656
+
657
+ ### Preview Server Ownership
658
+ - The MCP does NOT start or manage the preview server
659
+ - The preview must be started externally: run \`coursecode preview\` in a terminal (human) or via run_command (AI agent)
660
+ - If preview is not running, runtime tools will fail with a clear error message
661
+ - The headless browser auto-reconnects when Vite rebuilds (file changes)
662
+
663
+ ### Navigation API
664
+ - coursecode_state → get slim TOC with slide IDs, current slide, interactions, engagement, lmsState, apiLog, errors, frameworkLogs, consoleLogs
665
+ - coursecode_navigate(slideId) → go to any slide instantly by ID
666
+ - coursecode_screenshot(slideId) → navigate + capture in one call
667
+ - coursecode_interact(interactionId, response) → set response + evaluate in one call
668
+ - NEVER click nav buttons or menu items via browser tools. Use these MCP tools.
669
+
670
+ ### RULES
671
+ - Do NOT use browser_subagent, open_browser_url, or any external browser tool to view the course
672
+ - Do NOT add manual waits or setTimeout delays — all tool calls return instantly
673
+ - Do NOT screenshot every slide sequentially — target specific slides to verify
674
+ - NEVER modify files in framework/ or lib/ — these are the framework internals. All authoring goes in course/ only.
675
+ - Efficient loop: edit files → lint → fix errors → screenshot specific slides to verify
676
+
677
+ ### Customization (all in course/, never in framework/)
678
+ - **CSS overrides**: Edit \`course/theme.css\` — override palette tokens to rebrand (all colors cascade via color-mix). Use framework utility classes first (css_catalog), theme.css only for brand-specific overrides.
679
+ - **Custom components**: Add \`.js\` files to \`course/components/\` — auto-discovered at build time. Use component_catalog for built-in options first.
680
+ - **Custom interactions**: Add \`.js\` files to \`course/interactions/\` — auto-discovered. See \`course/interactions/PLUGIN_GUIDE.md\` for the template.
681
+ - **Custom icons**: Add SVG definitions to \`course/icons.js\` — merged with built-in icons. Use icon_catalog to check existing icons first.`;
682
+ }
683
+
684
+ const __packageRoot = path.dirname(__dirname); // lib/ -> repo root
685
+
686
+ /**
687
+ * Find the course/ directory, trying cwd first then package root.
688
+ * Returns the path to the course/ dir, or null if not found.
689
+ */
690
+ function findCourseDir() {
691
+ const candidates = [process.cwd(), __packageRoot];
692
+ for (const root of candidates) {
693
+ if (fs.existsSync(path.join(root, 'course'))) {
694
+ return path.join(root, 'course');
695
+ }
696
+ if (fs.existsSync(path.join(root, 'template', 'course'))) {
697
+ return path.join(root, 'template', 'course');
698
+ }
699
+ }
700
+ return null;
701
+ }
702
+
703
+ /**
704
+ * Build the full MCP instructions string — called at server startup.
705
+ * Also used by workflow_status to provide refreshed instructions mid-session.
706
+ */
707
+ export async function buildInstructions(port = 4173) {
708
+ const status = await getWorkflowStatus(port);
709
+ const { stageNumber, stage, checklist } = status;
710
+
711
+ const courseDir = findCourseDir();
712
+ const courseInfo = courseDir ? getCourseInfo(courseDir) : { title: null, description: null };
713
+
714
+ // Header: framework identity + course info + stage
715
+ const header = [
716
+ 'CourseCode: AI-assisted e-learning course authoring for LMS (cmi5/SCORM).',
717
+ courseInfo.title ? `Course: "${courseInfo.title}"${courseInfo.description ? ` — ${courseInfo.description}` : ''}` : '',
718
+ `Stage ${stageNumber}/5: ${stage}`,
719
+ ].filter(Boolean).join('\n');
720
+
721
+ // Directory tree (if course exists)
722
+ let tree = '';
723
+ if (courseDir) {
724
+ tree = `\ncourse/\n${buildDirTree(courseDir, '', 2)}`;
725
+ }
726
+
727
+ // Browser rules (always included)
728
+ const browserRules = buildBrowserRules();
729
+
730
+ // Automation warning (when config exists but automation is off)
731
+ let automationWarning = '';
732
+ if (checklist.hasCourseConfig && !checklist.hasAutomationEnabled) {
733
+ automationWarning = `\n\n## ⚠️ Automation Disabled
734
+ MCP runtime tools (state, navigate, interact, screenshot, reset) require \`environment.automation.enabled: true\` in course-config.js. Without it, the headless browser cannot access the course API and these tools will fail.
735
+ You MUST notify the author about this and let them decide whether to enable it. Do not silently modify the config.`;
736
+ }
737
+
738
+ // Stage-specific body
739
+ const stageBody = buildStageInstructions(stageNumber, courseDir || '', checklist);
740
+
741
+ return `${header}${tree}\n\n${browserRules}${automationWarning}\n\n${stageBody}`;
742
+ }
743
+
744
+ /**
745
+ * Get the enhanced workflow status with stage-specific instructions.
746
+ * Called by the coursecode_workflow_status tool handler.
747
+ */
748
+ export async function getWorkflowStatusWithInstructions(port = 4173) {
749
+ const status = await getWorkflowStatus(port);
750
+ const courseDir = findCourseDir() || '';
751
+ status.instructions = buildStageInstructions(status.stageNumber, courseDir, status.checklist);
752
+ return status;
753
+ }