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
docagent_cli/main.py ADDED
@@ -0,0 +1,1641 @@
1
+ """Main entry point and CLI loop for docagent."""
2
+
3
+ # ruff: noqa: E402
4
+ # Imports placed after warning filters to suppress deprecation warnings
5
+
6
+ # Suppress deprecation warnings from langchain_core (e.g., Pydantic V1 on Python 3.14+)
7
+ import warnings
8
+
9
+ warnings.filterwarnings("ignore", module="langchain_core._api.deprecation")
10
+
11
+ import argparse
12
+ import asyncio
13
+ import contextlib
14
+ import importlib.util
15
+ import json
16
+ import logging
17
+ import os
18
+ import shutil
19
+ import sys
20
+ import traceback
21
+ from collections.abc import Callable, Sequence
22
+ from pathlib import Path
23
+ from typing import TYPE_CHECKING, Any
24
+
25
+ if TYPE_CHECKING:
26
+ from docagent_cli.app import AppResult
27
+ from docagent_cli.mcp_tools import MCPServerInfo
28
+
29
+ # Suppress Pydantic v1 compatibility warnings from langchain on Python 3.14+
30
+ warnings.filterwarnings("ignore", message=".*Pydantic V1.*", category=UserWarning)
31
+
32
+ from docagent_cli._version import __version__
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+ # Duplicated from agent.DEFAULT_AGENT_NAME to avoid importing the heavy agent
37
+ # module at startup. Keep in sync with agent.py. Tested.
38
+ _DEFAULT_AGENT_NAME = "agent"
39
+
40
+
41
+ def check_cli_dependencies() -> None:
42
+ """Check if CLI optional dependencies are installed."""
43
+ missing = []
44
+
45
+ if importlib.util.find_spec("requests") is None:
46
+ missing.append("requests")
47
+
48
+ if importlib.util.find_spec("dotenv") is None:
49
+ missing.append("python-dotenv")
50
+
51
+ if importlib.util.find_spec("tavily") is None:
52
+ missing.append("tavily-python")
53
+
54
+ if importlib.util.find_spec("textual") is None:
55
+ missing.append("textual")
56
+
57
+ if missing:
58
+ print("\nMissing required CLI dependencies!") # noqa: T201 # CLI output for missing dependencies
59
+ print("\nThe following packages are required to use the docagent CLI:") # noqa: T201 # CLI output for missing dependencies
60
+ for pkg in missing:
61
+ print(f" - {pkg}") # noqa: T201 # CLI output for missing dependencies
62
+ print("\nPlease install them with:") # noqa: T201 # CLI output for missing dependencies
63
+ print(" pip install docagent[cli]") # noqa: T201 # CLI output for missing dependencies
64
+ print("\nOr install all dependencies:") # noqa: T201 # CLI output for missing dependencies
65
+ print(" pip install 'docagent[cli]'") # noqa: T201 # CLI output for missing dependencies
66
+ sys.exit(1)
67
+
68
+
69
+ _RIPGREP_URL = "https://github.com/BurntSushi/ripgrep#installation"
70
+
71
+ _RIPGREP_SUPPRESS_HINT = (
72
+ "To suppress, add to ~/.docagent/config.toml:\n"
73
+ "\\[warnings]\n"
74
+ 'suppress = \\["ripgrep"]'
75
+ )
76
+
77
+
78
+ def _ripgrep_install_hint() -> str:
79
+ """Return a platform-specific install command for ripgrep.
80
+
81
+ Falls back to the GitHub URL when the platform isn't recognized.
82
+ """
83
+ plat = sys.platform
84
+ if plat == "darwin":
85
+ if shutil.which("brew"):
86
+ return "brew install ripgrep"
87
+ if shutil.which("port"):
88
+ return "sudo port install ripgrep"
89
+ elif plat == "linux":
90
+ if shutil.which("apt-get"):
91
+ return "sudo apt-get install ripgrep"
92
+ if shutil.which("dnf"):
93
+ return "sudo dnf install ripgrep"
94
+ if shutil.which("pacman"):
95
+ return "sudo pacman -S ripgrep"
96
+ if shutil.which("zypper"):
97
+ return "sudo zypper install ripgrep"
98
+ if shutil.which("apk"):
99
+ return "sudo apk add ripgrep"
100
+ if shutil.which("nix-env"):
101
+ return "nix-env -iA nixpkgs.ripgrep"
102
+ elif plat == "win32":
103
+ if shutil.which("choco"):
104
+ return "choco install ripgrep"
105
+ if shutil.which("scoop"):
106
+ return "scoop install ripgrep"
107
+ if shutil.which("winget"):
108
+ return "winget install BurntSushi.ripgrep"
109
+ # Cross-platform fallbacks
110
+ if shutil.which("cargo"):
111
+ return "cargo install ripgrep"
112
+ if shutil.which("conda"):
113
+ return "conda install -c conda-forge ripgrep"
114
+ return _RIPGREP_URL
115
+
116
+
117
+ def check_optional_tools(*, config_path: Path | None = None) -> list[str]:
118
+ """Check for recommended external tools and return missing tool names.
119
+
120
+ Skips tools that the user has suppressed via
121
+ `[warnings].suppress` in `config.toml`.
122
+
123
+ Args:
124
+ config_path: Path to config file.
125
+
126
+ Defaults to `~/.docagent/config.toml`.
127
+
128
+ Returns:
129
+ List of missing tool names (e.g. `["ripgrep"]`).
130
+ """
131
+ from docagent_cli.model_config import is_warning_suppressed
132
+
133
+ missing: list[str] = []
134
+ if shutil.which("rg") is None and not is_warning_suppressed("ripgrep", config_path):
135
+ missing.append("ripgrep")
136
+ return missing
137
+
138
+
139
+ def format_tool_warning_tui(tool: str) -> str:
140
+ """Format a missing-tool warning for the TUI toast.
141
+
142
+ Args:
143
+ tool: Name of the missing tool.
144
+
145
+ Returns:
146
+ Plain-text warning suitable for `App.notify`.
147
+ """
148
+ if tool == "ripgrep":
149
+ hint = _ripgrep_install_hint()
150
+ return (
151
+ "ripgrep is not installed; the grep tool will use a slower fallback.\n"
152
+ f"\nInstall: {hint}\n\n"
153
+ f"{_RIPGREP_SUPPRESS_HINT}"
154
+ )
155
+ return f"{tool} is not installed."
156
+
157
+
158
+ def format_tool_warning_cli(tool: str) -> str:
159
+ """Format a missing-tool warning for non-interactive console output.
160
+
161
+ Args:
162
+ tool: Name of the missing tool.
163
+
164
+ Returns:
165
+ Warning string suitable for `console.print`.
166
+ """
167
+ if tool == "ripgrep":
168
+ hint = _ripgrep_install_hint()
169
+ if hint.startswith("http"):
170
+ hint = f"[link={hint}]{hint}[/link]"
171
+ return (
172
+ "ripgrep is not installed; the grep tool will use a slower fallback.\n"
173
+ f"Install: {hint}\n\n"
174
+ f"{_RIPGREP_SUPPRESS_HINT}\n"
175
+ )
176
+ return f"{tool} is not installed."
177
+
178
+
179
+ async def _preload_session_mcp_server_info(
180
+ *,
181
+ mcp_config_path: str | None,
182
+ no_mcp: bool,
183
+ trust_project_mcp: bool | None,
184
+ ) -> list["MCPServerInfo"] | None:
185
+ """Load MCP metadata for the interactive TUI in server mode.
186
+
187
+ In server mode the actual MCP tools are created inside the LangGraph server
188
+ process, but the local Textual app still needs MCP metadata for the welcome
189
+ banner and `/mcp` viewer. This preloads the metadata in the CLI process and
190
+ immediately cleans up any temporary MCP sessions it opened.
191
+
192
+ Args:
193
+ mcp_config_path: Optional explicit MCP config path.
194
+ no_mcp: Whether MCP loading is disabled.
195
+ trust_project_mcp: Project-level MCP trust decision.
196
+
197
+ Returns:
198
+ MCP server metadata for the TUI, or `None` when MCP is disabled.
199
+ """
200
+ if no_mcp:
201
+ return None
202
+
203
+ from docagent_cli.mcp_tools import resolve_and_load_mcp_tools
204
+ from docagent_cli.project_utils import ProjectContext
205
+
206
+ session_manager = None
207
+ try:
208
+ try:
209
+ project_context = ProjectContext.from_user_cwd(Path.cwd())
210
+ except OSError:
211
+ logger.warning("Could not determine working directory for MCP preload")
212
+ project_context = None
213
+ _tools, session_manager, server_info = await resolve_and_load_mcp_tools(
214
+ explicit_config_path=mcp_config_path,
215
+ no_mcp=no_mcp,
216
+ trust_project_mcp=trust_project_mcp,
217
+ project_context=project_context,
218
+ )
219
+ return server_info
220
+ finally:
221
+ if session_manager is not None:
222
+ try:
223
+ await session_manager.cleanup()
224
+ except Exception:
225
+ logger.warning(
226
+ "MCP metadata preload cleanup failed",
227
+ exc_info=True,
228
+ )
229
+
230
+
231
+ def parse_args() -> argparse.Namespace:
232
+ """Parse command line arguments.
233
+
234
+ Returns:
235
+ Parsed arguments namespace.
236
+ """
237
+ from docagent_cli.output import add_json_output_arg
238
+ from docagent_cli.skills import setup_skills_parser
239
+
240
+ # Factory that builds an argparse Action whose __call__ invokes the
241
+ # supplied *help_fn* instead of argparse's default help text. Each
242
+ # subcommand can pass its own Rich-formatted help screen so that
243
+ # `docagent <subcommand> -h` shows context-specific help.
244
+ def _make_help_action(
245
+ help_fn: Callable[[], None],
246
+ ) -> type[argparse.Action]:
247
+ """Create an argparse Action that displays *help_fn* and exits.
248
+
249
+ argparse requires a *class* (not a callable) for custom actions.
250
+ This factory uses a closure: the returned `_ShowHelp` class captures
251
+ *help_fn* from the enclosing scope so that each subcommand can wire `-h`
252
+ to its own Rich help screen.
253
+
254
+ Args:
255
+ help_fn: Callable that prints help text to the console.
256
+
257
+ Returns:
258
+ An argparse Action class wired to the given help function.
259
+ """
260
+
261
+ class _ShowHelp(argparse.Action):
262
+ def __init__(
263
+ self,
264
+ option_strings: list[str],
265
+ dest: str = argparse.SUPPRESS,
266
+ default: str = argparse.SUPPRESS,
267
+ **kwargs: Any,
268
+ ) -> None:
269
+ super().__init__(
270
+ option_strings=option_strings,
271
+ dest=dest,
272
+ default=default,
273
+ nargs=0,
274
+ **kwargs,
275
+ )
276
+
277
+ def __call__(
278
+ self,
279
+ parser: argparse.ArgumentParser,
280
+ namespace: argparse.Namespace, # noqa: ARG002 # Required by argparse Action interface
281
+ values: str | Sequence[Any] | None, # noqa: ARG002 # Required by argparse Action interface
282
+ option_string: str | None = None, # noqa: ARG002 # Required by argparse Action interface
283
+ ) -> None:
284
+ with contextlib.suppress(BrokenPipeError):
285
+ help_fn()
286
+ parser.exit()
287
+
288
+ return _ShowHelp
289
+
290
+ # Lazy wrapper: defers `ui` import until the help action fires (i.e.,
291
+ # only when the user passes `-h`). This avoids pulling in Rich and config at
292
+ # parse time for the common non-help path.
293
+ def _lazy_help(fn_name: str) -> Callable[[], None]:
294
+ def _show() -> None:
295
+ from docagent_cli import ui
296
+
297
+ getattr(ui, fn_name)()
298
+
299
+ return _show
300
+
301
+ def help_parent(help_fn: Callable[[], None]) -> list[argparse.ArgumentParser]:
302
+ parent = argparse.ArgumentParser(add_help=False)
303
+ parent.add_argument("-h", "--help", action=_make_help_action(help_fn))
304
+ return [parent]
305
+
306
+ parser = argparse.ArgumentParser(
307
+ description=("Deep Agents - AI Coding Assistant"),
308
+ formatter_class=argparse.RawDescriptionHelpFormatter,
309
+ add_help=False,
310
+ )
311
+ subparsers = parser.add_subparsers(dest="command", help="Command to run")
312
+
313
+ subparsers.add_parser(
314
+ "help",
315
+ help="Show help information",
316
+ add_help=False,
317
+ parents=help_parent(_lazy_help("show_help")),
318
+ )
319
+
320
+ agents_parser = subparsers.add_parser(
321
+ "agents",
322
+ help="Manage agents",
323
+ add_help=False,
324
+ parents=help_parent(_lazy_help("show_agents_help")),
325
+ )
326
+ add_json_output_arg(agents_parser)
327
+ agents_sub = agents_parser.add_subparsers(dest="agents_command")
328
+
329
+ agents_list = agents_sub.add_parser(
330
+ "list",
331
+ aliases=["ls"],
332
+ help="List all agents",
333
+ add_help=False,
334
+ parents=help_parent(_lazy_help("show_list_help")),
335
+ )
336
+ add_json_output_arg(agents_list)
337
+
338
+ agents_reset = agents_sub.add_parser(
339
+ "reset",
340
+ help="Reset an agent's prompt to default",
341
+ add_help=False,
342
+ parents=help_parent(_lazy_help("show_reset_help")),
343
+ )
344
+ add_json_output_arg(agents_reset)
345
+ agents_reset.add_argument("--agent", required=True, help="Name of agent to reset")
346
+ agents_reset.add_argument(
347
+ "--target", dest="source_agent", help="Copy prompt from another agent"
348
+ )
349
+ agents_reset.add_argument(
350
+ "--dry-run",
351
+ action="store_true",
352
+ help="Show what would happen without making changes",
353
+ )
354
+
355
+ setup_skills_parser(
356
+ subparsers,
357
+ make_help_action=_make_help_action,
358
+ add_output_args=add_json_output_arg,
359
+ )
360
+
361
+ threads_parser = subparsers.add_parser(
362
+ "threads",
363
+ help="Manage conversation threads",
364
+ add_help=False,
365
+ parents=help_parent(_lazy_help("show_threads_help")),
366
+ )
367
+ add_json_output_arg(threads_parser)
368
+ threads_sub = threads_parser.add_subparsers(dest="threads_command")
369
+
370
+ threads_list = threads_sub.add_parser(
371
+ "list",
372
+ aliases=["ls"],
373
+ help="List threads",
374
+ add_help=False,
375
+ parents=help_parent(_lazy_help("show_threads_list_help")),
376
+ )
377
+ add_json_output_arg(threads_list)
378
+ threads_list.add_argument(
379
+ "--agent", default=None, help="Filter by agent name (default: show all)"
380
+ )
381
+ threads_list.add_argument(
382
+ "-n",
383
+ "--limit",
384
+ type=int,
385
+ default=None,
386
+ help="Max number of threads to display (default: 20)",
387
+ )
388
+ threads_list.add_argument(
389
+ "--sort",
390
+ choices=["created", "updated"],
391
+ default=None,
392
+ help="Sort threads by timestamp (default: from config, or updated)",
393
+ )
394
+ threads_list.add_argument(
395
+ "--branch",
396
+ default=None,
397
+ help="Filter by git branch name",
398
+ )
399
+ threads_list.add_argument(
400
+ "-v",
401
+ "--verbose",
402
+ action="store_true",
403
+ default=False,
404
+ help="Show all columns (branch, created, prompt)",
405
+ )
406
+ threads_list.add_argument(
407
+ "-r",
408
+ "--relative",
409
+ action=argparse.BooleanOptionalAction,
410
+ default=None,
411
+ help="Show timestamps as relative time (default: from config, or absolute)",
412
+ )
413
+ threads_delete = threads_sub.add_parser(
414
+ "delete",
415
+ help="Delete a thread",
416
+ add_help=False,
417
+ parents=help_parent(_lazy_help("show_threads_delete_help")),
418
+ )
419
+ add_json_output_arg(threads_delete)
420
+ threads_delete.add_argument("thread_id", help="Thread ID to delete")
421
+ threads_delete.add_argument(
422
+ "--dry-run",
423
+ action="store_true",
424
+ help="Show what would happen without making changes",
425
+ )
426
+
427
+ update_parser = subparsers.add_parser(
428
+ "update",
429
+ help="Check for and install CLI updates",
430
+ add_help=False,
431
+ parents=help_parent(_lazy_help("show_update_help")),
432
+ )
433
+ add_json_output_arg(update_parser)
434
+
435
+ # Default interactive mode — argument order here determines the
436
+ # usage line printed by argparse; keep in sync with ui.show_help().
437
+ parser.add_argument(
438
+ "-r",
439
+ "--resume",
440
+ dest="resume_thread",
441
+ nargs="?",
442
+ const="__MOST_RECENT__",
443
+ default=None,
444
+ metavar="ID",
445
+ help="Resume thread: -r for most recent, -r <ID> for specific thread",
446
+ )
447
+
448
+ parser.add_argument(
449
+ "-a",
450
+ "--agent",
451
+ default=_DEFAULT_AGENT_NAME,
452
+ metavar="NAME",
453
+ help="Agent to use (e.g., coder, researcher).",
454
+ )
455
+
456
+ parser.add_argument(
457
+ "-M",
458
+ "--model",
459
+ metavar="MODEL",
460
+ help="Model to use (e.g., claude-sonnet-4-6, gpt-5.2). "
461
+ "Provider is auto-detected from model name.",
462
+ )
463
+
464
+ parser.add_argument(
465
+ "--model-params",
466
+ metavar="JSON",
467
+ help="Extra kwargs to pass to the model as a JSON string "
468
+ '(e.g., \'{"temperature": 0.7, "max_tokens": 4096}\'). '
469
+ "These take priority, overriding config file values.",
470
+ )
471
+
472
+ parser.add_argument(
473
+ "--profile-override",
474
+ metavar="JSON",
475
+ help="Override model profile fields as a JSON string "
476
+ "(e.g., '{\"max_input_tokens\": 4096}'). "
477
+ "Merged on top of config file profile overrides.",
478
+ )
479
+
480
+ parser.add_argument(
481
+ "--default-model",
482
+ metavar="MODEL",
483
+ nargs="?",
484
+ const="__SHOW__",
485
+ default=None,
486
+ help="Set the default model for future launches "
487
+ "(e.g., anthropic:claude-opus-4-6). "
488
+ "Use --default-model with no argument to show the current default. "
489
+ "Use --clear-default-model to remove it.",
490
+ )
491
+
492
+ parser.add_argument(
493
+ "--clear-default-model",
494
+ action="store_true",
495
+ help="Clear the default model, falling back to recent model "
496
+ "or environment auto-detection.",
497
+ )
498
+
499
+ parser.add_argument(
500
+ "-m",
501
+ "--message",
502
+ dest="initial_prompt",
503
+ metavar="TEXT",
504
+ help="Initial prompt to auto-submit when session starts",
505
+ )
506
+
507
+ parser.add_argument(
508
+ "-n",
509
+ "--non-interactive",
510
+ dest="non_interactive_message",
511
+ metavar="TEXT",
512
+ help="Run a single task non-interactively and exit "
513
+ "(shell disabled unless --shell-allow-list is set)",
514
+ )
515
+
516
+ parser.add_argument(
517
+ "-q",
518
+ "--quiet",
519
+ action="store_true",
520
+ help="Clean output for piping — only the agent's response "
521
+ "goes to stdout. Requires -n or piped stdin.",
522
+ )
523
+
524
+ parser.add_argument(
525
+ "--no-stream",
526
+ dest="no_stream",
527
+ action="store_true",
528
+ help="Buffer the full response and write it to stdout at once "
529
+ "instead of streaming token-by-token. Requires -n or piped stdin.",
530
+ )
531
+
532
+ parser.add_argument(
533
+ "--stdin",
534
+ action="store_true",
535
+ help="Read input from stdin explicitly (instead of auto-detection)",
536
+ )
537
+
538
+ add_json_output_arg(parser, default="text")
539
+
540
+ parser.add_argument(
541
+ "-y",
542
+ "--auto-approve",
543
+ action="store_true",
544
+ help=(
545
+ "Auto-approve all tool calls without prompting "
546
+ "(disables human-in-the-loop). Affected tools: shell "
547
+ "execution, file writes/edits, web search, and URL fetch. "
548
+ "Use with caution — the agent can execute arbitrary commands."
549
+ ),
550
+ )
551
+
552
+ parser.add_argument(
553
+ "--sandbox",
554
+ choices=["none", "agentcore", "modal", "daytona", "runloop", "langsmith"],
555
+ default="none",
556
+ metavar="TYPE",
557
+ help=(
558
+ "Remote sandbox for code execution "
559
+ "(default: none - local only; langsmith is included, "
560
+ "agentcore/modal/daytona/runloop require downloading extras)"
561
+ ),
562
+ )
563
+
564
+ parser.add_argument(
565
+ "--sandbox-id",
566
+ metavar="ID",
567
+ help="Existing sandbox ID to reuse (skips creation and cleanup)",
568
+ )
569
+
570
+ parser.add_argument(
571
+ "--sandbox-setup",
572
+ metavar="PATH",
573
+ help="Path to setup script to run in sandbox after creation",
574
+ )
575
+ parser.add_argument(
576
+ "-S",
577
+ "--shell-allow-list",
578
+ metavar="LIST",
579
+ help="Comma-separated list of shell commands to auto-approve, "
580
+ "'recommended' for safe defaults, or 'all' to allow any command. "
581
+ "Applies to both -n and interactive modes.",
582
+ )
583
+ parser.add_argument(
584
+ "--mcp-config",
585
+ help="Path to MCP servers JSON configuration file (Claude Desktop format). "
586
+ "Merged on top of auto-discovered configs (highest precedence).",
587
+ )
588
+ parser.add_argument(
589
+ "--no-mcp",
590
+ action="store_true",
591
+ help="Disable all MCP tool loading (skip auto-discovery and explicit config)",
592
+ )
593
+ parser.add_argument(
594
+ "--trust-project-mcp",
595
+ action="store_true",
596
+ help="Trust project-level MCP configs with stdio servers "
597
+ "(skip interactive approval prompt)",
598
+ )
599
+
600
+ try:
601
+ from importlib.metadata import (
602
+ PackageNotFoundError,
603
+ version as _pkg_version,
604
+ )
605
+
606
+ sdk_version = _pkg_version("docagent")
607
+ except PackageNotFoundError:
608
+ logger.debug("docagent SDK package not found in environment")
609
+ sdk_version = "unknown"
610
+ except Exception:
611
+ logger.warning("Unexpected error looking up SDK version", exc_info=True)
612
+ sdk_version = "unknown"
613
+ parser.add_argument(
614
+ "--update",
615
+ action="store_true",
616
+ help="Check for and install updates, then exit",
617
+ )
618
+ parser.add_argument(
619
+ "--acp",
620
+ action="store_true",
621
+ help="Run as an ACP server over stdio instead of launching the Textual UI",
622
+ )
623
+
624
+ parser.add_argument(
625
+ "-v",
626
+ "--version",
627
+ action="version",
628
+ version=f"docagent-cli {__version__}\ndocagent (SDK) {sdk_version}",
629
+ )
630
+ parser.add_argument(
631
+ "-h",
632
+ "--help",
633
+ action=_make_help_action(_lazy_help("show_help")),
634
+ )
635
+
636
+ return parser.parse_args()
637
+
638
+
639
+ async def run_textual_cli_async(
640
+ assistant_id: str,
641
+ *,
642
+ auto_approve: bool = False,
643
+ sandbox_type: str = "none", # str (not None) to match argparse choices
644
+ sandbox_id: str | None = None,
645
+ sandbox_setup: str | None = None,
646
+ model_name: str | None = None,
647
+ model_params: dict[str, Any] | None = None,
648
+ profile_override: dict[str, Any] | None = None,
649
+ thread_id: str | None = None,
650
+ resume_thread: str | None = None,
651
+ initial_prompt: str | None = None,
652
+ mcp_config_path: str | None = None,
653
+ no_mcp: bool = False,
654
+ trust_project_mcp: bool | None = None,
655
+ ) -> "AppResult":
656
+ """Run the Textual CLI interface (async version).
657
+
658
+ Starts a LangGraph server in a subprocess and connects the TUI to it via the
659
+ `langgraph-sdk` client.
660
+
661
+ Args:
662
+ assistant_id: Agent identifier for memory storage
663
+ auto_approve: Whether to auto-approve tool usage
664
+ sandbox_type: Type of sandbox
665
+ ("none", "agentcore", "modal", "runloop", "daytona", "langsmith")
666
+ sandbox_id: Optional existing sandbox ID to reuse.
667
+ sandbox_setup: Optional path to setup script to run in the sandbox
668
+ after creation.
669
+ model_name: Optional model name to use
670
+ model_params: Extra kwargs from `--model-params` to pass to the model.
671
+
672
+ These override config file values.
673
+ profile_override: Extra profile fields from `--profile-override`.
674
+
675
+ Merged on top of config file profile overrides.
676
+ thread_id: Thread ID for the session.
677
+
678
+ `None` when `resume_thread` is provided (the TUI resolves the final
679
+ ID asynchronously).
680
+ resume_thread: Raw resume intent from `-r` flag.
681
+
682
+ `'__MOST_RECENT__'` for bare `-r`, a thread ID string for `-r <id>`,
683
+ or `None` for new sessions.
684
+
685
+ Resolved asynchronously inside the TUI.
686
+ initial_prompt: Optional prompt to auto-submit when session starts
687
+ mcp_config_path: Optional path to MCP servers JSON configuration file.
688
+
689
+ Merged on top of auto-discovered configs (highest precedence).
690
+ no_mcp: Disable all MCP tool loading.
691
+ trust_project_mcp: Controls project-level stdio server trust.
692
+
693
+ `True` to allow, `False` to deny, `None` to check trust store.
694
+
695
+ Returns:
696
+ An `AppResult` with the return code and final thread ID.
697
+ """
698
+ from rich.text import Text
699
+
700
+ from docagent_cli.app import AppResult, run_textual_app
701
+ from docagent_cli.config import (
702
+ _get_default_model_spec,
703
+ detect_provider,
704
+ settings,
705
+ )
706
+ from docagent_cli.model_config import ModelConfigError, ModelSpec
707
+
708
+ # Resolve display-name cheaply (<1ms, no langchain) so the status
709
+ # bar can show the model on first paint. The expensive create_model()
710
+ # (~560ms) is deferred to a background worker.
711
+
712
+ try:
713
+ resolved_spec = model_name or _get_default_model_spec()
714
+ except ModelConfigError as e:
715
+ from rich.markup import escape
716
+
717
+ from docagent_cli.config import console
718
+
719
+ console.print(f"[bold red]Error:[/bold red] {escape(str(e))}", highlight=False)
720
+ return AppResult(return_code=1, thread_id=None)
721
+
722
+ parsed = ModelSpec.try_parse(resolved_spec)
723
+ if parsed:
724
+ settings.model_provider = parsed.provider
725
+ settings.model_name = parsed.model
726
+ else:
727
+ settings.model_name = resolved_spec
728
+ settings.model_provider = detect_provider(resolved_spec) or ""
729
+
730
+ model_kwargs: dict[str, Any] = {
731
+ "model_spec": model_name,
732
+ "extra_kwargs": model_params,
733
+ "profile_overrides": profile_override,
734
+ }
735
+
736
+ # Build kwargs for deferred server startup (runs inside the TUI).
737
+ # Never pass auto_approve to the server — the interactive server must
738
+ # always configure full HITL interrupts so that Shift+Tab can toggle
739
+ # approval mode mid-session. The -y flag is handled client-side via
740
+ # session_state.auto_approve in textual_adapter.py.
741
+ server_kwargs: dict[str, Any] = {
742
+ "assistant_id": assistant_id,
743
+ "model_name": model_name,
744
+ "model_params": model_params,
745
+ "sandbox_type": sandbox_type,
746
+ "sandbox_id": sandbox_id,
747
+ "sandbox_setup": sandbox_setup,
748
+ "enable_ask_user": True,
749
+ "mcp_config_path": mcp_config_path,
750
+ "no_mcp": no_mcp,
751
+ "trust_project_mcp": trust_project_mcp,
752
+ "interactive": True,
753
+ }
754
+
755
+ mcp_preload_kwargs: dict[str, Any] | None = None
756
+ if not no_mcp:
757
+ mcp_preload_kwargs = {
758
+ "mcp_config_path": mcp_config_path,
759
+ "no_mcp": no_mcp,
760
+ "trust_project_mcp": trust_project_mcp,
761
+ }
762
+
763
+ try:
764
+ result = await run_textual_app(
765
+ assistant_id=assistant_id,
766
+ backend=None,
767
+ auto_approve=auto_approve,
768
+ cwd=Path.cwd(),
769
+ thread_id=thread_id,
770
+ resume_thread=resume_thread,
771
+ initial_prompt=initial_prompt,
772
+ profile_override=profile_override,
773
+ server_kwargs=server_kwargs,
774
+ mcp_preload_kwargs=mcp_preload_kwargs,
775
+ model_kwargs=model_kwargs,
776
+ )
777
+ except Exception as e:
778
+ logger.debug("App error", exc_info=True)
779
+ from docagent_cli.config import console
780
+
781
+ error_text = Text("Application error: ", style="red")
782
+ error_text.append(str(e))
783
+ console.print(error_text)
784
+ if logger.isEnabledFor(logging.DEBUG):
785
+ console.print(Text(traceback.format_exc(), style="dim"))
786
+ return AppResult(return_code=1, thread_id=None)
787
+
788
+ return result
789
+
790
+
791
+ async def _run_acp_cli_async(
792
+ assistant_id: str,
793
+ *,
794
+ run_acp_agent: Callable[[Any], Any],
795
+ agent_server_cls: type[Any],
796
+ model_name: str | None = None,
797
+ model_params: dict[str, Any] | None = None,
798
+ profile_override: dict[str, Any] | None = None,
799
+ mcp_config_path: str | None = None,
800
+ no_mcp: bool = False,
801
+ trust_project_mcp: bool | None = None,
802
+ ) -> int:
803
+ """Run ACP server mode and return a process exit code.
804
+
805
+ Args:
806
+ assistant_id: Agent identifier to initialize.
807
+ run_acp_agent: ACP server runner function.
808
+ agent_server_cls: ACP server class constructor.
809
+ model_name: Optional model name to use.
810
+ model_params: Extra kwargs from `--model-params` to pass to the model.
811
+ profile_override: Extra profile fields from `--profile-override`.
812
+ mcp_config_path: Optional path to MCP servers JSON configuration file.
813
+ no_mcp: Disable all MCP tool loading.
814
+ trust_project_mcp: Controls project-level stdio server trust.
815
+
816
+ Returns:
817
+ Exit code for ACP mode.
818
+ """
819
+ from docagent_cli.agent import create_cli_agent, load_async_subagents
820
+ from docagent_cli.config import create_model, settings
821
+ from docagent_cli.model_config import ModelConfigError, save_recent_model
822
+ from docagent_cli.tools import fetch_url, http_request, web_search
823
+
824
+ try:
825
+ model_result = create_model(
826
+ model_name,
827
+ extra_kwargs=model_params,
828
+ profile_overrides=profile_override,
829
+ )
830
+ except ModelConfigError as exc:
831
+ sys.stderr.write(f"Error: {exc}\n")
832
+ sys.stderr.flush()
833
+ return 1
834
+ model_result.apply_to_settings()
835
+
836
+ # Persist the resolved model so [models].recent is always populated.
837
+ save_recent_model(f"{model_result.provider}:{model_result.model_name}")
838
+
839
+ tools: list[Any] = [http_request, fetch_url]
840
+ if settings.has_tavily:
841
+ tools.append(web_search)
842
+
843
+ mcp_session_manager = None
844
+ mcp_server_info = None
845
+ try:
846
+ from docagent_cli.mcp_tools import resolve_and_load_mcp_tools
847
+
848
+ (
849
+ mcp_tools,
850
+ mcp_session_manager,
851
+ mcp_server_info,
852
+ ) = await resolve_and_load_mcp_tools(
853
+ explicit_config_path=mcp_config_path,
854
+ no_mcp=no_mcp,
855
+ trust_project_mcp=trust_project_mcp,
856
+ )
857
+ tools.extend(mcp_tools)
858
+ except FileNotFoundError as exc:
859
+ msg = f"Error: MCP config file not found: {exc}\n"
860
+ sys.stderr.write(msg)
861
+ sys.stderr.flush()
862
+ return 1
863
+ except RuntimeError as exc:
864
+ msg = f"Error: Failed to load MCP tools: {exc}\n"
865
+ sys.stderr.write(msg)
866
+ sys.stderr.flush()
867
+ return 1
868
+
869
+ async_subagents = load_async_subagents() or None
870
+
871
+ try:
872
+ from langgraph.checkpoint.memory import InMemorySaver
873
+
874
+ agent_graph, _backend = create_cli_agent(
875
+ model=model_result.model,
876
+ assistant_id=assistant_id,
877
+ tools=tools,
878
+ mcp_server_info=mcp_server_info,
879
+ checkpointer=InMemorySaver(),
880
+ async_subagents=async_subagents,
881
+ )
882
+ except Exception as exc:
883
+ sys.stderr.write(f"Error: failed to create agent: {exc}\n")
884
+ sys.stderr.flush()
885
+ logger.debug("ACP agent creation failed", exc_info=True)
886
+ return 1
887
+
888
+ server = agent_server_cls(agent_graph) # Pregel is a CompiledStateGraph at runtime
889
+ exit_code = 0
890
+ try:
891
+ await run_acp_agent(server)
892
+ except KeyboardInterrupt:
893
+ pass
894
+ except Exception as exc:
895
+ sys.stderr.write(f"Error: ACP server failed: {exc}\n")
896
+ sys.stderr.flush()
897
+ logger.exception("ACP server crashed")
898
+ exit_code = 1
899
+ finally:
900
+ if mcp_session_manager is not None:
901
+ try:
902
+ await mcp_session_manager.cleanup()
903
+ except Exception:
904
+ logger.warning("MCP session cleanup failed", exc_info=True)
905
+ return exit_code
906
+
907
+
908
+ def apply_stdin_pipe(args: argparse.Namespace) -> None:
909
+ r"""Read piped stdin and merge it into the parsed CLI arguments.
910
+
911
+ When stdin is not a TTY (i.e. input is piped), reads all available text
912
+ and applies it to the argument namespace. If stdin is a TTY or the piped
913
+ input is empty/whitespace-only, the function returns without modifying
914
+ `args`. Leading and trailing whitespace is stripped from piped input.
915
+
916
+ - If `non_interactive_message` is already set (`-n`), prepends the
917
+ piped text to it (the CLI still runs non-interactively):
918
+
919
+ ```bash
920
+ cat context.txt | docagent -n "summarize this"
921
+ # non_interactive_message = "{contents of context.txt}\n\nsummarize this"
922
+ ```
923
+
924
+ - If `initial_prompt` is already set (`-m`, but not `-n`), prepends
925
+ the piped text to it (the CLI still runs interactively):
926
+
927
+ ```bash
928
+ cat error.log | docagent -m "explain this"
929
+ # initial_prompt = "{contents of error.log}\n\nexplain this"
930
+ ```
931
+
932
+ - Otherwise, sets `non_interactive_message` to the piped text, causing
933
+ the CLI to run non-interactively with it as the prompt:
934
+
935
+ ```bash
936
+ echo "fix the typo in README.md" | docagent
937
+ # non_interactive_message = "fix the typo in README.md"
938
+ ```
939
+
940
+ Args:
941
+ args: The parsed argument namespace (mutated in place).
942
+ """
943
+ from docagent_cli.config import console
944
+
945
+ explicit_stdin = args.stdin
946
+
947
+ if sys.stdin is None:
948
+ if explicit_stdin:
949
+ console.print(
950
+ "[bold red]Error:[/bold red] --stdin was passed but stdin "
951
+ "is not available."
952
+ )
953
+ sys.exit(1)
954
+ return
955
+
956
+ try:
957
+ is_tty = sys.stdin.isatty()
958
+ except (ValueError, OSError):
959
+ if explicit_stdin:
960
+ console.print(
961
+ "[bold red]Error:[/bold red] --stdin was passed but stdin "
962
+ "state could not be determined."
963
+ )
964
+ sys.exit(1)
965
+ return
966
+
967
+ if is_tty:
968
+ if explicit_stdin:
969
+ console.print(
970
+ "[bold red]Error:[/bold red] --stdin was passed but stdin "
971
+ "is a terminal. Pipe input or use -n instead.\n"
972
+ " cat prompt.txt | docagent --stdin -q"
973
+ )
974
+ sys.exit(1)
975
+ return
976
+
977
+ max_stdin_bytes = 10 * 1024 * 1024 # 10 MiB
978
+
979
+ try:
980
+ stdin_text = sys.stdin.read(max_stdin_bytes + 1)
981
+ except UnicodeDecodeError:
982
+ msg = "Could not read piped input — ensure the input is valid text"
983
+ console.print(f"[bold red]Error:[/bold red] {msg}")
984
+ sys.exit(1)
985
+ except (OSError, ValueError) as exc:
986
+ from rich.markup import escape
987
+
988
+ console.print(
989
+ f"[bold red]Error:[/bold red] Failed to read piped input: "
990
+ f"{escape(str(exc))}"
991
+ )
992
+ sys.exit(1)
993
+
994
+ if len(stdin_text) > max_stdin_bytes:
995
+ msg = (
996
+ f"Piped input exceeds {max_stdin_bytes // (1024 * 1024)} MiB limit. "
997
+ "Consider writing the content to a file and referencing it instead."
998
+ )
999
+ console.print(f"[bold red]Error:[/bold red] {msg}")
1000
+ sys.exit(1)
1001
+
1002
+ stdin_text = stdin_text.strip()
1003
+
1004
+ if not stdin_text:
1005
+ return
1006
+
1007
+ if args.non_interactive_message:
1008
+ args.non_interactive_message = f"{stdin_text}\n\n{args.non_interactive_message}"
1009
+ elif args.initial_prompt:
1010
+ args.initial_prompt = f"{stdin_text}\n\n{args.initial_prompt}"
1011
+ else:
1012
+ args.non_interactive_message = stdin_text
1013
+
1014
+ # Restore stdin from the real terminal so the interactive Textual app
1015
+ # (used by the -m path) can read keyboard/mouse input normally.
1016
+ # Textual's driver reads from file descriptor 0 directly (not sys.stdin),
1017
+ # so we must replace the underlying fd with /dev/tty using os.dup2.
1018
+ try:
1019
+ tty_fd = os.open("/dev/tty", os.O_RDONLY)
1020
+ except OSError:
1021
+ # No controlling terminal (CI, Docker, headless). Non-interactive
1022
+ # path still works; interactive -m path will fail later with a
1023
+ # clear "not a terminal" error from Textual.
1024
+ return
1025
+
1026
+ try:
1027
+ os.dup2(tty_fd, 0)
1028
+ os.close(tty_fd)
1029
+ sys.stdin = open(0, encoding="utf-8", closefd=False) # noqa: SIM115 # fd 0 requires open() for TTY restoration
1030
+ except OSError:
1031
+ console.print(
1032
+ "[yellow]Warning:[/yellow] TTY restoration failed. "
1033
+ "Interactive mode (-m) may not work correctly."
1034
+ )
1035
+ logger.warning(
1036
+ "TTY restoration failed after opening /dev/tty",
1037
+ exc_info=True,
1038
+ )
1039
+ try:
1040
+ os.close(tty_fd)
1041
+ except OSError:
1042
+ logger.warning(
1043
+ "Failed to close TTY fd %d during cleanup",
1044
+ tty_fd,
1045
+ exc_info=True,
1046
+ )
1047
+
1048
+
1049
+ def _print_session_stats(stats: Any, console: Any) -> None: # noqa: ANN401
1050
+ """Print a session-level usage stats table to the console on TUI exit.
1051
+
1052
+ Args:
1053
+ stats: The cumulative session stats from the Textual app.
1054
+ console: Rich console for output.
1055
+ """
1056
+ from docagent_cli.textual_adapter import SessionStats, print_usage_table
1057
+
1058
+ if not isinstance(stats, SessionStats):
1059
+ return
1060
+ print_usage_table(stats, stats.wall_time_seconds, console)
1061
+
1062
+
1063
+ def _check_mcp_project_trust(*, trust_flag: bool = False) -> bool | None:
1064
+ """Check whether project-level MCP stdio servers should be trusted.
1065
+
1066
+ When the project has no stdio servers in project-level configs, returns
1067
+ `None` (no gate needed). When `--trust-project-mcp` was passed, returns
1068
+ `True`. Otherwise checks the persistent trust store; if untrusted, shows
1069
+ an interactive approval prompt.
1070
+
1071
+ Args:
1072
+ trust_flag: Whether `--trust-project-mcp` was passed.
1073
+
1074
+ Returns:
1075
+ `True` to allow project stdio servers, `False` to deny, or `None`
1076
+ when no project stdio servers exist.
1077
+ """
1078
+ from docagent_cli.mcp_tools import (
1079
+ classify_discovered_configs,
1080
+ discover_mcp_configs,
1081
+ extract_stdio_server_commands,
1082
+ load_mcp_config_lenient,
1083
+ )
1084
+ from docagent_cli.project_utils import ProjectContext
1085
+
1086
+ try:
1087
+ project_context = ProjectContext.from_user_cwd(Path.cwd())
1088
+ config_paths = discover_mcp_configs(project_context=project_context)
1089
+ except (OSError, RuntimeError):
1090
+ return None
1091
+
1092
+ _, project_configs = classify_discovered_configs(config_paths)
1093
+ if not project_configs:
1094
+ return None
1095
+
1096
+ # Collect all stdio servers across project configs
1097
+ all_stdio: list[tuple[str, str, list[str]]] = []
1098
+ for path in project_configs:
1099
+ cfg = load_mcp_config_lenient(path)
1100
+ if cfg is not None:
1101
+ all_stdio.extend(extract_stdio_server_commands(cfg))
1102
+
1103
+ if not all_stdio:
1104
+ return None
1105
+
1106
+ if trust_flag:
1107
+ return True
1108
+
1109
+ # Check trust store
1110
+ from docagent_cli.mcp_trust import (
1111
+ compute_config_fingerprint,
1112
+ is_project_mcp_trusted,
1113
+ trust_project_mcp,
1114
+ )
1115
+
1116
+ project_root = str(
1117
+ (project_context.project_root or project_context.user_cwd).resolve()
1118
+ )
1119
+ fingerprint = compute_config_fingerprint(project_configs)
1120
+
1121
+ if is_project_mcp_trusted(project_root, fingerprint):
1122
+ return True
1123
+
1124
+ # Interactive prompt
1125
+ from rich.console import Console as _Console
1126
+
1127
+ prompt_console = _Console(stderr=True)
1128
+ prompt_console.print()
1129
+ prompt_console.print(
1130
+ "[bold yellow]Project MCP servers require approval:[/bold yellow]"
1131
+ )
1132
+ for name, cmd, args in all_stdio:
1133
+ args_str = " ".join(args) if args else ""
1134
+ prompt_console.print(f' [bold]"{name}"[/bold]: {cmd} {args_str}')
1135
+ prompt_console.print()
1136
+
1137
+ try:
1138
+ answer = input("Allow? [y/N]: ").strip().lower()
1139
+ except (EOFError, KeyboardInterrupt):
1140
+ answer = ""
1141
+
1142
+ if answer == "y":
1143
+ trust_project_mcp(project_root, fingerprint)
1144
+ return True
1145
+ return False
1146
+
1147
+
1148
+ def cli_main() -> None:
1149
+ """Entry point for console script."""
1150
+ # Fix for gRPC fork issue on macOS
1151
+ # https://github.com/grpc/grpc/issues/37642
1152
+ if sys.platform == "darwin":
1153
+ os.environ["GRPC_ENABLE_FORK_SUPPORT"] = "0"
1154
+
1155
+ # Note: LANGSMITH_PROJECT override is handled lazily by config.py's
1156
+ # _ensure_bootstrap() (triggered on first access of `settings`).
1157
+ # This ensures agent traces use DEEPAGENTS_CLI_LANGSMITH_PROJECT while
1158
+ # shell commands use the user's original LANGSMITH_PROJECT.
1159
+
1160
+ # Fast path: print version without loading heavy dependencies
1161
+ if len(sys.argv) == 2 and sys.argv[1] in {"-v", "--version"}: # noqa: PLR2004 # argv length check for fast-path
1162
+ try:
1163
+ from importlib.metadata import (
1164
+ PackageNotFoundError,
1165
+ version as _pkg_version,
1166
+ )
1167
+
1168
+ sdk_version = _pkg_version("docagent")
1169
+ except PackageNotFoundError:
1170
+ sdk_version = "unknown"
1171
+ except Exception: # Best-effort SDK version lookup
1172
+ logger.debug("Unexpected error looking up SDK version", exc_info=True)
1173
+ sdk_version = "unknown"
1174
+ print(f"docagent-cli {__version__}\ndocagent (SDK) {sdk_version}") # noqa: T201 # CLI version output
1175
+ sys.exit(0)
1176
+
1177
+ # ACP mode does not require Textual, so skip UI dependency checks when
1178
+ # the flag is present in raw argv.
1179
+ if "--acp" not in sys.argv[1:]:
1180
+ check_cli_dependencies()
1181
+
1182
+ try:
1183
+ args = parse_args()
1184
+
1185
+ # Import console/settings AFTER arg parsing so --help (which exits
1186
+ # inside parse_args) never pays the settings bootstrap cost.
1187
+ from docagent_cli.config import console, settings
1188
+
1189
+ model_params: dict[str, Any] | None = None
1190
+ raw_kwargs = getattr(args, "model_params", None)
1191
+ if raw_kwargs:
1192
+ try:
1193
+ model_params = json.loads(raw_kwargs)
1194
+ except json.JSONDecodeError as e:
1195
+ console.print(
1196
+ f"[bold red]Error:[/bold red] --model-params is not valid JSON: {e}"
1197
+ )
1198
+ sys.exit(1)
1199
+ if not isinstance(model_params, dict):
1200
+ console.print(
1201
+ "[bold red]Error:[/bold red] --model-params must be a JSON object"
1202
+ )
1203
+ sys.exit(1)
1204
+
1205
+ profile_override: dict[str, Any] | None = None
1206
+ raw_profile = getattr(args, "profile_override", None)
1207
+ if raw_profile:
1208
+ try:
1209
+ profile_override = json.loads(raw_profile)
1210
+ except json.JSONDecodeError as e:
1211
+ console.print(
1212
+ "[bold red]Error:[/bold red] "
1213
+ f"--profile-override is not valid JSON: {e}"
1214
+ )
1215
+ sys.exit(1)
1216
+ if not isinstance(profile_override, dict):
1217
+ console.print(
1218
+ "[bold red]Error:[/bold red] "
1219
+ "--profile-override must be a JSON object"
1220
+ )
1221
+ sys.exit(1)
1222
+
1223
+ if getattr(args, "acp", False):
1224
+ try:
1225
+ from acp import run_agent as run_acp_agent
1226
+ from docagent_acp.server import AgentServerACP
1227
+ except ImportError as exc:
1228
+ msg = (
1229
+ f"ACP dependencies not available: {exc}\n"
1230
+ "Install with: pip install docagent-acp\n"
1231
+ )
1232
+ sys.stderr.write(msg)
1233
+ sys.stderr.flush()
1234
+ sys.exit(1)
1235
+
1236
+ if getattr(args, "no_mcp", False) and getattr(args, "mcp_config", None):
1237
+ msg = (
1238
+ "Error: --no-mcp and --mcp-config are mutually exclusive."
1239
+ " Use one or the other.\n"
1240
+ " docagent --mcp-config path/to/config.json\n"
1241
+ " docagent --no-mcp\n"
1242
+ )
1243
+ sys.stderr.write(msg)
1244
+ sys.stderr.flush()
1245
+ sys.exit(2)
1246
+
1247
+ exit_code = asyncio.run(
1248
+ _run_acp_cli_async(
1249
+ assistant_id=args.agent,
1250
+ run_acp_agent=run_acp_agent,
1251
+ agent_server_cls=AgentServerACP,
1252
+ model_name=getattr(args, "model", None),
1253
+ model_params=model_params,
1254
+ profile_override=profile_override,
1255
+ mcp_config_path=getattr(args, "mcp_config", None),
1256
+ no_mcp=getattr(args, "no_mcp", False),
1257
+ trust_project_mcp=getattr(args, "trust_project_mcp", False),
1258
+ )
1259
+ )
1260
+ sys.exit(exit_code)
1261
+
1262
+ # Apply shell-allow-list from command line if provided (overrides env var)
1263
+ if args.shell_allow_list:
1264
+ from docagent_cli.config import parse_shell_allow_list
1265
+
1266
+ settings.shell_allow_list = parse_shell_allow_list(args.shell_allow_list)
1267
+
1268
+ apply_stdin_pipe(args)
1269
+
1270
+ if getattr(args, "no_mcp", False) and getattr(args, "mcp_config", None):
1271
+ from rich.console import Console as _Console
1272
+
1273
+ _Console(stderr=True).print(
1274
+ "[bold red]Error:[/bold red] --no-mcp and --mcp-config "
1275
+ "are mutually exclusive. Use one or the other.\n"
1276
+ " docagent --mcp-config path/to/config.json\n"
1277
+ " docagent --no-mcp"
1278
+ )
1279
+ sys.exit(2)
1280
+
1281
+ if (args.quiet or args.no_stream) and not args.non_interactive_message:
1282
+ # Print to stderr (not the module-level stdout console) and exit
1283
+ # with code 2 to match the POSIX convention for usage errors, as
1284
+ # argparse's parser.error() would.
1285
+ from rich.console import Console as _Console
1286
+
1287
+ flags = []
1288
+ if args.quiet:
1289
+ flags.append("--quiet")
1290
+ if args.no_stream:
1291
+ flags.append("--no-stream")
1292
+ flag = " and ".join(flags)
1293
+ _Console(stderr=True).print(
1294
+ f"[bold red]Error:[/bold red] {flag} requires "
1295
+ "--non-interactive (-n) or piped stdin\n"
1296
+ " docagent -n 'summarize README.md' --quiet"
1297
+ )
1298
+ sys.exit(2)
1299
+
1300
+ # Handle --update flag or `update` subcommand (headless, no session)
1301
+ if args.update or args.command == "update":
1302
+ try:
1303
+ from rich.markup import escape
1304
+
1305
+ from docagent_cli._version import __version__ as cli_version
1306
+ from docagent_cli.update_check import (
1307
+ is_update_available,
1308
+ perform_upgrade,
1309
+ upgrade_command,
1310
+ )
1311
+
1312
+ console.print("Checking for updates...", style="dim")
1313
+ available, latest = is_update_available(bypass_cache=True)
1314
+ if latest is None:
1315
+ console.print(
1316
+ "[bold yellow]Warning:[/bold yellow] Could not "
1317
+ "reach PyPI. Check your network and try again."
1318
+ )
1319
+ sys.exit(1)
1320
+ if not available:
1321
+ console.print(f"Already on the latest version (v{cli_version}).")
1322
+ sys.exit(0)
1323
+
1324
+ console.print(
1325
+ f"Update available: v{latest} "
1326
+ f"(current: v{cli_version}). Upgrading..."
1327
+ )
1328
+ success, output = asyncio.run(perform_upgrade())
1329
+ if success:
1330
+ console.print(f"[green]Updated to v{latest}.[/green]")
1331
+ else:
1332
+ cmd = upgrade_command()
1333
+ detail = f": {escape(output[:200])}" if output else ""
1334
+ console.print(
1335
+ f"[bold red]Auto-update failed{detail}[/bold red]\n"
1336
+ f"Run manually: [cyan]{cmd}[/cyan]"
1337
+ )
1338
+ sys.exit(1)
1339
+ sys.exit(0)
1340
+ except Exception:
1341
+ logger.warning("--update failed", exc_info=True)
1342
+ console.print(
1343
+ "[bold red]Error:[/bold red] Update failed.\n"
1344
+ "Run manually: [cyan]uv tool upgrade "
1345
+ "docagent-cli[/cyan]"
1346
+ )
1347
+ sys.exit(1)
1348
+
1349
+ # Handle --default-model / --clear-default-model (headless, no session)
1350
+ if args.clear_default_model:
1351
+ from docagent_cli.model_config import clear_default_model
1352
+
1353
+ if clear_default_model():
1354
+ console.print("Default model cleared.")
1355
+ else:
1356
+ console.print(
1357
+ "[bold red]Error:[/bold red] Could not clear default model. "
1358
+ "Check permissions for ~/.docagent/"
1359
+ )
1360
+ sys.exit(1)
1361
+ sys.exit(0)
1362
+
1363
+ if args.default_model is not None:
1364
+ from docagent_cli.model_config import (
1365
+ ModelConfig,
1366
+ save_default_model,
1367
+ )
1368
+
1369
+ if args.default_model == "__SHOW__":
1370
+ config = ModelConfig.load()
1371
+ if config.default_model:
1372
+ console.print(f"Default model: {config.default_model}")
1373
+ else:
1374
+ console.print("No default model set.")
1375
+ sys.exit(0)
1376
+
1377
+ model_spec = args.default_model
1378
+ # Auto-detect provider for bare model names
1379
+ from docagent_cli.config import detect_provider
1380
+ from docagent_cli.model_config import ModelSpec
1381
+
1382
+ parsed = ModelSpec.try_parse(model_spec)
1383
+ if not parsed:
1384
+ provider = detect_provider(model_spec)
1385
+ if provider:
1386
+ model_spec = f"{provider}:{model_spec}"
1387
+
1388
+ if save_default_model(model_spec):
1389
+ console.print(f"Default model set to {model_spec}")
1390
+ else:
1391
+ console.print(
1392
+ "[bold red]Error:[/bold red] Could not save default model. "
1393
+ "Check permissions for ~/.docagent/"
1394
+ )
1395
+ sys.exit(1)
1396
+ sys.exit(0)
1397
+
1398
+ output_format = getattr(args, "output_format", "text")
1399
+
1400
+ if args.command == "help":
1401
+ from docagent_cli.ui import show_help
1402
+
1403
+ show_help()
1404
+ elif args.command == "agents":
1405
+ from docagent_cli.agent import list_agents, reset_agent
1406
+ from docagent_cli.ui import show_agents_help
1407
+
1408
+ # "ls" is an argparse alias for "list"
1409
+ if args.agents_command in {"list", "ls"}:
1410
+ list_agents(output_format=output_format)
1411
+ elif args.agents_command == "reset":
1412
+ reset_agent(
1413
+ args.agent,
1414
+ args.source_agent,
1415
+ dry_run=args.dry_run,
1416
+ output_format=output_format,
1417
+ )
1418
+ else:
1419
+ show_agents_help()
1420
+ elif args.command == "skills":
1421
+ from docagent_cli.skills import execute_skills_command
1422
+
1423
+ execute_skills_command(args)
1424
+ elif args.command == "threads":
1425
+ from docagent_cli.sessions import (
1426
+ delete_thread_command,
1427
+ list_threads_command,
1428
+ )
1429
+ from docagent_cli.ui import show_threads_help
1430
+
1431
+ # "ls" is an argparse alias for "list" — argparse stores the
1432
+ # alias as-is in the namespace, so we must match both values.
1433
+ if args.threads_command in {"list", "ls"}:
1434
+ asyncio.run(
1435
+ list_threads_command(
1436
+ agent_name=getattr(args, "agent", None),
1437
+ limit=getattr(args, "limit", None),
1438
+ sort_by=getattr(args, "sort", None),
1439
+ branch=getattr(args, "branch", None),
1440
+ verbose=getattr(args, "verbose", False),
1441
+ relative=getattr(args, "relative", None),
1442
+ output_format=output_format,
1443
+ )
1444
+ )
1445
+ elif args.threads_command == "delete":
1446
+ asyncio.run(
1447
+ delete_thread_command(
1448
+ args.thread_id,
1449
+ dry_run=args.dry_run,
1450
+ output_format=output_format,
1451
+ )
1452
+ )
1453
+ else:
1454
+ # No subcommand provided, show threads help screen
1455
+ show_threads_help()
1456
+ elif args.non_interactive_message:
1457
+ # Check for optional tools before running agent (stderr so
1458
+ # --quiet piped output stays clean)
1459
+ try:
1460
+ from rich.console import Console as _Console
1461
+ except ImportError:
1462
+ logger.warning(
1463
+ "Could not import rich.console; skipping tool warnings",
1464
+ exc_info=True,
1465
+ )
1466
+ else:
1467
+ try:
1468
+ warn_console = _Console(stderr=True)
1469
+ for tool in check_optional_tools():
1470
+ warn_console.print(
1471
+ f"[yellow]Warning:[/yellow] {format_tool_warning_cli(tool)}"
1472
+ )
1473
+ except Exception:
1474
+ logger.debug("Failed to check for optional tools", exc_info=True)
1475
+ # Validate sandbox provider deps before spawning server subprocess
1476
+ if args.sandbox and args.sandbox not in {"none", "langsmith"}:
1477
+ from docagent_cli.integrations.sandbox_factory import (
1478
+ verify_sandbox_deps,
1479
+ )
1480
+
1481
+ try:
1482
+ verify_sandbox_deps(args.sandbox)
1483
+ except ImportError as exc:
1484
+ from rich.markup import escape
1485
+
1486
+ console.print(f"[bold red]Error:[/bold red] {escape(str(exc))}")
1487
+ sys.exit(1)
1488
+
1489
+ # Non-interactive mode - execute single task and exit
1490
+ from docagent_cli.non_interactive import run_non_interactive
1491
+
1492
+ exit_code = asyncio.run(
1493
+ run_non_interactive(
1494
+ message=args.non_interactive_message,
1495
+ assistant_id=args.agent,
1496
+ model_name=getattr(args, "model", None),
1497
+ model_params=model_params,
1498
+ profile_override=profile_override,
1499
+ sandbox_type=args.sandbox,
1500
+ sandbox_id=args.sandbox_id,
1501
+ sandbox_setup=getattr(args, "sandbox_setup", None),
1502
+ quiet=args.quiet,
1503
+ stream=not args.no_stream,
1504
+ mcp_config_path=getattr(args, "mcp_config", None),
1505
+ no_mcp=getattr(args, "no_mcp", False),
1506
+ trust_project_mcp=getattr(args, "trust_project_mcp", False),
1507
+ )
1508
+ )
1509
+ sys.exit(exit_code)
1510
+ else:
1511
+ # Interactive mode - handle thread resume
1512
+ from rich.style import Style
1513
+ from rich.text import Text
1514
+
1515
+ from docagent_cli.config import (
1516
+ build_langsmith_thread_url,
1517
+ )
1518
+ from docagent_cli.sessions import (
1519
+ generate_thread_id,
1520
+ thread_exists,
1521
+ )
1522
+
1523
+ # Instead of resolving thread_id here with synchronous asyncio.run()
1524
+ # DB calls, pass the raw resume request to the TUI and let it
1525
+ # resolve asynchronously during startup.
1526
+ resume_thread = args.resume_thread # "__MOST_RECENT__", "<id>", or None
1527
+ thread_id = None if resume_thread else generate_thread_id()
1528
+
1529
+ # Validate sandbox provider deps before spawning server subprocess
1530
+ if args.sandbox and args.sandbox not in {"none", "langsmith"}:
1531
+ from docagent_cli.integrations.sandbox_factory import (
1532
+ verify_sandbox_deps,
1533
+ )
1534
+
1535
+ try:
1536
+ verify_sandbox_deps(args.sandbox)
1537
+ except ImportError as exc:
1538
+ from rich.markup import escape
1539
+
1540
+ console.print(f"[bold red]Error:[/bold red] {escape(str(exc))}")
1541
+ sys.exit(1)
1542
+
1543
+ # Check project MCP trust before launching TUI
1544
+ mcp_trust_decision = _check_mcp_project_trust(
1545
+ trust_flag=getattr(args, "trust_project_mcp", False),
1546
+ )
1547
+
1548
+ # Run Textual CLI
1549
+ return_code = 0
1550
+ try:
1551
+ result = asyncio.run(
1552
+ run_textual_cli_async(
1553
+ assistant_id=args.agent,
1554
+ auto_approve=args.auto_approve,
1555
+ sandbox_type=args.sandbox,
1556
+ sandbox_id=args.sandbox_id,
1557
+ sandbox_setup=getattr(args, "sandbox_setup", None),
1558
+ model_name=getattr(args, "model", None),
1559
+ model_params=model_params,
1560
+ profile_override=profile_override,
1561
+ thread_id=thread_id,
1562
+ resume_thread=resume_thread,
1563
+ initial_prompt=getattr(args, "initial_prompt", None),
1564
+ mcp_config_path=getattr(args, "mcp_config", None),
1565
+ no_mcp=getattr(args, "no_mcp", False),
1566
+ trust_project_mcp=mcp_trust_decision,
1567
+ )
1568
+ )
1569
+ return_code = result.return_code
1570
+ # The user may have switched threads via /threads during the
1571
+ # session; use the final thread ID for teardown messages.
1572
+ thread_id = result.thread_id or thread_id
1573
+ _print_session_stats(result.session_stats, console)
1574
+ except Exception as e: # noqa: BLE001 # Top-level error handler for the application
1575
+ error_msg = Text("\nApplication error: ", style="red")
1576
+ error_msg.append(str(e))
1577
+ console.print(error_msg)
1578
+ console.print(Text(traceback.format_exc(), style="dim"))
1579
+ sys.exit(1)
1580
+
1581
+ # Show LangSmith thread link for threads with checkpointed
1582
+ # content (same table that backs the `/threads` listing).
1583
+ if thread_id:
1584
+ try:
1585
+ thread_url = build_langsmith_thread_url(thread_id)
1586
+ if thread_url and asyncio.run(thread_exists(thread_id)):
1587
+ console.print()
1588
+ ls_hint = Text("View this thread in LangSmith: ", style="dim")
1589
+ ls_hint.append(
1590
+ thread_url,
1591
+ style=Style(dim=True, link=thread_url),
1592
+ )
1593
+ console.print(ls_hint)
1594
+ except Exception:
1595
+ logger.debug(
1596
+ "Could not display LangSmith thread URL on teardown",
1597
+ exc_info=True,
1598
+ )
1599
+
1600
+ # Show resume hint on exit for threads with checkpointed content.
1601
+ if thread_id and return_code == 0 and asyncio.run(thread_exists(thread_id)):
1602
+ console.print()
1603
+ console.print("[dim]Resume this thread with:[/dim]")
1604
+ hint = Text("docagent -r ", style="cyan")
1605
+ hint.append(str(thread_id), style="cyan")
1606
+ console.print(hint)
1607
+
1608
+ # Warn about available update on exit
1609
+ try:
1610
+ if result.update_available[0]:
1611
+ from docagent_cli.update_check import (
1612
+ is_auto_update_enabled,
1613
+ upgrade_command,
1614
+ )
1615
+
1616
+ latest = result.update_available[1]
1617
+ console.print()
1618
+ update_msg = Text("Update available: ", style="yellow bold")
1619
+ update_msg.append(f"v{latest}", style="yellow")
1620
+ console.print(update_msg)
1621
+ cmd_hint = Text("Run: ", style="dim")
1622
+ cmd_hint.append(upgrade_command(), style="cyan")
1623
+ console.print(cmd_hint)
1624
+ if not is_auto_update_enabled():
1625
+ auto_hint = Text("Enable auto-updates: ", style="dim")
1626
+ auto_hint.append("/auto-update", style="cyan")
1627
+ console.print(auto_hint)
1628
+ except Exception:
1629
+ logger.debug("Failed to display exit update banner", exc_info=True)
1630
+ except KeyboardInterrupt:
1631
+ # Clean exit on Ctrl+C — suppress ugly traceback.
1632
+ # `console` may not be bound if Ctrl+C arrives during config import.
1633
+ try:
1634
+ console.print("\n\n[yellow]Interrupted[/yellow]")
1635
+ except NameError:
1636
+ sys.stderr.write("\n\nInterrupted\n")
1637
+ sys.exit(0)
1638
+
1639
+
1640
+ if __name__ == "__main__":
1641
+ cli_main()