docagent-cli 0.0.35__py3-none-any.whl
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.
- docagent_cli/__init__.py +36 -0
- docagent_cli/__main__.py +6 -0
- docagent_cli/_ask_user_types.py +90 -0
- docagent_cli/_cli_context.py +27 -0
- docagent_cli/_debug.py +52 -0
- docagent_cli/_env_vars.py +56 -0
- docagent_cli/_server_config.py +352 -0
- docagent_cli/_session_stats.py +114 -0
- docagent_cli/_testing_models.py +144 -0
- docagent_cli/_version.py +17 -0
- docagent_cli/agent.py +1193 -0
- docagent_cli/app.py +4979 -0
- docagent_cli/app.tcss +283 -0
- docagent_cli/ask_user.py +301 -0
- docagent_cli/built_in_skills/__init__.py +5 -0
- docagent_cli/built_in_skills/doc-coauthoring/SKILL.md +375 -0
- docagent_cli/built_in_skills/docx/LICENSE.txt +30 -0
- docagent_cli/built_in_skills/docx/SKILL.md +590 -0
- docagent_cli/built_in_skills/docx/scripts/__init__.py +1 -0
- docagent_cli/built_in_skills/docx/scripts/accept_changes.py +135 -0
- docagent_cli/built_in_skills/docx/scripts/comment.py +318 -0
- docagent_cli/built_in_skills/docx/scripts/office/helpers/__init__.py +0 -0
- docagent_cli/built_in_skills/docx/scripts/office/helpers/merge_runs.py +199 -0
- docagent_cli/built_in_skills/docx/scripts/office/helpers/simplify_redlines.py +197 -0
- docagent_cli/built_in_skills/docx/scripts/office/pack.py +159 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/mce/mc.xsd +75 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- docagent_cli/built_in_skills/docx/scripts/office/soffice.py +183 -0
- docagent_cli/built_in_skills/docx/scripts/office/unpack.py +132 -0
- docagent_cli/built_in_skills/docx/scripts/office/validate.py +111 -0
- docagent_cli/built_in_skills/docx/scripts/office/validators/__init__.py +15 -0
- docagent_cli/built_in_skills/docx/scripts/office/validators/base.py +847 -0
- docagent_cli/built_in_skills/docx/scripts/office/validators/docx.py +446 -0
- docagent_cli/built_in_skills/docx/scripts/office/validators/pptx.py +275 -0
- docagent_cli/built_in_skills/docx/scripts/office/validators/redlining.py +247 -0
- docagent_cli/built_in_skills/docx/scripts/templates/comments.xml +3 -0
- docagent_cli/built_in_skills/docx/scripts/templates/commentsExtended.xml +3 -0
- docagent_cli/built_in_skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- docagent_cli/built_in_skills/docx/scripts/templates/commentsIds.xml +3 -0
- docagent_cli/built_in_skills/docx/scripts/templates/people.xml +3 -0
- docagent_cli/built_in_skills/pdf/LICENSE.txt +30 -0
- docagent_cli/built_in_skills/pdf/SKILL.md +314 -0
- docagent_cli/built_in_skills/pdf/forms.md +294 -0
- docagent_cli/built_in_skills/pdf/reference.md +612 -0
- docagent_cli/built_in_skills/pdf/scripts/check_bounding_boxes.py +65 -0
- docagent_cli/built_in_skills/pdf/scripts/check_fillable_fields.py +11 -0
- docagent_cli/built_in_skills/pdf/scripts/convert_pdf_to_images.py +33 -0
- docagent_cli/built_in_skills/pdf/scripts/create_validation_image.py +37 -0
- docagent_cli/built_in_skills/pdf/scripts/extract_form_field_info.py +122 -0
- docagent_cli/built_in_skills/pdf/scripts/extract_form_structure.py +115 -0
- docagent_cli/built_in_skills/pdf/scripts/fill_fillable_fields.py +98 -0
- docagent_cli/built_in_skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
- docagent_cli/built_in_skills/pptx/LICENSE.txt +30 -0
- docagent_cli/built_in_skills/pptx/SKILL.md +232 -0
- docagent_cli/built_in_skills/pptx/editing.md +205 -0
- docagent_cli/built_in_skills/pptx/pptxgenjs.md +420 -0
- docagent_cli/built_in_skills/pptx/scripts/__init__.py +0 -0
- docagent_cli/built_in_skills/pptx/scripts/add_slide.py +195 -0
- docagent_cli/built_in_skills/pptx/scripts/clean.py +286 -0
- docagent_cli/built_in_skills/pptx/scripts/office/helpers/__init__.py +0 -0
- docagent_cli/built_in_skills/pptx/scripts/office/helpers/merge_runs.py +199 -0
- docagent_cli/built_in_skills/pptx/scripts/office/helpers/simplify_redlines.py +197 -0
- docagent_cli/built_in_skills/pptx/scripts/office/pack.py +159 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/mce/mc.xsd +75 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- docagent_cli/built_in_skills/pptx/scripts/office/soffice.py +183 -0
- docagent_cli/built_in_skills/pptx/scripts/office/unpack.py +132 -0
- docagent_cli/built_in_skills/pptx/scripts/office/validate.py +111 -0
- docagent_cli/built_in_skills/pptx/scripts/office/validators/__init__.py +15 -0
- docagent_cli/built_in_skills/pptx/scripts/office/validators/base.py +847 -0
- docagent_cli/built_in_skills/pptx/scripts/office/validators/docx.py +446 -0
- docagent_cli/built_in_skills/pptx/scripts/office/validators/pptx.py +275 -0
- docagent_cli/built_in_skills/pptx/scripts/office/validators/redlining.py +247 -0
- docagent_cli/built_in_skills/pptx/scripts/thumbnail.py +289 -0
- docagent_cli/built_in_skills/remember/SKILL.md +118 -0
- docagent_cli/built_in_skills/skill-creator/LICENSE.txt +202 -0
- docagent_cli/built_in_skills/skill-creator/SKILL.md +485 -0
- docagent_cli/built_in_skills/skill-creator/agents/analyzer.md +274 -0
- docagent_cli/built_in_skills/skill-creator/agents/comparator.md +202 -0
- docagent_cli/built_in_skills/skill-creator/agents/grader.md +223 -0
- docagent_cli/built_in_skills/skill-creator/assets/eval_review.html +146 -0
- docagent_cli/built_in_skills/skill-creator/eval-viewer/generate_review.py +471 -0
- docagent_cli/built_in_skills/skill-creator/eval-viewer/viewer.html +1325 -0
- docagent_cli/built_in_skills/skill-creator/references/schemas.md +430 -0
- docagent_cli/built_in_skills/skill-creator/scripts/__init__.py +0 -0
- docagent_cli/built_in_skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- docagent_cli/built_in_skills/skill-creator/scripts/generate_report.py +326 -0
- docagent_cli/built_in_skills/skill-creator/scripts/improve_description.py +247 -0
- docagent_cli/built_in_skills/skill-creator/scripts/package_skill.py +136 -0
- docagent_cli/built_in_skills/skill-creator/scripts/quick_validate.py +103 -0
- docagent_cli/built_in_skills/skill-creator/scripts/run_eval.py +310 -0
- docagent_cli/built_in_skills/skill-creator/scripts/run_loop.py +328 -0
- docagent_cli/built_in_skills/skill-creator/scripts/utils.py +47 -0
- docagent_cli/built_in_skills/theme-factory/LICENSE.txt +202 -0
- docagent_cli/built_in_skills/theme-factory/SKILL.md +59 -0
- docagent_cli/built_in_skills/theme-factory/theme-showcase.pdf +0 -0
- docagent_cli/built_in_skills/theme-factory/themes/arctic-frost.md +19 -0
- docagent_cli/built_in_skills/theme-factory/themes/botanical-garden.md +19 -0
- docagent_cli/built_in_skills/theme-factory/themes/desert-rose.md +19 -0
- docagent_cli/built_in_skills/theme-factory/themes/forest-canopy.md +19 -0
- docagent_cli/built_in_skills/theme-factory/themes/golden-hour.md +19 -0
- docagent_cli/built_in_skills/theme-factory/themes/midnight-galaxy.md +19 -0
- docagent_cli/built_in_skills/theme-factory/themes/modern-minimalist.md +19 -0
- docagent_cli/built_in_skills/theme-factory/themes/ocean-depths.md +19 -0
- docagent_cli/built_in_skills/theme-factory/themes/sunset-boulevard.md +19 -0
- docagent_cli/built_in_skills/theme-factory/themes/tech-innovation.md +19 -0
- docagent_cli/built_in_skills/xlsx/LICENSE.txt +30 -0
- docagent_cli/built_in_skills/xlsx/SKILL.md +292 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/helpers/__init__.py +0 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/helpers/merge_runs.py +199 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/pack.py +159 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/soffice.py +183 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/unpack.py +132 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/validate.py +111 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/validators/__init__.py +15 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/validators/base.py +847 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/validators/docx.py +446 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/validators/pptx.py +275 -0
- docagent_cli/built_in_skills/xlsx/scripts/office/validators/redlining.py +247 -0
- docagent_cli/built_in_skills/xlsx/scripts/recalc.py +184 -0
- docagent_cli/clipboard.py +128 -0
- docagent_cli/command_registry.py +284 -0
- docagent_cli/config.py +2418 -0
- docagent_cli/configurable_model.py +162 -0
- docagent_cli/default_agent_prompt.md +12 -0
- docagent_cli/editor.py +142 -0
- docagent_cli/file_ops.py +473 -0
- docagent_cli/formatting.py +28 -0
- docagent_cli/hooks.py +206 -0
- docagent_cli/input.py +787 -0
- docagent_cli/integrations/__init__.py +1 -0
- docagent_cli/integrations/sandbox_factory.py +873 -0
- docagent_cli/integrations/sandbox_provider.py +71 -0
- docagent_cli/local_context.py +718 -0
- docagent_cli/main.py +1641 -0
- docagent_cli/mcp_tools.py +707 -0
- docagent_cli/mcp_trust.py +168 -0
- docagent_cli/media_utils.py +478 -0
- docagent_cli/model_config.py +1620 -0
- docagent_cli/non_interactive.py +948 -0
- docagent_cli/offload.py +371 -0
- docagent_cli/output.py +69 -0
- docagent_cli/project_utils.py +188 -0
- docagent_cli/py.typed +0 -0
- docagent_cli/remote_client.py +515 -0
- docagent_cli/server.py +520 -0
- docagent_cli/server_graph.py +196 -0
- docagent_cli/server_manager.py +365 -0
- docagent_cli/sessions.py +1262 -0
- docagent_cli/skills/__init__.py +18 -0
- docagent_cli/skills/commands.py +1090 -0
- docagent_cli/skills/load.py +192 -0
- docagent_cli/subagents.py +173 -0
- docagent_cli/system_prompt.md +247 -0
- docagent_cli/textual_adapter.py +1352 -0
- docagent_cli/theme.py +842 -0
- docagent_cli/token_state.py +31 -0
- docagent_cli/tool_display.py +298 -0
- docagent_cli/tools.py +236 -0
- docagent_cli/ui.py +420 -0
- docagent_cli/unicode_security.py +516 -0
- docagent_cli/update_check.py +454 -0
- docagent_cli/widgets/__init__.py +9 -0
- docagent_cli/widgets/_links.py +63 -0
- docagent_cli/widgets/approval.py +442 -0
- docagent_cli/widgets/ask_user.py +398 -0
- docagent_cli/widgets/autocomplete.py +691 -0
- docagent_cli/widgets/chat_input.py +1827 -0
- docagent_cli/widgets/diff.py +248 -0
- docagent_cli/widgets/history.py +188 -0
- docagent_cli/widgets/loading.py +177 -0
- docagent_cli/widgets/mcp_viewer.py +362 -0
- docagent_cli/widgets/message_store.py +675 -0
- docagent_cli/widgets/messages.py +1751 -0
- docagent_cli/widgets/model_selector.py +964 -0
- docagent_cli/widgets/status.py +372 -0
- docagent_cli/widgets/theme_selector.py +164 -0
- docagent_cli/widgets/thread_selector.py +1905 -0
- docagent_cli/widgets/tool_renderers.py +148 -0
- docagent_cli/widgets/tool_widgets.py +274 -0
- docagent_cli/widgets/welcome.py +339 -0
- docagent_cli-0.0.35.data/data/docagent_cli/default_agent_prompt.md +12 -0
- docagent_cli-0.0.35.dist-info/METADATA +200 -0
- docagent_cli-0.0.35.dist-info/RECORD +300 -0
- docagent_cli-0.0.35.dist-info/WHEEL +4 -0
- docagent_cli-0.0.35.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Trust store for project-level MCP server configurations.
|
|
2
|
+
|
|
3
|
+
Manages persistent approval of project-level MCP configs that contain stdio
|
|
4
|
+
servers (which execute local commands). Trust is fingerprint-based: if the
|
|
5
|
+
config content changes, the user must re-approve.
|
|
6
|
+
|
|
7
|
+
Trust entries are stored in `~/.docagent/config.toml` under
|
|
8
|
+
`[mcp_trust.projects]`.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import contextlib
|
|
14
|
+
import hashlib
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
import tempfile
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
_DEFAULT_CONFIG_DIR = Path.home() / ".docagent"
|
|
24
|
+
_DEFAULT_CONFIG_PATH = _DEFAULT_CONFIG_DIR / "config.toml"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def compute_config_fingerprint(config_paths: list[Path]) -> str:
|
|
28
|
+
"""Compute a SHA-256 fingerprint over sorted, concatenated config contents.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
config_paths: Paths to config files to fingerprint.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Fingerprint string in the form `sha256:<hex>`.
|
|
35
|
+
"""
|
|
36
|
+
hasher = hashlib.sha256()
|
|
37
|
+
for path in sorted(config_paths):
|
|
38
|
+
try:
|
|
39
|
+
hasher.update(path.read_bytes())
|
|
40
|
+
except OSError:
|
|
41
|
+
logger.warning("Could not read %s for fingerprinting", path, exc_info=True)
|
|
42
|
+
return f"sha256:{hasher.hexdigest()}"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _load_config(config_path: Path) -> dict[str, Any]:
|
|
46
|
+
"""Read the TOML config file.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Parsed TOML data, or an empty dict on failure.
|
|
50
|
+
"""
|
|
51
|
+
import tomllib
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
if not config_path.exists():
|
|
55
|
+
return {}
|
|
56
|
+
with config_path.open("rb") as f:
|
|
57
|
+
return tomllib.load(f)
|
|
58
|
+
except (OSError, tomllib.TOMLDecodeError):
|
|
59
|
+
logger.debug("Could not read config %s", config_path, exc_info=True)
|
|
60
|
+
return {}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _save_config(data: dict[str, Any], config_path: Path) -> bool:
|
|
64
|
+
"""Atomic write of TOML data to config_path.
|
|
65
|
+
|
|
66
|
+
Uses `tempfile.mkstemp` + `Path.replace` for crash safety.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
data: Full TOML data dict to write.
|
|
70
|
+
config_path: Destination path.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
`True` on success, `False` on I/O failure.
|
|
74
|
+
"""
|
|
75
|
+
import tomli_w
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
fd, tmp_path = tempfile.mkstemp(dir=config_path.parent, suffix=".tmp")
|
|
80
|
+
try:
|
|
81
|
+
with os.fdopen(fd, "wb") as f:
|
|
82
|
+
tomli_w.dump(data, f)
|
|
83
|
+
Path(tmp_path).replace(config_path)
|
|
84
|
+
except BaseException:
|
|
85
|
+
with contextlib.suppress(OSError):
|
|
86
|
+
Path(tmp_path).unlink()
|
|
87
|
+
raise
|
|
88
|
+
except (OSError, ValueError):
|
|
89
|
+
logger.exception("Failed to save config to %s", config_path)
|
|
90
|
+
return False
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def is_project_mcp_trusted(
|
|
95
|
+
project_root: str,
|
|
96
|
+
fingerprint: str,
|
|
97
|
+
*,
|
|
98
|
+
config_path: Path | None = None,
|
|
99
|
+
) -> bool:
|
|
100
|
+
"""Check whether a project's MCP config is trusted with the given fingerprint.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
project_root: Absolute path to the project root.
|
|
104
|
+
fingerprint: Expected fingerprint string (`sha256:<hex>`).
|
|
105
|
+
config_path: Path to the trust config file.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
`True` if the stored fingerprint matches.
|
|
109
|
+
"""
|
|
110
|
+
if config_path is None:
|
|
111
|
+
config_path = _DEFAULT_CONFIG_PATH
|
|
112
|
+
|
|
113
|
+
data = _load_config(config_path)
|
|
114
|
+
projects = data.get("mcp_trust", {}).get("projects", {})
|
|
115
|
+
return projects.get(project_root) == fingerprint
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def trust_project_mcp(
|
|
119
|
+
project_root: str,
|
|
120
|
+
fingerprint: str,
|
|
121
|
+
*,
|
|
122
|
+
config_path: Path | None = None,
|
|
123
|
+
) -> bool:
|
|
124
|
+
"""Persist trust for a project's MCP config.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
project_root: Absolute path to the project root.
|
|
128
|
+
fingerprint: Fingerprint to store (`sha256:<hex>`).
|
|
129
|
+
config_path: Path to the trust config file.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
`True` if the entry was saved successfully.
|
|
133
|
+
"""
|
|
134
|
+
if config_path is None:
|
|
135
|
+
config_path = _DEFAULT_CONFIG_PATH
|
|
136
|
+
|
|
137
|
+
data = _load_config(config_path)
|
|
138
|
+
if "mcp_trust" not in data:
|
|
139
|
+
data["mcp_trust"] = {}
|
|
140
|
+
if "projects" not in data["mcp_trust"]:
|
|
141
|
+
data["mcp_trust"]["projects"] = {}
|
|
142
|
+
data["mcp_trust"]["projects"][project_root] = fingerprint
|
|
143
|
+
return _save_config(data, config_path)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def revoke_project_mcp_trust(
|
|
147
|
+
project_root: str,
|
|
148
|
+
*,
|
|
149
|
+
config_path: Path | None = None,
|
|
150
|
+
) -> bool:
|
|
151
|
+
"""Remove trust for a project's MCP config.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
project_root: Absolute path to the project root.
|
|
155
|
+
config_path: Path to the trust config file.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
`True` if the entry was removed (or didn't exist).
|
|
159
|
+
"""
|
|
160
|
+
if config_path is None:
|
|
161
|
+
config_path = _DEFAULT_CONFIG_PATH
|
|
162
|
+
|
|
163
|
+
data = _load_config(config_path)
|
|
164
|
+
projects = data.get("mcp_trust", {}).get("projects", {})
|
|
165
|
+
if project_root not in projects:
|
|
166
|
+
return True
|
|
167
|
+
del data["mcp_trust"]["projects"][project_root]
|
|
168
|
+
return _save_config(data, config_path)
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"""Utilities for handling image and video media from clipboard and files."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import io
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import pathlib
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
# S404: subprocess needed for clipboard access via pngpaste/osascript
|
|
11
|
+
import subprocess # noqa: S404
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from langchain_core.messages.content import VideoContentBlock
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
IMAGE_EXTENSIONS: frozenset[str] = frozenset(
|
|
23
|
+
{
|
|
24
|
+
".png",
|
|
25
|
+
".jpg",
|
|
26
|
+
".jpeg",
|
|
27
|
+
".gif",
|
|
28
|
+
".bmp",
|
|
29
|
+
".tiff",
|
|
30
|
+
".tif",
|
|
31
|
+
".webp",
|
|
32
|
+
".ico",
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
"""Common image file extensions supported by PIL."""
|
|
36
|
+
|
|
37
|
+
VIDEO_EXTENSIONS: frozenset[str] = frozenset(
|
|
38
|
+
{
|
|
39
|
+
".mp4",
|
|
40
|
+
".mov",
|
|
41
|
+
".avi",
|
|
42
|
+
".webm",
|
|
43
|
+
".m4v",
|
|
44
|
+
".wmv",
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
"""Video file extensions with validated magic-byte support."""
|
|
48
|
+
|
|
49
|
+
MAX_MEDIA_BYTES: int = 20 * 1024 * 1024
|
|
50
|
+
"""Maximum media file size (20 MB). Keeps base64 payload under ~27 MB."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _get_executable(name: str) -> str | None:
|
|
54
|
+
"""Get full path to an executable using shutil.which().
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
name: Name of the executable to find
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Full path to executable, or None if not found.
|
|
61
|
+
"""
|
|
62
|
+
return shutil.which(name)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class ImageData:
|
|
67
|
+
"""Represents a pasted image with its base64 encoding."""
|
|
68
|
+
|
|
69
|
+
base64_data: str
|
|
70
|
+
format: str # "png", "jpeg", etc.
|
|
71
|
+
placeholder: str # Display text like "[image 1]"
|
|
72
|
+
|
|
73
|
+
def to_message_content(self) -> dict:
|
|
74
|
+
"""Convert to LangChain message content format.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dict with type and image_url for multimodal messages.
|
|
78
|
+
"""
|
|
79
|
+
return {
|
|
80
|
+
"type": "image_url",
|
|
81
|
+
"image_url": {"url": f"data:image/{self.format};base64,{self.base64_data}"},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class VideoData:
|
|
87
|
+
"""Represents a pasted video with its base64 encoding."""
|
|
88
|
+
|
|
89
|
+
base64_data: str
|
|
90
|
+
format: str # "mp4", "quicktime", etc.
|
|
91
|
+
placeholder: str # Display text like "[video 1]"
|
|
92
|
+
|
|
93
|
+
def to_message_content(self) -> "VideoContentBlock":
|
|
94
|
+
"""Convert to LangChain `VideoContentBlock` format.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
`VideoContentBlock` with base64 data and mime_type.
|
|
98
|
+
"""
|
|
99
|
+
from langchain_core.messages.content import create_video_block
|
|
100
|
+
|
|
101
|
+
return create_video_block(
|
|
102
|
+
base64=self.base64_data,
|
|
103
|
+
mime_type=f"video/{self.format}",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_clipboard_image() -> ImageData | None:
|
|
108
|
+
"""Attempt to read an image from the system clipboard.
|
|
109
|
+
|
|
110
|
+
Supports macOS via `pngpaste` or `osascript`.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
ImageData if an image is found, None otherwise.
|
|
114
|
+
"""
|
|
115
|
+
if sys.platform == "darwin":
|
|
116
|
+
return _get_macos_clipboard_image()
|
|
117
|
+
logger.warning(
|
|
118
|
+
"Clipboard image paste is not supported on %s. "
|
|
119
|
+
"Only macOS is currently supported. "
|
|
120
|
+
"You can still attach images by dragging and dropping file paths.",
|
|
121
|
+
sys.platform,
|
|
122
|
+
)
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_image_from_path(path: pathlib.Path) -> ImageData | None:
|
|
127
|
+
"""Read and encode an image file from disk.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
path: Path to the image file.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
`ImageData` when the file is a valid image, otherwise `None`.
|
|
134
|
+
"""
|
|
135
|
+
from PIL import Image, UnidentifiedImageError
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
file_size = path.stat().st_size
|
|
139
|
+
if file_size == 0:
|
|
140
|
+
logger.debug("Image file is empty: %s", path)
|
|
141
|
+
return None
|
|
142
|
+
if file_size > MAX_MEDIA_BYTES:
|
|
143
|
+
logger.warning(
|
|
144
|
+
"Image file %s is too large (%d MB, max %d MB)",
|
|
145
|
+
path,
|
|
146
|
+
file_size // (1024 * 1024),
|
|
147
|
+
MAX_MEDIA_BYTES // (1024 * 1024),
|
|
148
|
+
)
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
image_bytes = path.read_bytes()
|
|
152
|
+
if not image_bytes:
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
with Image.open(io.BytesIO(image_bytes)) as image:
|
|
156
|
+
image_format = (image.format or "").lower()
|
|
157
|
+
|
|
158
|
+
if image_format == "jpg":
|
|
159
|
+
image_format = "jpeg"
|
|
160
|
+
if not image_format:
|
|
161
|
+
suffix = path.suffix.lower().removeprefix(".")
|
|
162
|
+
image_format = "jpeg" if suffix == "jpg" else suffix
|
|
163
|
+
if not image_format:
|
|
164
|
+
image_format = "png"
|
|
165
|
+
|
|
166
|
+
return ImageData(
|
|
167
|
+
base64_data=encode_to_base64(image_bytes),
|
|
168
|
+
format=image_format,
|
|
169
|
+
placeholder="[image]",
|
|
170
|
+
)
|
|
171
|
+
except (UnidentifiedImageError, OSError) as e:
|
|
172
|
+
logger.debug("Failed to load image from %s: %s", path, e, exc_info=True)
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _detect_video_format(data: bytes) -> str | None:
|
|
177
|
+
"""Detect video MIME subtype from magic bytes.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
data: Raw file bytes (at least 12 bytes for reliable detection).
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
MIME subtype (e.g. "mp4", "webm") or `None` if unrecognized.
|
|
184
|
+
"""
|
|
185
|
+
min_avi_len = 12
|
|
186
|
+
if data[4:8] == b"ftyp":
|
|
187
|
+
# ftyp box: major brand at bytes 8-12 distinguishes MOV vs MP4
|
|
188
|
+
brand = data[8:12]
|
|
189
|
+
if brand == b"qt ":
|
|
190
|
+
return "quicktime"
|
|
191
|
+
return "mp4"
|
|
192
|
+
if data[:4] == b"RIFF" and len(data) >= min_avi_len and data[8:12] == b"AVI ":
|
|
193
|
+
return "avi"
|
|
194
|
+
if data[:4] == b"\x30\x26\xb2\x75": # ASF/WMV
|
|
195
|
+
return "x-ms-wmv"
|
|
196
|
+
if data[:4] == b"\x1a\x45\xdf\xa3": # WebM/Matroska (EBML header)
|
|
197
|
+
return "webm"
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def get_video_from_path(path: pathlib.Path) -> VideoData | None:
|
|
202
|
+
"""Read and encode a video file from disk.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
path: Path to the video file.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
`VideoData` when the file is a valid video, otherwise `None`.
|
|
209
|
+
"""
|
|
210
|
+
suffix = path.suffix.lower()
|
|
211
|
+
if suffix not in VIDEO_EXTENSIONS:
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
file_size = path.stat().st_size
|
|
216
|
+
if file_size == 0:
|
|
217
|
+
logger.debug("Video file is empty: %s", path)
|
|
218
|
+
return None
|
|
219
|
+
if file_size > MAX_MEDIA_BYTES:
|
|
220
|
+
logger.warning(
|
|
221
|
+
"Video file %s is too large (%d MB, max %d MB)",
|
|
222
|
+
path,
|
|
223
|
+
file_size // (1024 * 1024),
|
|
224
|
+
MAX_MEDIA_BYTES // (1024 * 1024),
|
|
225
|
+
)
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
video_bytes = path.read_bytes()
|
|
229
|
+
|
|
230
|
+
# Validate it's a real video file by checking magic bytes
|
|
231
|
+
# MP4 starts with ftyp, MOV also uses ftyp, AVI starts with RIFF
|
|
232
|
+
min_video_len = 8
|
|
233
|
+
if len(video_bytes) < min_video_len:
|
|
234
|
+
logger.debug("Video file too small (%d bytes): %s", len(video_bytes), path)
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
# Detect format from magic bytes (not extension) so renamed files
|
|
238
|
+
# get the correct MIME type.
|
|
239
|
+
detected_format = _detect_video_format(video_bytes)
|
|
240
|
+
if detected_format is None:
|
|
241
|
+
logger.warning(
|
|
242
|
+
"Video file %s has unrecognized signature for extension '%s'; "
|
|
243
|
+
"skipping. If this is a valid video, the format may not be "
|
|
244
|
+
"supported yet.",
|
|
245
|
+
path,
|
|
246
|
+
suffix,
|
|
247
|
+
)
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
return VideoData(
|
|
251
|
+
base64_data=encode_to_base64(video_bytes),
|
|
252
|
+
format=detected_format,
|
|
253
|
+
placeholder="[video]",
|
|
254
|
+
)
|
|
255
|
+
except OSError as e:
|
|
256
|
+
logger.warning("Failed to load video from %s: %s", path, e, exc_info=True)
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def get_media_from_path(path: pathlib.Path) -> ImageData | VideoData | None:
|
|
261
|
+
"""Try to load a file as an image first, then as a video.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
path: Path to the media file.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
`ImageData` or `VideoData` if the file is valid media, otherwise `None`.
|
|
268
|
+
"""
|
|
269
|
+
result: ImageData | VideoData | None = get_image_from_path(path)
|
|
270
|
+
if result is not None:
|
|
271
|
+
return result
|
|
272
|
+
return get_video_from_path(path)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _get_macos_clipboard_image() -> ImageData | None:
|
|
276
|
+
"""Get clipboard image on macOS using pngpaste or osascript.
|
|
277
|
+
|
|
278
|
+
First tries pngpaste (faster if installed), then falls back to osascript.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
ImageData if an image is found, None otherwise.
|
|
282
|
+
"""
|
|
283
|
+
from PIL import Image, UnidentifiedImageError
|
|
284
|
+
|
|
285
|
+
# Try pngpaste first (fast if installed)
|
|
286
|
+
pngpaste_path = _get_executable("pngpaste")
|
|
287
|
+
if pngpaste_path:
|
|
288
|
+
try:
|
|
289
|
+
# S603: pngpaste_path is validated via shutil.which(), args are hardcoded
|
|
290
|
+
result = subprocess.run( # noqa: S603
|
|
291
|
+
[pngpaste_path, "-"],
|
|
292
|
+
capture_output=True,
|
|
293
|
+
check=False,
|
|
294
|
+
timeout=2,
|
|
295
|
+
)
|
|
296
|
+
if result.returncode == 0 and result.stdout:
|
|
297
|
+
# Successfully got PNG data - validate it's a real image
|
|
298
|
+
try:
|
|
299
|
+
Image.open(io.BytesIO(result.stdout))
|
|
300
|
+
base64_data = base64.b64encode(result.stdout).decode("utf-8")
|
|
301
|
+
return ImageData(
|
|
302
|
+
base64_data=base64_data,
|
|
303
|
+
format="png", # 'pngpaste -' always outputs PNG
|
|
304
|
+
placeholder="[image]",
|
|
305
|
+
)
|
|
306
|
+
except (
|
|
307
|
+
# UnidentifiedImageError: corrupted or non-image data
|
|
308
|
+
UnidentifiedImageError,
|
|
309
|
+
OSError, # OSError: I/O errors during image processing
|
|
310
|
+
) as e:
|
|
311
|
+
logger.debug(
|
|
312
|
+
"Invalid image data from pngpaste: %s", e, exc_info=True
|
|
313
|
+
)
|
|
314
|
+
except FileNotFoundError:
|
|
315
|
+
# pngpaste not installed - expected on systems without it
|
|
316
|
+
logger.debug("pngpaste not found, falling back to osascript")
|
|
317
|
+
except subprocess.TimeoutExpired:
|
|
318
|
+
logger.debug("pngpaste timed out after 2 seconds")
|
|
319
|
+
|
|
320
|
+
# Fallback to osascript with temp file (built-in but slower)
|
|
321
|
+
return _get_clipboard_via_osascript()
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _get_clipboard_via_osascript() -> ImageData | None:
|
|
325
|
+
"""Get clipboard image via osascript using a temp file.
|
|
326
|
+
|
|
327
|
+
osascript outputs data in a special format that can't be captured as raw binary,
|
|
328
|
+
so we write to a temp file instead.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
ImageData if an image is found, None otherwise.
|
|
332
|
+
"""
|
|
333
|
+
from PIL import Image, UnidentifiedImageError
|
|
334
|
+
|
|
335
|
+
# Get osascript path - it's a macOS builtin so should always exist
|
|
336
|
+
osascript_path = _get_executable("osascript")
|
|
337
|
+
if not osascript_path:
|
|
338
|
+
return None
|
|
339
|
+
|
|
340
|
+
# Create a temp file for the image
|
|
341
|
+
fd, temp_path = tempfile.mkstemp(suffix=".png")
|
|
342
|
+
os.close(fd)
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
# First check if clipboard has PNG data
|
|
346
|
+
# S603: osascript_path is validated via shutil.which(), args are hardcoded
|
|
347
|
+
check_result = subprocess.run( # noqa: S603
|
|
348
|
+
[osascript_path, "-e", "clipboard info"],
|
|
349
|
+
capture_output=True,
|
|
350
|
+
check=False,
|
|
351
|
+
timeout=2,
|
|
352
|
+
text=True,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
if check_result.returncode != 0:
|
|
356
|
+
return None
|
|
357
|
+
|
|
358
|
+
# Check for PNG or TIFF in clipboard info
|
|
359
|
+
clipboard_info = check_result.stdout.lower()
|
|
360
|
+
if "pngf" not in clipboard_info and "tiff" not in clipboard_info:
|
|
361
|
+
return None
|
|
362
|
+
|
|
363
|
+
# Try to get PNG first, fall back to TIFF
|
|
364
|
+
if "pngf" in clipboard_info:
|
|
365
|
+
get_script = f"""
|
|
366
|
+
set pngData to the clipboard as «class PNGf»
|
|
367
|
+
set theFile to open for access POSIX file "{temp_path}" with write permission
|
|
368
|
+
write pngData to theFile
|
|
369
|
+
close access theFile
|
|
370
|
+
return "success"
|
|
371
|
+
""" # noqa: E501
|
|
372
|
+
else:
|
|
373
|
+
get_script = f"""
|
|
374
|
+
set tiffData to the clipboard as TIFF picture
|
|
375
|
+
set theFile to open for access POSIX file "{temp_path}" with write permission
|
|
376
|
+
write tiffData to theFile
|
|
377
|
+
close access theFile
|
|
378
|
+
return "success"
|
|
379
|
+
""" # noqa: E501
|
|
380
|
+
|
|
381
|
+
# S603: osascript_path validated via shutil.which(), script is internal
|
|
382
|
+
result = subprocess.run( # noqa: S603
|
|
383
|
+
[osascript_path, "-e", get_script],
|
|
384
|
+
capture_output=True,
|
|
385
|
+
check=False,
|
|
386
|
+
timeout=3,
|
|
387
|
+
text=True,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
if result.returncode != 0 or "success" not in result.stdout:
|
|
391
|
+
return None
|
|
392
|
+
|
|
393
|
+
# Check if file was created and has content
|
|
394
|
+
if (
|
|
395
|
+
not pathlib.Path(temp_path).exists()
|
|
396
|
+
or pathlib.Path(temp_path).stat().st_size == 0
|
|
397
|
+
):
|
|
398
|
+
return None
|
|
399
|
+
|
|
400
|
+
# Read and validate the image
|
|
401
|
+
image_data = pathlib.Path(temp_path).read_bytes()
|
|
402
|
+
|
|
403
|
+
try:
|
|
404
|
+
image = Image.open(io.BytesIO(image_data))
|
|
405
|
+
# Convert to PNG if it's not already (e.g., if we got TIFF)
|
|
406
|
+
buffer = io.BytesIO()
|
|
407
|
+
image.save(buffer, format="PNG")
|
|
408
|
+
buffer.seek(0)
|
|
409
|
+
base64_data = base64.b64encode(buffer.getvalue()).decode("utf-8")
|
|
410
|
+
|
|
411
|
+
return ImageData(
|
|
412
|
+
base64_data=base64_data,
|
|
413
|
+
format="png",
|
|
414
|
+
placeholder="[image]",
|
|
415
|
+
)
|
|
416
|
+
except (
|
|
417
|
+
# UnidentifiedImageError: corrupted or non-image data
|
|
418
|
+
UnidentifiedImageError,
|
|
419
|
+
OSError, # OSError: I/O errors during image processing
|
|
420
|
+
) as e:
|
|
421
|
+
logger.debug(
|
|
422
|
+
"Failed to process clipboard image via osascript: %s", e, exc_info=True
|
|
423
|
+
)
|
|
424
|
+
return None
|
|
425
|
+
|
|
426
|
+
except subprocess.TimeoutExpired:
|
|
427
|
+
logger.debug("osascript timed out while accessing clipboard")
|
|
428
|
+
return None
|
|
429
|
+
except OSError as e:
|
|
430
|
+
logger.debug("OSError accessing clipboard via osascript: %s", e)
|
|
431
|
+
return None
|
|
432
|
+
finally:
|
|
433
|
+
# Clean up temp file
|
|
434
|
+
try:
|
|
435
|
+
pathlib.Path(temp_path).unlink()
|
|
436
|
+
except OSError as e:
|
|
437
|
+
logger.debug("Failed to clean up temp file %s: %s", temp_path, e)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def encode_to_base64(data: bytes) -> str:
|
|
441
|
+
"""Encode raw bytes to a base64 string.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
data: Raw bytes to encode.
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
Base64-encoded string.
|
|
448
|
+
"""
|
|
449
|
+
return base64.b64encode(data).decode("utf-8")
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def create_multimodal_content(
|
|
453
|
+
text: str, images: list[ImageData], videos: list[VideoData] | None = None
|
|
454
|
+
) -> list[dict]:
|
|
455
|
+
"""Create multimodal message content with text, images, and videos.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
text: Text content of the message
|
|
459
|
+
images: List of ImageData objects
|
|
460
|
+
videos: Optional list of VideoData objects
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
List of content blocks in LangChain message format.
|
|
464
|
+
"""
|
|
465
|
+
content_blocks = []
|
|
466
|
+
|
|
467
|
+
# Add text block
|
|
468
|
+
if text.strip():
|
|
469
|
+
content_blocks.append({"type": "text", "text": text})
|
|
470
|
+
|
|
471
|
+
# Add image blocks
|
|
472
|
+
content_blocks.extend(image.to_message_content() for image in images)
|
|
473
|
+
|
|
474
|
+
# Add video blocks
|
|
475
|
+
if videos:
|
|
476
|
+
content_blocks.extend(video.to_message_content() for video in videos)
|
|
477
|
+
|
|
478
|
+
return content_blocks
|