nexforge-cli 0.2.12__tar.gz → 0.2.14__tar.gz

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 (198) hide show
  1. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/PKG-INFO +2 -2
  2. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/README.md +1 -1
  3. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/manual/01-/345/256/211/350/243/205/344/270/216/351/205/215/347/275/256.md +3 -1
  4. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/manual/02-/347/225/214/351/235/242/344/270/216/345/277/253/346/215/267/351/224/256.md +1 -1
  5. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/manual/03-/346/250/241/345/236/213/351/205/215/347/275/256.md +23 -0
  6. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/manual/04-/345/267/245/345/205/267/345/217/202/350/200/203.md +2 -2
  7. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/manual/05-/346/225/260/346/215/256/344/270/216/346/220/234/347/264/242.md +3 -1
  8. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/manual/08-/350/207/252/345/256/232/344/271/211/351/205/215/347/275/256.md +2 -1
  9. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/manual/10-/346/217/222/344/273/266/345/274/200/345/217/221.md +2 -2
  10. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/manual/README.md +1 -1
  11. nexforge_cli-0.2.14/plugin_gui_auto/README.md +86 -0
  12. nexforge_cli-0.2.14/plugin_gui_auto/guiauto/__init__.py +80 -0
  13. nexforge_cli-0.2.14/plugin_gui_auto/guiauto/action.py +194 -0
  14. nexforge_cli-0.2.14/plugin_gui_auto/guiauto/base.py +161 -0
  15. nexforge_cli-0.2.14/plugin_gui_auto/guiauto/tools.py +429 -0
  16. nexforge_cli-0.2.14/plugin_gui_auto/guiauto/vision.py +243 -0
  17. nexforge_cli-0.2.14/plugin_gui_auto/guiauto/window.py +207 -0
  18. nexforge_cli-0.2.14/plugin_gui_auto/install_plugin.py +148 -0
  19. nexforge_cli-0.2.14/plugin_gui_auto/requirements.txt +6 -0
  20. nexforge_cli-0.2.14/plugin_gui_auto/smoke_safe.py +43 -0
  21. nexforge_cli-0.2.14/plugin_gui_auto/verify_final.py +61 -0
  22. nexforge_cli-0.2.14/plugin_gui_auto/verify_host.py +33 -0
  23. nexforge_cli-0.2.14/plugin_gui_auto/verify_plugin.py +94 -0
  24. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/pyproject.toml +1 -1
  25. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/__init__.py +1 -1
  26. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/app.py +35 -2
  27. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/cli.py +1 -1
  28. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/config.py +17 -3
  29. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/agent.py +8 -4
  30. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/federation.py +3 -0
  31. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/goal_manager.py +14 -2
  32. nexforge_cli-0.2.14/src/nexforge/core/model_catalog.py +114 -0
  33. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/provider.py +8 -0
  34. nexforge_cli-0.2.14/src/nexforge/core/update_check.py +99 -0
  35. nexforge_cli-0.2.14/src/nexforge/features/mode_features.py +686 -0
  36. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/session_feature.py +2 -0
  37. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/i18n.py +15 -1
  38. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/provider/deepseek.py +82 -1
  39. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/provider/openai.py +8 -3
  40. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/provider/qianwen.py +8 -0
  41. nexforge_cli-0.2.14/src/nexforge/store/director_store.py +113 -0
  42. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/theme.tcss +13 -4
  43. nexforge_cli-0.2.14/src/nexforge/tools/image_tools.py +320 -0
  44. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/screenshot_tools.py +53 -24
  45. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/clipboard_image.py +54 -33
  46. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/screens/chat.py +196 -18
  47. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/screens/chat_actions.py +29 -5
  48. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/slash_handler.py +2 -1
  49. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/model_sidebar.py +50 -3
  50. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/statusbar.py +1 -1
  51. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/web/server.py +1 -1
  52. nexforge_cli-0.2.14/tests/test_app_mouse.py +72 -0
  53. nexforge_cli-0.2.14/tests/test_cross_platform.py +354 -0
  54. nexforge_cli-0.2.14/tests/test_director_loop.py +278 -0
  55. nexforge_cli-0.2.14/tests/test_director_store.py +65 -0
  56. nexforge_cli-0.2.14/tests/test_dual_panel.py +203 -0
  57. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_federation.py +18 -0
  58. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_goal_confirm.py +3 -0
  59. nexforge_cli-0.2.14/tests/test_model_catalog.py +191 -0
  60. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_p2_core.py +21 -6
  61. nexforge_cli-0.2.14/tests/test_tool_iters.py +55 -0
  62. nexforge_cli-0.2.14/tests/test_update_check.py +122 -0
  63. nexforge_cli-0.2.14/tests/test_vision_model.py +65 -0
  64. nexforge_cli-0.2.14/tests/test_vision_tool_retention.py +98 -0
  65. nexforge_cli-0.2.12/src/nexforge/features/mode_features.py +0 -456
  66. nexforge_cli-0.2.12/src/nexforge/tools/image_tools.py +0 -173
  67. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/.gitignore +0 -0
  68. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/INSTALL.md +0 -0
  69. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/STATS_API.md +0 -0
  70. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/agent.svg +0 -0
  71. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/cost.svg +0 -0
  72. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/edit.svg +0 -0
  73. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/err.svg +0 -0
  74. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/file.svg +0 -0
  75. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/model.svg +0 -0
  76. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/ok.svg +0 -0
  77. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/read.svg +0 -0
  78. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/running.svg +0 -0
  79. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/search.svg +0 -0
  80. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/shell.svg +0 -0
  81. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/thinking.svg +0 -0
  82. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/web.svg +0 -0
  83. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/assets/icons/write.svg +0 -0
  84. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/config.example.toml +0 -0
  85. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/devlog/2026-07-07.md +0 -0
  86. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/doc//344/273/243/347/240/201/344/274/230/345/214/226/350/256/241/345/210/222.md" +0 -0
  87. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/doc//347/211/271/346/200/247/346/250/241/345/235/227/346/263/250/345/206/214/345/210/266-/345/256/236/346/226/275/350/256/241/345/210/222.md" +0 -0
  88. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/doc//351/241/271/347/233/256/346/200/273/347/233/221/345/276/252/347/216/257-/345/274/200/345/217/221/350/256/241/345/210/222.md" +0 -0
  89. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/docs/submission/README-/346/212/225/347/250/277/350/257/264/346/230/216.md" +0 -0
  90. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/docs/submission/nexforge.md +0 -0
  91. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/docs/submission/nexforge.zh-CN.md +0 -0
  92. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/manual/06-/346/235/203/351/231/220/344/270/216/346/250/241/345/274/217.md" +0 -0
  93. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/manual/07-/344/274/232/350/257/235/344/270/216/350/256/260/345/277/206.md" +0 -0
  94. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/manual/09-/345/270/270/350/247/201/351/227/256/351/242/230.md" +0 -0
  95. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/manual//351/231/204/345/275/225-/345/257/271/346/257/224/345/210/206/346/236/220.md" +0 -0
  96. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/scripts/publish.sh +0 -0
  97. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/scripts/smoke_provider.py +0 -0
  98. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/scripts/smoke_tui.py +0 -0
  99. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/__main__.py +0 -0
  100. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/__init__.py +0 -0
  101. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/context.py +0 -0
  102. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/jobs.py +0 -0
  103. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/knowledge_base.py +0 -0
  104. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/memory_engine.py +0 -0
  105. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/permissions.py +0 -0
  106. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/plugin.py +0 -0
  107. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/project_mapper.py +0 -0
  108. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/router.py +0 -0
  109. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/session.py +0 -0
  110. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/summarizer.py +0 -0
  111. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/system_inject.py +0 -0
  112. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/tool_manifest.py +0 -0
  113. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/core/types.py +0 -0
  114. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/__init__.py +0 -0
  115. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/auto_skills.py +0 -0
  116. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/base.py +0 -0
  117. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/federation_feature.py +0 -0
  118. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/kb_feature.py +0 -0
  119. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/map_feature.py +0 -0
  120. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/memory_feature.py +0 -0
  121. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/plan_feature.py +0 -0
  122. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/registry.py +0 -0
  123. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/slash_feature.py +0 -0
  124. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/stats_feature.py +0 -0
  125. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/features/tool_manifest_feature.py +0 -0
  126. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/icons.py +0 -0
  127. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/mcp/__init__.py +0 -0
  128. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/provider/__init__.py +0 -0
  129. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/provider/_retry.py +0 -0
  130. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/provider/anthropic.py +0 -0
  131. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/provider/balance.py +0 -0
  132. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/skills/loader.py +0 -0
  133. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/stats/__init__.py +0 -0
  134. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/stats/heartbeat.py +0 -0
  135. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/store/__init__.py +0 -0
  136. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/store/plan_store.py +0 -0
  137. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/store/session_store.py +0 -0
  138. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/__init__.py +0 -0
  139. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/_paths.py +0 -0
  140. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/background.py +0 -0
  141. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/base.py +0 -0
  142. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/data_tools.py +0 -0
  143. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/edit_file.py +0 -0
  144. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/federation.py +0 -0
  145. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/fs_ops.py +0 -0
  146. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/git.py +0 -0
  147. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/kb_tools.py +0 -0
  148. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/list_dir.py +0 -0
  149. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/multi_edit.py +0 -0
  150. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/ops_tools.py +0 -0
  151. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/read_file.py +0 -0
  152. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/registry.py +0 -0
  153. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/search.py +0 -0
  154. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/search_content.py +0 -0
  155. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/search_files.py +0 -0
  156. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/shell.py +0 -0
  157. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/web.py +0 -0
  158. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/tools/write_file.py +0 -0
  159. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/__init__.py +0 -0
  160. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/screen_api.py +0 -0
  161. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/screens/__init__.py +0 -0
  162. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/screens/splash.py +0 -0
  163. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/__init__.py +0 -0
  164. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/apikey_modal.py +0 -0
  165. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/banner.py +0 -0
  166. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/confirm_modal.py +0 -0
  167. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/diff_modal.py +0 -0
  168. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/image_sidebar.py +0 -0
  169. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/messages.py +0 -0
  170. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/mode_warning.py +0 -0
  171. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/multiline_modal.py +0 -0
  172. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/multiline_prompt.py +0 -0
  173. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/plan_sidebar.py +0 -0
  174. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/ui/widgets/sidebar.py +0 -0
  175. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/utils/__init__.py +0 -0
  176. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/utils/logging.py +0 -0
  177. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/web/__init__.py +0 -0
  178. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/src/nexforge/web/static/index.html +0 -0
  179. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/conftest.py +0 -0
  180. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_approval.py +0 -0
  181. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_config.py +0 -0
  182. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_i18n.py +0 -0
  183. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_icons.py +0 -0
  184. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_lang_cmd.py +0 -0
  185. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_m2_tools.py +0 -0
  186. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_manual_consistency.py +0 -0
  187. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_plugin.py +0 -0
  188. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_sanitize.py +0 -0
  189. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_screenshot_tools.py +0 -0
  190. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_session_repair.py +0 -0
  191. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_session_store.py +0 -0
  192. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_sidebar.py +0 -0
  193. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_sidebar_actions.py +0 -0
  194. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_splash_modal.py +0 -0
  195. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_statusbar.py +0 -0
  196. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_system_inject.py +0 -0
  197. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_tools.py +0 -0
  198. {nexforge_cli-0.2.12 → nexforge_cli-0.2.14}/tests/test_tui.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nexforge-cli
3
- Version: 0.2.12
3
+ Version: 0.2.14
4
4
  Summary: NexForgeCLI — 一个面向 DeepSeek V4 的终端智能体(TUI + CLI 双模式)
5
5
  Project-URL: Homepage, https://gitee.com/Lighthorn/nexforgecli
6
6
  Author: NexForge
@@ -51,7 +51,7 @@ Description-Content-Type: text/markdown
51
51
 
52
52
  NexForgeCLI 是一个运行在终端里的智能体助手,既支持 DeepSeek/千问等多模型,也是**插件宿主**(plugins 目录即插即用)。既能作为全屏 TUI 交互式对话/编码,也能作为 one-shot 命令行工具嵌进脚本和管道。
53
53
 
54
- > 当前版本:**v0.2.11** · PyPI: `pip install nexforge-cli`
54
+ > 当前版本:**v0.2.14** · PyPI: `pip install nexforge-cli`
55
55
 
56
56
  ---
57
57
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  NexForgeCLI 是一个运行在终端里的智能体助手,既支持 DeepSeek/千问等多模型,也是**插件宿主**(plugins 目录即插即用)。既能作为全屏 TUI 交互式对话/编码,也能作为 one-shot 命令行工具嵌进脚本和管道。
10
10
 
11
- > 当前版本:**v0.2.11** · PyPI: `pip install nexforge-cli`
11
+ > 当前版本:**v0.2.14** · PyPI: `pip install nexforge-cli`
12
12
 
13
13
  ---
14
14
 
@@ -1,6 +1,6 @@
1
1
  # 安装与配置
2
2
 
3
- > NexForgeCLI v0.2.12
3
+ > NexForgeCLI v0.2.14
4
4
 
5
5
  ## 环境要求
6
6
 
@@ -72,6 +72,8 @@ API Key 保存在 `~/.nexforge/auth.toml`,不会被 Git 追踪。
72
72
  pip install --upgrade nexforge-cli
73
73
  ```
74
74
 
75
+ 启动 nf 时会自动检查 PyPI 最新版本(24 小时缓存一次,失败静默),若有新版本会在界面提示升级命令。
76
+
75
77
  ## 卸载
76
78
 
77
79
  ```bash
@@ -64,7 +64,7 @@
64
64
  |------|------|
65
65
  | `/key <key>` | 设置 API Key |
66
66
  | `/provider [<name>]` | 无参数列出提供者;带参数切换 |
67
- | `/model flash|pro|auto` | 切换模型档位(仅 DeepSeek) |
67
+ | `/model flash|pro|auto|vision` | 切换模型档位(仅 DeepSeek, vision=视觉模型) |
68
68
  | `/think on|off` | 开关思考模式(仅 DeepSeek) |
69
69
  | `/balance` | 查询账户余额(仅 DeepSeek) |
70
70
  | `/cost` | 显示累计费用 |
@@ -20,8 +20,10 @@ DeepSeek 是内置的默认模型,首次运行只需提供 API Key 即可使
20
20
  | flash | 快速模型 | 常规编码、文件操作、日常问答 |
21
21
  | pro | 推理模型 | 复杂算法、架构设计、调试 |
22
22
  | auto | 自动路由 | 默认。小任务 flash,大任务自动升级 pro |
23
+ | vision | 视觉模型 | 图片识别/理解(deepseek-v4-flash-vision-exp, 发图即用) |
23
24
 
24
25
  **默认 flash**:deepseek 涨价后默认固定 flash 省成本;需要深度推理时用 `/model pro`,或 `/model auto` 恢复自动路由。
26
+ **视觉模型**:`/model vision` 或侧边栏切"视觉"档位后,粘贴图片直接分析(原生多模态,不走识图工具);vision 档位自动关闭思考模式(exp 模型兼容性)。
25
27
 
26
28
  auto 模式下的升级触发条件:
27
29
  - 关键词(重构/架构/算法/证明/调试/为什么/设计)
@@ -123,6 +125,27 @@ models = ["claude-sonnet-4-20250514"]
123
125
 
124
126
  切换后自动持久化,重启 nf 保持选中。
125
127
 
128
+ ### 动态模型列表(千问 / OpenAI 兼容)
129
+
130
+ 大模型更新太快,`models.toml` 里手写的模型名可能过时。对 **千问** 和 **OpenAI 兼容**(`type = "openai"`)provider,F6 侧边栏会:
131
+
132
+ - 自动调用该平台的 `/models` 接口,拉取当前 API Key 下**实际可用**的模型列表
133
+ - 拉取结果缓存 24 小时,失败时回退 `models.toml` 里写的静态列表
134
+ - 每个模型名都是一个可点击按钮,直接点选切换具体模型(当前使用的模型标绿点 ●)
135
+
136
+ ```
137
+ │ ● qianwen ✓ │
138
+ │ 类型: qianwen │
139
+ │ ● qwen3.8-max │ ← 当前使用
140
+ │ qwen-vision-plus │ ← 动态拉取到的新模型, 点选即切换
141
+ │ qwen-long │
142
+ │ [🔄 刷新模型] │ ← 强制重新拉取最新列表
143
+ ```
144
+
145
+ 点「🔄 刷新模型」可强制清缓存重新拉取(比如平台刚上线了新模型)。
146
+
147
+ **选择持久化**:点选的具体模型记录到 `~/.nexforge/model_selection.json`(独立于只读的 `models.toml`,不污染配置),重启 nf 保持选中。
148
+
126
149
  ## 自动路由 vs 多模型切换
127
150
 
128
151
  - **自动路由**(DeepSeek auto 档):同一个模型内,根据任务复杂度自动选择 flash 或 pro
@@ -45,7 +45,7 @@ NexForgeCLI 内置 **35 个工具**供 AI 调用(`git_commit` 需在 config.to
45
45
  |------|------|
46
46
  | `web_fetch` | HTTP GET 抓取 URL(带 User-Agent, 5MB 限制) |
47
47
  | `search_web` | 网络搜索(默认 Bing, 可选 Google/DuckDuckGo/SearXNG) |
48
- | `image_vision` | 图片识别(阿里云千问 VL) |
48
+ | `image_vision` | 图片识别(deepseek-vision + 千问 VL 双引擎, 支持 cross 交叉验证) |
49
49
  | `image_gen` | 图片生成(阿里云通义万相) |
50
50
  | `kb_search` | 知识库检索(全局+项目两级) |
51
51
  | `kb_write` | 知识沉淀(写入项目/全局知识库) |
@@ -87,7 +87,7 @@ NexForgeCLI 内置 **35 个工具**供 AI 调用(`git_commit` 需在 config.to
87
87
 
88
88
  | 限制 | 值 | 说明 |
89
89
  |------|-----|------|
90
- | 工具调用轮数 | 200 | 防死循环 |
90
+ | 工具调用轮数 | 300(目标模式 500) | 防死循环; 单轮内连续调工具上限, 目标模式自动放大 |
91
91
  | 单轮总超时 | 0 秒(不限额) | 默认不限额; 可 /timeout <秒> 或 config.toml 的 `turn_timeout` 设置 |
92
92
  | 单工具超时 | 60 秒 | |
93
93
 
@@ -50,7 +50,9 @@ HTTP GET 抓取指定 URL,返回正文(截断至 20000 字符)。带浏览
50
50
 
51
51
  ### image_vision(图片识别)
52
52
 
53
- 传入图片路径和问题,调用阿里云千问 VL 视觉模型分析图片内容。
53
+ 传入图片路径和问题,分析图片内容。**双引擎识别**:
54
+ - `mode=auto`(默认):优先用当前对话 provider 的视觉模型(如 deepseek-vision,共用对话 key),失败回退阿里云千问 qwen-vl
55
+ - `mode=cross`:**双模型交叉验证**——deepseek-vision 与 qwen-vl 并行识别同一张图,返回双答案供交叉印证,减少单一模型的视觉误差(适合 GUI 验收/关键判断)
54
56
 
55
57
  ### image_gen(图片生成)
56
58
 
@@ -25,7 +25,7 @@
25
25
 
26
26
  ```toml
27
27
  [models]
28
- default_tier = "flash" # flash | pro | auto(默认 flash 省成本; auto 会按需升级 pro)
28
+ default_tier = "flash" # flash | pro | auto | vision(默认 flash 省成本; auto 会按需升级 pro; vision=视觉模型)
29
29
  thinking = true # 思考模式
30
30
  reasoning_effort = "high" # high | max
31
31
  max_output_tokens = 32768 # 输出上限(含思考 tokens), 长思考会吃配额, 不够可调大
@@ -59,6 +59,7 @@ model = "qwen-audio-3.0-tts-flash" # 千问 TTS 模型
59
59
  voice = "loongmary" # 音色(flash 用 loong 系列: loongmary/loongeva/loongJohn 等)
60
60
  engine = "auto" # auto | qianwen | local
61
61
  max_chars = 100 # 简报最大字符数
62
+ enabled = true # 语音播报总开关
62
63
 
63
64
  [search]
64
65
  engine = "bing" # bing | duckduckgo | google | searxng
@@ -70,9 +70,9 @@ def register(api):
70
70
 
71
71
  `~/.nexforge/plugins/speech_summary.py`(已随本机安装)——每轮任务结束用 TTS 播报简报:
72
72
 
73
- - **引擎**:千问语音合成(qwen-audio-3.0-tts-plus,官方 dashscope SDK WebSocket 方式)优先,失败自动回退 Windows 本地语音(SAPI)
73
+ - **引擎**:千问语音合成(qwen-audio-3.0-tts-flash,官方 dashscope SDK WebSocket 方式)优先,失败自动回退 Windows 本地语音(SAPI)
74
74
  - **配置**(config.toml `[tts]` 段):`engine=auto|qianwen|local`、`voice`、`api_key`(留空用 [image] 按量 key)、`ws_url`、`max_chars`、`enabled`
75
75
  - **不阻塞**:合成+播放在后台线程,回合结束流程零延迟
76
76
  - **简报内容**:项目名 + 工具调用次数 + AI 最终总结摘要(自动清理 Markdown 符号)
77
77
 
78
- > 千问 TTS 使用条件:① `pip install dashscope`;② 平台控制台**开通 qwen-audio-3.0-tts-plus 按量服务**(¥1.4/万字符,Token Plan 套餐不含)。未满足时自动回退本地语音,不打断工作。
78
+ > 千问 TTS 使用条件:① `pip install dashscope`;② 平台控制台**开通 qwen-audio-3.0-tts-flash 按量服务**(¥1.4/万字符,Token Plan 套餐不含)。未满足时自动回退本地语音,不打断工作。
@@ -1,4 +1,4 @@
1
- # NexForgeCLI 用户手册 · v0.2.12
1
+ # NexForgeCLI 用户手册 · v0.2.14
2
2
 
3
3
  ## 这是什么
4
4
 
@@ -0,0 +1,86 @@
1
+ # NF-GUI —— 给 AI 的跨平台 GUI 自动化插件
2
+
3
+ 让 NexForgeCLI 的 AI 获得操作计算机鼠标、键盘、屏幕的能力:能看界面、能精准定位元素、
4
+ 能点击/拖拽/输入,用于 GUI 应用的自动化测试、界面验收、交互验证。
5
+
6
+ ## 特性
7
+
8
+ - **跨平台**:Windows / macOS / Linux(pyautogui 底层,统一逻辑像素坐标)
9
+ - **三层定位**:模板匹配(快准)· 视觉模型(通用)· 像素特征(零依赖)
10
+ - **复杂拖拽**:直线/曲线路径、分段控制点、按住停顿——滑块、调整大小、排序全支持
11
+ - **中文输入**:自动走剪贴板,不受键盘布局影响
12
+ - **操作留痕**:每次操作前自动存档 before 截图到 `.nexforge/gui_logs/`
13
+ - **安全**:鼠标甩到左上角即中断(FAILSAFE)+ 写操作默认需审批
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ cd plugin_gui_auto
19
+ python install_plugin.py # 装依赖 + 安装到用户级 + 自检
20
+ python install_plugin.py --project # 或装到当前项目
21
+ # 重启 nf 或执行 /plugins reload
22
+ ```
23
+
24
+ 可选依赖:`opencv-python`(模板匹配)、`pyperclip`(中文输入,通常已随 pyautogui 带)。
25
+
26
+ ## 工具清单
27
+
28
+ | 工具 | 功能 | 审批 |
29
+ |------|------|------|
30
+ | `gui_screen` | 截取屏幕/窗口/区域 | 免 |
31
+ | `gui_find` | 定位元素中心坐标(template/prompt/color 三选一) | 免 |
32
+ | `gui_click` | 点击(左/右/中,单/双击,窗口内偏移) | 需 |
33
+ | `gui_drag` | 拖拽(直线/曲线,分段控制,按住停顿) | 需 |
34
+ | `gui_type` | 输入文本(中文走剪贴板) | 需 |
35
+ | `gui_key` | 按键/组合键(enter/ctrl+c/alt+f4) | 需 |
36
+ | `gui_scroll` | 滚动 | 需 |
37
+ | `gui_windows` | 窗口管理(枚举/激活/几何/最小化/最大化/关闭) | 免 |
38
+
39
+ ## 典型工作流
40
+
41
+ ```
42
+ AI 收到 GUI 测试任务后:
43
+
44
+ 1. gui_windows list # 找目标窗口(或 gui_windows activate handle 激活)
45
+ 2. gui_screen # 截图看当前界面
46
+ 3. gui_find prompt="登录按钮的中心" # 定位(视觉模型)
47
+ # 或 gui_find template="btn.png" # 定位(模板匹配, 更快)
48
+ 4. gui_click x=960 y=540 # 点击
49
+ 5. gui_type text="admin" # 输入
50
+ 6. gui_drag x1=100 y1=200 x2=300 y2=200 curve=true # 拖滑块
51
+ 7. gui_screen # 再截图验证结果
52
+ ```
53
+
54
+ ## 坐标体系
55
+
56
+ - 所有坐标是**屏幕逻辑像素**(与 DPI 无关,Windows 高 DPI 自动处理)
57
+ - `gui_windows geometry` 可拿窗口矩形;`client` 动作转客户区坐标
58
+ - 多显示器:pyautogui 只可靠操作主显示器(官方限制),次屏请先拖到主屏
59
+
60
+ ## 跨平台注意
61
+
62
+ | 平台 | 说明 |
63
+ |------|------|
64
+ | Windows | 功能最全(窗口控制/DPI 感知/PrintWindow 截图) |
65
+ | Linux | 需 X11;窗口枚举依赖 `xdotool`(`sudo apt install xdotool`);Wayland 需 XWayland |
66
+ | macOS | pyautogui 需辅助功能权限(系统设置→隐私→辅助功能);窗口控制走 AppleScript |
67
+
68
+ ## 开发
69
+
70
+ ```
71
+ guiauto/
72
+ __init__.py register(api) 插件入口 + 技能提示
73
+ base.py 核心底座(平台/DPI/坐标/安全/截图)
74
+ vision.py 视觉定位(模板匹配/视觉模型/像素特征)
75
+ action.py 鼠标键盘操作(点击/拖拽/输入/按键/滚动)
76
+ window.py 窗口控制(枚举/激活/几何/最小化/关闭)
77
+ tools.py 8 个 BaseTool 工具层(暴露给 AI)
78
+ ```
79
+
80
+ ## 设计决策
81
+
82
+ - **视觉模型定位为主力**:Qt/WPF/Web 等自绘控件 UIAutomation 支持有限,视觉定位通用;
83
+ 模板匹配作为"常用元素"的加速通道;像素特征零依赖兜底
84
+ - **操作与定位分离**:`gui_find` 只回答"在哪",`gui_click/drag` 只负责"执行",
85
+ AI 可组合出任意流程
86
+ - **轻量**:核心依赖仅 pyautogui;模板匹配/视觉模型均为可选增强
@@ -0,0 +1,80 @@
1
+ """NF-GUI 自动化插件 —— 让 AI 获得操作计算机鼠标/键盘/屏幕的能力。
2
+
3
+ 安装方式(二选一):
4
+ 1) 用户级(所有项目可用): 把整个 guiauto/ 目录复制到
5
+ ~/.nexforge/plugins/guiauto/
6
+ 2) 项目级(仅当前项目): 复制到
7
+ <项目根>/.nexforge/plugins/guiauto/
8
+
9
+ 复制后重启 nf(或 /plugins reload)即生效。验证: 运行 gui_screen 应成功截图。
10
+
11
+ 提供 8 个 AI 工具(均以 gui_ 前缀, 不与内置工具冲突):
12
+ gui_screen 截取屏幕/窗口/区域(GUI 自动化第一步)
13
+ gui_find 定位元素中心坐标(模板匹配/视觉模型/像素特征 三选一)
14
+ gui_click 点击(左/右/中, 单击/双击, 窗口内偏移)
15
+ gui_drag 拖拽(直线/曲线, 分段控制点, 按住停顿)
16
+ gui_type 输入文本(中文自动走剪贴板)
17
+ gui_key 按键/组合键(enter/ctrl+c/alt+f4)
18
+ gui_scroll 滚动
19
+ gui_windows 窗口管理(枚举/激活/几何/最小化/最大化/关闭)
20
+
21
+ 典型工作流: gui_screen 看界面 → gui_find 定位 → gui_click/gui_drag 操作
22
+ → gui_screen 验证结果。
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ __version__ = "0.1.0"
28
+
29
+ from . import base as B
30
+
31
+
32
+ def register(api):
33
+ """插件宿主入口: 注册 GUI 自动化工具 + 技能提示。"""
34
+ from .tools import (
35
+ GuiClickTool,
36
+ GuiDragTool,
37
+ GuiFindTool,
38
+ GuiKeyTool,
39
+ GuiScreenTool,
40
+ GuiScrollTool,
41
+ GuiTypeTool,
42
+ GuiWindowsTool,
43
+ )
44
+
45
+ for tool in (
46
+ GuiScreenTool(),
47
+ GuiFindTool(),
48
+ GuiClickTool(),
49
+ GuiDragTool(),
50
+ GuiTypeTool(),
51
+ GuiKeyTool(),
52
+ GuiScrollTool(),
53
+ GuiWindowsTool(),
54
+ ):
55
+ try:
56
+ api.register_tool(tool)
57
+ except Exception:
58
+ pass # 单个工具注册失败不阻塞其他
59
+
60
+ api.register_skill("gui-auto", _skill_hint)
61
+
62
+
63
+ def _skill_hint(screen=None) -> str | None:
64
+ """技能提示: 每轮注入 system, 引导 AI 在 GUI 任务时使用工具链。"""
65
+ try:
66
+ err = B.ensure_ready()
67
+ if err:
68
+ return None # pyautogui 未装, 不注入
69
+ return (
70
+ "你有 GUI 自动化工具(gui_*): 需要操作/测试图形界面时, 按此流程:\n"
71
+ "1. gui_windows list 找目标窗口(或用 handle 激活)\n"
72
+ "2. gui_screen 截图看当前界面\n"
73
+ "3. gui_find 定位元素中心坐标(template=图标模板 / prompt=中文描述 / color=颜色)\n"
74
+ "4. gui_click / gui_drag / gui_type / gui_key 执行操作\n"
75
+ "5. 再 gui_screen 截图验证结果\n"
76
+ "坐标均为屏幕逻辑像素; 拖拽滑块/调整大小用 gui_drag(curve=true 更真实); "
77
+ "中文输入用 gui_type; 操作前会自动存档 before 截图到 .nexforge/gui_logs/。"
78
+ )
79
+ except Exception:
80
+ return None
@@ -0,0 +1,194 @@
1
+ """NF-GUI 操作执行 —— 「手」。
2
+
3
+ 基于 pyautogui 的鼠标/键盘操作, 统一坐标(逻辑像素):
4
+ - click: 点击(左/右/双击/三击)
5
+ - move: 移动(瞬时/平滑/指定步数曲线)
6
+ - drag: 拖拽(直线/分段曲线, 支持中间停顿)
7
+ - type: 输入文本(ASCII/Unicode, 中文自动走剪贴板)
8
+ - key: 按/组合键
9
+ - scroll: 滚动
10
+
11
+ 所有操作前可 `pre_action` 回调(如在 .nexforge/temp 存操作前后截图)。
12
+ 鼠标动作间有 PAUSE 间隔 + FAILSAFE 保护。
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import time
18
+ from typing import Callable
19
+
20
+ from . import base as B
21
+
22
+ # 操作日志(供 AI 回顾)
23
+ _OP_LOG: list[dict] = []
24
+
25
+
26
+ def op_log() -> list[dict]:
27
+ """返回本会话操作日志(最近 N 条)。"""
28
+ return _OP_LOG[-100:]
29
+
30
+
31
+ def _log(kind: str, **info) -> None:
32
+ info["kind"] = kind
33
+ info["t"] = time.strftime("%H:%M:%S")
34
+ _OP_LOG.append(info)
35
+ if len(_OP_LOG) > 200:
36
+ _OP_LOG.pop(0)
37
+
38
+
39
+ def _clamp(x: int, y: int) -> tuple[int, int]:
40
+ """坐标钳制到屏幕范围内(防误点出界)。"""
41
+ s = B.screen_size()
42
+ x = max(0, min(int(x), max(0, s["width"] - 1)))
43
+ y = max(0, min(int(y), max(0, s["height"] - 1)))
44
+ return x, y
45
+
46
+
47
+ def _pg():
48
+ if not B.PY_AUTOGUI_OK:
49
+ raise RuntimeError(B._dependency_error())
50
+ return B._pg
51
+
52
+
53
+ # ── 鼠标基础 ──────────────────────────────────────────────
54
+ def click(x: int, y: int, button: str = "left", clicks: int = 1,
55
+ interval: float = 0.2, **kw) -> dict:
56
+ """点击。button=left/right/middle; clicks=次数(双击=2)。"""
57
+ x, y = _clamp(x, y)
58
+ pg = _pg()
59
+ pg.click(x, y, clicks=max(1, min(int(clicks), 3)),
60
+ button=button, interval=max(0.0, float(interval)))
61
+ _log("click", x=x, y=y, button=button, clicks=clicks)
62
+ return {"ok": True, "x": x, "y": y, "button": button, "clicks": clicks}
63
+
64
+
65
+ def move(x: int, y: int, duration: float = 0.3, steps: int = 20, **kw) -> dict:
66
+ """移动鼠标。duration=总时长, steps=控制点数量(越大越平滑, 可模拟曲线)。"""
67
+ x, y = _clamp(x, y)
68
+ pg = _pg()
69
+ pg.moveTo(x, y, duration=max(0.05, float(duration)), tween=_tween(steps))
70
+ _log("move", x=x, y=y, duration=duration)
71
+ return {"ok": True, "x": x, "y": y}
72
+
73
+
74
+ def _tween(steps: int):
75
+ """生成平滑 tween(步数控制曲线平滑度)。"""
76
+ import pytweening
77
+ steps = max(1, min(int(steps), 100))
78
+ # 用 easeOutQuad 抛物线平滑(近似人类拖动, 避免瞬移)
79
+ return pytweening.easeOutQuad
80
+
81
+
82
+ def drag(x1: int, y1: int, x2: int, y2: int, duration: float = 0.8,
83
+ steps: int = 30, hold_ms: int = 200, button: str = "left",
84
+ curve: bool = False, **kw) -> dict:
85
+ """拖拽: 按住从 (x1,y1) 移动到 (x2,y2) 释放。
86
+
87
+ - steps: 中间控制点数量(越多越平滑, 支持精确路径)
88
+ - hold_ms: 按住后停顿(某些控件需要先稳定再拖)
89
+ - curve: 用曲线路径(模拟真实拖拽, 遍历中间插值点)
90
+ """
91
+ x1, y1 = _clamp(x1, y1)
92
+ x2, y2 = _clamp(x2, y2)
93
+ pg = _pg()
94
+ pg.moveTo(x1, y1)
95
+ pg.mouseDown(button=button)
96
+ if hold_ms > 0:
97
+ time.sleep(hold_ms / 1000.0)
98
+ steps = max(2, min(int(steps), 200))
99
+ if curve:
100
+ # 曲线路径: 用 tween 在 start/end 间插值
101
+ import pytweening
102
+ dur = max(0.2, float(duration))
103
+ start = time.monotonic()
104
+ while True:
105
+ t = (time.monotonic() - start) / dur
106
+ if t >= 1:
107
+ break
108
+ e = pytweening.easeInOutQuad(min(1.0, t))
109
+ cx = int(x1 + (x2 - x1) * e)
110
+ cy = int(y1 + (y2 - y1) * e)
111
+ pg.moveTo(cx, cy)
112
+ time.sleep(0.01)
113
+ else:
114
+ pg.moveTo(x2, y2, duration=max(0.1, float(duration)))
115
+ time.sleep(0.05)
116
+ pg.mouseUp(button=button)
117
+ _log("drag", x1=x1, y1=y1, x2=x2, y2=y2, steps=steps, button=button, curve=curve)
118
+ return {"ok": True, "from": [x1, y1], "to": [x2, y2],
119
+ "steps": steps, "button": button, "curve": curve}
120
+
121
+
122
+ def scroll(x: int, y: int, amount: int = -3, **kw) -> dict:
123
+ """滚动。amount>0 上滚, <0 下滚; x,y 为滚动时鼠标位置。"""
124
+ x, y = _clamp(x, y)
125
+ pg = _pg()
126
+ pg.moveTo(x, y)
127
+ pg.scroll(int(amount), x=x, y=y)
128
+ _log("scroll", x=x, y=y, amount=amount)
129
+ return {"ok": True, "x": x, "y": y, "amount": amount}
130
+
131
+
132
+ # ── 键盘 ──────────────────────────────────────────────────
133
+ def type_text(text: str, interval: float = 0.02, **kw) -> dict:
134
+ """输入文本。
135
+
136
+ 全 ASCII/常见符号 → 逐字符 typewrite(快)。
137
+ 中文/Unicode → 走剪贴板+Ctrl+V(准确, 不受键盘布局影响)。
138
+ """
139
+ pg = _pg()
140
+ try:
141
+ # 判断是否需要剪贴板(含非 ASCII 即走剪贴板)
142
+ if text and any(ord(c) > 127 for c in text):
143
+ import pyperclip
144
+ pyperclip.copy(text)
145
+ pg.hotkey("ctrl", "v")
146
+ else:
147
+ pg.typewrite(text, interval=max(0.0, float(interval)))
148
+ except Exception as e:
149
+ return {"ok": False, "error": f"键盘输入失败: {e}"}
150
+ _log("type", text=text[:40])
151
+ return {"ok": True, "chars": len(text)}
152
+
153
+
154
+ def key(keys, **kw) -> dict:
155
+ """按键/组合键。keys 可为 "enter" 或 ["ctrl", "c"] 组合。"""
156
+ pg = _pg()
157
+ try:
158
+ if isinstance(keys, str):
159
+ if "+" in keys:
160
+ parts = [p.strip() for p in keys.split("+")]
161
+ pg.hotkey(*parts)
162
+ else:
163
+ pg.press(keys)
164
+ elif isinstance(keys, (list, tuple)):
165
+ pg.hotkey(*keys)
166
+ else:
167
+ return {"ok": False, "error": f"keys 类型错误: {type(keys)}"}
168
+ except Exception as e:
169
+ return {"ok": False, "error": f"按键失败: {e}"}
170
+ _log("key", keys=keys)
171
+ return {"ok": True, "keys": keys}
172
+
173
+
174
+ def hotkey(*keys) -> dict:
175
+ """组合键(如 ctrl, shift, s)。"""
176
+ return key(list(keys))
177
+
178
+
179
+ # ── 前置操作钩子(截图存档) ────────────────────────────────
180
+ _pre_hook: Callable[[str], None] | None = None
181
+
182
+
183
+ def set_pre_hook(fn: Callable[[str], None] | None) -> None:
184
+ """设置每个动作前的回调(如存档 before 截图)。"""
185
+ global _pre_hook
186
+ _pre_hook = fn
187
+
188
+
189
+ def _notify(kind: str) -> None:
190
+ if _pre_hook:
191
+ try:
192
+ _pre_hook(kind)
193
+ except Exception:
194
+ pass