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,691 @@
|
|
|
1
|
+
"""Autocomplete system for @ mentions and / commands.
|
|
2
|
+
|
|
3
|
+
This is a custom implementation that handles trigger-based completion
|
|
4
|
+
for slash commands (/) and file mentions (@).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import contextlib
|
|
11
|
+
import shutil
|
|
12
|
+
|
|
13
|
+
# S404: subprocess is required for git ls-files to get project file list
|
|
14
|
+
import subprocess # noqa: S404
|
|
15
|
+
from difflib import SequenceMatcher
|
|
16
|
+
from enum import StrEnum
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import TYPE_CHECKING, Protocol
|
|
19
|
+
|
|
20
|
+
from docagent_cli.project_utils import find_project_root
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get_git_executable() -> str | None:
|
|
24
|
+
"""Get full path to git executable using shutil.which().
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Full path to git executable, or None if not found.
|
|
28
|
+
"""
|
|
29
|
+
return shutil.which("git")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from textual import events
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CompletionResult(StrEnum):
|
|
37
|
+
"""Result of handling a key event in the completion system."""
|
|
38
|
+
|
|
39
|
+
IGNORED = "ignored" # Key not handled, let default behavior proceed
|
|
40
|
+
HANDLED = "handled" # Key handled, prevent default
|
|
41
|
+
SUBMIT = "submit" # Key triggers submission (e.g., Enter on slash command)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CompletionView(Protocol):
|
|
45
|
+
"""Protocol for views that can display completion suggestions."""
|
|
46
|
+
|
|
47
|
+
def render_completion_suggestions(
|
|
48
|
+
self, suggestions: list[tuple[str, str]], selected_index: int
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Render the completion suggestions popup.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
suggestions: List of (label, description) tuples
|
|
54
|
+
selected_index: Index of currently selected item
|
|
55
|
+
"""
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
def clear_completion_suggestions(self) -> None:
|
|
59
|
+
"""Hide/clear the completion suggestions popup."""
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
def replace_completion_range(self, start: int, end: int, replacement: str) -> None:
|
|
63
|
+
"""Replace text in the input from start to end with replacement.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
start: Start index in the input text
|
|
67
|
+
end: End index in the input text
|
|
68
|
+
replacement: Text to insert
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CompletionController(Protocol):
|
|
74
|
+
"""Protocol for completion controllers."""
|
|
75
|
+
|
|
76
|
+
def can_handle(self, text: str, cursor_index: int) -> bool:
|
|
77
|
+
"""Check if this controller can handle the current input state."""
|
|
78
|
+
...
|
|
79
|
+
|
|
80
|
+
def on_text_changed(self, text: str, cursor_index: int) -> None:
|
|
81
|
+
"""Called when input text changes."""
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
def on_key(
|
|
85
|
+
self, event: events.Key, text: str, cursor_index: int
|
|
86
|
+
) -> CompletionResult:
|
|
87
|
+
"""Handle a key event. Returns how the event was handled."""
|
|
88
|
+
...
|
|
89
|
+
|
|
90
|
+
def reset(self) -> None:
|
|
91
|
+
"""Reset/clear the completion state."""
|
|
92
|
+
...
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ============================================================================
|
|
96
|
+
# Slash Command Completion
|
|
97
|
+
# ============================================================================
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
MAX_SUGGESTIONS = 10
|
|
101
|
+
"""UI cap so the completion popup doesn't get unwieldy."""
|
|
102
|
+
|
|
103
|
+
_MIN_SLASH_FUZZY_SCORE = 25
|
|
104
|
+
"""Minimum score for slash-command fuzzy matches."""
|
|
105
|
+
|
|
106
|
+
_MIN_DESC_SEARCH_LEN = 2
|
|
107
|
+
"""Minimum query length to search command descriptions (avoids single-char noise)."""
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class SlashCommandController:
|
|
111
|
+
"""Controller for / slash command completion."""
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
commands: list[tuple[str, str, str]],
|
|
116
|
+
view: CompletionView,
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Initialize the slash command controller.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
commands: List of `(command, description, hidden_keywords)` tuples.
|
|
122
|
+
view: View to render suggestions to.
|
|
123
|
+
"""
|
|
124
|
+
self._commands = commands
|
|
125
|
+
self._view = view
|
|
126
|
+
self._suggestions: list[tuple[str, str]] = []
|
|
127
|
+
self._selected_index = 0
|
|
128
|
+
|
|
129
|
+
def update_commands(self, commands: list[tuple[str, str, str]]) -> None:
|
|
130
|
+
"""Replace the commands list and reset suggestions.
|
|
131
|
+
|
|
132
|
+
Used to merge dynamically discovered skill commands with
|
|
133
|
+
the static command registry at runtime.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
commands: New list of `(command, description, hidden_keywords)` tuples.
|
|
137
|
+
"""
|
|
138
|
+
self._commands = commands
|
|
139
|
+
self.reset()
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def can_handle(text: str, cursor_index: int) -> bool: # noqa: ARG004 # Required by AutocompleteProvider interface
|
|
143
|
+
"""Handle input that starts with /.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
True if text starts with slash, indicating a command.
|
|
147
|
+
"""
|
|
148
|
+
return text.startswith("/")
|
|
149
|
+
|
|
150
|
+
def reset(self) -> None:
|
|
151
|
+
"""Clear suggestions."""
|
|
152
|
+
if self._suggestions:
|
|
153
|
+
self._suggestions.clear()
|
|
154
|
+
self._selected_index = 0
|
|
155
|
+
self._view.clear_completion_suggestions()
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def _score_command(search: str, cmd: str, desc: str, keywords: str = "") -> float:
|
|
159
|
+
"""Score a command against a search string. Higher = better match.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
search: Lowercase search string (without leading `/`).
|
|
163
|
+
cmd: Command name (e.g. `'/help'`).
|
|
164
|
+
desc: Command description text.
|
|
165
|
+
keywords: Space-separated hidden keywords for matching.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Score value where higher indicates better match quality.
|
|
169
|
+
"""
|
|
170
|
+
if not search:
|
|
171
|
+
return 0.0
|
|
172
|
+
name = cmd.lstrip("/").lower()
|
|
173
|
+
lower_desc = desc.lower()
|
|
174
|
+
# Prefix match on command name — highest priority
|
|
175
|
+
if name.startswith(search):
|
|
176
|
+
return 200.0
|
|
177
|
+
# Substring match on command name
|
|
178
|
+
if search in name:
|
|
179
|
+
return 150.0
|
|
180
|
+
# Hidden keyword match — treated like a word-boundary description match
|
|
181
|
+
if keywords and len(search) >= _MIN_DESC_SEARCH_LEN:
|
|
182
|
+
for kw in keywords.lower().split():
|
|
183
|
+
if kw.startswith(search) or search in kw:
|
|
184
|
+
return 120.0
|
|
185
|
+
# Substring match on description (require ≥2 chars to avoid single-letter noise)
|
|
186
|
+
if len(search) >= _MIN_DESC_SEARCH_LEN and search in lower_desc:
|
|
187
|
+
idx = lower_desc.find(search)
|
|
188
|
+
# Word-boundary bonus: match at start of description or after a space
|
|
189
|
+
if idx == 0 or lower_desc[idx - 1] == " ":
|
|
190
|
+
return 110.0
|
|
191
|
+
return 90.0
|
|
192
|
+
# Fuzzy match via SequenceMatcher on name + desc
|
|
193
|
+
name_ratio = SequenceMatcher(None, search, name).ratio()
|
|
194
|
+
desc_ratio = SequenceMatcher(None, search, lower_desc).ratio()
|
|
195
|
+
best = max(name_ratio * 60, desc_ratio * 30)
|
|
196
|
+
return best if best >= _MIN_SLASH_FUZZY_SCORE else 0.0
|
|
197
|
+
|
|
198
|
+
def on_text_changed(self, text: str, cursor_index: int) -> None:
|
|
199
|
+
"""Update suggestions when text changes."""
|
|
200
|
+
if cursor_index < 0 or cursor_index > len(text):
|
|
201
|
+
self.reset()
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
if not self.can_handle(text, cursor_index):
|
|
205
|
+
self.reset()
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
# Get the search string (text after /)
|
|
209
|
+
search = text[1:cursor_index].lower()
|
|
210
|
+
|
|
211
|
+
if not search:
|
|
212
|
+
# No search text — show all commands (display only cmd + desc)
|
|
213
|
+
suggestions = [(cmd, desc) for cmd, desc, _ in self._commands][
|
|
214
|
+
:MAX_SUGGESTIONS
|
|
215
|
+
]
|
|
216
|
+
else:
|
|
217
|
+
# Score and filter commands using fuzzy matching
|
|
218
|
+
scored = [
|
|
219
|
+
(score, cmd, desc)
|
|
220
|
+
for cmd, desc, kw in self._commands
|
|
221
|
+
if (score := self._score_command(search, cmd, desc, kw)) > 0
|
|
222
|
+
]
|
|
223
|
+
scored.sort(key=lambda x: -x[0])
|
|
224
|
+
suggestions = [(cmd, desc) for _, cmd, desc in scored[:MAX_SUGGESTIONS]]
|
|
225
|
+
|
|
226
|
+
if suggestions:
|
|
227
|
+
self._suggestions = suggestions
|
|
228
|
+
self._selected_index = 0
|
|
229
|
+
self._view.render_completion_suggestions(
|
|
230
|
+
self._suggestions, self._selected_index
|
|
231
|
+
)
|
|
232
|
+
else:
|
|
233
|
+
self.reset()
|
|
234
|
+
|
|
235
|
+
def on_key(
|
|
236
|
+
self, event: events.Key, _text: str, cursor_index: int
|
|
237
|
+
) -> CompletionResult:
|
|
238
|
+
"""Handle key events for navigation and selection.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
CompletionResult indicating how the key was handled.
|
|
242
|
+
"""
|
|
243
|
+
if not self._suggestions:
|
|
244
|
+
return CompletionResult.IGNORED
|
|
245
|
+
|
|
246
|
+
match event.key:
|
|
247
|
+
case "tab":
|
|
248
|
+
if self._apply_selected_completion(cursor_index):
|
|
249
|
+
return CompletionResult.HANDLED
|
|
250
|
+
return CompletionResult.IGNORED
|
|
251
|
+
case "enter":
|
|
252
|
+
if self._apply_selected_completion(cursor_index):
|
|
253
|
+
return CompletionResult.SUBMIT
|
|
254
|
+
return CompletionResult.HANDLED
|
|
255
|
+
case "down":
|
|
256
|
+
self._move_selection(1)
|
|
257
|
+
return CompletionResult.HANDLED
|
|
258
|
+
case "up":
|
|
259
|
+
self._move_selection(-1)
|
|
260
|
+
return CompletionResult.HANDLED
|
|
261
|
+
case "escape":
|
|
262
|
+
self.reset()
|
|
263
|
+
return CompletionResult.HANDLED
|
|
264
|
+
case _:
|
|
265
|
+
return CompletionResult.IGNORED
|
|
266
|
+
|
|
267
|
+
def _move_selection(self, delta: int) -> None:
|
|
268
|
+
"""Move selection up or down."""
|
|
269
|
+
if not self._suggestions:
|
|
270
|
+
return
|
|
271
|
+
count = len(self._suggestions)
|
|
272
|
+
self._selected_index = (self._selected_index + delta) % count
|
|
273
|
+
self._view.render_completion_suggestions(
|
|
274
|
+
self._suggestions, self._selected_index
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def _apply_selected_completion(self, cursor_index: int) -> bool:
|
|
278
|
+
"""Apply the currently selected completion.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
True if completion was applied, False if no suggestions.
|
|
282
|
+
"""
|
|
283
|
+
if not self._suggestions:
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
command, _ = self._suggestions[self._selected_index]
|
|
287
|
+
# Replace from start to cursor with the command
|
|
288
|
+
self._view.replace_completion_range(0, cursor_index, command)
|
|
289
|
+
self.reset()
|
|
290
|
+
return True
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# ============================================================================
|
|
294
|
+
# Fuzzy File Completion (from project root)
|
|
295
|
+
# ============================================================================
|
|
296
|
+
|
|
297
|
+
# Constants for fuzzy file completion
|
|
298
|
+
_MAX_FALLBACK_FILES = 1000
|
|
299
|
+
"""Hard cap on files returned by the non-git glob fallback."""
|
|
300
|
+
|
|
301
|
+
_MIN_FUZZY_SCORE = 15
|
|
302
|
+
"""Minimum score to include in file-completion results."""
|
|
303
|
+
|
|
304
|
+
_MIN_FUZZY_RATIO = 0.4
|
|
305
|
+
"""SequenceMatcher threshold for filename-only fuzzy matches."""
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _get_project_files(root: Path) -> list[str]:
|
|
309
|
+
"""Get project files using git ls-files or fallback to glob.
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
List of relative file paths from project root.
|
|
313
|
+
"""
|
|
314
|
+
git_path = _get_git_executable()
|
|
315
|
+
if git_path:
|
|
316
|
+
try:
|
|
317
|
+
# S603: git_path is validated via shutil.which(), args are hardcoded
|
|
318
|
+
result = subprocess.run( # noqa: S603
|
|
319
|
+
[git_path, "ls-files"],
|
|
320
|
+
cwd=root,
|
|
321
|
+
capture_output=True,
|
|
322
|
+
text=True,
|
|
323
|
+
timeout=5,
|
|
324
|
+
check=False,
|
|
325
|
+
)
|
|
326
|
+
if result.returncode == 0:
|
|
327
|
+
files = result.stdout.strip().split("\n")
|
|
328
|
+
return [f for f in files if f] # Filter empty strings
|
|
329
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
330
|
+
pass
|
|
331
|
+
|
|
332
|
+
# Fallback: simple glob (limited depth to avoid slowness)
|
|
333
|
+
files = []
|
|
334
|
+
try:
|
|
335
|
+
for pattern in ["*", "*/*", "*/*/*", "*/*/*/*"]:
|
|
336
|
+
for p in root.glob(pattern):
|
|
337
|
+
if p.is_file() and not any(part.startswith(".") for part in p.parts):
|
|
338
|
+
files.append(p.relative_to(root).as_posix())
|
|
339
|
+
if len(files) >= _MAX_FALLBACK_FILES:
|
|
340
|
+
break
|
|
341
|
+
if len(files) >= _MAX_FALLBACK_FILES:
|
|
342
|
+
break
|
|
343
|
+
except OSError:
|
|
344
|
+
pass
|
|
345
|
+
return files
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _fuzzy_score(query: str, candidate: str) -> float:
|
|
349
|
+
"""Score a candidate against query. Higher = better match.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Score value where higher indicates better match quality.
|
|
353
|
+
"""
|
|
354
|
+
query_lower = query.lower()
|
|
355
|
+
# Normalize path separators for cross-platform support
|
|
356
|
+
candidate_normalized = candidate.replace("\\", "/")
|
|
357
|
+
candidate_lower = candidate_normalized.lower()
|
|
358
|
+
|
|
359
|
+
# Extract filename for matching (prioritize filename over full path)
|
|
360
|
+
filename = candidate_normalized.rsplit("/", 1)[-1].lower()
|
|
361
|
+
filename_start = candidate_lower.rfind("/") + 1
|
|
362
|
+
|
|
363
|
+
# Check filename first (higher priority)
|
|
364
|
+
if query_lower in filename:
|
|
365
|
+
idx = filename.find(query_lower)
|
|
366
|
+
# Bonus for being at start of filename
|
|
367
|
+
if idx == 0:
|
|
368
|
+
return 150 + (1 / len(candidate))
|
|
369
|
+
# Bonus for word boundary in filename
|
|
370
|
+
if idx > 0 and filename[idx - 1] in "_-.":
|
|
371
|
+
return 120 + (1 / len(candidate))
|
|
372
|
+
return 100 + (1 / len(candidate))
|
|
373
|
+
|
|
374
|
+
# Check full path
|
|
375
|
+
if query_lower in candidate_lower:
|
|
376
|
+
idx = candidate_lower.find(query_lower)
|
|
377
|
+
# At start of filename
|
|
378
|
+
if idx == filename_start:
|
|
379
|
+
return 80 + (1 / len(candidate))
|
|
380
|
+
# At word boundary in path
|
|
381
|
+
if idx == 0 or candidate[idx - 1] in "/_-.":
|
|
382
|
+
return 60 + (1 / len(candidate))
|
|
383
|
+
return 40 + (1 / len(candidate))
|
|
384
|
+
|
|
385
|
+
# Fuzzy match on filename only (more relevant)
|
|
386
|
+
filename_ratio = SequenceMatcher(None, query_lower, filename).ratio()
|
|
387
|
+
if filename_ratio > _MIN_FUZZY_RATIO:
|
|
388
|
+
return filename_ratio * 30
|
|
389
|
+
|
|
390
|
+
# Fallback: fuzzy on full path
|
|
391
|
+
ratio = SequenceMatcher(None, query_lower, candidate_lower).ratio()
|
|
392
|
+
return ratio * 15
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _is_dotpath(path: str) -> bool:
|
|
396
|
+
"""Check if path contains dotfiles/dotdirs (e.g., .github/...).
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
True if path contains hidden directories or files.
|
|
400
|
+
"""
|
|
401
|
+
return any(part.startswith(".") for part in path.split("/"))
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _path_depth(path: str) -> int:
|
|
405
|
+
"""Get depth of path (number of / separators).
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
Number of path separators in the path.
|
|
409
|
+
"""
|
|
410
|
+
return path.count("/")
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _fuzzy_search(
|
|
414
|
+
query: str,
|
|
415
|
+
candidates: list[str],
|
|
416
|
+
limit: int = 10,
|
|
417
|
+
*,
|
|
418
|
+
include_dotfiles: bool = False,
|
|
419
|
+
) -> list[str]:
|
|
420
|
+
"""Return top matches sorted by score.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
query: Search query
|
|
424
|
+
candidates: List of file paths to search
|
|
425
|
+
limit: Max results to return
|
|
426
|
+
include_dotfiles: Whether to include dotfiles (default False)
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
List of matching file paths sorted by relevance score.
|
|
430
|
+
"""
|
|
431
|
+
# Filter dotfiles unless explicitly searching for them
|
|
432
|
+
filtered = (
|
|
433
|
+
candidates
|
|
434
|
+
if include_dotfiles
|
|
435
|
+
else [c for c in candidates if not _is_dotpath(c)]
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
if not query:
|
|
439
|
+
# Empty query: show root-level files first, sorted by depth then name
|
|
440
|
+
sorted_files = sorted(filtered, key=lambda p: (_path_depth(p), p.lower()))
|
|
441
|
+
return sorted_files[:limit]
|
|
442
|
+
|
|
443
|
+
scored = [
|
|
444
|
+
(score, c)
|
|
445
|
+
for c in filtered
|
|
446
|
+
if (score := _fuzzy_score(query, c)) >= _MIN_FUZZY_SCORE
|
|
447
|
+
]
|
|
448
|
+
scored.sort(key=lambda x: -x[0])
|
|
449
|
+
return [c for _, c in scored[:limit]]
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
class FuzzyFileController:
|
|
453
|
+
"""Controller for @ file completion with fuzzy matching from project root."""
|
|
454
|
+
|
|
455
|
+
def __init__(
|
|
456
|
+
self,
|
|
457
|
+
view: CompletionView,
|
|
458
|
+
cwd: Path | None = None,
|
|
459
|
+
) -> None:
|
|
460
|
+
"""Initialize the fuzzy file controller.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
view: View to render suggestions to
|
|
464
|
+
cwd: Starting directory to find project root from
|
|
465
|
+
"""
|
|
466
|
+
self._view = view
|
|
467
|
+
self._cwd = cwd or Path.cwd()
|
|
468
|
+
self._project_root = find_project_root(self._cwd) or self._cwd
|
|
469
|
+
self._suggestions: list[tuple[str, str]] = []
|
|
470
|
+
self._selected_index = 0
|
|
471
|
+
self._file_cache: list[str] | None = None
|
|
472
|
+
|
|
473
|
+
def _get_files(self) -> list[str]:
|
|
474
|
+
"""Get cached file list or refresh.
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
List of project file paths.
|
|
478
|
+
"""
|
|
479
|
+
if self._file_cache is None:
|
|
480
|
+
self._file_cache = _get_project_files(self._project_root)
|
|
481
|
+
return self._file_cache
|
|
482
|
+
|
|
483
|
+
def refresh_cache(self) -> None:
|
|
484
|
+
"""Force refresh of file cache."""
|
|
485
|
+
self._file_cache = None
|
|
486
|
+
|
|
487
|
+
async def warm_cache(self) -> None:
|
|
488
|
+
"""Pre-populate the file cache off the event loop."""
|
|
489
|
+
if self._file_cache is not None:
|
|
490
|
+
return
|
|
491
|
+
# Best-effort; _get_files() falls back to sync on failure.
|
|
492
|
+
with contextlib.suppress(Exception):
|
|
493
|
+
self._file_cache = await asyncio.to_thread(
|
|
494
|
+
_get_project_files, self._project_root
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
@staticmethod
|
|
498
|
+
def can_handle(text: str, cursor_index: int) -> bool:
|
|
499
|
+
"""Handle input that contains @ not followed by space.
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
True if cursor is after @ and within a file mention context.
|
|
503
|
+
"""
|
|
504
|
+
if cursor_index <= 0 or cursor_index > len(text):
|
|
505
|
+
return False
|
|
506
|
+
|
|
507
|
+
before_cursor = text[:cursor_index]
|
|
508
|
+
if "@" not in before_cursor:
|
|
509
|
+
return False
|
|
510
|
+
|
|
511
|
+
at_index = before_cursor.rfind("@")
|
|
512
|
+
if cursor_index <= at_index:
|
|
513
|
+
return False
|
|
514
|
+
|
|
515
|
+
# Fragment from @ to cursor must not contain spaces
|
|
516
|
+
fragment = before_cursor[at_index:cursor_index]
|
|
517
|
+
return bool(fragment) and " " not in fragment
|
|
518
|
+
|
|
519
|
+
def reset(self) -> None:
|
|
520
|
+
"""Clear suggestions."""
|
|
521
|
+
if self._suggestions:
|
|
522
|
+
self._suggestions.clear()
|
|
523
|
+
self._selected_index = 0
|
|
524
|
+
self._view.clear_completion_suggestions()
|
|
525
|
+
|
|
526
|
+
def on_text_changed(self, text: str, cursor_index: int) -> None:
|
|
527
|
+
"""Update suggestions when text changes."""
|
|
528
|
+
if not self.can_handle(text, cursor_index):
|
|
529
|
+
self.reset()
|
|
530
|
+
return
|
|
531
|
+
|
|
532
|
+
before_cursor = text[:cursor_index]
|
|
533
|
+
at_index = before_cursor.rfind("@")
|
|
534
|
+
search = before_cursor[at_index + 1 :]
|
|
535
|
+
|
|
536
|
+
suggestions = self._get_fuzzy_suggestions(search)
|
|
537
|
+
|
|
538
|
+
if suggestions:
|
|
539
|
+
self._suggestions = suggestions
|
|
540
|
+
self._selected_index = 0
|
|
541
|
+
self._view.render_completion_suggestions(
|
|
542
|
+
self._suggestions, self._selected_index
|
|
543
|
+
)
|
|
544
|
+
else:
|
|
545
|
+
self.reset()
|
|
546
|
+
|
|
547
|
+
def _get_fuzzy_suggestions(self, search: str) -> list[tuple[str, str]]:
|
|
548
|
+
"""Get fuzzy file suggestions.
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
List of (label, type_hint) tuples for matching files.
|
|
552
|
+
"""
|
|
553
|
+
files = self._get_files()
|
|
554
|
+
# Include dotfiles only if query starts with "."
|
|
555
|
+
include_dots = search.startswith(".")
|
|
556
|
+
matches = _fuzzy_search(
|
|
557
|
+
search, files, limit=MAX_SUGGESTIONS, include_dotfiles=include_dots
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
suggestions: list[tuple[str, str]] = []
|
|
561
|
+
for path in matches:
|
|
562
|
+
# Get file extension for type hint
|
|
563
|
+
ext = Path(path).suffix.lower()
|
|
564
|
+
type_hint = ext[1:] if ext else "file"
|
|
565
|
+
suggestions.append((f"@{path}", type_hint))
|
|
566
|
+
|
|
567
|
+
return suggestions
|
|
568
|
+
|
|
569
|
+
def on_key(
|
|
570
|
+
self, event: events.Key, text: str, cursor_index: int
|
|
571
|
+
) -> CompletionResult:
|
|
572
|
+
"""Handle key events for navigation and selection.
|
|
573
|
+
|
|
574
|
+
Returns:
|
|
575
|
+
CompletionResult indicating how the key was handled.
|
|
576
|
+
"""
|
|
577
|
+
if not self._suggestions:
|
|
578
|
+
return CompletionResult.IGNORED
|
|
579
|
+
|
|
580
|
+
match event.key:
|
|
581
|
+
case "tab" | "enter":
|
|
582
|
+
if self._apply_selected_completion(text, cursor_index):
|
|
583
|
+
return CompletionResult.HANDLED
|
|
584
|
+
return CompletionResult.IGNORED
|
|
585
|
+
case "down":
|
|
586
|
+
self._move_selection(1)
|
|
587
|
+
return CompletionResult.HANDLED
|
|
588
|
+
case "up":
|
|
589
|
+
self._move_selection(-1)
|
|
590
|
+
return CompletionResult.HANDLED
|
|
591
|
+
case "escape":
|
|
592
|
+
self.reset()
|
|
593
|
+
return CompletionResult.HANDLED
|
|
594
|
+
case _:
|
|
595
|
+
return CompletionResult.IGNORED
|
|
596
|
+
|
|
597
|
+
def _move_selection(self, delta: int) -> None:
|
|
598
|
+
"""Move selection up or down."""
|
|
599
|
+
if not self._suggestions:
|
|
600
|
+
return
|
|
601
|
+
count = len(self._suggestions)
|
|
602
|
+
self._selected_index = (self._selected_index + delta) % count
|
|
603
|
+
self._view.render_completion_suggestions(
|
|
604
|
+
self._suggestions, self._selected_index
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
def _apply_selected_completion(self, text: str, cursor_index: int) -> bool:
|
|
608
|
+
"""Apply the currently selected completion.
|
|
609
|
+
|
|
610
|
+
Returns:
|
|
611
|
+
True if completion was applied, False if no suggestions or invalid state.
|
|
612
|
+
"""
|
|
613
|
+
if not self._suggestions:
|
|
614
|
+
return False
|
|
615
|
+
|
|
616
|
+
label, _ = self._suggestions[self._selected_index]
|
|
617
|
+
before_cursor = text[:cursor_index]
|
|
618
|
+
at_index = before_cursor.rfind("@")
|
|
619
|
+
|
|
620
|
+
if at_index < 0:
|
|
621
|
+
return False
|
|
622
|
+
|
|
623
|
+
# Replace from @ to cursor with the completion
|
|
624
|
+
self._view.replace_completion_range(at_index, cursor_index, label)
|
|
625
|
+
self.reset()
|
|
626
|
+
return True
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
# Keep old name as alias for backwards compatibility
|
|
630
|
+
PathCompletionController = FuzzyFileController
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
# ============================================================================
|
|
634
|
+
# Multi-Completion Manager
|
|
635
|
+
# ============================================================================
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
class MultiCompletionManager:
|
|
639
|
+
"""Manages multiple completion controllers, delegating to the active one."""
|
|
640
|
+
|
|
641
|
+
def __init__(self, controllers: list[CompletionController]) -> None:
|
|
642
|
+
"""Initialize with a list of controllers.
|
|
643
|
+
|
|
644
|
+
Args:
|
|
645
|
+
controllers: List of completion controllers (checked in order)
|
|
646
|
+
"""
|
|
647
|
+
self._controllers = controllers
|
|
648
|
+
self._active: CompletionController | None = None
|
|
649
|
+
|
|
650
|
+
def on_text_changed(self, text: str, cursor_index: int) -> None:
|
|
651
|
+
"""Handle text change, activating the appropriate controller."""
|
|
652
|
+
# Find the first controller that can handle this input
|
|
653
|
+
candidate = None
|
|
654
|
+
for controller in self._controllers:
|
|
655
|
+
if controller.can_handle(text, cursor_index):
|
|
656
|
+
candidate = controller
|
|
657
|
+
break
|
|
658
|
+
|
|
659
|
+
# No controller can handle - reset if we had one active
|
|
660
|
+
if candidate is None:
|
|
661
|
+
if self._active is not None:
|
|
662
|
+
self._active.reset()
|
|
663
|
+
self._active = None
|
|
664
|
+
return
|
|
665
|
+
|
|
666
|
+
# Switch to new controller if different
|
|
667
|
+
if candidate is not self._active:
|
|
668
|
+
if self._active is not None:
|
|
669
|
+
self._active.reset()
|
|
670
|
+
self._active = candidate
|
|
671
|
+
|
|
672
|
+
# Let the active controller process the change
|
|
673
|
+
candidate.on_text_changed(text, cursor_index)
|
|
674
|
+
|
|
675
|
+
def on_key(
|
|
676
|
+
self, event: events.Key, text: str, cursor_index: int
|
|
677
|
+
) -> CompletionResult:
|
|
678
|
+
"""Handle key event, delegating to active controller.
|
|
679
|
+
|
|
680
|
+
Returns:
|
|
681
|
+
CompletionResult from active controller, or IGNORED if none active.
|
|
682
|
+
"""
|
|
683
|
+
if self._active is None:
|
|
684
|
+
return CompletionResult.IGNORED
|
|
685
|
+
return self._active.on_key(event, text, cursor_index)
|
|
686
|
+
|
|
687
|
+
def reset(self) -> None:
|
|
688
|
+
"""Reset all controllers."""
|
|
689
|
+
if self._active is not None:
|
|
690
|
+
self._active.reset()
|
|
691
|
+
self._active = None
|