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
docagent_cli/file_ops.py
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""Helpers for tracking file operations and computing diffs for CLI display."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import difflib
|
|
6
|
+
import logging
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from deepagents.backends.protocol import BackendProtocol
|
|
15
|
+
|
|
16
|
+
FileOpStatus = Literal["pending", "success", "error"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ApprovalPreview:
|
|
21
|
+
"""Data used to render HITL previews."""
|
|
22
|
+
|
|
23
|
+
title: str
|
|
24
|
+
details: list[str]
|
|
25
|
+
diff: str | None = None
|
|
26
|
+
diff_title: str | None = None
|
|
27
|
+
error: str | None = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _safe_read(path: Path) -> str | None:
|
|
31
|
+
"""Read file content, returning None on failure.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
File content as string, or None if reading fails.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
return path.read_text(encoding="utf-8")
|
|
38
|
+
except (OSError, UnicodeDecodeError) as e:
|
|
39
|
+
logger.debug("Failed to read file %s: %s", path, e)
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _count_lines(text: str) -> int:
|
|
44
|
+
"""Count lines in text, treating empty strings as zero lines.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Number of lines in the text.
|
|
48
|
+
"""
|
|
49
|
+
if not text:
|
|
50
|
+
return 0
|
|
51
|
+
return len(text.splitlines())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def compute_unified_diff(
|
|
55
|
+
before: str,
|
|
56
|
+
after: str,
|
|
57
|
+
display_path: str,
|
|
58
|
+
*,
|
|
59
|
+
max_lines: int | None = 800,
|
|
60
|
+
context_lines: int = 3,
|
|
61
|
+
) -> str | None:
|
|
62
|
+
"""Compute a unified diff between before and after content.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
before: Original content
|
|
66
|
+
after: New content
|
|
67
|
+
display_path: Path for display in diff headers
|
|
68
|
+
max_lines: Maximum number of diff lines (None for unlimited)
|
|
69
|
+
context_lines: Number of context lines around changes (default 3)
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Unified diff string or None if no changes
|
|
73
|
+
"""
|
|
74
|
+
before_lines = before.splitlines()
|
|
75
|
+
after_lines = after.splitlines()
|
|
76
|
+
diff_lines = list(
|
|
77
|
+
difflib.unified_diff(
|
|
78
|
+
before_lines,
|
|
79
|
+
after_lines,
|
|
80
|
+
fromfile=f"{display_path} (before)",
|
|
81
|
+
tofile=f"{display_path} (after)",
|
|
82
|
+
lineterm="",
|
|
83
|
+
n=context_lines,
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
if not diff_lines:
|
|
87
|
+
return None
|
|
88
|
+
if max_lines is not None and len(diff_lines) > max_lines:
|
|
89
|
+
truncated = diff_lines[: max_lines - 1]
|
|
90
|
+
truncated.append("...")
|
|
91
|
+
return "\n".join(truncated)
|
|
92
|
+
return "\n".join(diff_lines)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class FileOpMetrics:
|
|
97
|
+
"""Line and byte level metrics for a file operation."""
|
|
98
|
+
|
|
99
|
+
lines_read: int = 0
|
|
100
|
+
start_line: int | None = None
|
|
101
|
+
end_line: int | None = None
|
|
102
|
+
lines_written: int = 0
|
|
103
|
+
lines_added: int = 0
|
|
104
|
+
lines_removed: int = 0
|
|
105
|
+
bytes_written: int = 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class FileOperationRecord:
|
|
110
|
+
"""Track a single filesystem tool call."""
|
|
111
|
+
|
|
112
|
+
tool_name: str
|
|
113
|
+
display_path: str
|
|
114
|
+
physical_path: Path | None
|
|
115
|
+
tool_call_id: str | None
|
|
116
|
+
args: dict[str, Any] = field(default_factory=dict)
|
|
117
|
+
status: FileOpStatus = "pending"
|
|
118
|
+
error: str | None = None
|
|
119
|
+
metrics: FileOpMetrics = field(default_factory=FileOpMetrics)
|
|
120
|
+
diff: str | None = None
|
|
121
|
+
before_content: str | None = None
|
|
122
|
+
after_content: str | None = None
|
|
123
|
+
read_output: str | None = None
|
|
124
|
+
hitl_approved: bool = False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def resolve_physical_path(
|
|
128
|
+
path_str: str | None, assistant_id: str | None
|
|
129
|
+
) -> Path | None:
|
|
130
|
+
"""Convert a virtual/relative path to a physical filesystem path.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Resolved physical Path, or None if path is empty or resolution fails.
|
|
134
|
+
"""
|
|
135
|
+
if not path_str:
|
|
136
|
+
return None
|
|
137
|
+
try:
|
|
138
|
+
if assistant_id and path_str.startswith("/memories/"):
|
|
139
|
+
from docagent_cli.config import settings
|
|
140
|
+
|
|
141
|
+
agent_dir = settings.get_agent_dir(assistant_id)
|
|
142
|
+
suffix = path_str.removeprefix("/memories/").lstrip("/")
|
|
143
|
+
return (agent_dir / suffix).resolve()
|
|
144
|
+
path = Path(path_str)
|
|
145
|
+
if path.is_absolute():
|
|
146
|
+
return path
|
|
147
|
+
return (Path.cwd() / path).resolve()
|
|
148
|
+
except (OSError, ValueError):
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def format_display_path(path_str: str | None) -> str:
|
|
153
|
+
"""Format a path for display.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Formatted path string suitable for display.
|
|
157
|
+
"""
|
|
158
|
+
if not path_str:
|
|
159
|
+
return "(unknown)"
|
|
160
|
+
try:
|
|
161
|
+
path = Path(path_str)
|
|
162
|
+
if path.is_absolute():
|
|
163
|
+
return path.name or str(path)
|
|
164
|
+
return str(path)
|
|
165
|
+
except (OSError, ValueError):
|
|
166
|
+
return str(path_str)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def build_approval_preview(
|
|
170
|
+
tool_name: str,
|
|
171
|
+
args: dict[str, Any],
|
|
172
|
+
assistant_id: str | None,
|
|
173
|
+
) -> ApprovalPreview | None:
|
|
174
|
+
"""Collect summary info and diff for HITL approvals.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
ApprovalPreview with diff and details, or None if tool not supported.
|
|
178
|
+
"""
|
|
179
|
+
path_str = str(args.get("file_path") or args.get("path") or "")
|
|
180
|
+
display_path = format_display_path(path_str)
|
|
181
|
+
physical_path = resolve_physical_path(path_str, assistant_id)
|
|
182
|
+
|
|
183
|
+
if tool_name == "write_file":
|
|
184
|
+
content = str(args.get("content", ""))
|
|
185
|
+
before = (
|
|
186
|
+
_safe_read(physical_path)
|
|
187
|
+
if physical_path and physical_path.exists()
|
|
188
|
+
else ""
|
|
189
|
+
)
|
|
190
|
+
after = content
|
|
191
|
+
diff = compute_unified_diff(before or "", after, display_path, max_lines=100)
|
|
192
|
+
additions = 0
|
|
193
|
+
if diff:
|
|
194
|
+
additions = sum(
|
|
195
|
+
1
|
|
196
|
+
for line in diff.splitlines()
|
|
197
|
+
if line.startswith("+") and not line.startswith("+++")
|
|
198
|
+
)
|
|
199
|
+
total_lines = _count_lines(after)
|
|
200
|
+
details = [
|
|
201
|
+
f"File: {path_str}",
|
|
202
|
+
"Action: Create new file"
|
|
203
|
+
+ (" (overwrites existing content)" if before else ""),
|
|
204
|
+
f"Lines to write: {additions or total_lines}",
|
|
205
|
+
]
|
|
206
|
+
return ApprovalPreview(
|
|
207
|
+
title=f"Write {display_path}",
|
|
208
|
+
details=details,
|
|
209
|
+
diff=diff,
|
|
210
|
+
diff_title=f"Diff {display_path}",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if tool_name == "edit_file":
|
|
214
|
+
if physical_path is None:
|
|
215
|
+
return ApprovalPreview(
|
|
216
|
+
title=f"Update {display_path}",
|
|
217
|
+
details=[f"File: {path_str}", "Action: Replace text"],
|
|
218
|
+
error="Unable to resolve file path.",
|
|
219
|
+
)
|
|
220
|
+
before = _safe_read(physical_path)
|
|
221
|
+
if before is None:
|
|
222
|
+
return ApprovalPreview(
|
|
223
|
+
title=f"Update {display_path}",
|
|
224
|
+
details=[f"File: {path_str}", "Action: Replace text"],
|
|
225
|
+
error="Unable to read current file contents.",
|
|
226
|
+
)
|
|
227
|
+
old_string = str(args.get("old_string", ""))
|
|
228
|
+
new_string = str(args.get("new_string", ""))
|
|
229
|
+
replace_all = bool(args.get("replace_all"))
|
|
230
|
+
from deepagents.backends.utils import perform_string_replacement
|
|
231
|
+
|
|
232
|
+
replacement = perform_string_replacement(
|
|
233
|
+
before, old_string, new_string, replace_all
|
|
234
|
+
)
|
|
235
|
+
if isinstance(replacement, str):
|
|
236
|
+
return ApprovalPreview(
|
|
237
|
+
title=f"Update {display_path}",
|
|
238
|
+
details=[f"File: {path_str}", "Action: Replace text"],
|
|
239
|
+
error=replacement,
|
|
240
|
+
)
|
|
241
|
+
after, occurrences = replacement
|
|
242
|
+
diff = compute_unified_diff(before, after, display_path, max_lines=None)
|
|
243
|
+
additions = 0
|
|
244
|
+
deletions = 0
|
|
245
|
+
if diff:
|
|
246
|
+
additions = sum(
|
|
247
|
+
1
|
|
248
|
+
for line in diff.splitlines()
|
|
249
|
+
if line.startswith("+") and not line.startswith("+++")
|
|
250
|
+
)
|
|
251
|
+
deletions = sum(
|
|
252
|
+
1
|
|
253
|
+
for line in diff.splitlines()
|
|
254
|
+
if line.startswith("-") and not line.startswith("---")
|
|
255
|
+
)
|
|
256
|
+
action = "all occurrences" if replace_all else "single occurrence"
|
|
257
|
+
details = [
|
|
258
|
+
f"File: {path_str}",
|
|
259
|
+
f"Action: Replace text ({action})",
|
|
260
|
+
f"Occurrences matched: {occurrences}",
|
|
261
|
+
f"Lines changed: +{additions} / -{deletions}",
|
|
262
|
+
]
|
|
263
|
+
return ApprovalPreview(
|
|
264
|
+
title=f"Update {display_path}",
|
|
265
|
+
details=details,
|
|
266
|
+
diff=diff,
|
|
267
|
+
diff_title=f"Diff {display_path}",
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class FileOpTracker:
|
|
274
|
+
"""Collect file operation metrics during a CLI interaction."""
|
|
275
|
+
|
|
276
|
+
def __init__(
|
|
277
|
+
self, *, assistant_id: str | None, backend: BackendProtocol | None = None
|
|
278
|
+
) -> None:
|
|
279
|
+
"""Initialize the tracker."""
|
|
280
|
+
self.assistant_id = assistant_id
|
|
281
|
+
self.backend = backend
|
|
282
|
+
self.active: dict[str | None, FileOperationRecord] = {}
|
|
283
|
+
self.completed: list[FileOperationRecord] = []
|
|
284
|
+
|
|
285
|
+
def start_operation(
|
|
286
|
+
self, tool_name: str, args: dict[str, Any], tool_call_id: str | None
|
|
287
|
+
) -> None:
|
|
288
|
+
"""Begin tracking a file operation.
|
|
289
|
+
|
|
290
|
+
Creates a record for the operation and, for write/edit operations,
|
|
291
|
+
captures the file's content before modification.
|
|
292
|
+
"""
|
|
293
|
+
if tool_name not in {"read_file", "write_file", "edit_file"}:
|
|
294
|
+
return
|
|
295
|
+
path_str = str(args.get("file_path") or args.get("path") or "")
|
|
296
|
+
display_path = format_display_path(path_str)
|
|
297
|
+
record = FileOperationRecord(
|
|
298
|
+
tool_name=tool_name,
|
|
299
|
+
display_path=display_path,
|
|
300
|
+
physical_path=resolve_physical_path(path_str, self.assistant_id),
|
|
301
|
+
tool_call_id=tool_call_id,
|
|
302
|
+
args=args,
|
|
303
|
+
)
|
|
304
|
+
if tool_name in {"write_file", "edit_file"}:
|
|
305
|
+
if self.backend and path_str:
|
|
306
|
+
try:
|
|
307
|
+
responses = self.backend.download_files([path_str])
|
|
308
|
+
if (
|
|
309
|
+
responses
|
|
310
|
+
and responses[0].content is not None
|
|
311
|
+
and responses[0].error is None
|
|
312
|
+
):
|
|
313
|
+
record.before_content = responses[0].content.decode("utf-8")
|
|
314
|
+
else:
|
|
315
|
+
record.before_content = ""
|
|
316
|
+
except (OSError, UnicodeDecodeError, AttributeError) as e:
|
|
317
|
+
logger.debug(
|
|
318
|
+
"Failed to read before_content for %s: %s", path_str, e
|
|
319
|
+
)
|
|
320
|
+
record.before_content = ""
|
|
321
|
+
elif record.physical_path:
|
|
322
|
+
record.before_content = _safe_read(record.physical_path) or ""
|
|
323
|
+
self.active[tool_call_id] = record
|
|
324
|
+
|
|
325
|
+
def complete_with_message(self, tool_message: Any) -> FileOperationRecord | None: # noqa: ANN401 # Tool message type is dynamic
|
|
326
|
+
"""Complete a file operation with the tool message result.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
The completed FileOperationRecord, or None if no matching operation.
|
|
330
|
+
"""
|
|
331
|
+
tool_call_id = getattr(tool_message, "tool_call_id", None)
|
|
332
|
+
record = self.active.get(tool_call_id)
|
|
333
|
+
if record is None:
|
|
334
|
+
return None
|
|
335
|
+
|
|
336
|
+
content = tool_message.content
|
|
337
|
+
if isinstance(content, list):
|
|
338
|
+
# Some tool messages may return list segments; join them for analysis.
|
|
339
|
+
joined = []
|
|
340
|
+
for item in content:
|
|
341
|
+
if isinstance(item, str):
|
|
342
|
+
joined.append(item)
|
|
343
|
+
else:
|
|
344
|
+
joined.append(str(item))
|
|
345
|
+
content_text = "\n".join(joined)
|
|
346
|
+
else:
|
|
347
|
+
content_text = str(content) if content is not None else ""
|
|
348
|
+
|
|
349
|
+
if getattr(
|
|
350
|
+
tool_message, "status", "success"
|
|
351
|
+
) != "success" or content_text.lower().startswith("error"):
|
|
352
|
+
record.status = "error"
|
|
353
|
+
record.error = content_text
|
|
354
|
+
self._finalize(record)
|
|
355
|
+
return record
|
|
356
|
+
|
|
357
|
+
record.status = "success"
|
|
358
|
+
|
|
359
|
+
if record.tool_name == "read_file":
|
|
360
|
+
record.read_output = content_text
|
|
361
|
+
lines = _count_lines(content_text)
|
|
362
|
+
record.metrics.lines_read = lines
|
|
363
|
+
offset = record.args.get("offset")
|
|
364
|
+
limit = record.args.get("limit")
|
|
365
|
+
if isinstance(offset, int):
|
|
366
|
+
if offset > lines:
|
|
367
|
+
offset = 0
|
|
368
|
+
record.metrics.start_line = offset + 1
|
|
369
|
+
if lines:
|
|
370
|
+
record.metrics.end_line = offset + lines
|
|
371
|
+
elif lines:
|
|
372
|
+
record.metrics.start_line = 1
|
|
373
|
+
record.metrics.end_line = lines
|
|
374
|
+
if isinstance(limit, int) and lines > limit:
|
|
375
|
+
record.metrics.end_line = (record.metrics.start_line or 1) + limit - 1
|
|
376
|
+
else:
|
|
377
|
+
# For write/edit operations, read back from backend (or local filesystem)
|
|
378
|
+
self._populate_after_content(record)
|
|
379
|
+
if record.after_content is None:
|
|
380
|
+
record.status = "error"
|
|
381
|
+
record.error = "Could not read updated file content."
|
|
382
|
+
self._finalize(record)
|
|
383
|
+
return record
|
|
384
|
+
record.metrics.lines_written = _count_lines(record.after_content)
|
|
385
|
+
before_lines = _count_lines(record.before_content or "")
|
|
386
|
+
diff = compute_unified_diff(
|
|
387
|
+
record.before_content or "",
|
|
388
|
+
record.after_content,
|
|
389
|
+
record.display_path,
|
|
390
|
+
max_lines=100,
|
|
391
|
+
)
|
|
392
|
+
record.diff = diff
|
|
393
|
+
if diff:
|
|
394
|
+
additions = sum(
|
|
395
|
+
1
|
|
396
|
+
for line in diff.splitlines()
|
|
397
|
+
if line.startswith("+") and not line.startswith("+++")
|
|
398
|
+
)
|
|
399
|
+
deletions = sum(
|
|
400
|
+
1
|
|
401
|
+
for line in diff.splitlines()
|
|
402
|
+
if line.startswith("-") and not line.startswith("---")
|
|
403
|
+
)
|
|
404
|
+
record.metrics.lines_added = additions
|
|
405
|
+
record.metrics.lines_removed = deletions
|
|
406
|
+
elif record.tool_name == "write_file" and not (record.before_content or ""):
|
|
407
|
+
record.metrics.lines_added = record.metrics.lines_written
|
|
408
|
+
record.metrics.bytes_written = len(record.after_content.encode("utf-8"))
|
|
409
|
+
if (
|
|
410
|
+
record.diff is None
|
|
411
|
+
and (record.before_content or "") != record.after_content
|
|
412
|
+
):
|
|
413
|
+
record.diff = compute_unified_diff(
|
|
414
|
+
record.before_content or "",
|
|
415
|
+
record.after_content,
|
|
416
|
+
record.display_path,
|
|
417
|
+
max_lines=100,
|
|
418
|
+
)
|
|
419
|
+
if record.diff is None and before_lines != record.metrics.lines_written:
|
|
420
|
+
record.metrics.lines_added = max(
|
|
421
|
+
record.metrics.lines_written - before_lines, 0
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
self._finalize(record)
|
|
425
|
+
return record
|
|
426
|
+
|
|
427
|
+
def mark_hitl_approved(self, tool_name: str, args: dict[str, Any]) -> None:
|
|
428
|
+
"""Mark operations matching tool_name and file_path as HIL-approved."""
|
|
429
|
+
file_path = args.get("file_path") or args.get("path")
|
|
430
|
+
if not file_path:
|
|
431
|
+
return
|
|
432
|
+
|
|
433
|
+
# Mark all active records that match
|
|
434
|
+
for record in self.active.values():
|
|
435
|
+
if record.tool_name == tool_name:
|
|
436
|
+
record_path = record.args.get("file_path") or record.args.get("path")
|
|
437
|
+
if record_path == file_path:
|
|
438
|
+
record.hitl_approved = True
|
|
439
|
+
|
|
440
|
+
def _populate_after_content(self, record: FileOperationRecord) -> None:
|
|
441
|
+
# Use backend if available (works for any BackendProtocol implementation)
|
|
442
|
+
if self.backend:
|
|
443
|
+
try:
|
|
444
|
+
file_path = record.args.get("file_path") or record.args.get("path")
|
|
445
|
+
if file_path:
|
|
446
|
+
responses = self.backend.download_files([file_path])
|
|
447
|
+
if (
|
|
448
|
+
responses
|
|
449
|
+
and responses[0].content is not None
|
|
450
|
+
and responses[0].error is None
|
|
451
|
+
):
|
|
452
|
+
record.after_content = responses[0].content.decode("utf-8")
|
|
453
|
+
else:
|
|
454
|
+
record.after_content = None
|
|
455
|
+
else:
|
|
456
|
+
record.after_content = None
|
|
457
|
+
except (OSError, UnicodeDecodeError, AttributeError) as e:
|
|
458
|
+
logger.debug(
|
|
459
|
+
"Failed to read after_content for %s: %s",
|
|
460
|
+
record.args.get("file_path") or record.args.get("path"),
|
|
461
|
+
e,
|
|
462
|
+
)
|
|
463
|
+
record.after_content = None
|
|
464
|
+
else:
|
|
465
|
+
# Fallback: direct filesystem read when no backend provided
|
|
466
|
+
if record.physical_path is None:
|
|
467
|
+
record.after_content = None
|
|
468
|
+
return
|
|
469
|
+
record.after_content = _safe_read(record.physical_path)
|
|
470
|
+
|
|
471
|
+
def _finalize(self, record: FileOperationRecord) -> None:
|
|
472
|
+
self.completed.append(record)
|
|
473
|
+
self.active.pop(record.tool_call_id, None)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Lightweight text-formatting helpers.
|
|
2
|
+
|
|
3
|
+
Keep this module free of heavy dependencies so it can be imported anywhere
|
|
4
|
+
in the CLI without pulling in large frameworks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def format_duration(seconds: float) -> str:
|
|
11
|
+
"""Format a duration in seconds into a human-readable string.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
seconds: Duration in seconds.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Formatted string like `"5s"`, `"2.3s"`, `"5m 12s"`, or `"1h 23m 4s"`.
|
|
18
|
+
"""
|
|
19
|
+
rounded = round(seconds, 1)
|
|
20
|
+
if rounded < 60: # noqa: PLR2004
|
|
21
|
+
if rounded % 1 == 0:
|
|
22
|
+
return f"{int(rounded)}s"
|
|
23
|
+
return f"{rounded:.1f}s"
|
|
24
|
+
minutes, secs = divmod(int(rounded), 60)
|
|
25
|
+
if minutes < 60: # noqa: PLR2004
|
|
26
|
+
return f"{minutes}m {secs}s"
|
|
27
|
+
hours, minutes = divmod(minutes, 60)
|
|
28
|
+
return f"{hours}h {minutes}m {secs}s"
|
docagent_cli/hooks.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Lightweight hook dispatch for external tool integration.
|
|
2
|
+
|
|
3
|
+
Loads hook configuration from `~/.docagent/hooks.json` and fires matching
|
|
4
|
+
commands with JSON payloads on stdin. Subprocess work is offloaded to a
|
|
5
|
+
background thread so the caller's event loop is never stalled. Failures are
|
|
6
|
+
logged but never bubble up to the caller.
|
|
7
|
+
|
|
8
|
+
Config format (`~/.docagent/hooks.json`):
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{"hooks": [{"command": ["bash", "adapter.sh"], "events": ["session.start"]}]}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
If `events` is omitted or empty the hook receives **all** events.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import json
|
|
21
|
+
import logging
|
|
22
|
+
import subprocess # noqa: S404
|
|
23
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
_hooks_config: list[dict[str, Any]] | None = None
|
|
29
|
+
"""Cached config — loaded lazily on first dispatch."""
|
|
30
|
+
|
|
31
|
+
_background_tasks: set[asyncio.Task[None]] = set()
|
|
32
|
+
"""Strong references to fire-and-forget tasks to prevent GC."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _load_hooks() -> list[dict[str, Any]]:
|
|
36
|
+
"""Load and cache hook definitions from the config file.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
An empty list when the file is missing or malformed so that normal
|
|
40
|
+
execution is never interrupted.
|
|
41
|
+
"""
|
|
42
|
+
global _hooks_config # noqa: PLW0603
|
|
43
|
+
if _hooks_config is not None:
|
|
44
|
+
return _hooks_config
|
|
45
|
+
|
|
46
|
+
from docagent_cli.model_config import DEFAULT_CONFIG_DIR
|
|
47
|
+
|
|
48
|
+
hooks_path = DEFAULT_CONFIG_DIR / "hooks.json"
|
|
49
|
+
|
|
50
|
+
if not hooks_path.is_file():
|
|
51
|
+
_hooks_config = []
|
|
52
|
+
return _hooks_config
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
data = json.loads(hooks_path.read_text())
|
|
56
|
+
if not isinstance(data, dict):
|
|
57
|
+
logger.warning(
|
|
58
|
+
"Hooks config at %s must be a JSON object, got %s",
|
|
59
|
+
hooks_path,
|
|
60
|
+
type(data).__name__,
|
|
61
|
+
)
|
|
62
|
+
_hooks_config = []
|
|
63
|
+
return _hooks_config
|
|
64
|
+
hooks = data.get("hooks", [])
|
|
65
|
+
if not isinstance(hooks, list):
|
|
66
|
+
logger.warning(
|
|
67
|
+
"Hooks config 'hooks' key at %s must be a list, got %s",
|
|
68
|
+
hooks_path,
|
|
69
|
+
type(hooks).__name__,
|
|
70
|
+
)
|
|
71
|
+
_hooks_config = []
|
|
72
|
+
return _hooks_config
|
|
73
|
+
_hooks_config = hooks
|
|
74
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
75
|
+
logger.warning("Failed to load hooks config from %s: %s", hooks_path, exc)
|
|
76
|
+
_hooks_config = []
|
|
77
|
+
|
|
78
|
+
return _hooks_config
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _run_single_hook(command: list[str], event: str, payload_bytes: bytes) -> None:
|
|
82
|
+
"""Execute a single hook command, writing the JSON payload to its stdin.
|
|
83
|
+
|
|
84
|
+
Uses `subprocess.run` which automatically kills the child process on
|
|
85
|
+
timeout, preventing zombie/orphan process leaks.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
command: The command and arguments to run.
|
|
89
|
+
event: Event name (for logging).
|
|
90
|
+
payload_bytes: JSON payload to write to the command's stdin.
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
subprocess.run( # noqa: S603
|
|
94
|
+
command,
|
|
95
|
+
input=payload_bytes,
|
|
96
|
+
stdout=subprocess.DEVNULL,
|
|
97
|
+
stderr=subprocess.DEVNULL,
|
|
98
|
+
start_new_session=True,
|
|
99
|
+
timeout=5,
|
|
100
|
+
check=False,
|
|
101
|
+
)
|
|
102
|
+
except subprocess.TimeoutExpired:
|
|
103
|
+
logger.warning("Hook command timed out (>5s) for event %s: %s", event, command)
|
|
104
|
+
except (FileNotFoundError, PermissionError) as exc:
|
|
105
|
+
logger.warning("Hook command failed for event %s: %s — %s", event, command, exc)
|
|
106
|
+
except Exception:
|
|
107
|
+
logger.debug(
|
|
108
|
+
"Hook dispatch failed for event %s: %s",
|
|
109
|
+
event,
|
|
110
|
+
command,
|
|
111
|
+
exc_info=True,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _dispatch_hook_sync(
|
|
116
|
+
event: str, payload_bytes: bytes, hooks: list[dict[str, Any]]
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Dispatch matching hooks, running them concurrently via a thread pool.
|
|
119
|
+
|
|
120
|
+
Iterates over all configured hooks, skipping those whose event filter
|
|
121
|
+
does not match or whose `command` is missing/invalid. Matching hooks are
|
|
122
|
+
executed concurrently with a 5-second timeout per command. Errors are caught
|
|
123
|
+
per-hook and logged without propagating.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
event: Dotted event name (e.g. `'session.start'`).
|
|
127
|
+
payload_bytes: JSON payload to write to each command's stdin.
|
|
128
|
+
hooks: List of hook definition dicts from the config file.
|
|
129
|
+
"""
|
|
130
|
+
matching: list[list[str]] = []
|
|
131
|
+
for hook in hooks:
|
|
132
|
+
command = hook.get("command")
|
|
133
|
+
if not isinstance(command, list) or not command:
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
events = hook.get("events")
|
|
137
|
+
# Empty/missing events list means "subscribe to everything".
|
|
138
|
+
if events and event not in events:
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
matching.append(command)
|
|
142
|
+
|
|
143
|
+
if not matching:
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
if len(matching) == 1:
|
|
147
|
+
_run_single_hook(matching[0], event, payload_bytes)
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
with ThreadPoolExecutor(max_workers=len(matching)) as pool:
|
|
151
|
+
futures = [
|
|
152
|
+
pool.submit(_run_single_hook, cmd, event, payload_bytes) for cmd in matching
|
|
153
|
+
]
|
|
154
|
+
for future in futures:
|
|
155
|
+
future.result()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def dispatch_hook(event: str, payload: dict[str, Any]) -> None:
|
|
159
|
+
"""Fire matching hook commands with `payload` serialized as JSON on stdin.
|
|
160
|
+
|
|
161
|
+
The `event` name is automatically injected into the payload under the
|
|
162
|
+
`"event"` key so callers don't need to duplicate it.
|
|
163
|
+
|
|
164
|
+
The blocking subprocess work is offloaded to a thread so the caller's
|
|
165
|
+
event loop is never stalled. Matching hooks run concurrently, each with
|
|
166
|
+
a 5-second timeout. Errors are logged and never propagated.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
event: Dotted event name (e.g. `'session.start'`).
|
|
170
|
+
payload: Arbitrary JSON-serializable dict sent on the command's stdin.
|
|
171
|
+
"""
|
|
172
|
+
try:
|
|
173
|
+
hooks = _load_hooks()
|
|
174
|
+
if not hooks:
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
payload_bytes = json.dumps({"event": event, **payload}).encode()
|
|
178
|
+
await asyncio.to_thread(_dispatch_hook_sync, event, payload_bytes, hooks)
|
|
179
|
+
except Exception:
|
|
180
|
+
logger.warning(
|
|
181
|
+
"Unexpected error in dispatch_hook for event %s",
|
|
182
|
+
event,
|
|
183
|
+
exc_info=True,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def dispatch_hook_fire_and_forget(event: str, payload: dict[str, Any]) -> None:
|
|
188
|
+
"""Schedule `dispatch_hook` as a background task with a strong reference.
|
|
189
|
+
|
|
190
|
+
Use this instead of bare `create_task(dispatch_hook(...))` to prevent the
|
|
191
|
+
task from being garbage collected before completion.
|
|
192
|
+
|
|
193
|
+
Safe to call from sync code as long as an event loop is running.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
event: Dotted event name (e.g. `'session.start'`).
|
|
197
|
+
payload: Arbitrary JSON-serializable dict sent on the command's stdin.
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
loop = asyncio.get_running_loop()
|
|
201
|
+
except RuntimeError:
|
|
202
|
+
logger.debug("No running event loop; skipping hook for %s", event)
|
|
203
|
+
return
|
|
204
|
+
task = loop.create_task(dispatch_hook(event, payload))
|
|
205
|
+
_background_tasks.add(task)
|
|
206
|
+
task.add_done_callback(_background_tasks.discard)
|