@vue-skuilder/cli 0.1.11-9 → 0.1.12

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/CLAUDE.md CHANGED
@@ -16,9 +16,11 @@ npx @vue-skuilder/cli pack my-course
16
16
  ```
17
17
 
18
18
  ## Build System
19
- - **TypeScript**: Direct compilation to CommonJS in `dist/`
19
+ - **TypeScript**: Direct compilation to ES module in `dist/`
20
20
  - **Binary Entry**: `dist/cli.js` with executable shebang
21
21
  - **Module Type**: ES module with `.js` extension
22
+ - **Asset Embedding**: Templates and UI source files embedded during build
23
+ - **Complex Build**: Multi-step process with template copying and path adjustments
22
24
 
23
25
  ## CLI Commands
24
26
 
@@ -35,8 +37,14 @@ npx @vue-skuilder/cli pack my-course
35
37
  - `inquirer` - Interactive prompts and wizards
36
38
  - `chalk` - Terminal color output
37
39
 
38
- ### Vue-Skuilder Integration
40
+ ### Vue-Skuilder Integration (All Workspace Packages)
41
+ - `@vue-skuilder/common` - Shared types and utilities
42
+ - `@vue-skuilder/common-ui` - Base UI components
43
+ - `@vue-skuilder/courseware` - Course content types and question implementations
39
44
  - `@vue-skuilder/db` - Database utilities for course packaging
45
+ - `@vue-skuilder/edit-ui` - Course editing interface components
46
+ - `@vue-skuilder/express` - Express server for API endpoints
47
+ - `@vue-skuilder/mcp` - Model Context Protocol server integration
40
48
  - `@vue-skuilder/standalone-ui` - Standalone player for packaged courses
41
49
 
42
50
  ### File Management
@@ -63,11 +71,12 @@ npx @vue-skuilder/cli pack my-course
63
71
  - **Optimization**: Minifies and optimizes course assets
64
72
  - **Packaging**: Creates deployable course bundle
65
73
 
66
- ### MCP Server (`mcp-server.ts`)
67
- - **Protocol Implementation**: Full MCP server with 14 resources, 4 tools, 2 prompts
74
+ ### MCP Server Integration
75
+ - **Protocol Implementation**: Leverages `@vue-skuilder/mcp` package for full server functionality
68
76
  - **Course Access**: Direct CourseDBInterface integration for real-time data
69
- - **Content Generation**: AI-powered card creation and content authoring
77
+ - **Content Generation**: AI-powered card creation and content authoring via Claude Code
70
78
  - **ELO Integration**: Native support for difficulty rating system
79
+ - **Studio Command**: Launches MCP server alongside development environment
71
80
 
72
81
  ## Utilities
73
82
 
package/README.md CHANGED
@@ -73,10 +73,11 @@ skuilder init biology-course \
73
73
  Studio mode provides a complete visual editing environment for static courses:
74
74
 
75
75
  ```bash
76
- skuilder studio # Launch in current directory
77
- skuilder studio ./my-course # Launch against specific packed course directory
78
- skuilder studio --port 6000 # Use custom CouchDB port
79
- skuilder studio --no-browser # Don't auto-open browser
76
+ skuilder studio # Launch in current directory
77
+ skuilder studio ./my-course # Launch against specific skuilder app w/ packed course data
78
+ skuilder studio ./some/manifest.json # Launch against a specific packed course on disk
79
+ skuilder studio --port 6000 # Use custom CouchDB port
80
+ skuilder studio --no-browser # Don't auto-open browser
80
81
  ```
81
82
 
82
83
  #### What Studio Mode Does
@@ -1 +1 @@
1
- {"version":3,"file":"studio.d.ts","sourceRoot":"","sources":["../../src/commands/studio.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoDpC,wBAAgB,mBAAmB,IAAI,OAAO,CAoC7C"}
1
+ {"version":3,"file":"studio.d.ts","sourceRoot":"","sources":["../../src/commands/studio.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoDpC,wBAAgB,mBAAmB,IAAI,OAAO,CAuC7C"}
@@ -39,7 +39,7 @@ async function findAvailablePort(startPort) {
39
39
  export function createStudioCommand() {
40
40
  return new Command('studio')
41
41
  .description('Launch studio mode: a complete course editing environment with CouchDB, Express API, and web editor')
42
- .argument('[coursePath]', 'Path to static course directory', '.')
42
+ .argument('[coursePath]', 'Path to static course directory or manifest file', '.')
43
43
  .option('-p, --port <port>', 'CouchDB port for studio session', '5985')
44
44
  .option('--no-browser', 'Skip automatic browser launch')
45
45
  .action(launchStudio)
@@ -59,8 +59,11 @@ Studio Mode creates a full editing environment for static courses:
59
59
 
60
60
  Requirements:
61
61
  • Docker (for CouchDB instance)
62
- Valid static course project (with package.json)
63
- Course data in public/static-courses/ directory
62
+ - either
63
+ Valid static course project (with package.json)
64
+ • Course data in public/static-courses/ directory
65
+ - OR
66
+ - a valid mainfest.json
64
67
 
65
68
  Example:
66
69
  skuilder studio # Launch in current directory
@@ -75,74 +78,54 @@ let studioUIServer = null;
75
78
  async function launchStudio(coursePath, options) {
76
79
  try {
77
80
  console.log(chalk.cyan(`🎨 Launching Skuilder Studio...`));
78
- // Phase 2: Course Detection & Validation
81
+ // Input validation and course detection
79
82
  const resolvedPath = path.resolve(coursePath);
80
- console.log(chalk.gray(`📁 Course path: ${resolvedPath}`));
81
- if (!(await validateSuiCourse(resolvedPath))) {
82
- console.error(chalk.red(`❌ Not a valid standalone-ui course directory`));
83
- console.log(chalk.yellow(`💡 Studio mode requires a vue-skuilder course with:`));
84
- console.log(chalk.yellow(` - package.json with @vue-skuilder/* dependencies`));
85
- console.log(chalk.yellow(` - static-data/ OR public/static-courses/ directory with course content`));
86
- process.exit(1);
83
+ console.log(chalk.gray(`📁 Input path: ${resolvedPath}`));
84
+ let isManifestMode = false;
85
+ let actualCoursePath = resolvedPath;
86
+ // Check if input is a manifest file
87
+ const manifestValidation = await validateManifestCourse(resolvedPath);
88
+ if (manifestValidation.isValid) {
89
+ console.log(chalk.green(`✅ Valid course manifest detected`));
90
+ isManifestMode = true;
91
+ actualCoursePath = manifestValidation.coursePath;
92
+ console.log(chalk.gray(`📁 Course data path: ${actualCoursePath}`));
87
93
  }
88
- console.log(chalk.green(`✅ Valid standalone-ui course detected`));
89
- // Phase 0.5: Hash questions directory to determine studio-ui build needs
90
- console.log(chalk.cyan(`🔍 Analyzing local question types...`));
91
- let questionsHash;
92
- let studioUIPath;
93
- try {
94
- questionsHash = await withStudioBuildErrorHandling(() => hashQuestionsDirectory(resolvedPath), StudioBuildErrorType.QUESTIONS_HASH_ERROR, { coursePath: resolvedPath });
95
- // Ensure cache directory exists
96
- await ensureCacheDirectory(resolvedPath);
97
- const buildExists = studioBuildExists(resolvedPath, questionsHash);
98
- const buildPath = getStudioBuildPath(resolvedPath, questionsHash);
99
- console.log(chalk.gray(` Questions hash: ${questionsHash}`));
100
- console.log(chalk.gray(` Cached build exists: ${buildExists ? 'Yes' : 'No'}`));
101
- // Determine if we need to rebuild studio-ui
102
- if (buildExists) {
103
- console.log(chalk.gray(` Using cached build at: ${buildPath}`));
104
- studioUIPath = buildPath;
105
- }
106
- else {
107
- console.log(chalk.cyan(`🔨 Building studio-ui with local question types...`));
108
- studioUIPath = await buildStudioUIWithQuestions(resolvedPath, questionsHash);
109
- console.log(chalk.green(`✅ Studio-UI build complete: ${studioUIPath}`));
110
- }
111
- }
112
- catch (error) {
113
- // Handle catastrophic build errors by falling back to embedded source
114
- console.log(chalk.yellow(`⚠️ Unable to process questions due to ${error},\n⚠️ Using embedded studio-ui`));
115
- const embeddedPath = path.join(__dirname, '..', 'studio-ui-src');
116
- if (fs.existsSync(embeddedPath)) {
117
- studioUIPath = embeddedPath;
118
- console.log(chalk.gray(` Using embedded studio-ui source directly`));
119
- }
120
- else {
121
- console.error(chalk.red(`❌ No viable studio-ui source available`));
122
- throw new Error('Critical error: Cannot locate studio-ui source');
94
+ else {
95
+ // Check if it's a traditional scaffolded course directory
96
+ if (!(await validateSuiCourse(resolvedPath))) {
97
+ console.error(chalk.red(`❌ Not a valid course directory or manifest file`));
98
+ console.log(chalk.yellow(`💡 Studio mode accepts either:`));
99
+ console.log(chalk.yellow(` - Scaffolded course directory with package.json and static-data/`));
100
+ console.log(chalk.yellow(` - Course manifest.json file with chunks/ and indices/ directories`));
101
+ process.exit(1);
123
102
  }
103
+ console.log(chalk.green(`✅ Valid standalone-ui course detected`));
124
104
  }
125
- // Phase 1: CouchDB Management
126
- const studioDatabaseName = generateStudioDatabaseName(resolvedPath);
105
+ // Studio UI build preparation
106
+ const studioUIPath = isManifestMode
107
+ ? await handleManifestCourse()
108
+ : await handleSuiCourse(resolvedPath);
109
+ // Start CouchDB instance
110
+ const studioDatabaseName = generateStudioDatabaseName(actualCoursePath);
127
111
  console.log(chalk.cyan(`🗄️ Starting studio CouchDB instance: ${studioDatabaseName}`));
128
112
  couchDBManager = await startStudioCouchDB(studioDatabaseName, parseInt(options.port));
129
- // Phase 4: Populate CouchDB with course data
113
+ // Load course data into database
130
114
  console.log(chalk.cyan(`📦 Unpacking course data to studio database...`));
131
- const unpackResult = await unpackCourseToStudio(resolvedPath, couchDBManager.getConnectionDetails());
132
- // Phase 9.5: Launch Express backend
133
- console.log(chalk.cyan(`⚡ Starting Express backend server...`));
134
- const expressResult = await startExpressBackend(couchDBManager.getConnectionDetails(), resolvedPath, unpackResult.databaseName);
115
+ const unpackResult = await unpackCourseToStudio(actualCoursePath, couchDBManager.getConnectionDetails(), isManifestMode);
116
+ // Start Express API server
117
+ const expressResult = await startExpressBackend(couchDBManager.getConnectionDetails(), unpackResult.databaseName);
135
118
  expressServer = expressResult.server;
136
- // Phase 7: Launch studio-ui server
119
+ // Launch studio web interface
137
120
  console.log(chalk.cyan(`🌐 Starting studio-ui server...`));
138
121
  console.log(chalk.gray(` Debug: Unpack result - Database: "${unpackResult.databaseName}", Course ID: "${unpackResult.courseId}"`));
139
- const studioUIPort = await startStudioUIServer(couchDBManager.getConnectionDetails(), unpackResult, studioUIPath);
122
+ const studioUIPort = await startStudioUIServer(couchDBManager.getConnectionDetails(), unpackResult, studioUIPath, expressResult.url);
140
123
  console.log(chalk.green(`✅ Studio session ready!`));
141
124
  console.log(chalk.white(`🎨 Studio URL: http://localhost:${studioUIPort}`));
142
125
  console.log(chalk.gray(` Database: ${studioDatabaseName} on port ${options.port}`));
143
126
  console.log(chalk.gray(` Express API: ${expressResult.url}`));
144
127
  // Display MCP connection information
145
- const mcpInfo = getMCPConnectionInfo(unpackResult, couchDBManager, resolvedPath);
128
+ const mcpInfo = getMCPConnectionInfo(unpackResult, couchDBManager);
146
129
  console.log(chalk.blue(`🔗 MCP Server: ${mcpInfo.command}`));
147
130
  console.log(chalk.gray(` Connect MCP clients using the command above`));
148
131
  console.log(chalk.gray(` Environment variables for MCP:`));
@@ -150,7 +133,7 @@ async function launchStudio(coursePath, options) {
150
133
  console.log(chalk.gray(` ${key}=${value}`));
151
134
  });
152
135
  // Display .mcp.json content for Claude Code integration
153
- const mcpJsonContent = generateMCPJson(unpackResult, couchDBManager, resolvedPath);
136
+ const mcpJsonContent = generateMCPJson(unpackResult, couchDBManager);
154
137
  console.log(chalk.blue(`📋 .mcp.json content:`));
155
138
  console.log(chalk.gray(mcpJsonContent));
156
139
  if (options.browser) {
@@ -158,7 +141,7 @@ async function launchStudio(coursePath, options) {
158
141
  await openBrowser(`http://localhost:${studioUIPort}`);
159
142
  }
160
143
  console.log(chalk.gray(` Press Ctrl+C to stop studio session`));
161
- // Keep process alive and handle cleanup
144
+ // Session lifecycle management
162
145
  process.on('SIGINT', () => {
163
146
  void (async () => {
164
147
  console.log(chalk.cyan(`\n🔄 Stopping studio session...`));
@@ -183,7 +166,41 @@ async function launchStudio(coursePath, options) {
183
166
  }
184
167
  }
185
168
  /**
186
- * Phase 2: Validate that the given path contains a standalone-ui course
169
+ * Validate that the given path is a course manifest file
170
+ */
171
+ async function validateManifestCourse(manifestPath) {
172
+ try {
173
+ // Check if file exists
174
+ if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile()) {
175
+ return { isValid: false };
176
+ }
177
+ // Check if it's a JSON file
178
+ if (!manifestPath.endsWith('.json')) {
179
+ return { isValid: false };
180
+ }
181
+ // Try to parse as JSON
182
+ const manifestContent = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
183
+ // Validate required manifest fields
184
+ if (!manifestContent.courseId || !manifestContent.courseName) {
185
+ return { isValid: false };
186
+ }
187
+ // Find course data directory relative to manifest
188
+ const manifestDir = path.dirname(manifestPath);
189
+ const coursePath = manifestDir; // Course data should be alongside manifest
190
+ // Check for required course data structure
191
+ const chunksPath = path.join(coursePath, 'chunks');
192
+ const indicesPath = path.join(coursePath, 'indices');
193
+ if (!fs.existsSync(chunksPath) || !fs.existsSync(indicesPath)) {
194
+ return { isValid: false };
195
+ }
196
+ return { isValid: true, coursePath };
197
+ }
198
+ catch {
199
+ return { isValid: false };
200
+ }
201
+ }
202
+ /**
203
+ * Validate that the given path contains a standalone-ui course
187
204
  */
188
205
  async function validateSuiCourse(coursePath) {
189
206
  try {
@@ -300,7 +317,7 @@ async function stopStudioSession() {
300
317
  couchDBManager = null;
301
318
  }
302
319
  }
303
- async function startStudioUIServer(connectionDetails, unpackResult, studioPath) {
320
+ async function startStudioUIServer(connectionDetails, unpackResult, studioPath, expressApiUrl) {
304
321
  // Serve from built dist directory if it exists, otherwise fallback to source
305
322
  const distPath = path.join(studioPath, 'dist');
306
323
  const studioSourcePath = fs.existsSync(distPath) ? distPath : studioPath;
@@ -348,6 +365,9 @@ async function startStudioUIServer(connectionDetails, unpackResult, studioPath)
348
365
  name: '${unpackResult.databaseName}',
349
366
  courseId: '${unpackResult.courseId}',
350
367
  originalCourseId: '${unpackResult.courseId}'
368
+ },
369
+ express: {
370
+ url: '${expressApiUrl || 'http://localhost:3000'}'
351
371
  }
352
372
  };
353
373
  </script>
@@ -375,6 +395,9 @@ async function startStudioUIServer(connectionDetails, unpackResult, studioPath)
375
395
  name: '${unpackResult.databaseName}',
376
396
  courseId: '${unpackResult.courseId}',
377
397
  originalCourseId: '${unpackResult.courseId}'
398
+ },
399
+ express: {
400
+ url: '${expressApiUrl || 'http://localhost:3000'}'
378
401
  }
379
402
  };
380
403
  </script>
@@ -444,7 +467,7 @@ async function openBrowser(url) {
444
467
  /**
445
468
  * Phase 9.5: Start Express backend server
446
469
  */
447
- async function startExpressBackend(couchDbConnectionDetails, _projectPath, databaseName) {
470
+ async function startExpressBackend(couchDbConnectionDetails, databaseName) {
448
471
  console.log(chalk.blue('⚡ Starting Express backend server...'));
449
472
  // Find available port starting from 3001
450
473
  const availablePort = await findAvailablePort(3001);
@@ -470,6 +493,8 @@ async function startExpressBackend(couchDbConnectionDetails, _projectPath, datab
470
493
  },
471
494
  };
472
495
  try {
496
+ // Set NODE_ENV for studio mode authentication bypass
497
+ process.env.NODE_ENV = 'studio';
473
498
  // Create Express app using factory
474
499
  const app = createExpressApp(config);
475
500
  // Start server
@@ -495,29 +520,37 @@ async function startExpressBackend(couchDbConnectionDetails, _projectPath, datab
495
520
  /**
496
521
  * Phase 4: Unpack course data to studio CouchDB
497
522
  */
498
- async function unpackCourseToStudio(coursePath, connectionDetails) {
523
+ async function unpackCourseToStudio(coursePath, connectionDetails, isManifestMode = false) {
499
524
  try {
500
- // Find the course data directory (static-data OR public/static-courses)
501
- let courseDataPath = path.join(coursePath, 'static-data');
502
- if (!fs.existsSync(courseDataPath)) {
503
- // Try public/static-courses directory
504
- const publicStaticPath = path.join(coursePath, 'public', 'static-courses');
505
- if (fs.existsSync(publicStaticPath)) {
506
- // Find the first course directory inside public/static-courses
507
- const courses = fs
508
- .readdirSync(publicStaticPath, { withFileTypes: true })
509
- .filter((dirent) => dirent.isDirectory())
510
- .map((dirent) => dirent.name);
511
- if (courses.length > 0) {
512
- courseDataPath = path.join(publicStaticPath, courses[0]);
525
+ let courseDataPath;
526
+ if (isManifestMode) {
527
+ // For manifest mode, the coursePath already points to the course data directory
528
+ courseDataPath = coursePath;
529
+ console.log(chalk.gray(` Manifest mode: using course data at ${courseDataPath}`));
530
+ }
531
+ else {
532
+ // Find the course data directory (static-data OR public/static-courses)
533
+ courseDataPath = path.join(coursePath, 'static-data');
534
+ if (!fs.existsSync(courseDataPath)) {
535
+ // Try public/static-courses directory
536
+ const publicStaticPath = path.join(coursePath, 'public', 'static-courses');
537
+ if (fs.existsSync(publicStaticPath)) {
538
+ // Find the first course directory inside public/static-courses
539
+ const courses = fs
540
+ .readdirSync(publicStaticPath, { withFileTypes: true })
541
+ .filter((dirent) => dirent.isDirectory())
542
+ .map((dirent) => dirent.name);
543
+ if (courses.length > 0) {
544
+ courseDataPath = path.join(publicStaticPath, courses[0]);
545
+ }
546
+ else {
547
+ throw new Error('No course directories found in public/static-courses/');
548
+ }
513
549
  }
514
550
  else {
515
- throw new Error('No course directories found in public/static-courses/');
551
+ throw new Error('No course data found in static-data/ or public/static-courses/');
516
552
  }
517
553
  }
518
- else {
519
- throw new Error('No course data found in static-data/ or public/static-courses/');
520
- }
521
554
  }
522
555
  console.log(chalk.gray(` Course data path: ${courseDataPath}`));
523
556
  console.log(chalk.gray(` Running unpack directly...`));
@@ -648,9 +681,9 @@ async function buildDefaultStudioUI(buildPath) {
648
681
  console.log(chalk.gray(` Transforming workspace dependencies...`));
649
682
  const studioPackageJsonPath = path.join(buildPath, 'package.json');
650
683
  await transformPackageJsonForStudioBuild(studioPackageJsonPath);
651
- // Fix Vite config to use npm packages instead of monorepo paths
652
- console.log(chalk.gray(` Updating Vite configuration for standalone build...`));
653
- await fixViteConfigForStandaloneBuild(buildPath);
684
+ // Fix Vite config to use npm packages and resolve custom questions imports
685
+ console.log(chalk.gray(` Updating Vite configuration for studio build...`));
686
+ await createStudioViteConfig(buildPath);
654
687
  // Run Vite build process
655
688
  console.log(chalk.gray(` Running Vite build process...`));
656
689
  await runViteBuild(buildPath);
@@ -788,9 +821,9 @@ async function buildStudioUIWithCustomQuestions(buildPath, customQuestionsData)
788
821
  console.log(chalk.gray(` Transforming workspace dependencies...`));
789
822
  const studioPackageJsonPath = path.join(buildPath, 'package.json');
790
823
  await transformPackageJsonForStudioBuild(studioPackageJsonPath);
791
- // Step 2.5: Fix Vite config to use npm packages instead of monorepo paths
792
- console.log(chalk.gray(` Updating Vite configuration for standalone build...`));
793
- await fixViteConfigForStandaloneBuild(buildPath);
824
+ // Step 2.5: Fix Vite config to use npm packages and resolve custom questions imports
825
+ console.log(chalk.gray(` Updating Vite configuration for studio build...`));
826
+ await createStudioViteConfig(buildPath);
794
827
  // Step 3: Install custom questions package
795
828
  console.log(chalk.cyan(` Installing bundled course package: ${customQuestionsData.packageName} from ${customQuestionsData.coursePath}`));
796
829
  const distLibPath = path.join(customQuestionsData.coursePath, 'dist-lib');
@@ -954,17 +987,17 @@ async function runViteBuild(buildPath) {
954
987
  });
955
988
  }
956
989
  /**
957
- * Fix Vite configuration to work in standalone build environment
990
+ * Create Vite configuration for studio-ui build environment
958
991
  */
959
- async function fixViteConfigForStandaloneBuild(buildPath) {
992
+ async function createStudioViteConfig(buildPath) {
960
993
  const viteConfigPath = path.join(buildPath, 'vite.config.ts');
961
994
  if (!fs.existsSync(viteConfigPath)) {
962
995
  console.log(chalk.yellow(` Warning: vite.config.ts not found at ${viteConfigPath}`));
963
996
  return;
964
997
  }
965
- // Create a clean standalone vite config for external projects
966
- // Relies on standard npm package resolution instead of monorepo paths
967
- const standaloneViteConfig = `import { defineConfig } from 'vite';
998
+ // Create a clean studio vite config for studio-ui environment
999
+ // Includes aliases to resolve custom questions imports
1000
+ const studioViteConfig = `import { defineConfig } from 'vite';
968
1001
  import vue from '@vitejs/plugin-vue';
969
1002
 
970
1003
  export default defineConfig({
@@ -990,9 +1023,17 @@ export default defineConfig({
990
1023
  },
991
1024
  resolve: {
992
1025
  extensions: ['.js', '.ts', '.json', '.vue'],
1026
+ alias: {
1027
+ // Resolve @vue-skuilder packages to npm packages for custom questions import
1028
+ '@vue-skuilder/common': '@vue-skuilder/common',
1029
+ '@vue-skuilder/courseware': '@vue-skuilder/courseware',
1030
+ '@vue-skuilder/db': '@vue-skuilder/db',
1031
+ '@vue-skuilder/common-ui': '@vue-skuilder/common-ui',
1032
+ '@vue-skuilder/edit-ui': '@vue-skuilder/edit-ui'
1033
+ },
993
1034
  dedupe: [
994
1035
  'vue',
995
- 'vuetify',
1036
+ 'vuetify',
996
1037
  'vue-router',
997
1038
  'pinia',
998
1039
  '@vue-skuilder/db',
@@ -1003,52 +1044,32 @@ export default defineConfig({
1003
1044
  ]
1004
1045
  }
1005
1046
  });`;
1006
- fs.writeFileSync(viteConfigPath, standaloneViteConfig);
1007
- console.log(chalk.gray(` Vite config replaced with standalone version`));
1047
+ fs.writeFileSync(viteConfigPath, studioViteConfig);
1048
+ console.log(chalk.gray(` Vite config replaced with studio version`));
1008
1049
  }
1009
1050
  /**
1010
- * Determine the correct MCP server executable path/command based on project context
1051
+ * Determine the correct MCP server executable path.
1052
+ * This is now greatly simplified because the bundled server is always
1053
+ * located relative to this `studio.ts` file.
1011
1054
  */
1012
- function resolveMCPExecutable(projectPath) {
1013
- // Check if we're in the monorepo (packages/cli exists)
1014
- const monorepoCliPath = path.join(projectPath, 'packages', 'cli', 'dist', 'mcp-server.js');
1015
- if (fs.existsSync(monorepoCliPath)) {
1016
- return {
1017
- command: './packages/cli/dist/mcp-server.js',
1018
- args: [],
1019
- isNpx: false,
1020
- };
1021
- }
1022
- // Check if @vue-skuilder/cli is installed as a dependency
1023
- const scaffoldedCliPath = path.join(projectPath, 'node_modules', '@vue-skuilder', 'cli', 'dist', 'mcp-server.js');
1024
- if (fs.existsSync(scaffoldedCliPath)) {
1025
- return {
1026
- command: './node_modules/@vue-skuilder/cli/dist/mcp-server.js',
1027
- args: [],
1028
- isNpx: false,
1029
- };
1030
- }
1031
- // Fallback to npx approach
1055
+ function resolveMCPExecutable() {
1056
+ // Resolve the path to the bundled server relative to the current file.
1057
+ // __dirname is the `dist/commands` directory.
1058
+ const serverPath = path.resolve(__dirname, '..', 'mcp-server.js');
1032
1059
  return {
1033
- command: 'npx',
1034
- args: ['@vue-skuilder/cli', 'mcp-server'],
1035
- isNpx: true,
1060
+ command: 'node',
1061
+ args: [serverPath],
1036
1062
  };
1037
1063
  }
1038
1064
  /**
1039
1065
  * Generate MCP connection information for studio session
1040
1066
  */
1041
- function getMCPConnectionInfo(unpackResult, couchDBManager, projectPath) {
1067
+ function getMCPConnectionInfo(unpackResult, couchDBManager) {
1042
1068
  const couchDetails = couchDBManager.getConnectionDetails();
1043
- const executable = resolveMCPExecutable(projectPath);
1069
+ const executable = resolveMCPExecutable();
1070
+ const port = couchDetails.port || 5985;
1044
1071
  // Build command string for display
1045
- let commandStr;
1046
- if (executable.isNpx) {
1047
- commandStr = `${executable.command} ${executable.args.join(' ')} ${unpackResult.databaseName} ${couchDetails.port}`;
1048
- }
1049
- else {
1050
- commandStr = `node ${executable.command} ${unpackResult.databaseName} ${couchDetails.port}`;
1051
- }
1072
+ const commandStr = `${executable.command} ${executable.args.join(' ')} ${unpackResult.databaseName} ${port}`;
1052
1073
  return {
1053
1074
  command: commandStr,
1054
1075
  env: {
@@ -1062,10 +1083,10 @@ function getMCPConnectionInfo(unpackResult, couchDBManager, projectPath) {
1062
1083
  /**
1063
1084
  * Generate .mcp.json content for Claude Code integration
1064
1085
  */
1065
- function generateMCPJson(unpackResult, couchDBManager, projectPath, serverName = 'vue-skuilder-studio') {
1086
+ function generateMCPJson(unpackResult, couchDBManager, serverName = 'vue-skuilder-studio') {
1066
1087
  const couchDetails = couchDBManager.getConnectionDetails();
1067
1088
  const port = couchDetails.port || 5985;
1068
- const executable = resolveMCPExecutable(projectPath);
1089
+ const executable = resolveMCPExecutable();
1069
1090
  const mcpConfig = {
1070
1091
  mcpServers: {
1071
1092
  [serverName]: {
@@ -1082,4 +1103,54 @@ function generateMCPJson(unpackResult, couchDBManager, projectPath, serverName =
1082
1103
  };
1083
1104
  return JSON.stringify(mcpConfig, null, 2);
1084
1105
  }
1106
+ /**
1107
+ * Handle SUI course build process - extract questions hashing and UI building logic
1108
+ */
1109
+ async function handleSuiCourse(coursePath) {
1110
+ console.log(chalk.cyan(`🔍 Analyzing local question types...`));
1111
+ try {
1112
+ const questionsHash = await withStudioBuildErrorHandling(() => hashQuestionsDirectory(coursePath), StudioBuildErrorType.QUESTIONS_HASH_ERROR, { coursePath: coursePath });
1113
+ // Ensure cache directory exists
1114
+ await ensureCacheDirectory(coursePath);
1115
+ const buildExists = studioBuildExists(coursePath, questionsHash);
1116
+ const buildPath = getStudioBuildPath(coursePath, questionsHash);
1117
+ console.log(chalk.gray(` Questions hash: ${questionsHash}`));
1118
+ console.log(chalk.gray(` Cached build exists: ${buildExists ? 'Yes' : 'No'}`));
1119
+ let studioUIPath;
1120
+ // Determine if we need to rebuild studio-ui
1121
+ if (buildExists) {
1122
+ console.log(chalk.gray(` Using cached build at: ${buildPath}`));
1123
+ studioUIPath = buildPath;
1124
+ }
1125
+ else {
1126
+ console.log(chalk.cyan(`🔨 Building studio-ui with local question types...`));
1127
+ studioUIPath = await buildStudioUIWithQuestions(coursePath, questionsHash);
1128
+ console.log(chalk.green(`✅ Studio-UI build complete: ${studioUIPath}`));
1129
+ }
1130
+ return studioUIPath;
1131
+ }
1132
+ catch (error) {
1133
+ // Handle catastrophic build errors by falling back to embedded source
1134
+ console.log(chalk.yellow(`⚠️ Unable to process questions due to ${error},\n⚠️ Using embedded studio-ui`));
1135
+ const embeddedPath = path.join(__dirname, '..', 'studio-ui-src');
1136
+ if (fs.existsSync(embeddedPath)) {
1137
+ const studioUIPath = embeddedPath;
1138
+ console.log(chalk.gray(` Using embedded studio-ui source directly`));
1139
+ return studioUIPath;
1140
+ }
1141
+ else {
1142
+ console.error(chalk.red(`❌ No viable studio-ui source available`));
1143
+ throw new Error('Critical error: Cannot locate studio-ui source');
1144
+ }
1145
+ }
1146
+ }
1147
+ /**
1148
+ * Handle manifest course - simple default build without custom questions
1149
+ */
1150
+ async function handleManifestCourse() {
1151
+ console.log(chalk.cyan(`📋 Manifest mode: using default studio-ui build`));
1152
+ const manifestBuildPath = path.join(__dirname, '..', 'studio-builds', 'manifest-default');
1153
+ const studioUIPath = await buildDefaultStudioUI(manifestBuildPath);
1154
+ return studioUIPath;
1155
+ }
1085
1156
  //# sourceMappingURL=studio.js.map