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.
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/bin/cli.js +331 -0
- package/framework/assets/logo-coursecode-black.svg +14 -0
- package/framework/assets/logo-coursecode-white.svg +14 -0
- package/framework/assets/logo-coursecode.svg +14 -0
- package/framework/css/01-base.css +160 -0
- package/framework/css/02-layout.css +499 -0
- package/framework/css/accessibility.css +834 -0
- package/framework/css/components/accordions.css +710 -0
- package/framework/css/components/assessments.css +520 -0
- package/framework/css/components/audio-player.css +570 -0
- package/framework/css/components/badges.css +80 -0
- package/framework/css/components/breadcrumbs.css +87 -0
- package/framework/css/components/buttons.css +707 -0
- package/framework/css/components/callouts.css +1280 -0
- package/framework/css/components/cards.css +475 -0
- package/framework/css/components/carousel.css +193 -0
- package/framework/css/components/checkbox-group.css +123 -0
- package/framework/css/components/checklist.css +203 -0
- package/framework/css/components/collapse.css +96 -0
- package/framework/css/components/comparison.css +33 -0
- package/framework/css/components/content-image.css +36 -0
- package/framework/css/components/document-gallery.css +425 -0
- package/framework/css/components/dropdown.css +115 -0
- package/framework/css/components/embed-frame.css +142 -0
- package/framework/css/components/engagement.css +412 -0
- package/framework/css/components/features.css +35 -0
- package/framework/css/components/flip-cards.css +253 -0
- package/framework/css/components/footer.css +353 -0
- package/framework/css/components/forms.css +294 -0
- package/framework/css/components/hero.css +216 -0
- package/framework/css/components/images.css +528 -0
- package/framework/css/components/interactive-timeline.css +274 -0
- package/framework/css/components/intro-cards.css +30 -0
- package/framework/css/components/lightbox.css +666 -0
- package/framework/css/components/loading.css +65 -0
- package/framework/css/components/modals.css +235 -0
- package/framework/css/components/notifications.css +107 -0
- package/framework/css/components/quote.css +150 -0
- package/framework/css/components/sidebar.css +684 -0
- package/framework/css/components/slide-header.css +52 -0
- package/framework/css/components/spinner.css +62 -0
- package/framework/css/components/stats.css +44 -0
- package/framework/css/components/steps.css +232 -0
- package/framework/css/components/tables.css +90 -0
- package/framework/css/components/tabs.css +347 -0
- package/framework/css/components/timeline.css +154 -0
- package/framework/css/components/toggle.css +95 -0
- package/framework/css/components/tooltip.css +226 -0
- package/framework/css/components/video-player.css +438 -0
- package/framework/css/design-tokens.css +707 -0
- package/framework/css/framework.css +86 -0
- package/framework/css/interactions/accessibility.css +75 -0
- package/framework/css/interactions/base.css +92 -0
- package/framework/css/interactions/drag-drop.css +295 -0
- package/framework/css/interactions/fill-in-the-blank.css +236 -0
- package/framework/css/interactions/hotspots.css +69 -0
- package/framework/css/interactions/index.css +45 -0
- package/framework/css/interactions/interactive-image.css +359 -0
- package/framework/css/interactions/likert.css +126 -0
- package/framework/css/interactions/matching.css +354 -0
- package/framework/css/interactions/numeric-input.css +78 -0
- package/framework/css/interactions/sequencing.css +378 -0
- package/framework/css/interactions/true-false.css +177 -0
- package/framework/css/layouts/article.css +258 -0
- package/framework/css/layouts/base.css +30 -0
- package/framework/css/layouts/canvas.css +38 -0
- package/framework/css/layouts/focused.css +236 -0
- package/framework/css/layouts/index.css +29 -0
- package/framework/css/layouts/presentation.css +191 -0
- package/framework/css/layouts/traditional.css +52 -0
- package/framework/css/responsive.css +439 -0
- package/framework/css/utilities/accessibility-utils.css +59 -0
- package/framework/css/utilities/animations.css +419 -0
- package/framework/css/utilities/borders.css +72 -0
- package/framework/css/utilities/colors.css +76 -0
- package/framework/css/utilities/container.css +46 -0
- package/framework/css/utilities/decorative.css +442 -0
- package/framework/css/utilities/display.css +257 -0
- package/framework/css/utilities/flexbox.css +80 -0
- package/framework/css/utilities/grid.css +69 -0
- package/framework/css/utilities/icons.css +534 -0
- package/framework/css/utilities/lists.css +190 -0
- package/framework/css/utilities/spacing.css +167 -0
- package/framework/css/utilities/tables.css +81 -0
- package/framework/css/utilities/typography.css +159 -0
- package/framework/css/utilities/visibility.css +117 -0
- package/framework/docs/COURSE_AUTHORING_GUIDE.md +1773 -0
- package/framework/docs/COURSE_OUTLINE_GUIDE.md +725 -0
- package/framework/docs/COURSE_OUTLINE_TEMPLATE.md +161 -0
- package/framework/docs/DATA_MODEL.md +409 -0
- package/framework/docs/FRAMEWORK_GUIDE.md +1088 -0
- package/framework/docs/USER_GUIDE.md +583 -0
- package/framework/docs/examples/cloudflare-channel-relay.js +169 -0
- package/framework/docs/examples/cloudflare-data-worker.js +102 -0
- package/framework/docs/examples/cloudflare-error-worker.js +228 -0
- package/framework/index.html +175 -0
- package/framework/js/app/AppActions.js +410 -0
- package/framework/js/app/AppState.js +225 -0
- package/framework/js/app/AppUI.js +616 -0
- package/framework/js/assessment/AssessmentActions.js +615 -0
- package/framework/js/assessment/AssessmentFactory.js +471 -0
- package/framework/js/assessment/AssessmentState.js +322 -0
- package/framework/js/assessment/AssessmentUI.js +451 -0
- package/framework/js/automation/api-engagement.js +196 -0
- package/framework/js/automation/api-interactions.js +167 -0
- package/framework/js/automation/api.js +242 -0
- package/framework/js/automation/index.js +41 -0
- package/framework/js/components/interactions/drag-drop.js +884 -0
- package/framework/js/components/interactions/fill-in.js +535 -0
- package/framework/js/components/interactions/hotspot.js +702 -0
- package/framework/js/components/interactions/interaction-base.js +511 -0
- package/framework/js/components/interactions/likert.js +301 -0
- package/framework/js/components/interactions/matching.js +699 -0
- package/framework/js/components/interactions/multiple-choice.js +377 -0
- package/framework/js/components/interactions/numeric.js +271 -0
- package/framework/js/components/interactions/sequencing.js +423 -0
- package/framework/js/components/interactions/true-false.js +241 -0
- package/framework/js/components/ui-components/accordion.js +442 -0
- package/framework/js/components/ui-components/alert.js +88 -0
- package/framework/js/components/ui-components/audio-player.js +1193 -0
- package/framework/js/components/ui-components/callout.js +121 -0
- package/framework/js/components/ui-components/carousel.js +145 -0
- package/framework/js/components/ui-components/checkbox-group.js +87 -0
- package/framework/js/components/ui-components/checklist.js +40 -0
- package/framework/js/components/ui-components/collapse.js +114 -0
- package/framework/js/components/ui-components/comparison.js +30 -0
- package/framework/js/components/ui-components/conditional-display.js +150 -0
- package/framework/js/components/ui-components/content-image.js +41 -0
- package/framework/js/components/ui-components/dropdown.js +262 -0
- package/framework/js/components/ui-components/embed-frame.js +274 -0
- package/framework/js/components/ui-components/features.js +33 -0
- package/framework/js/components/ui-components/flip-card.js +230 -0
- package/framework/js/components/ui-components/form-validator.js +76 -0
- package/framework/js/components/ui-components/hero.js +49 -0
- package/framework/js/components/ui-components/index.js +12 -0
- package/framework/js/components/ui-components/interactive-image.js +235 -0
- package/framework/js/components/ui-components/interactive-timeline.js +285 -0
- package/framework/js/components/ui-components/intro-cards.js +35 -0
- package/framework/js/components/ui-components/lightbox.js +652 -0
- package/framework/js/components/ui-components/modal.js +386 -0
- package/framework/js/components/ui-components/notifications.js +145 -0
- package/framework/js/components/ui-components/progress.js +88 -0
- package/framework/js/components/ui-components/quote.js +41 -0
- package/framework/js/components/ui-components/stats.js +33 -0
- package/framework/js/components/ui-components/steps.js +41 -0
- package/framework/js/components/ui-components/tabs.js +255 -0
- package/framework/js/components/ui-components/timeline.js +42 -0
- package/framework/js/components/ui-components/toggle-group.js +73 -0
- package/framework/js/components/ui-components/tooltip.js +458 -0
- package/framework/js/components/ui-components/value-display.js +133 -0
- package/framework/js/components/ui-components/video-player.js +686 -0
- package/framework/js/core/component-catalog.js +121 -0
- package/framework/js/core/event-bus.js +178 -0
- package/framework/js/core/interaction-catalog.js +149 -0
- package/framework/js/dev/runtime-linter.js +1725 -0
- package/framework/js/drivers/cmi5-driver.js +768 -0
- package/framework/js/drivers/driver-factory.js +77 -0
- package/framework/js/drivers/driver-interface.js +110 -0
- package/framework/js/drivers/http-driver-base.js +241 -0
- package/framework/js/drivers/lti-driver.js +508 -0
- package/framework/js/drivers/proxy-driver.js +444 -0
- package/framework/js/drivers/scorm-12-driver.js +560 -0
- package/framework/js/drivers/scorm-2004-driver.js +775 -0
- package/framework/js/drivers/scorm-driver-base.js +112 -0
- package/framework/js/engagement/engagement-manager.js +404 -0
- package/framework/js/engagement/engagement-progress.js +191 -0
- package/framework/js/engagement/engagement-trackers.js +215 -0
- package/framework/js/engagement/requirement-strategies.js +268 -0
- package/framework/js/main.js +727 -0
- package/framework/js/managers/accessibility-manager.js +499 -0
- package/framework/js/managers/assessment-manager.js +230 -0
- package/framework/js/managers/audio-manager.js +944 -0
- package/framework/js/managers/comment-manager.js +88 -0
- package/framework/js/managers/flag-manager.js +86 -0
- package/framework/js/managers/interaction-manager.js +254 -0
- package/framework/js/managers/interaction-registry.js +96 -0
- package/framework/js/managers/objective-manager.js +423 -0
- package/framework/js/managers/score-manager.js +441 -0
- package/framework/js/managers/video-manager.js +536 -0
- package/framework/js/navigation/Breadcrumbs.js +234 -0
- package/framework/js/navigation/NavigationActions.js +1132 -0
- package/framework/js/navigation/NavigationState.js +276 -0
- package/framework/js/navigation/NavigationUI.js +574 -0
- package/framework/js/navigation/document-gallery.js +357 -0
- package/framework/js/navigation/navigation-helpers.js +175 -0
- package/framework/js/navigation/navigation-validators.js +174 -0
- package/framework/js/state/index.js +8 -0
- package/framework/js/state/lms-connection.js +482 -0
- package/framework/js/state/lms-error-utils.js +58 -0
- package/framework/js/state/state-commits.js +200 -0
- package/framework/js/state/state-domains.js +86 -0
- package/framework/js/state/state-manager.js +502 -0
- package/framework/js/state/state-validation.js +311 -0
- package/framework/js/state/transaction-log.js +41 -0
- package/framework/js/state/xapi-statement-service.js +325 -0
- package/framework/js/utilities/access-control.js +99 -0
- package/framework/js/utilities/breakpoint-manager.js +315 -0
- package/framework/js/utilities/canvas-slide.js +35 -0
- package/framework/js/utilities/conditional-display.js +388 -0
- package/framework/js/utilities/course-channel.js +214 -0
- package/framework/js/utilities/course-helpers.js +420 -0
- package/framework/js/utilities/data-reporter.js +273 -0
- package/framework/js/utilities/error-reporter.js +313 -0
- package/framework/js/utilities/hotspot-helper.js +341 -0
- package/framework/js/utilities/icons.js +348 -0
- package/framework/js/utilities/logger.js +92 -0
- package/framework/js/utilities/markdown-renderer.js +45 -0
- package/framework/js/utilities/scroll-tracker.js +68 -0
- package/framework/js/utilities/ui-initializer.js +146 -0
- package/framework/js/utilities/utilities.js +293 -0
- package/framework/js/utilities/view-manager.js +227 -0
- package/framework/js/validation/html-validators.js +422 -0
- package/framework/js/validation/scorm-validators.js +438 -0
- package/framework/js/vendor/pipwerks.js +931 -0
- package/framework/scripts/generate-narration.js +629 -0
- package/framework/scripts/tts-providers/azure-provider.js +178 -0
- package/framework/scripts/tts-providers/base-provider.js +81 -0
- package/framework/scripts/tts-providers/deepgram-provider.js +135 -0
- package/framework/scripts/tts-providers/elevenlabs-provider.js +148 -0
- package/framework/scripts/tts-providers/google-provider.js +272 -0
- package/framework/scripts/tts-providers/index.js +158 -0
- package/framework/scripts/tts-providers/openai-provider.js +143 -0
- package/framework/version.json +63 -0
- package/lib/authoring-api.js +919 -0
- package/lib/build-linter.js +450 -0
- package/lib/build-packaging.js +186 -0
- package/lib/build.js +88 -0
- package/lib/cloud.js +691 -0
- package/lib/convert.js +341 -0
- package/lib/course-parser.js +936 -0
- package/lib/course-writer.js +258 -0
- package/lib/create.js +248 -0
- package/lib/css-index.js +237 -0
- package/lib/dev.js +51 -0
- package/lib/export-content.js +1246 -0
- package/lib/headless-browser.js +413 -0
- package/lib/import.js +377 -0
- package/lib/index.js +80 -0
- package/lib/info.js +79 -0
- package/lib/interaction-formatters.js +568 -0
- package/lib/manifest/cmi5-manifest.js +63 -0
- package/lib/manifest/lti-tool-config.js +53 -0
- package/lib/manifest/manifest-factory.js +99 -0
- package/lib/manifest/scorm-12-manifest.js +61 -0
- package/lib/manifest/scorm-2004-manifest.js +94 -0
- package/lib/manifest/scorm-proxy-manifest.js +104 -0
- package/lib/manifest-parser.js +96 -0
- package/lib/mcp-prompts.js +753 -0
- package/lib/mcp-server.js +316 -0
- package/lib/narration.js +53 -0
- package/lib/pdf-structure.js +142 -0
- package/lib/preview-export.js +231 -0
- package/lib/preview-routes-api.js +662 -0
- package/lib/preview-routes-editing.js +159 -0
- package/lib/preview-routes-lms.js +230 -0
- package/lib/preview-server.js +564 -0
- package/lib/project-utils.js +269 -0
- package/lib/proxy-templates/proxy.html +68 -0
- package/lib/proxy-templates/scorm-bridge.js +112 -0
- package/lib/scaffold.js +193 -0
- package/lib/schema-extractor.js +361 -0
- package/lib/slide-source-editor.js +586 -0
- package/lib/stub-player/app-viewer.js +195 -0
- package/lib/stub-player/app.js +370 -0
- package/lib/stub-player/catalog-panel.js +312 -0
- package/lib/stub-player/config-panel.js +1303 -0
- package/lib/stub-player/content-generator.js +586 -0
- package/lib/stub-player/content-viewer.js +173 -0
- package/lib/stub-player/debug-panel.js +420 -0
- package/lib/stub-player/edit-mode.js +922 -0
- package/lib/stub-player/edit-utils.js +400 -0
- package/lib/stub-player/header-bar.js +354 -0
- package/lib/stub-player/interaction-editor.js +210 -0
- package/lib/stub-player/interactions-panel.js +565 -0
- package/lib/stub-player/lms-api.js +1094 -0
- package/lib/stub-player/login-screen.js +74 -0
- package/lib/stub-player/outline-mode.js +689 -0
- package/lib/stub-player/styles/_assessments-panel.css +245 -0
- package/lib/stub-player/styles/_base.css +89 -0
- package/lib/stub-player/styles/_catalog-icons.css +96 -0
- package/lib/stub-player/styles/_catalog-panel.css +291 -0
- package/lib/stub-player/styles/_config-panel.css +636 -0
- package/lib/stub-player/styles/_content-viewer.css +834 -0
- package/lib/stub-player/styles/_debug-panel.css +576 -0
- package/lib/stub-player/styles/_edit-mode.css +128 -0
- package/lib/stub-player/styles/_header-bar.css +343 -0
- package/lib/stub-player/styles/_interaction-editor.css +140 -0
- package/lib/stub-player/styles/_interactions-panel.css +1038 -0
- package/lib/stub-player/styles/_login-screen.css +102 -0
- package/lib/stub-player/styles/_outline-mode.css +752 -0
- package/lib/stub-player/styles.css +15 -0
- package/lib/stub-player.js +160 -0
- package/lib/test-data-reporting.js +176 -0
- package/lib/test-error-reporting.js +146 -0
- package/lib/token.js +86 -0
- package/lib/upgrade.js +257 -0
- package/lib/validation-rules.js +517 -0
- package/lib/vite-plugin-content-discovery.js +296 -0
- package/package.json +108 -0
- package/schemas/XMLSchema.dtd +402 -0
- package/schemas/adlcp_v1p3.xsd +111 -0
- package/schemas/adlnav_v1p3.xsd +61 -0
- package/schemas/adlseq_v1p3.xsd +93 -0
- package/schemas/common/anyElement.xsd +27 -0
- package/schemas/common/dataTypes.xsd +138 -0
- package/schemas/common/elementNames.xsd +767 -0
- package/schemas/common/elementTypes.xsd +786 -0
- package/schemas/common/rootElement.xsd +31 -0
- package/schemas/common/vocabTypes.xsd +345 -0
- package/schemas/common/vocabValues.xsd +257 -0
- package/schemas/datatypes.dtd +203 -0
- package/schemas/ims_xml.xsd +35 -0
- package/schemas/imscp_v1p1.xsd +368 -0
- package/schemas/imsss_v1p0.xsd +67 -0
- package/schemas/imsss_v1p0auxresource.xsd +19 -0
- package/schemas/imsss_v1p0control.xsd +20 -0
- package/schemas/imsss_v1p0delivery.xsd +17 -0
- package/schemas/imsss_v1p0limit.xsd +47 -0
- package/schemas/imsss_v1p0objective.xsd +67 -0
- package/schemas/imsss_v1p0random.xsd +16 -0
- package/schemas/imsss_v1p0rollup.xsd +46 -0
- package/schemas/imsss_v1p0seqrule.xsd +108 -0
- package/schemas/imsss_v1p0util.xsd +94 -0
- package/schemas/license.txt +17 -0
- package/schemas/lom.xsd +102 -0
- package/schemas/lomCustom.xsd +62 -0
- package/schemas/lomLoose.xsd +62 -0
- package/schemas/lomStrict.xsd +62 -0
- package/schemas/xml.xsd +81 -0
- package/template/.env.example +92 -0
- package/template/course/assets/audio/example-intro.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--compact-player.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--demo-modal.mp3 +0 -0
- package/template/course/assets/audio/example-ui-demo--full-player.mp3 +0 -0
- package/template/course/assets/docs/example_md_1.md +39 -0
- package/template/course/assets/docs/example_md_2.md +41 -0
- package/template/course/assets/docs/example_pdf_1_thumbnail.png +0 -0
- package/template/course/assets/docs/example_pdf_2.pdf +0 -0
- package/template/course/assets/images/course-architecture.svg +36 -0
- package/template/course/assets/images/logo.svg +14 -0
- package/template/course/assets/widgets/counter-demo.html +190 -0
- package/template/course/assets/widgets/gravity-painter.html +384 -0
- package/template/course/course-config.js +539 -0
- package/template/course/icons.js +19 -0
- package/template/course/interactions/PLUGIN_GUIDE.md +97 -0
- package/template/course/slides/example-course-structure.js +138 -0
- package/template/course/slides/example-final-exam.js +144 -0
- package/template/course/slides/example-finishing.js +127 -0
- package/template/course/slides/example-interactions-showcase.js +615 -0
- package/template/course/slides/example-preview-tour.js +129 -0
- package/template/course/slides/example-remedial.js +143 -0
- package/template/course/slides/example-summary.js +103 -0
- package/template/course/slides/example-ui-showcase.js +1805 -0
- package/template/course/slides/example-welcome.js +123 -0
- package/template/course/slides/example-workflow.js +140 -0
- package/template/course/theme.css +165 -0
- package/template/eslint.config.js +47 -0
- package/template/package.json +28 -0
- package/template/vite.config.js +339 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server for CourseCode
|
|
5
|
+
*
|
|
6
|
+
* Standalone Model Context Protocol server with persistent headless browser.
|
|
7
|
+
* Runtime tools (state, navigate, interact, screenshot) execute directly in
|
|
8
|
+
* a headless Chrome via puppeteer. Authoring tools work on the filesystem.
|
|
9
|
+
*
|
|
10
|
+
* The MCP never starts its own preview server — it connects to one that's
|
|
11
|
+
* already running (started by the human or via `coursecode preview`).
|
|
12
|
+
*
|
|
13
|
+
* Tool definitions and instructions live in mcp-prompts.js.
|
|
14
|
+
*
|
|
15
|
+
* Usage: coursecode mcp [--port 4173]
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
19
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
20
|
+
import {
|
|
21
|
+
CallToolRequestSchema,
|
|
22
|
+
ListToolsRequestSchema
|
|
23
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
24
|
+
import {
|
|
25
|
+
getCssCatalog,
|
|
26
|
+
getPreviewStatus,
|
|
27
|
+
getComponentCatalog,
|
|
28
|
+
getInteractionCatalog,
|
|
29
|
+
getIconCatalog,
|
|
30
|
+
lintCourse,
|
|
31
|
+
buildCourse
|
|
32
|
+
} from './authoring-api.js';
|
|
33
|
+
import { getContentExport } from './export-content.js';
|
|
34
|
+
import headless from './headless-browser.js';
|
|
35
|
+
import { TOOLS, buildInstructions, getWorkflowStatusWithInstructions } from './mcp-prompts.js';
|
|
36
|
+
|
|
37
|
+
const DEFAULT_PORT = 4173;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Ensure headless browser is connected to preview server.
|
|
41
|
+
* Fails fast if preview is not running — humans own the preview lifecycle.
|
|
42
|
+
*/
|
|
43
|
+
async function ensureHeadless(port) {
|
|
44
|
+
if (!headless.isRunning()) {
|
|
45
|
+
const status = await getPreviewStatus(port);
|
|
46
|
+
if (!status.running) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
'Preview server not running. Start it first:\n' +
|
|
49
|
+
' • Human: run `coursecode preview` in a terminal\n' +
|
|
50
|
+
' • AI agent: use run_command to execute `npm run preview`\n' +
|
|
51
|
+
'Then retry this tool call.'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
await headless.launch(port);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create and run the MCP server
|
|
60
|
+
*/
|
|
61
|
+
export async function startMcpServer(options = {}) {
|
|
62
|
+
const port = options.port || DEFAULT_PORT;
|
|
63
|
+
|
|
64
|
+
// Build dynamic instructions for current authoring stage
|
|
65
|
+
const instructions = await buildInstructions(port);
|
|
66
|
+
|
|
67
|
+
const server = new Server(
|
|
68
|
+
{
|
|
69
|
+
name: 'coursecode',
|
|
70
|
+
version: '2.0.0',
|
|
71
|
+
instructions
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
capabilities: {
|
|
75
|
+
tools: {}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// ========================================
|
|
81
|
+
// Tool Request Handlers
|
|
82
|
+
// ========================================
|
|
83
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
84
|
+
return { tools: TOOLS };
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
88
|
+
const { name, arguments: args } = request.params;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
let result;
|
|
92
|
+
|
|
93
|
+
switch (name) {
|
|
94
|
+
// === Runtime tools (headless browser) ===
|
|
95
|
+
case 'coursecode_state':
|
|
96
|
+
await ensureHeadless(port);
|
|
97
|
+
result = await headless.evaluate(() => {
|
|
98
|
+
const api = window.CourseCodeAutomation;
|
|
99
|
+
return {
|
|
100
|
+
slide: api.getCurrentSlide(),
|
|
101
|
+
toc: api.getToc(),
|
|
102
|
+
interactions: api.listInteractions(),
|
|
103
|
+
engagement: api.getEngagementState(),
|
|
104
|
+
frameworkLogs: api.getFrameworkLogs(),
|
|
105
|
+
lmsState: api.getLmsState()
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
// Read last 20 API log entries from parent window (stub player diagnostic)
|
|
109
|
+
result.apiLog = await headless.evaluateParent(() => {
|
|
110
|
+
return window._stubPlayerState?.apiLog?.slice(0, 20) || [];
|
|
111
|
+
});
|
|
112
|
+
// Read error log from parent window (stub player diagnostic)
|
|
113
|
+
result.errors = await headless.evaluateParent(() => {
|
|
114
|
+
return window._stubPlayerState?.errorLog || [];
|
|
115
|
+
});
|
|
116
|
+
// Append console errors/warnings captured from the page
|
|
117
|
+
result.consoleLogs = headless.getConsoleLogs();
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case 'coursecode_navigate':
|
|
121
|
+
if (!args?.slideId) throw new Error('slideId is required');
|
|
122
|
+
await ensureHeadless(port);
|
|
123
|
+
// Apply accessibility preferences before navigation
|
|
124
|
+
if (args.theme || args.highContrast !== undefined) {
|
|
125
|
+
await headless.evaluate(({ theme, highContrast }) => {
|
|
126
|
+
const api = window.CourseCodeAutomation;
|
|
127
|
+
if (theme) api.setAccessibilityPreference('theme', theme);
|
|
128
|
+
if (highContrast !== undefined) api.setAccessibilityPreference('highContrast', highContrast);
|
|
129
|
+
}, { theme: args.theme, highContrast: args.highContrast });
|
|
130
|
+
// Allow DOM to update after preference change
|
|
131
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
132
|
+
}
|
|
133
|
+
// Validate slide ID exists before navigating (avoids EventBus error cascades)
|
|
134
|
+
{
|
|
135
|
+
const validSlide = await headless.evaluate((slideId) => {
|
|
136
|
+
const toc = window.CourseCodeAutomation.getToc();
|
|
137
|
+
return toc.some(item => item.id === slideId);
|
|
138
|
+
}, args.slideId);
|
|
139
|
+
if (!validSlide) {
|
|
140
|
+
throw new Error(`Slide "${args.slideId}" not found. Use coursecode_state to get valid slide IDs.`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
await headless.evaluate((slideId) => {
|
|
144
|
+
window.CourseCodeAutomation.goToSlide(slideId);
|
|
145
|
+
}, args.slideId);
|
|
146
|
+
// State updates asynchronously after navigation
|
|
147
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
148
|
+
result = await headless.evaluate(() => {
|
|
149
|
+
const api = window.CourseCodeAutomation;
|
|
150
|
+
return {
|
|
151
|
+
slide: api.getCurrentSlide(),
|
|
152
|
+
interactions: api.listInteractions(),
|
|
153
|
+
engagement: api.getEngagementState(),
|
|
154
|
+
accessibility: api.getAccessibilityState()
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
break;
|
|
158
|
+
|
|
159
|
+
case 'coursecode_interact':
|
|
160
|
+
if (!args?.interactionId) throw new Error('interactionId is required');
|
|
161
|
+
if (args.response === undefined) throw new Error('response is required');
|
|
162
|
+
await ensureHeadless(port);
|
|
163
|
+
result = await headless.evaluate(({ interactionId, response }) => {
|
|
164
|
+
const api = window.CourseCodeAutomation;
|
|
165
|
+
api.setResponse(interactionId, response);
|
|
166
|
+
const checkResult = api.checkAnswer(interactionId);
|
|
167
|
+
return {
|
|
168
|
+
...checkResult,
|
|
169
|
+
state: {
|
|
170
|
+
slide: api.getCurrentSlide(),
|
|
171
|
+
interactions: api.listInteractions()
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}, { interactionId: args.interactionId, response: args.response });
|
|
175
|
+
break;
|
|
176
|
+
|
|
177
|
+
case 'coursecode_reset':
|
|
178
|
+
await ensureHeadless(port);
|
|
179
|
+
await headless.evaluate(() => {
|
|
180
|
+
localStorage.clear();
|
|
181
|
+
});
|
|
182
|
+
// Full browser restart to ensure clean state
|
|
183
|
+
await headless.shutdown();
|
|
184
|
+
await headless.launch(port);
|
|
185
|
+
result = { reset: true };
|
|
186
|
+
break;
|
|
187
|
+
|
|
188
|
+
case 'coursecode_screenshot':
|
|
189
|
+
await ensureHeadless(port);
|
|
190
|
+
// Validate slide ID if provided (same guard as navigate)
|
|
191
|
+
if (args?.slideId) {
|
|
192
|
+
const validSlide = await headless.evaluate((slideId) => {
|
|
193
|
+
const toc = window.CourseCodeAutomation.getToc();
|
|
194
|
+
return toc.some(item => item.id === slideId);
|
|
195
|
+
}, args.slideId);
|
|
196
|
+
if (!validSlide) {
|
|
197
|
+
throw new Error(`Slide "${args.slideId}" not found. Use coursecode_state to get valid slide IDs.`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
result = await headless.screenshot({
|
|
201
|
+
slideId: args?.slideId,
|
|
202
|
+
fullPage: args?.fullPage,
|
|
203
|
+
detailed: args?.detailed,
|
|
204
|
+
scrollY: args?.scrollY
|
|
205
|
+
});
|
|
206
|
+
return {
|
|
207
|
+
content: [{
|
|
208
|
+
type: 'image',
|
|
209
|
+
data: result.data,
|
|
210
|
+
mimeType: result.mimeType
|
|
211
|
+
}]
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// === Workflow & build tools ===
|
|
215
|
+
case 'coursecode_workflow_status':
|
|
216
|
+
result = await getWorkflowStatusWithInstructions(port);
|
|
217
|
+
break;
|
|
218
|
+
|
|
219
|
+
case 'coursecode_build':
|
|
220
|
+
result = await buildCourse(args || {});
|
|
221
|
+
break;
|
|
222
|
+
|
|
223
|
+
// === Catalog & validation tools (filesystem, no preview needed) ===
|
|
224
|
+
case 'coursecode_css_catalog':
|
|
225
|
+
result = getCssCatalog(args?.category);
|
|
226
|
+
break;
|
|
227
|
+
|
|
228
|
+
case 'coursecode_component_catalog':
|
|
229
|
+
result = getComponentCatalog(args?.type);
|
|
230
|
+
break;
|
|
231
|
+
case 'coursecode_interaction_catalog':
|
|
232
|
+
result = getInteractionCatalog(args?.type);
|
|
233
|
+
break;
|
|
234
|
+
case 'coursecode_icon_catalog':
|
|
235
|
+
result = getIconCatalog(args?.name);
|
|
236
|
+
break;
|
|
237
|
+
case 'coursecode_lint':
|
|
238
|
+
result = await lintCourse();
|
|
239
|
+
break;
|
|
240
|
+
|
|
241
|
+
case 'coursecode_export_content':
|
|
242
|
+
result = await getContentExport(args || {});
|
|
243
|
+
if (result === null) {
|
|
244
|
+
throw new Error('Failed to export content. Ensure course-config.js exists in course/');
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
content: [{
|
|
248
|
+
type: 'text',
|
|
249
|
+
text: result
|
|
250
|
+
}]
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
default:
|
|
254
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
content: [{
|
|
259
|
+
type: 'text',
|
|
260
|
+
text: JSON.stringify(result, null, 2)
|
|
261
|
+
}]
|
|
262
|
+
};
|
|
263
|
+
} catch (error) {
|
|
264
|
+
return {
|
|
265
|
+
content: [{
|
|
266
|
+
type: 'text',
|
|
267
|
+
text: `Error: ${error.message}`
|
|
268
|
+
}],
|
|
269
|
+
isError: true
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Graceful shutdown — clean up headless browser only.
|
|
275
|
+
// browser.close() tears down the entire Chrome process tree (parent + all
|
|
276
|
+
// child processes like GPU, renderer, utility). No manual PID hunting needed.
|
|
277
|
+
let cleaningUp = false;
|
|
278
|
+
const cleanup = async () => {
|
|
279
|
+
if (cleaningUp) return;
|
|
280
|
+
cleaningUp = true;
|
|
281
|
+
if (headless.isRunning()) {
|
|
282
|
+
await headless.shutdown();
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
process.on('SIGINT', async () => { await cleanup(); process.exit(0); });
|
|
287
|
+
process.on('SIGTERM', async () => { await cleanup(); process.exit(0); });
|
|
288
|
+
|
|
289
|
+
// Detect MCP host disconnect — when the IDE/editor drops the stdio pipe,
|
|
290
|
+
// stdin emits 'close'. This is the primary cleanup signal since SIGINT/SIGTERM
|
|
291
|
+
// are NOT sent when the host simply closes the transport.
|
|
292
|
+
process.stdin.on('close', async () => { await cleanup(); process.exit(0); });
|
|
293
|
+
|
|
294
|
+
process.on('uncaughtException', async (err) => {
|
|
295
|
+
process.stderr.write(`MCP uncaught exception: ${err.message}\n`);
|
|
296
|
+
await cleanup();
|
|
297
|
+
process.exit(1);
|
|
298
|
+
});
|
|
299
|
+
process.on('unhandledRejection', async (err) => {
|
|
300
|
+
process.stderr.write(`MCP unhandled rejection: ${err}\n`);
|
|
301
|
+
await cleanup();
|
|
302
|
+
process.exit(1);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Start the server
|
|
306
|
+
const transport = new StdioServerTransport();
|
|
307
|
+
await server.connect(transport);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// CLI entry point
|
|
311
|
+
if (process.argv[1]?.endsWith('mcp-server.js')) {
|
|
312
|
+
const args = process.argv.slice(2);
|
|
313
|
+
const portArg = args.find(a => a.startsWith('--port='));
|
|
314
|
+
const port = portArg ? parseInt(portArg.split('=')[1], 10) : DEFAULT_PORT;
|
|
315
|
+
startMcpServer({ port });
|
|
316
|
+
}
|
package/lib/narration.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narration command - generate audio narration from text
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import { validateProject } from './project-utils.js';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export async function narration(options = {}) {
|
|
12
|
+
validateProject();
|
|
13
|
+
|
|
14
|
+
const scriptPath = path.join(process.cwd(), 'framework', 'scripts', 'generate-narration.js');
|
|
15
|
+
|
|
16
|
+
if (!fs.existsSync(scriptPath)) {
|
|
17
|
+
console.error(`
|
|
18
|
+
❌ Narration script not found at: framework/scripts/generate-narration.js
|
|
19
|
+
|
|
20
|
+
Make sure your framework is up to date:
|
|
21
|
+
scorm upgrade
|
|
22
|
+
`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(`
|
|
27
|
+
🎙️ Generating audio narration...
|
|
28
|
+
`);
|
|
29
|
+
|
|
30
|
+
// Build args
|
|
31
|
+
const args = [scriptPath];
|
|
32
|
+
if (options.force) args.push('--force');
|
|
33
|
+
if (options.slide) args.push('--slide', options.slide);
|
|
34
|
+
if (options.dryRun) args.push('--dry-run');
|
|
35
|
+
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const child = spawn('node', args, {
|
|
38
|
+
cwd: process.cwd(),
|
|
39
|
+
stdio: 'inherit',
|
|
40
|
+
shell: true
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
child.on('close', (code) => {
|
|
44
|
+
if (code === 0) {
|
|
45
|
+
resolve();
|
|
46
|
+
} else {
|
|
47
|
+
reject(new Error(`Narration generation failed with code ${code}`));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
child.on('error', reject);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import PDFParser from 'pdf2json';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Custom PDF Structure Parser
|
|
6
|
+
* Uses pdf2json to extract text with layout information, then applies heuristics
|
|
7
|
+
* to identify headers and reconstruct document structure.
|
|
8
|
+
*/
|
|
9
|
+
export function parsePdfStructure(filePath) {
|
|
10
|
+
const pdfParser = new PDFParser();
|
|
11
|
+
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
pdfParser.on('pdfParser_dataError', errData => reject(errData.parserError));
|
|
14
|
+
pdfParser.on('pdfParser_dataReady', pdfData => {
|
|
15
|
+
try {
|
|
16
|
+
const structure = processPdfData(pdfData);
|
|
17
|
+
resolve(structure);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
reject(err);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
pdfParser.loadPDF(filePath);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function processPdfData(pdfData) {
|
|
28
|
+
const pages = pdfData.Pages || [];
|
|
29
|
+
let allTextBlocks = [];
|
|
30
|
+
|
|
31
|
+
// 1. Flatten all text blocks across pages
|
|
32
|
+
pages.forEach((page, pageIndex) => {
|
|
33
|
+
const texts = page.Texts || [];
|
|
34
|
+
texts.forEach(text => {
|
|
35
|
+
// text.R is an array of runs (usually 1)
|
|
36
|
+
// text.x, text.y are position units (approx 1/25th of an inch?)
|
|
37
|
+
|
|
38
|
+
const content = text.R.map(run => decodeURIComponent(run.T)).join('');
|
|
39
|
+
|
|
40
|
+
// TS = [fontId, fontSize, isBold, isItalic]
|
|
41
|
+
// We need to parse TS from the first run
|
|
42
|
+
const run = text.R[0];
|
|
43
|
+
const fontSize = run?.TS?.[1] || 12;
|
|
44
|
+
const isBold = run?.TS?.[2] === 1;
|
|
45
|
+
|
|
46
|
+
if (content.trim()) {
|
|
47
|
+
allTextBlocks.push({
|
|
48
|
+
text: content,
|
|
49
|
+
x: text.x,
|
|
50
|
+
y: text.y + (pageIndex * 100), // Global Y including page offset (heuristic 100 units per page)
|
|
51
|
+
w: text.w,
|
|
52
|
+
fontSize,
|
|
53
|
+
isBold,
|
|
54
|
+
page: pageIndex + 1
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// 2. Determine "Body Text" font size (most common size)
|
|
61
|
+
const sizeCounts = {};
|
|
62
|
+
allTextBlocks.forEach(block => {
|
|
63
|
+
const size = Math.round(block.fontSize * 10) / 10;
|
|
64
|
+
sizeCounts[size] = (sizeCounts[size] || 0) + 1;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
let bodySize = 0;
|
|
68
|
+
let maxCount = 0;
|
|
69
|
+
for (const [size, count] of Object.entries(sizeCounts)) {
|
|
70
|
+
if (count > maxCount) {
|
|
71
|
+
maxCount = count;
|
|
72
|
+
bodySize = parseFloat(size);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 3. Sort by Page -> Y -> X
|
|
77
|
+
allTextBlocks.sort((a, b) => {
|
|
78
|
+
if (a.page !== b.page) return a.page - b.page;
|
|
79
|
+
if (Math.abs(a.y - b.y) > 0.5) return a.y - b.y; // Roughly same line
|
|
80
|
+
return a.x - b.x;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// 4. Cluster into elements
|
|
84
|
+
const elements = [];
|
|
85
|
+
let currentElement = null;
|
|
86
|
+
|
|
87
|
+
allTextBlocks.forEach((block, index) => {
|
|
88
|
+
const isHeader = block.fontSize > bodySize * 1.1 || (block.isBold && block.fontSize >= bodySize);
|
|
89
|
+
const headingLevel = isHeader ? calculateHeaderLevel(block.fontSize, bodySize) : 0;
|
|
90
|
+
const type = isHeader ? `h${headingLevel}` : 'p';
|
|
91
|
+
|
|
92
|
+
// Check proximity to previous block
|
|
93
|
+
const prevBlock = allTextBlocks[index - 1];
|
|
94
|
+
|
|
95
|
+
// Vertical gap check (heuristic)
|
|
96
|
+
// If gap is small, it's likely the same paragraph
|
|
97
|
+
// If gap is large, it's a new paragraph
|
|
98
|
+
// If type changed (header <-> p), it's a new element
|
|
99
|
+
|
|
100
|
+
let isSameElement = false;
|
|
101
|
+
if (currentElement && currentElement.type === type && prevBlock) {
|
|
102
|
+
const verticalGap = block.y - prevBlock.y;
|
|
103
|
+
// Line height heuristic: adjacent lines are usually < 1.5 units apart in pdf2json coords
|
|
104
|
+
if (verticalGap < 2 && verticalGap > -0.5) {
|
|
105
|
+
isSameElement = true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isSameElement) {
|
|
110
|
+
currentElement.text += ' ' + decode(block.text);
|
|
111
|
+
} else {
|
|
112
|
+
if (currentElement) elements.push(currentElement);
|
|
113
|
+
currentElement = {
|
|
114
|
+
type,
|
|
115
|
+
text: decode(block.text),
|
|
116
|
+
page: block.page,
|
|
117
|
+
metadata: {
|
|
118
|
+
fontSize: block.fontSize,
|
|
119
|
+
isBold: block.isBold
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (currentElement) elements.push(currentElement);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
metadata: pdfData.Meta,
|
|
129
|
+
elements
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function calculateHeaderLevel(size, bodySize) {
|
|
134
|
+
const ratio = size / bodySize;
|
|
135
|
+
if (ratio > 1.5) return 1;
|
|
136
|
+
if (ratio > 1.2) return 2;
|
|
137
|
+
return 3;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function decode(str) {
|
|
141
|
+
return str.replace(/\s+/g, ' ').trim();
|
|
142
|
+
}
|