hap-cli 0.8.29__tar.gz → 0.8.31__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 (296) hide show
  1. {hap_cli-0.8.29 → hap_cli-0.8.31}/PKG-INFO +2 -1
  2. {hap_cli-0.8.29/hap_cli → hap_cli-0.8.31}/README.md +1 -0
  3. {hap_cli-0.8.29 → hap_cli-0.8.31/hap_cli}/README.md +1 -0
  4. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/README_CN.md +1 -0
  5. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/__init__.py +1 -1
  6. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/calendar_cmd.py +36 -1
  7. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/executor.py +6 -12
  8. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/calendar_mod.py +65 -1
  9. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/global_meta.py +15 -0
  10. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/record.py +8 -3
  11. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/upload.py +25 -1
  12. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/locale/messages.json +17 -1
  13. hap_cli-0.8.31/hap_cli/tests/test_calendar_discussion_cli.py +89 -0
  14. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_core.py +75 -0
  15. hap_cli-0.8.31/hap_cli/tests/test_upload_allow_down.py +118 -0
  16. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli.egg-info/PKG-INFO +2 -1
  17. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli.egg-info/SOURCES.txt +2 -0
  18. {hap_cli-0.8.29 → hap_cli-0.8.31}/MANIFEST.in +0 -0
  19. {hap_cli-0.8.29 → hap_cli-0.8.31}/app_live_tests/__init__.py +0 -0
  20. {hap_cli-0.8.29 → hap_cli-0.8.31}/app_live_tests/__main__.py +0 -0
  21. {hap_cli-0.8.29 → hap_cli-0.8.31}/app_live_tests/config.py +0 -0
  22. {hap_cli-0.8.29 → hap_cli-0.8.31}/app_live_tests/harness.py +0 -0
  23. {hap_cli-0.8.29 → hap_cli-0.8.31}/app_live_tests/smoke.py +0 -0
  24. {hap_cli-0.8.29 → hap_cli-0.8.31}/app_live_tests/state.py +0 -0
  25. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/__main__.py +0 -0
  26. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/__init__.py +0 -0
  27. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/app_cmd.py +0 -0
  28. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/app_creator_cmd.py +0 -0
  29. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/app_editor_cmd.py +0 -0
  30. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/attachment_cmd.py +0 -0
  31. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/auth_cmd.py +0 -0
  32. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/chart_cmd.py +0 -0
  33. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/chat_cmd.py +0 -0
  34. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/chatbot_cmd.py +0 -0
  35. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/config_cmd.py +0 -0
  36. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/contact_cmd.py +0 -0
  37. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/department_cmd.py +0 -0
  38. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/group_cmd.py +0 -0
  39. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/guide_cmd.py +0 -0
  40. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/icon_cmd.py +0 -0
  41. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/instance_cmd.py +0 -0
  42. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/node_cmd.py +0 -0
  43. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/optionset_cmd.py +0 -0
  44. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/page_cmd.py +0 -0
  45. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/post_cmd.py +0 -0
  46. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/record_cmd.py +0 -0
  47. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/region_cmd.py +0 -0
  48. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/role_cmd.py +0 -0
  49. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/update_cmd.py +0 -0
  50. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/upload_cmd.py +0 -0
  51. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/v3_registry.py +0 -0
  52. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/workflow_cmd.py +0 -0
  53. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/commands/worksheet_cmd.py +0 -0
  54. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/context.py +0 -0
  55. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/__init__.py +0 -0
  56. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/action_spec_adapter.py +0 -0
  57. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app.py +0 -0
  58. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/__init__.py +0 -0
  59. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
  60. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
  61. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
  62. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/charts.py +0 -0
  63. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/cleanup.py +0 -0
  64. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/compiler.py +0 -0
  65. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/config.py +0 -0
  66. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/design/design.schema.json +0 -0
  67. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/errors.py +0 -0
  68. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
  69. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/executor.py +0 -0
  70. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/fields.py +0 -0
  71. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/hap.py +0 -0
  72. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/merge.py +0 -0
  73. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/recording/__init__.py +0 -0
  74. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/recording/console.py +0 -0
  75. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
  76. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/recording/mirror.py +0 -0
  77. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/recording/report.py +0 -0
  78. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/schema.py +0 -0
  79. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
  80. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/__init__.py +0 -0
  81. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/cli.py +0 -0
  82. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
  83. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
  84. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
  85. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
  86. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/seed/template.py +0 -0
  87. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/selftest.py +0 -0
  88. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/smoke.py +0 -0
  89. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/steps.py +0 -0
  90. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/store.py +0 -0
  91. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/validate.py +0 -0
  92. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
  93. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/attachment.py +0 -0
  94. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/auth.py +0 -0
  95. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/chart.py +0 -0
  96. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/chat.py +0 -0
  97. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/chatbot.py +0 -0
  98. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/cli_access.py +0 -0
  99. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/contact.py +0 -0
  100. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/control_type_codes.py +0 -0
  101. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/department.py +0 -0
  102. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/__init__.py +0 -0
  103. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  104. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  105. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/apply.py +0 -0
  106. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/componentlower.py +0 -0
  107. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/editspec/component.schema.json +0 -0
  108. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
  109. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
  110. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/editspec/field.schema.json +0 -0
  111. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/editspec.py +0 -0
  112. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/errors.py +0 -0
  113. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/fieldlower.py +0 -0
  114. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  115. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/models.py +0 -0
  116. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/ops.py +0 -0
  117. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/planner.py +0 -0
  118. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/editor/reader.py +0 -0
  119. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/field_normalizer.py +0 -0
  120. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/field_spec_adapter.py +0 -0
  121. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/filter_translator.py +0 -0
  122. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/flow_node.py +0 -0
  123. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/group.py +0 -0
  124. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/guide.py +0 -0
  125. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/icon_index.py +0 -0
  126. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/instance.py +0 -0
  127. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/logger.py +0 -0
  128. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/optionset.py +0 -0
  129. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/page.py +0 -0
  130. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/post.py +0 -0
  131. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/response_crypto.py +0 -0
  132. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/role.py +0 -0
  133. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/role_perm_builder.py +0 -0
  134. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/session.py +0 -0
  135. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/token_crypto.py +0 -0
  136. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/update.py +0 -0
  137. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/v3/__init__.py +0 -0
  138. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/v3/dispatcher.py +0 -0
  139. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/v3/render.py +0 -0
  140. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/v3/schema.py +0 -0
  141. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/view_spec_adapter.py +0 -0
  142. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/workflow.py +0 -0
  143. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/workflow_node_dsl.py +0 -0
  144. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/worksheet.py +0 -0
  145. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/core/worksheet_templates.py +0 -0
  146. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/approval.md +0 -0
  147. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/chart.md +0 -0
  148. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/messages.md +0 -0
  149. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/overview.md +0 -0
  150. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/post.md +0 -0
  151. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/record.md +0 -0
  152. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/setup.md +0 -0
  153. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/en/skills.md +0 -0
  154. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/index.json +0 -0
  155. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/approval.md +0 -0
  156. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/chart.md +0 -0
  157. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/messages.md +0 -0
  158. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/overview.md +0 -0
  159. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/post.md +0 -0
  160. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/record.md +0 -0
  161. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/setup.md +0 -0
  162. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/guide/zh-Hans/skills.md +0 -0
  163. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/hap_cli.py +0 -0
  164. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/i18n.py +0 -0
  165. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/locale/__init__.py +0 -0
  166. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/locale/messages.schema.json +0 -0
  167. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/__init__.py +0 -0
  168. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/conftest.py +0 -0
  169. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_app_creator.py +0 -0
  170. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_app_editor.py +0 -0
  171. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_app_export_usage.py +0 -0
  172. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_approval_submit_fill_cli.py +0 -0
  173. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_attachment_share.py +0 -0
  174. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_auth_prerelease.py +0 -0
  175. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_bug_036430.py +0 -0
  176. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_bug_036750.py +0 -0
  177. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_chart.py +0 -0
  178. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_chat_attachment_send.py +0 -0
  179. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_chat_recall_cli.py +0 -0
  180. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_cli_access.py +0 -0
  181. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_config_cmd.py +0 -0
  182. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_content_language.py +0 -0
  183. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_control_lookup_guard.py +0 -0
  184. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
  185. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_full_e2e.py +0 -0
  186. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_global_meta.py +0 -0
  187. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_guide.py +0 -0
  188. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_i18n.py +0 -0
  189. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_instance_history_cli.py +0 -0
  190. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration.py +0 -0
  191. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_approval.py +0 -0
  192. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_approval_actions.py +0 -0
  193. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_calendar.py +0 -0
  194. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_destructive.py +0 -0
  195. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_misc.py +0 -0
  196. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_post.py +0 -0
  197. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_social.py +0 -0
  198. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_v3.py +0 -0
  199. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_workflow.py +0 -0
  200. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  201. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
  202. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_multi_profile.py +0 -0
  203. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
  204. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
  205. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_org_id_cli.py +0 -0
  206. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_org_id_docs.py +0 -0
  207. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_parameter_conventions.py +0 -0
  208. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  209. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_post_scope_cli.py +0 -0
  210. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
  211. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_record_field_alias.py +0 -0
  212. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
  213. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_record_write_fixes.py +0 -0
  214. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_select_add_option.py +0 -0
  215. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
  216. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_semantic_types_registry.py +0 -0
  217. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_update.py +0 -0
  218. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_update_mode.py +0 -0
  219. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_upload_host_resolution.py +0 -0
  220. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  221. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  222. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  223. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_v3_session.py +0 -0
  224. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  225. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
  226. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_workflow_list_cli.py +0 -0
  227. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
  228. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
  229. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_worksheet_create_section.py +0 -0
  230. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  231. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_worksheet_switch.py +0 -0
  232. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
  233. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/utils/__init__.py +0 -0
  234. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/utils/formatting.py +0 -0
  235. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/utils/options.py +0 -0
  236. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli/utils/parameter_mapping.py +0 -0
  237. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli.egg-info/dependency_links.txt +0 -0
  238. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli.egg-info/entry_points.txt +0 -0
  239. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli.egg-info/requires.txt +0 -0
  240. {hap_cli-0.8.29 → hap_cli-0.8.31}/hap_cli.egg-info/top_level.txt +0 -0
  241. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/__init__.py +0 -0
  242. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/__main__.py +0 -0
  243. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/_wftest.py +0 -0
  244. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/charts.py +0 -0
  245. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/cleanup.py +0 -0
  246. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/compiler.py +0 -0
  247. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/config.py +0 -0
  248. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/errors.py +0 -0
  249. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/executor.py +0 -0
  250. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/fields.py +0 -0
  251. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/hap.py +0 -0
  252. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/recording/__init__.py +0 -0
  253. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/recording/console.py +0 -0
  254. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/recording/jsonl.py +0 -0
  255. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/recording/mirror.py +0 -0
  256. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/recording/report.py +0 -0
  257. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/schema.py +0 -0
  258. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/seed/__init__.py +0 -0
  259. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/seed/cli.py +0 -0
  260. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/seed/executor.py +0 -0
  261. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/seed/template.py +0 -0
  262. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/selftest.py +0 -0
  263. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/smoke.py +0 -0
  264. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/step.py +0 -0
  265. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/steps.py +0 -0
  266. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/store.py +0 -0
  267. {hap_cli-0.8.29 → hap_cli-0.8.31}/live_tests/workflow_dsl.py +0 -0
  268. {hap_cli-0.8.29 → hap_cli-0.8.31}/setup.cfg +0 -0
  269. {hap_cli-0.8.29 → hap_cli-0.8.31}/setup.py +0 -0
  270. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/INDEX.json +0 -0
  271. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  272. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  273. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/create_optionset.yaml +0 -0
  274. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/create_role.yaml +0 -0
  275. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  276. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/delete_record.yaml +0 -0
  277. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/delete_role.yaml +0 -0
  278. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  279. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_app_info.yaml +0 -0
  280. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  281. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  282. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  283. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_details.yaml +0 -0
  284. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  285. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_list.yaml +0 -0
  286. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  287. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  288. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  289. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  290. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_regions.yaml +0 -0
  291. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_role_details.yaml +0 -0
  292. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/get_role_list.yaml +0 -0
  293. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  294. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  295. {hap_cli-0.8.29 → hap_cli-0.8.31}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  296. {hap_cli-0.8.29 → hap_cli-0.8.31}/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.29
3
+ Version: 0.8.31
4
4
  Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -264,6 +264,7 @@ discover every subcommand.
264
264
  | `calendar list / get / create / update / delete` | Event lifecycle |
265
265
  | `calendar categories` | Built-in categories |
266
266
  | `calendar add-member / remove-member` | Membership |
267
+ | `calendar add-discussion` | Comment on an event (attachments supported) |
267
268
 
268
269
  ### chat — chat & messaging
269
270
 
@@ -235,6 +235,7 @@ discover every subcommand.
235
235
  | `calendar list / get / create / update / delete` | Event lifecycle |
236
236
  | `calendar categories` | Built-in categories |
237
237
  | `calendar add-member / remove-member` | Membership |
238
+ | `calendar add-discussion` | Comment on an event (attachments supported) |
238
239
 
239
240
  ### chat — chat & messaging
240
241
 
@@ -235,6 +235,7 @@ discover every subcommand.
235
235
  | `calendar list / get / create / update / delete` | Event lifecycle |
236
236
  | `calendar categories` | Built-in categories |
237
237
  | `calendar add-member / remove-member` | Membership |
238
+ | `calendar add-discussion` | Comment on an event (attachments supported) |
238
239
 
239
240
  ### chat — chat & messaging
240
241
 
@@ -218,6 +218,7 @@ hap --json worksheet record list WORKSHEET_ID
218
218
  | `calendar list / get / create / update / delete` | 日程生命周期 |
219
219
  | `calendar categories` | 内置分类 |
220
220
  | `calendar add-member / remove-member` | 成员管理 |
221
+ | `calendar add-discussion` | 在日程下发表评论(可带附件) |
221
222
 
222
223
  ### chat — 聊天与消息
223
224
 
@@ -1,3 +1,3 @@
1
1
  """CLI harness for Mingdao HAP (hap-cli)."""
2
2
 
3
- __version__ = "0.8.29"
3
+ __version__ = "0.8.31"
@@ -543,4 +543,39 @@ def calendar_categories(ctx):
543
543
  summary = _simplify_categories_result(raw)
544
544
  ctx.output(summary, _render_categories)
545
545
  except Exception as e:
546
- ctx.handle_error(e)
546
+ ctx.handle_error(e)
547
+
548
+ @calendar.command("add-discussion")
549
+ @click.argument("calendar_id")
550
+ @click.option("--message", "-m", required=True, help="Comment text")
551
+ @click.option("--reply-id", default="", help="ID of the comment being replied to")
552
+ @click.option("--file", "-f", "files", multiple=True,
553
+ help="Attach a file to the comment (repeatable). Accepts a local "
554
+ "path or an http(s) URL.")
555
+ @pass_context
556
+ def calendar_add_discussion(ctx, calendar_id, message, reply_id, files):
557
+ """Post a comment on a calendar event.
558
+
559
+ Everyone invited to the event can see the comment. Attached files are
560
+ downloadable by every participant.
561
+ """
562
+ try:
563
+ session = ctx.get_session()
564
+ attachments = None
565
+ if files:
566
+ from hap_cli.core import upload as up_mod
567
+ attachments = up_mod.upload_files(
568
+ session, up_mod.source_descriptors(list(files)))
569
+ raw = cal_mod.add_discussion(
570
+ session, calendar_id, message,
571
+ reply_id=reply_id, attachments=attachments,
572
+ )
573
+ data = raw.get("data") if isinstance(raw, dict) else None
574
+ summary = {
575
+ "success": True,
576
+ "discussion_id": (data or {}).get("discussionId", ""),
577
+ "calendar_id": calendar_id,
578
+ }
579
+ ctx.output(summary, lambda d: click.echo("Comment posted."))
580
+ except Exception as e:
581
+ ctx.handle_error(e)
@@ -141,18 +141,12 @@ class _Resolver:
141
141
 
142
142
  def _record_attachment_cell(descriptors: list[dict[str, Any]]) -> dict[str, Any]:
143
143
  """Wrap general uploaded-file descriptors into a worksheet Attachment
144
- (type 14) cell value (mirrors core upload.record_attachment_cell)."""
145
- attachments = []
146
- for i, d in enumerate(descriptors):
147
- attachments.append({
148
- "fileID": d.get("fileID"), "fileSize": d.get("fileSize"),
149
- "serverName": d.get("serverName"), "filePath": d.get("filePath"),
150
- "fileName": d.get("fileName"), "fileExt": d.get("fileExt"),
151
- "originalFileName": d.get("originalFileName"), "key": d.get("key"),
152
- "url": d.get("url"), "oldOriginalFileName": d.get("originalFileName"),
153
- "index": i, "isEdit": False,
154
- })
155
- return {"attachmentData": [], "attachments": attachments, "knowledgeAtts": []}
144
+ (type 14) cell value. Delegates to the core wrapper so the seeded rows
145
+ carry exactly what any other CLI upload does — the local copy this
146
+ replaced silently dropped each file's download permission."""
147
+ from hap_cli.core.upload import record_attachment_cell
148
+
149
+ return record_attachment_cell(descriptors)
156
150
 
157
151
 
158
152
  def _upload_row_attachments(
@@ -13,9 +13,12 @@ Enum re-exports (`FREQUENCY_*`, `REMIND_TYPE_*`) mirror
13
13
  `src/pages/calendar/modules/calendarDetail/constant.js`.
14
14
  """
15
15
 
16
+ import json
16
17
  from typing import Any, Optional
17
18
 
18
- from hap_cli.core.session import Session
19
+ from hap_cli.core import global_meta as gm
20
+ from hap_cli.core import record as rec_mod
21
+ from hap_cli.core.session import Session, SessionError
19
22
 
20
23
 
21
24
  # ── Enums (pd-openweb constants) ─────────────────────────────────────────
@@ -327,3 +330,64 @@ def remove_member(
327
330
  def get_categories(session: Session) -> dict[str, Any]:
328
331
  """Get all calendar categories (`Calendar.GetUserAllCalCategories`)."""
329
332
  return session.api_call("Calendar", "GetUserAllCalCategories", {})
333
+
334
+
335
+ # ── Discussions (comments on an event) ────────────────────────────────────
336
+
337
+
338
+ def add_discussion(
339
+ session: Session,
340
+ calendar_id: str,
341
+ message: str,
342
+ reply_id: str = "",
343
+ attachments: Optional[list[dict[str, Any]]] = None,
344
+ ) -> dict[str, Any]:
345
+ """Post a comment on a calendar event (`Discussion.AddDiscussion`).
346
+
347
+ Traced from `src/pages/calendar/modules/calendarDetail/container/
348
+ CalendarCommenter.jsx`, which configures the shared commenter
349
+ (`src/components/comment/commenter.jsx`) with:
350
+
351
+ - `sourceType` = SOURCE_TYPE.CALENDAR (3, comment/config.js)
352
+ - `sourceId` = the bare calendar id (no composite form, unlike the
353
+ worksheet-row case's ``{worksheetId}|{rowId}``)
354
+ - `appId` = md.global.APPInfo.calendarAppID, and *no*
355
+ `fromAppId` — a calendar event has no owning app
356
+
357
+ Two props of CalendarCommenter deliberately do **not** reach this
358
+ payload. `remark` is only consumed when the comment is additionally
359
+ shared to a post (`postAjax.addPost`), and `AddDiscussionRequest`
360
+ (sources/main_api/schemas/…AddDiscussionRequest.json) has no such
361
+ field. `extendsId` is documented "扩展ID(工作表:appId|viewId)" and the
362
+ calendar commenter never sets it.
363
+
364
+ ``attachments`` carries uploaded-file descriptors from
365
+ ``hap_cli.core.upload``; the frontend sends them JSON-encoded, and
366
+ each descriptor's ``allowDown`` is what lets anyone but the uploader
367
+ download the file (see upload._download_permission).
368
+ """
369
+ app_id = gm.calendar_app_id(session.get_global_meta())
370
+ if not app_id:
371
+ # Guessing an app id here would produce a discussion the calendar
372
+ # UI cannot render, which is worse than refusing.
373
+ raise SessionError(
374
+ "This server did not publish the calendar application, so the "
375
+ "comment cannot be posted. Check that you are signed in to the "
376
+ "right server, then try again."
377
+ )
378
+ data: dict[str, Any] = {
379
+ "sourceId": calendar_id,
380
+ "sourceType": rec_mod.DISCUSSION_SOURCE_CALENDAR,
381
+ "message": message,
382
+ "appId": app_id,
383
+ # commenter.jsx: `forReacordDiscussion && entityType === 2 ? 2 : 0`
384
+ # — a calendar comment is always the plain kind.
385
+ "entityType": 0,
386
+ }
387
+ if reply_id:
388
+ data["replyId"] = reply_id
389
+ if attachments:
390
+ data["attachments"] = json.dumps(attachments, ensure_ascii=False)
391
+ result = session.api_call("Discussion", "AddDiscussion", data)
392
+ rec_mod.ensure_discussion_ok(result, "add comment")
393
+ return result
@@ -156,6 +156,21 @@ def worksheet_row_app_id(meta: Any) -> str:
156
156
  return str(_app_info(meta).get("worksheetRowAppID") or "")
157
157
 
158
158
 
159
+ def calendar_app_id(meta: Any) -> str:
160
+ """Return ``md.global.APPInfo.calendarAppID``.
161
+
162
+ The internal "application" id HAP uses for **calendar-event**
163
+ discussions (``sourceType`` 3 / ``Commenter.TYPES.CALENDAR`` in
164
+ pd-openweb ``src/components/comment/config.js``). ``AddDiscussion``
165
+ must carry it as ``appId`` — see
166
+ ``src/pages/calendar/modules/calendarDetail/container/CalendarCommenter.jsx``.
167
+ Unlike the worksheet-row case there is no separate ``fromAppId``:
168
+ a calendar event has no owning app. Returns empty string when the
169
+ deployment does not expose it.
170
+ """
171
+ return str(_app_info(meta).get("calendarAppID") or "")
172
+
173
+
159
174
  def worksheet_app_id(meta: Any) -> str:
160
175
  """Return ``md.global.APPInfo.worksheetAppID`` (whole-worksheet
161
176
  discussions, ``sourceType`` 7). Empty string when absent."""
@@ -12,10 +12,15 @@ from hap_cli.core import global_meta as gm
12
12
  logger = logging.getLogger("hap_cli.record")
13
13
 
14
14
  # Discussion source types — pd-openweb src/components/comment/config.js
15
+ # CALENDAR = 3 calendar-event discussion
15
16
  # WORKSHEET = 7 whole-worksheet discussion
16
17
  # WORKSHEETROW = 8 single record/row discussion
17
18
  # Record-scoped discussions (add/get/delete on a row) are always type 8,
18
19
  # and their sourceId is "{worksheetId}|{rowId}" — NOT the bare rowId.
20
+ # The whole enum lives here because this module owns the Discussion
21
+ # endpoint primitives; other modules (calendar) import from it rather
22
+ # than redeclaring their own copy.
23
+ DISCUSSION_SOURCE_CALENDAR = 3
19
24
  DISCUSSION_SOURCE_WORKSHEET = 7
20
25
  DISCUSSION_SOURCE_WORKSHEET_ROW = 8
21
26
 
@@ -1050,7 +1055,7 @@ def _discussion_source_id(worksheet_id: str, row_id: str) -> str:
1050
1055
  return f"{worksheet_id}|{row_id}"
1051
1056
 
1052
1057
 
1053
- def _ensure_discussion_ok(result: Any, action: str) -> None:
1058
+ def ensure_discussion_ok(result: Any, action: str) -> None:
1054
1059
  """Raise if the Discussion endpoint reported failure.
1055
1060
 
1056
1061
  Discussion endpoints return ``{code: 1, data: ...}`` on success and a
@@ -1138,7 +1143,7 @@ def add_discussion(
1138
1143
  if attachments:
1139
1144
  data["attachments"] = json.dumps(attachments, ensure_ascii=False)
1140
1145
  result = session.api_call("Discussion", "AddDiscussion", data)
1141
- _ensure_discussion_ok(result, "add discussion")
1146
+ ensure_discussion_ok(result, "add discussion")
1142
1147
  return result
1143
1148
 
1144
1149
 
@@ -1157,7 +1162,7 @@ def remove_discussion(
1157
1162
  "Discussion", "RemoveDiscussion",
1158
1163
  {"discussionId": discussion_id, "sourceType": source_type},
1159
1164
  )
1160
- _ensure_discussion_ok(result, "delete discussion")
1165
+ ensure_discussion_ok(result, "delete discussion")
1161
1166
  return result
1162
1167
 
1163
1168
 
@@ -209,6 +209,23 @@ def _iso(deadline: Optional[int]) -> Optional[str]:
209
209
  return datetime.fromtimestamp(deadline, tz=timezone.utc).astimezone().isoformat()
210
210
 
211
211
 
212
+ def _download_permission(ext: str) -> dict[str, Any]:
213
+ """The per-file download permission the holder of the file is sent.
214
+
215
+ Whoever stores an attachment decides, per file, whether anyone but
216
+ the uploader may download it — an omitted flag used to read as "no",
217
+ so a file attached by the CLI answered 权限不足 for every colleague.
218
+ pd-openweb sets it in ``formatResponseData``
219
+ (src/components/UploadFiles/utils.js:202-206): non-pictures get
220
+ ``allowDown: true`` plus an empty ``docVersionID``, pictures get
221
+ neither because the viewer already treats a picture as downloadable
222
+ (UploadFiles/File.js:724). Mirrored verbatim, empty dict included.
223
+ """
224
+ if ext.lower() in _PICTURE_EXTS:
225
+ return {}
226
+ return {"allowDown": True, "docVersionID": ""}
227
+
228
+
212
229
  def upload_bytes(
213
230
  session: Session,
214
231
  *,
@@ -239,7 +256,7 @@ def upload_bytes(
239
256
  up = _post_to_store(upload_host(session), token, content,
240
257
  original_name=original, ext=ext,
241
258
  server_name=server_name)
242
- return {
259
+ descriptor = {
243
260
  "fileID": generate_file_oid(),
244
261
  "fileSize": len(content),
245
262
  "etag": up.get("etag"),
@@ -256,6 +273,8 @@ def upload_bytes(
256
273
  # (public-bucket) address, which does not expire.
257
274
  "urlExpiresAt": _iso(url_expires_at(token.get("url"))),
258
275
  }
276
+ descriptor.update(_download_permission(ext))
277
+ return descriptor
259
278
 
260
279
 
261
280
  def source_descriptors(sources: "list[str] | tuple[str, ...]") -> list[dict[str, str]]:
@@ -324,4 +343,9 @@ def record_attachment_cell(descriptors: list[dict[str, Any]]) -> dict[str, Any]:
324
343
  "index": i,
325
344
  "isEdit": False,
326
345
  })
346
+ # Carried, not recomputed: the descriptor already decided this at
347
+ # upload time — see _download_permission().
348
+ for key in ("allowDown", "docVersionID"):
349
+ if key in d:
350
+ attachments[-1][key] = d[key]
327
351
  return {"attachmentData": [], "attachments": attachments, "knowledgeAtts": []}
@@ -2217,6 +2217,22 @@
2217
2217
  "en": "Calendar and schedule management.",
2218
2218
  "zh-Hans": "日程与日历管理"
2219
2219
  },
2220
+ "calendar add-discussion": {
2221
+ "en": "Post a comment on a calendar event.\n\nEveryone invited to the event can see the comment. Attached files are\ndownloadable by every participant.",
2222
+ "zh-Hans": "在日程下发表评论。\n\n日程的所有参与者都能看到这条评论;随评论上传的附件,参与者均可下载。"
2223
+ },
2224
+ "calendar add-discussion --file": {
2225
+ "en": "Attach a file to the comment (repeatable). Accepts a local path or an http(s) URL.",
2226
+ "zh-Hans": "为评论添加附件(可重复指定)。可传本地路径或 http(s) 链接。"
2227
+ },
2228
+ "calendar add-discussion --message": {
2229
+ "en": "Comment text",
2230
+ "zh-Hans": "评论内容"
2231
+ },
2232
+ "calendar add-discussion --reply-id": {
2233
+ "en": "ID of the comment being replied to",
2234
+ "zh-Hans": "被回复的评论 ID"
2235
+ },
2220
2236
  "calendar add-member": {
2221
2237
  "en": "Add members to a calendar event.",
2222
2238
  "zh-Hans": "向日程事件添加成员"
@@ -4890,4 +4906,4 @@
4890
4906
  "zh-Hans": "应用到现有视图的高层视图 JSON(格式与 'view create --view-spec' 相同)。规格中表达的内容全部更新;其他设置保持不变。字段引用可为名称、别名或 controlId。提供时忽略其他选项"
4891
4907
  }
4892
4908
  }
4893
- }
4909
+ }
@@ -0,0 +1,89 @@
1
+ """`hap calendar add-discussion` — commenting on a calendar event.
2
+
3
+ Until this command existed, attaching a file to a calendar event's
4
+ comment thread had no CLI path at all: the only `Discussion/AddDiscussion`
5
+ caller in the codebase was the worksheet-record one, pinned to
6
+ `sourceType` 8. Anyone who needed it had to assemble the request by hand
7
+ against the raw session — which is how a calendar comment attachment
8
+ first shipped without its download permission (see
9
+ test_upload_allow_down.py).
10
+
11
+ Payload traced from pd-openweb
12
+ `src/pages/calendar/modules/calendarDetail/container/CalendarCommenter.jsx`
13
+ → `src/components/comment/commenter.jsx`, cross-checked against
14
+ `sources/main_api/schemas/MD.Web.Ajax.Request.Discussion.AddDiscussionRequest.json`.
15
+ """
16
+
17
+ from unittest.mock import patch
18
+
19
+ from click.testing import CliRunner
20
+
21
+ from hap_cli.hap_cli import cli
22
+
23
+
24
+ class TestAddDiscussionCli:
25
+
26
+ def test_message_reaches_the_core_call(self):
27
+ runner = CliRunner()
28
+ with patch("hap_cli.context.Context.get_session"), \
29
+ patch("hap_cli.core.calendar_mod.add_discussion",
30
+ return_value={"code": 1, "data": {"discussionId": "d1"}}) as m:
31
+ res = runner.invoke(cli, ["calendar", "add-discussion", "cal1",
32
+ "-m", "会前看下资料"])
33
+ assert res.exit_code == 0, res.output
34
+ assert m.call_args[0][1:] == ("cal1", "会前看下资料")
35
+ assert m.call_args[1]["reply_id"] == ""
36
+ assert m.call_args[1]["attachments"] is None
37
+
38
+ def test_reply_id_is_forwarded(self):
39
+ runner = CliRunner()
40
+ with patch("hap_cli.context.Context.get_session"), \
41
+ patch("hap_cli.core.calendar_mod.add_discussion",
42
+ return_value={"code": 1, "data": {}}) as m:
43
+ res = runner.invoke(cli, ["calendar", "add-discussion", "cal1",
44
+ "-m", "收到", "--reply-id", "d0"])
45
+ assert res.exit_code == 0, res.output
46
+ assert m.call_args[1]["reply_id"] == "d0"
47
+
48
+ def test_files_are_uploaded_and_attached(self, tmp_path):
49
+ runner = CliRunner()
50
+ f = tmp_path / "proto.zip"
51
+ f.write_bytes(b"zip")
52
+ descriptor = {"fileID": "o_1", "fileExt": ".zip", "allowDown": True}
53
+ with patch("hap_cli.context.Context.get_session"), \
54
+ patch("hap_cli.core.upload.upload_files",
55
+ return_value=[descriptor]) as up, \
56
+ patch("hap_cli.core.calendar_mod.add_discussion",
57
+ return_value={"code": 1, "data": {}}) as m:
58
+ res = runner.invoke(cli, ["calendar", "add-discussion", "cal1",
59
+ "-m", "原型包", "-f", str(f)])
60
+ assert res.exit_code == 0, res.output
61
+ assert up.call_args[0][1] == [{"path": str(f)}]
62
+ assert m.call_args[1]["attachments"] == [descriptor]
63
+
64
+ def test_an_http_source_is_fetched_rather_than_opened_as_a_path(self):
65
+ runner = CliRunner()
66
+ with patch("hap_cli.context.Context.get_session"), \
67
+ patch("hap_cli.core.upload.upload_files",
68
+ return_value=[{"fileID": "o_2"}]) as up, \
69
+ patch("hap_cli.core.calendar_mod.add_discussion",
70
+ return_value={"code": 1, "data": {}}):
71
+ res = runner.invoke(cli, ["calendar", "add-discussion", "cal1",
72
+ "-m", "链接", "-f",
73
+ "https://example.com/a.pdf"])
74
+ assert res.exit_code == 0, res.output
75
+ assert up.call_args[0][1] == [{"url": "https://example.com/a.pdf"}]
76
+
77
+ def test_message_is_required(self):
78
+ runner = CliRunner()
79
+ with patch("hap_cli.context.Context.get_session"):
80
+ res = runner.invoke(cli, ["calendar", "add-discussion", "cal1"])
81
+ assert res.exit_code != 0
82
+
83
+ def test_help_is_end_user_copy(self):
84
+ runner = CliRunner()
85
+ res = runner.invoke(cli, ["calendar", "add-discussion", "--help"])
86
+ assert res.exit_code == 0
87
+ for leak in ("AddDiscussion", "Discussion/", "sourceType", "pd-openweb",
88
+ "calendarAppID", "commenter", "endpoint"):
89
+ assert leak not in res.output
@@ -4022,6 +4022,81 @@ class TestCalendar:
4022
4022
  assert body["isAllCalendar"] is True
4023
4023
 
4024
4024
 
4025
+ class TestCalendarDiscussions:
4026
+ """Comments on a calendar event.
4027
+
4028
+ Traced from pd-openweb
4029
+ `src/pages/calendar/modules/calendarDetail/container/CalendarCommenter.jsx`,
4030
+ which feeds the shared `src/components/comment/commenter.jsx`:
4031
+ `sourceType` = SOURCE_TYPE.CALENDAR (3, config.js) and `appId` =
4032
+ `md.global.APPInfo.calendarAppID`. The commenter's `remark` prop is
4033
+ *not* part of the AddDiscussion payload — it is only used when the
4034
+ comment is additionally shared to a post (`postAjax.addPost`), and
4035
+ `AddDiscussionRequest` has no such field. `extendsId` is documented
4036
+ "扩展ID(工作表:appId|viewId)" and the calendar commenter never sets it.
4037
+ """
4038
+
4039
+ _META = {"md.global": {"APPInfo": {"calendarAppID": "cal-app-id"}}}
4040
+
4041
+ def _session(self):
4042
+ session = MagicMock()
4043
+ session.get_global_meta.return_value = self._META
4044
+ session.api_call.return_value = {"code": 1, "data": {"discussionId": "d1"}}
4045
+ return session
4046
+
4047
+ def test_payload_shape(self):
4048
+ from hap_cli.core import calendar_mod
4049
+ session = self._session()
4050
+ calendar_mod.add_discussion(session, "cal1", "看下这个附件")
4051
+ controller, action, payload = session.api_call.call_args[0][:3]
4052
+ assert (controller, action) == ("Discussion", "AddDiscussion")
4053
+ assert payload["sourceId"] == "cal1" # the bare calendar id
4054
+ assert payload["sourceType"] == 3 # SOURCE_TYPE.CALENDAR
4055
+ assert payload["appId"] == "cal-app-id" # APPInfo.calendarAppID
4056
+ assert payload["message"] == "看下这个附件"
4057
+ assert payload["entityType"] == 0
4058
+ # Worksheet-only / post-only fields must not leak in.
4059
+ assert "extendsId" not in payload
4060
+ assert "remark" not in payload
4061
+ assert "fromAppId" not in payload
4062
+
4063
+ def test_reply_id_is_sent_only_when_given(self):
4064
+ from hap_cli.core import calendar_mod
4065
+ session = self._session()
4066
+ calendar_mod.add_discussion(session, "cal1", "收到")
4067
+ assert "replyId" not in session.api_call.call_args[0][2]
4068
+ calendar_mod.add_discussion(session, "cal1", "收到", reply_id="d0")
4069
+ assert session.api_call.call_args[0][2]["replyId"] == "d0"
4070
+
4071
+ def test_attachments_are_json_encoded(self):
4072
+ from hap_cli.core import calendar_mod
4073
+ session = self._session()
4074
+ calendar_mod.add_discussion(
4075
+ session, "cal1", "原型包",
4076
+ attachments=[{"fileID": "o_1", "fileExt": ".zip", "allowDown": True}],
4077
+ )
4078
+ payload = session.api_call.call_args[0][2]
4079
+ sent = json.loads(payload["attachments"])
4080
+ assert sent[0]["fileID"] == "o_1"
4081
+ # The download permission must survive the hop — see
4082
+ # test_upload_allow_down.py for why it exists at all.
4083
+ assert sent[0]["allowDown"] is True
4084
+
4085
+ def test_failure_code_raises_instead_of_reporting_success(self):
4086
+ from hap_cli.core import calendar_mod
4087
+ session = self._session()
4088
+ session.api_call.return_value = {"code": 7} # 没有权限操作
4089
+ with pytest.raises(APIError):
4090
+ calendar_mod.add_discussion(session, "cal1", "hi")
4091
+
4092
+ def test_missing_calendar_app_id_is_an_error_not_a_guess(self):
4093
+ from hap_cli.core import calendar_mod
4094
+ session = self._session()
4095
+ session.get_global_meta.return_value = {"md.global": {"APPInfo": {}}}
4096
+ with pytest.raises(SessionError):
4097
+ calendar_mod.add_discussion(session, "cal1", "hi")
4098
+
4099
+
4025
4100
  # ── Chat Tests ────────────────────────────────────────────────────────────
4026
4101
 
4027
4102
 
@@ -0,0 +1,118 @@
1
+ """Uploaded files must carry their download permission.
2
+
3
+ Reported as "日程/讨论里别人上传的文件下载不了": a colleague opening a file
4
+ attached through the CLI got *您权限不足,无法下载,请联系管理员或文件上传者*,
5
+ while the same file attached from the web UI downloaded fine.
6
+
7
+ The file store does not decide this — the *client* does, per file, when
8
+ it hands the upload result to whatever will hold it. pd-openweb does it
9
+ in ``formatResponseData`` (src/components/UploadFiles/utils.js:202-206):
10
+ every non-picture upload gets ``allowDown: true`` and an empty
11
+ ``docVersionID``; pictures are left alone because the viewer treats them
12
+ as downloadable anyway (UploadFiles/File.js:724). Every attachment
13
+ payload the frontend sends — record comments, record attachment cells —
14
+ is that same object, stringified.
15
+
16
+ The CLI's uploaded-file descriptor never had the field, so anything it
17
+ attached was stored with the permission unset and only the uploader
18
+ could get the bytes back.
19
+ """
20
+
21
+ import json
22
+ from unittest.mock import MagicMock, patch
23
+
24
+ import pytest
25
+
26
+ from hap_cli.core import record as rec_mod
27
+ from hap_cli.core import upload as up_mod
28
+ from hap_cli.core.session import Session
29
+
30
+
31
+ @pytest.fixture
32
+ def session():
33
+ return Session(server_url="https://test.mingdao.com", auth_token="tok123")
34
+
35
+
36
+ class _store:
37
+ """Stand in for the credential + store round trip of an upload."""
38
+
39
+ def __init__(self, ext=".pdf"):
40
+ self.ext = ext
41
+
42
+ def __enter__(self):
43
+ token = {"uptoken": "t", "key": f"doc/20260909/k{self.ext}",
44
+ "serverName": "https://doc.mingdao.com/",
45
+ "fileName": f"k{self.ext}",
46
+ "url": f"https://doc.mingdao.com/doc/k{self.ext}"}
47
+ self._patches = [
48
+ patch.object(up_mod, "get_upload_token", return_value=token),
49
+ patch.object(up_mod, "upload_host",
50
+ return_value="https://upload.qiniup.com/"),
51
+ patch.object(up_mod, "_post_to_store", return_value={
52
+ "key": f"doc/20260909/k{self.ext}", "etag": "e",
53
+ "bucket": "mdoc", "filePath": "doc/20260909/", "fileName": "k",
54
+ "fileExt": self.ext, "originalFileName": "spec",
55
+ }),
56
+ ]
57
+ for p in self._patches:
58
+ p.start()
59
+ return self
60
+
61
+ def __exit__(self, *exc):
62
+ for p in self._patches:
63
+ p.stop()
64
+ return False
65
+
66
+
67
+ class TestDescriptorCarriesDownloadPermission:
68
+
69
+ def test_document_upload_is_downloadable(self, session):
70
+ with _store(".pdf"):
71
+ d = up_mod.upload_bytes(session, content=b"x", name="spec.pdf")
72
+ assert d["allowDown"] is True
73
+ assert d["docVersionID"] == ""
74
+
75
+ def test_archive_upload_is_downloadable(self, session):
76
+ # The reported file was a 13 MB .zip attached to a calendar comment.
77
+ with _store(".zip"):
78
+ d = up_mod.upload_bytes(session, content=b"x", name="proto.zip")
79
+ assert d["allowDown"] is True
80
+
81
+ def test_picture_upload_leaves_the_field_alone(self, session):
82
+ # Mirrors formatResponseData: pictures get neither field.
83
+ with _store(".png"):
84
+ d = up_mod.upload_bytes(session, content=b"x", name="cover.png")
85
+ assert "allowDown" not in d
86
+ assert "docVersionID" not in d
87
+
88
+
89
+ class TestConsumersKeepThePermission:
90
+
91
+ _META = {"md.global": {"APPInfo": {"worksheetRowAppID": "row-app-id"}}}
92
+
93
+ def test_record_comment_attachment_keeps_it(self):
94
+ session = MagicMock()
95
+ session.get_global_meta.return_value = self._META
96
+ session.api_call.return_value = {"code": 1, "data": {"discussionId": "d1"}}
97
+ rec_mod.add_discussion(
98
+ session, "ws1", "row1", "here you go", app_id="app1",
99
+ attachments=[{"fileID": "o_1", "fileExt": ".zip", "allowDown": True,
100
+ "docVersionID": ""}],
101
+ )
102
+ _, _, payload = session.api_call.call_args[0][:3]
103
+ assert json.loads(payload["attachments"])[0]["allowDown"] is True
104
+
105
+ def test_attachment_cell_keeps_it(self):
106
+ cell = up_mod.record_attachment_cell([
107
+ {"fileID": "o_1", "fileExt": ".zip", "originalFileName": "proto",
108
+ "allowDown": True, "docVersionID": ""},
109
+ ])
110
+ assert cell["attachments"][0]["allowDown"] is True
111
+ assert cell["attachments"][0]["docVersionID"] == ""
112
+
113
+ def test_attachment_cell_leaves_a_picture_alone(self):
114
+ cell = up_mod.record_attachment_cell([
115
+ {"fileID": "o_2", "fileExt": ".png", "originalFileName": "cover"},
116
+ ])
117
+ assert "allowDown" not in cell["attachments"][0]
118
+ assert "docVersionID" not in cell["attachments"][0]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hap-cli
3
- Version: 0.8.29
3
+ Version: 0.8.31
4
4
  Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -264,6 +264,7 @@ discover every subcommand.
264
264
  | `calendar list / get / create / update / delete` | Event lifecycle |
265
265
  | `calendar categories` | Built-in categories |
266
266
  | `calendar add-member / remove-member` | Membership |
267
+ | `calendar add-discussion` | Comment on an event (attachments supported) |
267
268
 
268
269
  ### chat — chat & messaging
269
270