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 CHANGED
@@ -18,11 +18,11 @@ Requires Node 18+.
18
18
  ## Use
19
19
 
20
20
  ```bash
21
- # one interactive lesson
22
- coursecast new "how DNS works"
21
+ # a whole course (~12 lessons + mastery dashboard), from one prompt
22
+ coursecast "how DNS works"
23
23
 
24
- # a whole course (~12 lessons + dashboard), deployed live
25
- coursecast course "intro to nuclear power" --deploy
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 course "learn rust" --engine gemini
43
+ coursecast "learn rust" --engine gemini
44
44
  ```
45
45
 
46
- Gemini extras: `GEMINI_MODEL` overrides the model (default `gemini-2.5-flash`);
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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- // coursecast — one prompt → an interactive lesson → deployed live.
2
+ // coursecast — one prompt → a whole interactive course → deployed live.
3
3
 
4
- import { createLesson, createCourse, deployDir, slugify } from '../src/index.mjs';
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 — generate an interactive lesson from one prompt, and deploy it.
21
+ coursecast — one prompt a whole interactive course, deployed live.
22
22
 
23
23
  USAGE
24
- coursecast new "<topic>" [options] generate ONE interactive lesson
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-2.5-flash)
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> (course) target number of lessons (default ~12)
33
- --concurrency <n> (course) how many lessons to generate at once (default 4)
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 new "kubernetes basics" --engine stub
42
- coursecast course "how DNS works" --engine gemini # cheap, search-grounded
43
- GEMINI_API_KEY=... coursecast course "learn rust" --engine gemini
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
- const cmd = argv[0];
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
- if (cmd !== 'new' && cmd !== 'course') { log(`Unknown command "${cmd}".`); usage(); process.exit(1); }
53
-
54
- const topic = argv[1] && !argv[1].startsWith('--') ? argv[1] : null;
55
- if (!topic) { log(`❌ Provide a topic: coursecast ${cmd} "your topic"`); process.exit(1); }
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
- let dir, entry;
67
- if (cmd === 'course') {
68
- const res = await createCourse({ topic, engine, model, storage, outDir: out, lessons, concurrency, log });
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 res = await deployDir({
73
+ const dep = await deployDir({
79
74
  dir, provider, prod: !has('preview'), dryRun: has('dry-run'), log,
80
75
  });
81
- if (res.dryRun) log(`ℹ️ Dry run — would run: ${res.command}`);
82
- else log(`\n🎉 Live at: ${res.url}`);
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 ${entry})`);
85
- log(`Deploy: coursecast ${cmd} "${topic}" --deploy --provider ${provider}`);
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.0",
4
- "description": "One prompt → an interactive lesson or a full multi-lesson course with a mastery dashboard — rendered to self-contained HTML and deployed live.",
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"
@@ -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-2.5-flash"), COURSECAST_RESEARCH=0 to skip search.
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-2.5-flash'
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() {
@@ -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 new "${t}"`, explain: 'Generates this lesson from a single prompt.' },
58
- { cmd: `coursecast course "${t}" --deploy`, explain: 'Generates a whole course and deploys it live.' },
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.' },