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.
Files changed (300) hide show
  1. docagent_cli/__init__.py +36 -0
  2. docagent_cli/__main__.py +6 -0
  3. docagent_cli/_ask_user_types.py +90 -0
  4. docagent_cli/_cli_context.py +27 -0
  5. docagent_cli/_debug.py +52 -0
  6. docagent_cli/_env_vars.py +56 -0
  7. docagent_cli/_server_config.py +352 -0
  8. docagent_cli/_session_stats.py +114 -0
  9. docagent_cli/_testing_models.py +144 -0
  10. docagent_cli/_version.py +17 -0
  11. docagent_cli/agent.py +1193 -0
  12. docagent_cli/app.py +4979 -0
  13. docagent_cli/app.tcss +283 -0
  14. docagent_cli/ask_user.py +301 -0
  15. docagent_cli/built_in_skills/__init__.py +5 -0
  16. docagent_cli/built_in_skills/doc-coauthoring/SKILL.md +375 -0
  17. docagent_cli/built_in_skills/docx/LICENSE.txt +30 -0
  18. docagent_cli/built_in_skills/docx/SKILL.md +590 -0
  19. docagent_cli/built_in_skills/docx/scripts/__init__.py +1 -0
  20. docagent_cli/built_in_skills/docx/scripts/accept_changes.py +135 -0
  21. docagent_cli/built_in_skills/docx/scripts/comment.py +318 -0
  22. docagent_cli/built_in_skills/docx/scripts/office/helpers/__init__.py +0 -0
  23. docagent_cli/built_in_skills/docx/scripts/office/helpers/merge_runs.py +199 -0
  24. docagent_cli/built_in_skills/docx/scripts/office/helpers/simplify_redlines.py +197 -0
  25. docagent_cli/built_in_skills/docx/scripts/office/pack.py +159 -0
  26. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  27. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  28. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  29. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  30. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  31. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  32. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  33. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  34. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  35. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  36. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  37. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  38. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  39. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  40. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  41. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  42. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  43. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  44. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  45. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  46. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  47. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  48. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  49. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  50. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  51. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  52. docagent_cli/built_in_skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  53. docagent_cli/built_in_skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  54. docagent_cli/built_in_skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  55. docagent_cli/built_in_skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  56. docagent_cli/built_in_skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  57. docagent_cli/built_in_skills/docx/scripts/office/schemas/mce/mc.xsd +75 -0
  58. docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  59. docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  60. docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  61. docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  62. docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  63. docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  64. docagent_cli/built_in_skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  65. docagent_cli/built_in_skills/docx/scripts/office/soffice.py +183 -0
  66. docagent_cli/built_in_skills/docx/scripts/office/unpack.py +132 -0
  67. docagent_cli/built_in_skills/docx/scripts/office/validate.py +111 -0
  68. docagent_cli/built_in_skills/docx/scripts/office/validators/__init__.py +15 -0
  69. docagent_cli/built_in_skills/docx/scripts/office/validators/base.py +847 -0
  70. docagent_cli/built_in_skills/docx/scripts/office/validators/docx.py +446 -0
  71. docagent_cli/built_in_skills/docx/scripts/office/validators/pptx.py +275 -0
  72. docagent_cli/built_in_skills/docx/scripts/office/validators/redlining.py +247 -0
  73. docagent_cli/built_in_skills/docx/scripts/templates/comments.xml +3 -0
  74. docagent_cli/built_in_skills/docx/scripts/templates/commentsExtended.xml +3 -0
  75. docagent_cli/built_in_skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  76. docagent_cli/built_in_skills/docx/scripts/templates/commentsIds.xml +3 -0
  77. docagent_cli/built_in_skills/docx/scripts/templates/people.xml +3 -0
  78. docagent_cli/built_in_skills/pdf/LICENSE.txt +30 -0
  79. docagent_cli/built_in_skills/pdf/SKILL.md +314 -0
  80. docagent_cli/built_in_skills/pdf/forms.md +294 -0
  81. docagent_cli/built_in_skills/pdf/reference.md +612 -0
  82. docagent_cli/built_in_skills/pdf/scripts/check_bounding_boxes.py +65 -0
  83. docagent_cli/built_in_skills/pdf/scripts/check_fillable_fields.py +11 -0
  84. docagent_cli/built_in_skills/pdf/scripts/convert_pdf_to_images.py +33 -0
  85. docagent_cli/built_in_skills/pdf/scripts/create_validation_image.py +37 -0
  86. docagent_cli/built_in_skills/pdf/scripts/extract_form_field_info.py +122 -0
  87. docagent_cli/built_in_skills/pdf/scripts/extract_form_structure.py +115 -0
  88. docagent_cli/built_in_skills/pdf/scripts/fill_fillable_fields.py +98 -0
  89. docagent_cli/built_in_skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  90. docagent_cli/built_in_skills/pptx/LICENSE.txt +30 -0
  91. docagent_cli/built_in_skills/pptx/SKILL.md +232 -0
  92. docagent_cli/built_in_skills/pptx/editing.md +205 -0
  93. docagent_cli/built_in_skills/pptx/pptxgenjs.md +420 -0
  94. docagent_cli/built_in_skills/pptx/scripts/__init__.py +0 -0
  95. docagent_cli/built_in_skills/pptx/scripts/add_slide.py +195 -0
  96. docagent_cli/built_in_skills/pptx/scripts/clean.py +286 -0
  97. docagent_cli/built_in_skills/pptx/scripts/office/helpers/__init__.py +0 -0
  98. docagent_cli/built_in_skills/pptx/scripts/office/helpers/merge_runs.py +199 -0
  99. docagent_cli/built_in_skills/pptx/scripts/office/helpers/simplify_redlines.py +197 -0
  100. docagent_cli/built_in_skills/pptx/scripts/office/pack.py +159 -0
  101. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  102. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  103. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  104. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  105. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  106. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  107. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  108. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  109. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  110. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  111. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  112. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  113. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  114. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  115. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  116. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  117. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  118. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  119. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  120. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  121. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  122. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  123. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  124. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  125. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  126. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  127. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  128. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  129. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  130. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  131. docagent_cli/built_in_skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  132. docagent_cli/built_in_skills/pptx/scripts/office/schemas/mce/mc.xsd +75 -0
  133. docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  134. docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  135. docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  136. docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  137. docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  138. docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  139. docagent_cli/built_in_skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  140. docagent_cli/built_in_skills/pptx/scripts/office/soffice.py +183 -0
  141. docagent_cli/built_in_skills/pptx/scripts/office/unpack.py +132 -0
  142. docagent_cli/built_in_skills/pptx/scripts/office/validate.py +111 -0
  143. docagent_cli/built_in_skills/pptx/scripts/office/validators/__init__.py +15 -0
  144. docagent_cli/built_in_skills/pptx/scripts/office/validators/base.py +847 -0
  145. docagent_cli/built_in_skills/pptx/scripts/office/validators/docx.py +446 -0
  146. docagent_cli/built_in_skills/pptx/scripts/office/validators/pptx.py +275 -0
  147. docagent_cli/built_in_skills/pptx/scripts/office/validators/redlining.py +247 -0
  148. docagent_cli/built_in_skills/pptx/scripts/thumbnail.py +289 -0
  149. docagent_cli/built_in_skills/remember/SKILL.md +118 -0
  150. docagent_cli/built_in_skills/skill-creator/LICENSE.txt +202 -0
  151. docagent_cli/built_in_skills/skill-creator/SKILL.md +485 -0
  152. docagent_cli/built_in_skills/skill-creator/agents/analyzer.md +274 -0
  153. docagent_cli/built_in_skills/skill-creator/agents/comparator.md +202 -0
  154. docagent_cli/built_in_skills/skill-creator/agents/grader.md +223 -0
  155. docagent_cli/built_in_skills/skill-creator/assets/eval_review.html +146 -0
  156. docagent_cli/built_in_skills/skill-creator/eval-viewer/generate_review.py +471 -0
  157. docagent_cli/built_in_skills/skill-creator/eval-viewer/viewer.html +1325 -0
  158. docagent_cli/built_in_skills/skill-creator/references/schemas.md +430 -0
  159. docagent_cli/built_in_skills/skill-creator/scripts/__init__.py +0 -0
  160. docagent_cli/built_in_skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  161. docagent_cli/built_in_skills/skill-creator/scripts/generate_report.py +326 -0
  162. docagent_cli/built_in_skills/skill-creator/scripts/improve_description.py +247 -0
  163. docagent_cli/built_in_skills/skill-creator/scripts/package_skill.py +136 -0
  164. docagent_cli/built_in_skills/skill-creator/scripts/quick_validate.py +103 -0
  165. docagent_cli/built_in_skills/skill-creator/scripts/run_eval.py +310 -0
  166. docagent_cli/built_in_skills/skill-creator/scripts/run_loop.py +328 -0
  167. docagent_cli/built_in_skills/skill-creator/scripts/utils.py +47 -0
  168. docagent_cli/built_in_skills/theme-factory/LICENSE.txt +202 -0
  169. docagent_cli/built_in_skills/theme-factory/SKILL.md +59 -0
  170. docagent_cli/built_in_skills/theme-factory/theme-showcase.pdf +0 -0
  171. docagent_cli/built_in_skills/theme-factory/themes/arctic-frost.md +19 -0
  172. docagent_cli/built_in_skills/theme-factory/themes/botanical-garden.md +19 -0
  173. docagent_cli/built_in_skills/theme-factory/themes/desert-rose.md +19 -0
  174. docagent_cli/built_in_skills/theme-factory/themes/forest-canopy.md +19 -0
  175. docagent_cli/built_in_skills/theme-factory/themes/golden-hour.md +19 -0
  176. docagent_cli/built_in_skills/theme-factory/themes/midnight-galaxy.md +19 -0
  177. docagent_cli/built_in_skills/theme-factory/themes/modern-minimalist.md +19 -0
  178. docagent_cli/built_in_skills/theme-factory/themes/ocean-depths.md +19 -0
  179. docagent_cli/built_in_skills/theme-factory/themes/sunset-boulevard.md +19 -0
  180. docagent_cli/built_in_skills/theme-factory/themes/tech-innovation.md +19 -0
  181. docagent_cli/built_in_skills/xlsx/LICENSE.txt +30 -0
  182. docagent_cli/built_in_skills/xlsx/SKILL.md +292 -0
  183. docagent_cli/built_in_skills/xlsx/scripts/office/helpers/__init__.py +0 -0
  184. docagent_cli/built_in_skills/xlsx/scripts/office/helpers/merge_runs.py +199 -0
  185. docagent_cli/built_in_skills/xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
  186. docagent_cli/built_in_skills/xlsx/scripts/office/pack.py +159 -0
  187. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  188. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  189. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  190. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  191. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  192. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  193. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  194. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  195. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  196. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  197. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  198. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  199. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  200. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  201. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  202. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  203. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  204. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  205. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  206. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  207. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  208. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  209. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  210. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  211. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  212. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  213. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  214. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  215. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  216. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  217. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  218. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
  219. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  220. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  221. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  222. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  223. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  224. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  225. docagent_cli/built_in_skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  226. docagent_cli/built_in_skills/xlsx/scripts/office/soffice.py +183 -0
  227. docagent_cli/built_in_skills/xlsx/scripts/office/unpack.py +132 -0
  228. docagent_cli/built_in_skills/xlsx/scripts/office/validate.py +111 -0
  229. docagent_cli/built_in_skills/xlsx/scripts/office/validators/__init__.py +15 -0
  230. docagent_cli/built_in_skills/xlsx/scripts/office/validators/base.py +847 -0
  231. docagent_cli/built_in_skills/xlsx/scripts/office/validators/docx.py +446 -0
  232. docagent_cli/built_in_skills/xlsx/scripts/office/validators/pptx.py +275 -0
  233. docagent_cli/built_in_skills/xlsx/scripts/office/validators/redlining.py +247 -0
  234. docagent_cli/built_in_skills/xlsx/scripts/recalc.py +184 -0
  235. docagent_cli/clipboard.py +128 -0
  236. docagent_cli/command_registry.py +284 -0
  237. docagent_cli/config.py +2418 -0
  238. docagent_cli/configurable_model.py +162 -0
  239. docagent_cli/default_agent_prompt.md +12 -0
  240. docagent_cli/editor.py +142 -0
  241. docagent_cli/file_ops.py +473 -0
  242. docagent_cli/formatting.py +28 -0
  243. docagent_cli/hooks.py +206 -0
  244. docagent_cli/input.py +787 -0
  245. docagent_cli/integrations/__init__.py +1 -0
  246. docagent_cli/integrations/sandbox_factory.py +873 -0
  247. docagent_cli/integrations/sandbox_provider.py +71 -0
  248. docagent_cli/local_context.py +718 -0
  249. docagent_cli/main.py +1641 -0
  250. docagent_cli/mcp_tools.py +707 -0
  251. docagent_cli/mcp_trust.py +168 -0
  252. docagent_cli/media_utils.py +478 -0
  253. docagent_cli/model_config.py +1620 -0
  254. docagent_cli/non_interactive.py +948 -0
  255. docagent_cli/offload.py +371 -0
  256. docagent_cli/output.py +69 -0
  257. docagent_cli/project_utils.py +188 -0
  258. docagent_cli/py.typed +0 -0
  259. docagent_cli/remote_client.py +515 -0
  260. docagent_cli/server.py +520 -0
  261. docagent_cli/server_graph.py +196 -0
  262. docagent_cli/server_manager.py +365 -0
  263. docagent_cli/sessions.py +1262 -0
  264. docagent_cli/skills/__init__.py +18 -0
  265. docagent_cli/skills/commands.py +1090 -0
  266. docagent_cli/skills/load.py +192 -0
  267. docagent_cli/subagents.py +173 -0
  268. docagent_cli/system_prompt.md +247 -0
  269. docagent_cli/textual_adapter.py +1352 -0
  270. docagent_cli/theme.py +842 -0
  271. docagent_cli/token_state.py +31 -0
  272. docagent_cli/tool_display.py +298 -0
  273. docagent_cli/tools.py +236 -0
  274. docagent_cli/ui.py +420 -0
  275. docagent_cli/unicode_security.py +516 -0
  276. docagent_cli/update_check.py +454 -0
  277. docagent_cli/widgets/__init__.py +9 -0
  278. docagent_cli/widgets/_links.py +63 -0
  279. docagent_cli/widgets/approval.py +442 -0
  280. docagent_cli/widgets/ask_user.py +398 -0
  281. docagent_cli/widgets/autocomplete.py +691 -0
  282. docagent_cli/widgets/chat_input.py +1827 -0
  283. docagent_cli/widgets/diff.py +248 -0
  284. docagent_cli/widgets/history.py +188 -0
  285. docagent_cli/widgets/loading.py +177 -0
  286. docagent_cli/widgets/mcp_viewer.py +362 -0
  287. docagent_cli/widgets/message_store.py +675 -0
  288. docagent_cli/widgets/messages.py +1751 -0
  289. docagent_cli/widgets/model_selector.py +964 -0
  290. docagent_cli/widgets/status.py +372 -0
  291. docagent_cli/widgets/theme_selector.py +164 -0
  292. docagent_cli/widgets/thread_selector.py +1905 -0
  293. docagent_cli/widgets/tool_renderers.py +148 -0
  294. docagent_cli/widgets/tool_widgets.py +274 -0
  295. docagent_cli/widgets/welcome.py +339 -0
  296. docagent_cli-0.0.35.data/data/docagent_cli/default_agent_prompt.md +12 -0
  297. docagent_cli-0.0.35.dist-info/METADATA +200 -0
  298. docagent_cli-0.0.35.dist-info/RECORD +300 -0
  299. docagent_cli-0.0.35.dist-info/WHEEL +4 -0
  300. 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