hap-cli 0.8.23__tar.gz → 0.8.24__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 (284) hide show
  1. {hap_cli-0.8.23 → hap_cli-0.8.24}/PKG-INFO +1 -1
  2. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/__init__.py +1 -1
  3. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/record_cmd.py +15 -6
  4. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/record.py +103 -23
  5. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/en/overview.md +3 -2
  6. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/en/record.md +180 -14
  7. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/zh-Hans/overview.md +2 -2
  8. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/zh-Hans/record.md +166 -12
  9. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_guide.py +11 -0
  10. hap_cli-0.8.24/hap_cli/tests/test_record_write_fixes.py +122 -0
  11. hap_cli-0.8.24/hap_cli/tests/test_select_add_option.py +78 -0
  12. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_select_rejects_relation_value.py +10 -6
  13. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli.egg-info/PKG-INFO +1 -1
  14. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli.egg-info/SOURCES.txt +2 -0
  15. {hap_cli-0.8.23 → hap_cli-0.8.24}/MANIFEST.in +0 -0
  16. {hap_cli-0.8.23 → hap_cli-0.8.24}/README.md +0 -0
  17. {hap_cli-0.8.23 → hap_cli-0.8.24}/app_live_tests/__init__.py +0 -0
  18. {hap_cli-0.8.23 → hap_cli-0.8.24}/app_live_tests/__main__.py +0 -0
  19. {hap_cli-0.8.23 → hap_cli-0.8.24}/app_live_tests/config.py +0 -0
  20. {hap_cli-0.8.23 → hap_cli-0.8.24}/app_live_tests/harness.py +0 -0
  21. {hap_cli-0.8.23 → hap_cli-0.8.24}/app_live_tests/smoke.py +0 -0
  22. {hap_cli-0.8.23 → hap_cli-0.8.24}/app_live_tests/state.py +0 -0
  23. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/README.md +0 -0
  24. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/README_CN.md +0 -0
  25. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/__main__.py +0 -0
  26. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/__init__.py +0 -0
  27. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/app_cmd.py +0 -0
  28. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/app_creator_cmd.py +0 -0
  29. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/app_editor_cmd.py +0 -0
  30. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/attachment_cmd.py +0 -0
  31. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/auth_cmd.py +0 -0
  32. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/calendar_cmd.py +0 -0
  33. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/chart_cmd.py +0 -0
  34. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/chat_cmd.py +0 -0
  35. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/chatbot_cmd.py +0 -0
  36. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/config_cmd.py +0 -0
  37. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/contact_cmd.py +0 -0
  38. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/department_cmd.py +0 -0
  39. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/group_cmd.py +0 -0
  40. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/guide_cmd.py +0 -0
  41. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/icon_cmd.py +0 -0
  42. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/instance_cmd.py +0 -0
  43. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/node_cmd.py +0 -0
  44. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/optionset_cmd.py +0 -0
  45. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/page_cmd.py +0 -0
  46. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/post_cmd.py +0 -0
  47. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/region_cmd.py +0 -0
  48. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/role_cmd.py +0 -0
  49. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/update_cmd.py +0 -0
  50. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/upload_cmd.py +0 -0
  51. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/v3_registry.py +0 -0
  52. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/workflow_cmd.py +0 -0
  53. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/commands/worksheet_cmd.py +0 -0
  54. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/context.py +0 -0
  55. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/__init__.py +0 -0
  56. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/action_spec_adapter.py +0 -0
  57. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app.py +0 -0
  58. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/__init__.py +0 -0
  59. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
  60. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
  61. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
  62. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/charts.py +0 -0
  63. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/cleanup.py +0 -0
  64. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/compiler.py +0 -0
  65. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/config.py +0 -0
  66. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/design/design.schema.json +0 -0
  67. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/errors.py +0 -0
  68. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
  69. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/executor.py +0 -0
  70. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/fields.py +0 -0
  71. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/hap.py +0 -0
  72. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/merge.py +0 -0
  73. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/recording/__init__.py +0 -0
  74. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/recording/console.py +0 -0
  75. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
  76. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/recording/mirror.py +0 -0
  77. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/recording/report.py +0 -0
  78. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/schema.py +0 -0
  79. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
  80. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/__init__.py +0 -0
  81. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/cli.py +0 -0
  82. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/executor.py +0 -0
  83. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
  84. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
  85. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
  86. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
  87. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/seed/template.py +0 -0
  88. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/selftest.py +0 -0
  89. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/smoke.py +0 -0
  90. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/steps.py +0 -0
  91. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/store.py +0 -0
  92. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/validate.py +0 -0
  93. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
  94. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/attachment.py +0 -0
  95. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/auth.py +0 -0
  96. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/calendar_mod.py +0 -0
  97. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/chart.py +0 -0
  98. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/chat.py +0 -0
  99. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/chatbot.py +0 -0
  100. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/cli_access.py +0 -0
  101. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/contact.py +0 -0
  102. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/control_type_codes.py +0 -0
  103. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/department.py +0 -0
  104. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/__init__.py +0 -0
  105. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  106. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  107. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/apply.py +0 -0
  108. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/componentlower.py +0 -0
  109. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/editspec/component.schema.json +0 -0
  110. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
  111. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
  112. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/editspec/field.schema.json +0 -0
  113. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/editspec.py +0 -0
  114. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/errors.py +0 -0
  115. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/fieldlower.py +0 -0
  116. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  117. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/models.py +0 -0
  118. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/ops.py +0 -0
  119. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/planner.py +0 -0
  120. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/editor/reader.py +0 -0
  121. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/field_normalizer.py +0 -0
  122. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/field_spec_adapter.py +0 -0
  123. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/filter_translator.py +0 -0
  124. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/flow_node.py +0 -0
  125. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/global_meta.py +0 -0
  126. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/group.py +0 -0
  127. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/guide.py +0 -0
  128. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/icon_index.py +0 -0
  129. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/instance.py +0 -0
  130. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/logger.py +0 -0
  131. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/optionset.py +0 -0
  132. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/page.py +0 -0
  133. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/post.py +0 -0
  134. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/response_crypto.py +0 -0
  135. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/role.py +0 -0
  136. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/role_perm_builder.py +0 -0
  137. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/session.py +0 -0
  138. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/token_crypto.py +0 -0
  139. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/update.py +0 -0
  140. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/upload.py +0 -0
  141. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/v3/__init__.py +0 -0
  142. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/v3/dispatcher.py +0 -0
  143. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/v3/render.py +0 -0
  144. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/v3/schema.py +0 -0
  145. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/view_spec_adapter.py +0 -0
  146. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/workflow.py +0 -0
  147. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/workflow_node_dsl.py +0 -0
  148. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/worksheet.py +0 -0
  149. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/core/worksheet_templates.py +0 -0
  150. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/en/messages.md +0 -0
  151. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/en/setup.md +0 -0
  152. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/en/skills.md +0 -0
  153. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/index.json +0 -0
  154. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/zh-Hans/messages.md +0 -0
  155. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/zh-Hans/setup.md +0 -0
  156. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/guide/zh-Hans/skills.md +0 -0
  157. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/hap_cli.py +0 -0
  158. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/i18n.py +0 -0
  159. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/locale/__init__.py +0 -0
  160. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/locale/messages.json +0 -0
  161. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/locale/messages.schema.json +0 -0
  162. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/__init__.py +0 -0
  163. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/conftest.py +0 -0
  164. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_app_creator.py +0 -0
  165. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_app_editor.py +0 -0
  166. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_app_export_usage.py +0 -0
  167. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_attachment_share.py +0 -0
  168. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_auth_prerelease.py +0 -0
  169. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_bug_036430.py +0 -0
  170. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_bug_036750.py +0 -0
  171. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_chart.py +0 -0
  172. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_chat_attachment_send.py +0 -0
  173. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_cli_access.py +0 -0
  174. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_config_cmd.py +0 -0
  175. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_content_language.py +0 -0
  176. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_control_lookup_guard.py +0 -0
  177. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_core.py +0 -0
  178. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
  179. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_full_e2e.py +0 -0
  180. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_global_meta.py +0 -0
  181. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_i18n.py +0 -0
  182. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_instance_history_cli.py +0 -0
  183. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration.py +0 -0
  184. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_approval.py +0 -0
  185. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_approval_actions.py +0 -0
  186. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_calendar.py +0 -0
  187. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_destructive.py +0 -0
  188. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_misc.py +0 -0
  189. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_post.py +0 -0
  190. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_social.py +0 -0
  191. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_v3.py +0 -0
  192. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_workflow.py +0 -0
  193. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  194. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
  195. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_multi_profile.py +0 -0
  196. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
  197. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
  198. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_org_id_cli.py +0 -0
  199. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_org_id_docs.py +0 -0
  200. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_parameter_conventions.py +0 -0
  201. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  202. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
  203. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_record_field_alias.py +0 -0
  204. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
  205. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_semantic_types_registry.py +0 -0
  206. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_update.py +0 -0
  207. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_update_mode.py +0 -0
  208. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_upload_host_resolution.py +0 -0
  209. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  210. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  211. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  212. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_v3_session.py +0 -0
  213. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  214. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
  215. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_workflow_list_cli.py +0 -0
  216. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
  217. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
  218. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_worksheet_create_section.py +0 -0
  219. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  220. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
  221. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/utils/__init__.py +0 -0
  222. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/utils/formatting.py +0 -0
  223. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/utils/options.py +0 -0
  224. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli/utils/parameter_mapping.py +0 -0
  225. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli.egg-info/dependency_links.txt +0 -0
  226. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli.egg-info/entry_points.txt +0 -0
  227. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli.egg-info/requires.txt +0 -0
  228. {hap_cli-0.8.23 → hap_cli-0.8.24}/hap_cli.egg-info/top_level.txt +0 -0
  229. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/__init__.py +0 -0
  230. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/__main__.py +0 -0
  231. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/_wftest.py +0 -0
  232. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/charts.py +0 -0
  233. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/cleanup.py +0 -0
  234. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/compiler.py +0 -0
  235. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/config.py +0 -0
  236. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/errors.py +0 -0
  237. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/executor.py +0 -0
  238. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/fields.py +0 -0
  239. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/hap.py +0 -0
  240. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/recording/__init__.py +0 -0
  241. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/recording/console.py +0 -0
  242. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/recording/jsonl.py +0 -0
  243. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/recording/mirror.py +0 -0
  244. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/recording/report.py +0 -0
  245. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/schema.py +0 -0
  246. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/seed/__init__.py +0 -0
  247. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/seed/cli.py +0 -0
  248. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/seed/executor.py +0 -0
  249. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/seed/template.py +0 -0
  250. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/selftest.py +0 -0
  251. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/smoke.py +0 -0
  252. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/step.py +0 -0
  253. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/steps.py +0 -0
  254. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/store.py +0 -0
  255. {hap_cli-0.8.23 → hap_cli-0.8.24}/live_tests/workflow_dsl.py +0 -0
  256. {hap_cli-0.8.23 → hap_cli-0.8.24}/setup.cfg +0 -0
  257. {hap_cli-0.8.23 → hap_cli-0.8.24}/setup.py +0 -0
  258. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/INDEX.json +0 -0
  259. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  260. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  261. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/create_optionset.yaml +0 -0
  262. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/create_role.yaml +0 -0
  263. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  264. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/delete_record.yaml +0 -0
  265. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/delete_role.yaml +0 -0
  266. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  267. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_app_info.yaml +0 -0
  268. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  269. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  270. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  271. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_details.yaml +0 -0
  272. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  273. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_list.yaml +0 -0
  274. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  275. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  276. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  277. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  278. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_regions.yaml +0 -0
  279. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_role_details.yaml +0 -0
  280. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/get_role_list.yaml +0 -0
  281. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  282. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  283. {hap_cli-0.8.23 → hap_cli-0.8.24}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  284. {hap_cli-0.8.23 → hap_cli-0.8.24}/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.23
3
+ Version: 0.8.24
4
4
  Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -1,3 +1,3 @@
1
1
  """CLI harness for Mingdao HAP (hap-cli)."""
2
2
 
3
- __version__ = "0.8.23"
3
+ __version__ = "0.8.24"
@@ -84,8 +84,12 @@ def record_get(ctx, worksheet_id, row_id, view_id):
84
84
  "list of option names or keys; member/relation fields a "
85
85
  "list of ids). Use either this or -f, not both.")
86
86
  @click.option("--no-workflow", is_flag=True, help="Don't trigger workflows")
87
+ @click.option(
88
+ "--app-id", "-a", default="",
89
+ help="Application ID. Optional — the worksheet id is enough on its own; this only scopes the field lookup.",
90
+ )
87
91
  @pass_context
88
- def record_create(ctx, worksheet_id, field, fields_json, no_workflow):
92
+ def record_create(ctx, worksheet_id, field, fields_json, no_workflow, app_id):
89
93
  """Create a new record.
90
94
 
91
95
  Pass values either as repeated -f CONTROL_ID=VALUE pairs or as one
@@ -112,10 +116,11 @@ def record_create(ctx, worksheet_id, field, fields_json, no_workflow):
112
116
  raise click.UsageError(str(exc))
113
117
  else:
114
118
  raw = _parse_fields(field)
115
- controls = rec_mod.encode_controls_by_id(session, worksheet_id, raw)
119
+ controls = rec_mod.encode_controls_by_id(
120
+ session, worksheet_id, raw, app_id=app_id)
116
121
  data = rec_mod.create_record(
117
122
  session, worksheet_id, controls,
118
- trigger_workflow=not no_workflow,
123
+ trigger_workflow=not no_workflow, app_id=app_id,
119
124
  )
120
125
  ctx.output(data, lambda d: click.echo(f"Created: {d}"))
121
126
  except Exception as e:
@@ -153,8 +158,12 @@ def _fields_json_to_raw(fields_json):
153
158
  "list of option names or keys; member/relation fields a "
154
159
  "list of ids). Use either this or -f, not both.")
155
160
  @click.option("--no-workflow", is_flag=True, help="Don't trigger workflows")
161
+ @click.option(
162
+ "--app-id", "-a", default="",
163
+ help="Application ID. Optional — the worksheet id is enough on its own; this only scopes the field lookup.",
164
+ )
156
165
  @pass_context
157
- def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow):
166
+ def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow, app_id):
158
167
  """Update a record.
159
168
 
160
169
  Pass values either as repeated -f CONTROL_ID=VALUE pairs or as one
@@ -180,10 +189,10 @@ def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow):
180
189
  else:
181
190
  raw = _parse_fields(field)
182
191
  controls = rec_mod.encode_controls_by_id(
183
- session, worksheet_id, raw, for_update=True)
192
+ session, worksheet_id, raw, for_update=True, app_id=app_id)
184
193
  data = rec_mod.update_record(
185
194
  session, worksheet_id, row_id, controls,
186
- trigger_workflow=not no_workflow,
195
+ trigger_workflow=not no_workflow, app_id=app_id,
187
196
  )
188
197
  ctx.output(data, lambda d: click.echo(f"Updated: {d}"))
189
198
  except Exception as e:
@@ -29,12 +29,36 @@ def _option_field_label(ctrl: dict[str, Any]) -> str:
29
29
  return str(ctrl.get("controlName") or ctrl.get("controlId") or "option field")
30
30
 
31
31
 
32
+ # The colour pd-openweb stamps on an option created this way
33
+ # (src/components/Form/core/utils.js:224).
34
+ _NEW_OPTION_COLOR = "#1677ff"
35
+ _NEW_OPTION_PREFIX = "add_"
36
+
37
+
38
+ def _new_option_payload(text: str) -> str:
39
+ """Wire form of "create this option and select it".
40
+
41
+ ``add_<text>`` is the form's own marker, never a wire value: pd-openweb
42
+ rewrites it in ``formatControlToServer`` (cases 10/11) into a JSON
43
+ string ``{"color", "value"}``, and that is what the server reads as a
44
+ new option. Passing the marker through untranslated is what used to
45
+ store ``{"key": "add_<text>", "value": null}`` — a blank cell, and no
46
+ option added to the field.
47
+
48
+ The frontend rewrites on ``indexOf('add_') > -1``; a prefix test is
49
+ used here so a genuine label containing ``add_`` still resolves to its
50
+ own key.
51
+ """
52
+ return json.dumps({"color": _NEW_OPTION_COLOR, "value": text},
53
+ ensure_ascii=False)
54
+
55
+
32
56
  def _reject_relation_shaped_option(ctrl: dict[str, Any], item: Any) -> None:
33
57
  """Fail loudly when a select cell is handed a relation/member/sub-table
34
58
  *object* shape instead of an option value.
35
59
 
36
60
  A single/multi-select cell legitimately carries only an option key, a
37
- known label, or an ``add_<text>`` new-option directive — never a
61
+ known label, or an ``add_<text>`` marker for a new option — never a
38
62
  ``[{"sid": rowId}]`` / ``{...}`` structure. When such a value slips
39
63
  through (e.g. row data maps a relation value to the wrong child field),
40
64
  the old code passed it straight to the server, which then failed
@@ -53,8 +77,9 @@ def _reject_relation_shaped_option(ctrl: dict[str, Any], item: Any) -> None:
53
77
  if looks_object:
54
78
  raise ValueError(
55
79
  f"Field '{_option_field_label(ctrl)}' is a select field and "
56
- f"expects an option key, a label, or an 'add_<text>' new-option "
57
- f"value, but got a relation/object-shaped value: {item!r}. "
80
+ f"expects an option key, an exact option label, or "
81
+ f"'add_<text>' to create a new option, but got a "
82
+ f"relation/object-shaped value: {item!r}. "
58
83
  f"This value belongs to a relation or member field, not this "
59
84
  f"select field — check the field mapping."
60
85
  )
@@ -86,8 +111,15 @@ def _resolve_option_keys(ctrl: dict[str, Any], raw: Any) -> list[str]:
86
111
  by_label[str(opt.get("value", ""))] = key
87
112
  out: list[str] = []
88
113
  for it in items:
89
- _reject_relation_shaped_option(ctrl, it)
90
114
  s = str(it)
115
+ # A known label wins over the marker, so an option literally named
116
+ # "add_something" still resolves to its own key.
117
+ if (isinstance(it, str) and s not in valid_keys and s not in by_label
118
+ and s.startswith(_NEW_OPTION_PREFIX)
119
+ and len(s) > len(_NEW_OPTION_PREFIX)):
120
+ out.append(_new_option_payload(s[len(_NEW_OPTION_PREFIX):]))
121
+ continue
122
+ _reject_relation_shaped_option(ctrl, it)
91
123
  out.append(s if s in valid_keys else by_label.get(s, s))
92
124
  return out
93
125
 
@@ -387,7 +419,7 @@ def _legacy_cell_value(ctrl: dict[str, Any], value: Any) -> str:
387
419
 
388
420
 
389
421
  def _controls_for_write(
390
- session: Session, worksheet_id: str,
422
+ session: Session, worksheet_id: str, app_id: str = "",
391
423
  ) -> list[dict[str, Any]]:
392
424
  """Fetch the worksheet's controls for a write, refusing an empty list.
393
425
 
@@ -406,7 +438,8 @@ def _controls_for_write(
406
438
  always has controls, so an empty list means "we could not read the
407
439
  fields", never "this worksheet has none". Abort before writing.
408
440
  """
409
- controls = ws_mod.get_worksheet_controls(session, worksheet_id)
441
+ controls = ws_mod.get_worksheet_controls(
442
+ session, worksheet_id, app_id=app_id)
410
443
  if not controls:
411
444
  logger.warning(
412
445
  "GetWorksheetControls returned no fields for worksheet %s; "
@@ -427,6 +460,7 @@ def encode_controls_by_id(
427
460
  raw_controls: list[dict[str, Any]],
428
461
  *,
429
462
  for_update: bool = False,
463
+ app_id: str = "",
430
464
  ) -> list[dict[str, Any]]:
431
465
  """Type-encode ``-f CONTROL_ID=VALUE`` cells for create/update.
432
466
 
@@ -457,7 +491,7 @@ def encode_controls_by_id(
457
491
  (in-place edits keyed by ``rowid``, fresh adds, and deletes — see
458
492
  :func:`_sublist_update_value`).
459
493
  """
460
- controls = _controls_for_write(session, worksheet_id)
494
+ controls = _controls_for_write(session, worksheet_id, app_id)
461
495
  by_id: dict[str, dict[str, Any]] = {
462
496
  c.get("controlId"): c for c in controls if c.get("controlId")
463
497
  }
@@ -860,32 +894,68 @@ def _reject_workflow_suppression(trigger_workflow: bool) -> None:
860
894
  )
861
895
 
862
896
 
897
+ def _raise_on_failed_write(result: Any, action: str) -> Any:
898
+ """Turn a rejected write into an error instead of a printed success.
899
+
900
+ ``AddWorksheetRow`` / ``UpdateWorksheetRow`` answer HTTP 200 with a
901
+ ``resultCode`` that says whether the row was actually saved: 1 means
902
+ saved, anything else means it was not. Nothing used to look at it, so
903
+ a validation failure — most often a missing required field, which
904
+ comes back as ``{"resultCode": 31, "badData": [<control ids>]}`` and
905
+ stores nothing — was printed as ``Created: {...}`` and exited 0.
906
+
907
+ Responses with no ``resultCode`` are left alone: not every endpoint
908
+ carries one, and absence is not failure.
909
+ """
910
+ if not isinstance(result, dict) or "resultCode" not in result:
911
+ return result
912
+ code = result.get("resultCode")
913
+ if code == 1:
914
+ return result
915
+ bad = result.get("badData")
916
+ detail = (f" It rejected these fields: {', '.join(map(str, bad))}."
917
+ if bad else " Check the record still exists and that each value "
918
+ "matches its field type.")
919
+ raise APIError(
920
+ f"The record was not saved — the server refused the {action} "
921
+ f"(resultCode {code}).{detail}",
922
+ code=code if isinstance(code, int) else None,
923
+ )
924
+
925
+
863
926
  def create_record(
864
927
  session: Session,
865
928
  worksheet_id: str,
866
929
  controls: list[dict[str, Any]],
867
930
  trigger_workflow: bool = True,
931
+ app_id: str = "",
868
932
  ) -> dict[str, Any]:
869
933
  """Create a new record.
870
934
 
935
+ ``worksheet_id`` identifies the worksheet on its own — a write does not
936
+ depend on which app is currently selected (verified against a worksheet
937
+ while an unrelated app was the default). ``app_id`` is optional and is
938
+ forwarded as ``appId``, which this endpoint's schema carries.
939
+
871
940
  Args:
872
941
  session: Active session
873
942
  worksheet_id: Worksheet ID
874
943
  controls: List of {controlId, value} dicts
875
944
  trigger_workflow: Whether to trigger workflows
945
+ app_id: Optional application ID
876
946
 
877
947
  Returns:
878
948
  Created record info
879
949
  """
880
950
  _reject_workflow_suppression(trigger_workflow)
881
- return session.api_call(
882
- "Worksheet",
883
- "AddWorksheetRow",
884
- {
885
- "worksheetId": worksheet_id,
886
- "receiveControls": controls,
887
- },
888
- )
951
+ data: dict[str, Any] = {
952
+ "worksheetId": worksheet_id,
953
+ "receiveControls": controls,
954
+ }
955
+ if app_id:
956
+ data["appId"] = app_id
957
+ return _raise_on_failed_write(
958
+ session.api_call("Worksheet", "AddWorksheetRow", data), "create")
889
959
 
890
960
 
891
961
  def update_record(
@@ -894,28 +964,38 @@ def update_record(
894
964
  row_id: str,
895
965
  controls: list[dict[str, Any]],
896
966
  trigger_workflow: bool = True,
967
+ app_id: str = "",
897
968
  ) -> dict[str, Any]:
898
969
  """Update an existing record.
899
970
 
971
+ ``app_id`` is accepted for a consistent command surface and is used to
972
+ scope the field-metadata lookup, but it is NOT sent: unlike
973
+ AddWorksheetRow, UpdateWorksheetRow's schema has no ``appId``, and
974
+ inventing one would diverge from the frontend.
975
+
900
976
  Args:
901
977
  session: Active session
902
978
  worksheet_id: Worksheet ID
903
979
  row_id: Record/row ID
904
980
  controls: List of {controlId, value} dicts
905
981
  trigger_workflow: Whether to trigger workflows
982
+ app_id: Optional application ID (metadata scope only)
906
983
 
907
984
  Returns:
908
985
  Update result
909
986
  """
910
987
  _reject_workflow_suppression(trigger_workflow)
911
- return session.api_call(
912
- "Worksheet",
913
- "UpdateWorksheetRow",
914
- {
915
- "worksheetId": worksheet_id,
916
- "rowId": row_id,
917
- "newOldControl": controls,
918
- },
988
+ return _raise_on_failed_write(
989
+ session.api_call(
990
+ "Worksheet",
991
+ "UpdateWorksheetRow",
992
+ {
993
+ "worksheetId": worksheet_id,
994
+ "rowId": row_id,
995
+ "newOldControl": controls,
996
+ },
997
+ ),
998
+ "update",
919
999
  )
920
1000
 
921
1001
 
@@ -35,8 +35,9 @@ generated live rather than copied. Expand any of them with `hap <command> --help
35
35
  - `--json` and `--profile` are **global options and go between `hap` and the
36
36
  subcommand**: `hap --json chat list` ✅, `hap chat list --json` ❌ ("No such option").
37
37
  - To see a worksheet's fields use `hap worksheet fields WS_ID` — not get / structure.
38
- - `record create/update/delete` **do not accept `--app-id`**; they use the default app.
39
- Set it once with `hap app select <appId>` before writing.
38
+ - Writing records **does not depend on the selected app** a `worksheetId` locates the
39
+ worksheet on its own and `-a/--app-id` is optional. A failing write is never "I forgot
40
+ to select the app".
40
41
  - Field value shapes differ a lot and **a wrong one still reports success** —
41
42
  read `hap guide record` before writing.
42
43
  - Before touching workflow nodes run `hap workflow structure PROCESS_ID` and use the
@@ -34,8 +34,11 @@ hap worksheet record create <WS_ID> --fields-json '[{"id":"<field>","value":...}
34
34
  They **cannot be mixed**. With `-f`, multiple values (multi-select, several members or
35
35
  relations) are comma-separated.
36
36
 
37
- > `record create/update/delete` **do not accept `-a/--app-id`**. Run
38
- > `hap app select <APP_ID>` first.
37
+ > **About `-a/--app-id`**: `create` / `update` / `delete` all accept it, but none of
38
+ > them *needs* it — a `worksheetId` identifies the worksheet on its own. Verified by
39
+ > setting an entirely unrelated app as the default: all three writes still succeeded.
40
+ > So a failing write is never "I forgot to select the app". Passing it is harmless: it
41
+ > scopes the field-metadata lookup, and create forwards it as `appId`.
39
42
 
40
43
  ### 1.3 Update replaces; only sub-tables merge
41
44
 
@@ -72,7 +75,38 @@ The `_id` alongside it (24 hex chars) is a different internal id. **Passing it t
72
75
  `record get` reports that the row does not exist or is not permitted**, and it can
73
76
  never be used in a relation field.
74
77
 
75
- ## 2. By field type
78
+ ### 1.7 "Created" does not mean it worked — check `resultCode`
79
+
80
+ The server uses `resultCode` to say whether the row was actually saved: **only 1 is
81
+ success.**
82
+
83
+ ```
84
+ Created: {'resultCode': 1, 'data': {...'rowid': '...'}} <- saved; the rowid is in data
85
+ ```
86
+
87
+ Anything else means nothing was written, and the CLI raises it as an error and exits
88
+ non-zero:
89
+
90
+ ```
91
+ Error: The record was not saved — the server refused the update (resultCode 4).
92
+ ```
93
+
94
+ When the reply carries `badData`, it names the field ids that were rejected.
95
+
96
+ > Note: **"required" is a form-level constraint and the API does not necessarily
97
+ > enforce it.** Omitting a `required: true` field still came back as `resultCode: 1`
98
+ > with the row saved. Do not rely on the API to catch a missing required value — look
99
+ > them up yourself:
100
+ >
101
+ > ```bash
102
+ > hap --json worksheet fields <WS_ID> | python3 -c "
103
+ > import json,sys
104
+ > for f in json.load(sys.stdin):
105
+ > if f.get('required'): print(f['id'], f['name'], f['type'])
106
+ > "
107
+ > ```
108
+
109
+ ## 2. By field type## 2. By field type
76
110
 
77
111
  ### Text and numbers
78
112
 
@@ -130,9 +164,26 @@ hap --json worksheet fields <WS_ID> # options[].key on that field
130
164
  > (`-f "<field>=high,priority"` splits into two invalid values)
131
165
  > - the option simply does not exist
132
166
  >
133
- > Keys have none of these problems. To add an option, change the field definition
134
- > first (see `hap guide skills`); the CLI does **not** support `add_<text>` to create
135
- > options on the fly.
167
+ > Keys have none of these problems.
168
+
169
+ **Creating an option as you write**
170
+
171
+ When the option does not exist yet, prefix it with `add_` and the server mints it and
172
+ selects it:
173
+
174
+ ```bash
175
+ hap worksheet record create <WS_ID> -f "<select field>=add_Urgent"
176
+ ```
177
+
178
+ Verified: the field went from `[Pending, Done]` to `[Pending, Done, Urgent]`, and the
179
+ record holds the freshly minted key. Multi-selects and dropdowns behave the same, and it
180
+ mixes with existing values: `-f "<field>=Pending,add_Urgent"`.
181
+
182
+ > Only a leading `add_` is treated as the marker — an option genuinely named
183
+ > `add_something` still resolves to its own key rather than creating a duplicate.
184
+ >
185
+ > To change the field definition itself (reorder options, set colours, delete one), use
186
+ > the app-editor skill from `hap guide skills`.
136
187
 
137
188
  ### Members, departments, org roles
138
189
 
@@ -233,18 +284,127 @@ field's `dataSource`, linked back through the reverse relation field (`sourceFie
233
284
 
234
285
  | Type | typeId | Pass |
235
286
  | --- | --- | --- |
236
- | `Attachment` | 14 | a fixed envelope, as a JSON string |
287
+ | `Attachment` | 14 | a fixed envelope, as a **JSON string** |
288
+
289
+ This is the only accepted shape, and it works with both `-f` and `--fields-json`:
290
+
291
+ ```json
292
+ {"attachmentData":[],"attachments":[<descriptor from hap upload>, ...],"knowledgeAtts":[]}
293
+ ```
294
+
295
+ **The whole flow (creating a record with attachments)**
237
296
 
238
297
  ```bash
239
- # 1) upload and keep the descriptor
240
- hap --json upload ./demo.txt --worksheet-id <WS_ID> -a <APP_ID>
298
+ hap app select <APP_ID>
299
+
300
+ # 1) Upload. Several files at once is fine; it returns an array of
301
+ # descriptors. Passing --worksheet-id / -a is recommended (see below).
302
+ hap --json upload ./a.txt ./b.pdf --worksheet-id <WS_ID> -a <APP_ID> > up.json
241
303
 
242
- # 2) put the descriptor into the attachments array as-is
243
- --fields-json '[{"id":"<attachment field>","value":"{\"attachmentData\":[],\"attachments\":[<descriptor>],\"knowledgeAtts\":[]}"}]'
304
+ # 2) Put the whole array in attachments, wrap the envelope, serialize it
305
+ python3 -c "
306
+ import json
307
+ env={'attachmentData':[],'attachments':json.load(open('up.json')),'knowledgeAtts':[]}
308
+ print(json.dumps([{'id':'<attachment field id>','value':json.dumps(env,ensure_ascii=False)}],ensure_ascii=False))
309
+ " > cell.json
310
+
311
+ # 3) Write
312
+ hap worksheet record create <WS_ID> --fields-json "$(cat cell.json)"
313
+ ```
314
+
315
+ > **The two detours everyone takes, both answered with an unexplained
316
+ > server error:**
317
+ >
318
+ > - passing the array `hap upload` returned **directly** (no envelope)
319
+ > - passing just the file's `url` string
320
+ >
321
+ > It must be the full `attachmentData` / `attachments` / `knowledgeAtts`
322
+ > envelope, **serialized to a string as a whole**.
323
+
324
+ > **Do not hand-write that envelope in a shell.** Nested quotes plus escaping is
325
+ > almost guaranteed to go wrong. Generate it with Python into a file and pass
326
+ > `--fields-json "$(cat cell.json)"`, as above.
327
+
328
+ **What `--worksheet-id` / `-a` change**
329
+
330
+ They decide where the file lands in the store:
331
+
332
+ | | `filePath` |
333
+ | --- | --- |
334
+ | without | `pic/20260902/`, `doc/20260902/` (a shared directory per file type) |
335
+ | with | `<orgId>/<appId>/<worksheetId>/20260902/` (filed under org / app / worksheet) |
336
+
337
+ **Pass them** — it is what the web UI does and it keeps files attributable. Writes also
338
+ succeed without them (verified for `.txt` and `.png`: written, read back, downloaded
339
+ intact), so they are not the first thing to suspect when a write fails.
340
+
341
+ **The `file_id` changes once the record is saved**
342
+
343
+ The `fileID` you get from the upload is a client-generated temporary id starting with
344
+ `o_`. Once the record is saved the server assigns a GUID, and that is what you read
345
+ back. **This is normal, not a sign the write went wrong.**
346
+
347
+ **Clearing an attachment field** (it does not follow the general rule)
348
+
349
+ | Pass | Result |
350
+ | --- | --- |
351
+ | `""`, or `-f "<field>="` | clears it |
352
+ | the empty envelope `{"attachmentData":[],"attachments":[],"knowledgeAtts":[]}` | clears it |
353
+ | `[]` | rejected: attachment save failed, bad parameter |
354
+ | `null` | same rejection |
355
+
356
+ `[]` and `null` clear other field types. **They do not clear attachments.**
357
+
358
+ **Update replaces, and there is no append switch**
359
+
360
+ New attachments drop whatever was there. The envelope's `attachmentData` **cannot** be
361
+ used to keep the existing ones: feeding it the `record get` read-back, and separately a
362
+ camelCase object carrying the stored `fileID`, both dropped the old file. (The CLI
363
+ passes attachment JSON through untouched, so this is the server's behaviour.)
364
+
365
+ **To append, build the full list yourself — but do not re-upload.** The bytes are
366
+ already in the store and a descriptor is only metadata pointing at them, so it can be
367
+ rebuilt from what you read back:
368
+
369
+ | Key from `record get` | Rebuilds which descriptor field |
370
+ | --- | --- |
371
+ | `file_path` (`https://host/doc/20260902/`) | `serverName` (`https://host/`) + `filePath` (`doc/20260902/`) |
372
+ | `file_name` (`xxxx.txt`) | `fileName` (`xxxx`) + `fileExt` (`.txt`) |
373
+ | `original_file_name` (`att1.txt`) | `originalFileName` (`att1`) |
374
+ | `file_size` | `fileSize` |
375
+ | `filePath` + `file_name` | `key` |
376
+ | — | `fileID`: any fresh client-side id will do |
377
+
378
+ ```python
379
+ import json, os
380
+ def rebuild(a):
381
+ """Turn one record-get read-back entry into a writable descriptor."""
382
+ scheme, rest = a["file_path"].split("//", 1)
383
+ host, _, path = rest.partition("/")
384
+ stem, ext = os.path.splitext(a["file_name"])
385
+ orig, _ = os.path.splitext(a["original_file_name"])
386
+ return {"fileID": "o_" + os.urandom(8).hex(), "fileSize": a["file_size"],
387
+ "serverName": f"{scheme}//{host}/", "filePath": path,
388
+ "fileName": stem, "fileExt": ext,
389
+ "originalFileName": orig, "key": path + a["file_name"]}
390
+
391
+ old = [rebuild(a) for a in record_get_result["<attachment field id>"]]
392
+ new = json.load(open("up.json"))
393
+ env = {"attachmentData": [], "attachments": old + new, "knowledgeAtts": []}
244
394
  ```
245
395
 
246
- > **Passing the array `hap upload` returns fails** with a parameter error. The
247
- > `attachmentData` / `attachments` / `knowledgeAtts` envelope is required.
396
+ Both the rebuilt and the newly uploaded files download correctly afterwards.
397
+
398
+ **Three read shapes, none of them the write shape**
399
+
400
+ | Read from | Shape | Keys |
401
+ | --- | --- | --- |
402
+ | `record get` | array | snake_case: `file_id` / `file_name` / `file_path` / `file_size` / `original_file_name` / `preview_url` … |
403
+ | `record list` | array | only `fileName` + `downloadUrl` |
404
+ | the return of `record create/update` | **a JSON string** | camelCase: `fileID` / `originalFilename` / `ext` / `filesize` / `viewUrl` / `downloadUrl` |
405
+
406
+ So **you cannot write back what you read**. Either keep the descriptors `hap upload`
407
+ gave you — they stay reusable — or rebuild them per the table above.
248
408
 
249
409
  ## 3. Filtering records
250
410
 
@@ -278,12 +438,18 @@ and pivots see `hap guide skills`.
278
438
  Every one of these returns success while storing something wrong, or nothing:
279
439
 
280
440
  1. **Misspelled field identifier** → the value is ignored.
281
- 2. **Display text instead of an option key, not matching exactly** → stored as an invalid key, blank in the UI.
441
+ 2. **Display text instead of an option key, not matching exactly** → stored as an
442
+ invalid key, blank in the UI. To add an option deliberately, use the `add_` prefix.
282
443
  3. **A place name in a region field** → stored as `{"code":"<name>","path":""}`.
283
444
  4. **A number beyond the field's precision** → silently rounded.
284
445
  5. **A title instead of a rowid in a relation** → nothing is linked.
285
446
  6. **A value on a read-only field** → ignored (except `DynamicLink`, which fails the
286
447
  whole write).
287
448
  7. **A multi-value update that omits existing values** → they are gone (sub-tables aside).
449
+ 8. **Clearing an attachment with `[]` or `null`** → rejected, and the field still holds
450
+ the old files (use `""`).
451
+ 9. **Relying on `attachmentData` to append** → the existing attachments are dropped.
452
+ 10. **Omitting a required field** → the API may accept it anyway, saving the row with
453
+ that value empty (see 1.7).
288
454
 
289
455
  So: **always read back after writing.** Do not trust the success alone.
@@ -32,8 +32,8 @@
32
32
  - `--json` / `--profile` 是**全局选项,必须放在 `hap` 和子命令之间**:
33
33
  `hap --json chat list` ✅,`hap chat list --json` ❌(报 "No such option")。
34
34
  - 看工作表字段用 `hap worksheet fields WS_ID`,**不是** get / structure。
35
- - `record create/update/delete` **不接受 `--app-id`**,只走默认应用;
36
- 改数据前先 `hap app select <appId>` 设一次。
35
+ - 写记录**不依赖默认应用**——`worksheetId` 就能定位工作表,`-a/--app-id` 可传可不传。
36
+ 写入失败时别往「是不是没 select 对应用」上想。
37
37
  - 写记录的取值格式差别很大且**写错不报错** —— 动手前先看 `hap guide record`。
38
38
  - 操作工作流节点前先 `hap workflow structure PROCESS_ID` 看真实结构,别编造节点 ID。
39
39
  - 排查问题开日志:`hap config log on`,日志在 `~/.hap-cli/hap-cli.log`(已对凭证脱敏)。