retiam 2.0.1 → 2.0.3
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/THIRD_PARTY_NOTICES.md +3 -3
- package/agent/AGENTS.md +70 -0
- package/agent/CLAUDE.md +2 -0
- package/agent/skill/SKILL.md +114 -0
- package/agent/skill/examples/AreaLesson.ts +48 -0
- package/agent/skill/examples/ExplainMotion.ts +32 -0
- package/agent/skill/examples/NarratedMotion.ts +50 -0
- package/agent/skill/examples/SecantToTangent.ts +142 -0
- package/agent/skill/references/api.md +111 -0
- package/agent/skill/references/explainers.md +123 -0
- package/agent/skill/references/quality.md +151 -0
- package/agent/skill/references/styles.md +58 -0
- package/agent/skill/references/tts.md +166 -0
- package/agent/templates/explainer/lesson.json +25 -0
- package/agent/templates/explainer/review.json +6 -0
- package/agent/templates/explainer/styles.json +92 -0
- package/bin/agent-kit.js +26 -0
- package/bin/cli.js +24 -4
- package/bin/context.js +57 -0
- package/bin/init.js +71 -12
- package/bin/lesson-project.js +30 -0
- package/bin/lesson.js +195 -0
- package/bin/lint.js +6 -6
- package/bin/snapshot-runtime.js +185 -0
- package/bin/snapshot.js +32 -225
- package/dist/THIRD_PARTY_NOTICES.md +3 -3
- package/dist/UTIF-D6vx2UEX.min.js +1 -0
- package/dist/{UTIF-2Ear_0Uo.js → UTIF-mkffT70R.js} +1 -1
- package/dist/decompress-D7LzbvPD.min.js +15 -0
- package/dist/{index-B2sJBy4c.js → index-CdBPrYbr.js} +3830 -3795
- package/dist/index-jgFyrc2D.min.js +10472 -0
- package/dist/libs/explainer/example.d.ts +3 -0
- package/dist/libs/explainer/index.d.ts +5 -0
- package/dist/libs/explainer/index.js +135 -0
- package/dist/libs/explainer/plan.d.ts +12 -0
- package/dist/libs/explainer/plan.js +219 -0
- package/dist/libs/explainer/runtime.d.ts +24 -0
- package/dist/libs/explainer/styles.d.ts +3 -0
- package/dist/libs/explainer/types.d.ts +124 -0
- package/dist/libs/explainer/visuals.d.ts +18 -0
- package/dist/libs/tts/animations/Speak.d.ts +13 -7
- package/dist/libs/tts/components/TTSNarrationComponent.d.ts +9 -7
- package/dist/libs/tts/core/TTSCache.d.ts +3 -3
- package/dist/libs/tts/core/TTSClient.d.ts +3 -3
- package/dist/libs/tts/core/types.d.ts +48 -6
- package/dist/libs/tts/index.d.ts +2 -1
- package/dist/libs/tts/index.js +203 -1333
- package/dist/libs/tts/node/index.d.ts +10 -0
- package/dist/libs/tts/node/index.js +791 -0
- package/dist/libs/tts/providers/ElevenLabsTTSProvider.d.ts +30 -0
- package/dist/libs/tts/providers/ITTSProvider.d.ts +2 -2
- package/dist/libs/vite/index.d.ts +28 -0
- package/dist/libs/vite/index.js +158 -0
- package/dist/main-Cc4QQbSK.min.js +5939 -0
- package/dist/{main-D_G4uIju.js → main-DMu_5Qy7.js} +1 -1
- package/dist/msEdgeV2-D3w4Xsn-.js +642 -0
- package/dist/opentype.module-B-1cYAb-.min.js +1 -0
- package/dist/retiam.es.js +1 -1
- package/dist/retiam.es.min.js +1 -0
- package/dist/src/engine/animation/Animation.d.ts +1 -0
- package/dist/src/engine/entities/composites/text/mathjaxInterop.d.ts +6 -0
- package/package.json +15 -8
- package/readme.md +27 -7
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Explainer production with Retiam
|
|
2
|
+
|
|
3
|
+
Use this for educational animations. Ordinary title animations and other short
|
|
4
|
+
edits do not need a lesson plan. An AI coding harness authors the subject model,
|
|
5
|
+
script and TypeScript scenes; the CLI supplies deterministic production tools.
|
|
6
|
+
No particular AI provider or additional agent service is required.
|
|
7
|
+
|
|
8
|
+
## Start from the learner's question
|
|
9
|
+
|
|
10
|
+
Read `lesson/lesson.json` and the existing source. Establish the topic, audience,
|
|
11
|
+
prerequisites, learning objective, duration constraints and requested style from
|
|
12
|
+
the prompt. Infer routine choices; ask only about consequential missing context.
|
|
13
|
+
The supplied area lesson is a working API reference, not the requested subject.
|
|
14
|
+
Replace its model and beats when the user asks for another topic.
|
|
15
|
+
|
|
16
|
+
Research claims that need verification. Record source IDs, titles, links or
|
|
17
|
+
notes in `sources`, and connect factual claims to those IDs. Explicitly mark an
|
|
18
|
+
illustrative model. A numeric simulation may be correct for its assumptions yet
|
|
19
|
+
misrepresent the real system; explain assumptions and avoid false causality.
|
|
20
|
+
|
|
21
|
+
Plan a visible construction and the narration together. Useful beats include a
|
|
22
|
+
question, a prediction, a counterexample, a mechanism, a generalization, an
|
|
23
|
+
application and a resolved takeaway. Choose the sequence for the subject rather
|
|
24
|
+
than forcing every topic into the same chapter count. Each beat records `goal`,
|
|
25
|
+
`visibleChange` and `focus`. Text such as “explain the process” is not a visible
|
|
26
|
+
change; “a molecule passes through the membrane while concentration decreases” is.
|
|
27
|
+
|
|
28
|
+
## Select the visual mechanism
|
|
29
|
+
|
|
30
|
+
- Algorithms: derive graph, table, queue and code updates from one execution trace.
|
|
31
|
+
Check a result against an independent reference. Use native `Code` and
|
|
32
|
+
`retiam/graphs`; introduce auxiliary representations only when they help.
|
|
33
|
+
- Mathematics: vary a parameter, deform geometry, decompose areas, or connect a
|
|
34
|
+
construction to `Tex`. Transfer identity and color into the notation.
|
|
35
|
+
- Physics: use `retiam/physics` when rigid-body dynamics explain the phenomenon;
|
|
36
|
+
mathematical fields or a deterministic analytic model may be more appropriate.
|
|
37
|
+
- Biology and other processes: author an accurate conceptual model and use
|
|
38
|
+
local interactions, flows and scale changes. Use vectors, shapes or labeled
|
|
39
|
+
assets; a process illustration need not pretend to be a physical simulation.
|
|
40
|
+
- History, economics and data: anchor timelines, maps, comparisons and charts to
|
|
41
|
+
sources. Reveal changes and relationships without animating an unsupported
|
|
42
|
+
causal claim.
|
|
43
|
+
|
|
44
|
+
Use the matching native sublibrary for supported features. Do not import every
|
|
45
|
+
library just to exercise the engine. Novel subjects still need original scenes.
|
|
46
|
+
|
|
47
|
+
## Write for a curious teacher
|
|
48
|
+
|
|
49
|
+
Keep formal definitions and conditions exact. Around them, invite the learner to
|
|
50
|
+
predict, give space to think, and explain why an observed event matters. Vary the
|
|
51
|
+
rhythm: not every sentence needs a question. Avoid forced jokes and filler.
|
|
52
|
+
|
|
53
|
+
For example: “A currently says four. What if we go through B?” Then a real pause
|
|
54
|
+
while the route lights up. “Two, plus one… three. That's better. Let's remember it.”
|
|
55
|
+
Do not expose the candidate or subtitle answer during that prediction pause.
|
|
56
|
+
|
|
57
|
+
Keep performance notes out of spoken text. A narration segment has an `id`,
|
|
58
|
+
`text`, and optional `pauseAfterMs`. Segments need not correspond one-to-one to
|
|
59
|
+
visual actions. Split at purposeful thinking pauses, retaining coherent speech
|
|
60
|
+
between them. Edge remains the default. When ElevenLabs is selected, use its
|
|
61
|
+
native provider and the initialization/performance guidance in [tts.md](tts.md).
|
|
62
|
+
Keep caption/cue `text` clean; put delivery markup in a segment's `speechText`.
|
|
63
|
+
Other generators may normalize recordings into `PreparedSpeech`.
|
|
64
|
+
Include narration in an explainer unless the user requests a silent treatment.
|
|
65
|
+
The sample's disabled narration is an offline-preview convenience, not a reason
|
|
66
|
+
to omit the requested video's spoken explanation.
|
|
67
|
+
|
|
68
|
+
## Public workflow
|
|
69
|
+
|
|
70
|
+
The core authoring types and runtime are exported by `retiam/explainer`. Pure
|
|
71
|
+
validation, compilation and style resolution also import in Node through
|
|
72
|
+
`retiam/explainer/plan`. Find declarations with `retiam context --json`.
|
|
73
|
+
|
|
74
|
+
1. Edit the lesson and write a subject model under `src/`. Persist the requested
|
|
75
|
+
profile in `style`. `retiam lesson style excalidraw` selects the whiteboard look;
|
|
76
|
+
custom style objects extend a preset. Read [styles.md](styles.md) for the look.
|
|
77
|
+
2. Run `npm run lesson:check`. It checks schema/source references and any prepared
|
|
78
|
+
audio. Its `requiredReview` remains a separate obligation, not a quality score.
|
|
79
|
+
3. For narration set `narration.enabled` to true, then run
|
|
80
|
+
`npm run lesson:prepare`. First-time native synthesis needs network access;
|
|
81
|
+
subsequent calls use the native cache. It writes `lesson/prepared.json` and
|
|
82
|
+
public audio files. Keep them for reproducible export. Silent preview timings
|
|
83
|
+
are explicitly marked `estimated` and are not locked speech timings.
|
|
84
|
+
4. Import the lesson and optional prepared manifest into the scene. Use
|
|
85
|
+
`prepareLesson(this, plan, {prepared})`, then `await lesson.run(handlers)`.
|
|
86
|
+
All handlers are built before playback. Put mutations in animations or `b.at()`;
|
|
87
|
+
do not mutate shared objects directly while constructing later beat handlers.
|
|
88
|
+
5. Bind timing to `b.cue('name')` and `b.durationMs`. A cue identifies a segment
|
|
89
|
+
and optionally a spoken phrase; repeated phrases require `occurrence`.
|
|
90
|
+
Missing, ambiguous or stale cues fail visibly instead of drifting silently.
|
|
91
|
+
Check timing with `npm run lesson:timeline`.
|
|
92
|
+
6. Type-check and lint, then capture setup, prediction, reveal, transition and
|
|
93
|
+
conclusion frames. `lesson check --json` lists useful sample times. Open the
|
|
94
|
+
PNGs and watch representative short clips for pacing. Fix observed issues.
|
|
95
|
+
7. Render with the normal Retiam renderer and export captions with
|
|
96
|
+
`npm run lesson:captions`. Inspect final video/audio streams, listen to speech,
|
|
97
|
+
check synchronization and pauses, and update `lesson/review.json` with concrete
|
|
98
|
+
evidence. Report any unavailable checks honestly.
|
|
99
|
+
|
|
100
|
+
The runtime preloads audio, compiles one frame-aligned clock, and schedules all
|
|
101
|
+
clips and visual beat groups on that clock. Native speech and imported assets
|
|
102
|
+
share `durationMs`, word timestamps and a URL in `PreparedSpeech`. Caption cues
|
|
103
|
+
are grouped within speech segments so they cannot bridge a thinking pause.
|
|
104
|
+
|
|
105
|
+
## Small executable reference
|
|
106
|
+
|
|
107
|
+
Copy `../examples/AreaLesson.ts` from the skill directory into `src/scenes/` to
|
|
108
|
+
run it. `npx create-retiam my-video --template explainer` opens this construction
|
|
109
|
+
with the lesson JSON editable outside the scene. The sample is silent by default
|
|
110
|
+
so a fresh preview works offline; enable native narration when needed.
|
|
111
|
+
|
|
112
|
+
`styleGeometry`, `drawGeometry`, `transferValue` and `emphasize` supply reusable
|
|
113
|
+
motion behaviors. Use public Retiam entities for the rest of the construction.
|
|
114
|
+
A style profile does not automatically make a static scene explanatory.
|
|
115
|
+
|
|
116
|
+
## Review evidence
|
|
117
|
+
|
|
118
|
+
In `lesson/review.json`, keep automatic command results separate from observed
|
|
119
|
+
facts. Record a frame/clip path, the beat reviewed, what was observed and whether
|
|
120
|
+
it was fixed. Inspect overlaps, readable label sizes, stable identity, meaningful
|
|
121
|
+
motion, prediction/reveal order, and a conclusion with time to read. Check claims
|
|
122
|
+
against sources and domain calculations against independent results. A passing
|
|
123
|
+
plan check cannot establish factual or pedagogical correctness.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# 3Blue1Brown-inspired visual explanations
|
|
2
|
+
|
|
3
|
+
The default direction is a 3Blue1Brown-inspired mathematical explainer, built
|
|
4
|
+
with Retiam. Teach through visual intuition and evolving constructions. Match
|
|
5
|
+
the user's subject, audience, duration, and aspect ratio; an explicitly different
|
|
6
|
+
style or palette overrides this default.
|
|
7
|
+
|
|
8
|
+
## Default visual and narrative direction
|
|
9
|
+
|
|
10
|
+
- **Start with a question worth seeing answered.** Give the viewer a concrete
|
|
11
|
+
example to predict or inspect. Build intuition before naming the general rule.
|
|
12
|
+
- **Make the mathematical objects the main actors.** Let a graph, geometry,
|
|
13
|
+
field, system, or construction occupy the stage. Develop it through the
|
|
14
|
+
explanation, with enough spatial continuity to follow what changed and why.
|
|
15
|
+
- **Use a clean dark canvas.** Favor black or deep charcoal, crisp geometry,
|
|
16
|
+
white mathematical typography, and a few clear accents. Blue and warm yellow
|
|
17
|
+
are useful starting accents; assign colors by mathematical role and preserve
|
|
18
|
+
those roles between diagrams, symbols, and transitions. Keep grids subdued.
|
|
19
|
+
- **Let notation grow from the construction.** Introduce a formula when the
|
|
20
|
+
diagram has established its meaning. Place corresponding terms and objects
|
|
21
|
+
in the same colors. Transform relevant terms deliberately, exposing the
|
|
22
|
+
reasoning instead of replacing complete screens of equations.
|
|
23
|
+
- **Choreograph discoveries.** Use smooth drawing, motion, decomposition,
|
|
24
|
+
regrouping, comparison, and parameter sweeps. Focus the camera or emphasize
|
|
25
|
+
an object when a detail becomes important. Leave time for the viewer to make
|
|
26
|
+
an inference before stating it, then test or generalize the insight.
|
|
27
|
+
- **Narrate as an explanation.** When using narration, generate it through
|
|
28
|
+
`retiam/tts`. Use conversational questions and precise language, synchronize
|
|
29
|
+
speech with visible events, and allow meaningful silent pauses. Keep requested
|
|
30
|
+
subtitles unobtrusive so they leave room for the mathematical construction.
|
|
31
|
+
Match vocal delivery to the beat's question, discovery, or resolution. For
|
|
32
|
+
expressive ElevenLabs delivery follow [tts.md](tts.md): choose model-compatible
|
|
33
|
+
cues, listen to a representative sample, and preserve clean caption text.
|
|
34
|
+
|
|
35
|
+
The goal is a clear, satisfying visual argument. Choose 2D or 3D according to
|
|
36
|
+
the concept. Use a slide, dashboard, or another aesthetic when requested.
|
|
37
|
+
|
|
38
|
+
## Plan events the viewer can see
|
|
39
|
+
|
|
40
|
+
For each substantial beat, identify:
|
|
41
|
+
|
|
42
|
+
| Decision | Useful answer |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| What must become clear? | A relationship, cause, comparison, or mechanism |
|
|
45
|
+
| What visibly changes? | A path, configuration, parameter, grouping, or viewpoint |
|
|
46
|
+
| What carries continuity? | The same point, node, symbol, shape, or color meaning |
|
|
47
|
+
| Where should the eye go? | One object or relationship at a time |
|
|
48
|
+
| What warrants a pause? | A meaningful comparison or resolved result |
|
|
49
|
+
|
|
50
|
+
Write an action such as “the second point travels along the curve while the
|
|
51
|
+
secant rotates and the gap collapses.” “Explain the derivative” or “show the next
|
|
52
|
+
heading” does not specify a visual event. Derive the number and length of beats
|
|
53
|
+
from the explanation; do not impose the same five scenes on every subject.
|
|
54
|
+
|
|
55
|
+
Prefer an evolving construction: establish the objects, expose a contrast or
|
|
56
|
+
problem, show the mechanism changing the state, then resolve and generalize.
|
|
57
|
+
Preserve enough context that the viewer can understand each transformation.
|
|
58
|
+
|
|
59
|
+
## Choreograph meaning
|
|
60
|
+
|
|
61
|
+
- **Carry the idea in geometry.** Trace an actual route, move a point along its
|
|
62
|
+
function, deform the existing object, reveal how pieces assemble, or run the
|
|
63
|
+
simulation. Text describes a result after the viewer has seen its cause.
|
|
64
|
+
- **Preserve identity.** Move or transform the same object between states. Keep
|
|
65
|
+
variable colors consistent between diagrams and formulas. Use matching-symbol
|
|
66
|
+
transforms for a meaningful equation change when appropriate.
|
|
67
|
+
When unrelated symbols should disappear, use `morphUnmatched: false` rather
|
|
68
|
+
than deforming every leftover glyph into a new one.
|
|
69
|
+
- **Direct attention.** Establish context, emphasize the current relationship,
|
|
70
|
+
and widen the view for the result. Zoom or pan only when a close relationship,
|
|
71
|
+
changing frontier, or new scale benefits from it. A well-composed static shot
|
|
72
|
+
can support excellent motion; constant camera movement can obscure it.
|
|
73
|
+
- **Use a few distinct motion functions.** Drawing reveals structure; travel
|
|
74
|
+
shows flow; a morph preserves correspondence; a brief pulse marks a state
|
|
75
|
+
change; a hold gives time to infer. Choose the functions the concept needs.
|
|
76
|
+
- **Coordinate timing.** Related changes should happen together. Give an action
|
|
77
|
+
an appropriate acceleration, settle, and readable pause. Use `Concurrent`
|
|
78
|
+
with an explicit group duration so its defaults do not retime the children.
|
|
79
|
+
Use linear progress for uniform physical/data sweeps and eased motion for
|
|
80
|
+
entrances or changes that should settle; follow the actual simulated model.
|
|
81
|
+
- **Keep labels local and short.** Anchor a cost near the affected node or a
|
|
82
|
+
quantity beside its geometric span. Prefer a few precise words to paragraphs.
|
|
83
|
+
Use `Tex` for formulas and meaningful term matching, not plain text equations.
|
|
84
|
+
- **Let narration explain why.** Align each utterance with the event it explains.
|
|
85
|
+
If requested, use `retiam/tts` and its prepared audio duration. Repeatedly
|
|
86
|
+
replacing paragraphs beside an otherwise idle diagram is not enough motion.
|
|
87
|
+
|
|
88
|
+
Choose colors for hierarchy and meaning, with enough contrast for the output.
|
|
89
|
+
Keep guides quieter than active geometry and leave negative space. The default
|
|
90
|
+
dark palette supports the construction; glow, 3D, and sound effects need an
|
|
91
|
+
explanatory purpose.
|
|
92
|
+
|
|
93
|
+
## Presentation patterns to revise
|
|
94
|
+
|
|
95
|
+
For an animation brief, revise these patterns unless they serve a specific need:
|
|
96
|
+
|
|
97
|
+
- A permanent title/header, large body panel, footer, chapter number, and page
|
|
98
|
+
counter framing every beat in the same layout.
|
|
99
|
+
- A diagram occupying one corner while most of the frame is explanatory text.
|
|
100
|
+
- An entire screen fading away, followed by a new heading and another fade-in.
|
|
101
|
+
- A long voiceover over a static scene with only a progress bar or decorative glow.
|
|
102
|
+
- Identical reveal timing for every beat, or unrelated objects appearing at once.
|
|
103
|
+
- An algorithm shown mainly as table rows changing while its mechanism stays idle.
|
|
104
|
+
|
|
105
|
+
For example, a Dijkstra explanation can begin with a question about competing
|
|
106
|
+
routes in the graph. Let the viewer predict a winner, then show why counting
|
|
107
|
+
roads can fail when their costs differ. Make the reason for finalizing the
|
|
108
|
+
cheapest unsettled cost visible using the nonnegative-edge assumption.
|
|
109
|
+
Trace the inspected edge, show the candidate cost beside the affected node, and
|
|
110
|
+
visibly retain or reject that route. Move attention with the active frontier,
|
|
111
|
+
keep cost and settled-state meanings consistent, then dim explored clutter and
|
|
112
|
+
trace the winning path. Use a priority queue or table when explaining that
|
|
113
|
+
structure, rather than reserving half the canvas for one throughout the video.
|
|
114
|
+
Follow actual weighted relaxation order; decorative propagation must not imply
|
|
115
|
+
an incorrect algorithm.
|
|
116
|
+
|
|
117
|
+
## Working reference
|
|
118
|
+
|
|
119
|
+
[SecantToTangent.ts](../examples/SecantToTangent.ts) is a silent visual reference:
|
|
120
|
+
|
|
121
|
+
| Time | Visible event | Purpose |
|
|
122
|
+
| --- | --- | --- |
|
|
123
|
+
| 0–3.1 s | Draw the curve, mark two points, introduce the secant and its gap | Establish the construction |
|
|
124
|
+
| 3.1–6.3 s | Move the second point along the curve; the line rotates and the gap shrinks | Show what determines the secant slope |
|
|
125
|
+
| 6.3–11.1 s | Bring attention closer and continue the convergence | Make the limiting relationship visible |
|
|
126
|
+
| 11.1–13.1 s | Resolve the tangent and transform the equation | Connect geometry to notation |
|
|
127
|
+
| 13.1–19.5 s | Move the tangent point across both sides of the curve, updating the slope | Generalize beyond one frozen example |
|
|
128
|
+
| 19.5–21.5 s | Hold the resolved construction | Allow the takeaway to register |
|
|
129
|
+
|
|
130
|
+
The smaller `ExplainMotion.ts` and `NarratedMotion.ts` examples isolate API and
|
|
131
|
+
speech mechanics. They are not visual templates for finished explainers.
|
|
132
|
+
|
|
133
|
+
## Review the rendered evidence
|
|
134
|
+
|
|
135
|
+
Capture the setup, the middle of the main mechanism, a transition, and the resolved
|
|
136
|
+
state at the intended aspect ratio. Open the PNGs and watch playback.
|
|
137
|
+
|
|
138
|
+
- Is the mathematical/data relationship correct throughout the motion?
|
|
139
|
+
- Does the construction establish intuition before introducing the general rule?
|
|
140
|
+
- Do corresponding geometric objects and mathematical terms keep their color meaning?
|
|
141
|
+
- Is the focal object clear, with readable labels and no accidental overlap or clipping?
|
|
142
|
+
- Does the main action demonstrate the idea without requiring every caption?
|
|
143
|
+
- Can the viewer follow the same object through transitions and reframing?
|
|
144
|
+
- Do motion and pauses feel purposeful when watched at normal speed?
|
|
145
|
+
- Does the conclusion remain visible long enough to read?
|
|
146
|
+
|
|
147
|
+
Compare the middle of adjacent beats. If only the heading or paragraph changes,
|
|
148
|
+
redesign the visible action. A deliberate reading hold or final rest is useful;
|
|
149
|
+
idle slides throughout an explanation need revision. Record concrete observed
|
|
150
|
+
issues and fix them. Lint and type checks cannot judge aesthetic quality or
|
|
151
|
+
explanatory correctness, and static screenshots cannot establish pacing.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Visual style profiles
|
|
2
|
+
|
|
3
|
+
Teaching goals and visual style are independent. Read the user's prompt first.
|
|
4
|
+
An explicit requested style overrides `lesson/lesson.json`; save that choice so
|
|
5
|
+
future revisions remain consistent. Keep the same mathematical/content model and
|
|
6
|
+
narration cues when restyling. Preserve explicit brand, aspect and accessibility
|
|
7
|
+
constraints. `retiam lesson styles --json` gives actual tokens for this version.
|
|
8
|
+
|
|
9
|
+
## mathematical — default, matching the Dijkstra video
|
|
10
|
+
|
|
11
|
+
Use navy `#0A1020`, light text, vivid blue and gold, with mint, coral, violet and
|
|
12
|
+
teal available as stable object identities. Color carries meaning across the
|
|
13
|
+
construction, labels, formulas and data transfers. Do not turn the palette into
|
|
14
|
+
random per-frame decoration. Let lines trace real paths, values travel between
|
|
15
|
+
representations, replacements settle smoothly and discarded candidates fade.
|
|
16
|
+
Ask a question, allow a thinking pause, then reveal its answer through geometry.
|
|
17
|
+
Use native `Tex` and syntax-highlighted `Code` where they explain the subject.
|
|
18
|
+
Introduce tables and queues as teaching objects only when relevant. Maintain
|
|
19
|
+
spatial continuity through derivations and applications; conclude with the idea
|
|
20
|
+
that the evolving construction has established.
|
|
21
|
+
|
|
22
|
+
## excalidraw — hand-drawn whiteboard
|
|
23
|
+
|
|
24
|
+
Use warm paper, dark ink, blue and amber markers. The profile selects Retiam's
|
|
25
|
+
bundled `excalifont` for vector labels. Use native `toExcalidraw` through
|
|
26
|
+
`styleGeometry`, and `drawGeometry` for progressive sketch drawing. Use seeded,
|
|
27
|
+
restrained roughness, intentional arrows and light hatching. Keep mathematical
|
|
28
|
+
symbols readable in `Tex`. A question, sketched experiment and resolving annotation
|
|
29
|
+
can share one evolving board. Use dark paper as a custom override if requested.
|
|
30
|
+
Do not apply roughness to every text glyph or continuously jitter all labels.
|
|
31
|
+
|
|
32
|
+
## minimal — clear vector construction
|
|
33
|
+
|
|
34
|
+
Use a light canvas, precise outlines, generous negative space and a small number
|
|
35
|
+
of semantic colors. Motion should isolate a relation: align, compare, decompose,
|
|
36
|
+
transfer or resolve. Keep labels local and avoid filling the quiet space with
|
|
37
|
+
paragraphs. Minimal graphics can still carry a lively teacher's narration.
|
|
38
|
+
|
|
39
|
+
## cinematic — deliberate depth and staging
|
|
40
|
+
|
|
41
|
+
Use a dark canvas, violet/teal accents, slower establishing moves and a strong
|
|
42
|
+
focus for the current event. Use depth or 3D when it explains a spatial relation;
|
|
43
|
+
2D with deliberate staging is also valid. Lighting and highlights support the
|
|
44
|
+
mechanism. Keep labels and formulas clear as the camera moves, and let the
|
|
45
|
+
resolved state rest. Motion quality does not require constant camera movement.
|
|
46
|
+
|
|
47
|
+
## Custom override
|
|
48
|
+
|
|
49
|
+
A prompt can describe a new palette or reference look. Preserve the selected
|
|
50
|
+
profile's useful behavior while overriding tokens in JSON, for example:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{"style":{"extends":"excalidraw","name":"Night chalkboard","background":"#122322","ink":"#F4F2DD","primary":"#7DE1C4","secondary":"#F1CC75","muted":"#94AAA5","accents":["#7DE1C4","#F1CC75"]}}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`resolveStyle` validates the tokens. Fonts must be bundled aliases or available
|
|
57
|
+
user-supplied font assets. Profile changes affect only entities using the profile;
|
|
58
|
+
agent-authored custom colors/materials must be reviewed during a restyle.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Retiam narration: synthesis and performance
|
|
2
|
+
|
|
3
|
+
Use `retiam/tts` for speech, timing, subtitles, and the scene voiceover bus.
|
|
4
|
+
Microsoft Edge is the default. Use the native `ElevenLabsTTSProvider` when the
|
|
5
|
+
user selects ElevenLabs and supplies credentials. Preserve the chosen provider;
|
|
6
|
+
diagnose failures rather than silently switching generators or installing Python
|
|
7
|
+
TTS tools. Read the installed declarations before using newer options.
|
|
8
|
+
|
|
9
|
+
## ElevenLabs: initialize the provider directly
|
|
10
|
+
|
|
11
|
+
No ElevenLabs Vite plugin or Vite configuration is needed. For a self-contained
|
|
12
|
+
scene example, put `new ElevenLabsTTSProvider()`, `await provider.init({ apiKey,
|
|
13
|
+
voiceId, ... })`, and `await provider.synthesize(...)` inside `Scene.run()`.
|
|
14
|
+
Pass the key directly to initialization. A `.env`, `process.env`, a preparation
|
|
15
|
+
script, and pre-generated audio are optional workflows, not setup requirements;
|
|
16
|
+
do not introduce them when the user requests a simple in-scene example.
|
|
17
|
+
Register synthesis in `composer.pendingTasks` before awaiting it for headless
|
|
18
|
+
rendering. A provider instance can be used directly; selecting it globally is
|
|
19
|
+
only needed when using the `TTS`, `Speak`, or `speak()` facades.
|
|
20
|
+
|
|
21
|
+
To select an initialized provider globally:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { ElevenLabsTTSProvider, TTS } from 'retiam/tts';
|
|
25
|
+
|
|
26
|
+
export async function useExpressiveVoice(apiKey: string, voiceId: string) {
|
|
27
|
+
const provider = new ElevenLabsTTSProvider();
|
|
28
|
+
await provider.init({
|
|
29
|
+
apiKey, voiceId,
|
|
30
|
+
modelId: 'eleven_v3',
|
|
31
|
+
voiceSettings: { stability: 0.5 },
|
|
32
|
+
});
|
|
33
|
+
TTS.setProvider(provider);
|
|
34
|
+
return provider;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`apiKey` and `voiceId` come from the caller's setup, not literal credentials
|
|
39
|
+
committed to a scene. Browser initialization uses the caller's key directly and
|
|
40
|
+
makes it accessible to that browser. For private preparation use Node's
|
|
41
|
+
`retiam/tts/node` entry point, with the same provider API, and ship the generated
|
|
42
|
+
audio. Do not add ElevenLabs credentials or middleware to `vite.config.ts`.
|
|
43
|
+
Initialization validates local settings without generating audio or fetching
|
|
44
|
+
voices. Use `await provider.fetchVoices()` when choosing a voice;
|
|
45
|
+
`provider.getVoices()` and `TTS.findVoice()` then use that fetched catalog.
|
|
46
|
+
Require a real ElevenLabs voice ID, not an Edge ID or an invented display name.
|
|
47
|
+
|
|
48
|
+
The default model is `eleven_multilingual_v2`. Select an available expressive
|
|
49
|
+
model such as `eleven_v3` or `eleven_v4` deliberately when directing with audio
|
|
50
|
+
tags. Keep a compatible model/voice pairing throughout a speaker's performance.
|
|
51
|
+
The provider accepts open model IDs, MP3 output formats, model-specific voice
|
|
52
|
+
settings, language, seed, pronunciation dictionaries, and neighboring-text context.
|
|
53
|
+
Per-line overrides go in `elevenlabs`; `speed` can also be supplied at the top
|
|
54
|
+
level. Omitted voice/rate settings inherit provider defaults through `Speak`,
|
|
55
|
+
`speak()` and `TTSNarrationComponent`. `TTS.init()` preserves the selected provider;
|
|
56
|
+
`TTS.configure()` explicitly restores the default Edge client.
|
|
57
|
+
|
|
58
|
+
## Direct a natural explanation
|
|
59
|
+
|
|
60
|
+
Choose the emotional action of a beat: inviting a prediction, weighing a
|
|
61
|
+
possibility, making a discovery, or calmly resolving the explanation. Let the
|
|
62
|
+
voice express that action. A constant promotional excitement or a cue on every
|
|
63
|
+
sentence makes an explanation harder to follow. Keep the speaker recognizable
|
|
64
|
+
across beats, with a voice suited to the requested language and audience.
|
|
65
|
+
|
|
66
|
+
Write speech for listening: concrete questions, contractions, varied sentence
|
|
67
|
+
lengths, and emphasis on the relationship changing on screen. Read technical
|
|
68
|
+
symbols as intended speech; keep formulas and concise notation in the visuals.
|
|
69
|
+
Prefer phrase or paragraph performances to synthesizing isolated words, so the
|
|
70
|
+
model can carry an idea through to its conclusion. Split at genuine explanatory
|
|
71
|
+
boundaries, preserving neighboring context when needed.
|
|
72
|
+
|
|
73
|
+
Model-specific controls:
|
|
74
|
+
|
|
75
|
+
- On v3/v4, use occasional delivery tags such as `[curious]` or `[excited]` for
|
|
76
|
+
a meaningful change in delivery. These models do not support SSML break tags;
|
|
77
|
+
use punctuation and phrasing for vocal pauses. Test the chosen voice's delivery.
|
|
78
|
+
- On v2 models, use natural punctuation and supported SSML breaks sparingly.
|
|
79
|
+
Do not assume v3 audio tags work on v2 or Edge. Edge supports its own `pitch`
|
|
80
|
+
prosody; ElevenLabs rejects nonzero Edge pitch settings.
|
|
81
|
+
- Lower stability can increase variation; higher stability can reduce it.
|
|
82
|
+
Treat settings as voice/model-dependent. Adjust a short representative sample
|
|
83
|
+
after listening, rather than maximizing style or copying one preset everywhere.
|
|
84
|
+
- Supported speed is 0.7–1.2. Start close to the voice's natural pace and use
|
|
85
|
+
explicit scene holds for prediction time. A seed is best-effort, not guaranteed
|
|
86
|
+
repeatability; approved local audio provides repeatable rendering.
|
|
87
|
+
|
|
88
|
+
Keep performance directions out of captions. Supply clean `displayText` when
|
|
89
|
+
synthesis text contains delivery or pronunciation markup:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { Speak } from 'retiam/tts';
|
|
93
|
+
|
|
94
|
+
export function directedLine() {
|
|
95
|
+
return new Speak('[curious] What changes if we move this point?', {
|
|
96
|
+
displayText: 'What changes if we move this point?',
|
|
97
|
+
elevenlabs: { modelId: 'eleven_v3', voiceSettings: { stability: 0.5 } },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Listen for pronunciation, expression, unwanted vocalizations, rushed endings,
|
|
103
|
+
and pauses before approving a voice. Regenerate only the lines that need it.
|
|
104
|
+
Keep approved audio across visual revisions; settings alone cannot lock a
|
|
105
|
+
performance. Do not spend credits merely to test whether initialization works.
|
|
106
|
+
See the model-specific [ElevenLabs delivery guide](https://elevenlabs.io/docs/overview/capabilities/text-to-speech/best-practices)
|
|
107
|
+
when using an unfamiliar model or control.
|
|
108
|
+
|
|
109
|
+
## Lessons and prepared speech
|
|
110
|
+
|
|
111
|
+
For ElevenLabs lessons set `narration.provider` to `elevenlabs`, set `voice` to
|
|
112
|
+
the chosen voice ID, and put model/delivery settings under `narration.elevenlabs`.
|
|
113
|
+
Keep each segment's `text` clean for captions and cue phrases. Optional
|
|
114
|
+
`speechText` contains the exact synthesis script, including delivery tags.
|
|
115
|
+
Keep actual prediction/reveal gaps in `pauseAfterMs`, separate from vocal rhythm.
|
|
116
|
+
|
|
117
|
+
`retiam lesson prepare` supports both native providers. For ElevenLabs the CLI
|
|
118
|
+
reads `ELEVENLABS_API_KEY` from its environment and passes it to provider.init().
|
|
119
|
+
The key does not go in the lesson JSON. It caches audio and word timings and
|
|
120
|
+
writes `lesson/prepared.json` plus `public/narration/*.mp3`. Reuse those assets
|
|
121
|
+
for subsequent preview and render. Changing delivery text or voice settings
|
|
122
|
+
invalidates the corresponding prepared speech. The runtime can also synthesize
|
|
123
|
+
using an initialized matching global provider or an explicit synthesis adapter.
|
|
124
|
+
|
|
125
|
+
## Edge browser and renderer setup
|
|
126
|
+
|
|
127
|
+
Only Edge uses the existing Vite proxy. New projects already enable it. For an
|
|
128
|
+
older project, preserve other plugins and add `msEdgeTTSPlugin` from
|
|
129
|
+
`retiam/vite` on the server side. Its route is `/api/tts/msedge/synthesize` in
|
|
130
|
+
Vite development/preview and Retiam's renderer. Static hosts need that server
|
|
131
|
+
route or prepared audio. Never import the Vite plugin into scene code.
|
|
132
|
+
|
|
133
|
+
## Timeline recipe
|
|
134
|
+
|
|
135
|
+
Use [NarratedMotion.ts](../examples/NarratedMotion.ts) as the executable
|
|
136
|
+
integration recipe, adapting its speech and visual action to the brief.
|
|
137
|
+
|
|
138
|
+
- Use `Speak` for simultaneous narration and motion. Set `entity` to an entity
|
|
139
|
+
already added to the scene, so playback uses that scene's audio bus.
|
|
140
|
+
- Call `prepareAudio()` before locking timings. Register the synthesis promise
|
|
141
|
+
in `this.composer.pendingTasks` before awaiting it. Do not register playback
|
|
142
|
+
promises or `this.play(...)`: those require timeline steps and would deadlock.
|
|
143
|
+
- Derive motion duration from prepared `Speak.duration`. Pass that duration to
|
|
144
|
+
`Concurrent` too, so its defaults do not rescale the narration.
|
|
145
|
+
- `await speak(this, text, options)` handles playback, subtitles, ducking and
|
|
146
|
+
scene pacing. For headless use, pre-synthesize the same text/options and
|
|
147
|
+
register only synthesis as pending work. The provider caches that result.
|
|
148
|
+
- `TTS.speak(...)` is a lower-level client facade; it does not pace the scene.
|
|
149
|
+
- Add `Subtitles` and pass it to the narration. Caption text uses the generated
|
|
150
|
+
audio's `text`; ElevenLabs character alignment is converted to word timings
|
|
151
|
+
for lesson cues. Missing timing metadata is not a license to guess cue times.
|
|
152
|
+
- `stream()` yields sentence-sized complete audio results, not transport byte
|
|
153
|
+
chunks. A directed line with `displayText` stays one performance.
|
|
154
|
+
|
|
155
|
+
## Diagnose and verify
|
|
156
|
+
|
|
157
|
+
For ElevenLabs, check initialization, voice/model compatibility, API permissions,
|
|
158
|
+
quota/rate-limit errors, and network access. Errors surface without automatic
|
|
159
|
+
paid retries. After disposal the provider requires initialization again. For
|
|
160
|
+
Edge, a 404 means missing middleware/route; a 502 means upstream synthesis
|
|
161
|
+
failed. Inspect the actual error before changing providers.
|
|
162
|
+
|
|
163
|
+
Browser autoplay may require a user gesture; headless tools supply their own
|
|
164
|
+
autoplay flag. Verify that exported video contains non-silent audio, listen to
|
|
165
|
+
the performance, and check synchronization. A type check or visual snapshot
|
|
166
|
+
does not establish speech quality.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"title": "Why does area grow so fast?",
|
|
4
|
+
"topic": "How a square's area changes when its side length doubles",
|
|
5
|
+
"audience": "Curious beginners",
|
|
6
|
+
"objective": "See why doubling both side lengths produces four unit squares.",
|
|
7
|
+
"style": "mathematical",
|
|
8
|
+
"narration": {"enabled": false, "provider": "msedge", "voice": "en-US-AndrewNeural", "speed": 1, "pitch": "+0Hz", "volume": 1},
|
|
9
|
+
"sources": [],
|
|
10
|
+
"claims": [{"id":"area-model","text":"A square of side two can be partitioned into four squares of side one.","sourceIds":[],"illustrative":true}],
|
|
11
|
+
"beats": [
|
|
12
|
+
{"id":"predict","goal":"Invite a prediction before exposing the area.","visibleChange":"Draw one unit square and mark its side length.","focus":"The original square","minDurationMs":4500,
|
|
13
|
+
"narration":[{"id":"question","text":"Here is one unit square. If we double its side length, what happens to its area?","pauseAfterMs":900}],
|
|
14
|
+
"cues":[{"id":"show","segment":"question"}]},
|
|
15
|
+
{"id":"grow","goal":"Separate a change in side length from a change in area.","visibleChange":"Expand the outline while retaining the original square.","focus":"Both sides grow","minDurationMs":5000,
|
|
16
|
+
"narration":[{"id":"expand","text":"Let's double both sides. The original square still fits in the corner. How much new space have we made?","pauseAfterMs":700}],
|
|
17
|
+
"cues":[{"id":"expand","segment":"expand"}]},
|
|
18
|
+
{"id":"count","goal":"Discover the four equal areas through decomposition.","visibleChange":"Reveal the three additional unit squares in sequence.","focus":"Four equal pieces","minDurationMs":5000,
|
|
19
|
+
"narration":[{"id":"reveal","text":"One here, another above, and one more in the corner. Together with our original square, that makes four."}],
|
|
20
|
+
"cues":[{"id":"reveal","segment":"reveal"}]},
|
|
21
|
+
{"id":"connect","goal":"Connect the construction to the general area relationship.","visibleChange":"Keep all four pieces visible and reveal the equation below them.","focus":"Side length and area","minDurationMs":4500,
|
|
22
|
+
"narration":[{"id":"insight","text":"Two times the side length gives four times the area. That's the square in side squared: two growing directions, multiplied together."}],
|
|
23
|
+
"cues":[{"id":"equation","segment":"insight"}]}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"automated": {},
|
|
3
|
+
"observations": [],
|
|
4
|
+
"requiredReview": ["Check factual claims and illustrative model assumptions.","Inspect the beginning, prediction pause, main mechanism, transition and conclusion.","Watch a short preview at normal speed to assess pacing and attention.","Verify the final audio track, captions and video dimensions."],
|
|
5
|
+
"status": "not-reviewed"
|
|
6
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mathematical": {
|
|
3
|
+
"name": "Dijkstra-style mathematical explainer",
|
|
4
|
+
"background": "#0A1020",
|
|
5
|
+
"ink": "#ECF0F5",
|
|
6
|
+
"muted": "#74869C",
|
|
7
|
+
"primary": "#6CD5FF",
|
|
8
|
+
"secondary": "#F7CF73",
|
|
9
|
+
"font": "roboto",
|
|
10
|
+
"strokeWidth": 0.035,
|
|
11
|
+
"rough": false,
|
|
12
|
+
"roughness": 0,
|
|
13
|
+
"fillStyle": "solid",
|
|
14
|
+
"motion": {
|
|
15
|
+
"drawMs": 1200,
|
|
16
|
+
"moveMs": 1400,
|
|
17
|
+
"holdMs": 700
|
|
18
|
+
},
|
|
19
|
+
"accents": [
|
|
20
|
+
"#65F0C4",
|
|
21
|
+
"#FF879F",
|
|
22
|
+
"#BA98FF",
|
|
23
|
+
"#55DDE0"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"excalidraw": {
|
|
27
|
+
"name": "Excalidraw whiteboard",
|
|
28
|
+
"background": "#FFFDF7",
|
|
29
|
+
"ink": "#263238",
|
|
30
|
+
"muted": "#7B858C",
|
|
31
|
+
"primary": "#1971C2",
|
|
32
|
+
"secondary": "#D46B08",
|
|
33
|
+
"font": "excalifont",
|
|
34
|
+
"strokeWidth": 0.04,
|
|
35
|
+
"rough": true,
|
|
36
|
+
"roughness": 0.7,
|
|
37
|
+
"fillStyle": "hachure",
|
|
38
|
+
"motion": {
|
|
39
|
+
"drawMs": 1600,
|
|
40
|
+
"moveMs": 1500,
|
|
41
|
+
"holdMs": 900
|
|
42
|
+
},
|
|
43
|
+
"accents": [
|
|
44
|
+
"#1971C2",
|
|
45
|
+
"#D46B08"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"minimal": {
|
|
49
|
+
"name": "Minimal vector",
|
|
50
|
+
"background": "#F8FAFC",
|
|
51
|
+
"ink": "#17212B",
|
|
52
|
+
"muted": "#68788A",
|
|
53
|
+
"primary": "#2563EB",
|
|
54
|
+
"secondary": "#C2410C",
|
|
55
|
+
"font": "roboto",
|
|
56
|
+
"strokeWidth": 0.025,
|
|
57
|
+
"rough": false,
|
|
58
|
+
"roughness": 0,
|
|
59
|
+
"fillStyle": "solid",
|
|
60
|
+
"motion": {
|
|
61
|
+
"drawMs": 900,
|
|
62
|
+
"moveMs": 1100,
|
|
63
|
+
"holdMs": 650
|
|
64
|
+
},
|
|
65
|
+
"accents": [
|
|
66
|
+
"#2563EB",
|
|
67
|
+
"#C2410C"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"cinematic": {
|
|
71
|
+
"name": "Cinematic diagrams",
|
|
72
|
+
"background": "#080B18",
|
|
73
|
+
"ink": "#F4F1FF",
|
|
74
|
+
"muted": "#8290AA",
|
|
75
|
+
"primary": "#B79AFF",
|
|
76
|
+
"secondary": "#58E0CE",
|
|
77
|
+
"font": "roboto",
|
|
78
|
+
"strokeWidth": 0.04,
|
|
79
|
+
"rough": false,
|
|
80
|
+
"roughness": 0,
|
|
81
|
+
"fillStyle": "solid",
|
|
82
|
+
"motion": {
|
|
83
|
+
"drawMs": 1500,
|
|
84
|
+
"moveMs": 2000,
|
|
85
|
+
"holdMs": 1000
|
|
86
|
+
},
|
|
87
|
+
"accents": [
|
|
88
|
+
"#B79AFF",
|
|
89
|
+
"#58E0CE"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
}
|
package/bin/agent-kit.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import {copyLessonKit} from './lesson-project.js';
|
|
5
|
+
|
|
6
|
+
export const agentKitRoot = fileURLToPath(new URL('../agent/', import.meta.url));
|
|
7
|
+
|
|
8
|
+
/** Used only while creating an empty project or preparing a fresh template. */
|
|
9
|
+
export function copyAgentKit(directory, options = {}) {
|
|
10
|
+
const files = [];
|
|
11
|
+
for (const name of ['AGENTS.md', 'CLAUDE.md']) {
|
|
12
|
+
fs.copyFileSync(path.join(agentKitRoot, name), path.join(directory, name));
|
|
13
|
+
files.push(name);
|
|
14
|
+
}
|
|
15
|
+
const destination = path.join(directory, '.agents/skills/retiam');
|
|
16
|
+
fs.cpSync(path.join(agentKitRoot, 'skill'), destination, { recursive: true });
|
|
17
|
+
function collect(folder) {
|
|
18
|
+
for (const entry of fs.readdirSync(folder, { withFileTypes: true })) {
|
|
19
|
+
const location = path.join(folder, entry.name);
|
|
20
|
+
if (entry.isDirectory()) collect(location);
|
|
21
|
+
else files.push(path.relative(directory, location).split(path.sep).join('/'));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
collect(destination);
|
|
25
|
+
return [...files,...copyLessonKit(directory,options)];
|
|
26
|
+
}
|