coursecast 0.1.0 → 0.2.1
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/README.md +6 -6
- package/bin/coursecast.mjs +28 -33
- package/package.json +2 -2
- package/src/engines/gemini.mjs +2 -2
- package/src/engines/stub.mjs +2 -2
package/README.md
CHANGED
|
@@ -18,11 +18,11 @@ Requires Node 18+.
|
|
|
18
18
|
## Use
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
# one
|
|
22
|
-
coursecast
|
|
21
|
+
# a whole course (~12 lessons + mastery dashboard), from one prompt
|
|
22
|
+
coursecast "how DNS works"
|
|
23
23
|
|
|
24
|
-
#
|
|
25
|
-
coursecast
|
|
24
|
+
# generate and deploy live to your own Vercel
|
|
25
|
+
coursecast "intro to nuclear power" --deploy
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Open the printed `index.html` — that's the whole product: no server, no build step,
|
|
@@ -40,10 +40,10 @@ The engine is what writes the content. Pick with `--engine`:
|
|
|
40
40
|
| `stub` | nothing — offline placeholder content | nothing |
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
coursecast
|
|
43
|
+
coursecast "learn rust" --engine gemini
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
Gemini extras: `GEMINI_MODEL` overrides the model (default `gemini-
|
|
46
|
+
Gemini extras: `GEMINI_MODEL` overrides the model (default `gemini-3.5-flash`);
|
|
47
47
|
`COURSECAST_RESEARCH=0` skips the web-search research pass (faster, cheaper, less current).
|
|
48
48
|
|
|
49
49
|
## All flags
|
package/bin/coursecast.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// coursecast — one prompt →
|
|
2
|
+
// coursecast — one prompt → a whole interactive course → deployed live.
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { createCourse, deployDir } from '../src/index.mjs';
|
|
5
5
|
|
|
6
6
|
const argv = process.argv.slice(2);
|
|
7
7
|
|
|
@@ -18,19 +18,18 @@ const log = (...a) => console.log(...a);
|
|
|
18
18
|
|
|
19
19
|
function usage() {
|
|
20
20
|
log(`
|
|
21
|
-
coursecast —
|
|
21
|
+
coursecast — one prompt → a whole interactive course, deployed live.
|
|
22
22
|
|
|
23
23
|
USAGE
|
|
24
|
-
coursecast
|
|
25
|
-
coursecast course "<topic>" [options] generate a whole multi-lesson course + dashboard
|
|
24
|
+
coursecast "<topic>" [options]
|
|
26
25
|
|
|
27
26
|
OPTIONS
|
|
28
27
|
--engine <id> generation engine: claude-code (default), gemini, codex, stub
|
|
29
|
-
--model <id> model hint passed to the engine (e.g. gemini-
|
|
28
|
+
--model <id> model hint passed to the engine (e.g. gemini-3.5-flash)
|
|
30
29
|
--storage <id> progress storage: local (default, no setup), cloud (sync across devices)
|
|
31
30
|
--provider <id> deploy target: vercel (default), netlify
|
|
32
|
-
--lessons <n>
|
|
33
|
-
--concurrency <n>
|
|
31
|
+
--lessons <n> target number of lessons (default ~12)
|
|
32
|
+
--concurrency <n> how many lessons to generate at once (default 4)
|
|
34
33
|
--deploy deploy after generating (otherwise generate only)
|
|
35
34
|
--dry-run with --deploy, print the deploy command without running it
|
|
36
35
|
--preview deploy as a preview instead of production
|
|
@@ -38,21 +37,24 @@ OPTIONS
|
|
|
38
37
|
-h, --help show this help
|
|
39
38
|
|
|
40
39
|
EXAMPLES
|
|
41
|
-
coursecast
|
|
42
|
-
coursecast
|
|
43
|
-
|
|
44
|
-
coursecast course "intro to nuclear power" --deploy --provider netlify
|
|
40
|
+
coursecast "kubernetes basics" --engine stub # offline test, no AI
|
|
41
|
+
coursecast "how DNS works" --engine gemini # cheap, search-grounded
|
|
42
|
+
coursecast "intro to nuclear power" --deploy # generate + ship to your Vercel
|
|
45
43
|
`);
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
async function main() {
|
|
49
|
-
|
|
50
|
-
if (!cmd || has('help') || argv.includes('-h')) { usage(); process.exit(cmd ? 0 : 1); }
|
|
47
|
+
if (has('help') || argv.includes('-h')) { usage(); process.exit(0); }
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
// The topic is the whole interface: the first non-flag argument.
|
|
50
|
+
const topic = argv[0] && !argv[0].startsWith('--') ? argv[0] : null;
|
|
51
|
+
if (!topic) { log(`❌ Provide a topic: coursecast "your topic"`); usage(); process.exit(1); }
|
|
52
|
+
// Old two-word syntax (`coursecast course "x"`) would silently make a course about
|
|
53
|
+
// "course" — catch it and point at the new shape instead.
|
|
54
|
+
if ((topic === 'course' || topic === 'lesson' || topic === 'new') && argv[1] && !argv[1].startsWith('--')) {
|
|
55
|
+
log(`❌ Subcommands are gone — it's just: coursecast "${argv[1]}"`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
56
58
|
|
|
57
59
|
const engine = getFlag('engine', 'claude-code');
|
|
58
60
|
const provider = getFlag('provider', 'vercel');
|
|
@@ -63,26 +65,19 @@ async function main() {
|
|
|
63
65
|
const concurrency = parseInt(getFlag('concurrency', ''), 10) || undefined;
|
|
64
66
|
|
|
65
67
|
try {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
dir = res.dir; entry = `${res.dir}/index.html`;
|
|
70
|
-
log(`✅ Course ready: ${res.lessons.length} lessons${res.failed.length ? ` (${res.failed.length} failed: ${res.failed.join(', ')})` : ''}`);
|
|
71
|
-
} else {
|
|
72
|
-
const res = await createLesson({ topic, engine, model, storage, outDir: out, log });
|
|
73
|
-
dir = res.dir; entry = res.file;
|
|
74
|
-
log(`✅ Lesson ready: ${res.file}`);
|
|
75
|
-
}
|
|
68
|
+
const res = await createCourse({ topic, engine, model, storage, outDir: out, lessons, concurrency, log });
|
|
69
|
+
const dir = res.dir;
|
|
70
|
+
log(`✅ Course ready: ${res.lessons.length} lessons${res.failed.length ? ` (${res.failed.length} failed: ${res.failed.join(', ')})` : ''}`);
|
|
76
71
|
|
|
77
72
|
if (has('deploy')) {
|
|
78
|
-
const
|
|
73
|
+
const dep = await deployDir({
|
|
79
74
|
dir, provider, prod: !has('preview'), dryRun: has('dry-run'), log,
|
|
80
75
|
});
|
|
81
|
-
if (
|
|
82
|
-
else log(`\n🎉 Live at: ${
|
|
76
|
+
if (dep.dryRun) log(`ℹ️ Dry run — would run: ${dep.command}`);
|
|
77
|
+
else log(`\n🎉 Live at: ${dep.url}`);
|
|
83
78
|
} else {
|
|
84
|
-
log(`\nPreview locally: npx serve "${dir}" (or open ${
|
|
85
|
-
log(`Deploy: coursecast
|
|
79
|
+
log(`\nPreview locally: npx serve "${dir}" (or open ${dir}/index.html)`);
|
|
80
|
+
log(`Deploy: coursecast "${topic}" --deploy --provider ${provider}`);
|
|
86
81
|
}
|
|
87
82
|
} catch (e) {
|
|
88
83
|
log(`\n❌ ${e.message}`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coursecast",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "One prompt →
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "One prompt → a full interactive course — live widgets, quizzes, a mastery dashboard — rendered to self-contained HTML and deployed live.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"coursecast": "bin/coursecast.mjs"
|
package/src/engines/gemini.mjs
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
// schema rather than inventing them from stale training data.
|
|
5
5
|
//
|
|
6
6
|
// Requires: GEMINI_API_KEY (free tier works — https://aistudio.google.com/apikey)
|
|
7
|
-
// Optional: GEMINI_MODEL (default "gemini-
|
|
7
|
+
// Optional: GEMINI_MODEL (default "gemini-3.5-flash"), COURSECAST_RESEARCH=0 to skip search.
|
|
8
8
|
|
|
9
9
|
import { buildSyllabusPrompt, buildLessonPrompt, finalizeLesson, extractJsonObject } from './shared-prompts.mjs'
|
|
10
10
|
|
|
11
11
|
export const name = 'gemini'
|
|
12
12
|
|
|
13
13
|
const API = 'https://generativelanguage.googleapis.com/v1beta/models'
|
|
14
|
-
const DEFAULT_MODEL = process.env.GEMINI_MODEL || 'gemini-
|
|
14
|
+
const DEFAULT_MODEL = process.env.GEMINI_MODEL || 'gemini-3.5-flash'
|
|
15
15
|
const RESEARCH_ON = process.env.COURSECAST_RESEARCH !== '0'
|
|
16
16
|
|
|
17
17
|
function apiKey() {
|
package/src/engines/stub.mjs
CHANGED
|
@@ -54,8 +54,8 @@ export async function generateLesson({ topic, slug, context }) {
|
|
|
54
54
|
js: `document.getElementById('stubBtn').addEventListener('click',function(){document.getElementById('stubOut').textContent='✅ Pipeline works — this is where a real simulation lives.';});`,
|
|
55
55
|
},
|
|
56
56
|
terminal: [
|
|
57
|
-
{ cmd: `coursecast
|
|
58
|
-
{ cmd: `coursecast
|
|
57
|
+
{ cmd: `coursecast "${t}"`, explain: 'Generates this whole course from a single prompt.' },
|
|
58
|
+
{ cmd: `coursecast "${t}" --deploy`, explain: 'Generates and deploys it live in one go.' },
|
|
59
59
|
],
|
|
60
60
|
checklist: [
|
|
61
61
|
{ title: 'Confirm the page renders', desc: 'You are reading it — the render step works.', hint: 'If styles are missing, the CSS did not inline correctly.' },
|