cotutor 0.2.0 → 0.4.0
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/LICENSE +249 -21
- package/NOTICE +42 -0
- package/README.md +115 -20
- package/avatars/chinese.webp +0 -0
- package/avatars/english.webp +0 -0
- package/avatars/math.webp +0 -0
- package/bin/cotutor.js +15 -5
- package/cards/canvas/card.css +8 -0
- package/cards/canvas/card.md +68 -0
- package/cards/choice/card.css +18 -0
- package/cards/choice/card.md +55 -0
- package/cards/code/card.css +5 -0
- package/cards/code/card.md +46 -0
- package/cards/dictation/card.css +10 -0
- package/cards/dictation/card.md +97 -0
- package/cards/image/card.css +10 -0
- package/cards/image/card.md +52 -0
- package/cards/lecture/card.css +9 -0
- package/cards/lecture/card.md +69 -0
- package/cards/paint/card.css +20 -0
- package/cards/paint/card.md +77 -0
- package/cards/photo/card.css +22 -0
- package/cards/photo/card.md +61 -0
- package/cards/read/card.css +6 -0
- package/cards/read/card.md +51 -0
- package/cards/text/card.css +3 -0
- package/cards/text/card.md +69 -0
- package/cards/tianzige/card.css +17 -0
- package/cards/tianzige/card.md +78 -0
- package/cards/tutor/card.css +79 -0
- package/cards/tutor/card.md +149 -0
- package/cards/word/card.css +24 -0
- package/cards/word/card.md +91 -0
- package/cards//346/235/277/344/271/246/346/200/216/344/271/210/345/206/231.md +159 -0
- package/dist/app/activity-eval-rules.d.ts +62 -0
- package/dist/app/activity-eval-rules.js +276 -0
- package/dist/app/activity-eval.d.ts +65 -0
- package/dist/app/activity-eval.js +159 -0
- package/dist/app/build.d.ts +11 -0
- package/dist/app/build.js +45 -0
- package/dist/app/cli/activities.d.ts +70 -0
- package/dist/app/cli/activities.js +196 -0
- package/dist/app/cli/cert.d.ts +12 -0
- package/dist/app/cli/cert.js +49 -0
- package/dist/app/cli/chalk/bake.d.ts +10 -0
- package/dist/app/cli/chalk/bake.js +58 -0
- package/dist/app/cli/chalk/browser.d.ts +16 -0
- package/dist/app/cli/chalk/browser.js +157 -0
- package/dist/app/cli/chalk/build.d.ts +14 -0
- package/dist/app/cli/chalk/build.js +100 -0
- package/dist/app/cli/chalk/dub.d.ts +51 -0
- package/dist/app/cli/chalk/dub.js +106 -0
- package/dist/app/cli/chalk/load-scene.d.ts +34 -0
- package/dist/app/cli/chalk/load-scene.js +117 -0
- package/dist/app/cli/chalk/main.d.ts +7 -0
- package/dist/app/cli/chalk/main.js +199 -0
- package/dist/app/cli/chalk/serve.d.ts +4 -0
- package/dist/app/cli/chalk/serve.js +28 -0
- package/dist/app/cli/chalk/snap.d.ts +65 -0
- package/dist/app/cli/chalk/snap.js +103 -0
- package/dist/app/cli/chalk/typecheck.d.ts +7 -0
- package/dist/app/cli/chalk/typecheck.js +51 -0
- package/dist/app/cli/doctor.d.ts +24 -0
- package/dist/app/cli/doctor.js +553 -0
- package/dist/app/cli/firewall.d.ts +24 -0
- package/dist/app/cli/firewall.js +79 -0
- package/dist/app/cli/grades.d.ts +14 -0
- package/dist/app/cli/grades.js +44 -0
- package/dist/{cli → app/cli}/init.d.ts +3 -1
- package/dist/app/cli/init.js +158 -0
- package/dist/app/cli/main.js +1038 -0
- package/dist/app/cli/migrate.d.ts +49 -0
- package/dist/app/cli/migrate.js +195 -0
- package/dist/app/cli/port-holder.d.ts +16 -0
- package/dist/app/cli/port-holder.js +34 -0
- package/dist/app/cli/port.d.ts +37 -0
- package/dist/app/cli/port.js +116 -0
- package/dist/app/cli/sandbox.d.ts +25 -0
- package/dist/app/cli/sandbox.js +166 -0
- package/dist/app/cli/serve-log.d.ts +13 -0
- package/dist/app/cli/serve-log.js +90 -0
- package/dist/app/cli/serve.d.ts +51 -0
- package/dist/app/cli/serve.js +168 -0
- package/dist/app/cli/service-windows.d.ts +50 -0
- package/dist/app/cli/service-windows.js +117 -0
- package/dist/app/cli/service.d.ts +138 -0
- package/dist/app/cli/service.js +429 -0
- package/dist/app/cli/shipped.d.ts +30 -0
- package/dist/app/cli/shipped.js +47 -0
- package/dist/app/cli/skeleton.d.ts +38 -0
- package/dist/app/cli/skeleton.js +161 -0
- package/dist/app/cli/skills.d.ts +59 -0
- package/dist/app/cli/skills.js +199 -0
- package/dist/app/cli/themes.d.ts +52 -0
- package/dist/app/cli/themes.js +148 -0
- package/dist/app/cli/tray-icons.d.ts +11 -0
- package/dist/app/cli/tray-icons.js +87 -0
- package/dist/app/cli/tray-ids.d.ts +6 -0
- package/dist/app/cli/tray-ids.js +6 -0
- package/dist/app/cli/tray-mac.d.ts +8 -0
- package/dist/app/cli/tray-mac.js +140 -0
- package/dist/app/cli/tray-run.d.ts +1 -0
- package/dist/app/cli/tray-run.js +33 -0
- package/dist/app/cli/tray-windows.d.ts +5 -0
- package/dist/app/cli/tray-windows.js +166 -0
- package/dist/app/cli/tray.d.ts +205 -0
- package/dist/app/cli/tray.js +618 -0
- package/dist/{cli → app/cli}/tutors.d.ts +15 -26
- package/dist/{cli → app/cli}/tutors.js +86 -110
- package/dist/app/cli/usage.d.ts +2 -0
- package/dist/app/cli/usage.js +35 -0
- package/dist/app/dictation/recognize.d.ts +18 -0
- package/dist/app/dictation/recognize.js +6 -0
- package/dist/app/dictation/route.d.ts +52 -0
- package/dist/app/dictation/route.js +511 -0
- package/dist/app/diff.d.ts +11 -0
- package/dist/app/diff.js +71 -0
- package/dist/app/http/access.d.ts +62 -0
- package/dist/app/http/access.js +180 -0
- package/dist/app/http/app.d.ts +241 -0
- package/dist/app/http/app.js +1159 -0
- package/dist/app/http/avatars.d.ts +17 -0
- package/dist/app/http/avatars.js +35 -0
- package/dist/app/http/dev-activities.d.ts +115 -0
- package/dist/app/http/dev-activities.js +193 -0
- package/dist/app/http/dev-overview.d.ts +108 -0
- package/dist/app/http/dev-overview.js +209 -0
- package/dist/app/http/dev-tutor-files.d.ts +80 -0
- package/dist/app/http/dev-tutor-files.js +151 -0
- package/dist/app/http/parent-home.d.ts +51 -0
- package/dist/app/http/parent-home.js +266 -0
- package/dist/app/http/parent-look.d.ts +2 -0
- package/dist/app/http/parent-look.js +151 -0
- package/dist/app/http/range.d.ts +9 -0
- package/dist/app/http/range.js +19 -0
- package/dist/app/http/raw-view.d.ts +117 -0
- package/dist/app/http/raw-view.js +149 -0
- package/dist/app/http/send-file.d.ts +7 -0
- package/dist/app/http/send-file.js +29 -0
- package/dist/{server → app/http}/stage.d.ts +9 -0
- package/dist/{server → app/http}/stage.js +34 -6
- package/dist/app/http/theme.d.ts +13 -0
- package/dist/app/http/theme.js +41 -0
- package/dist/app/http/voice-preview.d.ts +4 -0
- package/dist/app/http/voice-preview.js +34 -0
- package/dist/app/machine/bailian.d.ts +26 -0
- package/dist/app/machine/bailian.js +87 -0
- package/dist/app/mock/mock-live.d.ts +16 -0
- package/dist/app/mock/mock-live.js +140 -0
- package/dist/{server → app/mock}/mock.d.ts +38 -5
- package/dist/app/mock/mock.js +1125 -0
- package/dist/app/replay.d.ts +94 -0
- package/dist/app/replay.js +170 -0
- package/dist/app/skills/analyze-doc.d.ts +3 -0
- package/dist/app/skills/analyze-doc.js +47 -0
- package/dist/app/skills/home-doc.d.ts +5 -0
- package/dist/app/skills/home-doc.js +112 -0
- package/dist/app/skills/tune-doc.d.ts +3 -0
- package/dist/app/skills/tune-doc.js +80 -0
- package/dist/app/talk/list.d.ts +11 -0
- package/dist/app/talk/list.js +9 -0
- package/dist/app/talk/route.d.ts +16 -0
- package/dist/app/talk/route.js +142 -0
- package/dist/app/workspace.d.ts +63 -0
- package/dist/{cli → app}/workspace.js +100 -52
- package/dist/app/ws/route.d.ts +31 -0
- package/dist/app/ws/route.js +33 -0
- package/dist/app/ws/ws.d.ts +31 -0
- package/dist/app/ws/ws.js +169 -0
- package/dist/board/canvas-chat.d.ts +97 -0
- package/dist/board/canvas-chat.js +175 -0
- package/dist/board/card-guards.d.ts +15 -0
- package/dist/board/card-guards.js +38 -0
- package/dist/board/cards/canvas.d.ts +41 -0
- package/dist/board/cards/canvas.js +83 -0
- package/dist/{cards → board/cards}/choice.js +0 -12
- package/dist/{cards → board/cards}/code.js +0 -3
- package/dist/board/cards/dictation.d.ts +42 -0
- package/dist/board/cards/dictation.js +105 -0
- package/dist/board/cards/docs.d.ts +47 -0
- package/dist/board/cards/docs.js +152 -0
- package/dist/{cards → board/cards}/image.js +1 -10
- package/dist/board/cards/index.d.ts +62 -0
- package/dist/board/cards/index.js +144 -0
- package/dist/{cards → board/cards}/kind.d.ts +10 -4
- package/dist/board/cards/kind.js +9 -0
- package/dist/board/cards/lecture.d.ts +26 -0
- package/dist/board/cards/lecture.js +62 -0
- package/dist/board/cards/paint.d.ts +38 -0
- package/dist/board/cards/paint.js +85 -0
- package/dist/board/cards/photo.d.ts +15 -0
- package/dist/board/cards/photo.js +29 -0
- package/dist/{cards → board/cards}/read.js +1 -10
- package/dist/board/cards/text.d.ts +20 -0
- package/dist/board/cards/text.js +59 -0
- package/dist/board/cards/tianzige.d.ts +16 -0
- package/dist/board/cards/tianzige.js +64 -0
- package/dist/board/cards/tutor.d.ts +72 -0
- package/dist/board/cards/tutor.js +134 -0
- package/dist/board/cards/word.d.ts +20 -0
- package/dist/board/cards/word.js +76 -0
- package/dist/board/lecture.d.ts +100 -0
- package/dist/board/lecture.js +204 -0
- package/dist/board/paper.d.ts +20 -0
- package/dist/board/paper.js +76 -0
- package/dist/board/parse.d.ts +118 -0
- package/dist/board/parse.js +324 -0
- package/dist/board/presentation.d.ts +289 -0
- package/dist/board/presentation.js +613 -0
- package/dist/board/sections.d.ts +16 -0
- package/dist/board/sections.js +130 -0
- package/dist/brain/api-tools.d.ts +8 -0
- package/dist/brain/api-tools.js +191 -0
- package/dist/brain/api.d.ts +57 -0
- package/dist/brain/api.js +394 -0
- package/dist/brain/index.d.ts +97 -0
- package/dist/brain/index.js +11 -0
- package/dist/brain/log.d.ts +5 -0
- package/dist/brain/log.js +22 -0
- package/dist/brain/runtime.d.ts +30 -0
- package/dist/brain/runtime.js +23 -0
- package/dist/brain/stream-json.d.ts +16 -0
- package/dist/brain/stream-json.js +82 -0
- package/dist/brain/stream.d.ts +20 -0
- package/dist/brain/stream.js +70 -0
- package/dist/brain/transcript.d.ts +45 -0
- package/dist/brain/transcript.js +132 -0
- package/dist/brain/types.d.ts +99 -0
- package/dist/brain/types.js +14 -0
- package/dist/chalk/actions.d.ts +37 -0
- package/dist/chalk/actions.js +69 -0
- package/dist/chalk/bundle.d.ts +26 -0
- package/dist/chalk/bundle.js +69 -0
- package/dist/chalk/check.d.ts +28 -0
- package/dist/chalk/check.js +291 -0
- package/dist/chalk/circle.d.ts +64 -0
- package/dist/chalk/circle.js +193 -0
- package/dist/chalk/clock.d.ts +84 -0
- package/dist/chalk/clock.js +84 -0
- package/dist/chalk/geometry.d.ts +44 -0
- package/dist/chalk/geometry.js +139 -0
- package/dist/chalk/glyphs/capitals.d.ts +12 -0
- package/dist/chalk/glyphs/capitals.js +264 -0
- package/dist/chalk/glyphs/index.d.ts +21 -0
- package/dist/chalk/glyphs/index.js +18 -0
- package/dist/chalk/glyphs/letters.d.ts +13 -0
- package/dist/chalk/glyphs/letters.js +280 -0
- package/dist/chalk/glyphs/metrics.d.ts +33 -0
- package/dist/chalk/glyphs/metrics.js +28 -0
- package/dist/chalk/glyphs/registry.d.ts +7 -0
- package/dist/chalk/glyphs/registry.js +8 -0
- package/dist/chalk/glyphs/sample.d.ts +32 -0
- package/dist/chalk/glyphs/sample.js +102 -0
- package/dist/chalk/glyphs/skeleton.d.ts +64 -0
- package/dist/chalk/glyphs/skeleton.js +126 -0
- package/dist/chalk/glyphs/types.d.ts +58 -0
- package/dist/chalk/glyphs/types.js +17 -0
- package/dist/chalk/index.d.ts +23 -0
- package/dist/chalk/index.js +15 -0
- package/dist/chalk/manifest.d.ts +41 -0
- package/dist/chalk/manifest.js +6 -0
- package/dist/chalk/paint.d.ts +180 -0
- package/dist/chalk/paint.js +210 -0
- package/dist/chalk/pointer.d.ts +8 -0
- package/dist/chalk/pointer.js +21 -0
- package/dist/chalk/prepare.d.ts +11 -0
- package/dist/chalk/prepare.js +15 -0
- package/dist/chalk/scene-schema.d.ts +199 -0
- package/dist/chalk/scene-schema.js +91 -0
- package/dist/chalk/seed.d.ts +3 -0
- package/dist/chalk/seed.js +10 -0
- package/dist/chalk/text-metrics.d.ts +8 -0
- package/dist/chalk/text-metrics.js +156 -0
- package/dist/chalk/timeline.d.ts +47 -0
- package/dist/chalk/timeline.js +84 -0
- package/dist/chalk/types.d.ts +150 -0
- package/dist/chalk/types.js +8 -0
- package/dist/lib/index.d.ts +14 -12
- package/dist/lib/index.js +14 -12
- package/dist/media/draw.d.ts +23 -0
- package/dist/media/draw.js +44 -0
- package/dist/media/mp3.d.ts +6 -0
- package/dist/media/mp3.js +58 -0
- package/dist/media/mp4.d.ts +1 -0
- package/dist/media/mp4.js +58 -0
- package/dist/media/tts-voices.d.ts +18 -0
- package/dist/media/tts-voices.js +606 -0
- package/dist/media/tts.d.ts +56 -0
- package/dist/media/tts.js +174 -0
- package/dist/media/vision.d.ts +25 -0
- package/dist/media/vision.js +71 -0
- package/dist/memory/commit.d.ts +67 -0
- package/dist/memory/commit.js +130 -0
- package/dist/memory/context.d.ts +86 -0
- package/dist/memory/context.js +97 -0
- package/dist/memory/diary.d.ts +84 -0
- package/dist/memory/diary.js +183 -0
- package/dist/memory/grade.d.ts +2 -0
- package/dist/memory/grade.js +7 -0
- package/dist/{lib → memory}/plan.d.ts +1 -1
- package/dist/{lib → memory}/plan.js +1 -1
- package/dist/memory/proposals.d.ts +11 -0
- package/dist/memory/proposals.js +43 -0
- package/dist/memory/retrieval.d.ts +44 -0
- package/dist/memory/retrieval.js +157 -0
- package/dist/{lib → memory}/timetable.d.ts +0 -3
- package/dist/{lib → memory}/timetable.js +0 -11
- package/dist/memory/vault-notes.d.ts +165 -0
- package/dist/memory/vault-notes.js +359 -0
- package/dist/memory/vault.d.ts +70 -0
- package/dist/memory/vault.js +169 -0
- package/dist/realtime/bailian.d.ts +120 -0
- package/dist/realtime/bailian.js +289 -0
- package/dist/realtime/cues.d.ts +51 -0
- package/dist/realtime/cues.js +94 -0
- package/dist/realtime/listen.d.ts +32 -0
- package/dist/realtime/listen.js +53 -0
- package/dist/realtime/preview.d.ts +23 -0
- package/dist/realtime/preview.js +38 -0
- package/dist/realtime/prompt.d.ts +8 -0
- package/dist/realtime/prompt.js +13 -0
- package/dist/realtime/voices.d.ts +21 -0
- package/dist/realtime/voices.js +71 -0
- package/dist/runtime/activities.d.ts +29 -0
- package/dist/runtime/activities.js +72 -0
- package/dist/runtime/activity-state.d.ts +22 -0
- package/dist/runtime/activity-state.js +83 -0
- package/dist/runtime/activity.d.ts +63 -0
- package/dist/runtime/activity.js +428 -0
- package/dist/runtime/agent-file.d.ts +24 -0
- package/dist/runtime/agent-file.js +59 -0
- package/dist/runtime/auto-bookkeep.d.ts +30 -0
- package/dist/runtime/auto-bookkeep.js +84 -0
- package/dist/{lib → runtime/context}/context-pack.d.ts +3 -3
- package/dist/runtime/context/context-pack.js +182 -0
- package/dist/runtime/context/thread-digest.d.ts +4 -0
- package/dist/runtime/context/thread-digest.js +35 -0
- package/dist/runtime/context/tutor-rules.d.ts +25 -0
- package/dist/runtime/context/tutor-rules.js +47 -0
- package/dist/runtime/delivery/delivery.d.ts +94 -0
- package/dist/runtime/delivery/delivery.js +145 -0
- package/dist/runtime/delivery/playback.d.ts +25 -0
- package/dist/runtime/delivery/playback.js +29 -0
- package/dist/runtime/delivery/player.d.ts +145 -0
- package/dist/runtime/delivery/player.js +221 -0
- package/dist/runtime/delivery/queue.d.ts +89 -0
- package/dist/runtime/delivery/queue.js +59 -0
- package/dist/runtime/home/edit.d.ts +39 -0
- package/dist/runtime/home/edit.js +132 -0
- package/dist/runtime/home/home.d.ts +191 -0
- package/dist/runtime/home/home.js +318 -0
- package/dist/runtime/home/store.d.ts +142 -0
- package/dist/runtime/home/store.js +455 -0
- package/dist/runtime/journal/conversation.d.ts +46 -0
- package/dist/runtime/journal/conversation.js +89 -0
- package/dist/runtime/journal/events.d.ts +27 -0
- package/dist/runtime/journal/events.js +136 -0
- package/dist/runtime/journal/kid-log.d.ts +105 -0
- package/dist/runtime/journal/kid-log.js +133 -0
- package/dist/runtime/journal/ledger.d.ts +17 -0
- package/dist/{lib → runtime/journal}/ledger.js +7 -40
- package/dist/runtime/journal/mix.d.ts +42 -0
- package/dist/runtime/journal/mix.js +129 -0
- package/dist/runtime/journal/mouth-index.d.ts +26 -0
- package/dist/runtime/journal/mouth-index.js +34 -0
- package/dist/runtime/journal/mouth.d.ts +54 -0
- package/dist/runtime/journal/mouth.js +102 -0
- package/dist/runtime/journal/recorder.d.ts +91 -0
- package/dist/runtime/journal/recorder.js +148 -0
- package/dist/runtime/journal/store.d.ts +123 -0
- package/dist/runtime/journal/store.js +316 -0
- package/dist/runtime/kid-input.d.ts +16 -0
- package/dist/runtime/kid-input.js +194 -0
- package/dist/runtime/kid-view.d.ts +124 -0
- package/dist/runtime/kid-view.js +157 -0
- package/dist/runtime/lecture.d.ts +86 -0
- package/dist/runtime/lecture.js +237 -0
- package/dist/runtime/live/call.d.ts +231 -0
- package/dist/runtime/live/call.js +1244 -0
- package/dist/runtime/live/turns.d.ts +48 -0
- package/dist/runtime/live/turns.js +102 -0
- package/dist/runtime/runner.d.ts +243 -0
- package/dist/runtime/runner.js +777 -0
- package/dist/runtime/talk-run.d.ts +21 -0
- package/dist/runtime/talk-run.js +34 -0
- package/dist/scenes/dictation/match.d.ts +57 -0
- package/dist/scenes/dictation/match.js +255 -0
- package/dist/scenes/dictation/recognize.d.ts +86 -0
- package/dist/scenes/dictation/recognize.js +163 -0
- package/dist/scenes/dictation/store.d.ts +112 -0
- package/dist/scenes/dictation/store.js +99 -0
- package/dist/scenes/look.d.ts +26 -0
- package/dist/scenes/look.js +1 -0
- package/dist/scenes/paint/painter.d.ts +84 -0
- package/dist/scenes/paint/painter.js +171 -0
- package/dist/scenes/paint/prompt.d.ts +23 -0
- package/dist/scenes/paint/prompt.js +36 -0
- package/dist/scenes/talk/en-words.d.ts +8 -0
- package/dist/scenes/talk/en-words.js +44 -0
- package/dist/scenes/talk/list.d.ts +44 -0
- package/dist/scenes/talk/list.js +88 -0
- package/dist/scenes/talk/store.d.ts +27 -0
- package/dist/scenes/talk/store.js +61 -0
- package/dist/scenes/talk/warmup.d.ts +16 -0
- package/dist/scenes/talk/warmup.js +39 -0
- package/dist/schema/activity.d.ts +149 -0
- package/dist/schema/activity.js +113 -0
- package/dist/schema/board.d.ts +143 -2
- package/dist/schema/board.js +15 -3
- package/dist/schema/config.d.ts +339 -103
- package/dist/schema/config.js +269 -84
- package/dist/schema/context-pack.d.ts +76 -14
- package/dist/schema/context-pack.js +91 -10
- package/dist/{lib/conversation.d.ts → schema/conversation-files.d.ts} +11 -33
- package/dist/schema/conversation-files.js +26 -0
- package/dist/schema/conversation.d.ts +391 -49
- package/dist/schema/conversation.js +135 -19
- package/dist/schema/events.d.ts +138 -0
- package/dist/schema/events.js +1 -0
- package/dist/schema/home.d.ts +56 -0
- package/dist/schema/home.js +35 -0
- package/dist/schema/index.d.ts +14 -3
- package/dist/schema/index.js +14 -3
- package/dist/schema/kid-device.d.ts +20 -0
- package/dist/schema/kid-device.js +32 -0
- package/dist/schema/ledger.d.ts +8 -63
- package/dist/schema/ledger.js +7 -31
- package/dist/schema/memory.d.ts +73 -0
- package/dist/schema/memory.js +47 -0
- package/dist/schema/plan.d.ts +2 -2
- package/dist/schema/plan.js +2 -2
- package/dist/schema/sections.d.ts +32 -25
- package/dist/schema/sections.js +21 -22
- package/dist/schema/theme.d.ts +25 -0
- package/dist/schema/theme.js +26 -0
- package/dist/schema/timetable.d.ts +2 -2
- package/dist/schema/timetable.js +2 -2
- package/dist/schema/workspace.d.ts +68 -0
- package/dist/schema/workspace.js +37 -0
- package/dist/shared/default-port.d.ts +2 -0
- package/dist/shared/default-port.js +2 -0
- package/dist/shared/errors.d.ts +8 -0
- package/dist/shared/errors.js +13 -0
- package/dist/shared/home.d.ts +2 -0
- package/dist/shared/home.js +16 -0
- package/dist/shared/human-notes.d.ts +5 -0
- package/dist/shared/human-notes.js +7 -0
- package/dist/shared/qianwen.d.ts +54 -0
- package/dist/shared/qianwen.js +113 -0
- package/dist/shared/site-url.d.ts +2 -0
- package/dist/shared/site-url.js +5 -0
- package/dist/shared/time.d.ts +9 -0
- package/dist/shared/time.js +22 -0
- package/dist/shared/tokens.d.ts +18 -0
- package/dist/shared/tokens.js +11 -0
- package/dist/shared/wav.d.ts +10 -0
- package/dist/shared/wav.js +50 -0
- package/dist/stage/chunk-226TNLQA.js +129 -0
- package/dist/stage/chunk-24LGQEKE.js +1 -0
- package/dist/stage/chunk-25XV6UY3.js +10 -0
- package/dist/stage/chunk-2AZI3NHJ.js +1 -0
- package/dist/stage/chunk-2BQZTTBE.js +1 -0
- package/dist/stage/chunk-2M7CGY3Y.js +11 -0
- package/dist/stage/chunk-2VR4WFQ7.js +10 -0
- package/dist/stage/chunk-2ZYXBUWG.js +9 -0
- package/dist/stage/chunk-32LAWMGY.js +1 -0
- package/dist/stage/chunk-3LEMTSOO.js +11 -0
- package/dist/stage/chunk-3QCKGZYE.js +32 -0
- package/dist/stage/chunk-44P66WFA.js +1 -0
- package/dist/stage/chunk-4AM6FRR6.js +7 -0
- package/dist/stage/chunk-4QDE4SUK.js +1 -0
- package/dist/stage/chunk-4QESHV3U.js +1 -0
- package/dist/stage/chunk-4SG5QPHB.js +2 -0
- package/dist/stage/chunk-4T4UVCOC.js +1 -0
- package/dist/stage/chunk-4WUEPPG3.js +34 -0
- package/dist/stage/chunk-4YCN6UZ5.js +99 -0
- package/dist/stage/chunk-5V7ZY2AP.js +1 -0
- package/dist/stage/chunk-5VS5Y2VJ.js +6 -0
- package/dist/stage/chunk-5W4NO2ZD.js +62 -0
- package/dist/stage/chunk-65CRNICM.js +1 -0
- package/dist/stage/chunk-66MULO7F.js +5 -0
- package/dist/stage/chunk-6COFQTBS.js +162 -0
- package/dist/stage/chunk-6VZG3M3I.js +8 -0
- package/dist/stage/chunk-7HVDH43T.js +1 -0
- package/dist/stage/chunk-7JLJN4EZ.js +8 -0
- package/dist/stage/chunk-7QDSP4LN.js +1 -0
- package/dist/stage/chunk-7R6OQAO3.js +1 -0
- package/dist/stage/chunk-7VTTHZKW.js +7 -0
- package/dist/stage/chunk-ATHR5KEN.js +10 -0
- package/dist/stage/chunk-B6R5LTP2.js +36 -0
- package/dist/stage/chunk-BD4WE264.js +1 -0
- package/dist/stage/chunk-BONANU47.js +206 -0
- package/dist/stage/chunk-BTVW7OFK.js +89 -0
- package/dist/stage/chunk-BUTJIDHG.js +8 -0
- package/dist/stage/chunk-C2IPYMML.js +84 -0
- package/dist/stage/chunk-C54FTPPV.js +9 -0
- package/dist/stage/chunk-C6LOUF2Z.js +90 -0
- package/dist/stage/chunk-CFF3PILO.js +339 -0
- package/dist/stage/chunk-CI2FQ2JY.js +9 -0
- package/dist/stage/chunk-CW5O4RV7.js +12 -0
- package/dist/stage/chunk-CYABNQYD.js +1 -0
- package/dist/stage/chunk-D7FFA4NS.js +15 -0
- package/dist/stage/chunk-DUNNXU6Z.js +8 -0
- package/dist/stage/chunk-E3E4Q6JF.js +6 -0
- package/dist/stage/chunk-EB3U3LH7.js +9 -0
- package/dist/stage/chunk-EGSZ736B.js +8 -0
- package/dist/stage/chunk-EOBXW5VQ.js +2 -0
- package/dist/stage/chunk-ERZC3B7Y.js +11 -0
- package/dist/stage/chunk-ESDOOBIH.js +11 -0
- package/dist/stage/chunk-EVU4QAL4.js +178 -0
- package/dist/stage/chunk-EVVZI2NY.js +39 -0
- package/dist/stage/chunk-FBKAZUKD.js +39 -0
- package/dist/stage/chunk-FDVKYQJN.js +1 -0
- package/dist/stage/chunk-FGPVYHVR.js +5 -0
- package/dist/stage/chunk-FHUD4AZC.js +1 -0
- package/dist/stage/chunk-FKUJ2DMY.js +96 -0
- package/dist/stage/chunk-FNZGVIJV.js +1 -0
- package/dist/stage/chunk-GBYSEGYM.js +78 -0
- package/dist/stage/chunk-GCO4TDHX.js +1 -0
- package/dist/stage/chunk-GKTML2LG.js +10 -0
- package/dist/stage/chunk-GMDIHHXQ.js +11 -0
- package/dist/stage/chunk-GOUNACI3.js +10 -0
- package/dist/stage/chunk-GXAYATPF.js +3 -0
- package/dist/stage/chunk-H2K2DDXV.js +8 -0
- package/dist/stage/chunk-H3UJI6ZY.js +13 -0
- package/dist/stage/chunk-H6PUGCME.js +7 -0
- package/dist/stage/chunk-HNBUIMIG.js +1 -0
- package/dist/stage/chunk-HRZY2BJQ.js +10 -0
- package/dist/stage/chunk-HSPJGALW.js +30 -0
- package/dist/stage/chunk-IBYORHIP.js +25 -0
- package/dist/stage/chunk-IHF4EWTY.js +1 -0
- package/dist/stage/chunk-ISBRPNKI.js +1 -0
- package/dist/stage/chunk-IZ3OI3C2.js +2 -0
- package/dist/stage/chunk-JEFZAC2R.js +1 -0
- package/dist/stage/chunk-JFIGRDPO.js +1 -0
- package/dist/stage/chunk-JFVYBOOC.js +9 -0
- package/dist/stage/chunk-JGD4TUTQ.js +1 -0
- package/dist/stage/chunk-JO4BEHDH.js +4 -0
- package/dist/stage/chunk-JOWBS42C.js +1 -0
- package/dist/stage/chunk-JP3TM2TG.js +1 -0
- package/dist/stage/chunk-JVK2C52O.js +1 -0
- package/dist/stage/chunk-K4I76XBX.js +14 -0
- package/dist/stage/chunk-KA6CZZ4M.js +1 -0
- package/dist/stage/chunk-KESSJ36M.js +1 -0
- package/dist/stage/chunk-LKATEVRU.js +1 -0
- package/dist/stage/chunk-LS4EJ2EI.js +1 -0
- package/dist/stage/chunk-LUKJQC2X.js +70 -0
- package/dist/stage/chunk-LWULR3SM.js +1 -0
- package/dist/stage/chunk-LWVRFMKX.js +2 -0
- package/dist/stage/chunk-LXC4ZBPZ.js +1 -0
- package/dist/stage/chunk-M2UP255H.js +1 -0
- package/dist/stage/chunk-MAPPXUZQ.js +231 -0
- package/dist/stage/chunk-MBMPW7Z5.js +1 -0
- package/dist/stage/chunk-MD3HMMTK.js +1 -0
- package/dist/stage/chunk-MLPCUIKR.js +17 -0
- package/dist/stage/chunk-MNQIRPDQ.js +8 -0
- package/dist/stage/chunk-MRG7MJVS.js +1 -0
- package/dist/stage/chunk-MU6EXW72.js +10 -0
- package/dist/stage/chunk-MVU2X6LU.js +4 -0
- package/dist/stage/chunk-N6UA5CZV.js +36 -0
- package/dist/stage/chunk-NLZIT2TJ.js +7 -0
- package/dist/stage/chunk-NOJR3J7M.js +1 -0
- package/dist/stage/chunk-NT7ARDOL.js +1 -0
- package/dist/stage/chunk-NWOGMPZH.js +1 -0
- package/dist/stage/chunk-NXE4KMG3.js +40 -0
- package/dist/stage/chunk-O4YCZXD2.js +257 -0
- package/dist/stage/chunk-OAJZ7HLK.js +1 -0
- package/dist/stage/chunk-OCFCFASO.js +1 -0
- package/dist/stage/chunk-ODGPG5RY.js +7 -0
- package/dist/stage/chunk-ODUEA52Q.js +3 -0
- package/dist/stage/chunk-OGDJ3FCI.js +1 -0
- package/dist/stage/chunk-OLDJ55TT.js +1 -0
- package/dist/stage/chunk-OOCL4ATI.js +2 -0
- package/dist/stage/chunk-OQEFDUDV.js +1 -0
- package/dist/stage/chunk-OQWVEWG7.js +4 -0
- package/dist/stage/chunk-OUIX6BGK.js +1 -0
- package/dist/stage/chunk-OV7OZYUO.js +7 -0
- package/dist/stage/chunk-PFGDY33O.js +1 -0
- package/dist/stage/chunk-PFJDUJB3.js +139 -0
- package/dist/stage/chunk-PGEW76ER.js +1 -0
- package/dist/stage/chunk-PP76TAQX.js +32 -0
- package/dist/stage/chunk-PTBOP4OT.js +2 -0
- package/dist/stage/chunk-PX2RVHCK.js +1 -0
- package/dist/stage/chunk-Q7K3PNAD.js +1 -0
- package/dist/stage/chunk-QDDPLZDB.js +1 -0
- package/dist/stage/chunk-QLGLZ77F.js +1 -0
- package/dist/stage/chunk-QOPFDFHI.js +1 -0
- package/dist/stage/chunk-QXT4KAEJ.js +1 -0
- package/dist/stage/chunk-R4YVZCIT.js +1 -0
- package/dist/stage/chunk-R6P6EHWA.js +62 -0
- package/dist/stage/chunk-RCH3OXBI.js +9 -0
- package/dist/stage/chunk-RHPE44I2.js +1 -0
- package/dist/stage/chunk-RJ2R4FVC.js +1 -0
- package/dist/stage/chunk-RJH2YVTD.js +1 -0
- package/dist/stage/chunk-RLWWW5OB.js +6 -0
- package/dist/stage/chunk-S3WO35Z7.js +120 -0
- package/dist/stage/chunk-S7ZGX6MW.js +1 -0
- package/dist/stage/chunk-SIAWX27B.js +1 -0
- package/dist/stage/chunk-SLG4X622.js +1 -0
- package/dist/stage/chunk-SLJRBC77.js +168 -0
- package/dist/stage/chunk-SRORWQJZ.js +1 -0
- package/dist/stage/chunk-STZTUIIF.js +2 -0
- package/dist/stage/chunk-SVQ6MWNV.js +9 -0
- package/dist/stage/chunk-TL3HLL2S.js +132 -0
- package/dist/stage/chunk-U3DOE6SF.js +24 -0
- package/dist/stage/chunk-UABQIZKW.js +6 -0
- package/dist/stage/chunk-UDK3R764.js +1 -0
- package/dist/stage/chunk-UFF4E54B.js +41 -0
- package/dist/stage/chunk-UFP5Q4AY.js +1 -0
- package/dist/stage/chunk-UMH2TSW2.js +72 -0
- package/dist/stage/chunk-UTASVNF5.js +10 -0
- package/dist/stage/chunk-UW7ML5ML.js +88 -0
- package/dist/stage/chunk-V3J7F6OP.js +1 -0
- package/dist/stage/chunk-VEHP5B5Q.js +5 -0
- package/dist/stage/chunk-VRXWB733.js +9 -0
- package/dist/stage/chunk-VVDHBBS5.js +5 -0
- package/dist/stage/chunk-VYFOH6FU.js +106 -0
- package/dist/stage/chunk-W3VZTLQS.js +3 -0
- package/dist/stage/chunk-W7WNRWTZ.js +8 -0
- package/dist/stage/chunk-WHUCV53C.js +1 -0
- package/dist/stage/chunk-WULMYHIF.js +1 -0
- package/dist/stage/chunk-X54EX7AW.js +9 -0
- package/dist/stage/chunk-X565ISZM.js +1 -0
- package/dist/stage/chunk-XGZMDECB.js +1 -0
- package/dist/stage/chunk-XHONC4RF.js +172 -0
- package/dist/stage/chunk-XIK4XQ6E.js +24 -0
- package/dist/stage/chunk-XJQQWFTE.js +1 -0
- package/dist/stage/chunk-XM4WOU3I.js +13 -0
- package/dist/stage/chunk-XM7Y5HP5.js +38 -0
- package/dist/stage/chunk-XNYYRGD5.js +292 -0
- package/dist/stage/chunk-XOYVR3SV.js +1 -0
- package/dist/stage/chunk-XQIW5HCE.js +1 -0
- package/dist/stage/chunk-YEWL7FUW.js +8 -0
- package/dist/stage/chunk-YI55PY3V.js +1 -0
- package/dist/stage/chunk-YM63HI6W.js +1 -0
- package/dist/stage/chunk-YNLGLU4P.js +9 -0
- package/dist/stage/chunk-YTNMNOWP.js +1 -0
- package/dist/stage/chunk-YV62XKTC.js +1 -0
- package/dist/stage/chunk-YVBA3QW6.js +1 -0
- package/dist/stage/chunk-Z2SY2HOC.js +1 -0
- package/dist/stage/chunk-ZBTCQEBK.js +10 -0
- package/dist/stage/chunk-ZCUFN74D.js +1 -0
- package/dist/stage/chunk-ZHOULXEJ.js +11 -0
- package/dist/stage/chunk-ZTRZIET2.js +1 -0
- package/dist/stage/index.html +1 -0
- package/dist/stage/stage.css +1 -1
- package/dist/stage/stage.js +1 -4383
- package/dist/ui/dev/dev-page.d.ts +1 -0
- package/dist/ui/dev/dev-page.js +2021 -0
- package/dist/ui/dev/dev-theme.d.ts +6 -0
- package/dist/ui/dev/dev-theme.js +67 -0
- package/dist/ui/dev/voice-gallery-page.d.ts +1 -0
- package/dist/ui/dev/voice-gallery-page.js +121 -0
- package/dist/ui/dev/voice-scenes.d.ts +21 -0
- package/dist/ui/dev/voice-scenes.js +30 -0
- package/dist/ui/dictation/en-box.d.ts +2 -0
- package/dist/ui/dictation/en-box.js +86 -0
- package/dist/ui/dictation/en-page.d.ts +1 -0
- package/dist/ui/dictation/en-page.js +484 -0
- package/dist/ui/dictation/page.d.ts +1 -0
- package/dist/ui/dictation/page.js +1084 -0
- package/dist/ui/dictation/parent-page.d.ts +1 -0
- package/dist/ui/dictation/parent-page.js +234 -0
- package/dist/ui/dictation/reel-mode.d.ts +1 -0
- package/dist/ui/dictation/reel-mode.js +87 -0
- package/dist/ui/dictation/reel-page.d.ts +1 -0
- package/dist/ui/dictation/reel-page.js +62 -0
- package/dist/ui/dictation/reel.d.ts +163 -0
- package/dist/ui/dictation/reel.js +436 -0
- package/dist/ui/dictation/ui.d.ts +44 -0
- package/dist/ui/dictation/ui.js +80 -0
- package/dist/ui/dictation/unlock.d.ts +7 -0
- package/dist/ui/dictation/unlock.js +16 -0
- package/dist/ui/kid/audio-client.d.ts +11 -0
- package/dist/ui/kid/audio-client.js +74 -0
- package/dist/ui/kid/board-mark-art.d.ts +37 -0
- package/dist/ui/kid/board-mark-art.js +5 -0
- package/dist/ui/kid/board-mark.d.ts +24 -0
- package/dist/ui/kid/board-mark.js +58 -0
- package/dist/ui/kid/home-tiles.d.ts +14 -0
- package/dist/ui/kid/home-tiles.js +44 -0
- package/dist/ui/kid/kid-log.d.ts +15 -0
- package/dist/ui/kid/kid-log.js +87 -0
- package/dist/ui/kid/kid-nav.d.ts +11 -0
- package/dist/ui/kid/kid-nav.js +22 -0
- package/dist/ui/kid/kid-page.d.ts +9 -0
- package/dist/ui/kid/kid-page.js +3684 -0
- package/dist/ui/kid/letters.d.ts +26 -0
- package/dist/ui/kid/letters.js +51 -0
- package/dist/ui/kid/photo-edit.d.ts +83 -0
- package/dist/ui/kid/photo-edit.js +106 -0
- package/dist/ui/kid/photo-ui.d.ts +23 -0
- package/dist/ui/kid/photo-ui.js +60 -0
- package/dist/ui/kid/reel.d.ts +419 -0
- package/dist/ui/kid/reel.js +648 -0
- package/dist/ui/kid/tianzige.d.ts +8 -0
- package/dist/ui/kid/tianzige.js +45 -0
- package/dist/ui/kid/tutor-face.d.ts +35 -0
- package/dist/ui/kid/tutor-face.js +84 -0
- package/dist/ui/kid/voice-ui.d.ts +70 -0
- package/dist/ui/kid/voice-ui.js +316 -0
- package/dist/ui/kid/voice-view.d.ts +71 -0
- package/dist/ui/kid/voice-view.js +75 -0
- package/dist/ui/kid/word-draw.d.ts +2 -0
- package/dist/ui/kid/word-draw.js +107 -0
- package/dist/ui/kid/word-emoji.d.ts +10 -0
- package/dist/ui/kid/word-emoji.js +68 -0
- package/dist/ui/kid/word-layout.d.ts +25 -0
- package/dist/ui/kid/word-layout.js +29 -0
- package/dist/ui/parent/parent-home-page.d.ts +1 -0
- package/dist/ui/parent/parent-home-page.js +722 -0
- package/dist/ui/parent/parent-page.d.ts +2 -0
- package/dist/ui/parent/parent-page.js +511 -0
- package/dist/ui/parent/parent-settings-page.d.ts +1 -0
- package/dist/ui/parent/parent-settings-page.js +133 -0
- package/dist/ui/parent/qr-page.d.ts +8 -0
- package/dist/ui/parent/qr-page.js +66 -0
- package/dist/ui/shared/brand.d.ts +38 -0
- package/dist/ui/shared/brand.js +53 -0
- package/dist/ui/shared/favicon.d.ts +12 -0
- package/dist/ui/shared/favicon.js +96 -0
- package/dist/ui/shared/icon.d.ts +9 -0
- package/dist/ui/shared/icon.js +35 -0
- package/dist/ui/shared/inline-lib.d.ts +1 -0
- package/dist/ui/shared/inline-lib.js +28 -0
- package/dist/ui/shared/logo.d.ts +31 -0
- package/dist/ui/shared/logo.js +43 -0
- package/dist/ui/shared/page-dom.d.ts +12 -0
- package/dist/ui/shared/page-dom.js +36 -0
- package/dist/ui/shared/png.d.ts +1 -0
- package/dist/ui/shared/png.js +45 -0
- package/dist/ui/shared/qr-svg.d.ts +3 -0
- package/dist/ui/shared/qr-svg.js +32 -0
- package/dist/ui/shared/qr.d.ts +14 -0
- package/dist/ui/shared/qr.js +281 -0
- package/dist/ui/shared/reel-dock.d.ts +22 -0
- package/dist/ui/shared/reel-dock.js +365 -0
- package/dist/ui/shared/tutor-color.d.ts +15 -0
- package/dist/ui/shared/tutor-color.js +47 -0
- package/dist/ui/talk/page.d.ts +6 -0
- package/dist/ui/talk/page.js +294 -0
- package/grades/grade-2/README.md +11 -0
- package/grades/grade-2/VALIDATION.md +41 -0
- package/grades/grade-2/agents/chinese-tutor/activities//344/275/240/350/257/264/346/210/221/347/224/273.md +157 -0
- package/grades/grade-2/agents/chinese-tutor/activities//344/275/240/350/257/264/346/210/221/347/224/273./344/276/213/345/255/220.md +78 -0
- package/grades/grade-2/agents/chinese-tutor/activities//344/276/213/345/255/220/345/233/276//345/200/237/344/274/236.jpg +0 -0
- package/grades/grade-2/agents/chinese-tutor/activities//344/276/213/345/255/220/345/233/276//345/260/217/347/214/264/346/221/230/346/241/203.jpg +0 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/206/231/350/257/235.md +64 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/206/231/350/257/235./344/276/213/345/255/220.md +31 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/220/254/345/206/231.md +48 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/244/215/344/271/240.md +79 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/244/215/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/chinese-tutor/activities//345/260/217/350/257/276/345/240/202.md +53 -0
- package/grades/grade-2/agents/chinese-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232.md +81 -0
- package/grades/grade-2/agents/chinese-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/chinese-tutor/activities//347/234/213/345/233/276/350/257/264/350/257/235.md +94 -0
- package/grades/grade-2/agents/chinese-tutor/activities//347/234/213/345/233/276/350/257/264/350/257/235./344/276/213/345/255/220.md +63 -0
- package/grades/grade-2/agents/chinese-tutor/activities//351/242/204/344/271/240.md +82 -0
- package/grades/grade-2/agents/chinese-tutor/activities//351/242/204/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/chinese-tutor.md +59 -0
- package/grades/grade-2/agents/english-tutor/activities//345/217/243/350/257/255/350/257/276.md +149 -0
- package/grades/grade-2/agents/english-tutor/activities//345/217/243/350/257/255/350/257/276./344/276/213/345/255/220.md +13 -0
- package/grades/grade-2/agents/english-tutor/activities//345/220/254/345/206/231.md +47 -0
- package/grades/grade-2/agents/english-tutor/activities//345/244/215/344/271/240.md +78 -0
- package/grades/grade-2/agents/english-tutor/activities//345/244/215/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/english-tutor/activities//345/260/217/350/257/276/345/240/202.md +53 -0
- package/grades/grade-2/agents/english-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232.md +80 -0
- package/grades/grade-2/agents/english-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/english-tutor/activities//351/242/204/344/271/240.md +86 -0
- package/grades/grade-2/agents/english-tutor/activities//351/242/204/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/english-tutor.md +57 -0
- package/grades/grade-2/agents/math-tutor/activities//345/244/215/344/271/240.md +78 -0
- package/grades/grade-2/agents/math-tutor/activities//345/244/215/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/math-tutor/activities//345/260/217/350/257/276/345/240/202.md +56 -0
- package/grades/grade-2/agents/math-tutor/activities//345/260/217/350/257/276/345/240/202./344/276/213/345/255/220.md +52 -0
- package/grades/grade-2/agents/math-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232.md +80 -0
- package/grades/grade-2/agents/math-tutor/activities//346/243/200/346/237/245/344/275/234/344/270/232./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/math-tutor/activities//351/242/204/344/271/240.md +81 -0
- package/grades/grade-2/agents/math-tutor/activities//351/242/204/344/271/240./344/276/213/345/255/220.md +35 -0
- package/grades/grade-2/agents/math-tutor.md +51 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/index.json +44 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-1.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-2.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-3.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-4.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/audio/step-5.mp3 +0 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/bake.json +1 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/manifest.json +63 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen/scene.json +247 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen.md +40 -0
- package/grades/grade-2/examples/2026-10-27-pingjunfen.ts +26 -0
- package/grades/grade-2/examples//345/271/263/345/235/207/345/210/206./344/276/213/345/255/220.md +24 -0
- package/grades/grade-2/examples//346/225/260/345/255/246/345/271/263/345/235/207/345/210/206.md +42 -0
- package/grades/grade-2/validation/2026-10-27.json +4410 -0
- package/package.json +27 -15
- package/skills/chalk-cli/SKILL.md +62 -0
- package/skills/chalk-scene/SKILL.md +91 -0
- package/skills/chalk-teaching/SKILL.md +82 -0
- package/skills/chalk-teaching/models//344/272/244/351/224/231/346/225/260/345/210/227/345/210/206/350/241/214.md +8 -0
- package/skills/chalk-teaching/models//345/207/221/345/215/201.md +7 -0
- package/skills/chalk-teaching/models//345/221/250/346/234/237/350/247/204/345/276/213.md +6 -0
- package/skills/chalk-teaching/models//345/256/236/347/211/251/345/233/276-/345/242/236/345/207/217.md +6 -0
- package/skills/chalk-teaching/models//345/256/236/347/211/251/345/233/276-/346/257/224/345/244/232/345/260/221-/346/261/202/345/244/247/346/225/260/346/261/202/345/260/217/346/225/260.md +7 -0
- package/skills/chalk-teaching/models//345/256/236/347/211/251/345/233/276-/346/257/224/345/244/232/345/260/221-/346/261/202/345/267/256.md +6 -0
- package/skills/chalk-teaching/models//345/260/217/346/243/222/350/277/233/344/275/215/345/212/240.md +7 -0
- package/skills/chalk-teaching/models//345/260/217/346/243/222/351/200/200/344/275/215/345/207/217.md +6 -0
- package/skills/chalk-teaching/models//346/216/222/351/230/237/347/202/271/351/230/265.md +7 -0
- package/skills/chalk-teaching/models//346/227/266/351/227/264/346/225/260/350/275/264.md +6 -0
- package/skills/chalk-teaching/models//346/245/274/345/261/202/351/227/264/351/232/224/345/233/276.md +7 -0
- package/skills/chalk-teaching/models//347/240/264/345/215/201.md +7 -0
- package/skills/chalk-teaching/models//347/253/226/345/274/217/351/200/200/344/275/215.md +6 -0
- package/skills/chalk-teaching/models//347/272/277/346/256/265/345/233/276.md +7 -0
- package/skills/chalk-teaching/models-index.md +24 -0
- package/skills/chalk-teaching/references//350/256/262/350/247/243/346/250/241/346/235/277/344/270/211/346/241/243.md +32 -0
- package/skills/chalk-teaching/references//350/257/264/346/230/216/346/226/207/344/273/266.md +102 -0
- package/skills/chalk-verify/SKILL.md +57 -0
- package/skills/chalk-verify/references//350/257/204/345/210/244/346/240/207/345/207/206.md +69 -0
- package/skills/cotutor-analyze/SKILL.md +106 -0
- package/skills/cotutor-analyze/references//345/221/275/344/273/244/344/270/216/346/226/207/344/273/266.md +31 -0
- package/skills/cotutor-board/SKILL.md +373 -0
- package/skills/cotutor-board/references/README.md +38 -0
- package/skills/cotutor-board/references/canvas.md +68 -0
- package/skills/cotutor-board/references/choice.md +55 -0
- package/skills/cotutor-board/references/code.md +46 -0
- package/skills/cotutor-board/references/image.md +52 -0
- package/skills/cotutor-board/references/lecture.md +69 -0
- package/skills/cotutor-board/references/paint.md +77 -0
- package/skills/cotutor-board/references/photo.md +61 -0
- package/skills/cotutor-board/references/read.md +51 -0
- package/skills/cotutor-board/references/text.md +69 -0
- package/skills/cotutor-board/references/tianzige.md +78 -0
- package/skills/cotutor-board/references/word.md +91 -0
- package/skills/cotutor-home/SKILL.md +170 -0
- package/skills/cotutor-home/references/dictation.md +97 -0
- package/skills/cotutor-home/references/tutor.md +149 -0
- package/skills/cotutor-home/references//345/221/275/344/273/244.md +21 -0
- package/skills/cotutor-home/references//351/246/226/351/241/265/350/257/255/346/263/225.md +143 -0
- package/skills/cotutor-tune/SKILL.md +147 -0
- package/skills/cotutor-tune/references//345/255/227/346/256/265.md +53 -0
- package/skills/cotutor-tutor/SKILL.md +253 -0
- package/skills/cotutor-tutor/references//344/270/212/344/270/213/346/226/207/345/214/205.md +78 -0
- package/themes/default/kid.css +86 -0
- package/themes/default/theme.json +26 -0
- package/agents/chinese-tutor.md +0 -67
- package/agents/homework-tutor.md +0 -17
- package/agents/math-tutor.md +0 -68
- package/agents/planner.md +0 -18
- package/agents/reading-tutor.md +0 -65
- package/agents/scene-maker.md +0 -41
- package/dist/cards/canvas.d.ts +0 -22
- package/dist/cards/canvas.js +0 -65
- package/dist/cards/fill.d.ts +0 -14
- package/dist/cards/fill.js +0 -64
- package/dist/cards/index.d.ts +0 -48
- package/dist/cards/index.js +0 -115
- package/dist/cards/kind.js +0 -7
- package/dist/cards/scene.d.ts +0 -24
- package/dist/cards/scene.js +0 -67
- package/dist/cards/text.d.ts +0 -18
- package/dist/cards/text.js +0 -41
- package/dist/cli/cert.d.ts +0 -7
- package/dist/cli/cert.js +0 -36
- package/dist/cli/doctor.d.ts +0 -22
- package/dist/cli/doctor.js +0 -355
- package/dist/cli/init.js +0 -116
- package/dist/cli/main.js +0 -243
- package/dist/cli/serve.d.ts +0 -30
- package/dist/cli/serve.js +0 -56
- package/dist/cli/skeleton.d.ts +0 -39
- package/dist/cli/skeleton.js +0 -184
- package/dist/cli/skills.d.ts +0 -40
- package/dist/cli/skills.js +0 -170
- package/dist/cli/workspace.d.ts +0 -69
- package/dist/lib/agent-file.d.ts +0 -10
- package/dist/lib/agent-file.js +0 -16
- package/dist/lib/board.d.ts +0 -15
- package/dist/lib/board.js +0 -107
- package/dist/lib/context-pack.js +0 -55
- package/dist/lib/conversation.js +0 -88
- package/dist/lib/kid-board.d.ts +0 -148
- package/dist/lib/kid-board.js +0 -294
- package/dist/lib/kid-view.d.ts +0 -63
- package/dist/lib/kid-view.js +0 -93
- package/dist/lib/ledger.d.ts +0 -29
- package/dist/lib/run-plan.d.ts +0 -30
- package/dist/lib/run-plan.js +0 -32
- package/dist/lib/sections.d.ts +0 -12
- package/dist/lib/sections.js +0 -151
- package/dist/lib/stream.d.ts +0 -15
- package/dist/lib/stream.js +0 -69
- package/dist/lib/transcript.d.ts +0 -36
- package/dist/lib/transcript.js +0 -95
- package/dist/server/app.d.ts +0 -74
- package/dist/server/app.js +0 -406
- package/dist/server/kid-page.d.ts +0 -1
- package/dist/server/kid-page.js +0 -768
- package/dist/server/mock.js +0 -403
- package/dist/server/parent-page.d.ts +0 -8
- package/dist/server/parent-page.js +0 -393
- package/dist/server/runner.d.ts +0 -64
- package/dist/server/runner.js +0 -277
- package/dist/server/scene-props.d.ts +0 -16
- package/dist/server/scene-props.js +0 -39
- package/dist/server/store.d.ts +0 -43
- package/dist/server/store.js +0 -210
- package/dist/server/tts.d.ts +0 -51
- package/dist/server/tts.js +0 -99
- /package/dist/{cli → app/cli}/main.d.ts +0 -0
- /package/dist/{cards → board/cards}/choice.d.ts +0 -0
- /package/dist/{cards → board/cards}/code.d.ts +0 -0
- /package/dist/{cards → board/cards}/image.d.ts +0 -0
- /package/dist/{cards → board/cards}/read.d.ts +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type InstallStep } from './tutors.ts';
|
|
2
|
+
/** section:出厂模板里有的整段(dictation 这种后加的),你这份没有;retired:出厂写进去、应用已经不读的整段,补缺时删掉 */
|
|
3
|
+
export type GapKind = 'tutor' | 'runtime' | 'cards' | 'section' | 'retired';
|
|
4
|
+
export interface ConfigGap {
|
|
5
|
+
kind: GapKind;
|
|
6
|
+
/** 点位:tutors.math-tutor / runtimes.api-flash / dictation */
|
|
7
|
+
path: string;
|
|
8
|
+
/** 人话:缺的是什么、补上有什么用 */
|
|
9
|
+
detail: string;
|
|
10
|
+
/** 补上之后这个点位的值(--dry-run 打印、页面提示用);retired 是 undefined(删掉) */
|
|
11
|
+
value: unknown;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 这份 cotutor.json 比出厂模板少了什么。只看「有没有」,不看值——值是家长的。
|
|
15
|
+
* 注意:出厂老师是家长手动从 tutors 里删掉的(页面删不了出厂老师),这里也会当成缺,
|
|
16
|
+
* 所以 `--dry-run` 先列、doctor 那条不是必需项。
|
|
17
|
+
*/
|
|
18
|
+
export declare function configGaps(raw: unknown, agentsDir?: string): Promise<ConfigGap[]>;
|
|
19
|
+
export interface UpgradeConfigResult {
|
|
20
|
+
file: string;
|
|
21
|
+
gaps: ConfigGap[];
|
|
22
|
+
/** 真写了没有(--dry-run 与没有差异时都是 false) */
|
|
23
|
+
applied: boolean;
|
|
24
|
+
/** 新老师进了表之后顺带补的文件与目录(老师文件、agents/<name>/) */
|
|
25
|
+
installed: InstallStep[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 补缺并落盘:补丁打在**原始 JSON** 上(`$schema` `_note` 这些机器不认识的键原样留着),
|
|
29
|
+
* 整份过契约才写;不过就抛,原文件不动。
|
|
30
|
+
*/
|
|
31
|
+
export declare function upgradeConfig(root: string, opts?: {
|
|
32
|
+
dryRun?: boolean;
|
|
33
|
+
}): Promise<UpgradeConfigResult>;
|
|
34
|
+
/** doctor 与家长端都要:读文件算差异;文件读不到 / 坏了就当没有差异(那边有别的检查在报) */
|
|
35
|
+
export declare function configGapsOf(root: string): Promise<ConfigGap[]>;
|
|
36
|
+
export interface LayoutStep {
|
|
37
|
+
item: string;
|
|
38
|
+
action: 'moved' | 'kept' | 'removed';
|
|
39
|
+
note?: string;
|
|
40
|
+
}
|
|
41
|
+
/** doctor 用:老位置里还有东西的那几个(相对 workspace 根) */
|
|
42
|
+
export declare function legacyLayout(root: string): Promise<string[]>;
|
|
43
|
+
/**
|
|
44
|
+
* init / upgrade 第一步:老位置里的东西原样挪到新位置(不改内容、不看 hash),出厂件新不新由后面安装那一步照常查。
|
|
45
|
+
* 必须在 installTutors / installSkills 之前:不然新位置是空的,出厂件先拷进去,家长改过的那份就晾在老位置。
|
|
46
|
+
* 新位置已经有同名的不挪、报一声;老目录搬空了删掉(.claude/skills 随后换成链,见 skills.ts 的 linkClaudeSkills),.claude/ 里别的东西不碰。
|
|
47
|
+
* 顺带删掉 RETIRED_FILES(机器文件,删了也只是缓存没了)。
|
|
48
|
+
*/
|
|
49
|
+
export declare function migrateLayout(root: string): Promise<LayoutStep[]>;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 老 workspace 的迁移:cotutor.json 补缺(下面大半),和目录搬家、清掉不再用的机器文件(文件末尾的 migrateLayout)。
|
|
3
|
+
*
|
|
4
|
+
* cotutor.json 是**政策文件**:init 只在缺失时写模板,已有的那份机器永不改(家长的决定不由机器代做)。
|
|
5
|
+
* 代价是包更新带来的新出厂件不会自己出现在老 workspace 里,而且「没有」的表现是**静默**的——
|
|
6
|
+
* 所以有这一层:把「出厂模板有、你这份没有」算出来(doctor 的 `config.migrate` 点名、家长端设置页顶部提示),
|
|
7
|
+
* `cotutor upgrade --config` 只**补缺**——加缺的键;家长写过的值一个都不动(改小的预算、换掉的 default 运行时全都原样留着)。
|
|
8
|
+
* 唯一删的是 RETIRED 里点了名的出厂段(机器写进去、应用已经不读的,如 2026-10-31 的 tts);家长自己加的键不在表里,不碰。
|
|
9
|
+
*
|
|
10
|
+
* 差异是**现算的**:拿家长自己的 slug / name / port 生成一份出厂模板,和她这份逐项比,
|
|
11
|
+
* 所以以后模板再加东西,这里不用跟着改。
|
|
12
|
+
*/
|
|
13
|
+
import { workspaceGradePack } from "./grades.js";
|
|
14
|
+
import { lstat, mkdir, readFile, readdir, rename, rm, rmdir, writeFile } from 'node:fs/promises';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { CotutorConfigSchema, SKILLS_DIR, explainIssues } from "../../schema/index.js";
|
|
17
|
+
import { configTemplate, shippedAgents } from "./skeleton.js";
|
|
18
|
+
import { installTutors } from "./tutors.js";
|
|
19
|
+
import { CONFIG_FILE, ConfigError, readJson } from "../workspace.js";
|
|
20
|
+
const isObj = (v) => Boolean(v) && typeof v === 'object' && !Array.isArray(v);
|
|
21
|
+
/** 出厂模板:拿这份配置自己的 slug / name / port 生成,免得差异里混进「孩子叫什么」这种无关项 */
|
|
22
|
+
async function factoryConfig(raw, agentsDir) {
|
|
23
|
+
const kid = isObj(raw.kid) ? raw.kid : {};
|
|
24
|
+
const server = isObj(raw.server) ? raw.server : {};
|
|
25
|
+
return JSON.parse(configTemplate({
|
|
26
|
+
slug: typeof kid.slug === 'string' ? kid.slug : 'kid',
|
|
27
|
+
name: typeof kid.name === 'string' ? kid.name : undefined,
|
|
28
|
+
port: typeof server.port === 'number' ? server.port : undefined,
|
|
29
|
+
tutors: await shippedAgents(agentsDir),
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 这份 cotutor.json 比出厂模板少了什么。只看「有没有」,不看值——值是家长的。
|
|
34
|
+
* 注意:出厂老师是家长手动从 tutors 里删掉的(页面删不了出厂老师),这里也会当成缺,
|
|
35
|
+
* 所以 `--dry-run` 先列、doctor 那条不是必需项。
|
|
36
|
+
*/
|
|
37
|
+
export async function configGaps(raw, agentsDir) {
|
|
38
|
+
if (!isObj(raw))
|
|
39
|
+
return [];
|
|
40
|
+
const factory = await factoryConfig(raw, agentsDir);
|
|
41
|
+
const gaps = [];
|
|
42
|
+
const mineTutors = isObj(raw.tutors) ? raw.tutors : {};
|
|
43
|
+
const factoryTutors = isObj(factory.tutors) ? factory.tutors : {};
|
|
44
|
+
for (const [name, entry] of Object.entries(factoryTutors)) {
|
|
45
|
+
if (name in mineTutors)
|
|
46
|
+
continue;
|
|
47
|
+
const display = isObj(entry) && typeof entry.display === 'string' ? entry.display : name;
|
|
48
|
+
gaps.push({ kind: 'tutor', path: `tutors.${name}`, detail: `出厂老师 ${display}(${name})不在 tutors 里`, value: entry });
|
|
49
|
+
}
|
|
50
|
+
// 卡的清单(2026-10-07,《卡片协议.md》「谁拿到哪些卡」):出厂老师有、你这份没写 → 补出厂的;不想裁就写全部种类,这条就不再提
|
|
51
|
+
for (const [name, entry] of Object.entries(factoryTutors)) {
|
|
52
|
+
const mine = mineTutors[name];
|
|
53
|
+
if (!isObj(mine) || 'cards' in mine || !isObj(entry) || !Array.isArray(entry.cards))
|
|
54
|
+
continue;
|
|
55
|
+
gaps.push({ kind: 'cards', path: `tutors.${name}.cards`, detail: `${name} 没写卡的清单,板书写法里讲全部卡;出厂清单 ${entry.cards.join(' ')}`, value: entry.cards });
|
|
56
|
+
}
|
|
57
|
+
const mineRuntimes = isObj(raw.runtimes) ? raw.runtimes : {};
|
|
58
|
+
const factoryRuntimes = isObj(factory.runtimes) ? factory.runtimes : {};
|
|
59
|
+
for (const [name, rt] of Object.entries(factoryRuntimes)) {
|
|
60
|
+
if (name === 'default' || name in mineRuntimes)
|
|
61
|
+
continue;
|
|
62
|
+
gaps.push({ kind: 'runtime', path: `runtimes.${name}`, detail: `出厂运行时 ${name} 不在 runtimes 里`, value: rt });
|
|
63
|
+
}
|
|
64
|
+
// 后加的整段(2026-10-10 听写的拍照认词要求):写进文件家长才看得见、改得动;已经有这一段就不碰
|
|
65
|
+
for (const key of SECTIONS) {
|
|
66
|
+
if (key in raw || !(key in factory))
|
|
67
|
+
continue;
|
|
68
|
+
gaps.push({ kind: 'section', path: key, detail: `缺 ${key} 这一段(${SECTION_WHAT[key]})`, value: factory[key] });
|
|
69
|
+
}
|
|
70
|
+
for (const key of RETIRED) {
|
|
71
|
+
if (key in raw)
|
|
72
|
+
gaps.push({ kind: 'retired', path: key, detail: `${key} 这一段不再用了(${RETIRED_WHAT[key]}),删掉`, value: undefined });
|
|
73
|
+
}
|
|
74
|
+
return gaps;
|
|
75
|
+
}
|
|
76
|
+
/** 补缺时会整段补上的配置段:出厂模板写了、写进文件才好改的 */
|
|
77
|
+
const SECTIONS = ['dictation'];
|
|
78
|
+
const SECTION_WHAT = { dictation: '听写拍照认词用的模型,补上就能在文件里改(挑词的要求在听写 activity 文件)' };
|
|
79
|
+
/** 不再用的出厂段:老模板写进去的,应用已经不读(契约照样放过,只是留着让人以为还管用) */
|
|
80
|
+
const RETIRED = ['tts'];
|
|
81
|
+
const RETIRED_WHAT = { tts: '配音 2026-10-31 并进了应用,直连百炼,不再起 voxtell 命令' };
|
|
82
|
+
function setAt(obj, path, value) {
|
|
83
|
+
const keys = path.split('.');
|
|
84
|
+
let cur = obj;
|
|
85
|
+
for (const k of keys.slice(0, -1)) {
|
|
86
|
+
if (!isObj(cur[k]))
|
|
87
|
+
cur[k] = {};
|
|
88
|
+
cur = cur[k];
|
|
89
|
+
}
|
|
90
|
+
const last = keys[keys.length - 1];
|
|
91
|
+
if (value === undefined)
|
|
92
|
+
delete cur[last];
|
|
93
|
+
else
|
|
94
|
+
cur[last] = value;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 补缺并落盘:补丁打在**原始 JSON** 上(`$schema` `_note` 这些机器不认识的键原样留着),
|
|
98
|
+
* 整份过契约才写;不过就抛,原文件不动。
|
|
99
|
+
*/
|
|
100
|
+
export async function upgradeConfig(root, opts = {}) {
|
|
101
|
+
const file = join(root, CONFIG_FILE);
|
|
102
|
+
const raw = readJson(file);
|
|
103
|
+
if (raw === null)
|
|
104
|
+
throw new ConfigError(file, '不存在:先 cotutor init <slug> 建 workspace');
|
|
105
|
+
if (!isObj(raw))
|
|
106
|
+
throw new ConfigError(file, '顶层不是一个 JSON 对象');
|
|
107
|
+
const gaps = await configGaps(raw, (await workspaceGradePack(root)).agentsDir);
|
|
108
|
+
if (opts.dryRun || !gaps.length)
|
|
109
|
+
return { file, gaps, applied: false, installed: [] };
|
|
110
|
+
const merged = JSON.parse(JSON.stringify(raw));
|
|
111
|
+
for (const g of gaps)
|
|
112
|
+
setAt(merged, g.path, g.value);
|
|
113
|
+
const r = CotutorConfigSchema.safeParse(merged);
|
|
114
|
+
if (!r.success) {
|
|
115
|
+
throw new ConfigError(file, `补完之后不合契约,没有写入:\n${explainIssues(r.error.issues).map((l) => ` - ${l}`).join('\n')}`);
|
|
116
|
+
}
|
|
117
|
+
const tmp = `${file}.tmp`;
|
|
118
|
+
await writeFile(tmp, `${JSON.stringify(merged, null, 2)}\n`);
|
|
119
|
+
await rename(tmp, file);
|
|
120
|
+
// 新老师进了表,她的文件与家还得在,否则 doctor 立刻报「老师文件读不到」——
|
|
121
|
+
// 补缺就补到底(installTutors 是 init 用的同一条路:缺的拷、有的不动)
|
|
122
|
+
// 老布局先搬:不然新位置是空的,出厂老师先拷进去,家长改过的那份晾在 .claude/agents/
|
|
123
|
+
if (gaps.some((g) => g.kind === 'tutor'))
|
|
124
|
+
await migrateLayout(root);
|
|
125
|
+
const installed = gaps.some((g) => g.kind === 'tutor')
|
|
126
|
+
? (await installTutors(root, Object.keys(r.data.tutors))).filter((s) => s.action === 'created' || s.action === 'replaced')
|
|
127
|
+
: [];
|
|
128
|
+
return { file, gaps, applied: true, installed };
|
|
129
|
+
}
|
|
130
|
+
/** doctor 与家长端都要:读文件算差异;文件读不到 / 坏了就当没有差异(那边有别的检查在报) */
|
|
131
|
+
export async function configGapsOf(root) {
|
|
132
|
+
try {
|
|
133
|
+
return await configGaps(JSON.parse(await readFile(join(root, CONFIG_FILE), 'utf8')), (await workspaceGradePack(root)).agentsDir);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// ---- 目录搬家(2026-10-30):老师文件 .claude/agents/ → agents/,技能 .claude/skills/ → .agents/skills/ ----
|
|
140
|
+
// 起因:Claude CLI 2026-10-28 移除后 .claude/ 只是历史名字;Codex、Qwen Code 认 .agents/skills,老师文件和老师的家并排才和包里一样。
|
|
141
|
+
// 老 workspace 都搬完了这一段就删(同 2026-10-22 删 .qwen 链的迁移)。
|
|
142
|
+
/** 还没搬完的老位置(实体目录才算;.claude/skills 是链就是新布局) */
|
|
143
|
+
const LEGACY_DIRS = [
|
|
144
|
+
{ from: '.claude/agents', to: 'agents' },
|
|
145
|
+
{ from: '.claude/skills', to: SKILLS_DIR },
|
|
146
|
+
];
|
|
147
|
+
/**
|
|
148
|
+
* 不再用的机器文件(相对 workspace 根):2026-10-31 配音的缓存合成一份 .cotutor/tts/,试听、听写念词原来各存一处,设置页的「试一句」也去掉了;
|
|
149
|
+
* 同日 drawtell 并进本包,做课包是 cotutor chalk,不再要指向 drawtell CLI 的壳 .cotutor/drawtell
|
|
150
|
+
*/
|
|
151
|
+
const RETIRED_FILES = ['.cotutor/tts-preview', '.cotutor/dictation-say', '.cotutor/tts-try.mp3', '.cotutor/drawtell'];
|
|
152
|
+
/** doctor 用:老位置里还有东西的那几个(相对 workspace 根) */
|
|
153
|
+
export async function legacyLayout(root) {
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const { from } of LEGACY_DIRS) {
|
|
156
|
+
const st = await lstat(join(root, from)).catch(() => null);
|
|
157
|
+
if (st?.isDirectory() && (await readdir(join(root, from))).some((n) => n !== '.DS_Store'))
|
|
158
|
+
out.push(`${from}/`);
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* init / upgrade 第一步:老位置里的东西原样挪到新位置(不改内容、不看 hash),出厂件新不新由后面安装那一步照常查。
|
|
164
|
+
* 必须在 installTutors / installSkills 之前:不然新位置是空的,出厂件先拷进去,家长改过的那份就晾在老位置。
|
|
165
|
+
* 新位置已经有同名的不挪、报一声;老目录搬空了删掉(.claude/skills 随后换成链,见 skills.ts 的 linkClaudeSkills),.claude/ 里别的东西不碰。
|
|
166
|
+
* 顺带删掉 RETIRED_FILES(机器文件,删了也只是缓存没了)。
|
|
167
|
+
*/
|
|
168
|
+
export async function migrateLayout(root) {
|
|
169
|
+
const steps = [];
|
|
170
|
+
for (const { from, to } of LEGACY_DIRS) {
|
|
171
|
+
const dir = join(root, from);
|
|
172
|
+
if (!(await lstat(dir).catch(() => null))?.isDirectory())
|
|
173
|
+
continue;
|
|
174
|
+
await rm(join(dir, '.DS_Store'), { force: true });
|
|
175
|
+
for (const name of (await readdir(dir)).sort()) {
|
|
176
|
+
const dest = join(root, to, name);
|
|
177
|
+
if (await lstat(dest).catch(() => null)) {
|
|
178
|
+
steps.push({ item: `${from}/${name}`, action: 'kept', note: `${to}/${name} 已经有了,没挪;对一下要哪份,删掉另一份` });
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
await mkdir(join(root, to), { recursive: true });
|
|
182
|
+
await rename(join(dir, name), dest);
|
|
183
|
+
steps.push({ item: `${to}/${name}`, action: 'moved', note: `从 ${from}/ 挪来` });
|
|
184
|
+
}
|
|
185
|
+
if (await rmdir(dir).then(() => true, () => false))
|
|
186
|
+
steps.push({ item: `${from}/`, action: 'removed', note: '搬空了' });
|
|
187
|
+
}
|
|
188
|
+
for (const rel of RETIRED_FILES) {
|
|
189
|
+
if (!(await lstat(join(root, rel)).catch(() => null)))
|
|
190
|
+
continue;
|
|
191
|
+
await rm(join(root, rel), { recursive: true, force: true });
|
|
192
|
+
steps.push({ item: rel, action: 'removed', note: '不再用的机器文件' });
|
|
193
|
+
}
|
|
194
|
+
return steps;
|
|
195
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ServiceHost } from './service.ts';
|
|
2
|
+
export interface PortHolder {
|
|
3
|
+
pid: number;
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
/** lsof -Fpc 的输出:一行 p<pid>,一行 c<命令名> */
|
|
7
|
+
export declare function parseLsof(out: string): PortHolder | null;
|
|
8
|
+
/**
|
|
9
|
+
* 谁在监听(Windows):只认对端端口是 0,不看 State。2026-11-06 Windows 11 build 26300 上监听的 socket 状态是乱的:
|
|
10
|
+
* netstat 显示 CLOSED、FIN_WAIT_1,-State Listen 查不到,读 State 还可能抛异常
|
|
11
|
+
*/
|
|
12
|
+
export declare function holderScript(port: number): string;
|
|
13
|
+
/** 上面那段的输出;坏的、空的当不知道 */
|
|
14
|
+
export declare function parseHolder(text: string): PortHolder | null;
|
|
15
|
+
/** 谁在监听这个端口;查不到(别人的进程、没装 lsof)→ null */
|
|
16
|
+
export declare function portHolder(host: Pick<ServiceHost, 'platform' | 'run'>, port: number): Promise<PortHolder | null>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** 谁在监听这个端口:port.ts(端口的规矩)和托盘(tray.ts)都用,单独一个文件,托盘进程才不用加载 workspace 与 zod */
|
|
2
|
+
import { psArgs } from "./service-windows.js";
|
|
3
|
+
/** lsof -Fpc 的输出:一行 p<pid>,一行 c<命令名> */
|
|
4
|
+
export function parseLsof(out) {
|
|
5
|
+
const pid = /^p(\d+)$/m.exec(out)?.[1];
|
|
6
|
+
const name = /^c(.+)$/m.exec(out)?.[1];
|
|
7
|
+
return pid && name ? { pid: Number(pid), name } : null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 谁在监听(Windows):只认对端端口是 0,不看 State。2026-11-06 Windows 11 build 26300 上监听的 socket 状态是乱的:
|
|
11
|
+
* netstat 显示 CLOSED、FIN_WAIT_1,-State Listen 查不到,读 State 还可能抛异常
|
|
12
|
+
*/
|
|
13
|
+
export function holderScript(port) {
|
|
14
|
+
return [
|
|
15
|
+
`$c = Get-NetTCPConnection -LocalPort ${port} | Where-Object { $_.RemotePort -eq 0 } | Select-Object -First 1`,
|
|
16
|
+
'if ($c) { $p = Get-Process -Id $c.OwningProcess; ConvertTo-Json -Compress @{ pid = [int]$c.OwningProcess; name = $p.ProcessName } }',
|
|
17
|
+
].join('\n');
|
|
18
|
+
}
|
|
19
|
+
/** 上面那段的输出;坏的、空的当不知道 */
|
|
20
|
+
export function parseHolder(text) {
|
|
21
|
+
try {
|
|
22
|
+
const o = JSON.parse(text.trim());
|
|
23
|
+
return typeof o.pid === 'number' && typeof o.name === 'string' && o.name ? { pid: o.pid, name: o.name } : null;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** 谁在监听这个端口;查不到(别人的进程、没装 lsof)→ null */
|
|
30
|
+
export async function portHolder(host, port) {
|
|
31
|
+
if (host.platform === 'win32')
|
|
32
|
+
return parseHolder((await host.run('powershell.exe', psArgs(holderScript(port)))).stdout);
|
|
33
|
+
return parseLsof((await host.run('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-Fpc'])).stdout);
|
|
34
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { holderScript, parseHolder, parseLsof, portHolder, type PortHolder } from './port-holder.ts';
|
|
2
|
+
import { type ServiceHost } from './service.ts';
|
|
3
|
+
export declare const PORT_CANDIDATES: readonly number[];
|
|
4
|
+
/** sandbox 用哪个端口 */
|
|
5
|
+
export declare const sandboxPort: (port: number) => number;
|
|
6
|
+
export type PortState = 'free' | 'busy' | 'denied';
|
|
7
|
+
/**
|
|
8
|
+
* 真绑一下就放开,看系统让不让用这个端口(被占主要靠 portState 先问的那一声)。Windows 上绑通配地址会弹防火墙的窗(还会立刻建 Block 规则),探一下不该弹,所以绑 127.0.0.1,那边也没有低端口的限制;
|
|
9
|
+
* macOS 反过来,只能绑通配;别的系统同 macOS
|
|
10
|
+
*/
|
|
11
|
+
export declare function tryBind(port: number, platform?: NodeJS.Platform): Promise<PortState>;
|
|
12
|
+
/** 本机这个端口上有人答应吗 */
|
|
13
|
+
export declare function answersLocally(port: number, timeoutMs?: number): Promise<boolean>;
|
|
14
|
+
/** 先问一声再绑:别的程序只占着 127.0.0.1:443 时,绑 0.0.0.0:443 照样成功(2026-11-06 macOS、Windows 都试过),光绑看不出被占 */
|
|
15
|
+
export declare function portState(port: number): Promise<PortState>;
|
|
16
|
+
export interface PortChoice {
|
|
17
|
+
port: number;
|
|
18
|
+
skipped: {
|
|
19
|
+
port: number;
|
|
20
|
+
why: PortState | 'taken';
|
|
21
|
+
}[];
|
|
22
|
+
}
|
|
23
|
+
/** taken:别的 workspace 写着的端口,连同它们的 sandbox 都让开;选不出来报用法错误 */
|
|
24
|
+
export declare function choosePort(taken: Iterable<number>, probe?: (port: number) => Promise<PortState>, candidates?: readonly number[]): Promise<PortChoice>;
|
|
25
|
+
/** init 里 cotutor.json 那一行的说明 */
|
|
26
|
+
export declare function choiceNote(c: PortChoice): string;
|
|
27
|
+
/** 这台电脑上别的 workspace 写着的端口(~/cotutor/ 下的,加上用户配置指的那个):还没起的也要让开 */
|
|
28
|
+
export declare function knownPorts(): number[];
|
|
29
|
+
export { holderScript, parseHolder, parseLsof, portHolder, type PortHolder };
|
|
30
|
+
export interface Holder {
|
|
31
|
+
/** 「nginx(pid 12)」;不知道是谁 → null */
|
|
32
|
+
text: string | null;
|
|
33
|
+
cotutor: 'service' | 'serve' | null;
|
|
34
|
+
}
|
|
35
|
+
export declare function whoHolds(host: Pick<ServiceHost, 'platform' | 'run'>, port: number): Promise<Holder>;
|
|
36
|
+
/** serve 绑不上时那句话;service = 后台服务里(不重试,说怎么腾) */
|
|
37
|
+
export declare function bindFailure(code: 'EACCES' | 'EADDRINUSE', port: number, who: Holder | null, service: boolean): string;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 端口的规矩:新 workspace 只在 cotutor init 选一次,写进 cotutor.json,以后不变。
|
|
3
|
+
* 不变是因为平板上的主屏幕图标、页面存的设置都绑在「协议 + 主机 + 端口」上:换一次,平板要重新扫码、重加图标。所以运行时被占了只报错说清楚,不像 Vite 那样自动换下一个。
|
|
4
|
+
* 依次试 443(地址里不用写端口)、8443、8445…:系统不让用、被占着、留给了别的孩子就看下一个,不按系统写分支。
|
|
5
|
+
* macOS 普通用户绑得上 0.0.0.0:443,绑 127.0.0.1:443 反而不行(Mojave 起如此,苹果没写文档,2026-11-06 在 27.2 上试过);Linux 默认 1024 以下要 root,落到 8443。
|
|
6
|
+
* 隔一个号给 sandbox 留的:它的端口是真 workspace 的 +1。
|
|
7
|
+
* 旧 workspace 写着什么就是什么;cotutor.json 没写端口的走 DEFAULT_PORT,不跟着这里变。
|
|
8
|
+
*/
|
|
9
|
+
import { createConnection, createServer } from 'node:net';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { DEFAULT_PORT } from "../../schema/index.js";
|
|
13
|
+
import { holderScript, parseHolder, parseLsof, portHolder } from "./port-holder.js";
|
|
14
|
+
import { probeHealth } from "./service.js";
|
|
15
|
+
import { CONFIG_FILE, USER_CONFIG, UsageError, expandPath, homeWorkspaces, readJson } from "../workspace.js";
|
|
16
|
+
export const PORT_CANDIDATES = [443, ...Array.from({ length: 9 }, (_, i) => 8443 + i * 2)];
|
|
17
|
+
/** sandbox 用哪个端口 */
|
|
18
|
+
export const sandboxPort = (port) => (port === 65535 ? port - 1 : port + 1);
|
|
19
|
+
/**
|
|
20
|
+
* 真绑一下就放开,看系统让不让用这个端口(被占主要靠 portState 先问的那一声)。Windows 上绑通配地址会弹防火墙的窗(还会立刻建 Block 规则),探一下不该弹,所以绑 127.0.0.1,那边也没有低端口的限制;
|
|
21
|
+
* macOS 反过来,只能绑通配;别的系统同 macOS
|
|
22
|
+
*/
|
|
23
|
+
export function tryBind(port, platform = process.platform) {
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
const s = createServer();
|
|
26
|
+
s.once('error', (e) => resolve(e.code === 'EACCES' ? 'denied' : 'busy'));
|
|
27
|
+
s.listen(port, platform === 'win32' ? '127.0.0.1' : '0.0.0.0', () => s.close(() => resolve('free')));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** 本机这个端口上有人答应吗 */
|
|
31
|
+
export function answersLocally(port, timeoutMs = 400) {
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
const c = createConnection({ host: '127.0.0.1', port });
|
|
34
|
+
const finish = (yes) => { c.destroy(); resolve(yes); };
|
|
35
|
+
c.setTimeout(timeoutMs, () => finish(false));
|
|
36
|
+
c.once('connect', () => finish(true));
|
|
37
|
+
c.once('error', () => finish(false));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** 先问一声再绑:别的程序只占着 127.0.0.1:443 时,绑 0.0.0.0:443 照样成功(2026-11-06 macOS、Windows 都试过),光绑看不出被占 */
|
|
41
|
+
export async function portState(port) {
|
|
42
|
+
return (await answersLocally(port)) ? 'busy' : tryBind(port);
|
|
43
|
+
}
|
|
44
|
+
/** taken:别的 workspace 写着的端口,连同它们的 sandbox 都让开;选不出来报用法错误 */
|
|
45
|
+
export async function choosePort(taken, probe = portState, candidates = PORT_CANDIDATES) {
|
|
46
|
+
const used = new Set();
|
|
47
|
+
for (const p of taken) {
|
|
48
|
+
used.add(p);
|
|
49
|
+
used.add(sandboxPort(p));
|
|
50
|
+
}
|
|
51
|
+
const skipped = [];
|
|
52
|
+
for (const port of candidates) {
|
|
53
|
+
if (used.has(port) || used.has(sandboxPort(port))) {
|
|
54
|
+
skipped.push({ port, why: 'taken' });
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const state = await probe(port);
|
|
58
|
+
if (state === 'free')
|
|
59
|
+
return { port, skipped };
|
|
60
|
+
skipped.push({ port, why: state });
|
|
61
|
+
}
|
|
62
|
+
throw new UsageError(`端口 ${candidates.join('、')} 都用不了:cotutor init 加 --port <n> 指定一个`);
|
|
63
|
+
}
|
|
64
|
+
/** init 里 cotutor.json 那一行的说明 */
|
|
65
|
+
export function choiceNote(c) {
|
|
66
|
+
const WHY = { busy: '被占着', denied: '这个系统不让普通用户用', taken: '留给了另一个孩子', free: '' };
|
|
67
|
+
const first = c.skipped[0];
|
|
68
|
+
const head = c.port === 443 ? '端口 443,地址里不用写端口' : `端口 ${c.port}${first ? `(${first.port} ${WHY[first.why]})` : ''}`;
|
|
69
|
+
return `${head};选定了就不再变,改了平板要重新扫码`;
|
|
70
|
+
}
|
|
71
|
+
/** 这台电脑上别的 workspace 写着的端口(~/cotutor/ 下的,加上用户配置指的那个):还没起的也要让开 */
|
|
72
|
+
export function knownPorts() {
|
|
73
|
+
const roots = new Set(homeWorkspaces());
|
|
74
|
+
try {
|
|
75
|
+
const user = readJson(USER_CONFIG);
|
|
76
|
+
if (typeof user?.workspace === 'string' && user.workspace)
|
|
77
|
+
roots.add(expandPath(user.workspace, homedir()));
|
|
78
|
+
}
|
|
79
|
+
catch { /* 用户配置坏了 doctor 去报 */ }
|
|
80
|
+
const ports = [];
|
|
81
|
+
for (const root of roots) {
|
|
82
|
+
try {
|
|
83
|
+
const raw = readJson(join(root, CONFIG_FILE));
|
|
84
|
+
if (!raw)
|
|
85
|
+
continue;
|
|
86
|
+
ports.push(typeof raw.server?.port === 'number' ? raw.server.port : DEFAULT_PORT);
|
|
87
|
+
}
|
|
88
|
+
catch { /* 配置坏了:那边 doctor 去报,这里当它没有 */ }
|
|
89
|
+
}
|
|
90
|
+
return ports;
|
|
91
|
+
}
|
|
92
|
+
export { holderScript, parseHolder, parseLsof, portHolder };
|
|
93
|
+
export async function whoHolds(host, port) {
|
|
94
|
+
const h = await probeHealth(port);
|
|
95
|
+
const pid = h?.pid ? `(pid ${h.pid})` : '';
|
|
96
|
+
if (h)
|
|
97
|
+
return h.mode === 'service' ? { text: `cotutor 的后台服务${pid}`, cotutor: 'service' } : { text: `另一个 cotutor serve${pid}`, cotutor: 'serve' };
|
|
98
|
+
const p = await portHolder(host, port);
|
|
99
|
+
return { text: p ? `${p.name}(pid ${p.pid})` : null, cotutor: null };
|
|
100
|
+
}
|
|
101
|
+
const CHANGE_PORT = '要换端口就改 cotutor.json 的 server.port,平板要重新扫码、重加主屏幕图标';
|
|
102
|
+
/** serve 绑不上时那句话;service = 后台服务里(不重试,说怎么腾) */
|
|
103
|
+
export function bindFailure(code, port, who, service) {
|
|
104
|
+
if (code === 'EACCES') {
|
|
105
|
+
const head = `系统不让这个用户监听端口 ${port}(Linux 上 1024 以下要管理员权限,Windows 上可能落在系统保留的端口段)`;
|
|
106
|
+
return service ? `${head},后台服务先不起。${CHANGE_PORT},然后 cotutor service start` : `${head}。--port 临时换一个;${CHANGE_PORT}`;
|
|
107
|
+
}
|
|
108
|
+
const head = `端口 ${port} 被占着${who?.text ? `:${who.text} 在用` : ''}`;
|
|
109
|
+
if (service)
|
|
110
|
+
return who?.cotutor ? `${head},后台服务先不起;关掉它以后 cotutor service start` : `${head},后台服务先不起;腾出来以后 cotutor service start。${CHANGE_PORT}`;
|
|
111
|
+
if (who?.cotutor === 'service')
|
|
112
|
+
return `${head}。要在终端里调试先 cotutor service stop(cotutor service status 看状态);也可以 --port 临时换一个`;
|
|
113
|
+
if (who?.cotutor === 'serve')
|
|
114
|
+
return `${head}。关掉那个终端里的它;也可以 --port 临时换一个`;
|
|
115
|
+
return `${head}。关掉它,或 --port 临时换一个;${CHANGE_PORT}`;
|
|
116
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Workspace } from '../../schema/workspace.ts';
|
|
2
|
+
import { type ResolveOptions } from '../workspace.ts';
|
|
3
|
+
export interface SandboxResult {
|
|
4
|
+
/** sandbox 的根:后面的命令都当 --workspace 用它 */
|
|
5
|
+
root: string;
|
|
6
|
+
/** 从哪个真 workspace 分出来的 */
|
|
7
|
+
from: string;
|
|
8
|
+
/** 这次新分的(第一次,或 --fresh) */
|
|
9
|
+
created: boolean;
|
|
10
|
+
/** 分的时候 paths 怎么处理的(指在外面、只读照旧用的那几个) */
|
|
11
|
+
notes: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface SandboxOptions extends ResolveOptions {
|
|
14
|
+
fresh?: boolean;
|
|
15
|
+
now?: Date;
|
|
16
|
+
}
|
|
17
|
+
/** ~/cotutor/ray → ~/cotutor/.sandbox/ray */
|
|
18
|
+
export declare const sandboxRootOf: (real: string) => string;
|
|
19
|
+
export declare function openSandbox(override: string | undefined, opts?: SandboxOptions): Promise<SandboxResult>;
|
|
20
|
+
/**
|
|
21
|
+
* sandbox 的 cotutor.json:在真的那份原文上改(_note 这些机器不认识的键照留)。
|
|
22
|
+
* 标题加「试用 · 」、端口 +1、证书改绝对路径、自动记账关掉;paths 里每个配过的角色照它在真 workspace 里落在哪,改成指 sandbox 里对应的那处(写相对的,sandbox 挪了也不断)。
|
|
23
|
+
* 落在 workspace 与 vault 外面的:只读的材料照旧指那份,captures 改回缺省,其余会写的拦下——试用写进去的会混进真的。
|
|
24
|
+
*/
|
|
25
|
+
export declare function sandboxConfig(raw: Record<string, unknown>, ws: Workspace, box: string, notes: string[]): Record<string, unknown>;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* --sandbox:命令换到真 workspace 旁边的试用 workspace(位置、拷什么、cotutor.json 改哪几处见 src/schema/workspace.ts 的 SANDBOX_*)。
|
|
3
|
+
* 没有就分一个;有了就接着用(试过的对话留着,能接着聊、能试记账);--fresh 重新分。
|
|
4
|
+
* 重新分先分到临时目录,成了才换掉旧的:中途断了旧的还在,半截的不会被当成分好的(标记最后写)。
|
|
5
|
+
* 已经在 sandbox 里(cwd 在里面、--workspace 指过来)就用它;--fresh 从它记着的真 workspace 重新分,不套娃。
|
|
6
|
+
*/
|
|
7
|
+
import { copyFile, cp, mkdir, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
8
|
+
import { basename, dirname, isAbsolute, join, relative, sep } from 'node:path';
|
|
9
|
+
import { PATH_ROLES, ROLE_DEFAULTS, WORKSPACE_ROLES } from "../../schema/index.js";
|
|
10
|
+
import { SANDBOX_DIR, SANDBOX_KEEP_MACHINE, SANDBOX_MARK, SANDBOX_SKIP, SANDBOX_TITLE, SANDBOX_VAULT } from "../../schema/workspace.js";
|
|
11
|
+
import { CONFIG_FILE, UsageError, expandPath, loadWorkspace, parseConfig, readJson, readSandboxMark, redactHome, resolveRoot } from "../workspace.js";
|
|
12
|
+
import { sandboxPort } from "./port.js";
|
|
13
|
+
import { DIRS, LEDGER_FILES } from "./skeleton.js";
|
|
14
|
+
/** vault 侧指在外面也不怕的角色:只读的材料,老师不往里写 */
|
|
15
|
+
const READ_ONLY_ROLES = ['textbooks', 'reference'];
|
|
16
|
+
/** ~/cotutor/ray → ~/cotutor/.sandbox/ray */
|
|
17
|
+
export const sandboxRootOf = (real) => join(dirname(real), SANDBOX_DIR, basename(real));
|
|
18
|
+
/** p 是 dir 本身或在它里面 */
|
|
19
|
+
function inside(p, dir) {
|
|
20
|
+
const r = relative(dir, p);
|
|
21
|
+
return r === '' || (r !== '..' && !r.startsWith(`..${sep}`) && !isAbsolute(r));
|
|
22
|
+
}
|
|
23
|
+
async function exists(p) {
|
|
24
|
+
return stat(p).then(() => true, () => false);
|
|
25
|
+
}
|
|
26
|
+
export async function openSandbox(override, opts = {}) {
|
|
27
|
+
const { root } = resolveRoot(override, opts);
|
|
28
|
+
const here = readSandboxMark(root);
|
|
29
|
+
const real = here ? here.from : root;
|
|
30
|
+
const box = here ? root : sandboxRootOf(real);
|
|
31
|
+
const existing = here ?? ((await exists(box)) ? readSandboxMark(box) : null);
|
|
32
|
+
if (!existing && (await exists(box)))
|
|
33
|
+
throw new UsageError(`${redactHome(box)} 已经有了,但不是 sandbox(没有 ${SANDBOX_MARK}),不动它;挪走再试`);
|
|
34
|
+
if (existing && !opts.fresh) {
|
|
35
|
+
const moved = await followPort(box, existing.from);
|
|
36
|
+
return { root: box, from: existing.from, created: false, notes: moved ? [moved] : [] };
|
|
37
|
+
}
|
|
38
|
+
if (!(await exists(join(real, CONFIG_FILE))))
|
|
39
|
+
throw new UsageError(`这个 sandbox 是从 ${redactHome(real)} 分出来的,那里已经没有 workspace 了,重新分不了`);
|
|
40
|
+
const notes = await fork(real, box, opts);
|
|
41
|
+
return { root: box, from: real, created: true, notes };
|
|
42
|
+
}
|
|
43
|
+
/** 试用的端口永远是真 workspace 的 +1:真的搬了端口,试用下次打开跟过去,不用记两套规矩;返回说明,没动 → null */
|
|
44
|
+
async function followPort(box, real) {
|
|
45
|
+
const file = join(box, CONFIG_FILE);
|
|
46
|
+
try {
|
|
47
|
+
const realRaw = readJson(join(real, CONFIG_FILE));
|
|
48
|
+
const raw = readJson(file);
|
|
49
|
+
if (realRaw === null || raw === null)
|
|
50
|
+
return null;
|
|
51
|
+
const want = sandboxPort(parseConfig(realRaw, join(real, CONFIG_FILE)).server.port);
|
|
52
|
+
const server = (raw.server && typeof raw.server === 'object' ? raw.server : {});
|
|
53
|
+
if (server.port === want)
|
|
54
|
+
return null;
|
|
55
|
+
await writeFile(file, `${JSON.stringify({ ...raw, server: { ...server, port: want } }, null, 2)}\n`);
|
|
56
|
+
return `真 workspace 换了端口,试用跟着改成 ${want}`;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null; // 真的那份坏了或没了:试用照旧
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** 真 workspace → box:根下照拷(跳过状态),外面的 vault 拷进来,补骨架,写改过的 cotutor.json,最后写标记再换上 */
|
|
63
|
+
async function fork(real, box, opts) {
|
|
64
|
+
const ws = loadWorkspace(real, opts);
|
|
65
|
+
if (ws.sandbox)
|
|
66
|
+
throw new UsageError(`${redactHome(real)} 本身就是 sandbox,不从它再分`);
|
|
67
|
+
const vault = ws.paths.vault;
|
|
68
|
+
if (!(await stat(vault).then((s) => s.isDirectory(), () => false)))
|
|
69
|
+
throw new UsageError(`vault 不在:${redactHome(vault)}(cotutor.json 的 paths.vault);先修好真 workspace(cotutor doctor)再分`);
|
|
70
|
+
const notes = [];
|
|
71
|
+
const config = sandboxConfig(readJson(ws.files.config), ws, box, notes);
|
|
72
|
+
parseConfig(config, join(box, CONFIG_FILE));
|
|
73
|
+
const skip = new Set(SANDBOX_SKIP.map((n) => join(real, n)));
|
|
74
|
+
if (inside(ws.paths.captures, real))
|
|
75
|
+
skip.add(ws.paths.captures);
|
|
76
|
+
const keep = (src) => basename(src) !== '.git' && !skip.has(src);
|
|
77
|
+
const tmp = `${box}.tmp-${process.pid}`;
|
|
78
|
+
await rm(tmp, { recursive: true, force: true });
|
|
79
|
+
await mkdir(tmp, { recursive: true });
|
|
80
|
+
try {
|
|
81
|
+
for (const name of await readdir(real)) {
|
|
82
|
+
const src = join(real, name);
|
|
83
|
+
if (name === CONFIG_FILE || !keep(src))
|
|
84
|
+
continue;
|
|
85
|
+
// 链照原样拷(.claude/skills → ../.agents/skills 在 sandbox 里还是指自己那份)
|
|
86
|
+
await cp(src, join(tmp, name), { recursive: true, verbatimSymlinks: true, filter: keep });
|
|
87
|
+
}
|
|
88
|
+
await mkdir(join(tmp, '.cotutor'), { recursive: true });
|
|
89
|
+
for (const f of SANDBOX_KEEP_MACHINE)
|
|
90
|
+
await copyFile(join(real, '.cotutor', f), join(tmp, '.cotutor', f)).catch(() => undefined);
|
|
91
|
+
if (!inside(vault, real)) {
|
|
92
|
+
if (await exists(join(tmp, SANDBOX_VAULT)))
|
|
93
|
+
throw new UsageError(`workspace 根下已经有 ${SANDBOX_VAULT},外面的 vault 拷不进 sandbox;把它改个名再分`);
|
|
94
|
+
await cp(vault, join(tmp, SANDBOX_VAULT), { recursive: true, verbatimSymlinks: true, filter: (src) => basename(src) !== '.git' });
|
|
95
|
+
}
|
|
96
|
+
for (const d of DIRS)
|
|
97
|
+
await mkdir(join(tmp, d), { recursive: true });
|
|
98
|
+
for (const f of LEDGER_FILES)
|
|
99
|
+
await writeFile(join(tmp, f), '');
|
|
100
|
+
await writeFile(join(tmp, CONFIG_FILE), `${JSON.stringify(config, null, 2)}\n`);
|
|
101
|
+
const mark = { from: real, vault, forkedAt: (opts.now ?? new Date()).toISOString() };
|
|
102
|
+
await writeFile(join(tmp, SANDBOX_MARK), `${JSON.stringify(mark, null, 2)}\n`);
|
|
103
|
+
// 旧的到这里才删:openSandbox 查过它带标记
|
|
104
|
+
if (await exists(box))
|
|
105
|
+
await rm(box, { recursive: true, force: true });
|
|
106
|
+
await rename(tmp, box);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
await rm(tmp, { recursive: true, force: true });
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
return notes;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* sandbox 的 cotutor.json:在真的那份原文上改(_note 这些机器不认识的键照留)。
|
|
116
|
+
* 标题加「试用 · 」、端口 +1、证书改绝对路径、自动记账关掉;paths 里每个配过的角色照它在真 workspace 里落在哪,改成指 sandbox 里对应的那处(写相对的,sandbox 挪了也不断)。
|
|
117
|
+
* 落在 workspace 与 vault 外面的:只读的材料照旧指那份,captures 改回缺省,其余会写的拦下——试用写进去的会混进真的。
|
|
118
|
+
*/
|
|
119
|
+
export function sandboxConfig(raw, ws, box, notes) {
|
|
120
|
+
const real = ws.root;
|
|
121
|
+
const vault = ws.paths.vault;
|
|
122
|
+
const isObj = (v) => Boolean(v) && typeof v === 'object' && !Array.isArray(v);
|
|
123
|
+
const cfg = structuredClone(raw);
|
|
124
|
+
cfg.title = `${SANDBOX_TITLE} · ${ws.config.title}`;
|
|
125
|
+
const port = ws.config.server.port;
|
|
126
|
+
const server = { ...(isObj(raw.server) ? raw.server : {}), port: sandboxPort(port) };
|
|
127
|
+
const https = ws.config.server.https;
|
|
128
|
+
if (https)
|
|
129
|
+
server.https = { cert: expandPath(https.cert, real), key: expandPath(https.key, real) };
|
|
130
|
+
cfg.server = server;
|
|
131
|
+
// 自动记账关掉:试出来的话题不值得每个花一轮老师去记;要试记账,家长端点「记账」或在 sandbox 的 cotutor.json 打开
|
|
132
|
+
if (ws.config.vault.autoBookkeep) {
|
|
133
|
+
cfg.vault = { ...(isObj(raw.vault) ? raw.vault : {}), autoBookkeep: false };
|
|
134
|
+
notes.push('自动记账在 sandbox 里关了(试出来的话题不值得花钱记);要试记账,家长端点「记账」,或在 sandbox 的 cotutor.json 打开 vault.autoBookkeep');
|
|
135
|
+
}
|
|
136
|
+
const paths = isObj(raw.paths) ? { ...raw.paths } : {};
|
|
137
|
+
const vaultInside = inside(vault, real);
|
|
138
|
+
const newVault = vaultInside ? join(box, relative(real, vault)) : join(box, SANDBOX_VAULT);
|
|
139
|
+
if (!vaultInside)
|
|
140
|
+
paths.vault = SANDBOX_VAULT;
|
|
141
|
+
else if ('vault' in paths)
|
|
142
|
+
paths.vault = relative(real, vault) || '.';
|
|
143
|
+
for (const role of Object.keys(paths)) {
|
|
144
|
+
if (role === 'vault')
|
|
145
|
+
continue;
|
|
146
|
+
const abs = ws.paths[role];
|
|
147
|
+
const workspaceSide = PATH_ROLES.includes(role) && WORKSPACE_ROLES.includes(role);
|
|
148
|
+
const target = inside(abs, vault) ? join(newVault, relative(vault, abs)) : inside(abs, real) ? join(box, relative(real, abs)) : null;
|
|
149
|
+
if (target) {
|
|
150
|
+
paths[role] = relative(workspaceSide ? box : newVault, target) || '.';
|
|
151
|
+
}
|
|
152
|
+
else if (READ_ONLY_ROLES.includes(role)) {
|
|
153
|
+
paths[role] = abs;
|
|
154
|
+
notes.push(`paths.${role} 在 workspace 与 vault 外面(${redactHome(abs)}):只读,照旧用那份`);
|
|
155
|
+
}
|
|
156
|
+
else if (workspaceSide) {
|
|
157
|
+
delete paths[role];
|
|
158
|
+
notes.push(`paths.${role} 在 workspace 外面(${redactHome(abs)}):sandbox 里改用缺省的 ${ROLE_DEFAULTS[role]}`);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
throw new UsageError(`paths.${role} 指到 workspace 与 vault 外面(${redactHome(abs)}),试用时写进去的会混进真的。改成 vault 里的路径再分,或者另建一个试用 workspace(cotutor init <slug> --dir <dir>)`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
cfg.paths = paths;
|
|
165
|
+
return cfg;
|
|
166
|
+
}
|