hap-cli 0.8.0__tar.gz → 0.8.2__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 (217) hide show
  1. {hap_cli-0.8.0 → hap_cli-0.8.2}/PKG-INFO +1 -1
  2. {hap_cli-0.8.0 → hap_cli-0.8.2}/app_live_tests/smoke.py +179 -139
  3. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/__init__.py +1 -1
  4. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/app_cmd.py +52 -13
  5. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/app_editor_cmd.py +6 -2
  6. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/chart_cmd.py +5 -6
  7. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/config_cmd.py +19 -2
  8. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/contact_cmd.py +1 -1
  9. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/group_cmd.py +5 -5
  10. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/instance_cmd.py +43 -10
  11. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/node_cmd.py +46 -15
  12. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/optionset_cmd.py +1 -1
  13. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/page_cmd.py +9 -6
  14. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/post_cmd.py +3 -3
  15. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/record_cmd.py +162 -36
  16. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/role_cmd.py +44 -23
  17. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/upload_cmd.py +1 -1
  18. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/v3_registry.py +8 -4
  19. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/workflow_cmd.py +44 -6
  20. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/worksheet_cmd.py +40 -23
  21. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/action_spec_adapter.py +14 -1
  22. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/app.py +140 -17
  23. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/auth.py +199 -33
  24. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/chart.py +5 -0
  25. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/chat.py +20 -19
  26. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/editspec.py +48 -13
  27. hap_cli-0.8.2/hap_cli/core/editor/ops.py +188 -0
  28. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/filter_translator.py +74 -28
  29. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/global_meta.py +25 -0
  30. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/group.py +4 -1
  31. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/instance.py +54 -1
  32. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/page.py +9 -3
  33. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/record.py +268 -27
  34. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/role.py +78 -44
  35. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/session.py +14 -0
  36. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/v3/dispatcher.py +23 -0
  37. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/view_spec_adapter.py +91 -12
  38. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/workflow_node_dsl.py +806 -43
  39. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/worksheet.py +93 -5
  40. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/conftest.py +52 -0
  41. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_app_editor.py +55 -53
  42. hap_cli-0.8.2/hap_cli/tests/test_app_restore.py +123 -0
  43. hap_cli-0.8.2/hap_cli/tests/test_bug_036430.py +167 -0
  44. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_chart.py +14 -0
  45. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_config_cmd.py +23 -0
  46. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_core.py +506 -45
  47. hap_cli-0.8.2/hap_cli/tests/test_destructive_confirm_cli.py +173 -0
  48. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_worksheet_extra.py +8 -4
  49. hap_cli-0.8.2/hap_cli/tests/test_org_approval_v3_semantic_cli.py +261 -0
  50. hap_cli-0.8.2/hap_cli/tests/test_record_batch_v3_semantic_cli.py +269 -0
  51. hap_cli-0.8.2/hap_cli/tests/test_record_v3_semantic_cli.py +94 -0
  52. hap_cli-0.8.2/hap_cli/tests/test_semantic_types_registry.py +92 -0
  53. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_v3_session.py +25 -0
  54. hap_cli-0.8.2/hap_cli/tests/test_workflow_v3_semantic_cli.py +651 -0
  55. hap_cli-0.8.2/hap_cli/tests/test_worksheet_v3_semantic_cli.py +350 -0
  56. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli.egg-info/PKG-INFO +1 -1
  57. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli.egg-info/SOURCES.txt +9 -8
  58. hap_cli-0.8.2/skills/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +75 -0
  59. {hap_cli-0.8.0 → hap_cli-0.8.2}/skills/hap-cli-app-editor/scripts/editspec/envelope.schema.json +11 -34
  60. hap_cli-0.8.0/hap_cli/core/editor/ops.py +0 -543
  61. hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/application.schema.json +0 -59
  62. hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/chatbot.schema.json +0 -52
  63. hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +0 -55
  64. hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/custom-page.schema.json +0 -49
  65. hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/node.schema.json +0 -73
  66. hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/role.schema.json +0 -90
  67. hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/view.schema.json +0 -64
  68. hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/workflow.schema.json +0 -60
  69. hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/worksheet.schema.json +0 -51
  70. {hap_cli-0.8.0 → hap_cli-0.8.2}/MANIFEST.in +0 -0
  71. {hap_cli-0.8.0 → hap_cli-0.8.2}/README.md +0 -0
  72. {hap_cli-0.8.0 → hap_cli-0.8.2}/app_live_tests/__init__.py +0 -0
  73. {hap_cli-0.8.0 → hap_cli-0.8.2}/app_live_tests/__main__.py +0 -0
  74. {hap_cli-0.8.0 → hap_cli-0.8.2}/app_live_tests/config.py +0 -0
  75. {hap_cli-0.8.0 → hap_cli-0.8.2}/app_live_tests/harness.py +0 -0
  76. {hap_cli-0.8.0 → hap_cli-0.8.2}/app_live_tests/state.py +0 -0
  77. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/README.md +0 -0
  78. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/README_CN.md +0 -0
  79. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/__init__.py +0 -0
  80. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/auth_cmd.py +0 -0
  81. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/calendar_cmd.py +0 -0
  82. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/chat_cmd.py +0 -0
  83. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/chatbot_cmd.py +0 -0
  84. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/department_cmd.py +0 -0
  85. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/icon_cmd.py +0 -0
  86. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/region_cmd.py +0 -0
  87. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/context.py +0 -0
  88. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/__init__.py +0 -0
  89. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/calendar_mod.py +0 -0
  90. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/chatbot.py +0 -0
  91. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/contact.py +0 -0
  92. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/control_type_codes.py +0 -0
  93. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/department.py +0 -0
  94. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/__init__.py +0 -0
  95. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  96. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  97. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/apply.py +0 -0
  98. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/componentlower.py +0 -0
  99. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/errors.py +0 -0
  100. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/fieldlower.py +0 -0
  101. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  102. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/models.py +0 -0
  103. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/planner.py +0 -0
  104. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/reader.py +0 -0
  105. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/field_normalizer.py +0 -0
  106. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/field_spec_adapter.py +0 -0
  107. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/flow_node.py +0 -0
  108. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/icon_index.py +0 -0
  109. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/logger.py +0 -0
  110. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/optionset.py +0 -0
  111. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/post.py +0 -0
  112. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/response_crypto.py +0 -0
  113. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/role_perm_builder.py +0 -0
  114. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/token_crypto.py +0 -0
  115. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/upload.py +0 -0
  116. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/v3/__init__.py +0 -0
  117. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/v3/render.py +0 -0
  118. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/v3/schema.py +0 -0
  119. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/workflow.py +0 -0
  120. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/worksheet_templates.py +0 -0
  121. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/hap_cli.py +0 -0
  122. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/i18n.py +0 -0
  123. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/locale/__init__.py +0 -0
  124. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/locale/messages.json +0 -0
  125. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/locale/messages.schema.json +0 -0
  126. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/__init__.py +0 -0
  127. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_app_export_usage.py +0 -0
  128. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_auth_prerelease.py +0 -0
  129. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_full_e2e.py +0 -0
  130. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_global_meta.py +0 -0
  131. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_i18n.py +0 -0
  132. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration.py +0 -0
  133. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_approval.py +0 -0
  134. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_approval_actions.py +0 -0
  135. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_calendar.py +0 -0
  136. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_destructive.py +0 -0
  137. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_misc.py +0 -0
  138. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_post.py +0 -0
  139. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_social.py +0 -0
  140. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_v3.py +0 -0
  141. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_workflow.py +0 -0
  142. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_multi_profile.py +0 -0
  143. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_org_id_cli.py +0 -0
  144. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_org_id_docs.py +0 -0
  145. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_parameter_conventions.py +0 -0
  146. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  147. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  148. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  149. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  150. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  151. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  152. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/utils/__init__.py +0 -0
  153. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/utils/formatting.py +0 -0
  154. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/utils/options.py +0 -0
  155. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/utils/parameter_mapping.py +0 -0
  156. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli.egg-info/dependency_links.txt +0 -0
  157. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli.egg-info/entry_points.txt +0 -0
  158. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli.egg-info/requires.txt +0 -0
  159. {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli.egg-info/top_level.txt +0 -0
  160. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/__init__.py +0 -0
  161. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/__main__.py +0 -0
  162. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/_wftest.py +0 -0
  163. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/charts.py +0 -0
  164. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/cleanup.py +0 -0
  165. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/compiler.py +0 -0
  166. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/config.py +0 -0
  167. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/errors.py +0 -0
  168. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/executor.py +0 -0
  169. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/fields.py +0 -0
  170. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/hap.py +0 -0
  171. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/recording/__init__.py +0 -0
  172. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/recording/console.py +0 -0
  173. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/recording/jsonl.py +0 -0
  174. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/recording/mirror.py +0 -0
  175. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/recording/report.py +0 -0
  176. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/schema.py +0 -0
  177. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/seed/__init__.py +0 -0
  178. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/seed/cli.py +0 -0
  179. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/seed/executor.py +0 -0
  180. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/seed/template.py +0 -0
  181. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/selftest.py +0 -0
  182. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/smoke.py +0 -0
  183. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/step.py +0 -0
  184. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/steps.py +0 -0
  185. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/store.py +0 -0
  186. {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/workflow_dsl.py +0 -0
  187. {hap_cli-0.8.0 → hap_cli-0.8.2}/setup.cfg +0 -0
  188. {hap_cli-0.8.0 → hap_cli-0.8.2}/setup.py +0 -0
  189. {hap_cli-0.8.0 → hap_cli-0.8.2}/skills/hap-cli-app-editor/scripts/editspec/component.schema.json +0 -0
  190. {hap_cli-0.8.0 → hap_cli-0.8.2}/skills/hap-cli-app-editor/scripts/editspec/field.schema.json +0 -0
  191. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/INDEX.json +0 -0
  192. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  193. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  194. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/create_optionset.yaml +0 -0
  195. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/create_role.yaml +0 -0
  196. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  197. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/delete_record.yaml +0 -0
  198. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/delete_role.yaml +0 -0
  199. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  200. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_app_info.yaml +0 -0
  201. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  202. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  203. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  204. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_details.yaml +0 -0
  205. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  206. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_list.yaml +0 -0
  207. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  208. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  209. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  210. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  211. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_regions.yaml +0 -0
  212. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_role_details.yaml +0 -0
  213. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_role_list.yaml +0 -0
  214. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  215. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  216. {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  217. {hap_cli-0.8.0 → hap_cli-0.8.2}/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.0
3
+ Version: 0.8.2
4
4
  Summary: CLI harness for MingDAO HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -1,16 +1,21 @@
1
- """Live smoke scenarios for the editor, run against the persistent app.
2
-
3
- Each scenario drives the real editor entry point and asserts the result
4
- by reading back through ``hap``. Scenarios create their own worksheet and
5
- delete it at the end, so the persistent app stays clean and the suite is
6
- re-runnable. Run: ``python3 -m app_live_tests smoke [--case NAME]``.
1
+ """Live smoke scenarios for the editor + bare CLI, run against the persistent app.
2
+
3
+ Edit-spec scenarios (field/component/custom-action) drive the real editor
4
+ entry point; everything else uses the bare ``hap`` commands that replaced
5
+ the retired edit-spec ops. Each scenario asserts the result by reading back
6
+ through ``hap``. Scenarios create their own worksheet and delete it at the
7
+ end, so the persistent app stays clean and the suite is re-runnable.
8
+ Run: ``python3 -m app_live_tests smoke [--case NAME]``.
7
9
  """
8
10
  from __future__ import annotations
9
11
 
12
+ import json
10
13
  import sys
14
+ import tempfile
15
+ from pathlib import Path
11
16
 
12
17
  from app_live_tests import state
13
- from app_live_tests.harness import editor_apply, hap
18
+ from app_live_tests.harness import editor, editor_apply, hap
14
19
 
15
20
  _failures: list[str] = []
16
21
 
@@ -23,6 +28,14 @@ def check(name: str, cond: bool, detail: str = "") -> None:
23
28
  print(f" FAIL {name}: {detail}")
24
29
 
25
30
 
31
+ def _try(args: list[str]) -> tuple[bool, object]:
32
+ """Run a bare ``hap`` command; return (ok, payload-or-error-text)."""
33
+ try:
34
+ return True, hap(args)
35
+ except RuntimeError as e:
36
+ return False, str(e)
37
+
38
+
26
39
  # ── helpers ──────────────────────────────────────────────────────────────
27
40
  def _worksheets(app_id: str) -> dict[str, str]:
28
41
  detail = hap(["app", "info", "-a", app_id])
@@ -48,7 +61,7 @@ def _ensure_absent(app_id: str, name: str) -> None:
48
61
  """Delete a leftover worksheet of this name (from a crashed prior run)."""
49
62
  ws = _worksheets(app_id)
50
63
  if name in ws:
51
- hap(["worksheet", "delete", ws[name], "-a", app_id], check=False)
64
+ hap(["worksheet", "delete", ws[name], "-a", app_id, "-y"], check=False)
52
65
 
53
66
 
54
67
  def _make_worksheet(app_id: str, section_id: str, name: str) -> str:
@@ -59,19 +72,18 @@ def _make_worksheet(app_id: str, section_id: str, name: str) -> str:
59
72
 
60
73
  # ── scenarios ──────────────────────────────────────────────────────────────
61
74
  def case_worksheet(app_id: str, section_id: str) -> None:
62
- print("scenario: worksheet create/update/delete")
75
+ print("scenario: worksheet create/update/delete (bare commands)")
63
76
  name = "LT-ws"
64
77
  _ensure_absent(app_id, name)
65
- rc, out, err = editor_apply({"app": app_id, "ops": [
66
- {"type": "worksheet.create", "name": name},
67
- {"type": "worksheet.update", "worksheet": name, "desc": "live test"},
68
- ]})
69
- check("apply create+update succeeds", rc == 0, err or out)
78
+ ok, res = _try(["worksheet", "create", app_id, name,
79
+ "--section-id", section_id])
80
+ ws_id = res.get("workSheetId") if isinstance(res, dict) else ""
81
+ check("worksheet create succeeds", ok and bool(ws_id), str(res))
82
+ ok, res = _try(["worksheet", "update", ws_id, "--desc", "live test"])
83
+ check("worksheet update succeeds", ok, str(res))
70
84
  check("worksheet exists after create", name in _worksheets(app_id))
71
- rc, out, err = editor_apply({"app": app_id, "ops": [
72
- {"type": "worksheet.delete", "worksheet": name, "confirm": True},
73
- ]})
74
- check("apply delete succeeds", rc == 0, err or out)
85
+ ok, res = _try(["worksheet", "delete", ws_id, "-a", app_id, "-y"])
86
+ check("worksheet delete succeeds", ok, str(res))
75
87
  check("worksheet gone after delete", name not in _worksheets(app_id))
76
88
 
77
89
 
@@ -113,31 +125,32 @@ def case_field(app_id: str, section_id: str) -> None:
113
125
  rows = [(c["controlName"], c.get("row")) for c in _fields(ws_id)]
114
126
  first = min(rows, key=lambda t: (t[1] if t[1] is not None else 9999))
115
127
  check("reordered field is first", first[0] == "金额", str(rows))
116
- hap(["worksheet", "delete", ws_id, "-a", app_id], check=False)
128
+ hap(["worksheet", "delete", ws_id, "-a", app_id, "-y"], check=False)
117
129
 
118
130
 
119
131
  def case_view(app_id: str, section_id: str) -> None:
120
- print("scenario: view create/update/delete")
132
+ print("scenario: view create/update/delete (bare commands)")
121
133
  name = "LT-views"
122
134
  ws_id = _make_worksheet(app_id, section_id, name)
123
- rc, out, err = editor_apply({"app": app_id, "ops": [
124
- {"type": "view.create", "worksheet": name, "name": "按状态",
125
- "view_type": "board"},
126
- {"type": "view.update", "worksheet": name, "view": "按状态",
127
- "name": "看板", "edit_attrs": ["name"]},
128
- ]})
129
- check("apply view create+update succeeds", rc == 0, err or out)
135
+ ok, res = _try(["worksheet", "view", "create", ws_id, "按状态",
136
+ "--view-type", "board", "-a", app_id])
137
+ check("view create succeeds", ok, str(res))
138
+ view = next((v for v in _views(ws_id) if v.get("name") == "按状态"), {})
139
+ view_id = view.get("viewId") or view.get("id")
140
+ check("view present after create", bool(view_id),
141
+ str({v.get("name") for v in _views(ws_id)}))
142
+ ok, res = _try(["worksheet", "view", "update", ws_id, view_id,
143
+ "--name", "看板", "-a", app_id])
144
+ check("view update (rename) succeeds", ok, str(res))
130
145
  names = {v.get("name") for v in _views(ws_id)}
131
146
  check("view renamed to 看板", "看板" in names and "按状态" not in names,
132
147
  str(names))
133
- rc, out, err = editor_apply({"app": app_id, "ops": [
134
- {"type": "view.delete", "worksheet": name, "view": "看板",
135
- "confirm": True},
136
- ]})
137
- check("apply view delete succeeds", rc == 0, err or out)
148
+ ok, res = _try(["worksheet", "view", "delete", ws_id, view_id,
149
+ "-a", app_id, "-y"])
150
+ check("view delete succeeds", ok, str(res))
138
151
  names = {v.get("name") for v in _views(ws_id)}
139
152
  check("view gone after delete", "看板" not in names, str(names))
140
- hap(["worksheet", "delete", ws_id, "-a", app_id], check=False)
153
+ hap(["worksheet", "delete", ws_id, "-a", app_id, "-y"], check=False)
141
154
 
142
155
 
143
156
  def _roles(app_id: str) -> dict[str, str]:
@@ -153,32 +166,28 @@ def case_role(app_id: str, section_id: str) -> None:
153
166
  existing = _roles(app_id)
154
167
  for n in (name, renamed):
155
168
  if n in existing:
156
- hap(["app", "role", "delete", existing[n], "-a", app_id], check=False)
169
+ hap(["app", "role", "delete", existing[n], "-a", app_id, "-y"], check=False)
157
170
  who = hap(["auth", "whoami"])
158
171
  me = who.get("id") if isinstance(who, dict) else ""
159
- rc, out, err = editor_apply({"app": app_id, "ops": [
160
- {"type": "role.create", "name": name, "description": "live test role"},
161
- ]})
162
- check("apply role.create succeeds", rc == 0, err or out)
172
+ ok, res = _try(["app", "role", "create", "--name", name,
173
+ "--description", "live test role", "--type", "0",
174
+ "--permission-scope", "80", "-a", app_id])
175
+ check("role create succeeds", ok, str(res))
163
176
  check("role exists after create", name in _roles(app_id))
164
- rc, out, err = editor_apply({"app": app_id, "ops": [
165
- {"type": "role.update", "role": name, "rename": renamed},
166
- ]})
167
- check("apply role.update rename succeeds", rc == 0, err or out)
177
+ role_id = _roles(app_id).get(name, "")
178
+ ok, res = _try(["app", "role", "rename", app_id, role_id, "-n", renamed])
179
+ check("role rename succeeds", ok, str(res))
168
180
  check("role renamed", renamed in _roles(app_id) and name not in _roles(app_id))
169
- current = renamed if renamed in _roles(app_id) else name
170
- rc, out, err = editor_apply({"app": app_id, "ops": [
171
- {"type": "role.add_member", "role": current,
172
- "members": {"user_ids": [me]}},
173
- {"type": "role.remove_member", "role": current,
174
- "members": {"user_ids": [me]}},
175
- ]})
176
- check("apply role member add+remove succeed", rc == 0, err or out)
177
- rc, out, err = editor_apply({"app": app_id, "ops": [
178
- {"type": "role.delete", "role": current, "confirm": True},
179
- ]})
180
- check("apply role.delete succeeds", rc == 0, err or out)
181
- check("role gone after delete", current not in _roles(app_id))
181
+ current_id = _roles(app_id).get(renamed) or role_id
182
+ ok, res = _try(["app", "role", "add-member", current_id,
183
+ "--user-ids", me, "-a", app_id])
184
+ check("role add-member succeeds", ok, str(res))
185
+ ok, res = _try(["app", "role", "remove-member", current_id,
186
+ "--user-ids", me, "-a", app_id])
187
+ check("role remove-member succeeds", ok, str(res))
188
+ ok, res = _try(["app", "role", "delete", current_id, "-a", app_id, "-y"])
189
+ check("role delete succeeds", ok, str(res))
190
+ check("role gone after delete", renamed not in _roles(app_id))
182
191
 
183
192
 
184
193
  def _actions(ws_id: str) -> list[dict]:
@@ -205,14 +214,14 @@ def case_custom_action(app_id: str, section_id: str) -> None:
205
214
  btn = next((b for b in _actions(ws_id) if b.get("name") == "标记"), {})
206
215
  btn_ref = btn.get("btnId") or btn.get("id")
207
216
  if btn_ref:
208
- rc, out, err = editor_apply({"app": app_id, "ops": [
209
- {"type": "custom-action.delete", "worksheet": name,
210
- "action": btn_ref, "confirm": True},
211
- ]})
212
- check("apply custom-action.delete succeeds", rc == 0, err or out)
217
+ # custom-action.delete is retired from the edit-spec; use the bare
218
+ # command instead.
219
+ ok, res = _try(["worksheet", "delete-custom-action", ws_id, btn_ref,
220
+ "-a", app_id, "-y"])
221
+ check("delete-custom-action succeeds", ok, str(res))
213
222
  names = {b.get("name") for b in _actions(ws_id)}
214
223
  check("custom action gone after delete", "标记" not in names, str(names))
215
- hap(["worksheet", "delete", ws_id, "-a", app_id], check=False)
224
+ hap(["worksheet", "delete", ws_id, "-a", app_id, "-y"], check=False)
216
225
 
217
226
 
218
227
  def _items(app_id: str) -> dict[str, str]:
@@ -233,68 +242,80 @@ def _workflow_names(app_id: str) -> set[str]:
233
242
 
234
243
 
235
244
  def case_chatbot(app_id: str, section_id: str) -> None:
236
- print("scenario: chatbot create/update/delete")
245
+ print("scenario: chatbot create/update/delete (bare commands)")
237
246
  name = "LT-bot"
247
+ # NOTE: --permanent is a silent server-side no-op for chatbots (returns
248
+ # true but the item stays); recycle-bin delete actually removes it.
238
249
  if name in _items(app_id):
239
- hap(["worksheet", "delete", _items(app_id)[name], "-a", app_id], check=False)
240
- rc, out, err = editor_apply({"app": app_id, "ops": [
241
- {"type": "chatbot.create", "name": name, "prompt": "你是测试助手"},
242
- ]})
243
- check("apply chatbot.create succeeds", rc == 0, err or out)
250
+ hap(["app", "chatbot", "delete", _items(app_id)[name], "-a", app_id,
251
+ "--section-id", section_id, "-y"], check=False)
252
+ ok, res = _try(["app", "chatbot", "create", app_id, name,
253
+ "--section-id", section_id, "--prompt", "你是测试助手"])
254
+ check("chatbot create succeeds", ok, str(res))
244
255
  check("chatbot present after create", name in _items(app_id))
245
- rc, out, err = editor_apply({"app": app_id, "ops": [
246
- {"type": "chatbot.update", "chatbot": name, "welcome_text": "你好"},
247
- {"type": "chatbot.delete", "chatbot": name, "confirm": True},
248
- ]})
249
- check("apply chatbot.update+delete succeed", rc == 0, err or out)
256
+ bot_id = _items(app_id).get(name, "")
257
+ ok, res = _try(["app", "chatbot", "update-config", bot_id,
258
+ "--welcome-text", "你好"])
259
+ check("chatbot update-config succeeds", ok, str(res))
260
+ ok, res = _try(["app", "chatbot", "delete", bot_id, "-a", app_id,
261
+ "--section-id", section_id, "-y"])
262
+ check("chatbot delete succeeds", ok, str(res))
250
263
  check("chatbot gone after delete", name not in _items(app_id))
251
264
 
252
265
 
253
266
  def case_custom_page(app_id: str, section_id: str) -> None:
254
- print("scenario: custom-page create/update/delete")
267
+ print("scenario: custom-page create/rename/delete (bare commands)")
255
268
  name, renamed = "LT-page", "LT-page-2"
256
269
  items = _items(app_id)
257
270
  for n in (name, renamed):
258
271
  if n in items:
259
272
  hap(["custom-page", "delete", app_id, items[n],
260
273
  "--section-id", section_id, "--yes"], check=False)
261
- rc, out, err = editor_apply({"app": app_id, "ops": [
262
- {"type": "custom-page.create", "name": name},
263
- ]})
264
- check("apply custom-page.create succeeds", rc == 0, err or out)
274
+ ok, res = _try(["custom-page", "create", app_id, name,
275
+ "--section-id", section_id])
276
+ check("custom-page create succeeds", ok, str(res))
265
277
  check("page present after create", name in _items(app_id))
266
- rc, out, err = editor_apply({"app": app_id, "ops": [
267
- {"type": "custom-page.update", "page": name, "name": renamed},
268
- ]})
269
- check("apply custom-page.update succeeds", rc == 0, err or out)
278
+ page_id = _items(app_id).get(name, "")
279
+ ok, res = _try(["custom-page", "rename", app_id, page_id,
280
+ "--section-id", section_id, "--name", renamed])
281
+ check("custom-page rename succeeds", ok, str(res))
270
282
  check("page renamed", renamed in _items(app_id) and name not in _items(app_id))
271
- rc, out, err = editor_apply({"app": app_id, "ops": [
272
- {"type": "custom-page.delete", "page": renamed, "confirm": True},
273
- ]})
274
- check("apply custom-page.delete succeeds", rc == 0, err or out)
283
+ ok, res = _try(["custom-page", "delete", app_id, page_id,
284
+ "--section-id", section_id, "--yes"])
285
+ check("custom-page delete succeeds", ok, str(res))
275
286
  check("page gone after delete", renamed not in _items(app_id))
276
287
 
277
288
 
289
+ def _make_workflow(app_id: str, name: str) -> str:
290
+ """Create a scheduled (type 5) workflow and return its process id.
291
+
292
+ A worksheet-triggered (type 1) workflow with no trigger bound is
293
+ excluded from the list by HAP, so it can't be resolved by name. A
294
+ scheduled workflow lists immediately.
295
+ """
296
+ _, org_id = state.ensure_app()
297
+ res = hap(["workflow", "create", "-c", org_id, "-n", name,
298
+ "-a", app_id, "--type", "5"], check=False)
299
+ pid = res.get("id") if isinstance(res, dict) else None
300
+ if not pid:
301
+ data = hap(["workflow", "list", app_id], check=False)
302
+ if isinstance(data, dict):
303
+ data = data.get("list") or data.get("data") or []
304
+ pid = next((w.get("id") for w in data if w.get("name") == name), None)
305
+ return pid or ""
306
+
307
+
278
308
  def case_workflow(app_id: str, section_id: str) -> None:
279
- print("scenario: workflow create/update/delete")
309
+ print("scenario: workflow create/update/delete (bare commands)")
280
310
  name, renamed = "LT-flow", "LT-flow-2"
281
- # Use a scheduled (type 5) trigger: a worksheet-triggered (type 1)
282
- # workflow with no trigger bound is excluded from the list by HAP, so
283
- # it can't be resolved by name. A scheduled workflow lists immediately.
284
- rc, out, err = editor_apply({"app": app_id, "ops": [
285
- {"type": "workflow.create", "name": name, "trigger_type": 5},
286
- ]})
287
- check("apply workflow.create succeeds", rc == 0, err or out)
311
+ pid = _make_workflow(app_id, name)
312
+ check("workflow create succeeds", bool(pid))
288
313
  check("workflow present after create", name in _workflow_names(app_id))
289
- rc, out, err = editor_apply({"app": app_id, "ops": [
290
- {"type": "workflow.update", "workflow": name, "name": renamed},
291
- ]})
292
- check("apply workflow.update succeeds", rc == 0, err or out)
314
+ ok, res = _try(["workflow", "update", pid, "-n", renamed])
315
+ check("workflow update succeeds", ok, str(res))
293
316
  check("workflow renamed", renamed in _workflow_names(app_id))
294
- rc, out, err = editor_apply({"app": app_id, "ops": [
295
- {"type": "workflow.delete", "workflow": renamed, "confirm": True},
296
- ]})
297
- check("apply workflow.delete succeeds", rc == 0, err or out)
317
+ ok, res = _try(["workflow", "delete", pid, "-y"])
318
+ check("workflow delete succeeds", ok, str(res))
298
319
  check("workflow gone after delete", renamed not in _workflow_names(app_id))
299
320
 
300
321
 
@@ -350,35 +371,35 @@ def _node_names(pid: str) -> set[str]:
350
371
  return {n.get("name") for n in m.values()}
351
372
 
352
373
 
374
+ def _node_map(pid: str) -> dict:
375
+ data = hap(["workflow", "node", "list", pid], check=False)
376
+ return data.get("flowNodeMap", {}) if isinstance(data, dict) else {}
377
+
378
+
353
379
  def case_node(app_id: str, section_id: str) -> None:
354
- print("scenario: workflow node add/rename/delete")
380
+ print("scenario: workflow node add/rename/delete (bare commands)")
355
381
  name = "LT-nodeflow"
356
- existing = _workflow_names(app_id)
357
382
  # create a scheduled workflow to host nodes
358
- rc, out, err = editor_apply({"app": app_id, "ops": [
359
- {"type": "workflow.create", "name": name, "trigger_type": 5},
360
- ]})
361
- check("apply workflow.create (for nodes) succeeds", rc == 0, err or out)
362
- data = hap(["workflow", "list", app_id], check=False)
363
- if isinstance(data, dict):
364
- data = data.get("list") or data.get("data") or []
365
- pid = next((w.get("id") for w in data if w.get("name") == name), None)
366
- rc, out, err = editor_apply({"app": app_id, "ops": [
367
- {"type": "node.add", "workflow": name,
368
- "node": {"name": "测试节点", "node_type": 13}},
369
- ]})
370
- check("apply node.add succeeds", rc == 0, err or out)
383
+ pid = _make_workflow(app_id, name)
384
+ check("workflow create (for nodes) succeeds", bool(pid))
385
+ start_id = next((nid for nid, n in _node_map(pid).items()
386
+ if n.get("typeId") == 0), None)
387
+ check("start node resolved", bool(start_id), str(_node_map(pid)))
388
+ ok, res = _try(["workflow", "node", "add", pid, "--type", "13",
389
+ "-n", "测试节点", "--after", start_id])
390
+ check("node add succeeds", ok, str(res))
371
391
  check("node present after add", "测试节点" in _node_names(pid),
372
392
  str(_node_names(pid)))
373
- rc, out, err = editor_apply({"app": app_id, "ops": [
374
- {"type": "node.rename", "workflow": name, "node": "测试节点",
375
- "name": "改名节点"},
376
- {"type": "node.delete", "workflow": name, "node": "改名节点",
377
- "confirm": True},
378
- ]})
379
- check("apply node.rename+delete succeed", rc == 0, err or out)
393
+ node_id = next((nid for nid, n in _node_map(pid).items()
394
+ if n.get("name") == "测试节点"), "")
395
+ ok, res = _try(["workflow", "node", "rename", pid, node_id,
396
+ "-n", "改名节点"])
397
+ check("node rename succeeds", ok, str(res))
398
+ check("node renamed", "改名节点" in _node_names(pid), str(_node_names(pid)))
399
+ ok, res = _try(["workflow", "node", "delete", pid, node_id, "-y"])
400
+ check("node delete succeeds", ok, str(res))
380
401
  names = _node_names(pid)
381
- check("node renamed then deleted",
402
+ check("node gone after delete",
382
403
  "改名节点" not in names and "测试节点" not in names, str(names))
383
404
  hap(["workflow", "delete", pid, "--yes"], check=False)
384
405
 
@@ -389,29 +410,25 @@ def _sections(app_id: str) -> dict[str, str]:
389
410
 
390
411
 
391
412
  def case_application(app_id: str, section_id: str) -> None:
392
- print("scenario: app.update + section add/update/delete")
413
+ print("scenario: app update + section add/edit/delete (bare commands)")
393
414
  name, renamed = "LT-section", "LT-section-2"
394
415
  secs = _sections(app_id)
395
416
  for n in (name, renamed):
396
417
  if n in secs:
397
418
  hap(["app", "delete-section", app_id, secs[n], "-y"], check=False)
398
- # app.update: set nav color (cosmetic, no restore needed)
399
- rc, out, err = editor_apply({"app": app_id, "ops": [
400
- {"type": "app.update", "nav_color": "#2296F3"},
401
- {"type": "section.add", "name": name},
402
- ]})
403
- check("apply app.update + section.add succeed", rc == 0, err or out)
419
+ # app update: set nav color (cosmetic, no restore needed)
420
+ ok, res = _try(["app", "update", app_id, "--nav-color", "#2296F3"])
421
+ check("app update succeeds", ok, str(res))
422
+ ok, res = _try(["app", "add-section", app_id, "-n", name])
423
+ check("add-section succeeds", ok, str(res))
404
424
  check("section present after add", name in _sections(app_id))
405
- rc, out, err = editor_apply({"app": app_id, "ops": [
406
- {"type": "section.update", "section": name, "name": renamed},
407
- ]})
408
- check("apply section.update succeeds", rc == 0, err or out)
425
+ sec_id = _sections(app_id).get(name, "")
426
+ ok, res = _try(["app", "edit-section", app_id, sec_id, "-n", renamed])
427
+ check("edit-section succeeds", ok, str(res))
409
428
  check("section renamed", renamed in _sections(app_id)
410
429
  and name not in _sections(app_id))
411
- rc, out, err = editor_apply({"app": app_id, "ops": [
412
- {"type": "section.delete", "section": renamed, "confirm": True},
413
- ]})
414
- check("apply section.delete succeeds", rc == 0, err or out)
430
+ ok, res = _try(["app", "delete-section", app_id, sec_id, "-y"])
431
+ check("delete-section succeeds", ok, str(res))
415
432
  check("section gone after delete", renamed not in _sections(app_id))
416
433
 
417
434
 
@@ -497,6 +514,28 @@ def case_app_logs(app_id: str, section_id: str) -> None:
497
514
  f"foreign apps: {sorted(foreign)}")
498
515
 
499
516
 
517
+ def case_retired_op_rejected(app_id: str, section_id: str) -> None:
518
+ """`hap app-editor validate` must reject retired ops and point at the
519
+ bare CLI replacement."""
520
+ print("scenario: retired edit-spec op rejected by validate")
521
+ spec = {"app": app_id, "ops": [
522
+ {"type": "view.update", "worksheet": "x", "view": "y", "name": "z"},
523
+ ]}
524
+ with tempfile.NamedTemporaryFile(
525
+ "w", suffix=".edit.json", delete=False, encoding="utf-8") as fh:
526
+ json.dump(spec, fh, ensure_ascii=False)
527
+ path = fh.name
528
+ try:
529
+ rc, out, err = editor(["validate", path])
530
+ finally:
531
+ Path(path).unlink(missing_ok=True)
532
+ check("validate exits non-zero for retired op", rc != 0,
533
+ f"rc={rc} out={out!r}")
534
+ blob = err + out
535
+ check("error points at bare command",
536
+ "hap worksheet view update" in blob, blob.strip())
537
+
538
+
500
539
  _CASES = {
501
540
  "worksheet": case_worksheet,
502
541
  "field": case_field,
@@ -509,6 +548,7 @@ _CASES = {
509
548
  "workflow": case_workflow,
510
549
  "node": case_node,
511
550
  "application": case_application,
551
+ "retired-op": case_retired_op_rejected,
512
552
  "app-usage": case_app_usage,
513
553
  "app-export": case_app_export,
514
554
  "app-logs": case_app_logs,
@@ -1,3 +1,3 @@
1
1
  """CLI harness for MingDAO HAP (hap-cli)."""
2
2
 
3
- __version__ = "0.8.0"
3
+ __version__ = "0.8.2"
@@ -154,7 +154,7 @@ def app_unselect(ctx):
154
154
  @click.option(
155
155
  "--configuration", "configuratiuon",
156
156
  default="",
157
- help="JSON link-configuration blob (upstream field name is 'configuratiuon' — sic)",
157
+ help="JSON link-configuration blob",
158
158
  )
159
159
  @click.option(
160
160
  "--pc-display/--no-pc-display", "pc_display", default=None,
@@ -224,6 +224,7 @@ def app_create(
224
224
  @click.argument("app_id")
225
225
  @click.option("--name", "-n", default="", help="New name")
226
226
  @click.option("--desc", "-d", default="", help="New description")
227
+ @click.option("--icon", default="", help="Icon name, e.g. 0_lego")
227
228
  @click.option("--icon-color", default="", help="Icon color hex")
228
229
  @click.option("--nav-color", default="", help="Navigation bar color hex")
229
230
  @click.option(
@@ -238,7 +239,7 @@ def app_create(
238
239
  help="Show-icon flag (3 bits, e.g. 011). Defaults by nav style.",
239
240
  )
240
241
  @pass_context
241
- def app_update(ctx, app_id, name, desc, icon_color, nav_color,
242
+ def app_update(ctx, app_id, name, desc, icon, icon_color, nav_color,
242
243
  pc_nav_style, display_icon):
243
244
  """Update application information (incl. PC navigation style)."""
244
245
  try:
@@ -251,7 +252,7 @@ def app_update(ctx, app_id, name, desc, icon_color, nav_color,
251
252
  # pcNavList[*].defaultDisplayIcon).
252
253
  di = "100" if nav == 3 else "011"
253
254
  data = app_mod.update_app(
254
- session, app_id, name=name, description=desc,
255
+ session, app_id, name=name, description=desc, icon=icon,
255
256
  icon_color=icon_color, nav_color=nav_color,
256
257
  pc_nav_style=nav, display_icon=di,
257
258
  )
@@ -318,13 +319,43 @@ def app_delete(ctx, app_id, org_id, yes):
318
319
  @app.command("add-section")
319
320
  @click.argument("app_id")
320
321
  @click.option("--name", "-n", default="", help="Section name")
322
+ @click.option(
323
+ "--sections-json", default="",
324
+ help='Several sections at once, as a JSON array, e.g. '
325
+ '\'[{"name": "Ops", "icon": "folder"}]\' '
326
+ '(alternative to --name)',
327
+ )
328
+ @click.option(
329
+ "--parent-id", default="",
330
+ help="Parent section ID; omit to create at the top level",
331
+ )
321
332
  @pass_context
322
- def app_add_section(ctx, app_id, name):
323
- """Add a section/group to an application."""
333
+ def app_add_section(ctx, app_id, name, sections_json, parent_id):
334
+ """Add one or more sections/groups to an application."""
324
335
  try:
336
+ import json as _json
337
+ if name and sections_json:
338
+ raise click.UsageError(
339
+ "Use either --name or --sections-json, not both.")
325
340
  session = ctx.get_session()
326
- data = app_mod.add_section(session, app_id, name)
327
- ctx.output(data, lambda d: click.echo(f"Section added: {d}"))
341
+ if sections_json:
342
+ try:
343
+ parsed = _json.loads(sections_json)
344
+ except ValueError as e:
345
+ raise click.UsageError(f"--sections-json is not valid JSON: {e}")
346
+ try:
347
+ sections = app_mod.v3_sections_to_add_section_calls(parsed)
348
+ except ValueError as e:
349
+ raise click.UsageError(f"--sections-json: {e}")
350
+ data = app_mod.add_sections_batch(
351
+ session, app_id, sections, parent_id=parent_id,
352
+ )
353
+ ctx.output(data, lambda d: click.echo(f"Sections added: {d}"))
354
+ else:
355
+ data = app_mod.add_section(
356
+ session, app_id, name, parent_id=parent_id,
357
+ )
358
+ ctx.output(data, lambda d: click.echo(f"Section added: {d}"))
328
359
  except Exception as e:
329
360
  ctx.handle_error(e)
330
361
 
@@ -382,19 +413,27 @@ def app_backup(ctx, app_id, org_id, no_data):
382
413
  @app.command("restore")
383
414
  @click.argument("app_id")
384
415
  @org_id_option()
385
- @click.option("--backup-id", "-b", required=True, help="Backup ID")
386
- @click.option("--file-url", required=True, help="Backup file URL")
387
- @click.option("--file-name", required=True, help="Backup file name")
416
+ @click.option("--backup-id", "-b", default="", help="Restore from an existing backup (its ID)")
417
+ @click.option("--file-url", default="", help="Restore from an uploaded backup file (its URL)")
418
+ @click.option("--file-name", default="", help="File name to show for an uploaded backup file")
388
419
  @click.option("--no-data", is_flag=True, help="Restore without data")
389
420
  @click.option("--as-new", is_flag=True, help="Restore as a new app")
390
421
  @pass_context
391
422
  def app_restore(ctx, app_id, org_id, backup_id, file_url, file_name, no_data, as_new):
392
- """Restore an application from backup."""
423
+ """Restore an application, either from an existing backup or an uploaded file.
424
+
425
+ Provide exactly one source: --backup-id (an existing backup) or
426
+ --file-url (an uploaded backup file).
427
+ """
393
428
  try:
429
+ if bool(backup_id) == bool(file_url):
430
+ raise click.UsageError(
431
+ "Provide exactly one of --backup-id or --file-url."
432
+ )
394
433
  session = ctx.get_session()
395
434
  data = app_mod.restore_app(
396
- session, app_id, require_api_project_id(session, org_id), backup_id,
397
- file_url, file_name,
435
+ session, app_id, require_api_project_id(session, org_id),
436
+ backup_id=backup_id, file_url=file_url, file_name=file_name,
398
437
  contain_data=not no_data, is_restore_new=as_new,
399
438
  )
400
439
  ctx.output(data, lambda d: click.echo(f"Restore started: {d}"))
@@ -2,8 +2,12 @@
2
2
 
3
3
  Thin Click layer over the editor engine in ``hap_cli.core.editor``:
4
4
  edit-spec validation, live inspection, dry-run planning, and applying
5
- single-element changes (worksheet / field / view / role / custom-action /
6
- workflow / custom-page / component / chatbot / node / app / section).
5
+ the edits whose safe orchestration lives in the engine worksheet
6
+ fields (full-control-set write-back), custom-page components
7
+ (layout read-modify-write), and custom-action create/update (high-level
8
+ spec translation). Every other element edit is a single API call served
9
+ by a plain ``hap`` command; retired op types are rejected with the
10
+ replacement command (see ``editspec.RETIRED_OPS``).
7
11
 
8
12
  The edit-spec schemas are documented and shipped with the
9
13
  ``hap-cli-app-editor`` skill; this command resolves them at runtime.