clawed 2.5.2__tar.gz → 2.5.3__tar.gz
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.
- {clawed-2.5.2 → clawed-2.5.3}/PKG-INFO +1 -1
- {clawed-2.5.2 → clawed-2.5.3}/clawed/__init__.py +1 -1
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/generate.py +35 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/compile_game.py +17 -10
- {clawed-2.5.2 → clawed-2.5.3}/clawed/export_pptx.py +77 -43
- clawed-2.5.3/clawed/review_output.py +253 -0
- {clawed-2.5.2 → clawed-2.5.3}/pyproject.toml +1 -1
- {clawed-2.5.2 → clawed-2.5.3}/.gitignore +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/LICENSE +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/README.md +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/__main__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/_legacy_gateway.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/approvals.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/autonomy.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/context.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/core.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/custom_tools.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/drive/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/drive/auth.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/drive/client.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/fake_llm.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/loop.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/memory/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/memory/curriculum.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/memory/curriculum_kb.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/memory/embeddings.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/memory/episodes.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/memory/identity.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/memory/loader.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/memory/preferences.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/planner.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/prompt.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/scheduler.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/base.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/configure_profile.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/curriculum_map.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/drive_create_doc.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/drive_create_slides.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/drive_list.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/drive_organize.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/drive_read.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/drive_upload.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/export_document.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/gap_analysis.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/generate_assessment.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/generate_lesson.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/generate_lesson_bundle.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/generate_materials.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/generate_unit.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/ingest_materials.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/parent_comm.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/read_heartbeat.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/read_workspace.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/request_approval.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/schedule_task.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/search_lessons.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/search_my_materials.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/search_standards.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/student_insights.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/sub_packet.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/switch_model.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/agent_core/tools/update_soul.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/analytics.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/deps.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/routes/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/routes/chat.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/routes/export.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/routes/feedback.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/routes/gateway_chat.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/routes/generate.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/routes/ingest.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/routes/lessons.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/routes/school.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/routes/settings.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/routes/tools.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/server.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/static/app.js +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/static/style.css +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/static/widget.js +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/templates/analytics.html +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/templates/base.html +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/templates/dashboard.html +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/templates/generate.html +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/templates/index.html +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/templates/lesson.html +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/templates/profile.html +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/templates/settings.html +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/templates/stats.html +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/api/templates/students.html +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/assessment.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/asset_registry.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/async_utils.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/auth/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/auth/google_auth.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/bot_state.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/chat.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/cli.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/cli_chat.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/_helpers.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/bot.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/config.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/config_llm.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/config_profile.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/export.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/game.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/generate_assessment.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/generate_unit.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/queue.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/schedule_cmd.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/sub.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/train.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/commands/workspace_cmd.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/compile_slides.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/compile_student.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/compile_teacher.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/config.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/corpus.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/curriculum_map.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/database.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/demo/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/demo/demo_assessment.json +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/demo/demo_formative_assessment.json +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/demo/demo_lesson_materials.json +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/demo/demo_lesson_science_g6.json +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/demo/demo_lesson_social_studies_g8.json +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/demo/demo_master_content.json +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/demo/demo_pacing_guide.json +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/demo/demo_quiz.json +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/demo/demo_rubric.json +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/demo/demo_unit_plan.json +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/demo/demo_year_map.json +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/differentiation.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/doc_export.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/drive.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/evaluation.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/export_docx.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/export_handout.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/export_markdown.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/export_pdf.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/export_templates.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/export_theme.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/exporter.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/failure_codes.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/feedback.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/formats/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/formats/flipchart.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/formats/notebook.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/formats/xbk.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/gateway.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/gateway_response.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/generation.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/generation_report.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/handlers/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/handlers/export.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/handlers/feedback.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/handlers/gaps.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/handlers/generate.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/handlers/ingest.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/handlers/misc.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/handlers/onboard.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/handlers/schedule.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/handlers/standards.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/hermes_plugin.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/image_pipeline.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/improver.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/ingestor.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/io.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/lesson.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/llm.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/master_content.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/materials.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/mcp_server.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/memory_engine.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/model_router.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/models.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/multi_agent.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/onboarding.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/openclaw_plugin.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/parent_comm.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/persona.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/persona_evolution.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/planner.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/504_accommodations.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/admin_lesson_plan.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/assessment.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/curriculum_gaps.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/dbq_assessment.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/differentiation.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/formative_assessment.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/iep_modification.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/lesson_plan.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/master_content.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/multi_agent_researcher.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/multi_agent_reviewer.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/pacing_guide.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/parent_note.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/persona_extract.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/quiz.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/rubric.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/student_packet.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/sub_packet.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/summative_assessment.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/tiered_assignments.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/unit_plan.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/worksheet.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/prompts/year_map.txt +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/quality.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/reading_report.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/router.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/sanitize.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/scheduler.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/school.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/search.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/art.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/base.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/computer_science.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/ela.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/foreign_language.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/history.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/library.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/math.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/music.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/physical_education.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/science.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/social_studies.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/skills/special_education.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/slide_images.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/standards.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/state.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/state_standards.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/student_bot.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/student_cli.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/student_telegram_bot.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/sub_packet.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/task_queue.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/templates_lib.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/tools.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/transports/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/transports/cli.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/transports/hermes.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/transports/openclaw.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/transports/student_telegram.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/transports/telegram.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/transports/web.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/tui.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/tui_chat.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/validation.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/voice.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/voice_check.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/clawed/workspace.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/eduagent/__init__.py +0 -0
- {clawed-2.5.2 → clawed-2.5.3}/eduagent/_compat.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clawed
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.3
|
|
4
4
|
Summary: Free AI lesson planner for teachers. Learns your teaching voice from your curriculum files and generates complete lesson packages. Open source, runs locally, no subscription.
|
|
5
5
|
Project-URL: Homepage, https://sirhanmacx.github.io/Claw-ED
|
|
6
6
|
Project-URL: Documentation, https://github.com/SirhanMacx/Claw-ED#readme
|
|
@@ -17,7 +17,7 @@ if hasattr(sys.stderr, "reconfigure"):
|
|
|
17
17
|
except Exception:
|
|
18
18
|
pass
|
|
19
19
|
|
|
20
|
-
__version__ = "2.5.
|
|
20
|
+
__version__ = "2.5.3"
|
|
21
21
|
__author__ = "Jon Maccarello & Claw-ED contributors"
|
|
22
22
|
__description__ = "Personal AI teaching agent. Learns your voice, works while you sleep."
|
|
23
23
|
|
|
@@ -501,6 +501,41 @@ def lesson(
|
|
|
501
501
|
console.print(f"\n[green]Lesson saved:[/green] {json_path}")
|
|
502
502
|
if export_path:
|
|
503
503
|
console.print(f"[green]Exported:[/green] {export_path}")
|
|
504
|
+
|
|
505
|
+
# ── Quality review ────────────────────────────────────────────
|
|
506
|
+
if export_path:
|
|
507
|
+
try:
|
|
508
|
+
from clawed.review_output import (
|
|
509
|
+
review_docx,
|
|
510
|
+
review_pptx,
|
|
511
|
+
)
|
|
512
|
+
export_p = Path(export_path) if not isinstance(export_path, Path) else export_path
|
|
513
|
+
if export_p.suffix == ".pptx":
|
|
514
|
+
review = review_pptx(export_p)
|
|
515
|
+
elif export_p.suffix == ".docx":
|
|
516
|
+
review = review_docx(export_p)
|
|
517
|
+
else:
|
|
518
|
+
review = None
|
|
519
|
+
if review:
|
|
520
|
+
if review.passed:
|
|
521
|
+
console.print(
|
|
522
|
+
f" Quality: [green]{review.score:.1f}/10[/green]"
|
|
523
|
+
)
|
|
524
|
+
else:
|
|
525
|
+
console.print(
|
|
526
|
+
f" Quality: [red]{review.score:.1f}/10 "
|
|
527
|
+
f"({len(review.issues)} issues)[/red]"
|
|
528
|
+
)
|
|
529
|
+
for issue in review.issues[:5]:
|
|
530
|
+
_sev = {"critical": "red", "major": "yellow", "minor": "dim"}
|
|
531
|
+
console.print(
|
|
532
|
+
f" [{_sev.get(issue['severity'], 'dim')}]"
|
|
533
|
+
f"{issue['location']}: "
|
|
534
|
+
f"{issue['description']}[/{_sev.get(issue['severity'], 'dim')}]"
|
|
535
|
+
)
|
|
536
|
+
except Exception:
|
|
537
|
+
pass
|
|
538
|
+
|
|
504
539
|
console.print(
|
|
505
540
|
Panel(
|
|
506
541
|
f"[bold]Objective:[/bold] {daily.objective}\n"
|
|
@@ -48,12 +48,24 @@ without a <head> or <body> wrapper.
|
|
|
48
48
|
- The game MUST work on phones, tablets, Chromebooks (responsive, touch).
|
|
49
49
|
- The game MUST be genuinely FUN — not a boring quiz with buttons.
|
|
50
50
|
- Include scoring, feedback, progression (levels or rounds).
|
|
51
|
-
- Use modern CSS (gradients, animations, shadows) for visual polish.
|
|
52
51
|
- Include a start screen with the lesson title and instructions.
|
|
53
52
|
- Include an end screen with score and "play again" button.
|
|
54
53
|
- ALL educational content must be embedded as data in the JS.
|
|
55
54
|
- Add a small footer: "Made with Claw-ED — github.com/SirhanMacx/Claw-ED"
|
|
56
55
|
|
|
56
|
+
VISUALS — DO NOT USE IMAGE FILES. Create ALL visuals programmatically:
|
|
57
|
+
- Use Three.js for immersive 3D scenes themed to the lesson content. \
|
|
58
|
+
Age of Exploration = 3D ocean with ships. Civil War = battlefields. \
|
|
59
|
+
Renaissance = marble halls. Science = molecular structures.
|
|
60
|
+
- Use CSS gradients, animations, box-shadows, and transforms for 2D polish.
|
|
61
|
+
- Use HTML Canvas for custom diagrams, maps, or illustrations.
|
|
62
|
+
- Use CSS shapes and emoji for icons — never <img> tags.
|
|
63
|
+
- Use SVG inline for any detailed graphics (maps, diagrams, symbols).
|
|
64
|
+
- The game should feel IMMERSIVE — like the student is inside the topic, \
|
|
65
|
+
not reading a quiz. 3D environments, particle effects, ambient animation.
|
|
66
|
+
- Go ABOVE AND BEYOND visually. This should look like a real game, not a \
|
|
67
|
+
school worksheet with buttons.
|
|
68
|
+
|
|
57
69
|
WHAT MAKES A GREAT LEARNING GAME:
|
|
58
70
|
- The mechanic TEACHES, not just tests. Students learn through the gameplay.
|
|
59
71
|
- Wrong answers give feedback that explains WHY it's wrong.
|
|
@@ -338,15 +350,10 @@ async def compile_game(
|
|
|
338
350
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
339
351
|
|
|
340
352
|
config = config or AppConfig.load()
|
|
341
|
-
#
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if anthropic_key:
|
|
346
|
-
config.provider = LLMProvider.ANTHROPIC
|
|
347
|
-
config.anthropic_model = "claude-opus-4-6"
|
|
348
|
-
else:
|
|
349
|
-
config = route_model("game_generate", config)
|
|
353
|
+
# Route to DEEP tier within the teacher's chosen provider.
|
|
354
|
+
# Teacher picked Ollama? Gets their best Ollama model.
|
|
355
|
+
# Teacher picked Anthropic? Gets Opus. Their choice.
|
|
356
|
+
config = route_model("game_generate", config)
|
|
350
357
|
client = LLMClient(config)
|
|
351
358
|
|
|
352
359
|
# Build the game generation prompt
|
|
@@ -829,7 +829,10 @@ def export_lesson_pptx(
|
|
|
829
829
|
for item in list_items[:max_bullets]:
|
|
830
830
|
item = item.strip()
|
|
831
831
|
if len(item) > 20:
|
|
832
|
-
|
|
832
|
+
# Truncate at word boundary, not mid-word
|
|
833
|
+
if len(item) > 140:
|
|
834
|
+
item = item[:140].rsplit(' ', 1)[0] + '...'
|
|
835
|
+
bullets.append(item)
|
|
833
836
|
if bullets:
|
|
834
837
|
return bullets
|
|
835
838
|
# Fallback: first N sentences that are short enough to be readable
|
|
@@ -970,37 +973,65 @@ def export_lesson_pptx(
|
|
|
970
973
|
|
|
971
974
|
_add_footer(slide, slide_num[0])
|
|
972
975
|
|
|
973
|
-
# ── Source
|
|
974
|
-
#
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
976
|
+
# ── Primary Source slides (from structured lesson data) ─────
|
|
977
|
+
# Use lesson.primary_sources (PrimarySourceDocument objects)
|
|
978
|
+
# instead of regex-extracting fragments from DI prose.
|
|
979
|
+
source_img_idx = 3
|
|
980
|
+
primary_sources = getattr(lesson, "primary_sources", []) or []
|
|
981
|
+
for ps in primary_sources[:3]:
|
|
982
|
+
# Get source fields — handle both dict and object
|
|
983
|
+
if isinstance(ps, dict):
|
|
984
|
+
ps_title = ps.get("title", "") or ps.get("document_label", "")
|
|
985
|
+
ps_author = ps.get("author", "")
|
|
986
|
+
ps_date = ps.get("date", "")
|
|
987
|
+
ps_context = ps.get("context", "")
|
|
988
|
+
ps_text = ps.get("full_text", "") or ps.get("text", "")
|
|
989
|
+
ps_questions = ps.get("analysis_questions", [])
|
|
990
|
+
else:
|
|
991
|
+
ps_title = getattr(ps, "title", "") or getattr(ps, "document_label", "")
|
|
992
|
+
ps_author = getattr(ps, "author", "")
|
|
993
|
+
ps_date = getattr(ps, "date", "")
|
|
994
|
+
ps_context = getattr(ps, "context", "")
|
|
995
|
+
ps_text = getattr(ps, "full_text", "")
|
|
996
|
+
ps_questions = getattr(ps, "analysis_questions", [])
|
|
997
|
+
|
|
998
|
+
if not ps_text and not ps_title:
|
|
999
|
+
continue
|
|
1000
|
+
|
|
987
1001
|
slide = _next_slide()
|
|
988
1002
|
_tinted_bg(slide, theme["accent"])
|
|
989
1003
|
|
|
990
|
-
# "Source
|
|
1004
|
+
# "Primary Source" badge with title
|
|
1005
|
+
badge_text = ps_title or "Primary Source"
|
|
991
1006
|
badge = _rounded_card(
|
|
992
1007
|
slide,
|
|
993
|
-
Inches(0.8), Inches(0.
|
|
994
|
-
Inches(
|
|
1008
|
+
Inches(0.8), Inches(0.5),
|
|
1009
|
+
Inches(5.0), Inches(0.7),
|
|
995
1010
|
theme["primary"],
|
|
996
1011
|
)
|
|
997
1012
|
badge_tf = badge.text_frame
|
|
998
1013
|
badge_tf.paragraphs[0].alignment = PP_ALIGN.CENTER
|
|
999
1014
|
run = badge_tf.paragraphs[0].add_run()
|
|
1000
|
-
run.text =
|
|
1001
|
-
_set_text_props(run,
|
|
1015
|
+
run.text = _clean_slide_text(badge_text)[:80]
|
|
1016
|
+
_set_text_props(run, 20, theme["text_light"], bold=True)
|
|
1017
|
+
|
|
1018
|
+
# Attribution line (author + date)
|
|
1019
|
+
attribution = ""
|
|
1020
|
+
if ps_author:
|
|
1021
|
+
attribution = ps_author
|
|
1022
|
+
if ps_date:
|
|
1023
|
+
attribution += f", {ps_date}" if attribution else ps_date
|
|
1024
|
+
if attribution:
|
|
1025
|
+
tb_attr = slide.shapes.add_textbox(
|
|
1026
|
+
Inches(0.8), Inches(1.3), Inches(8.0), Inches(0.4),
|
|
1027
|
+
)
|
|
1028
|
+
p_attr = tb_attr.text_frame.paragraphs[0]
|
|
1029
|
+
run_attr = p_attr.add_run()
|
|
1030
|
+
run_attr.text = f"— {_clean_slide_text(attribution)}"
|
|
1031
|
+
_set_text_props(run_attr, 14, theme["text_dim"])
|
|
1032
|
+
run_attr.font.italic = True
|
|
1002
1033
|
|
|
1003
|
-
#
|
|
1034
|
+
# Source text — the actual excerpt
|
|
1004
1035
|
src_img = images.get(f"entity_{source_img_idx}")
|
|
1005
1036
|
text_width = slide_w - Inches(2.0)
|
|
1006
1037
|
if src_img:
|
|
@@ -1008,40 +1039,43 @@ def export_lesson_pptx(
|
|
|
1008
1039
|
_add_sidebar_image(slide, src_img)
|
|
1009
1040
|
source_img_idx += 1
|
|
1010
1041
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
if len(clean_quote) > 350:
|
|
1015
|
-
clean_quote = clean_quote[:347].rsplit(" ", 1)[0] + "…"
|
|
1042
|
+
clean_text = _clean_slide_text(ps_text.strip())
|
|
1043
|
+
if len(clean_text) > 400:
|
|
1044
|
+
clean_text = clean_text[:397].rsplit(" ", 1)[0] + "…"
|
|
1016
1045
|
|
|
1017
|
-
|
|
1046
|
+
text_font = 18 if len(clean_text) < 200 else 15 if len(clean_text) < 350 else 13
|
|
1018
1047
|
|
|
1048
|
+
y_start = Inches(1.8) if attribution else Inches(1.5)
|
|
1019
1049
|
tb = slide.shapes.add_textbox(
|
|
1020
|
-
Inches(1.
|
|
1050
|
+
Inches(1.0), y_start, text_width, Inches(3.5),
|
|
1021
1051
|
)
|
|
1022
1052
|
tf = tb.text_frame
|
|
1023
1053
|
tf.word_wrap = True
|
|
1024
1054
|
p = tf.paragraphs[0]
|
|
1025
1055
|
p.alignment = PP_ALIGN.LEFT
|
|
1026
|
-
p.line_spacing = Pt(
|
|
1056
|
+
p.line_spacing = Pt(text_font + 7)
|
|
1027
1057
|
run = p.add_run()
|
|
1028
|
-
run.text = f"\u201c{
|
|
1029
|
-
_set_text_props(run,
|
|
1058
|
+
run.text = f"\u201c{clean_text}\u201d" if clean_text else "(Source text not available)"
|
|
1059
|
+
_set_text_props(run, text_font, theme["text_dark"])
|
|
1030
1060
|
run.font.italic = True
|
|
1031
1061
|
|
|
1032
|
-
#
|
|
1033
|
-
if
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
tb_attr = slide.shapes.add_textbox(
|
|
1038
|
-
Inches(1.5), slide_h - Inches(1.6), text_width, Inches(0.7),
|
|
1062
|
+
# Analysis questions (if any)
|
|
1063
|
+
if ps_questions:
|
|
1064
|
+
q_y = slide_h - Inches(2.0)
|
|
1065
|
+
tb_q = slide.shapes.add_textbox(
|
|
1066
|
+
Inches(1.0), q_y, slide_w - Inches(2.0), Inches(1.2),
|
|
1039
1067
|
)
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1068
|
+
tf_q = tb_q.text_frame
|
|
1069
|
+
tf_q.word_wrap = True
|
|
1070
|
+
for qi, question in enumerate(ps_questions[:2]):
|
|
1071
|
+
p_q = tf_q.paragraphs[0] if qi == 0 else tf_q.add_paragraph()
|
|
1072
|
+
p_q.space_before = Pt(4)
|
|
1073
|
+
run_q = p_q.add_run()
|
|
1074
|
+
q_text = _clean_slide_text(question)
|
|
1075
|
+
if len(q_text) > 100:
|
|
1076
|
+
q_text = q_text[:97].rsplit(" ", 1)[0] + "…"
|
|
1077
|
+
run_q.text = f"Q{qi+1}: {q_text}"
|
|
1078
|
+
_set_text_props(run_q, 14, theme["text_dark"], bold=True)
|
|
1045
1079
|
|
|
1046
1080
|
_add_footer(slide, slide_num[0])
|
|
1047
1081
|
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Post-generation output review agent.
|
|
2
|
+
|
|
3
|
+
Checks every generated output (PPTX, DOCX, HTML game) for quality
|
|
4
|
+
issues before delivery to the teacher. Catches:
|
|
5
|
+
- Truncated/nonsensical text
|
|
6
|
+
- Images not matching slide content
|
|
7
|
+
- Answer keys visible in student materials
|
|
8
|
+
- Overcrowded slides
|
|
9
|
+
- Empty or incomplete sections
|
|
10
|
+
- Text starting mid-sentence
|
|
11
|
+
|
|
12
|
+
If issues are found, returns a structured report. The caller decides
|
|
13
|
+
whether to regenerate or deliver with warnings.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import re
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OutputReview:
|
|
27
|
+
"""Quality review results for a generated output."""
|
|
28
|
+
|
|
29
|
+
def __init__(self) -> None:
|
|
30
|
+
self.issues: list[dict[str, Any]] = []
|
|
31
|
+
self.score: float = 10.0 # Start perfect, deduct for issues
|
|
32
|
+
|
|
33
|
+
def add_issue(
|
|
34
|
+
self, severity: str, location: str, description: str
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Add a quality issue. Severity: critical, major, minor."""
|
|
37
|
+
deductions = {"critical": 3.0, "major": 1.5, "minor": 0.5}
|
|
38
|
+
self.issues.append({
|
|
39
|
+
"severity": severity,
|
|
40
|
+
"location": location,
|
|
41
|
+
"description": description,
|
|
42
|
+
})
|
|
43
|
+
self.score = max(0.0, self.score - deductions.get(severity, 0.5))
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def passed(self) -> bool:
|
|
47
|
+
"""Output passes review if score >= 6.0 and no critical issues."""
|
|
48
|
+
has_critical = any(i["severity"] == "critical" for i in self.issues)
|
|
49
|
+
return self.score >= 6.0 and not has_critical
|
|
50
|
+
|
|
51
|
+
def summary(self) -> str:
|
|
52
|
+
"""Human-readable summary."""
|
|
53
|
+
if not self.issues:
|
|
54
|
+
return f"PASSED ({self.score:.1f}/10) — no issues found"
|
|
55
|
+
lines = [f"{'PASSED' if self.passed else 'FAILED'} ({self.score:.1f}/10)"]
|
|
56
|
+
for issue in self.issues:
|
|
57
|
+
icon = {"critical": "X", "major": "!", "minor": "~"}
|
|
58
|
+
lines.append(
|
|
59
|
+
f" [{icon.get(issue['severity'], '?')}] "
|
|
60
|
+
f"{issue['location']}: {issue['description']}"
|
|
61
|
+
)
|
|
62
|
+
return "\n".join(lines)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def review_pptx(pptx_path: Path) -> OutputReview:
|
|
66
|
+
"""Review a generated PPTX for quality issues."""
|
|
67
|
+
from pptx import Presentation
|
|
68
|
+
|
|
69
|
+
review = OutputReview()
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
prs = Presentation(str(pptx_path))
|
|
73
|
+
except Exception as e:
|
|
74
|
+
review.add_issue("critical", "file", f"Cannot open PPTX: {e}")
|
|
75
|
+
return review
|
|
76
|
+
|
|
77
|
+
for i, slide in enumerate(prs.slides):
|
|
78
|
+
slide_num = i + 1
|
|
79
|
+
loc = f"Slide {slide_num}"
|
|
80
|
+
|
|
81
|
+
# Collect all text from the slide
|
|
82
|
+
texts = []
|
|
83
|
+
has_image = False
|
|
84
|
+
for shape in slide.shapes:
|
|
85
|
+
if shape.has_text_frame:
|
|
86
|
+
for para in shape.text_frame.paragraphs:
|
|
87
|
+
t = para.text.strip()
|
|
88
|
+
if t:
|
|
89
|
+
texts.append(t)
|
|
90
|
+
if shape.shape_type == 13:
|
|
91
|
+
has_image = True
|
|
92
|
+
|
|
93
|
+
all_text = " ".join(texts)
|
|
94
|
+
|
|
95
|
+
# Check: empty slide (no text at all)
|
|
96
|
+
if not texts or (len(texts) == 1 and len(texts[0]) < 5):
|
|
97
|
+
review.add_issue("major", loc, "Slide is empty or has minimal content")
|
|
98
|
+
|
|
99
|
+
# Check: text starts mid-sentence (fragment)
|
|
100
|
+
for t in texts:
|
|
101
|
+
if t and len(t) > 10:
|
|
102
|
+
first_char = t[0]
|
|
103
|
+
if first_char.islower() or first_char in ("'", "\u2019"):
|
|
104
|
+
review.add_issue(
|
|
105
|
+
"major", loc,
|
|
106
|
+
f"Text starts mid-sentence: '{t[:50]}...'"
|
|
107
|
+
)
|
|
108
|
+
break
|
|
109
|
+
|
|
110
|
+
# Check: truncated text (ends with ... or ellipsis mid-word)
|
|
111
|
+
for t in texts:
|
|
112
|
+
if t.endswith("...") or t.endswith("\u2026"):
|
|
113
|
+
# Check if truncation is mid-word
|
|
114
|
+
before_ellipsis = t.rstrip(".\u2026 ")
|
|
115
|
+
if before_ellipsis and before_ellipsis[-1].isalpha():
|
|
116
|
+
review.add_issue(
|
|
117
|
+
"minor", loc,
|
|
118
|
+
f"Text truncated mid-word: '...{t[-40:]}'"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Check: too much text on one slide (>500 chars)
|
|
122
|
+
if len(all_text) > 600:
|
|
123
|
+
review.add_issue(
|
|
124
|
+
"major", loc,
|
|
125
|
+
f"Overcrowded slide ({len(all_text)} chars) — "
|
|
126
|
+
f"should be split across multiple slides"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Check: answer keys visible
|
|
130
|
+
answer_patterns = [
|
|
131
|
+
r"\(Answer:\s*[^)]+\)",
|
|
132
|
+
r"\(answer:\s*[^)]+\)",
|
|
133
|
+
r"ANSWER KEY",
|
|
134
|
+
r"Answer:\s+\w+",
|
|
135
|
+
]
|
|
136
|
+
for pattern in answer_patterns:
|
|
137
|
+
if re.search(pattern, all_text):
|
|
138
|
+
review.add_issue(
|
|
139
|
+
"critical", loc,
|
|
140
|
+
"Answer key visible in student-facing slide"
|
|
141
|
+
)
|
|
142
|
+
break
|
|
143
|
+
|
|
144
|
+
return review
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def review_docx(docx_path: Path) -> OutputReview:
|
|
148
|
+
"""Review a generated DOCX handout for quality issues."""
|
|
149
|
+
from docx import Document
|
|
150
|
+
|
|
151
|
+
review = OutputReview()
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
doc = Document(str(docx_path))
|
|
155
|
+
except Exception as e:
|
|
156
|
+
review.add_issue("critical", "file", f"Cannot open DOCX: {e}")
|
|
157
|
+
return review
|
|
158
|
+
|
|
159
|
+
all_text = ""
|
|
160
|
+
for para in doc.paragraphs:
|
|
161
|
+
text = para.text.strip()
|
|
162
|
+
all_text += text + " "
|
|
163
|
+
|
|
164
|
+
# Check: answer keys visible
|
|
165
|
+
if re.search(
|
|
166
|
+
r"\(Answer:\s*[^)]+\)|\(answer:\s*[^)]+\)|ANSWER KEY",
|
|
167
|
+
text,
|
|
168
|
+
):
|
|
169
|
+
review.add_issue(
|
|
170
|
+
"critical",
|
|
171
|
+
"content",
|
|
172
|
+
f"Answer key visible: '{text[:60]}...'"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Check: document too short
|
|
176
|
+
if len(all_text) < 500:
|
|
177
|
+
review.add_issue(
|
|
178
|
+
"major", "content", "Document is very short — may be incomplete"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Check: missing key sections
|
|
182
|
+
sections_expected = ["do now", "aim", "exit ticket"]
|
|
183
|
+
text_lower = all_text.lower()
|
|
184
|
+
for section in sections_expected:
|
|
185
|
+
if section not in text_lower:
|
|
186
|
+
review.add_issue(
|
|
187
|
+
"minor", "structure",
|
|
188
|
+
f"Missing expected section: '{section}'"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return review
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def review_game_html(html_path: Path) -> OutputReview:
|
|
195
|
+
"""Review a generated HTML game for quality issues."""
|
|
196
|
+
review = OutputReview()
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
html = html_path.read_text(encoding="utf-8")
|
|
200
|
+
except Exception as e:
|
|
201
|
+
review.add_issue("critical", "file", f"Cannot read HTML: {e}")
|
|
202
|
+
return review
|
|
203
|
+
|
|
204
|
+
# Check: has proper HTML structure
|
|
205
|
+
if "<script>" not in html and "<script " not in html:
|
|
206
|
+
review.add_issue("critical", "structure", "No <script> tag — game won't run")
|
|
207
|
+
|
|
208
|
+
if "<style>" not in html and "<style " not in html:
|
|
209
|
+
review.add_issue("critical", "structure", "No <style> tag — game won't display")
|
|
210
|
+
|
|
211
|
+
if "<body>" not in html and "<body " not in html:
|
|
212
|
+
review.add_issue("major", "structure", "No <body> tag")
|
|
213
|
+
|
|
214
|
+
# Check: has game elements
|
|
215
|
+
game_indicators = [
|
|
216
|
+
"addEventListener", "onclick", "function", "score",
|
|
217
|
+
"querySelector", "getElementById",
|
|
218
|
+
]
|
|
219
|
+
found = sum(1 for g in game_indicators if g in html)
|
|
220
|
+
if found < 3:
|
|
221
|
+
review.add_issue(
|
|
222
|
+
"critical", "content",
|
|
223
|
+
f"Only {found}/6 game indicators found — may not be interactive"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Check: file size (too small = broken, too large = bloated)
|
|
227
|
+
size_kb = len(html) / 1024
|
|
228
|
+
if size_kb < 5:
|
|
229
|
+
review.add_issue("critical", "content", f"Game is only {size_kb:.0f}KB — likely broken")
|
|
230
|
+
elif size_kb > 200:
|
|
231
|
+
review.add_issue("minor", "size", f"Game is {size_kb:.0f}KB — may load slowly")
|
|
232
|
+
|
|
233
|
+
# Check: duplicate DOCTYPE
|
|
234
|
+
if html.count("<!DOCTYPE") > 1:
|
|
235
|
+
review.add_issue("major", "structure", "Duplicate <!DOCTYPE> tags")
|
|
236
|
+
|
|
237
|
+
return review
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def review_all_outputs(output_dir: Path) -> dict[str, OutputReview]:
|
|
241
|
+
"""Review all generated outputs in a directory."""
|
|
242
|
+
reviews: dict[str, OutputReview] = {}
|
|
243
|
+
|
|
244
|
+
for pptx in output_dir.glob("*.pptx"):
|
|
245
|
+
reviews[pptx.name] = review_pptx(pptx)
|
|
246
|
+
|
|
247
|
+
for docx in output_dir.glob("*handout*.docx"):
|
|
248
|
+
reviews[docx.name] = review_docx(docx)
|
|
249
|
+
|
|
250
|
+
for html in output_dir.glob("game_*.html"):
|
|
251
|
+
reviews[html.name] = review_game_html(html)
|
|
252
|
+
|
|
253
|
+
return reviews
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "clawed"
|
|
7
|
-
version = "2.5.
|
|
7
|
+
version = "2.5.3"
|
|
8
8
|
description = "Free AI lesson planner for teachers. Learns your teaching voice from your curriculum files and generates complete lesson packages. Open source, runs locally, no subscription."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|