hap-cli 0.9.6__tar.gz → 0.9.8__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 (417) hide show
  1. {hap_cli-0.9.6 → hap_cli-0.9.8}/PKG-INFO +2 -2
  2. {hap_cli-0.9.6/hap_cli → hap_cli-0.9.8}/README.md +1 -1
  3. {hap_cli-0.9.6 → hap_cli-0.9.8/hap_cli}/README.md +1 -1
  4. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/README_CN.md +1 -1
  5. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/__init__.py +1 -1
  6. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/group_cmd.py +54 -20
  7. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/post_cmd.py +69 -16
  8. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/group.py +43 -0
  9. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/post.py +85 -6
  10. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/upload.py +33 -3
  11. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/en/post.md +20 -5
  12. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/index.json +2 -2
  13. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/zh-Hans/post.md +16 -4
  14. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/locale/messages.json +36 -4
  15. hap_cli-0.9.8/hap_cli/tests/test_post_group_friction.py +205 -0
  16. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli.egg-info/PKG-INFO +2 -2
  17. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli.egg-info/SOURCES.txt +1 -0
  18. {hap_cli-0.9.6 → hap_cli-0.9.8}/MANIFEST.in +0 -0
  19. {hap_cli-0.9.6 → hap_cli-0.9.8}/app_live_tests/__init__.py +0 -0
  20. {hap_cli-0.9.6 → hap_cli-0.9.8}/app_live_tests/__main__.py +0 -0
  21. {hap_cli-0.9.6 → hap_cli-0.9.8}/app_live_tests/config.py +0 -0
  22. {hap_cli-0.9.6 → hap_cli-0.9.8}/app_live_tests/fixtures/__init__.py +0 -0
  23. {hap_cli-0.9.6 → hap_cli-0.9.8}/app_live_tests/fixtures/feedback_scenario.py +0 -0
  24. {hap_cli-0.9.6 → hap_cli-0.9.8}/app_live_tests/harness.py +0 -0
  25. {hap_cli-0.9.6 → hap_cli-0.9.8}/app_live_tests/smoke.py +0 -0
  26. {hap_cli-0.9.6 → hap_cli-0.9.8}/app_live_tests/state.py +0 -0
  27. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/__main__.py +0 -0
  28. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/_entry.py +0 -0
  29. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/__init__.py +0 -0
  30. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/app_cmd.py +0 -0
  31. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/app_creator_cmd.py +0 -0
  32. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/app_editor_cmd.py +0 -0
  33. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/attachment_cmd.py +0 -0
  34. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/auth_cmd.py +0 -0
  35. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/calendar_cmd.py +0 -0
  36. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/chart_cmd.py +0 -0
  37. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/chat_cmd.py +0 -0
  38. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/chatbot_cmd.py +0 -0
  39. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/config_cmd.py +0 -0
  40. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/contact_cmd.py +0 -0
  41. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/department_cmd.py +0 -0
  42. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/guide_cmd.py +0 -0
  43. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/icon_cmd.py +0 -0
  44. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/instance_cmd.py +0 -0
  45. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/node_cmd.py +0 -0
  46. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/optionset_cmd.py +0 -0
  47. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/page_cmd.py +0 -0
  48. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/record_cmd.py +0 -0
  49. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/region_cmd.py +0 -0
  50. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/role_cmd.py +0 -0
  51. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/update_cmd.py +0 -0
  52. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/upload_cmd.py +0 -0
  53. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/v3_registry.py +0 -0
  54. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/workflow_cmd.py +0 -0
  55. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/commands/worksheet_cmd.py +0 -0
  56. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/context.py +0 -0
  57. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/__init__.py +0 -0
  58. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/action_spec_adapter.py +0 -0
  59. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app.py +0 -0
  60. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/__init__.py +0 -0
  61. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
  62. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
  63. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
  64. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/charts.py +0 -0
  65. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/cleanup.py +0 -0
  66. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/compiler.py +0 -0
  67. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/config.py +0 -0
  68. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/design/design.schema.json +0 -0
  69. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/errors.py +0 -0
  70. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
  71. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/executor.py +0 -0
  72. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/fields.py +0 -0
  73. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/hap.py +0 -0
  74. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/merge.py +0 -0
  75. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/recording/__init__.py +0 -0
  76. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/recording/console.py +0 -0
  77. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
  78. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/recording/mirror.py +0 -0
  79. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/recording/report.py +0 -0
  80. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/schema.py +0 -0
  81. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
  82. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/seed/__init__.py +0 -0
  83. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/seed/cli.py +0 -0
  84. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/seed/executor.py +0 -0
  85. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
  86. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
  87. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
  88. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
  89. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/seed/template.py +0 -0
  90. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/selftest.py +0 -0
  91. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/smoke.py +0 -0
  92. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/steps.py +0 -0
  93. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/store.py +0 -0
  94. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/validate.py +0 -0
  95. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
  96. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/attachment.py +0 -0
  97. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/auth.py +0 -0
  98. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/calendar_mod.py +0 -0
  99. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/chart.py +0 -0
  100. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/chat.py +0 -0
  101. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/chatbot.py +0 -0
  102. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/cli_access.py +0 -0
  103. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/condition_dsl.py +0 -0
  104. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/contact.py +0 -0
  105. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/control_type_codes.py +0 -0
  106. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/department.py +0 -0
  107. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/__init__.py +0 -0
  108. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  109. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  110. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/apply.py +0 -0
  111. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/componentlower.py +0 -0
  112. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/editspec/component.schema.json +0 -0
  113. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
  114. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
  115. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/editspec/field.schema.json +0 -0
  116. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/editspec.py +0 -0
  117. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/errors.py +0 -0
  118. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/fieldlower.py +0 -0
  119. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  120. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/models.py +0 -0
  121. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/ops.py +0 -0
  122. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/planner.py +0 -0
  123. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/editor/reader.py +0 -0
  124. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/field_normalizer.py +0 -0
  125. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/field_spec_adapter.py +0 -0
  126. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/filter_translator.py +0 -0
  127. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/filter_v3.py +0 -0
  128. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/flow_node.py +0 -0
  129. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/global_meta.py +0 -0
  130. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/guide.py +0 -0
  131. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/icon_index.py +0 -0
  132. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/instance.py +0 -0
  133. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/layout_check.py +0 -0
  134. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/logger.py +0 -0
  135. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/optionset.py +0 -0
  136. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/page.py +0 -0
  137. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/record.py +0 -0
  138. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/record_defaults.py +0 -0
  139. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/record_form_checks.py +0 -0
  140. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/record_lookups.py +0 -0
  141. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/record_rules.py +0 -0
  142. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/relation_scope.py +0 -0
  143. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/relogin.py +0 -0
  144. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/response_crypto.py +0 -0
  145. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/role.py +0 -0
  146. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/role_perm_builder.py +0 -0
  147. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/rule_spec_adapter.py +0 -0
  148. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/session.py +0 -0
  149. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/token_crypto.py +0 -0
  150. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/update.py +0 -0
  151. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/v3/__init__.py +0 -0
  152. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/v3/dispatcher.py +0 -0
  153. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/v3/render.py +0 -0
  154. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/v3/schema.py +0 -0
  155. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/view_spec_adapter.py +0 -0
  156. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/workflow.py +0 -0
  157. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/workflow_node_dsl.py +0 -0
  158. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/worksheet.py +0 -0
  159. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/worksheet_form.py +0 -0
  160. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/worksheet_reference.py +0 -0
  161. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/core/worksheet_templates.py +0 -0
  162. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/en/approval.md +0 -0
  163. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/en/chart.md +0 -0
  164. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/en/messages.md +0 -0
  165. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/en/overview.md +0 -0
  166. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/en/record.md +0 -0
  167. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/en/setup.md +0 -0
  168. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/en/skills.md +0 -0
  169. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/en/workflow.md +0 -0
  170. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/en/worksheet.md +0 -0
  171. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/zh-Hans/approval.md +0 -0
  172. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/zh-Hans/chart.md +0 -0
  173. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/zh-Hans/messages.md +0 -0
  174. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/zh-Hans/overview.md +0 -0
  175. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/zh-Hans/record.md +0 -0
  176. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/zh-Hans/setup.md +0 -0
  177. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/zh-Hans/skills.md +0 -0
  178. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/zh-Hans/workflow.md +0 -0
  179. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/guide/zh-Hans/worksheet.md +0 -0
  180. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/i18n.py +0 -0
  181. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/locale/__init__.py +0 -0
  182. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/locale/messages.schema.json +0 -0
  183. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/__init__.py +0 -0
  184. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/conftest.py +0 -0
  185. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_action_mdpublic_parity.py +0 -0
  186. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_advanced_setting_guard.py +0 -0
  187. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_app_creator.py +0 -0
  188. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_app_editor.py +0 -0
  189. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_app_export_usage.py +0 -0
  190. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_app_light_color.py +0 -0
  191. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_app_section_name.py +0 -0
  192. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_approval_submit_fill_cli.py +0 -0
  193. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_approval_wire.py +0 -0
  194. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_atomic_write.py +0 -0
  195. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_attachment_share.py +0 -0
  196. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_auth_prerelease.py +0 -0
  197. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_batch_add_atomic.py +0 -0
  198. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_bug_036430.py +0 -0
  199. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_bug_036750.py +0 -0
  200. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_calendar_date_range.py +0 -0
  201. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_calendar_discussion_cli.py +0 -0
  202. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_chart.py +0 -0
  203. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_chart_filter_merge.py +0 -0
  204. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_chat_attachment_send.py +0 -0
  205. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_chat_messages_cli.py +0 -0
  206. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_chat_recall_cli.py +0 -0
  207. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_chat_send_socket.py +0 -0
  208. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_cli_access.py +0 -0
  209. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_coded_responses.py +0 -0
  210. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_condition_dsl.py +0 -0
  211. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_condition_dsl_entries.py +0 -0
  212. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_condition_dsl_readback.py +0 -0
  213. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_config_cmd.py +0 -0
  214. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_content_language.py +0 -0
  215. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_control_lookup_guard.py +0 -0
  216. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_core.py +0 -0
  217. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_create_worksheet_partial.py +0 -0
  218. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
  219. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_dsl_existing_node_refs.py +0 -0
  220. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_dsl_multi_record_guard.py +0 -0
  221. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_editor_inline_subtable.py +0 -0
  222. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_engine_core_parity.py +0 -0
  223. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_arg_consistency.py +0 -0
  224. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_bidirectional.py +0 -0
  225. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_discussion_mentions.py +0 -0
  226. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_entry_module.py +0 -0
  227. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_field_record_ref.py +0 -0
  228. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_http_error_body.py +0 -0
  229. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_json_anywhere.py +0 -0
  230. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_json_file_arg.py +0 -0
  231. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_layout_check.py +0 -0
  232. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_node_get_detail.py +0 -0
  233. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_node_summary.py +0 -0
  234. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_post_cursor.py +0 -0
  235. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_publish_reasons.py +0 -0
  236. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_replicate_workflow.py +0 -0
  237. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_save_fields_shape.py +0 -0
  238. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_subgroups.py +0 -0
  239. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_subtable_read.py +0 -0
  240. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_worksheet_references.py +0 -0
  241. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_worksheet_visibility.py +0 -0
  242. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fb_worksheet_workflows.py +0 -0
  243. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_fields_mdpublic_parity.py +0 -0
  244. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_full_e2e.py +0 -0
  245. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g1_chart_parity.py +0 -0
  246. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g1_record_parity.py +0 -0
  247. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g1_upload_parity.py +0 -0
  248. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g1_worksheet_parity.py +0 -0
  249. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g2_coverage_parity.py +0 -0
  250. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g3_workflow_parity.py +0 -0
  251. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g4_approval_parity.py +0 -0
  252. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g5_low_frequency_parity.py +0 -0
  253. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g6_advanced_fields.py +0 -0
  254. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g6_fields_roundtrip.py +0 -0
  255. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g7_misc_gaps.py +0 -0
  256. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g7b_permanent_delete.py +0 -0
  257. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g8_filter_operator.py +0 -0
  258. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g8b_pivot_operators.py +0 -0
  259. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g8c_empty_filter_and_help_truth.py +0 -0
  260. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_g9_appid_resolve.py +0 -0
  261. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_global_meta.py +0 -0
  262. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_guide.py +0 -0
  263. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_guide_sections.py +0 -0
  264. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_i18n.py +0 -0
  265. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_inner_flow_batch_add.py +0 -0
  266. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_instance_envelope.py +0 -0
  267. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_instance_history_cli.py +0 -0
  268. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_integration.py +0 -0
  269. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_integration_approval.py +0 -0
  270. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_integration_approval_actions.py +0 -0
  271. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_integration_calendar.py +0 -0
  272. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_integration_destructive.py +0 -0
  273. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_integration_misc.py +0 -0
  274. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_integration_post.py +0 -0
  275. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_integration_social.py +0 -0
  276. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_integration_v3.py +0 -0
  277. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_integration_workflow.py +0 -0
  278. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  279. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_jsonschema_mini_copy.py +0 -0
  280. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
  281. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_multi_profile.py +0 -0
  282. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
  283. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_noninteractive_confirm.py +0 -0
  284. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
  285. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_org_id_cli.py +0 -0
  286. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_org_id_docs.py +0 -0
  287. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_page_filters_group_read.py +0 -0
  288. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_page_filters_group_update.py +0 -0
  289. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_page_save_page_id.py +0 -0
  290. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_parameter_conventions.py +0 -0
  291. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  292. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_post_attachments.py +0 -0
  293. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_post_scope_cli.py +0 -0
  294. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
  295. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_record_business_rules.py +0 -0
  296. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_record_defaults.py +0 -0
  297. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_record_doc_id_hidden.py +0 -0
  298. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_record_field_alias.py +0 -0
  299. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_record_form_checks.py +0 -0
  300. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_record_run_action.py +0 -0
  301. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
  302. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_record_write_fixes.py +0 -0
  303. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_relation_scope.py +0 -0
  304. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_relogin.py +0 -0
  305. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_rmw_mdpublic_parity.py +0 -0
  306. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_role_app_scope.py +0 -0
  307. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_rule_filters_and_field_warning.py +0 -0
  308. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_save_action_silent_drop.py +0 -0
  309. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_save_rule_spec.py +0 -0
  310. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_select_add_option.py +0 -0
  311. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
  312. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_semantic_types_registry.py +0 -0
  313. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_subtable_mdpublic_parity.py +0 -0
  314. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_update.py +0 -0
  315. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_update_mode.py +0 -0
  316. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_upload_allow_down.py +0 -0
  317. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_upload_host_resolution.py +0 -0
  318. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_upload_mdpublic_parity.py +0 -0
  319. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  320. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_v3_bool_flags.py +0 -0
  321. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  322. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  323. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_v3_response_format_json.py +0 -0
  324. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_v3_session.py +0 -0
  325. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  326. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_view_mdpublic_parity.py +0 -0
  327. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_wf_config_keys.py +0 -0
  328. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_wf_inner_scope.py +0 -0
  329. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_wf_json_file_arg.py +0 -0
  330. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_wf_node_delete_missing.py +0 -0
  331. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_wf_save_action_dsl_fields.py +0 -0
  332. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_wf_structure_listing.py +0 -0
  333. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_wf_trigger_outcome.py +0 -0
  334. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_wf_trigger_preserve.py +0 -0
  335. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_wo4_result_handling.py +0 -0
  336. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_workflow_code_node.py +0 -0
  337. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_workflow_condition_field_types.py +0 -0
  338. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_workflow_condition_slots.py +0 -0
  339. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_workflow_delete_cascade.py +0 -0
  340. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
  341. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_workflow_lifecycle_wire.py +0 -0
  342. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_workflow_list_cli.py +0 -0
  343. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_workflow_node_wire.py +0 -0
  344. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_workflow_publish_inner.py +0 -0
  345. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
  346. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
  347. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_worksheet_create_section.py +0 -0
  348. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  349. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_worksheet_filter_read.py +0 -0
  350. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_worksheet_switch.py +0 -0
  351. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
  352. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/utils/__init__.py +0 -0
  353. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/utils/atomic_write.py +0 -0
  354. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/utils/confirm.py +0 -0
  355. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/utils/formatting.py +0 -0
  356. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/utils/options.py +0 -0
  357. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli/utils/parameter_mapping.py +0 -0
  358. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli.egg-info/dependency_links.txt +0 -0
  359. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli.egg-info/entry_points.txt +0 -0
  360. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli.egg-info/requires.txt +0 -0
  361. {hap_cli-0.9.6 → hap_cli-0.9.8}/hap_cli.egg-info/top_level.txt +0 -0
  362. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/__init__.py +0 -0
  363. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/__main__.py +0 -0
  364. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/_wftest.py +0 -0
  365. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/charts.py +0 -0
  366. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/cleanup.py +0 -0
  367. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/compiler.py +0 -0
  368. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/config.py +0 -0
  369. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/errors.py +0 -0
  370. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/executor.py +0 -0
  371. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/fields.py +0 -0
  372. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/hap.py +0 -0
  373. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/recording/__init__.py +0 -0
  374. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/recording/console.py +0 -0
  375. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/recording/jsonl.py +0 -0
  376. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/recording/mirror.py +0 -0
  377. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/recording/report.py +0 -0
  378. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/schema.py +0 -0
  379. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/seed/__init__.py +0 -0
  380. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/seed/cli.py +0 -0
  381. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/seed/executor.py +0 -0
  382. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/seed/template.py +0 -0
  383. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/selftest.py +0 -0
  384. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/smoke.py +0 -0
  385. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/step.py +0 -0
  386. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/steps.py +0 -0
  387. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/store.py +0 -0
  388. {hap_cli-0.9.6 → hap_cli-0.9.8}/live_tests/workflow_dsl.py +0 -0
  389. {hap_cli-0.9.6 → hap_cli-0.9.8}/setup.cfg +0 -0
  390. {hap_cli-0.9.6 → hap_cli-0.9.8}/setup.py +0 -0
  391. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/INDEX.json +0 -0
  392. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  393. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  394. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/create_optionset.yaml +0 -0
  395. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/create_role.yaml +0 -0
  396. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  397. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/delete_record.yaml +0 -0
  398. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/delete_role.yaml +0 -0
  399. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  400. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_app_info.yaml +0 -0
  401. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  402. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  403. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  404. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_record_details.yaml +0 -0
  405. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  406. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_record_list.yaml +0 -0
  407. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  408. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  409. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  410. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  411. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_regions.yaml +0 -0
  412. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_role_details.yaml +0 -0
  413. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/get_role_list.yaml +0 -0
  414. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  415. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  416. {hap_cli-0.9.6 → hap_cli-0.9.8}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  417. {hap_cli-0.9.6 → hap_cli-0.9.8}/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.9.6
3
+ Version: 0.9.8
4
4
  Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -289,7 +289,7 @@ discover every subcommand.
289
289
 
290
290
  | Command | Description |
291
291
  |---|---|
292
- | `group list` | List open groups you have joined (`--org-id`, `--keywords`, and pagination options) |
292
+ | `group list` | List open groups you have joined (`--org-id`, `--keywords`, pagination; `has_more`/`next_page` when more exist, `--all` for every group, `--feed-only` for groups that can receive a post) |
293
293
  | `group info GROUP_ID` | Group metadata |
294
294
  | `group create` | Create a new group (`--name`, `--org-id`, optional `--member`) |
295
295
  | `group add-member` | Add one or more members (`--group-id`, `--account-id`) |
@@ -260,7 +260,7 @@ discover every subcommand.
260
260
 
261
261
  | Command | Description |
262
262
  |---|---|
263
- | `group list` | List open groups you have joined (`--org-id`, `--keywords`, and pagination options) |
263
+ | `group list` | List open groups you have joined (`--org-id`, `--keywords`, pagination; `has_more`/`next_page` when more exist, `--all` for every group, `--feed-only` for groups that can receive a post) |
264
264
  | `group info GROUP_ID` | Group metadata |
265
265
  | `group create` | Create a new group (`--name`, `--org-id`, optional `--member`) |
266
266
  | `group add-member` | Add one or more members (`--group-id`, `--account-id`) |
@@ -260,7 +260,7 @@ discover every subcommand.
260
260
 
261
261
  | Command | Description |
262
262
  |---|---|
263
- | `group list` | List open groups you have joined (`--org-id`, `--keywords`, and pagination options) |
263
+ | `group list` | List open groups you have joined (`--org-id`, `--keywords`, pagination; `has_more`/`next_page` when more exist, `--all` for every group, `--feed-only` for groups that can receive a post) |
264
264
  | `group info GROUP_ID` | Group metadata |
265
265
  | `group create` | Create a new group (`--name`, `--org-id`, optional `--member`) |
266
266
  | `group add-member` | Add one or more members (`--group-id`, `--account-id`) |
@@ -243,7 +243,7 @@ hap --json worksheet record list WORKSHEET_ID
243
243
 
244
244
  | 命令 | 说明 |
245
245
  |---|---|
246
- | `group list` | 分页列出当前用户已加入的正常群组(可用 `--org-id`、`--keywords` 筛选) |
246
+ | `group list` | 分页列出当前用户已加入的正常群组(可用 `--org-id`、`--keywords` 筛选;还有更多时返回 `has_more`/`next_page`;`--all` 取全部;`--feed-only` 只列能发动态的群) |
247
247
 
248
248
  ### region — 行政区划
249
249
 
@@ -3,7 +3,7 @@
3
3
  import sys as _sys
4
4
  import types as _types
5
5
 
6
- __version__ = "0.9.6"
6
+ __version__ = "0.9.8"
7
7
 
8
8
  # The command tree used to live in hap_cli/hap_cli.py. A module named
9
9
  # after the package it sits in shadows that package: run python with the
@@ -18,37 +18,71 @@ def group():
18
18
 
19
19
 
20
20
  def _output_group_list(data):
21
- rows = [
22
- {
23
- "groupId": row.get("groupId", ""),
24
- "name": row.get("name") or row.get("groupName", ""),
25
- "groupMemberCount": row.get("groupMemberCount", ""),
26
- }
27
- for row in (data or {}).get("list", [])
28
- ]
21
+ rows = (data or {}).get("list", [])
22
+ with_feed = any("isPost" in row for row in rows)
23
+ columns = ["groupId", "name", "groupMemberCount"]
24
+ headers = ["Group ID", "Name", "Members"]
25
+ if with_feed:
26
+ columns.append("isPost")
27
+ headers.append("Feed")
29
28
  output_table(
30
- rows,
31
- ["groupId", "name", "groupMemberCount"],
32
- ["Group ID", "Name", "Members"],
29
+ [
30
+ {
31
+ "groupId": row.get("groupId", ""),
32
+ "name": row.get("name") or row.get("groupName", ""),
33
+ "groupMemberCount": row.get("groupMemberCount", ""),
34
+ "isPost": "yes" if row.get("isPost") else "no",
35
+ }
36
+ for row in rows
37
+ ],
38
+ columns,
39
+ headers,
33
40
  )
41
+ if data.get("has_more"):
42
+ click.echo(
43
+ f"Showing {len(rows)} of {data.get('allCount')} groups. Next page: "
44
+ f"-p {data.get('next_page')}, or --all for every group.",
45
+ err=True,
46
+ )
34
47
 
35
48
 
36
49
  @group.command("list")
37
50
  @org_id_option()
38
51
  @click.option("--keywords", "-k", default="", help="Filter groups by name")
39
52
  @pagination_options
53
+ @click.option("--all", "all_pages", is_flag=True,
54
+ help="Return every group instead of one page.")
55
+ @click.option("--feed-only", is_flag=True,
56
+ help="Only groups with the feed on, which are the ones a post "
57
+ "can be published to. Checks every group, so it is slower.")
40
58
  @pass_context
41
- def group_list(ctx, org_id, keywords, page_size, page):
42
- """List open chat groups you have joined."""
59
+ def group_list(ctx, org_id, keywords, page_size, page, all_pages, feed_only):
60
+ """List open chat groups you have joined.
61
+
62
+ One page at a time by default; when more exist the output says so
63
+ (has_more / next_page) — use --all to get every group at once.
64
+ """
43
65
  try:
44
66
  session = ctx.get_session()
45
- data = group_mod.get_groups(
46
- session,
47
- project_id=require_api_project_id(session, org_id),
48
- keywords=keywords,
49
- page_index=page,
50
- page_size=page_size,
51
- )
67
+ project_id = require_api_project_id(session, org_id)
68
+ if all_pages or feed_only:
69
+ data = group_mod.get_all_groups(session, project_id, keywords=keywords)
70
+ else:
71
+ data = group_mod.get_groups(
72
+ session,
73
+ project_id=project_id,
74
+ keywords=keywords,
75
+ page_index=page,
76
+ page_size=page_size,
77
+ ) or {}
78
+ total = data.get("allCount")
79
+ shown = (page - 1) * page_size + len(data.get("list") or [])
80
+ data["has_more"] = isinstance(total, int) and shown < total
81
+ if data["has_more"]:
82
+ data["next_page"] = page + 1
83
+ if feed_only:
84
+ rows = group_mod.mark_feed_groups(session, data.get("list") or [])
85
+ data["list"] = [row for row in rows if row.get("isPost")]
52
86
  ctx.output(data, _output_group_list)
53
87
  except Exception as e:
54
88
  ctx.handle_error(e)
@@ -683,6 +683,50 @@ def post_get(ctx, post_id):
683
683
  ctx.handle_error(e)
684
684
 
685
685
 
686
+ def _upload_progress(event: str, info: dict[str, Any]) -> None:
687
+ """One line per file on stderr, so --json output stays parseable."""
688
+ size = info.get("size")
689
+ size_text = f" ({size / 1048576:.1f} MB)" if size else ""
690
+ where = f"[{info['index']}/{info['total']}]"
691
+ if event == "start":
692
+ click.echo(f"Uploading {where} {info['name']}{size_text} ...", err=True)
693
+ elif event == "done":
694
+ click.echo(f"Uploaded {where} {info['name']}{size_text} in "
695
+ f"{info.get('seconds', 0):.1f}s", err=True)
696
+
697
+
698
+ def _preview_mentions(ctx, org_id: str, message: str) -> list[dict[str, Any]]:
699
+ """Who a --dry-run message would mention, looked up by account id.
700
+
701
+ Only signs in when the message actually mentions someone, so a plain
702
+ dry run stays offline."""
703
+ ids = post_mod.extract_mentions(message)
704
+ if not ids:
705
+ return []
706
+ session = ctx.get_session()
707
+ names = post_mod.resolve_accounts(
708
+ session, require_api_project_id(session, org_id), ids)
709
+ return [{"accountId": i, "name": names.get(i), "found": i in names}
710
+ for i in ids]
711
+
712
+
713
+ def _render_dry_run(d: dict[str, Any]) -> None:
714
+ line = "Would publish to: " + ", ".join(d["audience"])
715
+ if d["cardToGroups"]:
716
+ line += f"; card to groups: {', '.join(d['cardToGroups'])}"
717
+ if d["files"]:
718
+ line += f"; files: {', '.join(d['files'])}"
719
+ click.echo(line)
720
+ if d["categories"]:
721
+ click.echo("Categories: " + ", ".join(d["categories"]))
722
+ for m in d["mentions"]:
723
+ if m["found"]:
724
+ click.echo(f"Mentions: {m['name']} ({m['accountId']})")
725
+ else:
726
+ click.echo(f"Warning: no one in this organization has account id "
727
+ f"{m['accountId']}; that mention will not notify anyone.")
728
+
729
+
686
730
  @post.command("create")
687
731
  @org_id_option()
688
732
  @click.option("--message", "-m", required=True, help="Post content")
@@ -732,6 +776,12 @@ def post_create(ctx, org_id, message, post_type, files, share_group_ids,
732
776
  --org-id when neither is given. Who can see a post is covered in
733
777
  `hap guide post audience`; the group-chat card is `hap guide post card`.
734
778
  Files given with --file are uploaded and attached to the post.
779
+
780
+ In the message, #name# files the post under the category (topic) of
781
+ that name, and user:<accountId> followed by a space mentions and
782
+ notifies a person — get the id with `hap contact resolve <name>`. --dry-run lists both, with the
783
+ mentioned names, before anything is published. Details in
784
+ `hap guide post tags`.
735
785
  """
736
786
  try:
737
787
  if post_type == post_mod.POST_TYPE_ATTACHMENT and not files:
@@ -744,17 +794,14 @@ def post_create(ctx, org_id, message, post_type, files, share_group_ids,
744
794
  [f"group:{g}" for g in share_group_ids]
745
795
  + [f"org:{o}" for o in share_org_ids]
746
796
  ) or [f"org:{org_id or '<current>'} (everyone, default)"]
747
- ctx.output(
748
- {"audience": audience, "cardToGroups": list(card_group_ids),
749
- "message": message, "files": list(files),
750
- "published": False},
751
- lambda d: click.echo(
752
- "Would publish to: " + ", ".join(d["audience"])
753
- + (f"; card to groups: {', '.join(d['cardToGroups'])}"
754
- if d["cardToGroups"] else "")
755
- + (f"; files: {', '.join(d['files'])}"
756
- if d["files"] else "")),
757
- )
797
+ preview = {"audience": audience,
798
+ "cardToGroups": list(card_group_ids),
799
+ "message": post_mod.mention_syntax(message),
800
+ "files": list(files),
801
+ "categories": post_mod.extract_topics(message),
802
+ "mentions": _preview_mentions(ctx, org_id, message),
803
+ "published": False}
804
+ ctx.output(preview, _render_dry_run)
758
805
  return
759
806
  session = ctx.get_session()
760
807
  # Only a group with the feed turned on can be a post audience. The
@@ -778,8 +825,9 @@ def post_create(ctx, org_id, message, post_type, files, share_group_ids,
778
825
  + ". Pick a group whose feed is on, or send a card to the "
779
826
  "chat instead with --card-to-group.")
780
827
  pid = require_api_project_id(session, org_id)
781
- attachments = (post_mod.upload_post_files(session, list(files))
782
- if files else None)
828
+ attachments = (post_mod.upload_post_files(
829
+ session, list(files), progress=_upload_progress)
830
+ if files else None)
783
831
  raw = post_mod.add_post(
784
832
  session,
785
833
  project_id=pid,
@@ -868,11 +916,16 @@ def post_delete(ctx, post_id, yes):
868
916
  )
869
917
  @pass_context
870
918
  def post_comment(ctx, post_id, message, reply_id, reply_account_id, files):
871
- """Add a comment to a post, optionally with files attached."""
919
+ """Add a comment to a post, optionally with files attached.
920
+
921
+ Mention someone with user:<accountId> followed by a space; they are
922
+ notified. Get the id with `hap contact resolve <name>`.
923
+ """
872
924
  try:
873
925
  session = ctx.get_session()
874
- attachments = (post_mod.upload_post_files(session, list(files))
875
- if files else None)
926
+ attachments = (post_mod.upload_post_files(
927
+ session, list(files), progress=_upload_progress)
928
+ if files else None)
876
929
  raw = post_mod.add_comment(
877
930
  session, post_id, message,
878
931
  reply_id=reply_id, reply_account_id=reply_account_id,
@@ -1,10 +1,13 @@
1
1
  """Group management for Mingdao HAP."""
2
2
 
3
3
  import json
4
+ import logging
4
5
  from typing import Any, Optional
5
6
 
6
7
  from hap_cli.core.session import Session
7
8
 
9
+ logger = logging.getLogger("hap_cli.group")
10
+
8
11
 
9
12
  def get_groups(
10
13
  session: Session,
@@ -36,6 +39,46 @@ def get_groups(
36
39
  )
37
40
 
38
41
 
42
+ def get_all_groups(
43
+ session: Session,
44
+ project_id: str,
45
+ keywords: str = "",
46
+ page_size: int = 100,
47
+ ) -> dict[str, Any]:
48
+ """Every page of :func:`get_groups`, as one ``{list, allCount}``.
49
+
50
+ Stops on a short page or once ``allCount`` rows are in, whichever
51
+ comes first, so a server that miscounts cannot loop forever.
52
+ """
53
+ rows: list[dict[str, Any]] = []
54
+ total = None
55
+ page = 1
56
+ while True:
57
+ data = get_groups(session, project_id, keywords=keywords,
58
+ page_index=page, page_size=page_size) or {}
59
+ batch = data.get("list") or []
60
+ rows.extend(batch)
61
+ if total is None:
62
+ total = data.get("allCount")
63
+ if len(batch) < page_size or (isinstance(total, int) and len(rows) >= total):
64
+ break
65
+ page += 1
66
+ logger.info("group: fetched %d group(s) over %d page(s)", len(rows), page)
67
+ return {"list": rows, "allCount": total if total is not None else len(rows)}
68
+
69
+
70
+ def mark_feed_groups(session: Session, rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
71
+ """Add ``isPost`` (feed on, so the group can be a post audience) to
72
+ each row. ``GetGroups`` rows do not carry it — only ``GetGroupInfo``
73
+ does — so this costs one request per group."""
74
+ for row in rows:
75
+ info = get_group_info(session, row.get("groupId", "")) or {}
76
+ if isinstance(info.get("data"), dict):
77
+ info = info["data"]
78
+ row["isPost"] = bool(info.get("isPost"))
79
+ return rows
80
+
81
+
39
82
  def get_group_info(
40
83
  session: Session,
41
84
  group_id: str,
@@ -26,7 +26,8 @@ Response envelopes (from a live probe on SaaS):
26
26
 
27
27
  import json
28
28
  import logging
29
- from typing import Any, Optional
29
+ import re
30
+ from typing import Any, Callable, Optional
30
31
 
31
32
  from hap_cli.core.session import APIError, Session
32
33
 
@@ -238,22 +239,100 @@ def _attachment_fields(attachments: list[dict[str, Any]]) -> dict[str, str]:
238
239
  }
239
240
 
240
241
 
241
- def upload_post_files(session: Session, sources: list[str]) -> list[dict[str, Any]]:
242
+ def upload_post_files(
243
+ session: Session,
244
+ sources: list[str],
245
+ progress: Optional[Callable[[str, dict[str, Any]], None]] = None,
246
+ ) -> list[dict[str, Any]]:
242
247
  """Upload local paths / http(s) addresses and return attachment items
243
248
  for :func:`add_post` / :func:`add_comment`.
244
249
 
245
250
  No organization is named for the upload credential: the feed mounts
246
251
  its uploader without a projectId (MDPublic createFeed/
247
252
  CreateFeedContent.jsx:124-136), same as chat does here.
253
+ ``progress`` is handed to ``upload.upload_files`` unchanged.
248
254
  """
249
255
  from hap_cli.core import upload as upload_mod
250
256
 
251
257
  descriptors = upload_mod.upload_files(
252
- session, upload_mod.source_descriptors(list(sources)))
258
+ session, upload_mod.source_descriptors(list(sources)),
259
+ progress=progress)
253
260
  logger.info("post: uploaded %d attachment(s)", len(descriptors))
254
261
  return upload_mod.attachment_items(descriptors)
255
262
 
256
263
 
264
+ # What the feed composer writes into a post body (MDPublic
265
+ # src/components/MentionsInput/index.jsx). A topic is `#name#`, half- or
266
+ # full-width `#` (categoryLetterArr, :19; :308). The server files the post
267
+ # under the category of that name, creating it when new, and reads it back
268
+ # as `categories` — there is no category field on AddPost itself.
269
+ #
270
+ # A person is `user:<accountId>`: posts and post comments open the
271
+ # composer with sourceType POST, whose mentionItemSyntax is
272
+ # `<type>:<id>` (:75-78), and a captured AddPost / AddPostComment from the
273
+ # web feed (2026-10-12) carries exactly `user:<id> `. The server turns that
274
+ # into the stored `[aid]<id>[/aid]` marker *and* notifies the person.
275
+ # Sending `[aid]…[/aid]` directly renders as a mention but notifies nobody,
276
+ # so callers that learned the stored form are rewritten — the post editor
277
+ # does the same when it loads a post back (EditPostDialog.jsx:94).
278
+ _TOPIC_RE = re.compile(r"[##]([^##\n]+?)[##]")
279
+ _MENTION_RE = re.compile(r"(?<![0-9A-Za-z_])user:([0-9A-Za-z-]+)")
280
+ _STORED_MENTION_RE = re.compile(r"\[aid\]\s*([0-9A-Za-z-]+)\s*\[/aid\]")
281
+
282
+
283
+ def mention_syntax(message: str) -> str:
284
+ """Rewrite stored-form ``[aid]<id>[/aid]`` mentions to ``user:<id>``.
285
+
286
+ A space follows each rewritten mention unless whitespace already does,
287
+ matching the composer, which leaves one after the picked name."""
288
+ text = message or ""
289
+
290
+ def _sub(m: "re.Match[str]") -> str:
291
+ follow = text[m.end():m.end() + 1]
292
+ return f"user:{m.group(1)}" + ("" if not follow or follow.isspace() else " ")
293
+
294
+ return _STORED_MENTION_RE.sub(_sub, text)
295
+
296
+
297
+ def extract_topics(message: str) -> list[str]:
298
+ """Topic names written as ``#name#`` in a post body, in order, unique."""
299
+ seen: list[str] = []
300
+ for name in _TOPIC_RE.findall(message or ""):
301
+ name = name.strip()
302
+ if name and name not in seen:
303
+ seen.append(name)
304
+ return seen
305
+
306
+
307
+ def extract_mentions(message: str) -> list[str]:
308
+ """Account ids mentioned in a post body (``user:<id>``, or the stored
309
+ ``[aid]<id>[/aid]`` form), in order, unique."""
310
+ seen: list[str] = []
311
+ for account_id in _MENTION_RE.findall(mention_syntax(message)):
312
+ if account_id not in seen:
313
+ seen.append(account_id)
314
+ return seen
315
+
316
+
317
+ def resolve_accounts(
318
+ session: Session, project_id: str, account_ids: list[str],
319
+ ) -> dict[str, str]:
320
+ """Map account ids to display names via ``User.GetUserListByAccountId``
321
+ (MDPublic src/api/user.js:12). ``projectId`` is mandatory — the server
322
+ answers "组织编号不能为空" without it. Ids it does not know are simply
323
+ missing from the result."""
324
+ if not account_ids:
325
+ return {}
326
+ raw = session.api_call("User", "GetUserListByAccountId", {
327
+ "projectId": project_id, "accountIds": list(account_ids)})
328
+ rows = raw.get("list") if isinstance(raw, dict) else raw
329
+ names: dict[str, str] = {}
330
+ for row in rows or []:
331
+ if isinstance(row, dict) and row.get("accountId"):
332
+ names[row["accountId"]] = str(row.get("fullname") or "")
333
+ return names
334
+
335
+
257
336
  def add_post(
258
337
  session: Session,
259
338
  project_id: str,
@@ -306,7 +385,7 @@ def add_post(
306
385
  data: dict[str, Any] = {
307
386
  "projectId": project_id,
308
387
  "postType": post_type,
309
- "postMsg": message,
388
+ "postMsg": mention_syntax(message),
310
389
  "scope": scope,
311
390
  }
312
391
  if attachments:
@@ -327,7 +406,7 @@ def edit_post(
327
406
  doesn't have the previous text we omit it (the server accepts
328
407
  either shape, but the frontend always sends the old text).
329
408
  """
330
- data: dict[str, Any] = {"postId": post_id, "postMsg": message}
409
+ data: dict[str, Any] = {"postId": post_id, "postMsg": mention_syntax(message)}
331
410
  if old_message:
332
411
  data["oldPostMsg"] = old_message
333
412
  return _require_post_success(
@@ -356,7 +435,7 @@ def add_comment(
356
435
  string form as on a post (MDPublic src/pages/feed/components/post/
357
436
  comment/postCommentInput.jsx:181-192; pd-openweb identical).
358
437
  """
359
- data: dict[str, Any] = {"postID": post_id, "message": message}
438
+ data: dict[str, Any] = {"postID": post_id, "message": mention_syntax(message)}
360
439
  if reply_id:
361
440
  data["replyID"] = reply_id
362
441
  if reply_account_id:
@@ -27,7 +27,7 @@ import random
27
27
  import re
28
28
  import time
29
29
  from datetime import datetime, timezone
30
- from typing import Any, Optional
30
+ from typing import Any, Callable, Optional
31
31
  from urllib.parse import parse_qs, quote, urlparse
32
32
 
33
33
  import requests
@@ -404,11 +404,30 @@ def upload_files(
404
404
  project_id: str = "",
405
405
  bucket: Optional[int] = None,
406
406
  server_name: str = "",
407
+ progress: Optional[Callable[[str, dict[str, Any]], None]] = None,
407
408
  ) -> list[dict[str, Any]]:
408
409
  """Upload a list of ``{name?, url}`` / ``{name?, path}`` descriptors and
409
- return one uploaded-file descriptor per file (order preserved)."""
410
+ return one uploaded-file descriptor per file (order preserved).
411
+
412
+ ``progress(event, info)`` is called with ``"start"`` before each file
413
+ and ``"done"`` after it; ``info`` carries ``index`` (1-based),
414
+ ``total``, ``name``, ``size`` (bytes, ``None`` for a URL not yet
415
+ fetched) and, on ``"done"``, ``seconds``. Large videos take a while
416
+ and the caller has nothing else to show in the meantime.
417
+ """
410
418
  out: list[dict[str, Any]] = []
411
- for f in files:
419
+ total = len(files)
420
+ for index, f in enumerate(files, start=1):
421
+ label = f.get("name") or os.path.basename(f.get("path") or f.get("url") or "")
422
+ size = None
423
+ if f.get("path") and not f.get("url"):
424
+ try:
425
+ size = os.path.getsize(f["path"])
426
+ except OSError:
427
+ size = None
428
+ info = {"index": index, "total": total, "name": label, "size": size}
429
+ _report(progress, "start", info)
430
+ started = time.monotonic()
412
431
  url = f.get("url")
413
432
  path = f.get("path")
414
433
  if url:
@@ -424,9 +443,20 @@ def upload_files(
424
443
  session, content=content, name=name, worksheet_id=worksheet_id,
425
444
  app_id=app_id, project_id=project_id, bucket=bucket,
426
445
  server_name=server_name))
446
+ _report(progress, "done", {**info, "name": name, "size": len(content),
447
+ "seconds": time.monotonic() - started})
427
448
  return out
428
449
 
429
450
 
451
+ def _report(progress, event: str, info: dict[str, Any]) -> None:
452
+ if progress is None:
453
+ return
454
+ try:
455
+ progress(event, info)
456
+ except Exception as exc:
457
+ logger.warning("Upload progress callback failed: %s", exc)
458
+
459
+
430
460
  def attachment_items(descriptors: list[dict[str, Any]]) -> list[dict[str, Any]]:
431
461
  """Uploaded-file descriptors as the attachment items the uploader hands
432
462
  to whatever it is attached to — the ``formatResponseData`` shape
@@ -41,7 +41,8 @@ audience. Naming a `--to-group` does **not** additionally publish company-wide.
41
41
  A plain chat group cannot receive a post. The server refuses with
42
42
  "选择了没有加入的群组" (you picked a group you have not joined), which is
43
43
  **misleading** — you are in the group; it simply has no feed. The CLI checks
44
- first and names the group instead. To check one: `hap group info <groupId>`
44
+ first and names the group instead. To list every group that can receive a
45
+ post: `hap group list --feed-only`. To check one: `hap group info <groupId>`
45
46
  and look at `isPost`. To merely drop a link into such a group, use
46
47
  `--card-to-group` below.
47
48
 
@@ -90,12 +91,26 @@ remove it too, recall that message with
90
91
  `hap chat recall <messageId> --group-id <groupId>` (your own messages within 5
91
92
  minutes of sending; a group admin can recall a member's message with no limit).
92
93
 
93
- ## 3. Topic tags
94
- <!-- hook: Writing #tag# in the body registers a topic; hap post topics lists what is available. -->
94
+ ## 3. Categories and mentions in the body
95
+ <!-- hook: #name# in the body sets the post's category; user:<accountId> mentions and notifies a person; --dry-run previews both. -->
95
96
  <!-- alias: tags -->
96
97
 
97
- Writing `#tag#` in the body registers a topic (for example `#今日工作#`), which
98
- shows up in the post's categories. List available topics with `hap post topics`.
98
+ There is no category option: **the category comes from the body.** Writing
99
+ `#name#` (for example `#今日工作#`) files the post under the category of that
100
+ name, creating it if it is new. List existing ones with `hap post topics`.
101
+
102
+ To mention someone, write `user:<accountId>` followed by a space where the
103
+ `@name` should go — the person is notified. The same works in
104
+ `hap post comment`. Get the id from the person's name first:
105
+
106
+ ```bash
107
+ hap contact resolve 刘禹 # candidates[].id is the accountId
108
+ hap post create -m "#今日工作# 今天和 user:<accountId> 对齐了方案" --dry-run
109
+ ```
110
+
111
+ A published post's audience cannot be changed, so check with `--dry-run`
112
+ first: it lists the categories and each mention with the person's name, and
113
+ warns about an id that matches nobody in the organization.
99
114
 
100
115
  ## 4. Other common commands
101
116
  <!-- hook: Listing, searching, reading, commenting on and deleting posts, plus what post search --scope accepts. -->
@@ -51,8 +51,8 @@
51
51
  "zh-Hans": "发动态与发布范围"
52
52
  },
53
53
  "summary": {
54
- "en": "Audience decides who can see a post, and you cannot read it back off an existing one.",
55
- "zh-Hans": "发布范围决定谁能看到,且无法从旧动态读出来。"
54
+ "en": "Audience decides who can see a post, and you cannot read it back off an existing one; #name# in the body sets the category, user:<accountId> notifies a person.",
55
+ "zh-Hans": "发布范围决定谁能看到,且无法从旧动态读出来;正文 #名称# 决定分类,user:<accountId> 提及并通知某人。"
56
56
  }
57
57
  },
58
58
  {
@@ -37,6 +37,7 @@ hap post create -m "内容" --to-org fe288386-xxxx
37
37
 
38
38
  纯聊天群不能接收动态。服务端对此的报错是「选择了没有加入的群组」,**具有误导性**
39
39
  ——你明明在群里,真实原因是这个群没开启动态功能。CLI 会先行检查并直接报出群名。
40
+ 列出所有能发动态的群:`hap group list --feed-only`。
40
41
  想查某个群是否可用:`hap group info <群组ID>`,看 `isPost` 是否为 true。
41
42
  只是想在这种群里贴个链接,用下面的 `--card-to-group`。
42
43
 
@@ -78,12 +79,23 @@ hap post create -m "内容" --to-group 9ccde712-xxxx --card-to-group 7f694ec7-xx
78
79
  指向一条已不存在的动态。要一并清掉,用 `hap chat recall <消息ID> --group-id <群组ID>`
79
80
  撤回那条卡片消息(自己的消息限发送后 5 分钟内,群管理员撤回他人消息不限时)。
80
81
 
81
- ## 3. 话题标签
82
- <!-- hook: 正文里写 #标签# 会登记话题,可用话题用 hap post topics 看 -->
82
+ ## 3. 正文里的分类与 @提及
83
+ <!-- hook: 正文 #名称# 定分类,user:id 提及并通知某人,--dry-run 可预览 -->
83
84
  <!-- alias: tags -->
84
85
 
85
- 正文里写 `#标签#` 会被识别为话题(例如 `#今日工作#`),发布后出现在动态的
86
- 分类里。可用的话题用 `hap post topics` 查。
86
+ 没有设置分类的选项:**分类由正文决定。** 正文里写 `#名称#`(例如 `#今日工作#`),
87
+ 动态就会归到同名分类下,分类不存在时会自动新建。已有分类用 `hap post topics` 查。
88
+
89
+ 要 @ 某人,在想出现「@姓名」的位置写 `user:<accountId>`,后面跟一个空格,对方会收到提醒。
90
+ `hap post comment` 里写法相同。先用姓名查出 id:
91
+
92
+ ```bash
93
+ hap contact resolve 刘禹 # candidates[].id 就是 accountId
94
+ hap post create -m "#今日工作# 今天和 user:<accountId> 对齐了方案" --dry-run
95
+ ```
96
+
97
+ 动态发出后范围无法再改,所以先用 `--dry-run` 检查:它会列出分类,以及每个
98
+ 提及对应的姓名;某个 id 在组织里找不到人时会给出警告。
87
99
 
88
100
  ## 4. 其它常用
89
101
  <!-- hook: 动态的列表、搜索、读取、评论、删除,以及 search --scope 接受哪些值 -->