nexforge-cli 0.2.11__tar.gz → 0.2.13__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 (193) hide show
  1. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/PKG-INFO +1 -1
  2. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/manual/01-/345/256/211/350/243/205/344/270/216/351/205/215/347/275/256.md +1 -1
  3. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/manual/02-/347/225/214/351/235/242/344/270/216/345/277/253/346/215/267/351/224/256.md +1 -1
  4. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/manual/03-/346/250/241/345/236/213/351/205/215/347/275/256.md +2 -0
  5. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/manual/04-/345/267/245/345/205/267/345/217/202/350/200/203.md +6 -2
  6. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/manual/05-/346/225/260/346/215/256/344/270/216/346/220/234/347/264/242.md +3 -1
  7. nexforge_cli-0.2.13/manual/07-/344/274/232/350/257/235/344/270/216/350/256/260/345/277/206.md +100 -0
  8. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/manual/08-/350/207/252/345/256/232/344/271/211/351/205/215/347/275/256.md +1 -1
  9. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/manual/README.md +1 -1
  10. nexforge_cli-0.2.13/plugin_gui_auto/README.md +86 -0
  11. nexforge_cli-0.2.13/plugin_gui_auto/guiauto/__init__.py +80 -0
  12. nexforge_cli-0.2.13/plugin_gui_auto/guiauto/action.py +194 -0
  13. nexforge_cli-0.2.13/plugin_gui_auto/guiauto/base.py +161 -0
  14. nexforge_cli-0.2.13/plugin_gui_auto/guiauto/tools.py +429 -0
  15. nexforge_cli-0.2.13/plugin_gui_auto/guiauto/vision.py +243 -0
  16. nexforge_cli-0.2.13/plugin_gui_auto/guiauto/window.py +207 -0
  17. nexforge_cli-0.2.13/plugin_gui_auto/install_plugin.py +148 -0
  18. nexforge_cli-0.2.13/plugin_gui_auto/requirements.txt +6 -0
  19. nexforge_cli-0.2.13/plugin_gui_auto/smoke_safe.py +43 -0
  20. nexforge_cli-0.2.13/plugin_gui_auto/verify_final.py +61 -0
  21. nexforge_cli-0.2.13/plugin_gui_auto/verify_host.py +33 -0
  22. nexforge_cli-0.2.13/plugin_gui_auto/verify_plugin.py +94 -0
  23. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/pyproject.toml +1 -1
  24. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/__init__.py +1 -1
  25. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/app.py +4 -1
  26. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/cli.py +1 -1
  27. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/config.py +7 -2
  28. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/agent.py +5 -4
  29. nexforge_cli-0.2.13/src/nexforge/core/federation.py +497 -0
  30. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/knowledge_base.py +8 -1
  31. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/summarizer.py +9 -0
  32. nexforge_cli-0.2.13/src/nexforge/core/system_inject.py +50 -0
  33. nexforge_cli-0.2.13/src/nexforge/features/federation_feature.py +183 -0
  34. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/kb_feature.py +4 -5
  35. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/map_feature.py +6 -6
  36. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/mode_features.py +62 -1
  37. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/i18n.py +19 -7
  38. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/provider/deepseek.py +82 -1
  39. nexforge_cli-0.2.13/src/nexforge/tools/federation.py +175 -0
  40. nexforge_cli-0.2.13/src/nexforge/tools/image_tools.py +320 -0
  41. nexforge_cli-0.2.13/src/nexforge/tools/kb_tools.py +133 -0
  42. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/registry.py +3 -0
  43. nexforge_cli-0.2.13/src/nexforge/tools/screenshot_tools.py +349 -0
  44. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/clipboard_image.py +54 -33
  45. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/screens/chat.py +35 -8
  46. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/slash_handler.py +60 -7
  47. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/model_sidebar.py +2 -2
  48. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/statusbar.py +6 -1
  49. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/web/server.py +1 -1
  50. nexforge_cli-0.2.13/tests/test_cross_platform.py +354 -0
  51. nexforge_cli-0.2.13/tests/test_federation.py +494 -0
  52. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_manual_consistency.py +1 -1
  53. nexforge_cli-0.2.13/tests/test_p2_core.py +127 -0
  54. nexforge_cli-0.2.13/tests/test_screenshot_tools.py +77 -0
  55. nexforge_cli-0.2.13/tests/test_statusbar.py +53 -0
  56. nexforge_cli-0.2.13/tests/test_system_inject.py +90 -0
  57. nexforge_cli-0.2.13/tests/test_vision_model.py +65 -0
  58. nexforge_cli-0.2.13/tests/test_vision_tool_retention.py +98 -0
  59. nexforge_cli-0.2.11/_tmp_whl_check.py +0 -19
  60. nexforge_cli-0.2.11/manual/07-/344/274/232/350/257/235/344/270/216/350/256/260/345/277/206.md +0 -63
  61. nexforge_cli-0.2.11/src/nexforge/core/federation.py +0 -202
  62. nexforge_cli-0.2.11/src/nexforge/features/federation_feature.py +0 -47
  63. nexforge_cli-0.2.11/src/nexforge/tools/federation.py +0 -86
  64. nexforge_cli-0.2.11/src/nexforge/tools/image_tools.py +0 -173
  65. nexforge_cli-0.2.11/src/nexforge/tools/kb_tools.py +0 -88
  66. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/.gitignore +0 -0
  67. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/INSTALL.md +0 -0
  68. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/README.md +0 -0
  69. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/STATS_API.md +0 -0
  70. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/agent.svg +0 -0
  71. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/cost.svg +0 -0
  72. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/edit.svg +0 -0
  73. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/err.svg +0 -0
  74. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/file.svg +0 -0
  75. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/model.svg +0 -0
  76. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/ok.svg +0 -0
  77. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/read.svg +0 -0
  78. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/running.svg +0 -0
  79. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/search.svg +0 -0
  80. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/shell.svg +0 -0
  81. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/thinking.svg +0 -0
  82. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/web.svg +0 -0
  83. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/assets/icons/write.svg +0 -0
  84. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/config.example.toml +0 -0
  85. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/devlog/2026-07-07.md +0 -0
  86. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/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.11 → nexforge_cli-0.2.13}/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.11 → nexforge_cli-0.2.13}/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.11 → nexforge_cli-0.2.13}/docs/submission/README-/346/212/225/347/250/277/350/257/264/346/230/216.md" +0 -0
  90. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/docs/submission/nexforge.md +0 -0
  91. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/docs/submission/nexforge.zh-CN.md +0 -0
  92. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/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.11 → nexforge_cli-0.2.13}/manual/09-/345/270/270/350/247/201/351/227/256/351/242/230.md" +0 -0
  94. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/manual/10-/346/217/222/344/273/266/345/274/200/345/217/221.md" +0 -0
  95. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/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.11 → nexforge_cli-0.2.13}/scripts/publish.sh +0 -0
  97. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/scripts/smoke_provider.py +0 -0
  98. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/scripts/smoke_tui.py +0 -0
  99. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/__main__.py +0 -0
  100. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/__init__.py +0 -0
  101. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/context.py +0 -0
  102. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/goal_manager.py +0 -0
  103. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/jobs.py +0 -0
  104. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/memory_engine.py +0 -0
  105. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/permissions.py +0 -0
  106. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/plugin.py +0 -0
  107. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/project_mapper.py +0 -0
  108. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/provider.py +0 -0
  109. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/router.py +0 -0
  110. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/session.py +0 -0
  111. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/tool_manifest.py +0 -0
  112. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/core/types.py +0 -0
  113. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/__init__.py +0 -0
  114. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/auto_skills.py +0 -0
  115. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/base.py +0 -0
  116. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/memory_feature.py +0 -0
  117. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/plan_feature.py +0 -0
  118. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/registry.py +0 -0
  119. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/session_feature.py +0 -0
  120. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/slash_feature.py +0 -0
  121. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/stats_feature.py +0 -0
  122. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/features/tool_manifest_feature.py +0 -0
  123. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/icons.py +0 -0
  124. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/mcp/__init__.py +0 -0
  125. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/provider/__init__.py +0 -0
  126. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/provider/_retry.py +0 -0
  127. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/provider/anthropic.py +0 -0
  128. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/provider/balance.py +0 -0
  129. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/provider/openai.py +0 -0
  130. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/provider/qianwen.py +0 -0
  131. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/skills/loader.py +0 -0
  132. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/stats/__init__.py +0 -0
  133. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/stats/heartbeat.py +0 -0
  134. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/store/__init__.py +0 -0
  135. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/store/plan_store.py +0 -0
  136. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/store/session_store.py +0 -0
  137. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/theme.tcss +0 -0
  138. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/__init__.py +0 -0
  139. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/_paths.py +0 -0
  140. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/background.py +0 -0
  141. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/base.py +0 -0
  142. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/data_tools.py +0 -0
  143. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/edit_file.py +0 -0
  144. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/fs_ops.py +0 -0
  145. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/git.py +0 -0
  146. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/list_dir.py +0 -0
  147. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/multi_edit.py +0 -0
  148. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/ops_tools.py +0 -0
  149. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/read_file.py +0 -0
  150. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/search.py +0 -0
  151. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/search_content.py +0 -0
  152. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/search_files.py +0 -0
  153. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/shell.py +0 -0
  154. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/web.py +0 -0
  155. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/tools/write_file.py +0 -0
  156. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/__init__.py +0 -0
  157. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/screen_api.py +0 -0
  158. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/screens/__init__.py +0 -0
  159. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/screens/chat_actions.py +0 -0
  160. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/screens/splash.py +0 -0
  161. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/__init__.py +0 -0
  162. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/apikey_modal.py +0 -0
  163. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/banner.py +0 -0
  164. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/confirm_modal.py +0 -0
  165. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/diff_modal.py +0 -0
  166. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/image_sidebar.py +0 -0
  167. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/messages.py +0 -0
  168. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/mode_warning.py +0 -0
  169. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/multiline_modal.py +0 -0
  170. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/multiline_prompt.py +0 -0
  171. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/plan_sidebar.py +0 -0
  172. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/ui/widgets/sidebar.py +0 -0
  173. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/utils/__init__.py +0 -0
  174. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/utils/logging.py +0 -0
  175. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/web/__init__.py +0 -0
  176. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/src/nexforge/web/static/index.html +0 -0
  177. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/conftest.py +0 -0
  178. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_approval.py +0 -0
  179. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_config.py +0 -0
  180. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_goal_confirm.py +0 -0
  181. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_i18n.py +0 -0
  182. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_icons.py +0 -0
  183. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_lang_cmd.py +0 -0
  184. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_m2_tools.py +0 -0
  185. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_plugin.py +0 -0
  186. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_sanitize.py +0 -0
  187. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_session_repair.py +0 -0
  188. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_session_store.py +0 -0
  189. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_sidebar.py +0 -0
  190. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_sidebar_actions.py +0 -0
  191. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_splash_modal.py +0 -0
  192. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/tests/test_tools.py +0 -0
  193. {nexforge_cli-0.2.11 → nexforge_cli-0.2.13}/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.11
3
+ Version: 0.2.13
4
4
  Summary: NexForgeCLI — 一个面向 DeepSeek V4 的终端智能体(TUI + CLI 双模式)
5
5
  Project-URL: Homepage, https://gitee.com/Lighthorn/nexforgecli
6
6
  Author: NexForge
@@ -1,6 +1,6 @@
1
1
  # 安装与配置
2
2
 
3
- > NexForgeCLI v0.2.11
3
+ > NexForgeCLI v0.2.13
4
4
 
5
5
  ## 环境要求
6
6
 
@@ -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
  - 关键词(重构/架构/算法/证明/调试/为什么/设计)
@@ -45,10 +45,12 @@ 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` | 知识沉淀(写入项目/全局知识库) |
52
+ | `window_screenshot` | 指定窗口截图(Windows, 标题/进程定位, 遮挡也能截) |
53
+ | `web_screenshot` | Web 页面离线渲染截图(无头浏览器, 不弹窗) |
52
54
 
53
55
  详见 [05-数据与搜索](05-数据与搜索.md)。
54
56
 
@@ -77,7 +79,9 @@ NexForgeCLI 内置 **35 个工具**供 AI 调用(`git_commit` 需在 config.to
77
79
 
78
80
  | 工具 | 功能 |
79
81
  |------|------|
80
- | `federation` | 多项目联结体管理 |
82
+ | `federation` | 多项目联结体管理(init/add/remove/context/status/memory/sessions/handoff/close/task-add/task-list/task-done) |
83
+ | `kb_search` | 知识检索(scope 支持 fed 聚合联结体成员、member:<名> 指定成员) |
84
+ | `kb_write` | 知识沉淀(scope 支持 `member:<成员名>` 写回联结体成员知识库) |
81
85
 
82
86
  ## 工具限制
83
87
 
@@ -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
 
@@ -0,0 +1,100 @@
1
+ # 会话与记忆
2
+
3
+ ## 会话管理
4
+
5
+ ### 自动保存
6
+
7
+ 每次 AI 回复完成后自动保存当前会话到 `.nexforge/sessions/`,文件为可读 Markdown 格式。
8
+
9
+ ### 自动恢复
10
+
11
+ 启动时自动恢复当前项目最近一次会话,包括完整对话历史。
12
+
13
+ ### 长会话上下文压缩
14
+
15
+ 超过 60 轮时自动压缩:保留最近 20 轮全文,更早的对话压缩为**历史摘要**(恢复后可见,避免历史大段丢失);被压缩的原始对话**完整归档**到 `.nexforge/sessions/<id>.archive.md`,随时可查。
16
+
17
+ ### 会话侧边栏
18
+
19
+ 按 `F5` 打开会话侧边栏:
20
+
21
+ - **新建会话**:点击 `+ 新建会话`
22
+ - **切换会话**:点击会话名,自动保存当前、加载选中
23
+ - **重命名**:`/rename 新名称`
24
+ - **删除**:点击 `✖` 按钮
25
+
26
+ 当前活跃会话有绿色圆点标记。
27
+
28
+ ### 相关命令
29
+
30
+ | 命令 | 说明 |
31
+ |------|------|
32
+ | `/save [name]` | 保存并命名当前会话 |
33
+ | `/rename <name>` | 重命名当前会话 |
34
+ | `/sessions` | 列出历史会话 |
35
+ | `/clear` | 清空当前会话 |
36
+ | `/cost` | 显示累计 token 和费用 |
37
+
38
+ ## 项目记忆
39
+
40
+ NexForgeCLI 支持多层记忆,让 AI 在多次对话中持久记住项目信息。
41
+
42
+ ### 记忆类型
43
+
44
+ | 记忆层 | 文件 | 作用 |
45
+ |--------|------|------|
46
+ | **基础记忆** | `.nexforge/memory/base.md` | 所有场景共享的持久信息 |
47
+ | **运维记忆** | `.nexforge/memory/ops.md` | 运维操作记录(服务器配置、常用命令) |
48
+ | **目标记忆** | `.nexforge/memory/goal.md` | 目标模式专用上下文 |
49
+ | **已验证方案** | `.nexforge/ops_known.md` | 运维已验证的成功命令/调用(读取复用,可手动维护) |
50
+
51
+ 启动时自动加载基础记忆。运维模式开启时额外加载运维记忆。
52
+
53
+ ### 联 结体
54
+
55
+ 联结体是**多项目协同工作区**。关联多个相关项目,为每个项目设定角色,AI 在联结体上下文中工作,
56
+ **并把工作写回各成员项目自身结构**,保证成员回到各自的 nf 实例继续开发时零信息割裂。
57
+
58
+ ```bash
59
+ # 在 nf 输入框里用 /federation 命令
60
+ /federation init # 初始化联结体
61
+ /federation add <项目路径> [角色] # 添加成员(名称取目录名, 角色可选如 后端)
62
+ /federation remove <名称> # 移除成员
63
+ /federation context # 查看联结体上下文(成员+角色+最近共享记忆)
64
+ /federation status # 查看各成员实时状态(git分支/未提交/最近会话)
65
+ /federation memory # 查看共享记忆
66
+ /federation sessions # 列出各成员项目的会话历史
67
+ /federation handoff # 为所有成员生成/更新交接文档
68
+ /federation close # 结束本次协同: 全成员写回(交接+地图+纪要会话)
69
+ ```
70
+
71
+ #### 双向同步机制(写回)
72
+
73
+ 联结体是**临时工作区**,各成员项目是**持久家园**。协同期间自动写回:
74
+
75
+ | 写回内容 | 落点(成员项目内) | 触发 |
76
+ |---------|-----------------|------|
77
+ | 每轮操作日志 | `.nexforge/ops_log.md` | 每轮结束自动 |
78
+ | 进展记忆 | `.nexforge/memory/base.md` | 每轮结束自动 |
79
+ | 项目地图刷新 | `.nexforge/map/` | 轮末哈希比对,有变化才 sync |
80
+ | 知识沉淀 | `.nexforge/kb/`(`kb_write scope=member:<名>`) | AI 主动 |
81
+ | 交接文档 | `.nexforge/federation/handoff.md` | `/federation close` 或 `handoff` |
82
+ | 工作纪要会话 | `.nexforge/sessions/session-*.md` | `/federation close` |
83
+
84
+ 成员项目回到自身 nf 启动时,若检测到 `handoff.md` 会自动注入交接上下文,AI 延续工作不割裂。
85
+ `kb_write` 支持 `scope=member:<成员名>` 直接把知识写回指定成员的知识库;
86
+ `kb_search` 支持 `scope=fed`(聚合检索所有成员知识库) 和 `scope=member:<名>`(检索指定成员)。
87
+
88
+ #### 任务流转
89
+
90
+ AI 通过 `federation` 工具管理跨成员任务(`.nexforge/federation/tasks.md`):
91
+
92
+ ```
93
+ federation task-add name=<成员> content=<任务描述> # 分配任务, 返回 #id
94
+ federation task-list # 列出所有任务及状态
95
+ federation task-done content=<id> # 标记任务完成
96
+ ```
97
+
98
+ #### 双向记忆
99
+
100
+ 共享记忆按「用户指令 + AI 决策」成对记录(每轮自动),供联结体上下文读取,协同意图可追溯。
@@ -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), 长思考会吃配额, 不够可调大
@@ -1,4 +1,4 @@
1
- # NexForgeCLI 用户手册 · v0.2.11
1
+ # NexForgeCLI 用户手册 · v0.2.13
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