cotutor 0.4.2 → 0.4.4
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/CHANGELOG.md +39 -0
- package/README.md +44 -2
- package/dist/app/cli/backup.d.ts +68 -0
- package/dist/app/cli/backup.js +243 -0
- package/dist/app/cli/doctor-fix.d.ts +18 -0
- package/dist/app/cli/doctor-fix.js +70 -0
- package/dist/app/cli/doctor.js +8 -0
- package/dist/app/cli/init.d.ts +2 -0
- package/dist/app/cli/init.js +6 -3
- package/dist/app/cli/main.js +103 -0
- package/dist/app/cli/report.d.ts +11 -0
- package/dist/app/cli/report.js +110 -0
- package/dist/app/cli/root-ca.d.ts +8 -0
- package/dist/app/cli/root-ca.js +33 -0
- package/dist/app/cli/serve.d.ts +11 -2
- package/dist/app/cli/serve.js +58 -3
- package/dist/app/cli/skeleton.d.ts +1 -1
- package/dist/app/cli/skeleton.js +2 -2
- package/dist/app/cli/usage.d.ts +1 -1
- package/dist/app/cli/usage.js +3 -1
- package/dist/app/http/access.js +1 -1
- package/dist/app/http/app.js +4 -0
- package/dist/app/http/parent-home.js +9 -0
- package/dist/app/http/parent-setup.d.ts +19 -0
- package/dist/app/http/parent-setup.js +96 -0
- package/dist/app/http/stage.js +2 -2
- package/dist/app/mock/mock.js +3 -0
- package/dist/memory/vault-notes.d.ts +4 -0
- package/dist/memory/vault-notes.js +11 -0
- package/dist/shared/upgrade.d.ts +2 -0
- package/dist/shared/upgrade.js +5 -1
- package/dist/stage/fonts/Assistant/Assistant-Bold.woff2 +0 -0
- package/dist/stage/fonts/Assistant/Assistant-Medium.woff2 +0 -0
- package/dist/stage/fonts/Assistant/Assistant-Regular.woff2 +0 -0
- package/dist/stage/fonts/Assistant/Assistant-SemiBold.woff2 +0 -0
- package/dist/stage/fonts/Cascadia/CascadiaCode-Regular.woff2 +0 -0
- package/dist/stage/fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2 +0 -0
- package/dist/stage/fonts/ComicShanns/ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.woff2 +0 -0
- package/dist/stage/fonts/ComicShanns/ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.woff2 +0 -0
- package/dist/stage/fonts/ComicShanns/ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.woff2 +0 -0
- package/dist/stage/fonts/Excalifont/Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.woff2 +0 -0
- package/dist/stage/fonts/Excalifont/Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.woff2 +0 -0
- package/dist/stage/fonts/Excalifont/Excalifont-Regular-41b173a47b57366892116a575a43e2b6.woff2 +0 -0
- package/dist/stage/fonts/Excalifont/Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.woff2 +0 -0
- package/dist/stage/fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2 +0 -0
- package/dist/stage/fonts/Excalifont/Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.woff2 +0 -0
- package/dist/stage/fonts/Excalifont/Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.woff2 +0 -0
- package/dist/stage/fonts/Liberation/LiberationSans-Regular.woff2 +0 -0
- package/dist/stage/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 +0 -0
- package/dist/stage/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 +0 -0
- package/dist/stage/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2 +0 -0
- package/dist/stage/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2 +0 -0
- package/dist/stage/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2 +0 -0
- package/dist/stage/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2 +0 -0
- package/dist/stage/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2 +0 -0
- package/dist/stage/fonts/Virgil/Virgil-Regular.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-019d66dcad46dc156b162d267f981c20.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-04b718e5623574919c8b0dea5f301444.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-069e77aac84590e2e991d0a0176d34f2.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-06c77b8c66e51ed6c63ccb502dd8b8af.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-08e0dc436ad0ad61ba5558db0674d762.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-093b9ef39a46ceae95a1df18a0a3a326.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-095c169f3314805276f603a362766abd.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-09850c4077f3fffe707905872e0e2460.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-0986d134c05864f5025962eef9f994a0.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-0b5d723fdc4e249c140f0909e87d03b4.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-0f626226ba1272e832aea87bafd9720e.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-0f7fb1e0d5015bb1371343153ecf7ce3.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-0fa55a080fcd0f9dc2e0b0058b793df8.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-0facdf1ea213ba40261022f5d5ed4493.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-10a7ae9a371830a80c3d844acf1c02d7.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-112c051027b2d766c19a519f6ee1f4f7.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-11c345711937f0ba4b8f7b6b919c8440.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-12b52b58eb3df36804b9a654ec9ee194.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-13ae07ed2e272d26d59bc0691cd7117a.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-13d2887ec8ee73c43acdabc52a05af7b.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-145aa02cdd91946e67dc934e1acffe75.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-15dc6d811c9cd078f9086a740d5a1038.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-173945821411c09f70c95f98d590e697.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-1b611157cd46bb184d4fa4dae2d6a2b8.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-1e6fd68f1f3902ce48ce8c69df385622.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-1ee544f0f1dac422545c505baa788992.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-1fdc0c67ed57263a80fd108c1f6ccf24.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-203b0e569e3b14aac86a003dc3fa523e.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-20cc1bbf50e7efb442756cb605672c1f.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-20e7bf72fa05de9adf7dbcc7bf51dde6.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-21430ee05a1248901da8d0de08744d47.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-23686f7f29da6e8008c36dd3a80c83d6.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-23ad2d71b280f00b1363b95b7bea94eb.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-23f228f3999c01983860012330e4be08.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-24476a126f129212beb33f66853ea151.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-24a21c1e4449222e8d1898d69ff3a404.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-25b7f38e18f035f96cb5e547bd2bd08c.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-29cec36cd205b211da97acabaa62f055.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2a26d20a23b00898ce82f09d2ee47c3f.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2adbc89c11e65905393d3dfc468b9d5b.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2b7441d46298788ac94e610ffcc709b6.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2b77e8ebfb2367ab2662396a60e7d320.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2cf96d082d35ea3d8106851223ad0d16.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2d43040e86ff03ba677f6f9c04cd0805.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-2e33e8dc771ef5e1d9127d60a6b73679.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-33432927cd87d40cfe393c7482bf221f.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-353f33792a8f60dc69323ddf635a269e.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-36925dfe329a45086cbb7fc5c20d45ac.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3717077e38f98d89eae729b6c14e56dc.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3756e81d3e149cf6099163ee79944fec.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-395c35dd584b56b0789f58a0559beaf1.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3c9de2ae0ea4bc91a510942dfa4be8d2.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3cc70dbb64df5b21f1326cc24dee2195.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3e1f8f654357353bf0e04ba5c34b5f7f.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3e63ed8162808a9e425ed80a8bc79114.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-3eaa538115d76932653c21d8dc28f207.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4095eb84ef3874e2600247bee0b04026.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-41521fade99856108931b4768b1b2648.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-450da755d5bcb70906e1295e559b9602.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-452225341522a7942f0f6aab1a5c91a3.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4535823663ad81405188a528d8f2b1a2.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4806e761d750087c2d734fc64596eaff.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4a0fdb40036e87b40aa08dd30584cb85.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4a38cc3e9cf104e69ba246d37f8cf135.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4aca6a43e59aceee2166b0c7e4e85ef1.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4bfaa8ffa64c5ee560aa2daba7c9cbd3.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4dc6d5f188d5c96d44815cd1e81aa885.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4ddc14ed3eb0c3e46364317dfc0144a3.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4ee10ae43505e2e0bc62656ced49c0fa.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-4f50e5136e136527280bc902c5817561.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-51502f1206be09c565f1547c406e9558.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-51a0e808bbc8361236ac521a119758a3.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-52a84a22fd1369bffeaf21da2d6158dc.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5330a2119a716e4e7224ed108b085dac.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-543fa46ace099a7099dad69123399400.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-544fc28abe2c5c30e62383fd4dac255f.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-54acdfc2166ad7fcbd074f75fd4a56ba.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5572b3513ba8df57a3d5d7303ee6b11b.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-56467a5c8840c4d23a60b2f935114848.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-56a32a7689abd0326e57c10c6c069bb4.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-57862b464a55b18c7bf234ce22907d73.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-583d166e56ba0de4b77eabb47ef67839.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5882ffa04f32584d26109137e2da4352.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-58fd02350d0bc52cf1ca3bb32ce9766e.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5935a5775af3d5c6307ac667bd9ae74e.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-59e9ff77b0efaf684bc09274fb6908c9.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5a1ce3117cfe90c48e8fb4a9a00f694d.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5a45d991244d4c7140217e1e5f5ca4f4.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5a7fac4b8b23a6e4e5ba0c9bf1756c91.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5b0ed6971aaab9c8ad563230bd5471a7.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-5d2898fbc097a7e24c6f38d80587621e.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-603aefd23e350ba7eb124273e3c9bcf1.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-60a3089806700d379f11827ee9843b6b.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-60a41c7e1c68f22424e6d22df544bc82.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-642b26e2e5f5fb780b51b593dbc8c851.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-66493ba5a8367f2928812f446f47b56a.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-670ba603758d94268e8606f240a42e12.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-671a2c20b1eb9e4ef8a192833940e319.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-684d65f1793cac449dde5d59cb3c47fb.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-69c09cc5fa3e55c74fc4821f76909cc3.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-6ae5b42180ad70b971c91e7eefb8eba2.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-6e092f71c1e634059ada0e52abadce67.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-6f3256af8454371776bc46670d33cc65.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-6fe5c5973cc06f74b2387a631ea36b88.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-70c2eb8d64e71a42a834eb857ea9df51.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-70e811fd7994e61f408c923de6ddd078.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7197d6fda6cba7c3874c53d6381ca239.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-72252d73220fa3cd856677888cee1635.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-72536a3d71b694a0d53dd90ddceae41e.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-726303e0774b4e678bff8c2deb6ca603.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-733171b4ffcd17ea1fe1c0ba627173bf.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-739bc1a567439c7cffcd1614644593d2.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-73e309718fd16cea44b4d54a33581811.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7494dc504ae00ee9cd0505f990f88c5d.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-74e2263a91439c25b91d5132ce9f4d62.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-761d05e3cd968cf574166867998ef06a.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7718fe60986d8b42b1be9c5ace5ccf25.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-774d4f764a1299da5d28ec2f2ffe0d69.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-79d494361ae093b69e74ee9dbe65bfd4.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-79f007c1c6d07557120982951ea67998.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7a07ddc0f0c0f5f4a9bad6ee3dda66b5.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7ab2bed91166a9dca83a5ebfbe2a7f38.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7ccce86603f80a099ddb0cb21d4ae3e3.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7e4bde7e9c7f84cd34d8a845e384c746.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7e929f262f30c8ee78bf398150b1a7cd.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7eb9fffd1aa890d07d0f88cc82e6cfe4.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-7f855356ab893b0d2b9c1c83b8116f0e.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-829615148e6357d826b9242eb7fbbd1e.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-866fa7613df6b3fd272bcfd4530c0bb9.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-87599f94b6cc129d505b375798d0d751.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-8c2f33cee3993174f7e87c28e4bf42ee.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-8d3bcabb847b56243b16afe62adaaf21.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-8de5b863cb50dfefdd07cb11c774d579.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-8e9f97f01034820170065b2921b4fb5e.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-8f476c4c99813d57cbe6eca4727388ad.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-903bb6865f3452e2fda42e3a25547bc5.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-91ddb2969bf2d31ba02ad82998d1314c.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-938d90c10ff8c20386af7f242c05d6b0.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-93fc8f28a33234bcadf1527cafabd502.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-95429962233afd82db1c27df1500a28c.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-9544732d2e62d1a429674f8ee41b5d3a.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-9592bfc861f07bcb8d75c196b370e548.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-95bfd249da4902577b4b7d76ebdd0b44.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-968cffdc8ee679da094e77ebf50f58ef.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-97f7f48ce90c9429bf32ae51469db74d.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-982b630266d87db93d2539affb1275c6.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-98f2ad84457de7f3740d9920b8fa8667.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-99a16ef6a64934d5781933dbd9c46b2e.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-9cfb2a77a4e45025105ad29a1748b90d.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-9d81066dd2b337c938df6e90380a00dc.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-9eb5a99df4e76ac3363453ac9ca288b1.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a004ddfcb26e67bd6e678c8ed19e25ce.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a0ca5df4258213d7fc9fce80f65ce760.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a1f916d6039285c4ffb900cd654e418f.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a203b91dad570bf05a58c3c3ddb529bf.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a365e82ed54697a52f27adcea1315fe8.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a4c34be6d42152e64b0df90bc4607f64.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-a7accba310e821da5505f71c03b76bdb.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-aa0d470430e6391eca720c7cfa44446f.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-aa5c9ca6cf4fba00433b7aa3fa10671a.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-ac9ceb44437becc3e9c4dbfebab7fc2d.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b1220a3c61f85cc0408deedb4c5f57a2.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b358f7a51ece39a3247942b1feabdb29.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b57aaedfd8ebdf3931f25119dc6a5eb2.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b5c1596551c256e0e9cf02028595b092.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b6d128682ee29e471486354d486a1b90.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b6fd38ca30869792244804b04bc058da.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b7d203b051eff504ff59ddca7576b6a9.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-b96d9226ce77ec94ceca043d712182e6.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-ba3de316d63c7e339987b16f41a0b879.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-bafff7a14c27403dcc6cf1432e8ea836.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-bd77e3c7f9e0b072d96af37f73d1aa32.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-be549ab72f0719d606a5c01e2c0219b6.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c16ed9740b85badf16e86ea782a3062f.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c1f94158256bb1f3bf665b053d895af9.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c40533fdf4cc57177b12803598af7e59.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c4a687ac4f0c2766eefc9f77ed99cddf.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c69f61a4ab18d0488c8d1fc12e7028e8.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c8b71798409ccc126ee264a00aadcf21.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-c99eda15fc26a2941579560f76c3a5cf.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cb17fc3db95f6d139afc9d31a8e93293.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cbaaefaaf326668277aa24dfa93c4d28.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cd145ce4a0ea18469358df53c207bc1b.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cdbce89e82cc1ab53a2decbf5819278f.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-ce4884f96f11589608b76b726a755803.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cf2cc71752631e579e35b0e423bf2638.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cf6ff4e0f491ca0cf3038187a997b9b4.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-cfb211578629b7e8153b37240de6a9d5.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-d0cf73942fea1c74edbdf0b3011f4656.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-d2666cbed13462c5dc36fa2f15c202ca.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-d3716376641d615e2995605b29bca7b6.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-dac48066b5883d8b4551fc584f0c2a3e.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-dbea1af6dcd9860be40c3d18254338f5.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e11567fd2accf9957cd0d3c2be937d87.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e2ead7ea7da0437f085f42ffc05f8d13.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e3fcf5180fd466c8915c4e8069491054.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e480d9c614742d05f0e78f274f1e69e6.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e4bca6cfa53e499cae0a6be4894a90e9.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e51ef413167c6e14e0c0fdcc585f2fc9.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e5f453bb04da18eed01675eeebd88bf8.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-e656f091b9dc4709722c9f4b84d3c797.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-ec181b795ac1fb5a50d700b6e996d745.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-ee8bae97908d5147b423f77ad0d3c1bb.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-f0f13b5c60e0af5553bd359f5513be1b.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-f2b54d4e7be0eaefe1c2c56836fa5368.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-f56414bf9bced67990def8660e306759.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-f5d079153c99a25b9be5b8583c4cc8a7.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-f6032fc06eb20480f096199713f70885.woff2 +0 -0
- package/dist/stage/fonts/Xiaolai/Xiaolai-Regular-f8ee5d36068a42b51d0e4a1116cfcec1.woff2 +0 -0
- package/dist/ui/kid/kid-page.js +2 -0
- package/dist/ui/kid/offline-sw.d.ts +1 -0
- package/dist/ui/kid/offline-sw.js +42 -0
- package/dist/ui/parent/parent-home-page.js +49 -5
- package/dist/ui/parent/parent-settings-page.js +65 -1
- package/install.ps1 +66 -0
- package/install.sh +84 -0
- package/package.json +13 -10
- package/skills/cotutor-analyze/references//345/221/275/344/273/244/344/270/216/346/226/207/344/273/266.md +1 -1
- package/skills/cotutor-tune/references//345/255/227/346/256/265.md +1 -1
package/dist/app/cli/main.js
CHANGED
|
@@ -85,6 +85,24 @@ function printOpen(label, page, hint, open) {
|
|
|
85
85
|
openTarget(process.platform, page);
|
|
86
86
|
}
|
|
87
87
|
const wantsOpen = (flags) => flags.open === true || flags['open-qr'] === true;
|
|
88
|
+
/** 升级前的备份;试用的不备 */
|
|
89
|
+
async function backupBeforeUpgrade(root) {
|
|
90
|
+
const { describeBackup, makeBackup } = await import("./backup.js");
|
|
91
|
+
let vault;
|
|
92
|
+
try {
|
|
93
|
+
const ws = loadWorkspace(root);
|
|
94
|
+
if (ws.sandbox)
|
|
95
|
+
return null;
|
|
96
|
+
vault = ws.paths.vault;
|
|
97
|
+
}
|
|
98
|
+
catch { /* 配置读不了(老布局、写坏了):workspace 照样备,vault 不管 */ }
|
|
99
|
+
try {
|
|
100
|
+
return `升级前留了一份备份:${describeBackup(await makeBackup({ root, vault }, { why: 'upgrade', version: BUILD.version }))}`;
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
return `! 这次没备份成:${err instanceof Error ? err.message : String(err)}`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
88
106
|
/** 不用 workspace 的命令:--sandbox 对它们没有意义 */
|
|
89
107
|
const NO_WORKSPACE = ['grades', 'init', 'mock', 'pin', 'cert', 'tray', 'update'];
|
|
90
108
|
export async function main(argv) {
|
|
@@ -146,7 +164,86 @@ export async function main(argv) {
|
|
|
146
164
|
}
|
|
147
165
|
return;
|
|
148
166
|
}
|
|
167
|
+
case 'report': {
|
|
168
|
+
const { buildReport } = await import("./report.js");
|
|
169
|
+
const ws = loadWorkspace(workspace);
|
|
170
|
+
process.stdout.write(await buildReport(ws, { health: await probeHealth(ws.config.server.port) }));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
case 'backup': {
|
|
174
|
+
const backup = await import("./backup.js");
|
|
175
|
+
const ws = loadWorkspace(workspace);
|
|
176
|
+
if (ws.sandbox)
|
|
177
|
+
throw new UsageError('试用的 workspace 不备份');
|
|
178
|
+
const target = { root: ws.root, vault: ws.paths.vault };
|
|
179
|
+
const [sub, name] = positionals;
|
|
180
|
+
if (!sub) {
|
|
181
|
+
const b = await backup.makeBackup(target, { why: 'manual', version: BUILD.version });
|
|
182
|
+
process.stdout.write(`备份好了:${backup.describeBackup(b)}\n 在 ${redactHome(backup.backupsOf(ws.root))}\n 不含录音(wav)和能重新生成的文件${b.audioSkipped ? `,这次跳过 ${b.audioSkipped} 个录音` : ''}\n`);
|
|
183
|
+
if (b.vault === 'unreadable')
|
|
184
|
+
process.stdout.write(`! vault 读不了(${redactHome(ws.paths.vault)}),这份里没有它\n`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (sub === 'list') {
|
|
188
|
+
const list = await backup.listBackups(ws.root);
|
|
189
|
+
if (json) {
|
|
190
|
+
process.stdout.write(`${JSON.stringify(list, null, 2)}\n`);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!list.length)
|
|
194
|
+
process.stdout.write('还没有备份:cotutor backup 备一份\n');
|
|
195
|
+
for (const b of list)
|
|
196
|
+
process.stdout.write(`${backup.describeBackup(b)}\n`);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (sub !== 'restore' || !name)
|
|
200
|
+
throw new UsageError(`cotutor backup [list | restore <名字> [--yes]]\n${USAGE}`);
|
|
201
|
+
if (!(await backup.listBackups(ws.root)).some((b) => b.name === name))
|
|
202
|
+
throw new UsageError(`没有这份备份:${name}(cotutor backup list 看有哪些)`);
|
|
203
|
+
if (flags.yes !== true) {
|
|
204
|
+
process.stdout.write(`会把孩子的文件夹恢复成 ${name} 那时的样子:备份里有的文件都覆盖回去,之后新多出来的不删。\n恢复前会先把现在的样子也备份一份,恢复错了还能回来。服务会停一下。录音不在备份里,不受影响。\n确定的话加 --yes\n`);
|
|
205
|
+
process.exitCode = 1;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const host = realHost();
|
|
209
|
+
const port = ws.config.server.port;
|
|
210
|
+
const health = await probeHealth(port);
|
|
211
|
+
if (health && health.mode !== 'service')
|
|
212
|
+
throw new UsageError(`端口 ${port} 上有一个 cotutor serve 在终端里跑着:先关掉它(那个终端里 Ctrl-C),再恢复`);
|
|
213
|
+
if ((health?.calls ?? 0) > 0)
|
|
214
|
+
throw new UsageError('孩子正在和老师通话,恢复要停服务:等下课再来');
|
|
215
|
+
const service = await findService(host, ws.root);
|
|
216
|
+
if (service && health)
|
|
217
|
+
await controlService(host, ws.root, 'stop');
|
|
218
|
+
const r = await backup.restoreBackup(target, name, { version: BUILD.version });
|
|
219
|
+
process.stdout.write(`恢复好了:${r.files} 个文件。恢复前的样子留在 ${r.safety}\n`);
|
|
220
|
+
if (r.vaultCopy)
|
|
221
|
+
process.stdout.write(`外面的 vault 没动;要拿回来,那一份的 md 在 ${redactHome(r.vaultCopy)}\n`);
|
|
222
|
+
if (service && health) {
|
|
223
|
+
await controlService(host, ws.root, 'start');
|
|
224
|
+
process.stdout.write('服务重起了\n');
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
149
228
|
case 'doctor': {
|
|
229
|
+
if (flags.fix === true) {
|
|
230
|
+
const { runArgv, runFixes } = await import("./doctor-fix.js");
|
|
231
|
+
const r = await runFixes({ doctor: () => doctorWorkspace(workspace, {}), run: runArgv, print: (l) => process.stdout.write(`${l}\n`) });
|
|
232
|
+
const left = r.checks.filter((c) => !c.ok);
|
|
233
|
+
const manual = left.filter((c) => c.human);
|
|
234
|
+
const stuck = left.filter((c) => c.cmd);
|
|
235
|
+
if (manual.length) {
|
|
236
|
+
process.stdout.write('还要人来的:\n');
|
|
237
|
+
for (const c of manual)
|
|
238
|
+
process.stdout.write(` ${c.required ? '✗' : '!'} ${c.name.padEnd(26)} ${c.detail}\n`);
|
|
239
|
+
}
|
|
240
|
+
for (const c of stuck)
|
|
241
|
+
process.stdout.write(`修不好:${c.name} ${c.detail}\n`);
|
|
242
|
+
process.stdout.write(r.ok ? '体检通过\n' : '体检不过(✗ 是必需项)\n');
|
|
243
|
+
if (!r.ok)
|
|
244
|
+
process.exitCode = 1;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
150
247
|
const r = await doctorWorkspace(workspace, { live: flags.live === true });
|
|
151
248
|
if (json)
|
|
152
249
|
process.stdout.write(`${JSON.stringify(redactDeep(r), null, 2)}\n`);
|
|
@@ -568,6 +665,12 @@ export async function main(argv) {
|
|
|
568
665
|
if (flags.grade !== undefined)
|
|
569
666
|
throw new UsageError('upgrade 只更新已安装的教学包,不能用 --grade 切换年级');
|
|
570
667
|
const { root } = resolveRoot(workspace);
|
|
668
|
+
// 升级前留一份:新代码弄坏了能回来。备份不成不拦升级,说一声。--dry-run 不动文件,不用
|
|
669
|
+
if (flags['dry-run'] !== true) {
|
|
670
|
+
const said = await backupBeforeUpgrade(root);
|
|
671
|
+
if (said && !json)
|
|
672
|
+
process.stdout.write(`${said}\n`);
|
|
673
|
+
}
|
|
571
674
|
// --config 是另一件事:政策文件补缺(老师文件与 skill 不碰)
|
|
572
675
|
if (flags.config === true) {
|
|
573
676
|
const dryRun = flags['dry-run'] === true;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Workspace } from '../workspace.ts';
|
|
2
|
+
import { type Health } from './service.ts';
|
|
3
|
+
/** 抹掉能认出这家人的东西。太短的(一个字的小名)不抹,不然满篇都被替换 */
|
|
4
|
+
export declare function scrub(text: string, secrets: readonly (string | null | undefined)[]): string;
|
|
5
|
+
export interface ReportOptions {
|
|
6
|
+
/** 在跑的服务答的;没在跑 null */
|
|
7
|
+
health: Health | null;
|
|
8
|
+
now?: Date;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
}
|
|
11
|
+
export declare function buildReport(ws: Workspace, o: ReportOptions): Promise<string>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 诊断信息(cotutor report、家长端「设置」里的「生成诊断信息」):出了问题时一次说清,发给开发者或帮忙的 AI 助手,不用来回问。
|
|
3
|
+
* 不含孩子的对话、照片、录音、档案和 key。只收:版本和系统、服务状态、体检没过的、最近出错的那几轮的原因(不要孩子说了什么)、日志里报错的行、孩子端页面的报错。
|
|
4
|
+
* 孩子的名字、key、业务空间、电脑名、IP、用户名一律抹掉(scrub),最后整篇再抹一遍。给人先看再发,不自动上传。
|
|
5
|
+
*/
|
|
6
|
+
import { arch, homedir, hostname, platform, release, userInfo } from 'node:os';
|
|
7
|
+
import { localDate } from "../../shared/time.js";
|
|
8
|
+
import { dashscopeAuth } from "../../realtime/bailian.js";
|
|
9
|
+
import { listDates, readIndex } from "../../runtime/journal/store.js";
|
|
10
|
+
import { readKidLog } from "../../runtime/journal/kid-log.js";
|
|
11
|
+
import { BUILD } from "../build.js";
|
|
12
|
+
import { listTutors } from "../../schema/config.js";
|
|
13
|
+
import { doctorWorkspace } from "./doctor.js";
|
|
14
|
+
import { listBackups } from "./backup.js";
|
|
15
|
+
import { findService, realHost, tailLog } from "./service.js";
|
|
16
|
+
import { trayStatus } from "./tray.js";
|
|
17
|
+
import { computerName } from "./serve.js";
|
|
18
|
+
const DAYS = 7;
|
|
19
|
+
const MAX_LINE = 180;
|
|
20
|
+
const MAX_ERRORS = 15;
|
|
21
|
+
const LOG_ERROR = /error|fail|失败|没成|崩|exception|ENO[A-Z]+|EADDR|EPERM|EACCES/i;
|
|
22
|
+
const cut = (s, n = MAX_LINE) => (s.length > n ? `${s.slice(0, n)}…` : s);
|
|
23
|
+
/** 抹掉能认出这家人的东西。太短的(一个字的小名)不抹,不然满篇都被替换 */
|
|
24
|
+
export function scrub(text, secrets) {
|
|
25
|
+
let out = text.replace(/sk-[A-Za-z0-9._-]{4,}/g, 'sk-***').replace(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g, (ip) => (ip === '127.0.0.1' ? ip : '<IP>'));
|
|
26
|
+
const home = homedir();
|
|
27
|
+
if (home)
|
|
28
|
+
out = out.split(home).join('$HOME');
|
|
29
|
+
const list = [...new Set(secrets.filter((s) => typeof s === 'string' && s.trim().length >= 2))].sort((a, b) => b.length - a.length);
|
|
30
|
+
for (const s of list)
|
|
31
|
+
out = out.split(s).join('<隐去>');
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
/** 这家人的名字们 */
|
|
35
|
+
function secretsOf(ws, env) {
|
|
36
|
+
const auth = dashscopeAuth(env);
|
|
37
|
+
let user = '';
|
|
38
|
+
try {
|
|
39
|
+
user = userInfo().username;
|
|
40
|
+
}
|
|
41
|
+
catch { /* 没有也行 */ }
|
|
42
|
+
const name = computerName();
|
|
43
|
+
return [ws.config.kid.name, ws.config.kid.slug, ws.config.title, auth?.workspaceId, auth?.key, user, name, `${name}.local`, hostname()].filter((s) => Boolean(s));
|
|
44
|
+
}
|
|
45
|
+
const DATE_N = (now, n) => Array.from({ length: n }, (_, i) => localDate(new Date(now.getTime() - i * 24 * 3600_000)));
|
|
46
|
+
/** 最近几天没成的那几轮:只要原因,不要内容 */
|
|
47
|
+
async function failedTurns(ws, now) {
|
|
48
|
+
const out = [];
|
|
49
|
+
const since = DATE_N(now, DAYS).at(-1);
|
|
50
|
+
for (const t of listTutors(ws.config)) {
|
|
51
|
+
for (const date of (await listDates(ws, t.name).catch(() => [])).filter((d) => d >= since)) {
|
|
52
|
+
const index = await readIndex(ws, t.name, date).catch(() => null);
|
|
53
|
+
for (const m of index?.messages ?? [])
|
|
54
|
+
if (m.result === 'error')
|
|
55
|
+
out.push(`${date} ${t.name} ${cut(String(m.error ?? '没说原因'), 120)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return out.sort().reverse().slice(0, MAX_ERRORS);
|
|
59
|
+
}
|
|
60
|
+
/** 孩子端页面报的错:错误信息和位置,不要上下文(ctx 里可能有孩子正在做的) */
|
|
61
|
+
async function kidErrors(ws, now) {
|
|
62
|
+
const rows = await readKidLog(ws, DATE_N(now, DAYS)).catch(() => []);
|
|
63
|
+
const seen = new Map();
|
|
64
|
+
for (const r of rows)
|
|
65
|
+
if (r.k === 'error') {
|
|
66
|
+
const key = `${r.page} ${cut(r.msg, 120)} @ ${cut(r.where, 60)}`;
|
|
67
|
+
seen.set(key, (seen.get(key) ?? 0) + 1);
|
|
68
|
+
}
|
|
69
|
+
return [...seen].slice(-MAX_ERRORS).map(([k, n]) => (n > 1 ? `${k} ×${n}` : k));
|
|
70
|
+
}
|
|
71
|
+
/** 服务日志里报错的行 */
|
|
72
|
+
async function logErrors(logDir) {
|
|
73
|
+
if (!logDir)
|
|
74
|
+
return [];
|
|
75
|
+
const t = await tailLog(logDir, 400).catch(() => null);
|
|
76
|
+
return (t?.lines ?? []).filter((l) => LOG_ERROR.test(l)).slice(-MAX_ERRORS).map((l) => cut(l));
|
|
77
|
+
}
|
|
78
|
+
export async function buildReport(ws, o) {
|
|
79
|
+
const now = o.now ?? new Date();
|
|
80
|
+
const env = o.env ?? process.env;
|
|
81
|
+
const host = realHost();
|
|
82
|
+
const h = o.health;
|
|
83
|
+
const lines = [];
|
|
84
|
+
const section = (title, items) => {
|
|
85
|
+
lines.push('', `## ${title}`);
|
|
86
|
+
lines.push(...(items.length ? items.map((s) => `- ${s}`) : ['- 没有']));
|
|
87
|
+
};
|
|
88
|
+
lines.push('# cotutor 诊断信息', '不含孩子的对话、照片、录音、档案和 key;孩子的名字、电脑名、IP 已隐去。', '');
|
|
89
|
+
lines.push(`- 版本:${BUILD.version ?? '?'}${BUILD.commit ? `(从仓库跑的,${BUILD.commit.slice(0, 7)})` : ''}`);
|
|
90
|
+
lines.push(`- 系统:${platform()} ${release()} ${arch()};Node ${process.version}`);
|
|
91
|
+
lines.push(`- 时间:${now.toISOString()}`);
|
|
92
|
+
const s = await findService(host, ws.root).catch(() => null);
|
|
93
|
+
const tray = await trayStatus(host).catch(() => null);
|
|
94
|
+
const running = h ? `在跑(${h.mode === 'service' ? '后台服务' : '终端里'},版本 ${h.version ?? '?'},起来于 ${h.started},走 ${h.scheme ?? '?'})` : '没答应';
|
|
95
|
+
section('服务', [
|
|
96
|
+
running,
|
|
97
|
+
`后台服务:${s ? '装了' : '没装'}`,
|
|
98
|
+
tray?.supported ? `图标:${tray.installed ? (tray.running ? '在跑' : '装了,没在跑') : '没装'}` : '图标:这个系统上没有',
|
|
99
|
+
...(h?.bailianTrouble ? [`百炼故障:${h.bailianTrouble.kind}(${h.bailianTrouble.why ?? ''})`] : []),
|
|
100
|
+
...(h?.configError ? [`配置:${cut(h.configError)}`] : []),
|
|
101
|
+
]);
|
|
102
|
+
const doc = await doctorWorkspace(ws.root, { probeEnv: true, firewall: false, now, env }).catch(() => null);
|
|
103
|
+
section('体检没过的', doc ? doc.checks.filter((c) => !c.ok).map((c) => `${c.required ? '必需' : '建议'} ${c.name}:${cut(c.detail, 140)}`) : ['体检没跑成']);
|
|
104
|
+
const backups = await listBackups(ws.root).catch(() => []);
|
|
105
|
+
section('备份', backups[0] ? [`最近一份 ${backups[0].at}(${backups[0].why}),共 ${backups.length} 份`] : ['还没有']);
|
|
106
|
+
section(`最近 ${DAYS} 天没成的话题(只有原因)`, await failedTurns(ws, now));
|
|
107
|
+
section('服务日志里报错的行', await logErrors(s?.logDir ?? null));
|
|
108
|
+
section(`孩子端页面最近 ${DAYS} 天的报错`, await kidErrors(ws, now));
|
|
109
|
+
return `${scrub(lines.join('\n'), secretsOf(ws, env))}\n`;
|
|
110
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const ROOT_CA = "rootCA.pem";
|
|
2
|
+
export declare function defaultCaRoot(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv, home?: string): string;
|
|
3
|
+
export declare function rootCaFile(o?: {
|
|
4
|
+
env?: NodeJS.ProcessEnv;
|
|
5
|
+
platform?: NodeJS.Platform;
|
|
6
|
+
home?: string;
|
|
7
|
+
askMkcert?: () => Promise<string>;
|
|
8
|
+
}): Promise<string | null>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 根证书(mkcert 的 rootCA.pem,公钥)在哪:平板扫码装证书时从这里给(serve.ts 的 http 那一面,GET /ca.crt)。
|
|
3
|
+
* 只认 rootCA.pem;同目录的 rootCA-key.pem 是私钥,永远不读、不给。
|
|
4
|
+
* 先看 CAROOT 环境变量和 mkcert 的缺省目录(后台服务的 PATH 里不一定有 mkcert),都没有再问 mkcert -CAROOT。
|
|
5
|
+
*/
|
|
6
|
+
import { execFile } from 'node:child_process';
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { promisify } from 'node:util';
|
|
11
|
+
const execFileP = promisify(execFile);
|
|
12
|
+
export const ROOT_CA = 'rootCA.pem';
|
|
13
|
+
export function defaultCaRoot(platform = process.platform, env = process.env, home = homedir()) {
|
|
14
|
+
if (platform === 'darwin')
|
|
15
|
+
return join(home, 'Library', 'Application Support', 'mkcert');
|
|
16
|
+
if (platform === 'win32')
|
|
17
|
+
return join(env.LOCALAPPDATA || join(home, 'AppData', 'Local'), 'mkcert');
|
|
18
|
+
return join(env.XDG_DATA_HOME || join(home, '.local', 'share'), 'mkcert');
|
|
19
|
+
}
|
|
20
|
+
export async function rootCaFile(o = {}) {
|
|
21
|
+
const env = o.env ?? process.env;
|
|
22
|
+
for (const dir of [env.CAROOT, defaultCaRoot(o.platform, env, o.home)]) {
|
|
23
|
+
if (dir && existsSync(join(dir, ROOT_CA)))
|
|
24
|
+
return join(dir, ROOT_CA);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const dir = await (o.askMkcert ?? (async () => (await execFileP('mkcert', ['-CAROOT'], { timeout: 5000, env })).stdout.trim()))();
|
|
28
|
+
if (dir && existsSync(join(dir, ROOT_CA)))
|
|
29
|
+
return join(dir, ROOT_CA);
|
|
30
|
+
}
|
|
31
|
+
catch { /* 没装 mkcert */ }
|
|
32
|
+
return null;
|
|
33
|
+
}
|
package/dist/app/cli/serve.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type Server } from 'node:http';
|
|
1
|
+
import { type RequestListener, type Server } from 'node:http';
|
|
2
|
+
import { type Server as NetServer } from 'node:net';
|
|
2
3
|
import { siteUrl } from '../../shared/site-url.ts';
|
|
3
4
|
import type { ListenInfo } from '../../ui/parent/qr-page.ts';
|
|
4
5
|
import { type AppContext } from '../http/app.ts';
|
|
@@ -14,7 +15,8 @@ export interface ServeOptions extends ResolveOptions {
|
|
|
14
15
|
awake?: boolean;
|
|
15
16
|
}
|
|
16
17
|
export interface ServeResult {
|
|
17
|
-
|
|
18
|
+
/** 用 https 时是前面那层(同一个端口分给 https 和 http);不用 https 就是 http 服务本身 */
|
|
19
|
+
server: NetServer;
|
|
18
20
|
ctx: AppContext;
|
|
19
21
|
ws: Workspace;
|
|
20
22
|
port: number;
|
|
@@ -42,6 +44,13 @@ export declare function localName(name?: string): string;
|
|
|
42
44
|
export declare function certCovers(certFile: string, host: string): boolean;
|
|
43
45
|
/** 扫码页要的三样;listen 之后才知道真端口 */
|
|
44
46
|
export declare function listenInfo(https: boolean, port: number): ListenInfo;
|
|
47
|
+
/**
|
|
48
|
+
* 用 https 时 http 那一面只做两件事:给根证书(平板扫码装证书,这时还不信任 https,所以走 http)、别的都跳到 https
|
|
49
|
+
* (切到 https 以前开着的页面、旧书签、手输的 http 地址)。只给 rootCA.pem,私钥永远不给;找不到就说怎么弄。
|
|
50
|
+
*/
|
|
51
|
+
export declare function plainHandler(caFile: () => string | null): RequestListener;
|
|
52
|
+
/** 同一个端口既认 https 又认 http:看第一个字节,TLS 握手(0x16)交给 https,别的交给 http 那一面 */
|
|
53
|
+
export declare function httpAndHttps(secure: Server, plain: Server): NetServer;
|
|
45
54
|
export declare function serveWorkspace(opts?: ServeOptions): Promise<ServeResult>;
|
|
46
55
|
/**
|
|
47
56
|
* busy() 为真就起一个拦闲置睡眠的子进程,为假就停;serve 没了它自己退:macOS 是 caffeinate -i -w <pid>,Windows 是一段 PowerShell(service-windows.ts 的 awakeScript);Linux 什么都不做。
|
package/dist/app/cli/serve.js
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { execFileSync, spawn } from 'node:child_process';
|
|
9
9
|
import { X509Certificate } from 'node:crypto';
|
|
10
|
-
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { createReadStream, existsSync, readFileSync } from 'node:fs';
|
|
11
11
|
import { createServer as createHttp } from 'node:http';
|
|
12
12
|
import { createServer as createHttps } from 'node:https';
|
|
13
|
+
import { createServer as createNetServer } from 'node:net';
|
|
14
|
+
import { rootCaFile } from "./root-ca.js";
|
|
13
15
|
import { hostname, networkInterfaces } from 'node:os';
|
|
14
16
|
import { join } from 'node:path';
|
|
15
17
|
import { siteUrl } from "../../shared/site-url.js";
|
|
@@ -18,6 +20,8 @@ import { answersLocally } from "./port.js";
|
|
|
18
20
|
import { awakeScript, psArgs } from "./service-windows.js";
|
|
19
21
|
import { createContext, createHandler } from "../http/app.js";
|
|
20
22
|
import { liveUpgrade } from "../ws/route.js";
|
|
23
|
+
import { startDailyBackups } from "./backup.js";
|
|
24
|
+
import { BUILD } from "../build.js";
|
|
21
25
|
import { AUTO_BOOKKEEP_EVERY_MS, autoBookkeep } from "../../runtime/auto-bookkeep.js";
|
|
22
26
|
import { USER_CERT_DIR, expandPath, loadWorkspace } from "../workspace.js";
|
|
23
27
|
export { siteUrl };
|
|
@@ -89,6 +93,47 @@ export function listenInfo(https, port) {
|
|
|
89
93
|
const ip = lanAddresses()[0];
|
|
90
94
|
return { name: siteUrl(scheme, localName(), port), ip: ip ? siteUrl(scheme, ip, port) : null, https };
|
|
91
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* 用 https 时 http 那一面只做两件事:给根证书(平板扫码装证书,这时还不信任 https,所以走 http)、别的都跳到 https
|
|
98
|
+
* (切到 https 以前开着的页面、旧书签、手输的 http 地址)。只给 rootCA.pem,私钥永远不给;找不到就说怎么弄。
|
|
99
|
+
*/
|
|
100
|
+
export function plainHandler(caFile) {
|
|
101
|
+
return (req, res) => {
|
|
102
|
+
const path = (req.url ?? '/').split('?')[0];
|
|
103
|
+
if (req.method === 'GET' && (path === '/ca.crt' || path === '/ca')) {
|
|
104
|
+
const file = caFile();
|
|
105
|
+
if (file && existsSync(file)) {
|
|
106
|
+
res.writeHead(200, { 'content-type': 'application/x-x509-ca-cert', 'content-disposition': 'attachment; filename="cotutor-ca.crt"', 'cache-control': 'no-store' });
|
|
107
|
+
createReadStream(file).pipe(res);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
|
|
111
|
+
res.end('这台电脑上没找到根证书:先在电脑上跑 mkcert -install 和 cotutor cert');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
res.writeHead(301, { location: `https://${req.headers.host ?? 'localhost'}${req.url ?? '/'}`, 'cache-control': 'no-store' });
|
|
115
|
+
res.end();
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** 同一个端口既认 https 又认 http:看第一个字节,TLS 握手(0x16)交给 https,别的交给 http 那一面 */
|
|
119
|
+
export function httpAndHttps(secure, plain) {
|
|
120
|
+
const front = createNetServer((sock) => {
|
|
121
|
+
sock.on('error', () => undefined);
|
|
122
|
+
sock.once('data', (first) => {
|
|
123
|
+
sock.pause();
|
|
124
|
+
sock.unshift(first);
|
|
125
|
+
(first[0] === 0x16 ? secure : plain).emit('connection', sock);
|
|
126
|
+
process.nextTick(() => sock.resume());
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
front.once('close', () => {
|
|
130
|
+
for (const s of [secure, plain]) {
|
|
131
|
+
s.close();
|
|
132
|
+
s.closeAllConnections();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return front;
|
|
136
|
+
}
|
|
92
137
|
export async function serveWorkspace(opts = {}) {
|
|
93
138
|
const ws = loadWorkspace(opts.workspace, opts);
|
|
94
139
|
// 不探 CLI(起服务不用等它);防火墙要查:平板连不上时这里是家长唯一看得到提醒的地方
|
|
@@ -99,10 +144,13 @@ export async function serveWorkspace(opts = {}) {
|
|
|
99
144
|
ctx.openCalls = () => liveUpgrade.openCalls();
|
|
100
145
|
const handler = createHandler(ctx);
|
|
101
146
|
const tls = opts.http ? null : httpsFiles(ws);
|
|
102
|
-
const
|
|
147
|
+
const app = tls ? createHttps({ cert: readFileSync(tls.cert), key: readFileSync(tls.key) }, handler) : createHttp(handler);
|
|
103
148
|
// WebSocket:实时老师的那条线(src/app/ws/);不认就关掉
|
|
104
|
-
|
|
149
|
+
app.on('upgrade', (req, socket, head) => { void liveUpgrade(req, socket, head, ctx).then((took) => { if (!took)
|
|
105
150
|
socket.destroy(); }, () => socket.destroy()); });
|
|
151
|
+
// 用 https 时同一个端口也认 http(给平板装根证书、把 http 的跳过去)
|
|
152
|
+
const caFile = tls ? await rootCaFile() : null;
|
|
153
|
+
const server = tls ? httpAndHttps(app, createHttp(plainHandler(() => caFile))) : app;
|
|
106
154
|
// 别的程序只占着 127.0.0.1 时,绑通配地址照样成功:平板连到这边,这台电脑上开 localhost 却到了别的程序。先问一声,当被占处理(main.ts 接)
|
|
107
155
|
if (port !== 0 && (await answersLocally(port)))
|
|
108
156
|
throw Object.assign(new Error(`port ${port} answers on 127.0.0.1`), { code: 'EADDRINUSE', port });
|
|
@@ -131,6 +179,13 @@ export async function serveWorkspace(opts = {}) {
|
|
|
131
179
|
const recheck = setInterval(() => { void ctx.bailian.recheck().catch(() => undefined); }, BAILIAN_RECHECK_MS);
|
|
132
180
|
recheck.unref();
|
|
133
181
|
server.once('close', () => clearInterval(recheck));
|
|
182
|
+
// 每天备份一份(backup.ts):试用的不备;孩子在通话时往后推
|
|
183
|
+
const stopBackups = startDailyBackups({
|
|
184
|
+
target: () => (ctx.ws.sandbox ? null : { root: ctx.ws.root, vault: ctx.ws.paths.vault }),
|
|
185
|
+
busy: () => liveUpgrade.openCalls() > 0,
|
|
186
|
+
version: BUILD.version,
|
|
187
|
+
});
|
|
188
|
+
server.once('close', stopBackups);
|
|
134
189
|
if (opts.awake !== false) {
|
|
135
190
|
const stop = holdAwake(() => liveUpgrade.openCalls() > 0 || Object.keys(ctx.ws.config.tutors).some((t) => ctx.runner.running(t) !== null));
|
|
136
191
|
server.once('close', stop);
|
|
@@ -12,7 +12,7 @@ export interface ShippedAgent {
|
|
|
12
12
|
export declare function shippedAgents(dir?: string): Promise<ShippedAgent[]>;
|
|
13
13
|
export declare const GITIGNORE = "# \u8F6C\u5F55\u65E5\u5FD7\u4F53\u79EF\u5927\u3001\u53EF\u4ECE\u5BF9\u8BDD\u7D22\u5F15\u91CD\u5EFA\u5173\u952E\u4FE1\u606F;\u8981\u7559\u5168\u6863\u5C31\u5220\u6389\u4E0B\u9762\u8FD9\u884C\nconversations/**/*.log\n# \u914D\u97F3\u53EF\u91CD\u65B0\u5408\u6210\nconversations/**/*.mp3\n# \u56DE\u653E(cotutor replay)\u662F\u8C03\u63D0\u793A\u8BCD\u4E0E vault \u65F6\u7684\u5BF9\u7167,\u4E0D\u662F\u5B69\u5B50\u7684\u5BF9\u8BDD\nevals/\n# \u505A\u597D\u7684\u8BFE(\u8BFE\u5305\u3001\u89C6\u9891\u8BFE)\u4E0E\u8BFE\u5305\u622A\u56FE:\u4F53\u79EF\u5927,\u6E90\u5728\u5BB6\u957F\u505A\u8BFE\u7684\u5730\u65B9\nbundles/\nlectures/\nsnaps/\n# \u53E3\u8BED\u8BFE(\u5B69\u5B50\u7684\u539F\u58F0\u3001\u7167\u7247)\ntalk/\n# \u4F5C\u4E1A\u7167\u7247(\u5B69\u5B50\u5728\u8001\u5E08\u9875\u4E0A\u62CD\u7684;paths.captures):\u4F53\u79EF\u5927\u3001\u53EA\u662F\u8FD9\u4E00\u8F6E\u7684\u7D20\u6750,\u8BA4\u51FA\u7684\u6587\u5B57\u5728\u65E5\u8BB0\u91CC\ncaptures/\n# \u5BB6\u957F\u5728 workspace \u91CC\u7528 Claude Code \u65F6\u5B83\u7684\u4E2A\u4EBA\u8BBE\u7F6E\n.claude/settings.local.json\n# .cotutor/ \u662F\u673A\u5668\u6587\u4EF6(\u677F\u4E66\u5199\u6CD5\u7F13\u5B58\u3001\u914D\u97F3\u7F13\u5B58\u3001\u4E8C\u7EF4\u7801\u3001\u65E5\u5FD7),\u53EA\u7559\u51FA\u5382 hash \u4E0E schema\n.cotutor/*\n!.cotutor/shipped.json\n!.cotutor/cotutor.schema.json\nnode_modules/\n.DS_Store\n";
|
|
14
14
|
/** 档案模板(paths.profile,缺了才写):cotutor: profile 的那篇,原文整篇进每个话题的第一条 */
|
|
15
|
-
export declare function profileTemplate(name: string): string;
|
|
15
|
+
export declare function profileTemplate(name: string, schoolStart?: string): string;
|
|
16
16
|
/** 参考目录的 README(缺了才写) */
|
|
17
17
|
export declare const REFERENCE_README = "# \u53C2\u8003\n\n\u8FD9\u91CC\u653E\u653F\u7B56\u4E0E\u4F60\u81EA\u5DF1\u7684\u7B14\u8BB0:\u8BB2\u67D0\u7C7B\u9898\u7684\u504F\u597D\u3001\u8584\u5F31\u70B9\u3001\u5FCC\u8BB3\u3001\u6309\u9898\u578B\u6574\u7406\u7684\u4E1C\u897F\u2014\u2014\u968F\u4FBF\u5EFA,\u673A\u5668\u53EA\u8BFB\u4E0D\u5199\u3002\n\u8BA9\u8001\u5E08\u770B\u5230\u7684\u529E\u6CD5:\u5728\u6863\u6848\u6216\u8FD9\u4F4D\u8001\u5E08\u7684\u5165\u53E3\u6587\u4EF6(`cotutor: subject` \u7684\u90A3\u7BC7)\u91CC\u94FE\u8FC7\u53BB(`[[\u627E\u89C4\u5F8B\u586B\u6570]]`);\u8001\u5E08\u62FF\u5230\u8DEF\u5F84,\u8981\u7528\u65F6\u81EA\u5DF1\u8BFB\u3002\n";
|
|
18
18
|
export interface TutorTemplateInput {
|
package/dist/app/cli/skeleton.js
CHANGED
|
@@ -55,12 +55,12 @@ node_modules/
|
|
|
55
55
|
.DS_Store
|
|
56
56
|
`;
|
|
57
57
|
/** 档案模板(paths.profile,缺了才写):cotutor: profile 的那篇,原文整篇进每个话题的第一条 */
|
|
58
|
-
export function profileTemplate(name) {
|
|
58
|
+
export function profileTemplate(name, schoolStart) {
|
|
59
59
|
return `---
|
|
60
60
|
cotutor: profile
|
|
61
61
|
nickname: ${name}
|
|
62
62
|
birthday:
|
|
63
|
-
school_start:
|
|
63
|
+
school_start:${schoolStart ? ` ${schoolStart}` : ''}
|
|
64
64
|
city:
|
|
65
65
|
---
|
|
66
66
|
|
package/dist/app/cli/usage.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** 命令用法(cotutor --help;技能 cotutor-analyze 的 references/命令与文件.md 从这里取分析用的那几行,所以放单独文件,别的模块 import 不会跑到 CLI) */
|
|
2
|
-
export declare const USAGE = "\u7528\u6CD5:\n cotutor init <slug> [--dir <path>] [--name <\u5B69\u5B50\u540D>] [--grade <1-6>] [--port <n>] \u5EFA ~/cotutor/<slug>/ \u9AA8\u67B6(\u5E42\u7B49\u8865\u7F3A;\u9ED8\u8BA4\u4E8C\u5E74\u7EA7\u6559\u5B66\u5305;\u7AEF\u53E3\u5728\u8FD9\u91CC\u9009\u4E00\u6B21\u5199\u8FDB cotutor.json:443 \u4F18\u5148,\u4E0D\u884C\u5C31 8443\u30018445\u2026,\u4EE5\u540E\u4E0D\u53D8)\n cotutor grades [--json] \u5217\u51FA\u672C\u5305\u53EF\u5B89\u88C5\u7684\u5E74\u7EA7\u6559\u5B66\u5305\n cotutor doctor [--workspace <dir>] [--json] [--live] \u9010\u9879\u4F53\u68C0;--live \u771F\u8FDE\u4E00\u6B21\u767E\u70BC\u5B9E\u65F6\u6A21\u578B\u3001\u771F\u5408\u6210\u4E00\u53E5\u914D\u97F3(\u82B1\u4E00\u5206\u94B1)\u628A API \u5C42\u7684\u5751\u6446\u51FA\u6765(\u771F\u8DD1\u8001\u5E08\u7528 cotutor send);--json \u91CC\u4E0D\u8FC7\u7684\u9879\u5E26 cmd(\u80FD\u76F4\u63A5\u8DD1)\u6216 human(\u8981\u4EBA\u6765)\n cotutor update [--anyway] \u5347\u7EA7\u4E00\u6761\u547D\u4EE4\u7BA1\u5230\u5E95:\u95EE npm \u6709\u6CA1\u6709\u65B0\u7248\u672C,\u6362\u7A0B\u5E8F,\u6BCF\u4E2A\u5B69\u5B50\u66F4\u65B0\u51FA\u5382\u6587\u4EF6\u3001\u91CD\u8D77\u670D\u52A1\u548C\u56FE\u6807;\u5B69\u5B50\u5728\u901A\u8BDD\u65F6\u4E0D\u52A8(--anyway \u624D\u52A8),npm \u6CA1\u6210\u670D\u52A1\u4E0D\u52A8\n cotutor upgrade [--workspace <dir>] [--force <\u8001\u5E08>]... \u8001\u5E08\u6587\u4EF6\u3001activity \u4E0E skill \u6362\u65B0\u7248:\u6CA1\u6539\u8FC7\u7684\u76F4\u63A5\u6362,\u6539\u8FC7\u7684\u53EA\u62A5 diff(--force <\u8001\u5E08> \u6216 --force <\u8001\u5E08>/<\u540D> \u624D\u8986\u76D6,\u539F\u6587\u7559 .bak)\n cotutor upgrade --config [--dry-run] [--workspace <dir>] cotutor.json \u8865\u7F3A:\u65B0\u51FA\u5382\u8001\u5E08 / \u8FD0\u884C\u65F6 / \u547D\u4EE4\u6A21\u677F\u65D7\u6807(\u53EA\u52A0\u7F3A\u7684,\u4F60\u6539\u8FC7\u7684\u503C\u4E0D\u52A8)\n cotutor add <\u8001\u5E08\u540D> --display <\u663E\u793A\u540D> [--subject <\u5B66\u79D1>] [--avatar <emoji|\u56FE\u7247\u76F8\u5BF9\u8DEF\u5F84>] [--hidden] \u52A0\u4E00\u4F4D\u81EA\u5BB6\u7684\u8001\u5E08:\u51FA\u6A21\u677F\u6587\u4EF6\u3001\u8FDB cotutor.json\u3001\u5EFA\u76EE\u5F55(\u540D\u5B57\u4EE5 -tutor \u7ED3\u5C3E\u7684\u662F\u8001\u5E08,figshot \u7ED9\u5B83\u505A\u5934\u50CF)\n cotutor add-theme <\u4E3B\u9898\u540D> [--from <\u4E3B\u9898>] [--workspace <dir>] \u52A0\u4E00\u4E2A\u81EA\u5BB6\u7684\u4E3B\u9898:\u62F7\u4E00\u4EFD(\u7F3A\u7701\u51FA\u5382\u7684 default)\u5230 themes/<\u4E3B\u9898\u540D>/,\u6539 cotutor.json \u7684 kid.theme \u6362\u8FC7\u53BB\n cotutor serve [--workspace <dir>] [--port <n>] [--http] [--trace] [--open] [--log-dir <\u76EE\u5F55>] \u5728\u7EC8\u7AEF\u91CC\u8D77\u670D\u52A1(\u4E00 workspace \u4E00\u8FDB\u7A0B;~/.config/cotutor/certs/ \u6709\u8BC1\u4E66\u5C31\u8D70 HTTPS;--trace \u6BCF\u4E00\u8F6E\u7684\u4E8B\u4EF6\u6309\u9053\u6253\u5370;\u53EA\u6253\u5370\u5BB6\u957F\u7AEF\u9996\u9875\u7684\u5730\u5740,\u5B69\u5B50\u7684\u5E73\u677F\u5728\u90A3\u91CC\u626B\u7801;--open \u987A\u624B\u6253\u5F00,\u65E7\u540D --open-qr \u7167\u6837\u8BA4;--log-dir \u8F93\u51FA\u6309\u5929\u843D\u6587\u4EF6,\u540E\u53F0\u670D\u52A1\u7528;\u5173\u4E86\u7EC8\u7AEF\u5C31\u505C,\u4E00\u76F4\u5F00\u7740\u7528 cotutor service)\n cotutor open [--workspace <dir>] [--sandbox] [--print] [--json] \u6253\u5F00\u5BB6\u957F\u7AEF(\u8BFB cotutor.json,\u4E0D\u7528\u8BB0\u7AEF\u53E3);--sandbox \u5F00\u8BD5\u7528\u7684\u90A3\u4E2A(\u7AEF\u53E3\u6C38\u8FDC\u662F\u771F\u7684 +1);--print \u53EA\u6253\u5370\u5730\u5740;\u6CA1\u5728\u8DD1\u5C31\u8BF4\u600E\u4E48\u8D77,\u4EE5 1 \u9000\n cotutor service install | uninstall | start | stop | restart [--workspace <dir>] [--no-tray] [--no-open] \u628A serve \u88C5\u6210\u8FD9\u53F0\u7535\u8111\u7684\u540E\u53F0\u670D\u52A1(macOS launchd\u3001Linux systemd \u7528\u6237\u670D\u52A1\u3001Windows \u7528\u6237\u7684\u767B\u5F55\u9879,\u90FD\u4E0D\u8981\u7BA1\u7406\u5458):\u767B\u5F55\u540E\u81EA\u5DF1\u8D77\u6765\u3001\u5D29\u4E86 10 \u79D2\u540E\u518D\u8D77\u3001\u65E5\u5FD7\u6309\u5929\u843D\u6587\u4EF6\u7559 14 \u5929;\u4E00\u4E2A workspace \u4E00\u4E2A;\u7B2C\u4E00\u6B21 install \u6253\u5F00\u4E00\u6B21\u5BB6\u957F\u7AEF(--no-open \u4E0D\u5F00),\u4EE5\u540E\u7684\u542F\u52A8\u90FD\u4E0D\u5F00;\u6362\u4E86 node \u7248\u672C\u6216\u5305\u632A\u4E86\u5730\u65B9\u518D install \u4E00\u6B21\n cotutor tray on | off | status [--json] \u83DC\u5355\u680F / \u4EFB\u52A1\u680F\u56FE\u6807(macOS\u3001Windows;service install \u65F6\u4E00\u8D77\u88C5,--no-tray \u4E0D\u5E26):\u6B63\u5E38\u65F6\u53EA\u6709 logo,\u6709\u4E8B\u51FA\u6A59\u70B9,\u6CA1\u5728\u8DD1\u65F6\u53D8\u6DE1\u52A0\u7EA2\u70B9\u5E76\u901A\u77E5\u4E00\u6B21;\u70B9\u5B83\u770B\u72B6\u6001\u3001\u6253\u5F00\u5BB6\u957F\u7AEF\u3001\u91CD\u542F\u3001\u770B\u65E5\u5FD7\u3001\u9000\u51FA;off \u5173\u6389\u5E76\u8BB0\u4F4F\n cotutor service status [--json] | logs [--lines <n>] [--follow] \u540E\u53F0\u670D\u52A1\u5728\u4E0D\u5728\u8DD1\u3001\u7AEF\u53E3\u7B54\u4E0D\u7B54\u5E94\u3001\u8DD1\u7684\u54EA\u4E2A\u7248\u672C\u3001vault \u8BFB\u4E0D\u8BFB\u5F97\u5230\u3001\u65E5\u5FD7\u5728\u54EA;--json \u8FD8\u7B54\u5E73\u677F\u5730\u5740\u3001\u8BC1\u4E66\u3001\u767E\u70BC\u914D\u6CA1\u914D\u3001\u56FE\u6807;logs \u770B\u6700\u8FD1\u7684\u65E5\u5FD7\n cotutor pin [off | status] [--pin <\u6570\u5B57>] [--json] \u5BB6\u957F\u7AEF /parent \u4E0E\u5DE5\u4F5C\u53F0 /dev \u7684\u9501:\u673A\u5668\u7EA7 PIN(\u6240\u6709 workspace \u5171\u7528),iPad \u4E0A\u8F93\u4E00\u6B21\u3001\u8FD9\u53F0\u7535\u8111\u514D\u5BC6;\u6CA1\u8BBE\u5C31\u5168\u5F00(\u5BB6\u957F\u5728\u65C1\u8FB9\u53EF\u4EE5\u4E0D\u8BBE);off \u64A4\u6389\n cotutor cert [--host <\u540D\u6216IP>]... \u7528 mkcert \u5EFA\u8FD9\u53F0\u673A\u5668\u7684\u81EA\u7B7E\u8BC1\u4E66\u5230 ~/.config/cotutor/certs/(\u5E73\u677F\u3001\u624B\u673A\u4E0A\u5F55\u97F3\u8981 HTTPS;\u6240\u6709 workspace \u5171\u7528)\n cotutor send <\u8001\u5E08> [<\u6D88\u606F>] [--from kid|system] [--runtime <\u540D>] [--new] [--photo a.jpg,b.png] [--activity <\u540D> [--param '\u540D=\u503C;\u540D=\u503C']] [--lane main,tts] [--quiet] \u7EC8\u7AEF\u91CC\u53D1\u4E00\u6761,\u73B0\u573A\u6309\u9053\u6253\u5370\u6BCF\u9053\u5DE5\u5E8F\u7684\u4E8B\u4EF6,\u8BF4\u5B8C\u6253\u5370\u7ED3\u679C(\u5B69\u5B50\u7684\u8BDD\u7167\u5B9E\u65F6\u5199,\u540C\u5B69\u5B50\u7AEF:\u4E0A\u4E0B\u6587\u5305\u5E26 live:\u3001\u4E0D\u8865\u63D0\u95EE\u5361,\u53EA\u662F\u6CA1\u6709\u5634;--from system \u4E0D\u7B97;--new \u5F00\u65B0\u8BDD\u9898;--photo \u5E26\u4F5C\u4E1A\u7167\u7247,\u62F7\u8FDB captures/,\u6709\u7167\u7247\u65F6\u6D88\u606F\u53EF\u4EE5\u7A7A;--activity \u50CF\u5B69\u5B50\u70B9\u4E86\u9996\u9875\u7684 activity \u6309\u94AE,\u65B0\u5F00\u8BDD\u9898,\u53C2\u6570\u540C\u6309\u94AE\u4E0B\u9762\u90A3\u51E0\u884C;--quiet \u53EA\u8981\u7ED3\u679C)\n cotutor pack <\u8001\u5E08> [<\u6D88\u606F>] [--from kid|system] [--at <ISO\u65F6\u95F4>] [--activity <\u540D> [--param '\u540D=\u503C;\u540D=\u503C']] [--json] \u5E72\u8DD1\u4E0A\u4E0B\u6587\u5305(--activity \u770B activity \u8BDD\u9898\u7B2C\u4E00\u6761\u8001\u5E08\u62FF\u5230\u4EC0\u4E48):\u4E0D\u8D77\u6A21\u578B,\u6253\u5370\u73B0\u5728\u4F1A\u53D1\u7ED9\u8001\u5E08\u7684\u90A3\u4EFD + \u6BCF\u6BB5\u6765\u81EA\u54EA\u4E2A\u6587\u4EF6\u3001\u90A3\u91CC\u4E00\u5171\u51E0\u6761\u3001\u6309\u653F\u7B56\u5E26\u4E86\u51E0\u6761(\u6539\u4E86\u6863\u6848 / \u65E5\u8BB0 / \u8BA1\u5212\u7ACB\u523B\u770B\u6548\u679C)\n cotutor replay <\u8001\u5E08> <job> [<\u65E5\u671F>] [--runtime <\u540D>] [--json] \u56DE\u653E\u4E00\u8F6E:\u540C\u4E00\u95EE\u6309\u73B0\u5728\u7684 vault \u4E0E\u63D0\u793A\u8BCD\u518D\u8DD1\u4E00\u904D(\u843D evals/,\u4E0D\u8FDB\u5B69\u5B50\u7684\u5BF9\u8BDD\u3001\u4E0D\u914D\u97F3),\u8DD1\u5B8C\u5E76\u6392\u6253\u5370\u4E0A\u4E0B\u6587\u5305 / \u8BB2\u7A3F / \u5361 / \u8BFB\u4E86\u4EC0\u4E48\u7684 diff\n cotutor compare <\u8001\u5E08> [<evalJob>] [<\u65E5\u671F>] [--json] \u518D\u770B\u4E00\u6B21\u56DE\u653E\u7684\u5BF9\u7167(\u4E0D\u7ED9 evalJob \u5C31\u5217\u8FD9\u5929\u56DE\u653E\u8FC7\u54EA\u4E9B)\n cotutor show <\u8001\u5E08> <job> [<\u65E5\u671F>] [--json] [--evals] [--workspace <dir>] \u770B\u4E00\u8F6E:\u95EE\u4E86\u4EC0\u4E48\u3001\u4E0A\u4E0B\u6587\u5305\u3001\u5F53\u65F6\u7684\u8001\u5E08\u6587\u4EF6\u4E0E\u6280\u80FD hash\u3001\u8BB2\u7A3F\u4E0E\u5361\u3001\u8BFB\u4E86\u4EC0\u4E48\u3001token \u4E0E\u7528\u65F6\u3001\u7ED9\u5BB6\u957F\u7684\u5C3E\u5DF4;--json \u662F\u5BB6\u957F\u7AEF\u300C\u770B\u539F\u6587\u300D\u540C\u4E00\u4EFD\u6570\u636E,\u7ED9\u7F16\u7A0B\u52A9\u624B\u5206\u6790\u7528\n cotutor trace <\u8001\u5E08> <job> [<\u65E5\u671F>] [--lane \u2026] [--workspace <dir>] \u56DE\u653E\u4E00\u8F6E\u7684\u4E8B\u4EF6(<\u65E5\u671F>.<job>.events.jsonl;\u6392\u67E5\u6628\u5929\u90A3\u8F6E\u7528)\n cotutor rate <\u8001\u5E08> <\u8BDD\u9898> <1-5> [--date <\u65E5\u671F>] \u7ED9\u4E00\u4E2A\u8BDD\u9898\u6253\u661F(\u4E0E\u5BB6\u957F\u7AEF\u540C\u4E00\u6761\u8DEF;\u2265 vault.keepScore \u7684\u8BDD\u9898\u8BB0\u8D26\u65F6\u6458\u8981\u624D\u8FDB\u65E5\u8BB0)\n cotutor bookkeep <\u8001\u5E08> [--date <\u65E5\u671F>] [--thread <\u8BDD\u9898>]... \u8BB0\u8D26:\u8FD9\u5929\u6BCF\u4E2A\u8FD8\u6CA1\u8BB0\u8FC7\u7684\u8BDD\u9898\u5404\u8D77\u4E00\u8F6E\u8BB0\u8D26\u4EFB\u52A1,\u8001\u5E08\u56DE\u300C## \u8BB0\u8D26\u300D,\u5E94\u7528\u5199\u8FDB vault \u7684\u65E5\u8BB0(\u8BDD\u9898\u540D\u3001\u5B69\u5B50\u95EE\u3001\u6458\u8981\u3001\u89C2\u5BDF)\n cotutor home check [--published] [--json] \u68C0\u67E5\u9996\u9875\u8349\u7A3F home/draft.md(\u6216\u5DF2\u53D1\u5E03\u7684\u90A3\u4EFD):\u5B69\u5B50\u4F1A\u770B\u5230\u54EA\u51E0\u5F20\u5361\u3001\u6BCF\u4F4D\u8001\u5E08\u7684\u6309\u94AE\u3001\u8981\u6539\u7684\u4E0E\u63D0\u9192(\u5E26\u884C\u53F7);\u4E0D\u82B1\u94B1\n cotutor home publish [--force] [--from <\u6587\u4EF6>] [--json] \u53D1\u5E03\u9996\u9875:\u68C0\u67E5 \u2192 \u7559\u5386\u53F2 home/history/<id>.md \u2192 \u5199 home/published.json(\u5B69\u5B50\u7AEF\u53EA\u8BFB\u5B83);\u6709\u8981\u6539\u7684\u4E0D\u53D1,--force \u4E22\u6389\u90A3\u51E0\u5F20\u7167\u53D1;--from \u53D1\u53E6\u4E00\u4EFD(\u5982\u5386\u53F2)\n cotutor home show [--json] \u73B0\u5728\u53D1\u5E03\u7684\u662F\u54EA\u4EFD\u3001\u51E0\u5929\u524D\u3001\u6BCF\u4E2A\u6309\u94AE\u88AB\u70B9\u4E86\u51E0\u6B21(\u54EA\u5929\u3001\u54EA\u4E2A\u8BDD\u9898)\n cotutor activity check [<\u8001\u5E08> [<\u540D>]] [--json] \u68C0\u67E5 activity \u6587\u4EF6(agents/<\u8001\u5E08>/activities/<\u540D>.md):\u5BB6\u957F\u586B\u7684\u3001\u5E94\u7528\u7B97\u7684\u3001\u5E94\u7528\u62E6\u7684\u3001\u88C1\u6BB5\u3001\u63D2\u69FD\u3001\u6B63\u6587\u7528\u7684\u5361\u3001\u7B2C\u4E00\u53E5\u4E0E\u6536(\u5E26\u884C\u53F7);\u4E0D\u82B1\u94B1\n cotutor activity eval <\u8001\u5E08> <\u540D> [--runs <n>] [--only <\u4F8B\u5B50>] [--kid-model <\u5343\u95EE\u6A21\u578B>] [--json] \u7167 <\u540D>.\u4F8B\u5B50.md \u8DD1:\u771F\u8001\u5E08 + \u6A21\u62DF\u5B69\u5B50(\u5343\u95EE,\u7F3A\u7701 qwen3.8-flash,\u6709 Token Plan \u8D70\u5B83)\u591A\u8F6E(\u7167\u5B9E\u65F6\u5199,\u540C\u5B69\u5B50\u7AEF),\u5224\u53EA\u770B\u677F\u4E66;\u4E00\u4E2A\u4F8B\u5B50\u8DD1 n \u6B21(\u7F3A\u7701 3)\u6B21\u6B21\u90FD\u8FC7\u624D\u7B97\u8FC7;\u843D evals/,\u753B\u5BB6\u662F\u5047\u7684;\u82B1\u94B1\n cotutor chalk <check|build|bake|snap|dub> <scene.ts|\u8BFE\u5305 id> [...] \u505A\u5C0F\u8BFE\u5802\u7684\u8BFE\u5305:check \u6821\u9A8C\u573A\u666F\u3001build \u51FA\u8BFE\u5305\u5230 bundles/\u3001bake \u70E4\u753B\u9762\u3001snap \u6BCF\u6B65\u622A\u56FE\u3001dub \u914D\u97F3(cotutor chalk \u770B\u5404\u81EA\u7684\u9009\u9879;bake / snap \u8981\u672C\u673A\u7684 Chrome)\n cotutor mock [--port <n>] [--scenario normal|nokey] [--delay <ms>] [--http] \u4E0D\u7ECF\u771F\u5B9E\u8001\u5E08\u4E0E\u767E\u70BC,\u7528\u56FA\u5B9A\u7684\u677F\u4E66 JSON \u8D77\u5B69\u5B50\u7AEF,\u6D4B\u524D\u7AEF\u4EA4\u4E92\u4E0E\u6E32\u67D3(\u4E0D\u9700\u8981 workspace;\u8001\u5E08\u90FD\u8D70\u5B9E\u65F6\u8001\u5E08\u9875,\u5047\u5634\u7167\u811A\u672C\u5FF5\u9759\u97F3\u7684\u58F0\u97F3;nokey = \u6CA1\u914D\u767E\u70BC,\u5B69\u5B50\u7AEF\u53EA\u51FA\u300C\u8001\u5E08\u4EEC\u8FD8\u6CA1\u5230\u300D)\n cotutor --version | --help\nworkspace\u89E3\u6790:--workspace > COTUTOR_WORKSPACE > cwd \u6216\u7956\u5148\u6709 cotutor.json > ~/.config/cotutor/config.json > ~/cotutor/ \u4E0B\u552F\u4E00\u7684\u5B69\u5B50\u76EE\u5F55\n\u8BD5\u7528:\u8981 workspace \u7684\u547D\u4EE4\u52A0 --sandbox,\u6362\u5230\u771F workspace \u65C1\u8FB9\u7684 .sandbox/<\u76EE\u5F55\u540D>/,\u8BD5\u7684\u5BF9\u8BDD\u4E0E vault \u7684\u5199\u5165\u4E0D\u8FDB\u5B69\u5B50\u7684\u8BB0\u5F55(\u7B2C\u4E00\u6B21\u81EA\u52A8\u5206:\u8BBE\u7F6E\u7167\u62F7,\u5BF9\u8BDD\u4E0E\u7167\u7247\u4E0D\u5E26,vault \u62F7\u4E00\u4EFD;\u6807\u9898\u524D\u52A0\u300C\u8BD5\u7528\u300D\u3001\u7AEF\u53E3 +1\u3001\u81EA\u52A8\u8BB0\u8D26\u5173\u6389);--sandbox --fresh \u91CD\u65B0\u5206\n";
|
|
2
|
+
export declare const USAGE = "\u7528\u6CD5:\n cotutor init <slug> [--dir <path>] [--name <\u5B69\u5B50\u540D>] [--grade <1-6>] [--port <n>] \u5EFA ~/cotutor/<slug>/ \u9AA8\u67B6(\u5E42\u7B49\u8865\u7F3A;\u9ED8\u8BA4\u4E8C\u5E74\u7EA7\u6559\u5B66\u5305;\u7AEF\u53E3\u5728\u8FD9\u91CC\u9009\u4E00\u6B21\u5199\u8FDB cotutor.json:443 \u4F18\u5148,\u4E0D\u884C\u5C31 8443\u30018445\u2026,\u4EE5\u540E\u4E0D\u53D8)\n cotutor grades [--json] \u5217\u51FA\u672C\u5305\u53EF\u5B89\u88C5\u7684\u5E74\u7EA7\u6559\u5B66\u5305\n cotutor doctor [--workspace <dir>] [--json] [--live] [--fix] \u9010\u9879\u4F53\u68C0;--live \u771F\u8FDE\u4E00\u6B21\u767E\u70BC\u5B9E\u65F6\u6A21\u578B\u3001\u771F\u5408\u6210\u4E00\u53E5\u914D\u97F3(\u82B1\u4E00\u5206\u94B1)\u628A API \u5C42\u7684\u5751\u6446\u51FA\u6765(\u771F\u8DD1\u8001\u5E08\u7528 cotutor send);--json \u91CC\u4E0D\u8FC7\u7684\u9879\u5E26 cmd(\u80FD\u76F4\u63A5\u8DD1)\u6216 human(\u8981\u4EBA\u6765);--fix \u628A\u5E26 cmd \u7684\u4E00\u6761\u6761\u4FEE\u6389,\u8981\u4EBA\u6765\u7684\u7559\u7740\n cotutor report [--workspace <dir>] \u8BCA\u65AD\u4FE1\u606F:\u7248\u672C\u3001\u7CFB\u7EDF\u3001\u670D\u52A1\u3001\u4F53\u68C0\u6CA1\u8FC7\u7684\u3001\u6700\u8FD1\u62A5\u9519\u7684\u539F\u56E0;\u4E0D\u542B\u5B69\u5B50\u7684\u5BF9\u8BDD\u3001\u7167\u7247\u3001\u5F55\u97F3\u3001\u6863\u6848\u548C key,\u540D\u5B57\u548C IP \u5DF2\u62B9\u6389,\u5148\u770B\u518D\u53D1\n cotutor backup [list | restore <\u540D\u5B57> [--yes]] [--workspace <dir>] \u5907\u4EFD\u5230 workspace \u65C1\u8FB9\u7684 .backups/:\u4E0D\u542B\u5F55\u97F3;\u670D\u52A1\u5F00\u7740\u65F6\u6BCF\u5929\u4E00\u4EFD(\u7559 14 \u4EFD),cotutor upgrade \u524D\u4E00\u4EFD(\u7559 5 \u4EFD);\u6CA1\u53D8\u7684\u6587\u4EF6\u786C\u94FE\u63A5,\u4E0D\u5360\u7B2C\u4E8C\u4EFD\u5730\u65B9;restore \u5148\u628A\u73B0\u5728\u7684\u4E5F\u5907\u4E00\u4EFD\u518D\u62F7\u56DE\u53BB,\u4E0D\u52A0 --yes \u53EA\u8BF4\u4F1A\u505A\u4EC0\u4E48\n cotutor update [--anyway] \u5347\u7EA7\u4E00\u6761\u547D\u4EE4\u7BA1\u5230\u5E95:\u95EE npm \u6709\u6CA1\u6709\u65B0\u7248\u672C,\u6362\u7A0B\u5E8F,\u6BCF\u4E2A\u5B69\u5B50\u66F4\u65B0\u51FA\u5382\u6587\u4EF6\u3001\u91CD\u8D77\u670D\u52A1\u548C\u56FE\u6807;\u5B69\u5B50\u5728\u901A\u8BDD\u65F6\u4E0D\u52A8(--anyway \u624D\u52A8),npm \u6CA1\u6210\u670D\u52A1\u4E0D\u52A8\n cotutor upgrade [--workspace <dir>] [--force <\u8001\u5E08>]... \u8001\u5E08\u6587\u4EF6\u3001activity \u4E0E skill \u6362\u65B0\u7248:\u6CA1\u6539\u8FC7\u7684\u76F4\u63A5\u6362,\u6539\u8FC7\u7684\u53EA\u62A5 diff(--force <\u8001\u5E08> \u6216 --force <\u8001\u5E08>/<\u540D> \u624D\u8986\u76D6,\u539F\u6587\u7559 .bak)\n cotutor upgrade --config [--dry-run] [--workspace <dir>] cotutor.json \u8865\u7F3A:\u65B0\u51FA\u5382\u8001\u5E08 / \u8FD0\u884C\u65F6 / \u547D\u4EE4\u6A21\u677F\u65D7\u6807(\u53EA\u52A0\u7F3A\u7684,\u4F60\u6539\u8FC7\u7684\u503C\u4E0D\u52A8)\n cotutor add <\u8001\u5E08\u540D> --display <\u663E\u793A\u540D> [--subject <\u5B66\u79D1>] [--avatar <emoji|\u56FE\u7247\u76F8\u5BF9\u8DEF\u5F84>] [--hidden] \u52A0\u4E00\u4F4D\u81EA\u5BB6\u7684\u8001\u5E08:\u51FA\u6A21\u677F\u6587\u4EF6\u3001\u8FDB cotutor.json\u3001\u5EFA\u76EE\u5F55(\u540D\u5B57\u4EE5 -tutor \u7ED3\u5C3E\u7684\u662F\u8001\u5E08,figshot \u7ED9\u5B83\u505A\u5934\u50CF)\n cotutor add-theme <\u4E3B\u9898\u540D> [--from <\u4E3B\u9898>] [--workspace <dir>] \u52A0\u4E00\u4E2A\u81EA\u5BB6\u7684\u4E3B\u9898:\u62F7\u4E00\u4EFD(\u7F3A\u7701\u51FA\u5382\u7684 default)\u5230 themes/<\u4E3B\u9898\u540D>/,\u6539 cotutor.json \u7684 kid.theme \u6362\u8FC7\u53BB\n cotutor serve [--workspace <dir>] [--port <n>] [--http] [--trace] [--open] [--log-dir <\u76EE\u5F55>] \u5728\u7EC8\u7AEF\u91CC\u8D77\u670D\u52A1(\u4E00 workspace \u4E00\u8FDB\u7A0B;~/.config/cotutor/certs/ \u6709\u8BC1\u4E66\u5C31\u8D70 HTTPS;--trace \u6BCF\u4E00\u8F6E\u7684\u4E8B\u4EF6\u6309\u9053\u6253\u5370;\u53EA\u6253\u5370\u5BB6\u957F\u7AEF\u9996\u9875\u7684\u5730\u5740,\u5B69\u5B50\u7684\u5E73\u677F\u5728\u90A3\u91CC\u626B\u7801;--open \u987A\u624B\u6253\u5F00,\u65E7\u540D --open-qr \u7167\u6837\u8BA4;--log-dir \u8F93\u51FA\u6309\u5929\u843D\u6587\u4EF6,\u540E\u53F0\u670D\u52A1\u7528;\u5173\u4E86\u7EC8\u7AEF\u5C31\u505C,\u4E00\u76F4\u5F00\u7740\u7528 cotutor service)\n cotutor open [--workspace <dir>] [--sandbox] [--print] [--json] \u6253\u5F00\u5BB6\u957F\u7AEF(\u8BFB cotutor.json,\u4E0D\u7528\u8BB0\u7AEF\u53E3);--sandbox \u5F00\u8BD5\u7528\u7684\u90A3\u4E2A(\u7AEF\u53E3\u6C38\u8FDC\u662F\u771F\u7684 +1);--print \u53EA\u6253\u5370\u5730\u5740;\u6CA1\u5728\u8DD1\u5C31\u8BF4\u600E\u4E48\u8D77,\u4EE5 1 \u9000\n cotutor service install | uninstall | start | stop | restart [--workspace <dir>] [--no-tray] [--no-open] \u628A serve \u88C5\u6210\u8FD9\u53F0\u7535\u8111\u7684\u540E\u53F0\u670D\u52A1(macOS launchd\u3001Linux systemd \u7528\u6237\u670D\u52A1\u3001Windows \u7528\u6237\u7684\u767B\u5F55\u9879,\u90FD\u4E0D\u8981\u7BA1\u7406\u5458):\u767B\u5F55\u540E\u81EA\u5DF1\u8D77\u6765\u3001\u5D29\u4E86 10 \u79D2\u540E\u518D\u8D77\u3001\u65E5\u5FD7\u6309\u5929\u843D\u6587\u4EF6\u7559 14 \u5929;\u4E00\u4E2A workspace \u4E00\u4E2A;\u7B2C\u4E00\u6B21 install \u6253\u5F00\u4E00\u6B21\u5BB6\u957F\u7AEF(--no-open \u4E0D\u5F00),\u4EE5\u540E\u7684\u542F\u52A8\u90FD\u4E0D\u5F00;\u6362\u4E86 node \u7248\u672C\u6216\u5305\u632A\u4E86\u5730\u65B9\u518D install \u4E00\u6B21\n cotutor tray on | off | status [--json] \u83DC\u5355\u680F / \u4EFB\u52A1\u680F\u56FE\u6807(macOS\u3001Windows;service install \u65F6\u4E00\u8D77\u88C5,--no-tray \u4E0D\u5E26):\u6B63\u5E38\u65F6\u53EA\u6709 logo,\u6709\u4E8B\u51FA\u6A59\u70B9,\u6CA1\u5728\u8DD1\u65F6\u53D8\u6DE1\u52A0\u7EA2\u70B9\u5E76\u901A\u77E5\u4E00\u6B21;\u70B9\u5B83\u770B\u72B6\u6001\u3001\u6253\u5F00\u5BB6\u957F\u7AEF\u3001\u91CD\u542F\u3001\u770B\u65E5\u5FD7\u3001\u9000\u51FA;off \u5173\u6389\u5E76\u8BB0\u4F4F\n cotutor service status [--json] | logs [--lines <n>] [--follow] \u540E\u53F0\u670D\u52A1\u5728\u4E0D\u5728\u8DD1\u3001\u7AEF\u53E3\u7B54\u4E0D\u7B54\u5E94\u3001\u8DD1\u7684\u54EA\u4E2A\u7248\u672C\u3001vault \u8BFB\u4E0D\u8BFB\u5F97\u5230\u3001\u65E5\u5FD7\u5728\u54EA;--json \u8FD8\u7B54\u5E73\u677F\u5730\u5740\u3001\u8BC1\u4E66\u3001\u767E\u70BC\u914D\u6CA1\u914D\u3001\u56FE\u6807;logs \u770B\u6700\u8FD1\u7684\u65E5\u5FD7\n cotutor pin [off | status] [--pin <\u6570\u5B57>] [--json] \u5BB6\u957F\u7AEF /parent \u4E0E\u5DE5\u4F5C\u53F0 /dev \u7684\u9501:\u673A\u5668\u7EA7 PIN(\u6240\u6709 workspace \u5171\u7528),iPad \u4E0A\u8F93\u4E00\u6B21\u3001\u8FD9\u53F0\u7535\u8111\u514D\u5BC6;\u6CA1\u8BBE\u5C31\u5168\u5F00(\u5BB6\u957F\u5728\u65C1\u8FB9\u53EF\u4EE5\u4E0D\u8BBE);off \u64A4\u6389\n cotutor cert [--host <\u540D\u6216IP>]... \u7528 mkcert \u5EFA\u8FD9\u53F0\u673A\u5668\u7684\u81EA\u7B7E\u8BC1\u4E66\u5230 ~/.config/cotutor/certs/(\u5E73\u677F\u3001\u624B\u673A\u4E0A\u5F55\u97F3\u8981 HTTPS;\u6240\u6709 workspace \u5171\u7528)\n cotutor send <\u8001\u5E08> [<\u6D88\u606F>] [--from kid|system] [--runtime <\u540D>] [--new] [--photo a.jpg,b.png] [--activity <\u540D> [--param '\u540D=\u503C;\u540D=\u503C']] [--lane main,tts] [--quiet] \u7EC8\u7AEF\u91CC\u53D1\u4E00\u6761,\u73B0\u573A\u6309\u9053\u6253\u5370\u6BCF\u9053\u5DE5\u5E8F\u7684\u4E8B\u4EF6,\u8BF4\u5B8C\u6253\u5370\u7ED3\u679C(\u5B69\u5B50\u7684\u8BDD\u7167\u5B9E\u65F6\u5199,\u540C\u5B69\u5B50\u7AEF:\u4E0A\u4E0B\u6587\u5305\u5E26 live:\u3001\u4E0D\u8865\u63D0\u95EE\u5361,\u53EA\u662F\u6CA1\u6709\u5634;--from system \u4E0D\u7B97;--new \u5F00\u65B0\u8BDD\u9898;--photo \u5E26\u4F5C\u4E1A\u7167\u7247,\u62F7\u8FDB captures/,\u6709\u7167\u7247\u65F6\u6D88\u606F\u53EF\u4EE5\u7A7A;--activity \u50CF\u5B69\u5B50\u70B9\u4E86\u9996\u9875\u7684 activity \u6309\u94AE,\u65B0\u5F00\u8BDD\u9898,\u53C2\u6570\u540C\u6309\u94AE\u4E0B\u9762\u90A3\u51E0\u884C;--quiet \u53EA\u8981\u7ED3\u679C)\n cotutor pack <\u8001\u5E08> [<\u6D88\u606F>] [--from kid|system] [--at <ISO\u65F6\u95F4>] [--activity <\u540D> [--param '\u540D=\u503C;\u540D=\u503C']] [--json] \u5E72\u8DD1\u4E0A\u4E0B\u6587\u5305(--activity \u770B activity \u8BDD\u9898\u7B2C\u4E00\u6761\u8001\u5E08\u62FF\u5230\u4EC0\u4E48):\u4E0D\u8D77\u6A21\u578B,\u6253\u5370\u73B0\u5728\u4F1A\u53D1\u7ED9\u8001\u5E08\u7684\u90A3\u4EFD + \u6BCF\u6BB5\u6765\u81EA\u54EA\u4E2A\u6587\u4EF6\u3001\u90A3\u91CC\u4E00\u5171\u51E0\u6761\u3001\u6309\u653F\u7B56\u5E26\u4E86\u51E0\u6761(\u6539\u4E86\u6863\u6848 / \u65E5\u8BB0 / \u8BA1\u5212\u7ACB\u523B\u770B\u6548\u679C)\n cotutor replay <\u8001\u5E08> <job> [<\u65E5\u671F>] [--runtime <\u540D>] [--json] \u56DE\u653E\u4E00\u8F6E:\u540C\u4E00\u95EE\u6309\u73B0\u5728\u7684 vault \u4E0E\u63D0\u793A\u8BCD\u518D\u8DD1\u4E00\u904D(\u843D evals/,\u4E0D\u8FDB\u5B69\u5B50\u7684\u5BF9\u8BDD\u3001\u4E0D\u914D\u97F3),\u8DD1\u5B8C\u5E76\u6392\u6253\u5370\u4E0A\u4E0B\u6587\u5305 / \u8BB2\u7A3F / \u5361 / \u8BFB\u4E86\u4EC0\u4E48\u7684 diff\n cotutor compare <\u8001\u5E08> [<evalJob>] [<\u65E5\u671F>] [--json] \u518D\u770B\u4E00\u6B21\u56DE\u653E\u7684\u5BF9\u7167(\u4E0D\u7ED9 evalJob \u5C31\u5217\u8FD9\u5929\u56DE\u653E\u8FC7\u54EA\u4E9B)\n cotutor show <\u8001\u5E08> <job> [<\u65E5\u671F>] [--json] [--evals] [--workspace <dir>] \u770B\u4E00\u8F6E:\u95EE\u4E86\u4EC0\u4E48\u3001\u4E0A\u4E0B\u6587\u5305\u3001\u5F53\u65F6\u7684\u8001\u5E08\u6587\u4EF6\u4E0E\u6280\u80FD hash\u3001\u8BB2\u7A3F\u4E0E\u5361\u3001\u8BFB\u4E86\u4EC0\u4E48\u3001token \u4E0E\u7528\u65F6\u3001\u7ED9\u5BB6\u957F\u7684\u5C3E\u5DF4;--json \u662F\u5BB6\u957F\u7AEF\u300C\u770B\u539F\u6587\u300D\u540C\u4E00\u4EFD\u6570\u636E,\u7ED9\u7F16\u7A0B\u52A9\u624B\u5206\u6790\u7528\n cotutor trace <\u8001\u5E08> <job> [<\u65E5\u671F>] [--lane \u2026] [--workspace <dir>] \u56DE\u653E\u4E00\u8F6E\u7684\u4E8B\u4EF6(<\u65E5\u671F>.<job>.events.jsonl;\u6392\u67E5\u6628\u5929\u90A3\u8F6E\u7528)\n cotutor rate <\u8001\u5E08> <\u8BDD\u9898> <1-5> [--date <\u65E5\u671F>] \u7ED9\u4E00\u4E2A\u8BDD\u9898\u6253\u661F(\u4E0E\u5BB6\u957F\u7AEF\u540C\u4E00\u6761\u8DEF;\u2265 vault.keepScore \u7684\u8BDD\u9898\u8BB0\u8D26\u65F6\u6458\u8981\u624D\u8FDB\u65E5\u8BB0)\n cotutor bookkeep <\u8001\u5E08> [--date <\u65E5\u671F>] [--thread <\u8BDD\u9898>]... \u8BB0\u8D26:\u8FD9\u5929\u6BCF\u4E2A\u8FD8\u6CA1\u8BB0\u8FC7\u7684\u8BDD\u9898\u5404\u8D77\u4E00\u8F6E\u8BB0\u8D26\u4EFB\u52A1,\u8001\u5E08\u56DE\u300C## \u8BB0\u8D26\u300D,\u5E94\u7528\u5199\u8FDB vault \u7684\u65E5\u8BB0(\u8BDD\u9898\u540D\u3001\u5B69\u5B50\u95EE\u3001\u6458\u8981\u3001\u89C2\u5BDF)\n cotutor home check [--published] [--json] \u68C0\u67E5\u9996\u9875\u8349\u7A3F home/draft.md(\u6216\u5DF2\u53D1\u5E03\u7684\u90A3\u4EFD):\u5B69\u5B50\u4F1A\u770B\u5230\u54EA\u51E0\u5F20\u5361\u3001\u6BCF\u4F4D\u8001\u5E08\u7684\u6309\u94AE\u3001\u8981\u6539\u7684\u4E0E\u63D0\u9192(\u5E26\u884C\u53F7);\u4E0D\u82B1\u94B1\n cotutor home publish [--force] [--from <\u6587\u4EF6>] [--json] \u53D1\u5E03\u9996\u9875:\u68C0\u67E5 \u2192 \u7559\u5386\u53F2 home/history/<id>.md \u2192 \u5199 home/published.json(\u5B69\u5B50\u7AEF\u53EA\u8BFB\u5B83);\u6709\u8981\u6539\u7684\u4E0D\u53D1,--force \u4E22\u6389\u90A3\u51E0\u5F20\u7167\u53D1;--from \u53D1\u53E6\u4E00\u4EFD(\u5982\u5386\u53F2)\n cotutor home show [--json] \u73B0\u5728\u53D1\u5E03\u7684\u662F\u54EA\u4EFD\u3001\u51E0\u5929\u524D\u3001\u6BCF\u4E2A\u6309\u94AE\u88AB\u70B9\u4E86\u51E0\u6B21(\u54EA\u5929\u3001\u54EA\u4E2A\u8BDD\u9898)\n cotutor activity check [<\u8001\u5E08> [<\u540D>]] [--json] \u68C0\u67E5 activity \u6587\u4EF6(agents/<\u8001\u5E08>/activities/<\u540D>.md):\u5BB6\u957F\u586B\u7684\u3001\u5E94\u7528\u7B97\u7684\u3001\u5E94\u7528\u62E6\u7684\u3001\u88C1\u6BB5\u3001\u63D2\u69FD\u3001\u6B63\u6587\u7528\u7684\u5361\u3001\u7B2C\u4E00\u53E5\u4E0E\u6536(\u5E26\u884C\u53F7);\u4E0D\u82B1\u94B1\n cotutor activity eval <\u8001\u5E08> <\u540D> [--runs <n>] [--only <\u4F8B\u5B50>] [--kid-model <\u5343\u95EE\u6A21\u578B>] [--json] \u7167 <\u540D>.\u4F8B\u5B50.md \u8DD1:\u771F\u8001\u5E08 + \u6A21\u62DF\u5B69\u5B50(\u5343\u95EE,\u7F3A\u7701 qwen3.8-flash,\u6709 Token Plan \u8D70\u5B83)\u591A\u8F6E(\u7167\u5B9E\u65F6\u5199,\u540C\u5B69\u5B50\u7AEF),\u5224\u53EA\u770B\u677F\u4E66;\u4E00\u4E2A\u4F8B\u5B50\u8DD1 n \u6B21(\u7F3A\u7701 3)\u6B21\u6B21\u90FD\u8FC7\u624D\u7B97\u8FC7;\u843D evals/,\u753B\u5BB6\u662F\u5047\u7684;\u82B1\u94B1\n cotutor chalk <check|build|bake|snap|dub> <scene.ts|\u8BFE\u5305 id> [...] \u505A\u5C0F\u8BFE\u5802\u7684\u8BFE\u5305:check \u6821\u9A8C\u573A\u666F\u3001build \u51FA\u8BFE\u5305\u5230 bundles/\u3001bake \u70E4\u753B\u9762\u3001snap \u6BCF\u6B65\u622A\u56FE\u3001dub \u914D\u97F3(cotutor chalk \u770B\u5404\u81EA\u7684\u9009\u9879;bake / snap \u8981\u672C\u673A\u7684 Chrome)\n cotutor mock [--port <n>] [--scenario normal|nokey] [--delay <ms>] [--http] \u4E0D\u7ECF\u771F\u5B9E\u8001\u5E08\u4E0E\u767E\u70BC,\u7528\u56FA\u5B9A\u7684\u677F\u4E66 JSON \u8D77\u5B69\u5B50\u7AEF,\u6D4B\u524D\u7AEF\u4EA4\u4E92\u4E0E\u6E32\u67D3(\u4E0D\u9700\u8981 workspace;\u8001\u5E08\u90FD\u8D70\u5B9E\u65F6\u8001\u5E08\u9875,\u5047\u5634\u7167\u811A\u672C\u5FF5\u9759\u97F3\u7684\u58F0\u97F3;nokey = \u6CA1\u914D\u767E\u70BC,\u5B69\u5B50\u7AEF\u53EA\u51FA\u300C\u8001\u5E08\u4EEC\u8FD8\u6CA1\u5230\u300D)\n cotutor --version | --help\nworkspace\u89E3\u6790:--workspace > COTUTOR_WORKSPACE > cwd \u6216\u7956\u5148\u6709 cotutor.json > ~/.config/cotutor/config.json > ~/cotutor/ \u4E0B\u552F\u4E00\u7684\u5B69\u5B50\u76EE\u5F55\n\u8BD5\u7528:\u8981 workspace \u7684\u547D\u4EE4\u52A0 --sandbox,\u6362\u5230\u771F workspace \u65C1\u8FB9\u7684 .sandbox/<\u76EE\u5F55\u540D>/,\u8BD5\u7684\u5BF9\u8BDD\u4E0E vault \u7684\u5199\u5165\u4E0D\u8FDB\u5B69\u5B50\u7684\u8BB0\u5F55(\u7B2C\u4E00\u6B21\u81EA\u52A8\u5206:\u8BBE\u7F6E\u7167\u62F7,\u5BF9\u8BDD\u4E0E\u7167\u7247\u4E0D\u5E26,vault \u62F7\u4E00\u4EFD;\u6807\u9898\u524D\u52A0\u300C\u8BD5\u7528\u300D\u3001\u7AEF\u53E3 +1\u3001\u81EA\u52A8\u8BB0\u8D26\u5173\u6389);--sandbox --fresh \u91CD\u65B0\u5206\n";
|
package/dist/app/cli/usage.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
export const USAGE = `用法:
|
|
3
3
|
cotutor init <slug> [--dir <path>] [--name <孩子名>] [--grade <1-6>] [--port <n>] 建 ~/cotutor/<slug>/ 骨架(幂等补缺;默认二年级教学包;端口在这里选一次写进 cotutor.json:443 优先,不行就 8443、8445…,以后不变)
|
|
4
4
|
cotutor grades [--json] 列出本包可安装的年级教学包
|
|
5
|
-
cotutor doctor [--workspace <dir>] [--json] [--live] 逐项体检;--live 真连一次百炼实时模型、真合成一句配音(花一分钱)把 API 层的坑摆出来(真跑老师用 cotutor send);--json 里不过的项带 cmd(能直接跑)或 human(要人来)
|
|
5
|
+
cotutor doctor [--workspace <dir>] [--json] [--live] [--fix] 逐项体检;--live 真连一次百炼实时模型、真合成一句配音(花一分钱)把 API 层的坑摆出来(真跑老师用 cotutor send);--json 里不过的项带 cmd(能直接跑)或 human(要人来);--fix 把带 cmd 的一条条修掉,要人来的留着
|
|
6
|
+
cotutor report [--workspace <dir>] 诊断信息:版本、系统、服务、体检没过的、最近报错的原因;不含孩子的对话、照片、录音、档案和 key,名字和 IP 已抹掉,先看再发
|
|
7
|
+
cotutor backup [list | restore <名字> [--yes]] [--workspace <dir>] 备份到 workspace 旁边的 .backups/:不含录音;服务开着时每天一份(留 14 份),cotutor upgrade 前一份(留 5 份);没变的文件硬链接,不占第二份地方;restore 先把现在的也备一份再拷回去,不加 --yes 只说会做什么
|
|
6
8
|
cotutor update [--anyway] 升级一条命令管到底:问 npm 有没有新版本,换程序,每个孩子更新出厂文件、重起服务和图标;孩子在通话时不动(--anyway 才动),npm 没成服务不动
|
|
7
9
|
cotutor upgrade [--workspace <dir>] [--force <老师>]... 老师文件、activity 与 skill 换新版:没改过的直接换,改过的只报 diff(--force <老师> 或 --force <老师>/<名> 才覆盖,原文留 .bak)
|
|
8
10
|
cotutor upgrade --config [--dry-run] [--workspace <dir>] cotutor.json 补缺:新出厂老师 / 运行时 / 命令模板旗标(只加缺的,你改过的值不动)
|
package/dist/app/http/access.js
CHANGED
|
@@ -23,7 +23,7 @@ export const PIN_RE = /^\d{4,8}$/;
|
|
|
23
23
|
const MAX_FAILS = 5;
|
|
24
24
|
const LOCK_MS = 60_000;
|
|
25
25
|
/** 孩子端的路径:设了 PIN 也开;别的都要凭证。改孩子端加了新接口要来这里登记(tests/access.test.ts 盯着孩子端页面源码里用到的每条) */
|
|
26
|
-
const KID_EXACT = new Set(['/', '/manifest.webmanifest', '/favicon.svg', '/favicon-32.png', '/favicon.ico', '/api/health', '/api/auth', '/dictation', '/dictation/', '/talk', '/talk/', '/api/dictation', '/api/talk']);
|
|
26
|
+
const KID_EXACT = new Set(['/', '/sw.js', '/manifest.webmanifest', '/favicon.svg', '/favicon-32.png', '/favicon.ico', '/api/health', '/api/auth', '/dictation', '/dictation/', '/talk', '/talk/', '/api/dictation', '/api/talk']);
|
|
27
27
|
const KID_PREFIX = ['/kid/', '/stage/', '/api/kid/', '/api/audio/', '/api/bundles/', '/api/dictation/', '/api/talk/', '/api/live/'];
|
|
28
28
|
export function isKidPath(p) {
|
|
29
29
|
return KID_EXACT.has(p) || KID_PREFIX.some((x) => p.startsWith(x)) || /^\/icon-\d{2,4}\.png$/.test(p);
|
package/dist/app/http/app.js
CHANGED
|
@@ -37,6 +37,7 @@ import { knownLatest, knownNotes } from "../machine/update.js";
|
|
|
37
37
|
import { isNewer } from "../../shared/upgrade.js";
|
|
38
38
|
import { qrPage } from "../../ui/parent/qr-page.js";
|
|
39
39
|
import { kidPage } from "../../ui/kid/kid-page.js";
|
|
40
|
+
import { KID_SW_JS } from "../../ui/kid/offline-sw.js";
|
|
40
41
|
import { parentPage } from "../../ui/parent/parent-page.js";
|
|
41
42
|
import { kidHomeView } from "../../runtime/home/store.js";
|
|
42
43
|
import { photosOf } from "../../runtime/kid-input.js";
|
|
@@ -1070,6 +1071,9 @@ export async function route(method, path, ctx, body, who = 'local', client = {})
|
|
|
1070
1071
|
// 主屏幕(iPad「添加到主屏幕」):清单与图标都按标题现生成,没有静态资源
|
|
1071
1072
|
if (p === '/manifest.webmanifest')
|
|
1072
1073
|
return { status: 200, json: webManifest(ws.config.title), contentType: 'application/manifest+json; charset=utf-8' };
|
|
1074
|
+
// 孩子端的离线页(offline-sw.ts):平板打开时电脑关了、睡着了,给「老师们还没到」,不给浏览器的报错
|
|
1075
|
+
if (p === '/sw.js')
|
|
1076
|
+
return { status: 200, body: Buffer.from(KID_SW_JS), contentType: 'text/javascript; charset=utf-8', cacheControl: 'no-cache' };
|
|
1073
1077
|
const icon = /^\/icon-(\d{2,4})\.png$/.exec(p);
|
|
1074
1078
|
if (icon) {
|
|
1075
1079
|
const n = Number(icon[1]);
|
|
@@ -25,6 +25,8 @@ import { wordEmoji } from "../../ui/kid/word-emoji.js";
|
|
|
25
25
|
import { shownAvatar } from "./avatars.js";
|
|
26
26
|
import { qrNotes, qrSvg } from "../../ui/shared/qr-svg.js";
|
|
27
27
|
import { readDevices } from "../../runtime/journal/kid-log.js";
|
|
28
|
+
import { parentSetupRoute } from "./parent-setup.js";
|
|
29
|
+
import { rootCaFile } from "../cli/root-ca.js";
|
|
28
30
|
const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
29
31
|
const bad = (message) => ({ status: 400, json: { error: 'bad_request', message } });
|
|
30
32
|
/** 家长端列的老师:有脸的(-tutor 结尾)、开着的;藏着的也列(家长要能把他放回首页) */
|
|
@@ -153,13 +155,20 @@ async function kidQr(ctx) {
|
|
|
153
155
|
const devices = Object.values(await readDevices(ctx.ws));
|
|
154
156
|
const last = devices.map((d) => d.last).sort().at(-1) ?? null;
|
|
155
157
|
const parent = { url: `${info.name}parent`, ip: info.ip ? `${info.ip}parent` : null };
|
|
158
|
+
// 装根证书:走同一个端口的 http 那一面(serve.ts 的 plainHandler),平板这时还不信任 https;端口照写,http 缺省是 80
|
|
159
|
+
const caUrl = (u) => { const x = new URL(u); return `http://${x.hostname}:${x.port || '443'}/ca.crt`; };
|
|
160
|
+
const ca = info.https && (await rootCaFile()) ? { url: caUrl(info.name), ip: info.ip ? caUrl(info.ip) : null } : null;
|
|
156
161
|
return {
|
|
157
162
|
ready: true, https: info.https, url: info.name, svg: qrSvg(info.name), ip: info.ip, svgIp: info.ip ? qrSvg(info.ip) : null,
|
|
158
163
|
parent: { ...parent, svg: qrSvg(parent.url), svgIp: parent.ip ? qrSvg(parent.ip) : null },
|
|
164
|
+
ca: ca ? { ...ca, svg: qrSvg(ca.url), svgIp: ca.ip ? qrSvg(ca.ip) : null } : null,
|
|
159
165
|
notes: qrNotes(info.https).slice(1), devices: devices.length, last,
|
|
160
166
|
};
|
|
161
167
|
}
|
|
162
168
|
export async function parentHomeRoute(method, p, ctx, body) {
|
|
169
|
+
const care = await parentSetupRoute(method, p, ctx, body);
|
|
170
|
+
if (care)
|
|
171
|
+
return care;
|
|
163
172
|
if (p === '/api/parent/home' && method === 'GET')
|
|
164
173
|
return { status: 200, json: await parentHome(ctx) };
|
|
165
174
|
if (p === '/api/parent/qr' && method === 'GET')
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AppContext, RouteResult } from './app.ts';
|
|
2
|
+
export interface SetupStep {
|
|
3
|
+
id: string;
|
|
4
|
+
done: boolean;
|
|
5
|
+
/** true = 不做孩子用不了;false = 建议 */
|
|
6
|
+
required: boolean;
|
|
7
|
+
title: string;
|
|
8
|
+
detail: string;
|
|
9
|
+
/** 要在这台电脑的终端里跑的 */
|
|
10
|
+
commands?: string[];
|
|
11
|
+
/** 这一步直接在页面上选年级 */
|
|
12
|
+
grades?: boolean;
|
|
13
|
+
link?: {
|
|
14
|
+
href: string;
|
|
15
|
+
text: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export declare function setupSteps(ctx: AppContext): Promise<SetupStep[]>;
|
|
19
|
+
export declare function parentSetupRoute(method: string, p: string, ctx: AppContext, body: unknown): Promise<RouteResult | null>;
|