Skip to content

Commit c2dbb4b

Browse files
committed
[wip] add sk-contributor course app
1 parent 4b0d41b commit c2dbb4b

37 files changed

+2144
-5
lines changed

packages/sk-contributor/.gitignore

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Dependencies
2+
node_modules/
3+
/.pnp
4+
.pnp.js
5+
6+
# Production builds
7+
/dist
8+
/build
9+
/dist-lib
10+
11+
# Local env files
12+
.env
13+
.env.local
14+
.env.development.local
15+
.env.test.local
16+
.env.production.local
17+
18+
# Log files
19+
npm-debug.log*
20+
yarn-debug.log*
21+
yarn-error.log*
22+
pnpm-debug.log*
23+
lerna-debug.log*
24+
25+
# Runtime data
26+
pids
27+
*.pid
28+
*.seed
29+
*.pid.lock
30+
31+
# Coverage directory used by tools like istanbul
32+
coverage/
33+
*.lcov
34+
35+
# nyc test coverage
36+
.nyc_output
37+
38+
# Dependency directories
39+
jspm_packages/
40+
41+
# TypeScript cache
42+
*.tsbuildinfo
43+
44+
# Optional npm cache directory
45+
.npm
46+
47+
# Optional eslint cache
48+
.eslintcache
49+
50+
# Microbundle cache
51+
.rpt2_cache/
52+
.rts2_cache_cjs/
53+
.rts2_cache_es/
54+
.rts2_cache_umd/
55+
56+
# Optional REPL history
57+
.node_repl_history
58+
59+
# Output of 'npm pack'
60+
*.tgz
61+
62+
# Yarn Integrity file
63+
.yarn-integrity
64+
65+
# parcel-bundler cache (https://parceljs.org/)
66+
.cache
67+
.parcel-cache
68+
69+
# Next.js build output
70+
.next
71+
72+
# Nuxt.js build / generate output
73+
.nuxt
74+
dist
75+
76+
# Gatsby files
77+
.cache/
78+
public
79+
80+
# Storybook build outputs
81+
.out
82+
.storybook-out
83+
84+
# Temporary folders
85+
tmp/
86+
temp/
87+
88+
# Editor directories and files
89+
.vscode/
90+
.idea
91+
.DS_Store
92+
*.suo
93+
*.ntvs*
94+
*.njsproj
95+
*.sln
96+
*.sw?
97+
98+
# OS generated files
99+
Thumbs.db
100+
101+
# Cypress
102+
/cypress/videos/
103+
/cypress/screenshots/
104+
105+
# Local development
106+
.env.development
107+
.env.production
108+
109+
# Package manager lockfiles (uncomment if you want to ignore them)
110+
# package-lock.json
111+
# yarn.lock
112+
# pnpm-lock.yaml
113+
114+
# Skuilder specific
115+
/src/data/local-*.json
116+
.skuilder/

packages/sk-contributor/README.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Contributing
2+
3+
A Skuilder course application built with Vue 3, Vuetify, and Pinia.
4+
5+
## Data Layer
6+
7+
This project uses a static data layer with JSON files.
8+
9+
## Development
10+
11+
Install dependencies:
12+
```bash
13+
npm install
14+
```
15+
16+
Start the development server:
17+
```bash
18+
npm run dev
19+
```
20+
21+
Build for production:
22+
```bash
23+
npm run build
24+
```
25+
26+
## Studio Mode (Content Editing)
27+
28+
This project supports **Studio Mode** - a content editing web interface for modifying course data:
29+
30+
```bash
31+
npm run studio
32+
```
33+
34+
Studio mode provides:
35+
- **Visual Course Editor**: Interactive interface for editing course content
36+
- **Live Preview**: See changes immediately in the browser
37+
- **Hot Reload**: Changes are saved automatically to your course files
38+
- **No Setup Required**: Built into the Skuilder CLI - just run the command
39+
40+
When you run `npm run studio`, it will:
41+
1. Start a local CouchDB instance for temporary editing
42+
2. Load your course data from `public/static-courses/`
43+
3. Launch the studio interface at http://localhost:7174
44+
4. Provide MCP server connection for, eg, Claude Code integration
45+
5. Save changes back to your static course files when you flush
46+
47+
**Important**: Studio mode **overwrites** existing static data source files in `public/static-courses/`. Make sure to commit or backup your course data before making major edits.
48+
49+
### Claude Code Integration (MCP Server)
50+
51+
Studio mode automatically provides an MCP (Model Context Protocol) server for AI-powered course authoring with Claude Code. When you run `npm run studio`, it displays connection details like:
52+
53+
```bash
54+
🔗 MCP Server: node ./node_modules/@vue-skuilder/cli/dist/mcp-server.js course-id 5985
55+
📋 .mcp.json content:
56+
{
57+
"mcpServers": {
58+
"vue-skuilder-studio": {
59+
"command": "./node_modules/@vue-skuilder/cli/dist/mcp-server.js",
60+
"args": ["course-id", "5985"],
61+
"env": {
62+
"COUCHDB_SERVER_URL": "localhost:5985",
63+
"COUCHDB_SERVER_PROTOCOL": "http",
64+
"COUCHDB_USERNAME": "admin",
65+
"COUCHDB_PASSWORD": "password"
66+
}
67+
}
68+
}
69+
}
70+
```
71+
72+
Copy the generated `.mcp.json` content to your Claude Code configuration to enable:
73+
- **AI Content Creation**: Generate course cards with fill-in-the-blank and multiple-choice questions
74+
- **Smart Tagging**: Automatically tag cards and assign ELO difficulty ratings
75+
- **Course Analysis**: Analyze existing course content and suggest improvements
76+
- **Bulk Content Operations**: Create multiple cards at once with consistent formatting
77+
78+
## Configuration
79+
80+
Course configuration is managed in `skuilder.config.json`. You can modify:
81+
- Course title
82+
- Data layer settings
83+
- Theme customization
84+
- Database connection details (for dynamic data layer)
85+
86+
## Theme
87+
88+
Current theme: **default** (light mode)
89+
- Primary: #1976D2
90+
- Secondary: #424242
91+
- Accent: #82B1FF
92+
93+
This theme includes both light and dark variants. The application will use the light theme by default, but users can toggle between light and dark modes in their settings.
94+
95+
### Theme Customization
96+
97+
To customize the theme colors, edit the `theme` section in `skuilder.config.json`:
98+
99+
```json
100+
{
101+
"theme": {
102+
"name": "custom",
103+
"defaultMode": "light",
104+
"light": {
105+
"dark": false,
106+
"colors": {
107+
"primary": "#your-color",
108+
"secondary": "#your-color",
109+
"accent": "#your-color"
110+
// ... other semantic colors
111+
}
112+
},
113+
"dark": {
114+
"dark": true,
115+
"colors": {
116+
// ... dark variant colors
117+
}
118+
}
119+
}
120+
}
121+
```
122+
123+
The theme system supports all Vuetify semantic colors including error, success, warning, info, background, surface, and text colors. Changes to the configuration file are applied automatically on restart.
124+
125+
## Testing
126+
127+
Run end-to-end tests:
128+
```bash
129+
npm run test:e2e
130+
```
131+
132+
Run tests in headless mode:
133+
```bash
134+
npm run test:e2e:headless
135+
```
136+
137+
## Learn More
138+
139+
Visit the [Skuilder documentation](https://github.com/NiloCK/vue-skuilder) for more information about building course applications.

packages/sk-contributor/index.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" href="/favicon.ico" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Skuilder Course</title>
8+
<link
9+
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons"
10+
rel="stylesheet"
11+
/>
12+
</head>
13+
<body>
14+
<div id="app"></div>
15+
<script type="module" src="/src/main.ts"></script>
16+
</body>
17+
</html>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"keep": {
3+
"days": true,
4+
"amount": 7
5+
},
6+
"auditLog": "logs/.a74d0b06286c8a4cf196682f1943432de0472db1-audit.json",
7+
"files": [
8+
{
9+
"date": 1754318839444,
10+
"name": "logs/combined-2025-08-04.log",
11+
"hash": "ac657d834f87bd70823c7c054d01b0a549febfe17a45d1a51e4ca7f2e588ccaa"
12+
},
13+
{
14+
"date": 1754387825313,
15+
"name": "logs/combined-2025-08-05.log",
16+
"hash": "2cbc2fa0ed1ba4fdd98f30d7bb99dda4367a9ba8e52bd9110ced0e5a9275e7f8"
17+
}
18+
],
19+
"hashType": "sha256"
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"keep": {
3+
"days": true,
4+
"amount": 7
5+
},
6+
"auditLog": "logs/.d3dff8304a896044ec8db08dd8e9c1056ae5fb50-audit.json",
7+
"files": [
8+
{
9+
"date": 1754318839439,
10+
"name": "logs/error-2025-08-04.log",
11+
"hash": "0d820416f020343dd9d2e77f550e8147f6133977feec0415d2489a1c6c002150"
12+
},
13+
{
14+
"date": 1754387825307,
15+
"name": "logs/error-2025-08-05.log",
16+
"hash": "e312b4e2e088a6e5f4e3087bff850227a3a7698a0a933eb770261c818fd33a09"
17+
}
18+
],
19+
"hashType": "sha256"
20+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"FFMPEG path: /home/colin/pn/vue-skuilder/df1/node_modules/ffmpeg-static/ffmpeg","timestamp":"2025-08-04T14:47:19.450Z"}
2+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"FFMPEG version: ffmpeg version 6.0-static https://johnvansickle.com/ffmpeg/ Copyright (c) 2000-2023 the FFmpeg developers","timestamp":"2025-08-04T14:47:19.527Z"}
3+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Following all course databases for changes...","timestamp":"2025-08-04T14:49:16.848Z"}
4+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Starting initCourseDBDesignDocInsert...","timestamp":"2025-08-04T14:49:16.851Z"}
5+
{"caller":"Format.transform (logger.js:4)","level":"warn","message":"Course lookup database not found - skipping platform course discovery","timestamp":"2025-08-04T14:49:16.911Z"}
6+
{"caller":"Format.transform (logger.js:4)","level":"error","message":"Error in initCourseDBDesignDocInsert background task: [object Object]","timestamp":"2025-08-04T14:49:16.931Z"}
7+
{"caller":"Format.transform (logger.js:4)","level":"error","message":"Full error details in initCourseDBDesignDocInsert: {\"error\":\"not_found\",\"reason\":\"Database does not exist.\",\"status\":404,\"name\":\"not_found\",\"message\":\"Database does not exist.\",\"stack\":\"Error\\n at generateErrorFromResponse (/home/colin/pn/vue-skuilder/df1/node_modules/pouchdb/lib/index.js:553:18)\\n at fetchJSON (/home/colin/pn/vue-skuilder/df1/node_modules/pouchdb/lib/index.js:6886:19)\\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\\n at async /home/colin/pn/vue-skuilder/df1/node_modules/pouchdb/lib/index.js:7490:22\"}","timestamp":"2025-08-04T14:49:16.933Z"}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"FFMPEG path: /home/colin/pn/vue-skuilder/df1/node_modules/ffmpeg-static/ffmpeg","timestamp":"2025-08-05T09:57:05.321Z"}
2+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"FFMPEG version: ffmpeg version 6.0-static https://johnvansickle.com/ffmpeg/ Copyright (c) 2000-2023 the FFmpeg developers","timestamp":"2025-08-05T09:57:05.416Z"}
3+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Following all course databases for changes...","timestamp":"2025-08-05T09:57:13.800Z"}
4+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Starting initCourseDBDesignDocInsert...","timestamp":"2025-08-05T09:57:13.802Z"}
5+
{"caller":"Format.transform (logger.js:4)","level":"error","message":"Error in initCourseDBDesignDocInsert background task: [object Object]","timestamp":"2025-08-05T09:57:13.869Z"}
6+
{"caller":"Format.transform (logger.js:4)","level":"error","message":"Full error details in initCourseDBDesignDocInsert: {\"error\":\"not_found\",\"reason\":\"Database does not exist.\",\"status\":404,\"name\":\"not_found\",\"message\":\"Database does not exist.\",\"stack\":\"Error\\n at generateErrorFromResponse (/home/colin/pn/vue-skuilder/df1/node_modules/pouchdb/lib/index.js:553:18)\\n at fetchJSON (/home/colin/pn/vue-skuilder/df1/node_modules/pouchdb/lib/index.js:6886:19)\\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\\n at async /home/colin/pn/vue-skuilder/df1/node_modules/pouchdb/lib/index.js:7490:22\"}","timestamp":"2025-08-05T09:57:13.871Z"}
7+
{"caller":"Format.transform (logger.js:4)","level":"warn","message":"Course lookup database not found - skipping platform course discovery","timestamp":"2025-08-05T09:57:13.875Z"}
8+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"PACK_COURSE request from undefined...","timestamp":"2025-08-05T17:55:21.560Z"}
9+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Studio mode: bypassing authentication for local development","timestamp":"2025-08-05T17:55:21.563Z"}
10+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Authorized PACK_COURSE request made...","timestamp":"2025-08-05T17:55:21.565Z"}
11+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Starting PACK_COURSE for unpacked_c01c9577-8f54-4102-9d43-d2ae0591ddd8_20250805_oksk24...","timestamp":"2025-08-05T17:55:21.568Z"}
12+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Pack request data: {\n \"courseId\": \"unpacked_c01c9577-8f54-4102-9d43-d2ae0591ddd8_20250805_oksk24\",\n \"outputPath\": \"./public/static-courses/c01c9577-8f54-4102-9d43-d2ae0591ddd8\",\n \"couchdbUrl\": \"http://admin:password@localhost:5985/coursedb-unpacked_c01c9577-8f54-4102-9d43-d2ae0591ddd8_20250805_oksk24\"\n}","timestamp":"2025-08-05T17:55:21.572Z"}
13+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Using provided CouchDB URL: \"http://admin:password@localhost:5985/coursedb-unpacked_c01c9577-8f54-4102-9d43-d2ae0591ddd8_20250805_oksk24\"","timestamp":"2025-08-05T17:55:21.573Z"}
14+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Packing course unpacked_c01c9577-8f54-4102-9d43-d2ae0591ddd8_20250805_oksk24 from http://admin:password@localhost:5985/coursedb-unpacked_c01c9577-8f54-4102-9d43-d2ae0591ddd8_20250805_oksk24 to /home/colin/pn/vue-skuilder/df1/packages/sk-contributor/public/static-courses/c01c9577-8f54-4102-9d43-d2ae0591ddd8","timestamp":"2025-08-05T17:55:21.575Z"}
15+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Removing existing directory: /home/colin/pn/vue-skuilder/df1/packages/sk-contributor/public/static-courses/c01c9577-8f54-4102-9d43-d2ae0591ddd8","timestamp":"2025-08-05T17:55:21.583Z"}
16+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Creating PouchDB instance with URL: http://admin:password@localhost:5985/coursedb-unpacked_c01c9577-8f54-4102-9d43-d2ae0591ddd8_20250805_oksk24","timestamp":"2025-08-05T17:55:21.602Z"}
17+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"PouchDB constructor available: function","timestamp":"2025-08-05T17:55:21.604Z"}
18+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"PouchDB adapters: [\"leveldb\",\"http\",\"https\"]","timestamp":"2025-08-05T17:55:21.606Z"}
19+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"PouchDB instance created, adapter: http","timestamp":"2025-08-05T17:55:21.609Z"}
20+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"Pack completed in 210ms. Attachments: 0, Files written: 6","timestamp":"2025-08-05T17:55:21.780Z"}
21+
{"caller":"Format.transform (logger.js:4)","level":"info","message":"::ffff:127.0.0.1 - - [05/Aug/2025:17:55:21 +0000] \"POST / HTTP/1.1\" 200 223 \"http://localhost:7174/\" \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0\"","timestamp":"2025-08-05T17:55:21.794Z"}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"caller":"Format.transform (logger.js:4)","level":"error","message":"Error in initCourseDBDesignDocInsert background task: [object Object]","timestamp":"2025-08-04T14:49:16.931Z"}
2+
{"caller":"Format.transform (logger.js:4)","level":"error","message":"Full error details in initCourseDBDesignDocInsert: {\"error\":\"not_found\",\"reason\":\"Database does not exist.\",\"status\":404,\"name\":\"not_found\",\"message\":\"Database does not exist.\",\"stack\":\"Error\\n at generateErrorFromResponse (/home/colin/pn/vue-skuilder/df1/node_modules/pouchdb/lib/index.js:553:18)\\n at fetchJSON (/home/colin/pn/vue-skuilder/df1/node_modules/pouchdb/lib/index.js:6886:19)\\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\\n at async /home/colin/pn/vue-skuilder/df1/node_modules/pouchdb/lib/index.js:7490:22\"}","timestamp":"2025-08-04T14:49:16.933Z"}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"caller":"Format.transform (logger.js:4)","level":"error","message":"Error in initCourseDBDesignDocInsert background task: [object Object]","timestamp":"2025-08-05T09:57:13.869Z"}
2+
{"caller":"Format.transform (logger.js:4)","level":"error","message":"Full error details in initCourseDBDesignDocInsert: {\"error\":\"not_found\",\"reason\":\"Database does not exist.\",\"status\":404,\"name\":\"not_found\",\"message\":\"Database does not exist.\",\"stack\":\"Error\\n at generateErrorFromResponse (/home/colin/pn/vue-skuilder/df1/node_modules/pouchdb/lib/index.js:553:18)\\n at fetchJSON (/home/colin/pn/vue-skuilder/df1/node_modules/pouchdb/lib/index.js:6886:19)\\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\\n at async /home/colin/pn/vue-skuilder/df1/node_modules/pouchdb/lib/index.js:7490:22\"}","timestamp":"2025-08-05T09:57:13.871Z"}

0 commit comments

Comments
 (0)