hap-cli 0.8.24__tar.gz → 0.8.26__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 (287) hide show
  1. {hap_cli-0.8.24 → hap_cli-0.8.26}/PKG-INFO +3 -3
  2. {hap_cli-0.8.24/hap_cli → hap_cli-0.8.26}/README.md +2 -2
  3. {hap_cli-0.8.24 → hap_cli-0.8.26/hap_cli}/README.md +2 -2
  4. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/README_CN.md +2 -1
  5. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/__init__.py +1 -1
  6. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/chart_cmd.py +5 -2
  7. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/chat_cmd.py +58 -10
  8. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/config_cmd.py +93 -16
  9. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/instance_cmd.py +15 -4
  10. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/record_cmd.py +14 -2
  11. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/chart.py +30 -0
  12. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/chat.py +73 -0
  13. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/instance.py +147 -13
  14. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/record.py +7 -0
  15. hap_cli-0.8.26/hap_cli/guide/en/chart.md +150 -0
  16. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/guide/index.json +8 -0
  17. hap_cli-0.8.26/hap_cli/guide/zh-Hans/chart.md +133 -0
  18. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/hap_cli.py +4 -0
  19. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/i18n.py +24 -7
  20. hap_cli-0.8.26/hap_cli/locale/messages.json +4821 -0
  21. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_chart.py +33 -0
  22. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_chat_attachment_send.py +137 -0
  23. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_core.py +197 -1
  24. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_i18n.py +115 -16
  25. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_integration_approval_actions.py +34 -10
  26. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +6 -1
  27. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli.egg-info/PKG-INFO +3 -3
  28. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli.egg-info/SOURCES.txt +2 -0
  29. hap_cli-0.8.24/hap_cli/locale/messages.json +0 -3271
  30. {hap_cli-0.8.24 → hap_cli-0.8.26}/MANIFEST.in +0 -0
  31. {hap_cli-0.8.24 → hap_cli-0.8.26}/app_live_tests/__init__.py +0 -0
  32. {hap_cli-0.8.24 → hap_cli-0.8.26}/app_live_tests/__main__.py +0 -0
  33. {hap_cli-0.8.24 → hap_cli-0.8.26}/app_live_tests/config.py +0 -0
  34. {hap_cli-0.8.24 → hap_cli-0.8.26}/app_live_tests/harness.py +0 -0
  35. {hap_cli-0.8.24 → hap_cli-0.8.26}/app_live_tests/smoke.py +0 -0
  36. {hap_cli-0.8.24 → hap_cli-0.8.26}/app_live_tests/state.py +0 -0
  37. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/__main__.py +0 -0
  38. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/__init__.py +0 -0
  39. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/app_cmd.py +0 -0
  40. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/app_creator_cmd.py +0 -0
  41. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/app_editor_cmd.py +0 -0
  42. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/attachment_cmd.py +0 -0
  43. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/auth_cmd.py +0 -0
  44. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/calendar_cmd.py +0 -0
  45. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/chatbot_cmd.py +0 -0
  46. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/contact_cmd.py +0 -0
  47. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/department_cmd.py +0 -0
  48. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/group_cmd.py +0 -0
  49. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/guide_cmd.py +0 -0
  50. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/icon_cmd.py +0 -0
  51. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/node_cmd.py +0 -0
  52. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/optionset_cmd.py +0 -0
  53. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/page_cmd.py +0 -0
  54. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/post_cmd.py +0 -0
  55. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/region_cmd.py +0 -0
  56. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/role_cmd.py +0 -0
  57. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/update_cmd.py +0 -0
  58. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/upload_cmd.py +0 -0
  59. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/v3_registry.py +0 -0
  60. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/workflow_cmd.py +0 -0
  61. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/commands/worksheet_cmd.py +0 -0
  62. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/context.py +0 -0
  63. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/__init__.py +0 -0
  64. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/action_spec_adapter.py +0 -0
  65. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app.py +0 -0
  66. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/__init__.py +0 -0
  67. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
  68. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
  69. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
  70. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/charts.py +0 -0
  71. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/cleanup.py +0 -0
  72. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/compiler.py +0 -0
  73. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/config.py +0 -0
  74. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/design/design.schema.json +0 -0
  75. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/errors.py +0 -0
  76. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
  77. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/executor.py +0 -0
  78. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/fields.py +0 -0
  79. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/hap.py +0 -0
  80. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/merge.py +0 -0
  81. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/recording/__init__.py +0 -0
  82. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/recording/console.py +0 -0
  83. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
  84. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/recording/mirror.py +0 -0
  85. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/recording/report.py +0 -0
  86. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/schema.py +0 -0
  87. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
  88. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/__init__.py +0 -0
  89. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/cli.py +0 -0
  90. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/executor.py +0 -0
  91. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
  92. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
  93. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
  94. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
  95. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/seed/template.py +0 -0
  96. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/selftest.py +0 -0
  97. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/smoke.py +0 -0
  98. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/steps.py +0 -0
  99. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/store.py +0 -0
  100. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/validate.py +0 -0
  101. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
  102. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/attachment.py +0 -0
  103. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/auth.py +0 -0
  104. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/calendar_mod.py +0 -0
  105. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/chatbot.py +0 -0
  106. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/cli_access.py +0 -0
  107. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/contact.py +0 -0
  108. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/control_type_codes.py +0 -0
  109. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/department.py +0 -0
  110. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/__init__.py +0 -0
  111. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  112. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  113. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/apply.py +0 -0
  114. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/componentlower.py +0 -0
  115. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/editspec/component.schema.json +0 -0
  116. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
  117. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
  118. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/editspec/field.schema.json +0 -0
  119. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/editspec.py +0 -0
  120. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/errors.py +0 -0
  121. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/fieldlower.py +0 -0
  122. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  123. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/models.py +0 -0
  124. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/ops.py +0 -0
  125. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/planner.py +0 -0
  126. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/editor/reader.py +0 -0
  127. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/field_normalizer.py +0 -0
  128. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/field_spec_adapter.py +0 -0
  129. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/filter_translator.py +0 -0
  130. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/flow_node.py +0 -0
  131. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/global_meta.py +0 -0
  132. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/group.py +0 -0
  133. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/guide.py +0 -0
  134. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/icon_index.py +0 -0
  135. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/logger.py +0 -0
  136. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/optionset.py +0 -0
  137. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/page.py +0 -0
  138. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/post.py +0 -0
  139. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/response_crypto.py +0 -0
  140. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/role.py +0 -0
  141. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/role_perm_builder.py +0 -0
  142. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/session.py +0 -0
  143. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/token_crypto.py +0 -0
  144. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/update.py +0 -0
  145. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/upload.py +0 -0
  146. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/v3/__init__.py +0 -0
  147. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/v3/dispatcher.py +0 -0
  148. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/v3/render.py +0 -0
  149. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/v3/schema.py +0 -0
  150. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/view_spec_adapter.py +0 -0
  151. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/workflow.py +0 -0
  152. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/workflow_node_dsl.py +0 -0
  153. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/worksheet.py +0 -0
  154. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/core/worksheet_templates.py +0 -0
  155. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/guide/en/messages.md +0 -0
  156. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/guide/en/overview.md +0 -0
  157. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/guide/en/record.md +0 -0
  158. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/guide/en/setup.md +0 -0
  159. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/guide/en/skills.md +0 -0
  160. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/guide/zh-Hans/messages.md +0 -0
  161. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/guide/zh-Hans/overview.md +0 -0
  162. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/guide/zh-Hans/record.md +0 -0
  163. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/guide/zh-Hans/setup.md +0 -0
  164. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/guide/zh-Hans/skills.md +0 -0
  165. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/locale/__init__.py +0 -0
  166. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/locale/messages.schema.json +0 -0
  167. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/__init__.py +0 -0
  168. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/conftest.py +0 -0
  169. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_app_creator.py +0 -0
  170. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_app_editor.py +0 -0
  171. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_app_export_usage.py +0 -0
  172. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_attachment_share.py +0 -0
  173. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_auth_prerelease.py +0 -0
  174. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_bug_036430.py +0 -0
  175. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_bug_036750.py +0 -0
  176. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_cli_access.py +0 -0
  177. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_config_cmd.py +0 -0
  178. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_content_language.py +0 -0
  179. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_control_lookup_guard.py +0 -0
  180. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
  181. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_full_e2e.py +0 -0
  182. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_global_meta.py +0 -0
  183. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_guide.py +0 -0
  184. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_instance_history_cli.py +0 -0
  185. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_integration.py +0 -0
  186. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_integration_approval.py +0 -0
  187. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_integration_calendar.py +0 -0
  188. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_integration_destructive.py +0 -0
  189. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_integration_misc.py +0 -0
  190. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_integration_post.py +0 -0
  191. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_integration_social.py +0 -0
  192. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_integration_v3.py +0 -0
  193. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_integration_workflow.py +0 -0
  194. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  195. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
  196. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_multi_profile.py +0 -0
  197. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
  198. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_org_id_cli.py +0 -0
  199. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_org_id_docs.py +0 -0
  200. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_parameter_conventions.py +0 -0
  201. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  202. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
  203. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_record_field_alias.py +0 -0
  204. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
  205. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_record_write_fixes.py +0 -0
  206. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_select_add_option.py +0 -0
  207. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
  208. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_semantic_types_registry.py +0 -0
  209. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_update.py +0 -0
  210. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_update_mode.py +0 -0
  211. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_upload_host_resolution.py +0 -0
  212. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  213. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  214. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  215. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_v3_session.py +0 -0
  216. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  217. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
  218. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_workflow_list_cli.py +0 -0
  219. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
  220. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
  221. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_worksheet_create_section.py +0 -0
  222. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  223. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
  224. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/utils/__init__.py +0 -0
  225. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/utils/formatting.py +0 -0
  226. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/utils/options.py +0 -0
  227. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli/utils/parameter_mapping.py +0 -0
  228. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli.egg-info/dependency_links.txt +0 -0
  229. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli.egg-info/entry_points.txt +0 -0
  230. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli.egg-info/requires.txt +0 -0
  231. {hap_cli-0.8.24 → hap_cli-0.8.26}/hap_cli.egg-info/top_level.txt +0 -0
  232. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/__init__.py +0 -0
  233. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/__main__.py +0 -0
  234. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/_wftest.py +0 -0
  235. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/charts.py +0 -0
  236. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/cleanup.py +0 -0
  237. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/compiler.py +0 -0
  238. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/config.py +0 -0
  239. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/errors.py +0 -0
  240. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/executor.py +0 -0
  241. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/fields.py +0 -0
  242. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/hap.py +0 -0
  243. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/recording/__init__.py +0 -0
  244. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/recording/console.py +0 -0
  245. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/recording/jsonl.py +0 -0
  246. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/recording/mirror.py +0 -0
  247. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/recording/report.py +0 -0
  248. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/schema.py +0 -0
  249. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/seed/__init__.py +0 -0
  250. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/seed/cli.py +0 -0
  251. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/seed/executor.py +0 -0
  252. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/seed/template.py +0 -0
  253. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/selftest.py +0 -0
  254. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/smoke.py +0 -0
  255. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/step.py +0 -0
  256. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/steps.py +0 -0
  257. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/store.py +0 -0
  258. {hap_cli-0.8.24 → hap_cli-0.8.26}/live_tests/workflow_dsl.py +0 -0
  259. {hap_cli-0.8.24 → hap_cli-0.8.26}/setup.cfg +0 -0
  260. {hap_cli-0.8.24 → hap_cli-0.8.26}/setup.py +0 -0
  261. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/INDEX.json +0 -0
  262. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  263. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  264. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/create_optionset.yaml +0 -0
  265. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/create_role.yaml +0 -0
  266. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  267. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/delete_record.yaml +0 -0
  268. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/delete_role.yaml +0 -0
  269. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  270. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_app_info.yaml +0 -0
  271. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  272. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  273. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  274. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_details.yaml +0 -0
  275. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  276. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_list.yaml +0 -0
  277. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  278. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  279. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  280. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  281. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_regions.yaml +0 -0
  282. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_role_details.yaml +0 -0
  283. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/get_role_list.yaml +0 -0
  284. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  285. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  286. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  287. {hap_cli-0.8.24 → hap_cli-0.8.26}/sources/v3-api-schema/update_optionset.yaml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hap-cli
3
- Version: 0.8.24
3
+ Version: 0.8.26
4
4
  Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -273,7 +273,7 @@ discover every subcommand.
273
273
  | `chat messages` | 1-on-1 / group / inbox messages |
274
274
  | `chat files` | Files shared in a chat |
275
275
  | `chat send-to-one` | Send a message, optionally with files (`--file`), to one or more users (1-on-1) |
276
- | `chat send-to-group` | Send a plain-text message to a group chat |
276
+ | `chat send-to-group` | Send a message, optionally with files, to a group chat |
277
277
  | `chat card-detail` | Resolve embedded card references |
278
278
 
279
279
  ### group — chat group lifecycle
@@ -326,7 +326,7 @@ discover every subcommand.
326
326
  |---|---|
327
327
  | `config show` | Display the current configuration summary |
328
328
  | `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` completion |
329
- | `config language [LANG]` | Switch UI language (`en` / `zh-Hans` / `zh-Hant` / `ja`) |
329
+ | `config language [LANG]` | Switch UI language (`en` / `zh-Hans`, or `auto` to follow the account language) |
330
330
  | `config update [auto\|notify\|off]` | Self-upgrade mode; `notify` only reports, `off` never checks |
331
331
  | `config log on / off / status` | Toggle and inspect API logging |
332
332
  | `config log level LEVEL` | `DEBUG / INFO / WARNING / ERROR / CRITICAL` |
@@ -244,7 +244,7 @@ discover every subcommand.
244
244
  | `chat messages` | 1-on-1 / group / inbox messages |
245
245
  | `chat files` | Files shared in a chat |
246
246
  | `chat send-to-one` | Send a message, optionally with files (`--file`), to one or more users (1-on-1) |
247
- | `chat send-to-group` | Send a plain-text message to a group chat |
247
+ | `chat send-to-group` | Send a message, optionally with files, to a group chat |
248
248
  | `chat card-detail` | Resolve embedded card references |
249
249
 
250
250
  ### group — chat group lifecycle
@@ -297,7 +297,7 @@ discover every subcommand.
297
297
  |---|---|
298
298
  | `config show` | Display the current configuration summary |
299
299
  | `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` completion |
300
- | `config language [LANG]` | Switch UI language (`en` / `zh-Hans` / `zh-Hant` / `ja`) |
300
+ | `config language [LANG]` | Switch UI language (`en` / `zh-Hans`, or `auto` to follow the account language) |
301
301
  | `config update [auto\|notify\|off]` | Self-upgrade mode; `notify` only reports, `off` never checks |
302
302
  | `config log on / off / status` | Toggle and inspect API logging |
303
303
  | `config log level LEVEL` | `DEBUG / INFO / WARNING / ERROR / CRITICAL` |
@@ -244,7 +244,7 @@ discover every subcommand.
244
244
  | `chat messages` | 1-on-1 / group / inbox messages |
245
245
  | `chat files` | Files shared in a chat |
246
246
  | `chat send-to-one` | Send a message, optionally with files (`--file`), to one or more users (1-on-1) |
247
- | `chat send-to-group` | Send a plain-text message to a group chat |
247
+ | `chat send-to-group` | Send a message, optionally with files, to a group chat |
248
248
  | `chat card-detail` | Resolve embedded card references |
249
249
 
250
250
  ### group — chat group lifecycle
@@ -297,7 +297,7 @@ discover every subcommand.
297
297
  |---|---|
298
298
  | `config show` | Display the current configuration summary |
299
299
  | `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` completion |
300
- | `config language [LANG]` | Switch UI language (`en` / `zh-Hans` / `zh-Hant` / `ja`) |
300
+ | `config language [LANG]` | Switch UI language (`en` / `zh-Hans`, or `auto` to follow the account language) |
301
301
  | `config update [auto\|notify\|off]` | Self-upgrade mode; `notify` only reports, `off` never checks |
302
302
  | `config log on / off / status` | Toggle and inspect API logging |
303
303
  | `config log level LEVEL` | `DEBUG / INFO / WARNING / ERROR / CRITICAL` |
@@ -227,6 +227,7 @@ hap --json worksheet record list WORKSHEET_ID
227
227
  | `chat messages` | 单聊 / 群聊 / 收件箱(系统、应用、工作流等) |
228
228
  | `chat files` | 聊天中分享的文件 |
229
229
  | `chat send-to-one` | 向一个或多个用户发消息(单聊),可带附件 `--file`(文件作为聊天附件送达,不是链接) |
230
+ | `chat send-to-group` | 向群聊发消息,可带附件 `--file`,支持 `--at` / `--at-all` 提醒 |
230
231
  | `chat group-info` | 群组信息 |
231
232
  | `chat card-detail` | 解析消息中嵌入的卡片引用 |
232
233
 
@@ -271,7 +272,7 @@ hap --json worksheet record list WORKSHEET_ID
271
272
  |---|---|
272
273
  | `config show` | 显示 CLI 配置摘要 |
273
274
  | `config completion [bash\|zsh\|fish] [--install]` | 启用 `<Tab>` 补全 |
274
- | `config language [LANG]` | 切换显示语言(`en` / `zh-Hans` / `zh-Hant` / `ja`) |
275
+ | `config language [LANG]` | 切换显示语言(`en` / `zh-Hans`;`auto` 跟随登录账号的语言) |
275
276
  | `config update [auto\|notify\|off]` | 自动升级开关;`notify` 只提示不安装,`off` 不检查 |
276
277
  | `config log on / off / status` | 日志开关与状态 |
277
278
  | `config log level LEVEL` | 设置日志级别(`DEBUG/INFO/WARNING/ERROR/CRITICAL`) |
@@ -1,3 +1,3 @@
1
1
  """CLI harness for Mingdao HAP (hap-cli)."""
2
2
 
3
- __version__ = "0.8.24"
3
+ __version__ = "0.8.26"
@@ -123,7 +123,8 @@ def _resolve_app_id_for_filter(session, worksheet_id: str, extra: dict) -> str:
123
123
  return (data or {}).get("appId", "") if isinstance(data, dict) else ""
124
124
 
125
125
 
126
- @chart.command("create")
126
+ @chart.command("create", help="Create a new chart on a worksheet. "
127
+ "Chart spec shapes are in `hap guide chart`.")
127
128
  @click.argument("worksheet_id")
128
129
  @click.option("--name", required=True, help="Chart display name.")
129
130
  @click.option("--report-type", "report_type", type=int, required=True,
@@ -163,7 +164,9 @@ def chart_create(ctx, worksheet_id, name, report_type, app_type, app_id,
163
164
  ctx.handle_error(e)
164
165
 
165
166
 
166
- @chart.command("update")
167
+ @chart.command("update", help="Update an existing chart's configuration. "
168
+ "Read the config with `chart get`, edit it, submit "
169
+ "it back — see `hap guide chart`.")
167
170
  @click.argument("report_id")
168
171
  @click.option("--app-id", "-a", required=True, help="Worksheet ID the chart attaches to.")
169
172
  @click.option("--name", required=True, help="Chart display name.")
@@ -320,7 +320,13 @@ def chat_send_to_one(ctx, to, message, files, dry_run):
320
320
 
321
321
  @chat.command("send-to-group")
322
322
  @click.option("--group-id", "-g", required=True, help="Target group ID")
323
- @click.option("--message", "-m", required=True, help="Message content")
323
+ @click.option("--message", "-m", default="", help="Message text")
324
+ @click.option(
325
+ "--file", "-f", "files", multiple=True,
326
+ help="Attach a file: a local path or an http(s) address. Repeat for "
327
+ "several. The file arrives as an attachment members can open at "
328
+ "any time, not as a link that stops working.",
329
+ )
324
330
  @click.option(
325
331
  "--at", "at_account_ids", multiple=True,
326
332
  help="Account ID to @-mention (can repeat). Sends a separate "
@@ -331,19 +337,61 @@ def chat_send_to_one(ctx, to, message, files, dry_run):
331
337
  "--at-all", is_flag=True, default=False,
332
338
  help="@-mention everyone in the group.",
333
339
  )
340
+ @click.option(
341
+ "--dry-run", is_flag=True, default=False,
342
+ help="Show what would be sent, without uploading or sending anything.",
343
+ )
334
344
  @pass_context
335
- def chat_send_to_group(ctx, group_id, message, at_account_ids, at_all):
336
- """Send a plain-text message to a group chat."""
345
+ def chat_send_to_group(ctx, group_id, message, files, at_account_ids, at_all, dry_run):
346
+ """Send a message, optionally with files, to a group chat.
347
+
348
+ \b
349
+ hap chat send-to-group -g <groupId> -m "spec attached" -f ./spec.pdf
350
+
351
+ Text and each file arrive as separate messages, in that order. An
352
+ @-mention applies to the text line, so it needs --message.
353
+ """
354
+ if not message and not files:
355
+ ctx.handle_error(click.UsageError(
356
+ "Provide --message, --file, or both."
357
+ ))
358
+ return
359
+ sources = upload_mod.source_descriptors(list(files))
360
+ at_ids = list(at_account_ids) if at_account_ids else None
361
+ if dry_run:
362
+ ctx.output(
363
+ {"group_id": group_id, "message": message, "files": list(files),
364
+ "at": at_ids or [], "at_all": at_all, "sent": False},
365
+ lambda d: click.echo(
366
+ "Dry run — nothing sent."
367
+ + f"\n Group: {group_id}"
368
+ + (f"\n Message: {message}" if message else "")
369
+ + "".join(f"\n File: {f}" for f in files)
370
+ + (f"\n @: {', '.join(at_ids)}" if at_ids else "")
371
+ + ("\n @: everyone" if at_all else "")
372
+ ),
373
+ )
374
+ return
337
375
  try:
338
376
  session = ctx.get_session()
339
- data = chat_mod.send_group_message(
340
- session,
341
- group_id,
342
- message,
343
- at_account_ids=list(at_account_ids) if at_account_ids else None,
344
- at_all=at_all,
377
+ if not sources:
378
+ data = chat_mod.send_group_message(
379
+ session, group_id, message,
380
+ at_account_ids=at_ids, at_all=at_all,
381
+ )
382
+ ctx.output(data, lambda d: click.echo("Message sent."))
383
+ return
384
+ descriptors = chat_mod.upload_chat_files(session, sources)
385
+ acks = chat_mod.send_group_files(
386
+ session, group_id, descriptors, message,
387
+ at_account_ids=at_ids, at_all=at_all,
388
+ )
389
+ ctx.output(
390
+ {"sent": len(acks), "files": len(descriptors), "acks": acks},
391
+ lambda d: click.echo(
392
+ f"Sent {len(descriptors)} file(s) to the group."
393
+ ),
345
394
  )
346
- ctx.output(data, lambda d: click.echo("Message sent."))
347
395
  except Exception as e:
348
396
  ctx.handle_error(e)
349
397
 
@@ -2,12 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import logging
5
6
  import os
6
7
  import re
7
8
  import time
8
9
  from pathlib import Path
9
10
 
10
11
  import click
12
+ from typing import Optional
11
13
 
12
14
  from hap_cli.context import pass_context
13
15
  from hap_cli.core.logger import (
@@ -20,6 +22,8 @@ from hap_cli.core.logger import (
20
22
  )
21
23
  from hap_cli.i18n import t
22
24
 
25
+ logger = logging.getLogger("hap_cli.config")
26
+
23
27
 
24
28
  # ── Shell completion helpers (moved from hap_cli.hap_cli) ────────────────
25
29
 
@@ -252,46 +256,119 @@ def config_update(ctx, mode):
252
256
  # ── `hap config language` ─────────────────────────────────────────────────
253
257
 
254
258
 
259
+ # Languages a user can pin here. The catalog and the resolution logic
260
+ # (HAP_LANG, config.json, login auto-detect) still understand every code in
261
+ # ``i18n.LANG_OPTIONS`` — zh-Hant / ja keep working and keep falling back
262
+ # to English where untranslated — they are only hidden from this picker.
263
+ _SELECTABLE_LANGS = ("en", "zh-Hans")
264
+ # Picker entry 0 / argument value "auto": drop the manual pin and follow
265
+ # the language of the HAP account (re-read now, and again at every login).
266
+ _AUTO = "auto"
267
+
268
+
269
+ def _detect_account_language(ctx) -> Optional[str]:
270
+ """Read the HAP account language of the active profile, or ``None``.
271
+
272
+ Uses the same lookup as ``hap auth whoami`` (``Global/GetGlobalMeta`` →
273
+ ``Account.lang``). Any failure — not logged in, expired token, network —
274
+ yields ``None`` so the caller can still switch to auto mode and let the
275
+ next login fill the language in.
276
+ """
277
+ from hap_cli.core import auth as auth_mod
278
+ from hap_cli.core.session import (
279
+ _normalize_login_url, load_profile, resolve_profile_name,
280
+ )
281
+ from hap_cli.i18n import lang_from_hap_account
282
+
283
+ try:
284
+ session = ctx.get_session(require_auth=False)
285
+ if not session.auth_token:
286
+ return None
287
+ prof = load_profile(resolve_profile_name(ctx.profile))
288
+ login_url = _normalize_login_url(
289
+ prof.get("login_url") or prof.get("server_url", "")
290
+ ) or "https://www.mingdao.com"
291
+ info = auth_mod.get_user_info(login_url, session.auth_token)
292
+ return lang_from_hap_account(str(info.get("lang") or ""))
293
+ except Exception as exc: # noqa: BLE001 — auto mode must not fail on lookup
294
+ logger.warning("Could not read the account language: %s", exc)
295
+ return None
296
+
297
+
255
298
  @config.command("language")
256
299
  @click.argument(
257
300
  "lang",
258
301
  required=False,
259
- type=click.Choice(["en", "zh-Hans", "zh-Hant", "ja"]),
302
+ type=click.Choice([_AUTO, *_SELECTABLE_LANGS]),
260
303
  )
261
304
  @pass_context
262
305
  def config_language(ctx, lang):
263
306
  """Switch the CLI display language.
264
307
 
265
- Without an argument, shows an interactive picker. Pass one of
266
- ``en`` / ``zh-Hans`` / ``zh-Hant`` / ``ja`` to set non-interactively.
308
+ Without an argument, shows an interactive picker. Pass ``en`` or
309
+ ``zh-Hans`` to pin a language, or ``auto`` (``0`` in the picker) to
310
+ follow the language of the HAP account you log in with.
267
311
 
268
- The chosen language is saved as a manual preference and pinned —
269
- subsequent ``hap auth login`` calls won't overwrite it from the
270
- user's HAP account language.
312
+ A pinned language is saved as a manual preference subsequent
313
+ ``hap auth login`` calls won't overwrite it. ``auto`` removes the pin
314
+ and re-reads the language from your current account right away.
271
315
  """
272
316
  from hap_cli.core.session import load_config, save_config
273
- from hap_cli.i18n import LANG_LABELS, LANG_OPTIONS, reset_language_cache
317
+ from hap_cli.i18n import DEFAULT_LANGUAGE, LANG_LABELS, reset_language_cache
274
318
 
275
319
  if lang is None:
276
320
  cfg = load_config() or {}
277
- current = cfg.get("language") or "en"
278
- click.echo("Choose a language / 选择语言 / 選擇語言 / 言語を選択:")
279
- for i, code in enumerate(LANG_OPTIONS, start=1):
280
- mark = " (current)" if code == current else ""
321
+ current = cfg.get("language") or DEFAULT_LANGUAGE
322
+ is_auto = cfg.get("language_source") != "manual"
323
+ click.echo("Choose a language / 选择语言:")
324
+ mark = " (current)" if is_auto else ""
325
+ click.echo(f" 0. Auto — follow your HAP account language [auto]{mark}")
326
+ for i, code in enumerate(_SELECTABLE_LANGS, start=1):
327
+ mark = " (current)" if (not is_auto and code == current) else ""
281
328
  click.echo(f" {i}. {LANG_LABELS[code]} [{code}]{mark}")
329
+ if is_auto or current not in _SELECTABLE_LANGS:
330
+ default_index = 0
331
+ else:
332
+ default_index = _SELECTABLE_LANGS.index(current) + 1
282
333
  choice = click.prompt(
283
- "Enter 1-4",
284
- type=click.IntRange(1, len(LANG_OPTIONS)),
285
- default=LANG_OPTIONS.index(current) + 1,
334
+ f"Enter 0-{len(_SELECTABLE_LANGS)}",
335
+ type=click.IntRange(0, len(_SELECTABLE_LANGS)),
336
+ default=default_index,
286
337
  )
287
- lang = LANG_OPTIONS[choice - 1]
338
+ lang = _AUTO if choice == 0 else _SELECTABLE_LANGS[choice - 1]
339
+
340
+ if lang == _AUTO:
341
+ detected = _detect_account_language(ctx)
342
+ save_config({
343
+ "language": detected or DEFAULT_LANGUAGE,
344
+ "language_source": "auto",
345
+ })
346
+ reset_language_cache()
347
+ if ctx.json_mode:
348
+ from hap_cli.utils.formatting import output_json
349
+ output_json({
350
+ "status": "ok", "mode": "auto",
351
+ "language": detected or DEFAULT_LANGUAGE,
352
+ "detected": bool(detected),
353
+ })
354
+ elif detected:
355
+ click.echo(
356
+ "Language set to: Auto — following your HAP account language "
357
+ f"({LANG_LABELS[detected]} [{detected}])"
358
+ )
359
+ else:
360
+ click.echo(
361
+ "Language set to: Auto — it will follow your HAP account "
362
+ "language after the next login."
363
+ )
364
+ return
288
365
 
289
366
  save_config({"language": lang, "language_source": "manual"})
290
367
  reset_language_cache()
291
368
  label = LANG_LABELS[lang]
292
369
  if ctx.json_mode:
293
370
  from hap_cli.utils.formatting import output_json
294
- output_json({"status": "ok", "language": lang, "label": label})
371
+ output_json({"status": "ok", "mode": "manual", "language": lang, "label": label})
295
372
  else:
296
373
  click.echo(f"Language set to: {label} [{lang}]")
297
374
 
@@ -182,11 +182,17 @@ def instance_submit(ctx, instance_id, work_id, opinion):
182
182
 
183
183
  @instance.command("revoke")
184
184
  @click.argument("instance_id")
185
- @click.option("--work-id", "-w", default="", help="Work item ID")
185
+ @click.option("--work-id", "-w", default="",
186
+ help="Work item ID of the initiating step. Looked up from the "
187
+ "flow automatically when omitted.")
186
188
  @click.option("--opinion", "-o", default="", help="Revocation reason")
187
189
  @pass_context
188
190
  def instance_revoke(ctx, instance_id, work_id, opinion):
189
- """Revoke/withdraw a submitted instance."""
191
+ """Revoke/withdraw a submitted instance.
192
+
193
+ Only the initiator can withdraw a flow, and only while it is still
194
+ running.
195
+ """
190
196
  try:
191
197
  session = ctx.get_session()
192
198
  data = inst_mod.revoke(session, instance_id, work_id, opinion)
@@ -240,13 +246,18 @@ def instance_retry(ctx, instance_id):
240
246
 
241
247
  @instance.command("restart")
242
248
  @click.argument("instance_id")
243
- @click.option("--work-id", "-w", default="", help="Work item ID")
249
+ @click.option("--work-id", "-w", default="",
250
+ help="Work item ID of the pending initiating step created by "
251
+ "the revoke. Looked up from the flow automatically when "
252
+ "omitted.")
244
253
  @pass_context
245
254
  def instance_restart(ctx, instance_id, work_id):
246
255
  """Re-initiate a flow the initiator has revoked.
247
256
 
248
257
  Use this after the initiator revoked (withdrew) a still-running flow to
249
- start it again. It does not apply to flows that already finished.
258
+ start it again. It requires the approval flow to allow re-initiating
259
+ after a revoke; the initiator then gets a new fill task to submit. It
260
+ does not apply to flows that already finished.
250
261
  """
251
262
  try:
252
263
  session = ctx.get_session()
@@ -374,16 +374,28 @@ def record_discussions(ctx, worksheet_id, row_id, page_size, page):
374
374
  @click.option("--app-id", default="", help="Application ID (auto-detected from the worksheet if omitted)")
375
375
  @click.option("--view-id", default="", help="View ID the discussion originates from")
376
376
  @click.option("--reply-id", default="", help="ID of the discussion being replied to")
377
+ @click.option("--file", "-f", "files", multiple=True,
378
+ help="Attach a file to the comment (repeatable). Accepts a local "
379
+ "path or an http(s) URL.")
377
380
  @pass_context
378
- def record_add_discussion(ctx, worksheet_id, row_id, message, app_id, view_id, reply_id):
381
+ def record_add_discussion(ctx, worksheet_id, row_id, message, app_id, view_id, reply_id, files):
379
382
  """Add a discussion/comment to a record."""
380
383
  try:
381
384
  session = ctx.get_session()
385
+ attachments = None
386
+ if files:
387
+ from hap_cli.core import upload as up_mod
388
+ descriptors = up_mod.source_descriptors(list(files))
389
+ attachments = up_mod.upload_files(
390
+ session, descriptors,
391
+ worksheet_id=worksheet_id, app_id=app_id,
392
+ )
382
393
  data = rec_mod.add_discussion(
383
394
  session, worksheet_id, row_id, message,
384
395
  app_id=app_id, view_id=view_id, reply_id=reply_id,
396
+ attachments=attachments,
385
397
  )
386
- ctx.output(data, lambda d: click.echo(f"Discussion added."))
398
+ ctx.output(data, lambda d: click.echo("Discussion added."))
387
399
  except Exception as e:
388
400
  ctx.handle_error(e)
389
401
 
@@ -44,6 +44,30 @@ from hap_cli.core.session import Session
44
44
 
45
45
  logger = logging.getLogger("hap_cli.chart")
46
46
 
47
+ # A chart that is saved without a ``filter`` block saves successfully but
48
+ # never renders: the statistics backend cannot scope the query and
49
+ # ``report/getData`` answers status -1/0, which the UI paints as
50
+ # "无法形成图表 / 构成要素不存在或已删除". The chart looks fine in the editor
51
+ # (which supplies its own default scope) and only breaks after save — the
52
+ # single most common way a hand-written ``chart create`` spec silently
53
+ # produces a blank chart. The frontend's ``getNewReport`` always injects a
54
+ # default time filter for a new worksheet chart; we mirror that here, but
55
+ # default to ``rangeType: 0`` (全部时间 / all-time) instead of the UI's
56
+ # "last 365 days" so the chart is never blank merely because the default
57
+ # window excluded older data. Callers that supply their own ``filter``
58
+ # (any non-empty dict) keep it untouched.
59
+ _DEFAULT_CHART_FILTER: dict[str, Any] = {
60
+ "filterRangeId": "ctime",
61
+ "filterRangeName": "创建时间",
62
+ "rangeType": 0,
63
+ "rangeValue": None,
64
+ "today": False,
65
+ "dynamicFilter": {
66
+ "startType": 1, "startCount": 1, "startUnit": 1,
67
+ "endType": 1, "endCount": 1, "endUnit": 1,
68
+ },
69
+ }
70
+
47
71
 
48
72
  def list_charts(
49
73
  session: Session,
@@ -218,6 +242,12 @@ def save_chart(
218
242
  split_control_id = body["split"].get("controlId")
219
243
  if split_control_id:
220
244
  body["splitId"] = split_control_id
245
+ # A chart with no filter block saves but never renders (blank after
246
+ # save — see _DEFAULT_CHART_FILTER). Inject an all-time default when the
247
+ # caller omitted it; a caller-supplied filter (any non-empty value) is
248
+ # left untouched so an explicit scope always wins.
249
+ if not body.get("filter"):
250
+ body["filter"] = dict(_DEFAULT_CHART_FILTER)
221
251
  # Resolve the chart's filter block before sending saveReportConfig.
222
252
  if isinstance(body.get("filter"), dict):
223
253
  body["filter"] = _ensure_filter_id(
@@ -438,6 +438,79 @@ def send_group_message(
438
438
  return ack_data
439
439
 
440
440
 
441
+ def send_group_files(
442
+ session: Session,
443
+ group_id: str,
444
+ descriptors: list[dict[str, Any]],
445
+ message: str = "",
446
+ at_account_ids: Optional[list[str]] = None,
447
+ at_all: bool = False,
448
+ ) -> list[Any]:
449
+ """Send already-uploaded files (and an optional line of text) to a group.
450
+
451
+ Group file messages ride the same socket.io event as group text —
452
+ ``'send group message'`` (``src/pages/chat/utils/socket.js``
453
+ ``Message.send`` GROUP branch) — with the same per-message shape the
454
+ 1-on-1 path uses (SendToolbar ``FileUploaded`` → ``onSendFileMsg({file,
455
+ type}, msg)`` → ``ChatPanelSession.sendMessage``), the only difference
456
+ being ``togroup`` instead of ``touser`` (ChatPanelSession/index.js:355).
457
+ Order mirrors the chat panel: the typed text first, then one message
458
+ per file, all over one connection.
459
+
460
+ An @-mention (``at_account_ids`` / ``at_all``) only makes sense on the
461
+ text line, so the ``'group shake'`` follow-up carries the text
462
+ message's ack id and is skipped when there is no text — same
463
+ best-effort contract as :func:`send_group_message`.
464
+
465
+ Returns the acks in send order (unwrapped from the ``(err, data)``
466
+ tuple python-socketio surfaces). Emitting is strict — a failure to
467
+ deliver raises rather than reporting a phantom success.
468
+ """
469
+ import uuid
470
+
471
+ if not group_id:
472
+ raise ValueError("group_id is required")
473
+ if not descriptors and not message:
474
+ raise ValueError("nothing to send: provide files, a message, or both")
475
+
476
+ emits: list[tuple[str, dict[str, Any]]] = []
477
+ if message:
478
+ emits.append(("send group message", {
479
+ "type": MSGTYPE_TEXT,
480
+ "msg": message,
481
+ "togroup": group_id,
482
+ "waitingid": str(uuid.uuid4()),
483
+ }))
484
+ for descriptor in descriptors:
485
+ msg_type, file_obj = _chat_file_message(descriptor)
486
+ emits.append(("send group message", {
487
+ "type": msg_type,
488
+ "msg": _chat_file_label(msg_type, file_obj["name"]),
489
+ "file": file_obj,
490
+ "togroup": group_id,
491
+ "waitingid": str(uuid.uuid4()),
492
+ }))
493
+ raw_acks = _socket_emit_batch(session, emits, strict=True)
494
+ acks = [
495
+ ack[1] if isinstance(ack, tuple) and len(ack) >= 2 else ack
496
+ for ack in raw_acks
497
+ ]
498
+
499
+ to_user: Any = None
500
+ if at_all:
501
+ to_user = "all"
502
+ elif at_account_ids:
503
+ to_user = list(at_account_ids)
504
+ if to_user and message:
505
+ text_ack = acks[0]
506
+ message_id = text_ack.get("id") if isinstance(text_ack, dict) else None
507
+ shake_payload: dict[str, Any] = {"gid": group_id, "toUser": to_user}
508
+ if message_id:
509
+ shake_payload["messageId"] = message_id
510
+ _socket_emit(session, "group shake", shake_payload, strict=False)
511
+ return acks
512
+
513
+
441
514
  def get_group_info(
442
515
  session: Session,
443
516
  group_id: str,