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,1038 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cotutor CLI 入口。命令:init / doctor / serve;全局 --workspace <dir>、--sandbox [--fresh]、--json。
|
|
3
|
+
* exit:0 通过 / 1 体检不过 / 2 用法错误 / 4 内部错误。
|
|
4
|
+
*/
|
|
5
|
+
import { createInterface } from 'node:readline/promises';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { readFile } from 'node:fs/promises';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { constants as osConstants } from 'node:os';
|
|
10
|
+
import { basename, join } from 'node:path';
|
|
11
|
+
import { doctorWorkspace } from "./doctor.js";
|
|
12
|
+
import { initWorkspace } from "./init.js";
|
|
13
|
+
import { gradePacks } from "./grades.js";
|
|
14
|
+
import { makeCert } from "./cert.js";
|
|
15
|
+
import { PIN_RE, USER_PIN_FILE, clearPin, readPin, setPin } from "../http/access.js";
|
|
16
|
+
import { addTutorFile, upgradeTutors } from "./tutors.js";
|
|
17
|
+
import { activityRunFor, checkActivities, upgradeActivities } from "./activities.js";
|
|
18
|
+
import { configGapsOf, migrateLayout, upgradeConfig } from "./migrate.js";
|
|
19
|
+
import { linkClaudeSkills, upgradeSkills } from "./skills.js";
|
|
20
|
+
import { addTheme, upgradeThemes } from "./themes.js";
|
|
21
|
+
import { patchConfig } from "../workspace.js";
|
|
22
|
+
import { resolveRoot } from "../workspace.js";
|
|
23
|
+
import { bindFailure, portHolder, whoHolds } from "./port.js";
|
|
24
|
+
import { httpsFiles, serveWorkspace, siteUrl } from "./serve.js";
|
|
25
|
+
import { teeLogs } from "./serve-log.js";
|
|
26
|
+
import { controlService, earlyLogHint, findService, installService, listServices, probeHealth, realHost, runKeeper, servicePlatform, serviceStatus, tailLog, uninstallService, waitHealthy } from "./service.js";
|
|
27
|
+
import { openSandbox } from "./sandbox.js";
|
|
28
|
+
import { dropTrayIfNoServices, ensureTray, installTray, removeTray, trayDir, trayStatus, traySupported } from "./tray.js";
|
|
29
|
+
import { serveMock } from "../mock/mock.js";
|
|
30
|
+
import { UsageError, loadWorkspace, redactDeep, redactHome, workspaceReport } from "../workspace.js";
|
|
31
|
+
import { USAGE } from "./usage.js";
|
|
32
|
+
import { PHOTO_MAX_BYTES, createContext } from "../http/app.js";
|
|
33
|
+
import { MESSAGE_FROM } from "../../schema/index.js";
|
|
34
|
+
import { formatEvent, laneFilter, parseEvents } from "../../runtime/journal/events.js";
|
|
35
|
+
import { localDate } from "../../shared/time.js";
|
|
36
|
+
import { sumTokens, tokenText } from "../../shared/tokens.js";
|
|
37
|
+
import { rateThread, writeCapture } from "../../runtime/journal/store.js";
|
|
38
|
+
function version() {
|
|
39
|
+
const pkg = JSON.parse(readFileSync(fileURLToPath(new URL('../../../package.json', import.meta.url)), 'utf8'));
|
|
40
|
+
return pkg.version;
|
|
41
|
+
}
|
|
42
|
+
/** 能不能问人:真终端、不要 JSON */
|
|
43
|
+
function interactive(json) {
|
|
44
|
+
return !json && Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
45
|
+
}
|
|
46
|
+
function parseArgs(argv, valued) {
|
|
47
|
+
const flags = {};
|
|
48
|
+
const positionals = [];
|
|
49
|
+
let cmd;
|
|
50
|
+
for (let i = 0; i < argv.length; i++) {
|
|
51
|
+
const a = argv[i];
|
|
52
|
+
if (a.startsWith('--')) {
|
|
53
|
+
const key = a.slice(2);
|
|
54
|
+
if (valued.includes(key)) {
|
|
55
|
+
const next = argv[i + 1];
|
|
56
|
+
// --force 的值可省:upgrade --force <老师>,home publish --force
|
|
57
|
+
if ((next === undefined || next.startsWith('--')) && key === 'force') {
|
|
58
|
+
flags[key] = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (next === undefined || next.startsWith('--'))
|
|
62
|
+
throw new UsageError(`选项 --${key} 需要一个值。\n${USAGE}`);
|
|
63
|
+
flags[key] = next;
|
|
64
|
+
i++;
|
|
65
|
+
}
|
|
66
|
+
else
|
|
67
|
+
flags[key] = true;
|
|
68
|
+
}
|
|
69
|
+
else if (cmd === undefined)
|
|
70
|
+
cmd = a;
|
|
71
|
+
else
|
|
72
|
+
positionals.push(a);
|
|
73
|
+
}
|
|
74
|
+
return { cmd, positionals, flags };
|
|
75
|
+
}
|
|
76
|
+
/** 用默认浏览器打开(各系统各自的打开命令,失败不吭声) */
|
|
77
|
+
async function openInBrowser(page) {
|
|
78
|
+
const { spawn } = await import('node:child_process');
|
|
79
|
+
const [cmd, args] = process.platform === 'darwin' ? ['open', [page]] : process.platform === 'win32' ? ['cmd', ['/c', 'start', '', page]] : ['xdg-open', [page]];
|
|
80
|
+
spawn(cmd, args, { stdio: 'ignore', detached: true }).on('error', () => { }).unref();
|
|
81
|
+
}
|
|
82
|
+
/** 打开哪一页的那一行(地址独占行尾:后面紧跟全角括号的话,终端不把它认成链接);--open(旧名 --open-qr)顺手打开 */
|
|
83
|
+
async function printOpen(label, page, hint, open) {
|
|
84
|
+
process.stdout.write(` ${label} ${page}\n${' '.repeat(label.length * 2 + 3)}${hint}\n`);
|
|
85
|
+
if (open)
|
|
86
|
+
await openInBrowser(page);
|
|
87
|
+
}
|
|
88
|
+
const wantsOpen = (flags) => flags.open === true || flags['open-qr'] === true;
|
|
89
|
+
/** 不用 workspace 的命令:--sandbox 对它们没有意义 */
|
|
90
|
+
const NO_WORKSPACE = ['grades', 'init', 'mock', 'pin', 'cert', 'tray'];
|
|
91
|
+
export async function main(argv) {
|
|
92
|
+
let json = false;
|
|
93
|
+
try {
|
|
94
|
+
const { cmd, positionals, flags } = parseArgs(argv, ['workspace', 'dir', 'name', 'grade', 'port', 'from', 'runtime', 'force', 'display', 'subject', 'avatar', 'description', 'scenario', 'delay', 'lane', 'job', 'date', 'thread', 'at', 'photo', 'activity', 'param', 'runs', 'only', 'kid-model', 'pin', 'out', 'bundles', 'step', 'voice', 'tutor', 'instruction', 'log-dir', 'lines']);
|
|
95
|
+
json = flags.json === true;
|
|
96
|
+
let workspace = typeof flags.workspace === 'string' ? flags.workspace : undefined;
|
|
97
|
+
// --version / --help 是旗标不是命令,parseArgs 把它们收进 flags,cmd 拿不到,所以在 switch 前处理
|
|
98
|
+
if (flags.version === true || cmd === '-v') {
|
|
99
|
+
process.stdout.write(`${version()}\n`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (flags.help === true || cmd === undefined || cmd === '-h') {
|
|
103
|
+
process.stdout.write(USAGE);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// --sandbox:要 workspace 的命令都换到它的试用副本(src/app/cli/sandbox.ts),下面照常把它当 --workspace
|
|
107
|
+
if (flags.fresh === true && flags.sandbox !== true)
|
|
108
|
+
throw new UsageError('--fresh 只跟 --sandbox 一起用:cotutor serve --sandbox --fresh 重新分出试用 workspace');
|
|
109
|
+
if (flags.sandbox === true) {
|
|
110
|
+
if (NO_WORKSPACE.includes(cmd))
|
|
111
|
+
throw new UsageError(`${cmd} 不用 workspace,--sandbox 没有意义`);
|
|
112
|
+
if (cmd === 'service')
|
|
113
|
+
throw new UsageError('试用 workspace 不装后台服务:试的时候 cotutor serve --sandbox 在前台跑');
|
|
114
|
+
const sb = await openSandbox(workspace, { fresh: flags.fresh === true });
|
|
115
|
+
workspace = sb.root;
|
|
116
|
+
if (!json) {
|
|
117
|
+
process.stderr.write(sb.created ? `分出试用 workspace → ${redactHome(sb.root)}(从 ${redactHome(sb.from)};设置照拷,对话与照片不带,vault 拷了一份)\n` : `试用 workspace ${redactHome(sb.root)}\n`);
|
|
118
|
+
for (const n of sb.notes)
|
|
119
|
+
process.stderr.write(` ${n}\n`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
switch (cmd) {
|
|
123
|
+
case 'grades': {
|
|
124
|
+
const packs = (await gradePacks()).map(({ grade }) => ({ grade }));
|
|
125
|
+
process.stdout.write(json ? `${JSON.stringify(packs, null, 2)}\n` : `可安装的教学包:${packs.map((p) => `${p.grade} 年级`).join('、') || '无'}\n`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
case 'init': {
|
|
129
|
+
const slug = positionals[0];
|
|
130
|
+
if (!slug || !/^[a-z0-9][a-z0-9-]*$/.test(slug))
|
|
131
|
+
throw new UsageError(`init 需要一个 slug(小写字母数字连字符,是短名不是真名),如 cotutor init ming。\n${USAGE}`);
|
|
132
|
+
const port = typeof flags.port === 'string' ? Number(flags.port) : undefined;
|
|
133
|
+
if (port !== undefined && !(Number.isInteger(port) && port > 0 && port < 65536))
|
|
134
|
+
throw new UsageError('--port 要是 1–65535 的整数');
|
|
135
|
+
if (flags.grade !== undefined && (typeof flags.grade !== 'string' || !/^[1-6]$/.test(flags.grade)))
|
|
136
|
+
throw new UsageError('--grade 要是 1–6 的整数,如 --grade 2');
|
|
137
|
+
const r = await initWorkspace({ slug, grade: flags.grade === undefined ? undefined : Number(flags.grade), dir: typeof flags.dir === 'string' ? flags.dir : undefined, name: typeof flags.name === 'string' ? flags.name : undefined, port });
|
|
138
|
+
if (json)
|
|
139
|
+
process.stdout.write(`${JSON.stringify(redactDeep(r), null, 2)}\n`);
|
|
140
|
+
else {
|
|
141
|
+
process.stdout.write(`cotutor init → ${redactHome(r.root)}\n`);
|
|
142
|
+
for (const s of r.steps)
|
|
143
|
+
process.stdout.write(` ${s.action.padEnd(7)} ${s.item}${s.note ? ` (${s.note})` : ''}\n`);
|
|
144
|
+
process.stdout.write('下一步:\n');
|
|
145
|
+
for (const s of r.suggestions)
|
|
146
|
+
process.stdout.write(` - ${s}\n`);
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
case 'doctor': {
|
|
151
|
+
const r = await doctorWorkspace(workspace, { live: flags.live === true });
|
|
152
|
+
if (json)
|
|
153
|
+
process.stdout.write(`${JSON.stringify(redactDeep(r), null, 2)}\n`);
|
|
154
|
+
else {
|
|
155
|
+
for (const c of r.checks) {
|
|
156
|
+
process.stdout.write(`${c.ok ? '✓' : c.required ? '✗' : '!'} ${c.name.padEnd(28)} ${c.detail}\n`);
|
|
157
|
+
if (!c.ok && c.fix)
|
|
158
|
+
process.stdout.write(` 修复:${c.fix}\n`);
|
|
159
|
+
}
|
|
160
|
+
process.stdout.write(r.ok ? '体检通过\n' : '体检不过(✗ 是必需项)\n');
|
|
161
|
+
}
|
|
162
|
+
if (!r.ok)
|
|
163
|
+
process.exitCode = 1;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
case 'serve': {
|
|
167
|
+
const port = typeof flags.port === 'string' ? Number(flags.port) : undefined;
|
|
168
|
+
// 后台服务(cotutor service 装的)带 --log-dir:输出按天落文件
|
|
169
|
+
const logDir = typeof flags['log-dir'] === 'string' ? flags['log-dir'] : undefined;
|
|
170
|
+
if (logDir)
|
|
171
|
+
teeLogs(logDir);
|
|
172
|
+
// 没接住的错误:记一笔再以非 0 退(后台服务隔 10 秒再起);和 Node 缺省一样会退,只是日志里有时刻和这句
|
|
173
|
+
const fatal = (what) => (err) => {
|
|
174
|
+
process.stderr.write(`cotutor serve: ${what},退出${process.env.COTUTOR_SERVICE ? '(后台服务十秒后自己再起)' : ''}\n${err instanceof Error ? err.stack : String(err)}\n`);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
};
|
|
177
|
+
process.on('uncaughtException', fatal('没接住的错误'));
|
|
178
|
+
process.on('unhandledRejection', fatal('没接住的 Promise 错误'));
|
|
179
|
+
let r;
|
|
180
|
+
try {
|
|
181
|
+
r = await serveWorkspace({ workspace, port, http: flags.http === true });
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
const code = err.code;
|
|
185
|
+
if (code !== 'EADDRINUSE' && code !== 'EACCES')
|
|
186
|
+
throw err;
|
|
187
|
+
const busy = err.port ?? port ?? 0;
|
|
188
|
+
// 后台服务以 0 退:launchd / systemd 只在崩了时拉起,不一直重试抢端口;系统不让绑的,重试也没用
|
|
189
|
+
const service = Boolean(process.env.COTUTOR_SERVICE);
|
|
190
|
+
const who = code === 'EADDRINUSE' ? await whoHolds(realHost(), busy) : null;
|
|
191
|
+
process.stderr.write(`cotutor serve: ${bindFailure(code, busy, who, service)}\n`);
|
|
192
|
+
process.exitCode = service ? 0 : 1;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (flags.trace === true) {
|
|
196
|
+
const keep = laneFilter(typeof flags.lane === 'string' ? flags.lane : undefined);
|
|
197
|
+
r.ctx.runner.onEvent((e) => { if (keep(e.event))
|
|
198
|
+
process.stdout.write(`${e.tutor} ${e.job} ${formatEvent(e.event)}\n`); });
|
|
199
|
+
}
|
|
200
|
+
process.stdout.write(`cotutor serve ${JSON.stringify(workspaceReport(r.ws))}\n`);
|
|
201
|
+
if (r.ws.sandbox)
|
|
202
|
+
process.stdout.write(` 试用:从 ${redactHome(r.ws.sandbox.from)} 分出来,对话、照片、vault 的写入都留在这里,不进孩子的记录;--sandbox --fresh 重新分\n`);
|
|
203
|
+
for (const w of r.warnings)
|
|
204
|
+
process.stdout.write(` ! ${w}\n`);
|
|
205
|
+
if (!r.https)
|
|
206
|
+
process.stdout.write(' ! HTTP:平板、手机上按住说话要 HTTPS;cotutor cert 建证书后重启即走 HTTPS\n');
|
|
207
|
+
// 只给家长端一个入口:孩子的平板扫码、排首页、记录都在里面,工作台从它的右上角进
|
|
208
|
+
await printOpen('家长端', r.parentPage, '在这台电脑上打开:孩子的平板扫码、排首页、看记录都在里面;--open 顺手打开', wantsOpen(flags));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
case 'open': {
|
|
212
|
+
// 不用记端口:读 cotutor.json 打开家长端;--sandbox 开试用的那个(上面已经换了 workspace),--print 只打印不开
|
|
213
|
+
const ws = loadWorkspace(workspace);
|
|
214
|
+
const port = ws.config.server.port;
|
|
215
|
+
const page = siteUrl(httpsFiles(ws) ? 'https' : 'http', 'localhost', port, '/parent/home');
|
|
216
|
+
const running = Boolean(await probeHealth(port));
|
|
217
|
+
if (json)
|
|
218
|
+
process.stdout.write(`${JSON.stringify({ url: page, running }, null, 2)}\n`);
|
|
219
|
+
else if (running)
|
|
220
|
+
process.stdout.write(`${page}\n`);
|
|
221
|
+
else
|
|
222
|
+
process.stderr.write(`${page} 没在跑(端口 ${port}):${ws.sandbox ? 'cotutor serve --sandbox 起试用的' : 'cotutor service start,或在终端里 cotutor serve'}\n`);
|
|
223
|
+
if (!running) {
|
|
224
|
+
process.exitCode = 1;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (!json && flags.print !== true)
|
|
228
|
+
await openInBrowser(page);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
case 'tray': {
|
|
232
|
+
// 菜单栏 / 任务栏图标(tray.ts);tray run 由 bin/cotutor.js 直接走轻入口,不到这里
|
|
233
|
+
const sub = positionals[0];
|
|
234
|
+
if (sub !== 'on' && sub !== 'off' && sub !== 'status')
|
|
235
|
+
throw new UsageError(`tray 后面跟 on / off / status 之一。\n${USAGE}`);
|
|
236
|
+
const host = realHost();
|
|
237
|
+
if (!traySupported(host.platform))
|
|
238
|
+
throw new UsageError(`${host.platform} 上没做托盘图标(GNOME 缺省不显示托盘):cotutor service status 看服务状态`);
|
|
239
|
+
const word = host.platform === 'darwin' ? '菜单栏图标' : '任务栏图标';
|
|
240
|
+
if (sub === 'on') {
|
|
241
|
+
if (!(await listServices(host)).length)
|
|
242
|
+
throw new UsageError('还没装后台服务,图标没东西可看:cotutor service install 会一起装上图标');
|
|
243
|
+
const r = await installTray(host);
|
|
244
|
+
if (json)
|
|
245
|
+
process.stdout.write(`${JSON.stringify(redactDeep(r), null, 2)}\n`);
|
|
246
|
+
else
|
|
247
|
+
process.stdout.write(r.started ? `${word}起来了:登录后自己出现,点它看状态、打开家长端${host.platform === 'win32' ? '(新图标先收在任务栏右下角的 ^ 里)' : ''}\n` : `${word}的登录项写好了,这个会话里没起来,下次登录出现\n`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (sub === 'off') {
|
|
251
|
+
const had = await removeTray(host, { remember: true });
|
|
252
|
+
if (json)
|
|
253
|
+
process.stdout.write(`${JSON.stringify({ removed: had })}\n`);
|
|
254
|
+
else
|
|
255
|
+
process.stdout.write(`${had ? `关了${word}` : `${word}本来就没开`};后台服务不受影响,以后 service install 也不再带上。要回来:cotutor tray on\n`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const st = await trayStatus(host);
|
|
259
|
+
if (json) {
|
|
260
|
+
process.stdout.write(`${JSON.stringify(redactDeep({ ...st, dir: trayDir(host) }), null, 2)}\n`);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
process.stdout.write(!st.installed ? `${word}:没开${st.off ? '(家长关掉的)' : ''};cotutor tray on\n` : `${word}:${st.running ? `在跑,pid ${st.pid ?? '?'}` : '没在跑(点过退出就是这样,下次登录回来)'}\n`);
|
|
264
|
+
if (st.autostart === false)
|
|
265
|
+
process.stdout.write(' ! 下次登录不会自己起来:登录项被关掉或删掉了(任务管理器的「启动应用」、杀毒软件)。要它:cotutor tray on\n');
|
|
266
|
+
if (st.installed)
|
|
267
|
+
process.stdout.write(` 日志 ${redactHome(join(trayDir(host), 'tray.log'))}\n`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
case 'service': {
|
|
271
|
+
const sub = positionals[0];
|
|
272
|
+
// 看护进程(Windows 的启动器起的,不是给人敲的):起 serve,崩了再起
|
|
273
|
+
if (sub === 'run') {
|
|
274
|
+
const logDir = flags['log-dir'];
|
|
275
|
+
if (typeof workspace !== 'string' || typeof logDir !== 'string')
|
|
276
|
+
throw new UsageError('service run 要 --workspace 与 --log-dir');
|
|
277
|
+
process.exitCode = await runKeeper({ root: workspace, logDir, label: `cotutor-${basename(logDir)}` });
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const SUBS = ['install', 'uninstall', 'start', 'stop', 'restart', 'status', 'logs'];
|
|
281
|
+
if (!sub || !SUBS.includes(sub))
|
|
282
|
+
throw new UsageError(`service 后面跟 ${SUBS.join(' / ')} 之一。\n${USAGE}`);
|
|
283
|
+
const host = realHost();
|
|
284
|
+
const platform = servicePlatform(host.platform);
|
|
285
|
+
const ws = loadWorkspace(workspace);
|
|
286
|
+
if (ws.sandbox)
|
|
287
|
+
throw new UsageError('试用 workspace 不装后台服务:试的时候 cotutor serve --sandbox 在前台跑');
|
|
288
|
+
const root = ws.root;
|
|
289
|
+
const port = ws.config.server.port;
|
|
290
|
+
const scheme = httpsFiles(ws) ? 'https' : 'http';
|
|
291
|
+
const parentPage = siteUrl(scheme, 'localhost', port, '/parent/home');
|
|
292
|
+
const when = (iso) => { const d = new Date(iso); const ago = Math.round((Date.now() - d.getTime()) / 60_000); return `${localDate(d)} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}(${ago < 60 ? `${ago} 分钟` : ago < 2880 ? `${Math.round(ago / 60)} 小时` : `${Math.round(ago / 1440)} 天`}前)`; };
|
|
293
|
+
// 后台读不到 vault(macOS 拦着后台进程读「文稿」、iCloud 云盘)、版本对不上:装完、起完、看状态都提
|
|
294
|
+
const notes = (h) => {
|
|
295
|
+
const out = [];
|
|
296
|
+
if (h?.vault === 'denied')
|
|
297
|
+
out.push(platform === 'darwin'
|
|
298
|
+
? `! 后台服务读不到 vault(${redactHome(ws.paths.vault)}):macOS 不让后台进程读「文稿」「桌面」和 iCloud 云盘,也不弹窗问。系统设置 › 隐私与安全性 › 完全磁盘访问权限,点 + 加上 ${process.execPath}(或把 vault 挪出这几处),然后 cotutor service restart`
|
|
299
|
+
: `! 后台服务读不到 vault(${redactHome(ws.paths.vault)}):看看这个目录的权限,然后 cotutor service restart`);
|
|
300
|
+
if (h?.version && h.version !== version())
|
|
301
|
+
out.push(`! 在跑的是 ${h.version},装的是 ${version()}:cotutor service restart 换上新的`);
|
|
302
|
+
return out;
|
|
303
|
+
};
|
|
304
|
+
const tellLog = async (dir) => {
|
|
305
|
+
const t = dir ? await tailLog(dir, 15) : null;
|
|
306
|
+
if (t) {
|
|
307
|
+
process.stdout.write(` ${redactHome(t.file)} 最后几行:\n`);
|
|
308
|
+
for (const l of t.lines)
|
|
309
|
+
process.stdout.write(` ${l}\n`);
|
|
310
|
+
}
|
|
311
|
+
else if (dir)
|
|
312
|
+
process.stdout.write(` 日志还没有;起来之前的输出看 ${redactHome(earlyLogHint(platform, { label: `cotutor-${basename(dir)}`, logDir: dir }))}\n`);
|
|
313
|
+
};
|
|
314
|
+
if (sub === 'install') {
|
|
315
|
+
const before = await probeHealth(port);
|
|
316
|
+
const had = await findService(host, root);
|
|
317
|
+
if (before && before.mode !== 'service')
|
|
318
|
+
throw new UsageError(`端口 ${port} 上有一个 cotutor serve 在终端里跑着${before.pid ? `(pid ${before.pid})` : ''}:先关掉它(那个终端里 Ctrl-C),再 cotutor service install`);
|
|
319
|
+
if (before && !had)
|
|
320
|
+
throw new UsageError(`端口 ${port} 上已经是另一个 workspace 的后台服务:两个孩子各用各的端口(cotutor.json 的 server.port)`);
|
|
321
|
+
const r = await installService(host, root);
|
|
322
|
+
const h = await waitHealthy(port, 20_000, before?.started);
|
|
323
|
+
// 菜单栏图标一起装:它出问题不能让服务也算没装好
|
|
324
|
+
let tray = 'skipped';
|
|
325
|
+
if (flags['no-tray'] !== true) {
|
|
326
|
+
try {
|
|
327
|
+
tray = await ensureTray(host);
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
tray = 'failed';
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (json) {
|
|
334
|
+
process.stdout.write(`${JSON.stringify(redactDeep({ ...r, healthy: Boolean(h), health: h, tray }), null, 2)}\n`);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
process.stdout.write(`${r.replaced ? '重装了' : '装好了'}后台服务 ${r.spec.label}(${redactHome(r.file)})\n`);
|
|
338
|
+
process.stdout.write(` 登录这台电脑后自己起来,崩了隔 10 秒再起;关了终端照样跑。日志 ${redactHome(join(r.spec.logDir, '<日期>.log'))},留 14 天\n`);
|
|
339
|
+
if (platform !== 'linux')
|
|
340
|
+
process.stdout.write(` 电脑睡着时平板连不上:有通话时 cotutor 拦着它闲置睡眠,别的时候看系统设置(cotutor doctor 会提);合盖照样睡\n`);
|
|
341
|
+
if (platform === 'win32') {
|
|
342
|
+
process.stdout.write(' 没有黑窗口。任务管理器的「启动应用」里看不到 cotutor 这个名字,显示的是 Windows Based Script Host;登录后约一分钟起来,在不在跑看 cotutor service status\n');
|
|
343
|
+
// 平板连不连得进来:Windows 第一次监听时弹一次「允许访问」,点了取消(或没看见)就再也连不上;不在终端里跑了,这里不说就没地方说了
|
|
344
|
+
const { judgeFirewall, probeFirewall } = await import("./firewall.js");
|
|
345
|
+
const state = await probeFirewall(port);
|
|
346
|
+
const v = state && judgeFirewall(state, port, process.execPath);
|
|
347
|
+
if (v && !v.ok)
|
|
348
|
+
process.stdout.write(` ! ${v.detail}\n 修复:${v.fix ?? ''}\n 弄好以后 cotutor doctor 再查一次\n`);
|
|
349
|
+
}
|
|
350
|
+
if (platform === 'linux') {
|
|
351
|
+
const lg = await host.run('loginctl', ['show-user', process.env.USER ?? '', '-p', 'Linger']);
|
|
352
|
+
if (/Linger=no/.test(lg.stdout))
|
|
353
|
+
process.stdout.write(` ! 现在只在你登录着时跑;不登录也要跑:sudo loginctl enable-linger ${process.env.USER ?? '<用户名>'}\n`);
|
|
354
|
+
}
|
|
355
|
+
if (tray === 'started' || tray === 'later')
|
|
356
|
+
process.stdout.write(` ${platform === 'darwin' ? '菜单栏' : '任务栏'}多了 cotutor 图标:不用开终端就能看状态、打开家长端${tray === 'later' ? '(这个会话里没起来,下次登录出现)' : platform === 'win32' ? '(新图标先收在右下角的 ^ 里)' : ''};不要它:cotutor tray off\n`);
|
|
357
|
+
else if (tray === 'failed')
|
|
358
|
+
process.stdout.write(' ! 图标没装上,服务不受影响;cotutor tray on 再试\n');
|
|
359
|
+
if (h) {
|
|
360
|
+
await printOpen('家长端', parentPage, '孩子的平板扫码、排首页、看记录都在里面', wantsOpen(flags));
|
|
361
|
+
for (const n of notes(h))
|
|
362
|
+
process.stdout.write(`${n}\n`);
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
process.stdout.write(`! 装上了,但 20 秒内没答应(端口 ${port})。cotutor service logs 看看:\n`);
|
|
366
|
+
await tellLog(r.spec.logDir);
|
|
367
|
+
process.exitCode = 1;
|
|
368
|
+
}
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (sub === 'uninstall') {
|
|
372
|
+
const s = await uninstallService(host, root);
|
|
373
|
+
// 这是最后一个服务:图标也没事可做了
|
|
374
|
+
const trayGone = s ? await dropTrayIfNoServices(host).catch(() => false) : false;
|
|
375
|
+
if (json) {
|
|
376
|
+
process.stdout.write(`${JSON.stringify(redactDeep({ removed: s, trayRemoved: trayGone }), null, 2)}\n`);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
process.stdout.write(s ? `卸了后台服务 ${s.label};日志留在 ${redactHome(s.logDir ?? '')},不要了自己删\n要在终端里跑:cotutor serve\n` : '这个 workspace 没装后台服务\n');
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (sub === 'start' || sub === 'stop' || sub === 'restart') {
|
|
383
|
+
const before = sub === 'stop' ? null : await probeHealth(port);
|
|
384
|
+
if (sub === 'start' && before?.mode === 'service') {
|
|
385
|
+
process.stdout.write(`后台服务已经在跑了(pid ${before.pid ?? '?'})\n`);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (sub !== 'stop' && before && before.mode !== 'service')
|
|
389
|
+
throw new UsageError(`端口 ${port} 上有一个 cotutor serve 在终端里跑着:先关掉它(Ctrl-C)`);
|
|
390
|
+
const s = await controlService(host, root, sub);
|
|
391
|
+
if (sub === 'stop') {
|
|
392
|
+
process.stdout.write(`停了 ${s.label}(下次${platform === 'linux' ? '开机' : '登录'}又会自己起来;一直不要它:cotutor service uninstall)\n`);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const h = await waitHealthy(port, 20_000, before?.started);
|
|
396
|
+
if (h) {
|
|
397
|
+
process.stdout.write(`${sub === 'start' ? '起来了' : '重起了'}:pid ${h.pid ?? '?'},版本 ${h.version ?? '?'}\n`);
|
|
398
|
+
for (const n of notes(h))
|
|
399
|
+
process.stdout.write(`${n}\n`);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
process.stdout.write(`! 20 秒内没答应(端口 ${port}):\n`);
|
|
403
|
+
await tellLog(s.logDir);
|
|
404
|
+
process.exitCode = 1;
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (sub === 'status') {
|
|
409
|
+
const st = await serviceStatus(host, root, port);
|
|
410
|
+
// 没答应时看看是不是别的程序占着:后台服务遇到这种情况只写一句日志就不起了,这里要说得出来
|
|
411
|
+
const holder = st.health ? null : await portHolder(host, port);
|
|
412
|
+
if (json) {
|
|
413
|
+
process.stdout.write(`${JSON.stringify(redactDeep({ ...st, portHolder: holder, version: version(), parentPage }), null, 2)}\n`);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (!st.installed)
|
|
417
|
+
process.stdout.write('后台服务:没装(cotutor service install)\n');
|
|
418
|
+
else
|
|
419
|
+
process.stdout.write(`后台服务:装了 ${st.installed.label}(${redactHome(st.installed.file)})\n ${st.running ? `在跑:pid ${st.pid ?? '?'}` : `没在跑${st.lastExit ? `(上次退出:${st.lastExit})` : ''};cotutor service start`}\n`);
|
|
420
|
+
const h = st.health;
|
|
421
|
+
if (h)
|
|
422
|
+
process.stdout.write(` 端口 ${port} 答应:${h.mode === 'service' ? '后台服务' : `终端里的 cotutor serve(pid ${h.pid ?? '?'})`},版本 ${h.version ?? '?'},起来于 ${when(h.started)}\n 家长端 ${parentPage}\n`);
|
|
423
|
+
else
|
|
424
|
+
process.stdout.write(holder ? ` 端口 ${port} 没答应,被占着:${holder.name}(pid ${holder.pid})在用。腾出来以后 cotutor service start;或改 cotutor.json 的 server.port,平板要重新扫码、重加主屏幕图标\n` : ` 端口 ${port} 没答应\n`);
|
|
425
|
+
for (const n of notes(h))
|
|
426
|
+
process.stdout.write(`${n}\n`);
|
|
427
|
+
if (st.autostart === false)
|
|
428
|
+
process.stdout.write(' ! 下次登录不会自己起来:登录项被关掉或删掉了(任务管理器的「启动应用」、杀毒软件)。要它:cotutor service install 再装一次\n');
|
|
429
|
+
if (st.installed?.logDir)
|
|
430
|
+
process.stdout.write(` 日志 ${redactHome(st.installed.logDir)}(cotutor service logs)\n`);
|
|
431
|
+
if (st.installed && !h)
|
|
432
|
+
await tellLog(st.installed.logDir);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
// logs
|
|
436
|
+
const s = await findService(host, root);
|
|
437
|
+
if (!s?.logDir)
|
|
438
|
+
throw new UsageError('这个 workspace 没装后台服务,没有日志;终端里跑的 cotutor serve 输出就在那个终端里');
|
|
439
|
+
const n = typeof flags.lines === 'string' ? Number(flags.lines) : 50;
|
|
440
|
+
if (!(Number.isInteger(n) && n > 0))
|
|
441
|
+
throw new UsageError('--lines 要是正整数');
|
|
442
|
+
const t = await tailLog(s.logDir, n);
|
|
443
|
+
if (t)
|
|
444
|
+
for (const l of t.lines)
|
|
445
|
+
process.stdout.write(`${l}\n`);
|
|
446
|
+
else
|
|
447
|
+
process.stdout.write(`还没有日志;起来之前的输出看 ${redactHome(earlyLogHint(platform, s))}\n`);
|
|
448
|
+
if (flags.follow === true) {
|
|
449
|
+
const { open: openFile, stat: statFile } = await import('node:fs/promises');
|
|
450
|
+
const { logFileFor } = await import("./serve-log.js");
|
|
451
|
+
let file = t?.file ?? logFileFor(s.logDir, new Date());
|
|
452
|
+
let pos = (await statFile(file).catch(() => null))?.size ?? 0;
|
|
453
|
+
setInterval(() => {
|
|
454
|
+
void (async () => {
|
|
455
|
+
const today = logFileFor(s.logDir, new Date());
|
|
456
|
+
if (today !== file && (await statFile(today).catch(() => null))) {
|
|
457
|
+
file = today;
|
|
458
|
+
pos = 0;
|
|
459
|
+
}
|
|
460
|
+
const st = await statFile(file).catch(() => null);
|
|
461
|
+
if (!st || st.size <= pos)
|
|
462
|
+
return;
|
|
463
|
+
const fh = await openFile(file, 'r');
|
|
464
|
+
const buf = Buffer.alloc(st.size - pos);
|
|
465
|
+
await fh.read(buf, 0, buf.length, pos);
|
|
466
|
+
await fh.close();
|
|
467
|
+
pos = st.size;
|
|
468
|
+
process.stdout.write(buf);
|
|
469
|
+
})();
|
|
470
|
+
}, 500);
|
|
471
|
+
}
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
case 'mock': {
|
|
475
|
+
const port = typeof flags.port === 'string' ? Number(flags.port) : undefined;
|
|
476
|
+
if (port !== undefined && !(Number.isInteger(port) && port > 0 && port < 65536))
|
|
477
|
+
throw new UsageError('--port 要是 1–65535 的整数');
|
|
478
|
+
const scenario = typeof flags.scenario === 'string' ? flags.scenario : 'normal';
|
|
479
|
+
if (!['normal', 'nokey'].includes(scenario))
|
|
480
|
+
throw new UsageError('--scenario 只能是 normal / nokey');
|
|
481
|
+
const delayMs = typeof flags.delay === 'string' ? Number(flags.delay) : undefined;
|
|
482
|
+
if (delayMs !== undefined && !(Number.isInteger(delayMs) && delayMs >= 0))
|
|
483
|
+
throw new UsageError('--delay 要是非负整数(毫秒)');
|
|
484
|
+
const r = await serveMock({ port, scenario: scenario, delayMs, http: flags.http === true });
|
|
485
|
+
// 被杀(Ctrl-C、探针收尾)也走 exit,mock 的临时目录才删得掉
|
|
486
|
+
for (const s of ['SIGINT', 'SIGTERM', 'SIGHUP'])
|
|
487
|
+
process.once(s, () => process.exit(128 + osConstants.signals[s]));
|
|
488
|
+
process.stdout.write(`cotutor mock 场景 ${scenario}(不经真实老师与百炼:老师都走实时老师页,假嘴念静音的声音)\n`);
|
|
489
|
+
if (!r.https)
|
|
490
|
+
process.stdout.write(' ! HTTP:平板、手机上按住说话要 HTTPS;cotutor cert 建证书后重启即走 HTTPS\n');
|
|
491
|
+
for (const u of r.urls)
|
|
492
|
+
process.stdout.write(` ${u}\n`);
|
|
493
|
+
process.stdout.write(' 孩子端 /,家长端 /parent;直接开某位老师:/?tutor=chinese-tutor\n');
|
|
494
|
+
await printOpen('扫码页', r.qrPage, '在这台电脑上打开,平板、手机用相机扫;--open 顺手打开', wantsOpen(flags));
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
case 'add': {
|
|
498
|
+
const name = positionals[0];
|
|
499
|
+
const display = typeof flags.display === 'string' ? flags.display : undefined;
|
|
500
|
+
if (!name || !display)
|
|
501
|
+
throw new UsageError(`add 需要老师名和 --display 显示名,如 cotutor add science-tutor --display 科学老师 --subject 科学 --avatar 🔬。\n${USAGE}`);
|
|
502
|
+
const ws = loadWorkspace(workspace);
|
|
503
|
+
if (ws.config.tutors[name])
|
|
504
|
+
throw new UsageError(`cotutor.json 里已经有 ${name} 了;要改人设去老师团页或直接改文件`);
|
|
505
|
+
const subject = typeof flags.subject === 'string' ? flags.subject : undefined;
|
|
506
|
+
const r = await addTutorFile(ws.root, { name, display, subject, description: typeof flags.description === 'string' ? flags.description : undefined });
|
|
507
|
+
await patchConfig(ws, { tutors: { [name]: { display, ...(subject ? { subject } : {}), ...(typeof flags.avatar === 'string' ? { avatar: flags.avatar } : {}), enabled: true, ...(flags.hidden === true ? { hidden: true } : {}) } } });
|
|
508
|
+
if (json)
|
|
509
|
+
process.stdout.write(`${JSON.stringify(redactDeep(r), null, 2)}\n`);
|
|
510
|
+
else {
|
|
511
|
+
process.stdout.write(`加了 ${display}(${name}):\n ${redactHome(r.file)} ← 老师文件,打开把括号里那句换成这位老师的性子\n cotutor.json tutors.${name} ← 人设与政策(老师团页也能改)\n ${redactHome(r.home)}/ ← 它的家\n`);
|
|
512
|
+
process.stdout.write('服务不用重启;孩子端和家长端刷新就有。\n');
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
case 'add-theme': {
|
|
517
|
+
const name = positionals[0];
|
|
518
|
+
if (!name)
|
|
519
|
+
throw new UsageError(`add-theme 需要主题名,如 cotutor add-theme dark。\n${USAGE}`);
|
|
520
|
+
const { root } = resolveRoot(workspace);
|
|
521
|
+
const r = await addTheme(root, name, typeof flags.from === 'string' ? flags.from : 'default');
|
|
522
|
+
if (json)
|
|
523
|
+
process.stdout.write(`${JSON.stringify(redactDeep(r), null, 2)}\n`);
|
|
524
|
+
else
|
|
525
|
+
process.stdout.write(`加了主题 ${name}(拷自 ${r.from}):\n ${redactHome(r.dir)}/theme.json ← 槽的清单(名字 + 给什么用)\n ${redactHome(r.dir)}/kid.css ← 样式,改了刷新就有\n把 cotutor.json 的 kid.theme 改成 "${name}" 就换过去了(服务不用重启)。\n`);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
case 'upgrade': {
|
|
529
|
+
if (flags.grade !== undefined)
|
|
530
|
+
throw new UsageError('upgrade 只更新已安装的教学包,不能用 --grade 切换年级');
|
|
531
|
+
const { root } = resolveRoot(workspace);
|
|
532
|
+
// --config 是另一件事:政策文件补缺(老师文件与 skill 不碰)
|
|
533
|
+
if (flags.config === true) {
|
|
534
|
+
const dryRun = flags['dry-run'] === true;
|
|
535
|
+
const r = await upgradeConfig(root, { dryRun });
|
|
536
|
+
if (json)
|
|
537
|
+
process.stdout.write(`${JSON.stringify(redactDeep(r), null, 2)}\n`);
|
|
538
|
+
else if (!r.gaps.length)
|
|
539
|
+
process.stdout.write(`cotutor.json 不缺什么:出厂模板里的老师、运行时、命令模板旗标都有,也没有不再用的段。\n`);
|
|
540
|
+
else {
|
|
541
|
+
for (const g of r.gaps)
|
|
542
|
+
process.stdout.write(`${dryRun ? '·' : '✓'} ${g.path.padEnd(24)} ${g.detail}\n`);
|
|
543
|
+
for (const st of r.installed)
|
|
544
|
+
process.stdout.write(`✓ ${st.item.padEnd(24)} ${st.note ?? '建好了'}\n`);
|
|
545
|
+
process.stdout.write(dryRun
|
|
546
|
+
? `\n${r.gaps.length} 项要改,还没动文件:cotutor upgrade --config 真改(只加缺的、删不再用的出厂段,你改过的值不动)\n`
|
|
547
|
+
: `\n改了 ${r.gaps.length} 项到 ${redactHome(r.file)};服务在跑的话不用重启(改了就热重载)。\n`);
|
|
548
|
+
}
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const force = typeof flags.force === 'string' ? [flags.force, ...positionals] : positionals;
|
|
552
|
+
// 老布局先原样搬到新位置,下面再照常比新旧
|
|
553
|
+
const layoutSteps = await migrateLayout(root);
|
|
554
|
+
const steps = await upgradeTutors(root, { force });
|
|
555
|
+
const activitySteps = await upgradeActivities(root, { force });
|
|
556
|
+
const skillSteps = await upgradeSkills(root);
|
|
557
|
+
const link = await linkClaudeSkills(root);
|
|
558
|
+
const themeSteps = await upgradeThemes(root);
|
|
559
|
+
// 老师文件换新了,政策文件却不会自动多出新老师与新运行时(cotutor.json 是家长的),提一句
|
|
560
|
+
const gaps = await configGapsOf(root);
|
|
561
|
+
if (json)
|
|
562
|
+
process.stdout.write(`${JSON.stringify(redactDeep({ root, layout: layoutSteps, steps, activities: activitySteps, skills: skillSteps, claudeSkills: link, themes: themeSteps, configGaps: gaps }), null, 2)}\n`);
|
|
563
|
+
else {
|
|
564
|
+
for (const s of layoutSteps)
|
|
565
|
+
process.stdout.write(`${s.action === 'kept' ? '!' : '✓'} ${s.item.padEnd(24)} ${s.note ?? ''}\n`);
|
|
566
|
+
if (link.action !== 'exists')
|
|
567
|
+
process.stdout.write(`${link.action === 'kept' ? '!' : '✓'} ${link.item.padEnd(24)} ${link.note ?? ''}\n`);
|
|
568
|
+
const word = { upgraded: '已换新', latest: '已是最新', 'kept-custom': '自定义,保留', forced: '已覆盖(原文 .bak)', installed: '补上了', retired: '不再出厂,删了', renamed: '改了名' };
|
|
569
|
+
for (const s of skillSteps)
|
|
570
|
+
process.stdout.write(`${s.action === 'kept-custom' ? '!' : '✓'} skill ${s.name.padEnd(18)} ${word[s.action]}${s.basedOn && s.action !== 'latest' ? `(基于 ${s.basedOn})` : ''}${s.machine ? '(机器件)' : ''}\n`);
|
|
571
|
+
for (const s of themeSteps)
|
|
572
|
+
process.stdout.write(`${s.action === 'kept-custom' ? '!' : '✓'} theme ${s.name.padEnd(18)} ${word[s.action]}${s.basedOn && s.action !== 'latest' ? `(基于 ${s.basedOn})` : ''}${s.action === 'kept-custom' ? '(themes/ 里改过的主题不动)' : ''}\n`);
|
|
573
|
+
for (const s of steps) {
|
|
574
|
+
process.stdout.write(`${s.action === 'kept-custom' ? '!' : '✓'} ${s.name.padEnd(18)} ${word[s.action]}${s.basedOn && s.action !== 'latest' ? `(基于 ${s.basedOn})` : ''}\n`);
|
|
575
|
+
if (s.diff?.length) {
|
|
576
|
+
process.stdout.write(` 你的 vs 新版(- 你的 / + 新版),想用新版:cotutor upgrade --force ${s.name}\n`);
|
|
577
|
+
for (const l of s.diff.slice(0, 40))
|
|
578
|
+
process.stdout.write(` ${l}\n`);
|
|
579
|
+
if (s.diff.length > 40)
|
|
580
|
+
process.stdout.write(` …还有 ${s.diff.length - 40} 行\n`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
for (const s of activitySteps) {
|
|
584
|
+
process.stdout.write(`${s.action === 'kept-custom' ? '!' : '✓'} activity ${s.key.padEnd(18)} ${word[s.action]}${s.from ? `(原来叫「${s.from}」)` : ''}${s.basedOn && s.action !== 'latest' ? `(基于 ${s.basedOn})` : ''}\n`);
|
|
585
|
+
if (s.diff?.length) {
|
|
586
|
+
process.stdout.write(` 你的 vs 新版(- 你的 / + 新版),想用新版:cotutor upgrade --force ${s.key}\n`);
|
|
587
|
+
for (const l of s.diff.slice(0, 40))
|
|
588
|
+
process.stdout.write(` ${l}\n`);
|
|
589
|
+
if (s.diff.length > 40)
|
|
590
|
+
process.stdout.write(` …还有 ${s.diff.length - 40} 行\n`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (gaps.length)
|
|
594
|
+
process.stdout.write(`! 配置有 ${gaps.length} 项要改(新出厂老师 / 运行时 / 命令模板旗标 / 不再用的段):cotutor upgrade --config --dry-run 先看\n`);
|
|
595
|
+
}
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
case 'activity': {
|
|
599
|
+
const sub = positionals[0];
|
|
600
|
+
if (sub !== 'check' && sub !== 'eval')
|
|
601
|
+
throw new UsageError(`activity 后面跟 check / eval,如 cotutor activity check chinese-tutor 你说我画。\n${USAGE}`);
|
|
602
|
+
const ws = loadWorkspace(workspace);
|
|
603
|
+
const [, tutor, name] = positionals;
|
|
604
|
+
if (sub === 'eval') {
|
|
605
|
+
if (!tutor || !name)
|
|
606
|
+
throw new UsageError(`activity eval 要老师和名字,如 cotutor activity eval chinese-tutor 你说我画。\n${USAGE}`);
|
|
607
|
+
const runs = typeof flags.runs === 'string' ? Number(flags.runs) : 3;
|
|
608
|
+
if (!Number.isInteger(runs) || runs < 1 || runs > 10)
|
|
609
|
+
throw new UsageError('--runs 要是 1–10 的整数');
|
|
610
|
+
const { evalActivity, makeKidModel } = await import("../activity-eval.js");
|
|
611
|
+
const r = await evalActivity(ws, tutor, name, {
|
|
612
|
+
runs,
|
|
613
|
+
...(typeof flags.only === 'string' ? { only: flags.only } : {}),
|
|
614
|
+
kid: makeKidModel(typeof flags['kid-model'] === 'string' ? { model: flags['kid-model'] } : {}),
|
|
615
|
+
onTurn: json ? undefined : (e) => process.stdout.write(` ${e.example} #${e.run} 第 ${e.turn} 轮 孩子:${e.kid}\n 老师:${e.said.slice(0, 90)}${e.said.length > 90 ? '…' : ''}${e.paint ? ' [画图卡]' : ''}\n`),
|
|
616
|
+
});
|
|
617
|
+
if (json)
|
|
618
|
+
process.stdout.write(`${JSON.stringify(redactDeep(r), null, 2)}\n`);
|
|
619
|
+
else {
|
|
620
|
+
process.stdout.write('\n');
|
|
621
|
+
for (const x of r.examples) {
|
|
622
|
+
process.stdout.write(`${x.pass ? '✓' : '✗'} ${x.name} ${x.runs.filter((y) => y.pass).length}/${x.runs.length} 次过\n`);
|
|
623
|
+
x.runs.forEach((y, k) => {
|
|
624
|
+
for (const g of y.grades.filter((g) => !g.pass))
|
|
625
|
+
process.stdout.write(` #${k + 1} ${g.check}:${g.why ?? ''}\n`);
|
|
626
|
+
if (y.error)
|
|
627
|
+
process.stdout.write(` #${k + 1} ${y.error}\n`);
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
process.stdout.write(`用了 ${tokenText(r.tokens)}(老师;模拟孩子另算,很少)· 报告 ${r.file} · 每轮在 evals/${tutor}/ 里,cotutor show ${tutor} <job> --evals 看\n`);
|
|
631
|
+
}
|
|
632
|
+
if (r.examples.some((x) => !x.pass))
|
|
633
|
+
process.exitCode = 1;
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const results = await checkActivities(ws.root, ws.config.tutors, { tutor, name });
|
|
637
|
+
if (!results.length)
|
|
638
|
+
throw new UsageError(tutor ? `agents/${tutor}/activities/ 里没有${name ? ` ${name}.md` : ' activity 文件'}` : '这个 workspace 里还没有 activity 文件(cotutor upgrade 补出厂的)');
|
|
639
|
+
const fixes = results.reduce((n, r) => n + r.issues.filter((i) => i.level === 'fix').length, 0);
|
|
640
|
+
if (json)
|
|
641
|
+
process.stdout.write(`${JSON.stringify(redactDeep({ ok: fixes === 0, activities: results.map((r) => ({ tutor: r.tutor, name: r.name, file: r.file, issues: r.issues, params: r.activity?.params ?? [], state: r.activity?.state ?? [], gates: r.activity?.gates ?? [], tools: r.activity?.tools.map((t) => t.tool) ?? [] })) }), null, 2)}\n`);
|
|
642
|
+
else {
|
|
643
|
+
for (const r of results) {
|
|
644
|
+
const bad = r.issues.filter((i) => i.level === 'fix').length;
|
|
645
|
+
const a = r.activity;
|
|
646
|
+
process.stdout.write(`${bad ? '✗' : '✓'} ${r.tutor}/${r.name} ${redactHome(r.file)}\n`);
|
|
647
|
+
if (a)
|
|
648
|
+
process.stdout.write(` 家长填的:${a.params.map((p) => `${p.name}(${p.type}${p.optional ? ',可空' : ''}${p.default !== undefined ? `,缺省 ${p.default}` : ''})`).join(' ') || '无'} · 应用算的:${a.state.map((x) => x.name).join(' ') || '无'} · 应用拦的:${a.gates.map((g) => g.rule).join(' ') || '无'} · 工具人:${a.tools.map((t) => t.tool).join(' ') || '无'}\n`);
|
|
649
|
+
for (const i of r.issues)
|
|
650
|
+
process.stdout.write(` ${i.level === 'fix' ? '要改' : '提醒'}${i.line ? ` 第 ${i.line} 行` : ''}:${i.text}\n`);
|
|
651
|
+
}
|
|
652
|
+
process.stdout.write(fixes ? `有 ${fixes} 条要改\n` : '都对得上\n');
|
|
653
|
+
}
|
|
654
|
+
if (fixes)
|
|
655
|
+
process.exitCode = 1;
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
case 'pin': {
|
|
659
|
+
// cotutor pin [off | status] [--pin <数字>]:家长端与工作台的锁(src/app/http/access.ts)。机器级,不要 workspace;没设就全开
|
|
660
|
+
const arg = positionals[0];
|
|
661
|
+
if (arg === 'off')
|
|
662
|
+
await clearPin();
|
|
663
|
+
else if (arg !== 'status') {
|
|
664
|
+
let pin = typeof flags.pin === 'string' ? flags.pin : '';
|
|
665
|
+
if (!pin && arg === undefined && interactive(json)) {
|
|
666
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
667
|
+
try {
|
|
668
|
+
pin = (await rl.question('家长 PIN(4 到 8 位数字;iPad 上开 /parent、/dev 时输一次,留空只看现状):')).trim();
|
|
669
|
+
}
|
|
670
|
+
finally {
|
|
671
|
+
rl.close();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (pin) {
|
|
675
|
+
if (!PIN_RE.test(pin))
|
|
676
|
+
throw new UsageError('PIN 要 4 到 8 位数字:cotutor pin --pin 2468');
|
|
677
|
+
await setPin(pin);
|
|
678
|
+
}
|
|
679
|
+
else if (arg !== undefined)
|
|
680
|
+
throw new UsageError(`不认识 ${arg}:cotutor pin [off | status] [--pin <数字>]`);
|
|
681
|
+
}
|
|
682
|
+
const rec = await readPin();
|
|
683
|
+
if (json)
|
|
684
|
+
process.stdout.write(`${JSON.stringify({ pin: rec !== null, at: rec?.at ?? null, file: redactHome(USER_PIN_FILE) }, null, 2)}\n`);
|
|
685
|
+
else
|
|
686
|
+
process.stdout.write(rec ? `设了 PIN(${rec.at.slice(0, 10)},${redactHome(USER_PIN_FILE)}):iPad 上开 /parent、/dev 要输一次,这台电脑免密;刚改过的话以前登录过的设备要重输。撤掉:cotutor pin off\n` : '没设 PIN:家长端与工作台对局域网全开(家长在旁边、用完收回 iPad 可以这样)。设一个:cotutor pin --pin 2468\n');
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
case 'cert': {
|
|
690
|
+
const r = await makeCert(positionals.concat(typeof flags.host === 'string' ? [flags.host] : []));
|
|
691
|
+
if (json)
|
|
692
|
+
process.stdout.write(`${JSON.stringify(redactDeep(r), null, 2)}\n`);
|
|
693
|
+
else {
|
|
694
|
+
process.stdout.write(`证书:${redactHome(r.cert)}\n私钥:${redactHome(r.key)}\n主机:${r.hosts.join(' ')}\n`);
|
|
695
|
+
// 根证书怎么到设备上:Mac 隔空投送;Windows 等没有,用微信、邮件或网盘传
|
|
696
|
+
const rootCa = redactHome(join(r.caRoot, 'rootCA.pem'));
|
|
697
|
+
const send = process.platform === 'darwin' ? '隔空投送过去' : '用微信、邮件或网盘传到设备上';
|
|
698
|
+
process.stdout.write(`平板、手机要先信任这台机器的根证书 ${rootCa}(每台设备各做一次):\n`);
|
|
699
|
+
process.stdout.write(` iPad / iPhone:${send} → 设置里安装描述文件 → 通用 › 关于本机 › 证书信任设置里**把开关打开**(装了不等于信任)\n`);
|
|
700
|
+
process.stdout.write(` 安卓:${process.platform === 'darwin' ? '传到设备上' : send} → 设置里搜「CA 证书」,按提示安装(各家菜单不一样,会提示「网络可能受到监控」)\n`);
|
|
701
|
+
process.stdout.write(`然后 cotutor service restart(在终端里跑的就重开 cotutor serve),cotutor open 打开家长端,用相机扫「扫码打开」那张。地址认电脑名,换 Wi-Fi、IP 变了都不用重签;改了电脑名才要重跑这条。详见 docs/iPad与iPhone.md\n`);
|
|
702
|
+
}
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
case 'pack': {
|
|
706
|
+
const [tutor, ...rest] = positionals;
|
|
707
|
+
if (!tutor)
|
|
708
|
+
throw new UsageError(`pack 需要老师名,如 cotutor pack math-tutor "讲讲退位"。\n${USAGE}`);
|
|
709
|
+
const ws = loadWorkspace(workspace);
|
|
710
|
+
const { packDryRun } = await import("../../runtime/runner.js");
|
|
711
|
+
const from = typeof flags.from === 'string' ? flags.from : 'kid';
|
|
712
|
+
if (from !== 'kid' && from !== 'system')
|
|
713
|
+
throw new UsageError('--from 只能是 kid / system');
|
|
714
|
+
const at = typeof flags.at === 'string' ? new Date(flags.at) : new Date();
|
|
715
|
+
if (Number.isNaN(at.getTime()))
|
|
716
|
+
throw new UsageError(`--at 不是时间:${flags.at}`);
|
|
717
|
+
const activity = typeof flags.activity === 'string' ? await activityRunFor(ws.root, tutor, flags.activity, typeof flags.param === 'string' ? flags.param : '') : undefined;
|
|
718
|
+
const r = await packDryRun(ws, tutor, { from, at, text: rest.join(' ') || activity?.name || '(干跑)', ...(activity ? { activity } : {}) });
|
|
719
|
+
if (json) {
|
|
720
|
+
process.stdout.write(`${JSON.stringify(redactDeep(r), null, 2)}\n`);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const rel = (f) => redactHome(f.startsWith(ws.root) ? f.slice(ws.root.length + 1) : f);
|
|
724
|
+
const rp = r.report;
|
|
725
|
+
const out = [];
|
|
726
|
+
out.push(`发给 ${tutor} 的上下文包(干跑,${r.prompt.length} 字):`);
|
|
727
|
+
out.push(...r.prompt.split('\n').map((l) => ` │ ${l}`));
|
|
728
|
+
out.push('来源:');
|
|
729
|
+
out.push(` slot ${rel(rp.timetable.file)} · ${rp.timetable.found ? (rp.timetable.slot ? `命中「${rp.timetable.slot}」` : '现在不在任何时段') : '课程表读不到'}`);
|
|
730
|
+
const v = rp.vault;
|
|
731
|
+
const chars = (n, tail = false) => (n > v.limit ? `${n} 字,${tail ? `只带最近 ${v.limit}` : `截到 ${v.limit}`}` : `${n} 字`);
|
|
732
|
+
out.push(` vault ${redactHome(v.root)} · 学期 ${v.semester ?? '算不出'}`);
|
|
733
|
+
out.push(` profile ${v.profile ? `${v.profile}(${chars(v.chars.profile)})` : '没有 cotutor: profile 的笔记'}${v.extraProfiles.length ? ` · 另有 ${v.extraProfiles.join('、')} 没用` : ''}`);
|
|
734
|
+
out.push(` entry ${v.entry ? `${v.entry}(${chars(v.chars.entry)})` : v.subject ? `没有 subject: ${v.subject}、semester: ${v.semester ?? '?'} 的入口文件` : '这位老师没配 subject'}${v.extraEntries.length ? ` · 另有 ${v.extraEntries.join('、')} 没用` : ''}`);
|
|
735
|
+
out.push(` memory ${v.memory ? `${v.memory}(${chars(v.chars.memory, true)})` : '还没有(第一次写「## 记忆」时建)'}${v.extraMemories.length ? ` · 另有 ${v.extraMemories.join('、')} 没用` : ''}`);
|
|
736
|
+
if (v.refs.length)
|
|
737
|
+
out.push(` refs ${v.refs.join('、')}(只给路径)`);
|
|
738
|
+
out.push(` plan ${rel(rp.plan.file)} · ${rp.plan.found ? `这位老师 ${rp.plan.total} 行,带了 ${rp.plan.kept}(上限 ${rp.plan.limit})` : '本周计划不在'}`);
|
|
739
|
+
out.push(` recent ${rel(rp.recent.dir)}/ 最近 ${rp.recent.days} 天 · 有 ${rp.recent.filesFound.length} 天的日记${rp.recent.filesFound.length ? `(${rp.recent.filesFound[0]} … ${rp.recent.filesFound[rp.recent.filesFound.length - 1]})` : ''} · ${rp.recent.subject ? `学科「${rp.recent.subject}」` : '不按学科过滤'}的观察行共 ${rp.recent.total},带了 ${rp.recent.kept}(上限 ${rp.recent.limit},取最新的)`);
|
|
740
|
+
const cut = [v.chars.profile > v.limit ? '档案原文截了' : '', v.chars.entry > v.limit ? '入口文件原文截了' : '', v.chars.memory > v.limit ? '记忆原文截了' : '', rp.plan.total > rp.plan.kept ? `计划截掉 ${rp.plan.total - rp.plan.kept} 行` : '', rp.recent.total > rp.recent.kept ? `观察截掉 ${rp.recent.total - rp.recent.kept} 条` : ''].filter(Boolean);
|
|
741
|
+
out.push(cut.length ? `截掉的:${cut.join(';')}(改 cotutor.json policyDefaults.contextPack 的 entryChars / planLines / recent)` : '没截掉什么。');
|
|
742
|
+
out.push('这份不进任何文件;老师真跑时还会多 cards:(孩子在卡上做的)与 photos: 两段;同一话题续聊时没改过的笔记只写「未变」。');
|
|
743
|
+
process.stdout.write(`${out.join('\n')}\n`);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
case 'show': {
|
|
747
|
+
const [tutor, jobArg, dateArg] = positionals;
|
|
748
|
+
if (!tutor || !jobArg)
|
|
749
|
+
throw new UsageError(`show 需要老师名和 job,如 cotutor show math-tutor 1620-1 2026-09-12(日期缺省今天)。\n${USAGE}`);
|
|
750
|
+
const ws = loadWorkspace(workspace);
|
|
751
|
+
const { rawView } = await import("../http/raw-view.js");
|
|
752
|
+
const { localDate } = await import("../../shared/time.js");
|
|
753
|
+
const date = dateArg ?? localDate(new Date());
|
|
754
|
+
const { evalWorkspace } = await import("../replay.js");
|
|
755
|
+
const v = await rawView(flags.evals === true ? evalWorkspace(ws) : ws, tutor, date, jobArg);
|
|
756
|
+
if (!v)
|
|
757
|
+
throw new UsageError(`${tutor} ${date} 没有 ${jobArg} 这一轮(cotutor show 的日期缺省今天;job 与日期对不对?回放那轮要加 --evals)`);
|
|
758
|
+
if (json) {
|
|
759
|
+
process.stdout.write(`${JSON.stringify(redactDeep(v), null, 2)}\n`);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const secs = (ms) => (ms === undefined ? '?' : `${(ms / 1000).toFixed(1)}s`);
|
|
763
|
+
const out = [];
|
|
764
|
+
out.push(`${v.tutor} ${v.date} ${v.job} · ${v.from} 问 · ${v.result}${v.error ? ` · 出错 ${v.error}` : ''}${v.tokens ? ` · ${tokenText(v.tokens)}` : ''}${v.timing ? ` · 首句就绪 ${secs(v.timing.firstReadyMs)} 老师写完 ${secs(v.timing.doneMs)}` : ''}`);
|
|
765
|
+
out.push(`问:${v.text}`);
|
|
766
|
+
const src = v.pack?.sources;
|
|
767
|
+
out.push(`上下文包:${v.pack ? `${v.pack.prompt.length} 字 · ${v.pack.resume ? 'resume' : '新开'} · ${v.pack.runtime}` : '没落(老轮次)'}${src ? ` · 老师文件 ${src.agent ? `${src.agent.file} ${src.agent.hash.slice(7)}` : '读不到'} · 技能 ${Object.entries(src.skills).map(([k, h]) => `${k} ${h.slice(7, 13)}`).join(' / ') || '无'}` : ''}`);
|
|
768
|
+
if (v.pack)
|
|
769
|
+
out.push(...v.pack.prompt.split('\n').map((l) => ` │ ${l}`));
|
|
770
|
+
out.push(`读了什么:${v.tools.length ? '' : '没用工具'}`);
|
|
771
|
+
for (const t of v.tools)
|
|
772
|
+
out.push(` ${t.ok === false ? '✗' : t.ok === null ? '?' : '·'} ${t.name}${t.sub ? '(子代理)' : ''} ${t.arg}${t.chars ? ` → ${t.chars} 字` : ''}`);
|
|
773
|
+
out.push(`卡 ${v.kid.cards.length}:${v.kid.cards.map((c) => `${v.job}/${c.n} ${c.kind} ${c.label}`.trim()).join(' | ')}`);
|
|
774
|
+
out.push(`讲稿 ${v.kid.lines.length}:`);
|
|
775
|
+
for (const l of v.kid.lines)
|
|
776
|
+
out.push(` ${l.text}${l.cut ? `〔截:${l.cut}〕` : ''}`);
|
|
777
|
+
if (v.stored.parentText)
|
|
778
|
+
out.push(`给家长的尾巴:\n${v.stored.parentText.split('\n').map((l) => ` ${l}`).join('\n')}`);
|
|
779
|
+
if (v.stored.warnings.length)
|
|
780
|
+
out.push(`提醒:${v.stored.warnings.join(';')}`);
|
|
781
|
+
out.push(`文件:conversations/${tutor}/${v.files.log}${v.files.run ? ` · ${v.files.run}` : ''}`);
|
|
782
|
+
process.stdout.write(`${out.join('\n')}\n`);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
case 'replay': {
|
|
786
|
+
const [tutor, jobArg, dateArg] = positionals;
|
|
787
|
+
if (!tutor || !jobArg)
|
|
788
|
+
throw new UsageError(`replay 需要老师名和 job,如 cotutor replay math-tutor 1620-1 2026-09-12(日期缺省今天)。\n${USAGE}`);
|
|
789
|
+
const ws = loadWorkspace(workspace);
|
|
790
|
+
const { compareReplay, formatCompare, startReplay } = await import("../replay.js");
|
|
791
|
+
const { localDate } = await import("../../shared/time.js");
|
|
792
|
+
const date = dateArg ?? localDate(new Date());
|
|
793
|
+
const r = await startReplay(ws, tutor, date, jobArg, { runtime: typeof flags.runtime === 'string' ? flags.runtime : undefined, env: process.env });
|
|
794
|
+
if (!json)
|
|
795
|
+
process.stdout.write(`回放起了:evals/${tutor}/${date}.${r.evalJob}.*(原轮 ${jobArg}),等老师说完……\n`);
|
|
796
|
+
await r.done;
|
|
797
|
+
const c = await compareReplay(ws, tutor, date, r.evalJob);
|
|
798
|
+
if (!c)
|
|
799
|
+
throw new UsageError(`回放跑完了但读不到结果:evals/${tutor}/${date}.json`);
|
|
800
|
+
if (json)
|
|
801
|
+
process.stdout.write(`${JSON.stringify(redactDeep(c), null, 2)}\n`);
|
|
802
|
+
else
|
|
803
|
+
process.stdout.write(formatCompare(c));
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
case 'compare': {
|
|
807
|
+
const [tutor, evalJobArg, dateArg] = positionals;
|
|
808
|
+
if (!tutor)
|
|
809
|
+
throw new UsageError(`compare 需要老师名,如 cotutor compare math-tutor 1712-1 2026-09-12。\n${USAGE}`);
|
|
810
|
+
const ws = loadWorkspace(workspace);
|
|
811
|
+
const { compareReplay, formatCompare, listReplays } = await import("../replay.js");
|
|
812
|
+
const { localDate } = await import("../../shared/time.js");
|
|
813
|
+
const date = dateArg ?? localDate(new Date());
|
|
814
|
+
if (!evalJobArg) {
|
|
815
|
+
const rows = await listReplays(ws, tutor, date);
|
|
816
|
+
if (json)
|
|
817
|
+
process.stdout.write(`${JSON.stringify(rows, null, 2)}\n`);
|
|
818
|
+
else if (!rows.length)
|
|
819
|
+
process.stdout.write(`${tutor} ${date} 没有回放过(cotutor replay ${tutor} <job> ${date})\n`);
|
|
820
|
+
else
|
|
821
|
+
for (const r of rows)
|
|
822
|
+
process.stdout.write(`${r.evalJob} ← ${r.replayOf} ${r.at.slice(11)} ${r.result}${r.tokens ? ` ${tokenText(r.tokens)}` : ''}${r.runtime ? ` ${r.runtime}` : ''}\n`);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const c = await compareReplay(ws, tutor, date, evalJobArg);
|
|
826
|
+
if (!c)
|
|
827
|
+
throw new UsageError(`${tutor} ${date} 没有回放 ${evalJobArg}(cotutor compare ${tutor} ${date} 列这天的)`);
|
|
828
|
+
if (json)
|
|
829
|
+
process.stdout.write(`${JSON.stringify(redactDeep(c), null, 2)}\n`);
|
|
830
|
+
else
|
|
831
|
+
process.stdout.write(formatCompare(c));
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
case 'trace': {
|
|
835
|
+
const [tutor, jobArg, dateArg] = positionals;
|
|
836
|
+
if (!tutor || !jobArg)
|
|
837
|
+
throw new UsageError(`trace 需要老师名和 job,如 cotutor trace math-tutor 1620-1 2026-09-12(日期缺省今天)。\n${USAGE}`);
|
|
838
|
+
const ws = loadWorkspace(workspace);
|
|
839
|
+
const { localDate } = await import("../../shared/time.js");
|
|
840
|
+
const { conversationFiles } = await import("../../schema/index.js");
|
|
841
|
+
const date = dateArg ?? localDate(new Date());
|
|
842
|
+
const file = conversationFiles(ws.dirs.conversations, tutor, date).events(jobArg);
|
|
843
|
+
let text;
|
|
844
|
+
try {
|
|
845
|
+
text = readFileSync(file, 'utf8');
|
|
846
|
+
}
|
|
847
|
+
catch {
|
|
848
|
+
throw new UsageError(`没有这轮的事件文件:${redactHome(file)}(2026-09-13 之前的轮次没有事件;job 与日期对不对?)`);
|
|
849
|
+
}
|
|
850
|
+
const events = parseEvents(text).filter(laneFilter(typeof flags.lane === 'string' ? flags.lane : undefined));
|
|
851
|
+
if (json)
|
|
852
|
+
process.stdout.write(`${JSON.stringify(events, null, 2)}\n`);
|
|
853
|
+
else
|
|
854
|
+
for (const e of events)
|
|
855
|
+
process.stdout.write(`${formatEvent(e)}\n`);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
case 'rate': {
|
|
859
|
+
const [tutor, thread, score] = positionals;
|
|
860
|
+
const n = Number(score);
|
|
861
|
+
if (!tutor || !thread || !(n >= 1 && n <= 5 && Number.isInteger(n)))
|
|
862
|
+
throw new UsageError(`rate 需要老师名、话题 id 和 1–5 的星,如 cotutor rate math-tutor 1620-1 4。\n${USAGE}`);
|
|
863
|
+
const ws = loadWorkspace(workspace);
|
|
864
|
+
const date = typeof flags.date === 'string' ? flags.date : localDate(new Date());
|
|
865
|
+
const index = await rateThread(ws, tutor, date, thread, n);
|
|
866
|
+
if (json)
|
|
867
|
+
process.stdout.write(`${JSON.stringify({ tutor, date, thread, rating: index.ratings[thread] })}\n`);
|
|
868
|
+
else
|
|
869
|
+
process.stdout.write(`${tutor} ${date} 话题 ${thread}:${'★'.repeat(n)}${n >= ws.config.vault.keepScore ? '(记账时摘要进日记)' : `(不到 ${ws.config.vault.keepScore} 星,记账只记孩子问的话与观察)`}\n`);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
case 'bookkeep': {
|
|
873
|
+
const [tutor] = positionals;
|
|
874
|
+
if (!tutor)
|
|
875
|
+
throw new UsageError(`bookkeep 需要老师名,如 cotutor bookkeep math-tutor。\n${USAGE}`);
|
|
876
|
+
const ctx = createContext(loadWorkspace(workspace));
|
|
877
|
+
const date = typeof flags.date === 'string' ? flags.date : localDate(new Date());
|
|
878
|
+
const only = typeof flags.thread === 'string' ? [flags.thread] : undefined;
|
|
879
|
+
if (!json && flags.quiet !== true)
|
|
880
|
+
ctx.runner.onEvent((e) => { if (e.tutor === tutor && (e.event.lane === 'ledger' || e.event.lane === 'main' || e.event.lane === 'index'))
|
|
881
|
+
process.stdout.write(`${formatEvent(e.event)}\n`); });
|
|
882
|
+
const r = await ctx.runner.bookkeep(tutor, date, only);
|
|
883
|
+
if (!json) {
|
|
884
|
+
for (const sk of r.skipped)
|
|
885
|
+
process.stdout.write(`话题 ${sk.thread} 跳过:${sk.why}\n`);
|
|
886
|
+
if (!r.queued.length)
|
|
887
|
+
process.stdout.write('没有要记的话题\n');
|
|
888
|
+
else
|
|
889
|
+
process.stdout.write(`记账 ${r.queued.length} 个话题:${r.queued.join('、')}…\n`);
|
|
890
|
+
}
|
|
891
|
+
await ctx.runner.flush();
|
|
892
|
+
const { readIndex } = await import("../../runtime/journal/store.js");
|
|
893
|
+
const index = await readIndex(ctx.ws, tutor, date);
|
|
894
|
+
if (json)
|
|
895
|
+
process.stdout.write(`${JSON.stringify({ tutor, date, queued: r.queued, skipped: r.skipped, booked: index.booked }, null, 2)}\n`);
|
|
896
|
+
else
|
|
897
|
+
for (const th of r.queued) {
|
|
898
|
+
const m = index.messages.find((x) => x.bookkeep?.thread === th && x.result !== 'running');
|
|
899
|
+
process.stdout.write(`话题 ${th}:${index.booked[th] ? '记进日记了' : `没记成${m?.warnings?.length ? ' — ' + m.warnings.join(';') : m?.error ? ' — ' + m.error : ''}`}\n`);
|
|
900
|
+
}
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
case 'home': {
|
|
904
|
+
const sub = positionals[0];
|
|
905
|
+
if (sub !== 'check' && sub !== 'publish' && sub !== 'show')
|
|
906
|
+
throw new UsageError(`home 后面跟 check / publish / show,如 cotutor home check。\n${USAGE}`);
|
|
907
|
+
const ws = loadWorkspace(workspace);
|
|
908
|
+
const now = new Date();
|
|
909
|
+
const home = await import("../../runtime/home/store.js");
|
|
910
|
+
if (sub === 'show') {
|
|
911
|
+
const s = await home.homeStats(ws, now);
|
|
912
|
+
process.stdout.write(json ? `${JSON.stringify(s, null, 2)}\n` : `${home.formatStats(ws, s)}\n`);
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
if (sub === 'check') {
|
|
916
|
+
const published = flags.published === true;
|
|
917
|
+
let md;
|
|
918
|
+
if (published) {
|
|
919
|
+
const { home: pub, error } = await home.readPublished(ws);
|
|
920
|
+
if (!pub)
|
|
921
|
+
throw new UsageError(error ?? '还没发布过首页');
|
|
922
|
+
md = await home.publishedSource(ws, pub);
|
|
923
|
+
if (md === null)
|
|
924
|
+
throw new UsageError(`已发布那份的原文 ${pub.source} 不在了`);
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
md = await home.readDraft(ws);
|
|
928
|
+
if (md === null)
|
|
929
|
+
throw new UsageError('还没有草稿 home/draft.md(用 cotutor-home 技能写一份)');
|
|
930
|
+
}
|
|
931
|
+
const check = await home.checkHome(ws, md, now);
|
|
932
|
+
const kid = await home.kidHomeView(ws, now, { source: published ? 'published' : 'draft' });
|
|
933
|
+
if (json)
|
|
934
|
+
process.stdout.write(`${JSON.stringify({ ok: check.fixes === 0, for: check.doc.for ?? null, issues: check.issues, cards: check.doc.cards, kid: kid.cards, note: check.doc.note }, null, 2)}\n`);
|
|
935
|
+
else
|
|
936
|
+
process.stdout.write(`${home.formatCheck(ws, check, published ? '已发布的那份' : '草稿 home/draft.md', kid)}\n`);
|
|
937
|
+
if (check.fixes)
|
|
938
|
+
process.exitCode = 1;
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const r = await home.publishHome(ws, { force: flags.force === true, from: typeof flags.from === 'string' ? flags.from : undefined, now });
|
|
942
|
+
if (json)
|
|
943
|
+
process.stdout.write(`${JSON.stringify({ ok: r.ok, id: r.home?.id ?? null, source: r.source, issues: r.check.issues, dropped: r.dropped }, null, 2)}\n`);
|
|
944
|
+
else if (!r.ok) {
|
|
945
|
+
const kid = await home.kidHomeView(ws, now, { source: 'draft' });
|
|
946
|
+
process.stdout.write(`${home.formatCheck(ws, r.check, r.source, kid)}\n没发布:有 ${r.check.fixes} 条要改;改好再发,或 --force 丢掉那几张照发\n`);
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
const h = r.home;
|
|
950
|
+
process.stdout.write(`发布了 ${h.id}(${r.source} → ${h.source},home/published.json)· ${h.cards.length} 张卡${h.for ? ` · for ${h.for}` : ''}\n`);
|
|
951
|
+
for (const d of r.dropped)
|
|
952
|
+
process.stdout.write(` 丢掉:${d}\n`);
|
|
953
|
+
for (const w of h.warnings)
|
|
954
|
+
process.stdout.write(` 提醒:${w}\n`);
|
|
955
|
+
process.stdout.write('孩子端刷新就是新首页(不用重起服务)\n');
|
|
956
|
+
}
|
|
957
|
+
if (!r.ok)
|
|
958
|
+
process.exitCode = 1;
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
case 'send': {
|
|
962
|
+
const [tutor, ...words] = positionals;
|
|
963
|
+
const photoFiles = typeof flags.photo === 'string' ? flags.photo.split(',').filter(Boolean) : [];
|
|
964
|
+
// --activity:像孩子点了首页的 activity 按钮,新开话题;没写消息就发 activity 的名字(按钮上的字)
|
|
965
|
+
const activityName = typeof flags.activity === 'string' ? flags.activity : undefined;
|
|
966
|
+
const text = words.join(' ') || activityName || '';
|
|
967
|
+
if (!tutor || (!text && !photoFiles.length))
|
|
968
|
+
throw new UsageError(`send 需要老师名和消息,如 cotutor send math-tutor "这一步为什么要借位"。\n${USAGE}`);
|
|
969
|
+
const from = typeof flags.from === 'string' ? flags.from : 'kid';
|
|
970
|
+
if (!MESSAGE_FROM.includes(from))
|
|
971
|
+
throw new UsageError(`--from 只能是 ${MESSAGE_FROM.join(' / ')}`);
|
|
972
|
+
const ws = loadWorkspace(workspace);
|
|
973
|
+
// --photo:和孩子端拍照同一条路,先拷进 captures/ 再随消息发;孩子端会先缩到长边 2000 的 jpeg,这里不缩,太大就拒
|
|
974
|
+
const photos = [];
|
|
975
|
+
if (photoFiles.length > 9)
|
|
976
|
+
throw new UsageError('--photo 一次最多 9 张');
|
|
977
|
+
for (const f of photoFiles) {
|
|
978
|
+
const ext = /\.(jpe?g|png)$/i.exec(f)?.[1]?.toLowerCase();
|
|
979
|
+
if (!ext)
|
|
980
|
+
throw new UsageError(`--photo 只认 jpg / png:${f}`);
|
|
981
|
+
const data = await readFile(f).catch(() => null);
|
|
982
|
+
if (!data)
|
|
983
|
+
throw new UsageError(`--photo 读不到:${f}`);
|
|
984
|
+
if (data.length > PHOTO_MAX_BYTES)
|
|
985
|
+
throw new UsageError(`--photo ${f} 太大(${(data.length / 1e6).toFixed(1)} MB,最多 ${PHOTO_MAX_BYTES / 1e6} MB);先缩,如 sips -s format jpeg -Z 2000 ${f} --out x.jpg`);
|
|
986
|
+
photos.push(await writeCapture(ws, new Date(), data, ext === 'png' ? 'png' : 'jpg'));
|
|
987
|
+
}
|
|
988
|
+
const ctx = createContext(ws);
|
|
989
|
+
// 现场打印每道工序的事件(--quiet / --json 不打);事件同时落在 events.jsonl,事后 cotutor trace 能回放
|
|
990
|
+
if (!json && flags.quiet !== true) {
|
|
991
|
+
const keep = laneFilter(typeof flags.lane === 'string' ? flags.lane : undefined);
|
|
992
|
+
ctx.runner.onEvent((e) => { if (e.tutor === tutor && keep(e.event))
|
|
993
|
+
process.stdout.write(`${formatEvent(e.event)}\n`); });
|
|
994
|
+
}
|
|
995
|
+
const activity = activityName ? await activityRunFor(ws.root, tutor, activityName, typeof flags.param === 'string' ? flags.param : '') : undefined;
|
|
996
|
+
// 孩子的话照实时写(同孩子端:上下文包带 live:、不补提问卡、讲稿不截;嘴不在,接住与打断不带);--from system 不算
|
|
997
|
+
const started = await ctx.runner.send(tutor, { from: from, text, runtime: typeof flags.runtime === 'string' ? flags.runtime : undefined, newThread: flags.new === true || Boolean(activity), ...(photos.length ? { photos } : {}), ...(activity ? { activity } : {}), ...(from === 'kid' ? { live: {} } : {}) });
|
|
998
|
+
if (!json)
|
|
999
|
+
process.stdout.write(`→ ${tutor} ${started.date} ${started.job} 话题 ${started.thread}(${started.plan.runtime}${started.plan.resume ? ',resume ' + started.plan.session : ',新会话'})…\n`);
|
|
1000
|
+
const index = await started.done;
|
|
1001
|
+
const m = index.messages.find((x) => x.job === started.job);
|
|
1002
|
+
if (json)
|
|
1003
|
+
process.stdout.write(`${JSON.stringify({ tutor, date: started.date, job: started.job, runtime: started.plan.runtime, resume: started.plan.resume, message: m, session: index.session, tokens: sumTokens(index.messages) }, null, 2)}\n`);
|
|
1004
|
+
else if (!m || m.result !== 'ok') {
|
|
1005
|
+
process.stdout.write(`本轮出错:${m?.error ?? '未知'};看 conversations/${tutor}/${started.date}.${started.job}.err.log\n`);
|
|
1006
|
+
process.exitCode = 1;
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
process.stdout.write(`孩子看到:${m.kidText ?? '(没有给孩子的话)'}\n`);
|
|
1010
|
+
const secs = (ms) => (ms < 120000 ? `${Math.round(ms / 100) / 10}s` : `${Math.round(ms / 6000) / 10}min`);
|
|
1011
|
+
const timing = [m.timing?.firstReadyMs !== undefined ? `首句就绪 ${secs(m.timing.firstReadyMs)}` : null, m.timing?.firstCardMs !== undefined ? `首卡 ${secs(m.timing.firstCardMs)}` : null, m.timing?.doneMs !== undefined ? `整轮 ${secs(m.timing.doneMs)}` : null].filter(Boolean);
|
|
1012
|
+
if (m.artifacts.length)
|
|
1013
|
+
process.stdout.write(`课包:${m.artifacts.join(', ')}\n`);
|
|
1014
|
+
process.stdout.write(`会话 ${index.session?.id ?? '?'}${timing.length ? ' · ' + timing.join(' · ') : ''}${m.tokens ? ` · 本轮 ${tokenText(m.tokens)}` : ''}${sumTokens(index.messages) ? ` · 今日 ${tokenText(sumTokens(index.messages))}` : ''}\n`);
|
|
1015
|
+
}
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
case 'chalk': {
|
|
1019
|
+
const { chalkCommand } = await import("./chalk/main.js");
|
|
1020
|
+
await chalkCommand(positionals, flags, { workspace, json });
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
default:
|
|
1024
|
+
throw new UsageError(`未知命令 '${cmd}'。\n${USAGE}`);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
catch (err) {
|
|
1028
|
+
if (err instanceof UsageError) {
|
|
1029
|
+
if (json)
|
|
1030
|
+
process.stdout.write(`${JSON.stringify({ ok: false, error: 'usage', message: err.message })}\n`);
|
|
1031
|
+
else
|
|
1032
|
+
process.stderr.write(`cotutor: ${err.message}\n`);
|
|
1033
|
+
process.exitCode = 2;
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
throw err;
|
|
1037
|
+
}
|
|
1038
|
+
}
|