hap-cli 0.8.20__tar.gz → 0.8.21__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 (264) hide show
  1. {hap_cli-0.8.20 → hap_cli-0.8.21}/PKG-INFO +2 -1
  2. {hap_cli-0.8.20/hap_cli → hap_cli-0.8.21}/README.md +1 -0
  3. {hap_cli-0.8.20 → hap_cli-0.8.21/hap_cli}/README.md +1 -0
  4. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/README_CN.md +1 -0
  5. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/__init__.py +1 -1
  6. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/config_cmd.py +44 -0
  7. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/action_spec_adapter.py +9 -3
  8. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/flow_node.py +41 -18
  9. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/record.py +31 -2
  10. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/session.py +9 -1
  11. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/update.py +39 -0
  12. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/upload.py +31 -13
  13. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/worksheet.py +38 -3
  14. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/i18n.py +49 -0
  15. hap_cli-0.8.21/hap_cli/tests/test_content_language.py +120 -0
  16. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_core.py +22 -6
  17. hap_cli-0.8.21/hap_cli/tests/test_no_workflow_unsupported.py +92 -0
  18. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +20 -10
  19. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_record_v3_semantic_cli.py +3 -1
  20. hap_cli-0.8.21/hap_cli/tests/test_update_mode.py +90 -0
  21. hap_cli-0.8.21/hap_cli/tests/test_upload_host_resolution.py +93 -0
  22. hap_cli-0.8.21/hap_cli/tests/test_workflow_read_write_keys.py +71 -0
  23. hap_cli-0.8.21/hap_cli/tests/test_worksheet_create_section.py +91 -0
  24. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +4 -0
  25. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli.egg-info/PKG-INFO +2 -1
  26. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli.egg-info/SOURCES.txt +6 -0
  27. {hap_cli-0.8.20 → hap_cli-0.8.21}/MANIFEST.in +0 -0
  28. {hap_cli-0.8.20 → hap_cli-0.8.21}/app_live_tests/__init__.py +0 -0
  29. {hap_cli-0.8.20 → hap_cli-0.8.21}/app_live_tests/__main__.py +0 -0
  30. {hap_cli-0.8.20 → hap_cli-0.8.21}/app_live_tests/config.py +0 -0
  31. {hap_cli-0.8.20 → hap_cli-0.8.21}/app_live_tests/harness.py +0 -0
  32. {hap_cli-0.8.20 → hap_cli-0.8.21}/app_live_tests/smoke.py +0 -0
  33. {hap_cli-0.8.20 → hap_cli-0.8.21}/app_live_tests/state.py +0 -0
  34. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/__main__.py +0 -0
  35. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/__init__.py +0 -0
  36. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/app_cmd.py +0 -0
  37. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/app_creator_cmd.py +0 -0
  38. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/app_editor_cmd.py +0 -0
  39. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/auth_cmd.py +0 -0
  40. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/calendar_cmd.py +0 -0
  41. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/chart_cmd.py +0 -0
  42. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/chat_cmd.py +0 -0
  43. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/chatbot_cmd.py +0 -0
  44. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/contact_cmd.py +0 -0
  45. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/department_cmd.py +0 -0
  46. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/group_cmd.py +0 -0
  47. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/icon_cmd.py +0 -0
  48. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/instance_cmd.py +0 -0
  49. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/node_cmd.py +0 -0
  50. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/optionset_cmd.py +0 -0
  51. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/page_cmd.py +0 -0
  52. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/post_cmd.py +0 -0
  53. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/record_cmd.py +0 -0
  54. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/region_cmd.py +0 -0
  55. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/role_cmd.py +0 -0
  56. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/update_cmd.py +0 -0
  57. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/upload_cmd.py +0 -0
  58. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/v3_registry.py +0 -0
  59. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/workflow_cmd.py +0 -0
  60. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/commands/worksheet_cmd.py +0 -0
  61. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/context.py +0 -0
  62. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/__init__.py +0 -0
  63. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app.py +0 -0
  64. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/__init__.py +0 -0
  65. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
  66. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
  67. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
  68. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/charts.py +0 -0
  69. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/cleanup.py +0 -0
  70. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/compiler.py +0 -0
  71. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/config.py +0 -0
  72. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/design/design.schema.json +0 -0
  73. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/errors.py +0 -0
  74. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
  75. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/executor.py +0 -0
  76. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/fields.py +0 -0
  77. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/hap.py +0 -0
  78. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/merge.py +0 -0
  79. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/recording/__init__.py +0 -0
  80. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/recording/console.py +0 -0
  81. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
  82. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/recording/mirror.py +0 -0
  83. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/recording/report.py +0 -0
  84. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/schema.py +0 -0
  85. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
  86. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/__init__.py +0 -0
  87. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/cli.py +0 -0
  88. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/executor.py +0 -0
  89. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
  90. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
  91. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
  92. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
  93. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/template.py +0 -0
  94. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/selftest.py +0 -0
  95. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/smoke.py +0 -0
  96. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/steps.py +0 -0
  97. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/store.py +0 -0
  98. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/validate.py +0 -0
  99. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
  100. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/auth.py +0 -0
  101. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/calendar_mod.py +0 -0
  102. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/chart.py +0 -0
  103. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/chat.py +0 -0
  104. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/chatbot.py +0 -0
  105. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/cli_access.py +0 -0
  106. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/contact.py +0 -0
  107. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/control_type_codes.py +0 -0
  108. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/department.py +0 -0
  109. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/__init__.py +0 -0
  110. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  111. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  112. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/apply.py +0 -0
  113. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/componentlower.py +0 -0
  114. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/editspec.py +0 -0
  115. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/errors.py +0 -0
  116. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/fieldlower.py +0 -0
  117. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  118. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/models.py +0 -0
  119. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/ops.py +0 -0
  120. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/planner.py +0 -0
  121. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/editor/reader.py +0 -0
  122. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/field_normalizer.py +0 -0
  123. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/field_spec_adapter.py +0 -0
  124. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/filter_translator.py +0 -0
  125. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/global_meta.py +0 -0
  126. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/group.py +0 -0
  127. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/icon_index.py +0 -0
  128. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/instance.py +0 -0
  129. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/logger.py +0 -0
  130. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/optionset.py +0 -0
  131. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/page.py +0 -0
  132. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/post.py +0 -0
  133. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/response_crypto.py +0 -0
  134. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/role.py +0 -0
  135. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/role_perm_builder.py +0 -0
  136. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/token_crypto.py +0 -0
  137. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/v3/__init__.py +0 -0
  138. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/v3/dispatcher.py +0 -0
  139. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/v3/render.py +0 -0
  140. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/v3/schema.py +0 -0
  141. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/view_spec_adapter.py +0 -0
  142. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/workflow.py +0 -0
  143. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/workflow_node_dsl.py +0 -0
  144. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/core/worksheet_templates.py +0 -0
  145. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/hap_cli.py +0 -0
  146. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/locale/__init__.py +0 -0
  147. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/locale/messages.json +0 -0
  148. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/locale/messages.schema.json +0 -0
  149. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/__init__.py +0 -0
  150. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/conftest.py +0 -0
  151. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_app_creator.py +0 -0
  152. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_app_editor.py +0 -0
  153. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_app_export_usage.py +0 -0
  154. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_auth_prerelease.py +0 -0
  155. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_bug_036430.py +0 -0
  156. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_bug_036750.py +0 -0
  157. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_chart.py +0 -0
  158. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_cli_access.py +0 -0
  159. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_config_cmd.py +0 -0
  160. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_control_lookup_guard.py +0 -0
  161. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
  162. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_full_e2e.py +0 -0
  163. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_global_meta.py +0 -0
  164. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_i18n.py +0 -0
  165. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_instance_history_cli.py +0 -0
  166. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_integration.py +0 -0
  167. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_integration_approval.py +0 -0
  168. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_integration_approval_actions.py +0 -0
  169. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_integration_calendar.py +0 -0
  170. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_integration_destructive.py +0 -0
  171. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_integration_misc.py +0 -0
  172. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_integration_post.py +0 -0
  173. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_integration_social.py +0 -0
  174. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_integration_v3.py +0 -0
  175. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_integration_workflow.py +0 -0
  176. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  177. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
  178. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_multi_profile.py +0 -0
  179. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
  180. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_org_id_cli.py +0 -0
  181. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_org_id_docs.py +0 -0
  182. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_parameter_conventions.py +0 -0
  183. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  184. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_record_field_alias.py +0 -0
  185. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
  186. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_semantic_types_registry.py +0 -0
  187. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_update.py +0 -0
  188. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  189. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  190. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  191. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_v3_session.py +0 -0
  192. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  193. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
  194. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_workflow_list_cli.py +0 -0
  195. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
  196. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  197. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/utils/__init__.py +0 -0
  198. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/utils/formatting.py +0 -0
  199. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/utils/options.py +0 -0
  200. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli/utils/parameter_mapping.py +0 -0
  201. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli.egg-info/dependency_links.txt +0 -0
  202. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli.egg-info/entry_points.txt +0 -0
  203. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli.egg-info/requires.txt +0 -0
  204. {hap_cli-0.8.20 → hap_cli-0.8.21}/hap_cli.egg-info/top_level.txt +0 -0
  205. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/__init__.py +0 -0
  206. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/__main__.py +0 -0
  207. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/_wftest.py +0 -0
  208. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/charts.py +0 -0
  209. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/cleanup.py +0 -0
  210. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/compiler.py +0 -0
  211. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/config.py +0 -0
  212. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/errors.py +0 -0
  213. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/executor.py +0 -0
  214. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/fields.py +0 -0
  215. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/hap.py +0 -0
  216. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/recording/__init__.py +0 -0
  217. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/recording/console.py +0 -0
  218. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/recording/jsonl.py +0 -0
  219. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/recording/mirror.py +0 -0
  220. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/recording/report.py +0 -0
  221. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/schema.py +0 -0
  222. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/seed/__init__.py +0 -0
  223. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/seed/cli.py +0 -0
  224. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/seed/executor.py +0 -0
  225. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/seed/template.py +0 -0
  226. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/selftest.py +0 -0
  227. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/smoke.py +0 -0
  228. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/step.py +0 -0
  229. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/steps.py +0 -0
  230. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/store.py +0 -0
  231. {hap_cli-0.8.20 → hap_cli-0.8.21}/live_tests/workflow_dsl.py +0 -0
  232. {hap_cli-0.8.20 → hap_cli-0.8.21}/setup.cfg +0 -0
  233. {hap_cli-0.8.20 → hap_cli-0.8.21}/setup.py +0 -0
  234. {hap_cli-0.8.20 → hap_cli-0.8.21}/skills/cli/hap-cli-app-editor/scripts/editspec/component.schema.json +0 -0
  235. {hap_cli-0.8.20 → hap_cli-0.8.21}/skills/cli/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +0 -0
  236. {hap_cli-0.8.20 → hap_cli-0.8.21}/skills/cli/hap-cli-app-editor/scripts/editspec/envelope.schema.json +0 -0
  237. {hap_cli-0.8.20 → hap_cli-0.8.21}/skills/cli/hap-cli-app-editor/scripts/editspec/field.schema.json +0 -0
  238. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/INDEX.json +0 -0
  239. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  240. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  241. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/create_optionset.yaml +0 -0
  242. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/create_role.yaml +0 -0
  243. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  244. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/delete_record.yaml +0 -0
  245. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/delete_role.yaml +0 -0
  246. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  247. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_app_info.yaml +0 -0
  248. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  249. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  250. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  251. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_details.yaml +0 -0
  252. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  253. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_list.yaml +0 -0
  254. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  255. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  256. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  257. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  258. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_regions.yaml +0 -0
  259. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_role_details.yaml +0 -0
  260. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/get_role_list.yaml +0 -0
  261. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  262. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  263. {hap_cli-0.8.20 → hap_cli-0.8.21}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  264. {hap_cli-0.8.20 → hap_cli-0.8.21}/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.20
3
+ Version: 0.8.21
4
4
  Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -313,6 +313,7 @@ discover every subcommand.
313
313
  | `config show` | Display the current configuration summary |
314
314
  | `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` completion |
315
315
  | `config language [LANG]` | Switch UI language (`en` / `zh-Hans` / `zh-Hant` / `ja`) |
316
+ | `config update [auto\|notify\|off]` | Self-upgrade mode; `notify` only reports, `off` never checks |
316
317
  | `config log on / off / status` | Toggle and inspect API logging |
317
318
  | `config log level LEVEL` | `DEBUG / INFO / WARNING / ERROR / CRITICAL` |
318
319
  | `config log view [--lines N] [--no-follow]` | Tail the log file |
@@ -284,6 +284,7 @@ discover every subcommand.
284
284
  | `config show` | Display the current configuration summary |
285
285
  | `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` completion |
286
286
  | `config language [LANG]` | Switch UI language (`en` / `zh-Hans` / `zh-Hant` / `ja`) |
287
+ | `config update [auto\|notify\|off]` | Self-upgrade mode; `notify` only reports, `off` never checks |
287
288
  | `config log on / off / status` | Toggle and inspect API logging |
288
289
  | `config log level LEVEL` | `DEBUG / INFO / WARNING / ERROR / CRITICAL` |
289
290
  | `config log view [--lines N] [--no-follow]` | Tail the log file |
@@ -284,6 +284,7 @@ discover every subcommand.
284
284
  | `config show` | Display the current configuration summary |
285
285
  | `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` completion |
286
286
  | `config language [LANG]` | Switch UI language (`en` / `zh-Hans` / `zh-Hant` / `ja`) |
287
+ | `config update [auto\|notify\|off]` | Self-upgrade mode; `notify` only reports, `off` never checks |
287
288
  | `config log on / off / status` | Toggle and inspect API logging |
288
289
  | `config log level LEVEL` | `DEBUG / INFO / WARNING / ERROR / CRITICAL` |
289
290
  | `config log view [--lines N] [--no-follow]` | Tail the log file |
@@ -258,6 +258,7 @@ hap --json worksheet record list WORKSHEET_ID
258
258
  | `config show` | 显示 CLI 配置摘要 |
259
259
  | `config completion [bash\|zsh\|fish] [--install]` | 启用 `<Tab>` 补全 |
260
260
  | `config language [LANG]` | 切换显示语言(`en` / `zh-Hans` / `zh-Hant` / `ja`) |
261
+ | `config update [auto\|notify\|off]` | 自动升级开关;`notify` 只提示不安装,`off` 不检查 |
261
262
  | `config log on / off / status` | 日志开关与状态 |
262
263
  | `config log level LEVEL` | 设置日志级别(`DEBUG/INFO/WARNING/ERROR/CRITICAL`) |
263
264
  | `config log view [--lines N] [--no-follow]` | 跟踪日志文件 |
@@ -1,3 +1,3 @@
1
1
  """CLI harness for Mingdao HAP (hap-cli)."""
2
2
 
3
- __version__ = "0.8.20"
3
+ __version__ = "0.8.21"
@@ -122,6 +122,8 @@ def config_completion(shell, install):
122
122
  @pass_context
123
123
  def config_show(ctx):
124
124
  """Display current harness configuration."""
125
+ from hap_cli.core.update import get_update_mode
126
+
125
127
  log_cfg = load_logging_config()
126
128
  data = {
127
129
  "logging": {
@@ -129,6 +131,7 @@ def config_show(ctx):
129
131
  "level": log_cfg["level"],
130
132
  "file": str(resolve_log_path(log_cfg)),
131
133
  },
134
+ "update": get_update_mode(),
132
135
  }
133
136
 
134
137
  def _human(d):
@@ -136,6 +139,7 @@ def config_show(ctx):
136
139
  click.echo(f" enabled : {d['logging']['enabled']}")
137
140
  click.echo(f" level : {d['logging']['level']}")
138
141
  click.echo(f" file : {d['logging']['file']}")
142
+ click.echo(f"update : {d['update']}")
139
143
 
140
144
  ctx.output(data, _human)
141
145
 
@@ -205,6 +209,46 @@ def log_status(ctx):
205
209
  _echo_log_status(ctx, cfg)
206
210
 
207
211
 
212
+ # ── `hap config update` ───────────────────────────────────────────────────
213
+
214
+
215
+ @config.command("update")
216
+ @click.argument(
217
+ "mode", required=False, type=click.Choice(["auto", "notify", "off"]),
218
+ )
219
+ @pass_context
220
+ def config_update(ctx, mode):
221
+ """Control whether hap-cli upgrades itself.
222
+
223
+ \b
224
+ auto check hourly and install new versions (default)
225
+ notify check hourly and tell you, but never install
226
+ off never check
227
+
228
+ Use ``notify`` or ``off`` to pin the version in scripted or
229
+ production environments. Without an argument, shows the current
230
+ setting. ``hap update`` still upgrades on demand in every mode.
231
+ """
232
+ from hap_cli.core.session import load_config, save_config
233
+ from hap_cli.core.update import get_update_mode
234
+
235
+ if mode is None:
236
+ current = get_update_mode()
237
+ if ctx.json_mode:
238
+ from hap_cli.utils.formatting import output_json
239
+ output_json({"update": current})
240
+ else:
241
+ click.echo(f"Self-update: {current}")
242
+ return
243
+
244
+ save_config({"update": mode})
245
+ if ctx.json_mode:
246
+ from hap_cli.utils.formatting import output_json
247
+ output_json({"status": "ok", "update": mode})
248
+ else:
249
+ click.echo(f"Self-update set to: {mode}")
250
+
251
+
208
252
  # ── `hap config language` ─────────────────────────────────────────────────
209
253
 
210
254
 
@@ -110,9 +110,15 @@ def build_button_payload(spec: dict[str, Any]) -> dict[str, Any]:
110
110
  # in the UI; for the dedicated 二次确认 button use clickType 2.
111
111
  if out["clickType"] == 1 and confirm:
112
112
  out["clickType"] = 2
113
- out["confirmMsg"] = confirm_msg or "你确认执行此操作吗?"
114
- out["sureName"] = spec.get("sureName", "确认")
115
- out["cancelName"] = spec.get("cancelName", "取消")
113
+ # Dialog text is stored on the button itself, so it must be
114
+ # written in the application's language, not ours. Follows the
115
+ # active CLI language; an explicit value always wins.
116
+ from hap_cli.i18n import content_default
117
+
118
+ out["confirmMsg"] = confirm_msg or content_default("button.confirmMsg")
119
+ out["sureName"] = spec.get("sureName") or content_default("button.sure")
120
+ out["cancelName"] = (
121
+ spec.get("cancelName") or content_default("button.cancel"))
116
122
 
117
123
  # enableWhen → filters (reuse the shared filter translator).
118
124
  enable_when = spec.get("enableWhen")
@@ -394,27 +394,50 @@ def save_node(
394
394
  if select_node_id:
395
395
  data["selectNodeId"] = select_node_id
396
396
  data.update(config)
397
- # Branch items (flowNodeType 2) have an asymmetric read/write key for
398
- # their filter: getNodeDetail returns it as ``conditions`` (see
399
- # pd-openweb Branch/index.jsx, which reads ``result.conditions``), but
400
- # saveNode only persists it under ``operateCondition`` (the same file
401
- # sends ``saveNode({ operateCondition: data })``). The value shape is
402
- # identical. Saving with the read-side ``conditions`` key is silently
403
- # ignored by the server — the save still reports success but the
404
- # condition is dropped. Accept the read key so copying a node's
405
- # read structure straight back round-trips correctly.
406
- if flow_node_type == 2 and "conditions" in data:
407
- if "operateCondition" not in data:
408
- data["operateCondition"] = data["conditions"]
409
- logger.info(
410
- "save_node: branch item %s — mapped read-side key "
411
- "'conditions' to write-side 'operateCondition'", node_id)
412
- # ``conditions`` is not a saveNode key; drop it so it doesn't ride
413
- # along as dead weight.
414
- data.pop("conditions", None)
397
+ _map_read_side_keys(data, node_id)
415
398
  return session.workflow_call("flowNode/saveNode", data)
416
399
 
417
400
 
401
+ # Node config keys whose read form differs from their write form. Reading
402
+ # a node, editing one value and writing it back is the natural way to work
403
+ # with these APIs, but the server ignores the read-side key on save — the
404
+ # call still reports success while the value is dropped.
405
+ #
406
+ # The filter is asymmetric on every filtering node in pd-openweb, not just
407
+ # on branches: the component holds it as ``conditions`` and posts it as
408
+ # ``operateCondition``, with an identical value shape.
409
+ # Branch/index.jsx:184 saveNode({ operateCondition: conditions })
410
+ # GetMoreRecord/index.jsx:184 saveNode({ operateCondition: conditions })
411
+ # Search/index.jsx:190 saveNode({ operateCondition: conditions })
412
+ # FindSystem/index.jsx:110 saveNode({ operateCondition: conditions })
413
+ # ``conditions`` is never itself a saveNode key, so translating it is safe
414
+ # for any node type.
415
+ _READ_TO_WRITE_KEYS = {
416
+ "conditions": "operateCondition",
417
+ }
418
+
419
+
420
+ def _map_read_side_keys(data: dict[str, Any], node_id: str) -> None:
421
+ """Rewrite read-side config keys to the keys saveNode persists.
422
+
423
+ An explicit write-side key always wins; the read-side key is then
424
+ dropped either way so it cannot ride along as dead weight.
425
+ """
426
+ for read_key, write_key in _READ_TO_WRITE_KEYS.items():
427
+ if read_key not in data:
428
+ continue
429
+ if write_key in data:
430
+ logger.info(
431
+ "save_node: node %s carries both '%s' and '%s'; keeping "
432
+ "the write-side value", node_id, read_key, write_key)
433
+ else:
434
+ data[write_key] = data[read_key]
435
+ logger.info(
436
+ "save_node: node %s — mapped read-side key '%s' to "
437
+ "write-side '%s'", node_id, read_key, write_key)
438
+ data.pop(read_key, None)
439
+
440
+
418
441
  # ── Testing ──────────────────────────────────────────────────────────────
419
442
 
420
443
 
@@ -831,6 +831,35 @@ def get_record(
831
831
  return session.api_call("Worksheet", "GetRowDetail", data)
832
832
 
833
833
 
834
+ # The main-site record endpoints have no workflow-suppression parameter.
835
+ # pd-openweb documents UpdateWorksheetRow as worksheetId / rowId /
836
+ # newOldControl / viewId / instanceId / workId / btnId / btnRemark /
837
+ # btnWorksheetId / btnRowId / pushUniqueId / rowStatus / updateType
838
+ # (src/api/worksheet.js:535-548), and AddWorksheetRow likewise has none.
839
+ # ``triggerWorkflow`` belongs to the open API v2 (worksheet/addRow,
840
+ # worksheet/editRow) — a different transport. We used to send it anyway;
841
+ # the main site accepted the body and ignored the flag, so the workflows
842
+ # ran while the caller believed they had been suppressed (confirmed live:
843
+ # a record-update flow stamped its field on an update sent with
844
+ # triggerWorkflow: false).
845
+ #
846
+ # Record writes cannot simply move to the open API: it silently drops
847
+ # RELATE_SHEET / SUB_LIST cells. Until there is a real way to suppress,
848
+ # refusing is the only honest answer — anyone reaching for this is trying
849
+ # to edit data *safely*, and quietly doing the unsafe thing is worse than
850
+ # failing.
851
+ def _reject_workflow_suppression(trigger_workflow: bool) -> None:
852
+ if trigger_workflow:
853
+ return
854
+ raise ValueError(
855
+ "Workflows cannot be suppressed for this write — they will run "
856
+ "whatever this command does, so the option is refused rather than "
857
+ "ignored. To edit records without them, turn the workflow off "
858
+ "first (hap workflow publish <id> --disable), make the change, "
859
+ "then turn it back on."
860
+ )
861
+
862
+
834
863
  def create_record(
835
864
  session: Session,
836
865
  worksheet_id: str,
@@ -848,13 +877,13 @@ def create_record(
848
877
  Returns:
849
878
  Created record info
850
879
  """
880
+ _reject_workflow_suppression(trigger_workflow)
851
881
  return session.api_call(
852
882
  "Worksheet",
853
883
  "AddWorksheetRow",
854
884
  {
855
885
  "worksheetId": worksheet_id,
856
886
  "receiveControls": controls,
857
- "triggerWorkflow": trigger_workflow,
858
887
  },
859
888
  )
860
889
 
@@ -878,6 +907,7 @@ def update_record(
878
907
  Returns:
879
908
  Update result
880
909
  """
910
+ _reject_workflow_suppression(trigger_workflow)
881
911
  return session.api_call(
882
912
  "Worksheet",
883
913
  "UpdateWorksheetRow",
@@ -885,7 +915,6 @@ def update_record(
885
915
  "worksheetId": worksheet_id,
886
916
  "rowId": row_id,
887
917
  "newOldControl": controls,
888
- "triggerWorkflow": trigger_workflow,
889
918
  },
890
919
  )
891
920
 
@@ -360,11 +360,19 @@ class Session:
360
360
  from hap_cli.core import cli_access
361
361
 
362
362
  cli_access.ensure_before_request(self, url)
363
+ from hap_cli.i18n import get_active_language
364
+
363
365
  headers = {
364
366
  "Content-Type": "application/json",
365
367
  "Authorization": f"md_pss_id {self.auth_token}",
366
368
  "X-Requested-With": "XMLHttpRequest",
367
369
  "User-Agent": hap_user_agent(),
370
+ # Give the server the language context it otherwise has to
371
+ # guess at. Defaults the server fills in (hints, template
372
+ # text) were coming back in Simplified Chinese for
373
+ # all-English applications because API requests carried no
374
+ # language at all — the browser always sends one.
375
+ "Accept-Language": get_active_language(),
368
376
  }
369
377
  # pd-openweb sends the caller's own accountId on every request
370
378
  # (``src/common/global.js`` ``disposeRequestParams``). Some
@@ -1005,7 +1013,7 @@ PROFILE_FIELDS = (
1005
1013
  # are properties of the human at the terminal / the install, not of any
1006
1014
  # one account, so logger and the language commands keep reading/writing
1007
1015
  # them via the plain load_config()/save_config() top-level accessors.
1008
- GLOBAL_FIELDS = ("logging", "language", "language_source")
1016
+ GLOBAL_FIELDS = ("logging", "language", "language_source", "update")
1009
1017
 
1010
1018
 
1011
1019
  def _empty_v2_doc() -> dict:
@@ -25,6 +25,8 @@ from typing import Any, Optional
25
25
 
26
26
  import requests
27
27
 
28
+ from hap_cli.core.session import load_config
29
+
28
30
  logger = logging.getLogger("hap_cli.update")
29
31
 
30
32
  PYPI_PACKAGE = "hap-cli"
@@ -172,6 +174,27 @@ def save_state(state: dict) -> None:
172
174
  # ── Background check orchestration ───────────────────────────────────────
173
175
 
174
176
 
177
+ UPDATE_MODES = ("auto", "notify", "off")
178
+ DEFAULT_UPDATE_MODE = "auto"
179
+
180
+
181
+ def get_update_mode() -> str:
182
+ """Resolve the self-update mode from config.
183
+
184
+ ``auto`` checks and upgrades, ``notify`` checks and reports without
185
+ upgrading, ``off`` does not check at all. Anything unrecognised — or
186
+ no setting — is ``auto``, the behaviour that shipped before this was
187
+ configurable.
188
+ """
189
+ try:
190
+ cfg = load_config() or {}
191
+ except Exception as e:
192
+ logger.warning("could not read the update mode from config: %s", e)
193
+ return DEFAULT_UPDATE_MODE
194
+ mode = cfg.get("update")
195
+ return mode if mode in UPDATE_MODES else DEFAULT_UPDATE_MODE
196
+
197
+
175
198
  def maybe_spawn_background_check() -> None:
176
199
  """Spawn a detached background update check if it is due.
177
200
 
@@ -179,6 +202,8 @@ def maybe_spawn_background_check() -> None:
179
202
  *before* spawning so rapid consecutive commands cannot stack
180
203
  duplicate children — at worst a failed child costs one interval.
181
204
  """
205
+ if get_update_mode() == "off":
206
+ return
182
207
  kind = get_install_kind()
183
208
  if kind in ("editable", "unknown"):
184
209
  return
@@ -246,6 +271,15 @@ def run_background_update() -> None:
246
271
  save_state(state)
247
272
  return
248
273
 
274
+ if get_update_mode() == "notify":
275
+ # Report it and stop — the point of this mode is that the
276
+ # installed version never changes underneath a running script.
277
+ state["last_result"] = "update_available"
278
+ state["notified"] = False
279
+ save_state(state)
280
+ logger.info("update to %s available (notify-only mode)", latest)
281
+ return
282
+
249
283
  ok, output = perform_upgrade(get_install_kind())
250
284
  if ok:
251
285
  state["last_result"] = "upgraded"
@@ -280,6 +314,11 @@ def consume_startup_notice() -> Optional[str]:
280
314
  f"Automatic upgrade to {latest} failed — "
281
315
  "run `hap update` to upgrade manually."
282
316
  )
317
+ elif result == "update_available":
318
+ latest = state.get("latest_version") or "a newer version"
319
+ msg = (
320
+ f"hap-cli {latest} is available — run `hap update` to install it."
321
+ )
283
322
  else:
284
323
  return None
285
324
 
@@ -30,12 +30,11 @@ from urllib.parse import urlparse
30
30
 
31
31
  import requests
32
32
 
33
- from hap_cli.core.session import Session
33
+ from hap_cli.core.session import Session, SessionError
34
34
  logger = logging.getLogger("hap_cli.upload")
35
35
 
36
- # Public-SaaS qiniu upload endpoint. Self-hosted overrides this via
37
- # FileStoreConfig.uploadHost in Global/GetGlobalMeta.
38
- _PUBLIC_UPLOAD_HOST = "https://upload.qiniup.com/"
36
+ # The upload endpoint is always read from Global/GetGlobalMeta — there is
37
+ # deliberately no constant to fall back to, see upload_host().
39
38
 
40
39
  # bucket ids (pd-openweb kc/utils): 4=picture(mdpic), 3=document(mdoc).
41
40
  _PICTURE_EXTS = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg",
@@ -67,17 +66,36 @@ def _bucket_for_ext(ext: str) -> int:
67
66
 
68
67
 
69
68
  def upload_host(session: Session) -> str:
70
- """Resolve the file-store upload endpoint. Self-hosted environments
71
- publish it as ``FileStoreConfig.uploadHost``; the public SaaS leaves
72
- it empty and uses qiniu directly."""
73
- try:
74
- meta = session.get_global_meta()
75
- host = (meta.get("FileStoreConfig") or {}).get("uploadHost")
69
+ """Resolve this environment's file-store upload endpoint.
70
+
71
+ Global/GetGlobalMeta answers ``{config: {...}, "md.global": {...}}``
72
+ and publishes the file store under ``md.global.FileStoreConfig`` —
73
+ the same object every frontend upload posts to
74
+ (``md.global.FileStoreConfig.uploadHost``, pd-openweb
75
+ src/library/plupload/createUploader.js:43 and 12 sibling call sites,
76
+ none of which has a fallback). We used to read ``FileStoreConfig``
77
+ at the top level, so it was never found on any environment and a
78
+ hardcoded public-qiniu constant was used instead. That constant
79
+ equals the public SaaS's own uploadHost, which hid the bug there and
80
+ broke every environment with its own file store: the bytes went to
81
+ public qiniu while the upload token came from the environment's
82
+ store, so the store answered 401 with nothing to point at.
83
+
84
+ A missing host is an error rather than a guess — guessing is what
85
+ turned a routing bug into an unexplainable 401.
86
+ """
87
+ meta = session.get_global_meta()
88
+ for scope in (meta.get("md.global"), meta):
89
+ if not isinstance(scope, dict):
90
+ continue
91
+ host = (scope.get("FileStoreConfig") or {}).get("uploadHost")
76
92
  if host:
77
93
  return host
78
- except Exception as e: # non-fatal: fall back to the public endpoint
79
- logger.warning("uploadHost lookup via GetGlobalMeta failed: %s", e)
80
- return _PUBLIC_UPLOAD_HOST
94
+ raise SessionError(
95
+ "This server did not publish a file upload address, so the file "
96
+ "cannot be uploaded. Check that you are signed in to the right "
97
+ "server, then try again."
98
+ )
81
99
 
82
100
 
83
101
  def get_upload_token(
@@ -217,6 +217,37 @@ FIELD_TYPES = {
217
217
  # ── Worksheet CRUD ───────────────────────────────────────────────────────
218
218
 
219
219
 
220
+ def _first_app_section_id(session: Session, app_id: str) -> str:
221
+ """Return the application's first sidebar section id.
222
+
223
+ AddWorkSheet files the new worksheet under a section, and some
224
+ deployments reject the call with a bare "Invalid Parameters" when
225
+ ``appSectionId`` is absent instead of defaulting to the first one.
226
+ Every application has at least one section and the UI always creates
227
+ into a specific one, so resolving it here spares the caller a lookup
228
+ they have no reason to perform.
229
+ """
230
+ from hap_cli.core import app as _app
231
+ try:
232
+ detail = _app.get_app_detail(session, app_id)
233
+ except Exception as exc:
234
+ logger.warning("section lookup for app %s failed: %s", app_id, exc)
235
+ raise ValueError(
236
+ f"Could not read the sections of application {app_id}, so there "
237
+ "is nowhere to put the new worksheet. Check the application id, "
238
+ "or name a section with --section-id."
239
+ ) from exc
240
+ sections = (detail or {}).get("sections") or []
241
+ for sec in sections:
242
+ sid = sec.get("appSectionId")
243
+ if sid:
244
+ return sid
245
+ raise ValueError(
246
+ f"Application {app_id} has no sections to create the worksheet in. "
247
+ "Add a section first, or name one with --section-id."
248
+ )
249
+
250
+
220
251
  def create_worksheet(
221
252
  session: Session,
222
253
  app_id: str,
@@ -266,8 +297,9 @@ def create_worksheet(
266
297
  }
267
298
  if project_id:
268
299
  data["projectId"] = project_id
269
- if section_id:
270
- data["appSectionId"] = section_id # API requires appSectionId
300
+ if not section_id:
301
+ section_id = _first_app_section_id(session, app_id)
302
+ data["appSectionId"] = section_id # API requires appSectionId
271
303
  data["iconUrl"] = icon_url or (
272
304
  f"https://fp1.mingdaoyun.cn/customIcon/{icon}.svg"
273
305
  )
@@ -878,7 +910,10 @@ def bind_sub_list_to_back_relate(
878
910
  except Exception:
879
911
  synth_name = None
880
912
  if not synth_name:
881
- synth_name = "关联记录"
913
+ # Becomes a visible column header in the customer's app, so it
914
+ # follows the active language rather than being hardcoded.
915
+ from hap_cli.i18n import content_default
916
+ synth_name = content_default("control.backRelation")
882
917
  from hap_cli.core import worksheet_templates as _wt
883
918
  new_rel = _wt.build_control(
884
919
  "RELATE_SHEET",
@@ -51,6 +51,55 @@ LANG_LABELS = {
51
51
  _CONFIG_FILE = Path.home() / ".hap-cli" / "config.json"
52
52
  _CATALOG_FILE = Path(__file__).parent / "locale" / "messages.json"
53
53
 
54
+ # ── Content defaults ─────────────────────────────────────────────────────
55
+ #
56
+ # Text this CLI writes *into* a HAP application, as opposed to the CLI's
57
+ # own interface text above. These end up stored in the customer's
58
+ # application and shown to its end users — a button's confirm dialog, a
59
+ # synthesized field name — so leaving them hardcoded in one language put
60
+ # Simplified Chinese into all-English applications regardless of the
61
+ # account or application language.
62
+ #
63
+ # Kept in source rather than in messages.json because they are payload
64
+ # values, not display strings: they must resolve identically whether or
65
+ # not the catalog file ships, and a missing key is a bug in our code, not
66
+ # an untranslated string.
67
+ CONTENT_DEFAULTS: dict[str, dict[str, str]] = {
68
+ # Custom-button confirm dialog (stored in the button's own
69
+ # sureName / cancelName / confirmMsg fields).
70
+ "button.sure": {
71
+ "en": "OK", "zh-Hans": "确认", "zh-Hant": "確認", "ja": "OK",
72
+ },
73
+ "button.cancel": {
74
+ "en": "Cancel", "zh-Hans": "取消", "zh-Hant": "取消",
75
+ "ja": "キャンセル",
76
+ },
77
+ "button.confirmMsg": {
78
+ "en": "Are you sure you want to do this?",
79
+ "zh-Hans": "你确认执行此操作吗?",
80
+ "zh-Hant": "你確認執行此操作嗎?",
81
+ "ja": "この操作を実行してもよろしいですか?",
82
+ },
83
+ # Name given to the reverse control synthesized when a two-way
84
+ # relation is paired and the caller supplied no name.
85
+ "control.backRelation": {
86
+ "en": "Related records", "zh-Hans": "关联记录",
87
+ "zh-Hant": "關聯記錄", "ja": "関連レコード",
88
+ },
89
+ }
90
+
91
+
92
+ def content_default(key: str) -> str:
93
+ """Return the default *content* string for ``key`` in the active
94
+ language, falling back to English.
95
+
96
+ Raises ``KeyError`` for an unknown key — these are our own payload
97
+ defaults, so a miss is a programming error rather than a missing
98
+ translation.
99
+ """
100
+ entry = CONTENT_DEFAULTS[key]
101
+ return entry.get(get_active_language()) or entry["en"]
102
+
54
103
 
55
104
  def lang_from_hap_account(account_lang: str) -> str:
56
105
  """Map HAP's ``md.global.Account.lang`` field to a CLI language code."""