hap-cli 0.8.3__tar.gz → 0.8.5__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 (211) hide show
  1. {hap_cli-0.8.3 → hap_cli-0.8.5}/PKG-INFO +9 -2
  2. {hap_cli-0.8.3/hap_cli → hap_cli-0.8.5}/README.md +8 -1
  3. {hap_cli-0.8.3 → hap_cli-0.8.5/hap_cli}/README.md +8 -1
  4. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/README_CN.md +8 -1
  5. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/__init__.py +1 -1
  6. hap_cli-0.8.5/hap_cli/__main__.py +11 -0
  7. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/app_cmd.py +0 -31
  8. hap_cli-0.8.5/hap_cli/commands/update_cmd.py +101 -0
  9. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/app.py +0 -49
  10. hap_cli-0.8.5/hap_cli/core/update.py +294 -0
  11. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/hap_cli.py +18 -0
  12. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/locale/messages.json +0 -21
  13. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_core.py +1 -1
  14. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_org_id_cli.py +0 -1
  15. hap_cli-0.8.5/hap_cli/tests/test_update.py +423 -0
  16. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli.egg-info/PKG-INFO +9 -2
  17. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli.egg-info/SOURCES.txt +4 -1
  18. hap_cli-0.8.3/hap_cli/tests/test_app_restore.py +0 -123
  19. {hap_cli-0.8.3 → hap_cli-0.8.5}/MANIFEST.in +0 -0
  20. {hap_cli-0.8.3 → hap_cli-0.8.5}/app_live_tests/__init__.py +0 -0
  21. {hap_cli-0.8.3 → hap_cli-0.8.5}/app_live_tests/__main__.py +0 -0
  22. {hap_cli-0.8.3 → hap_cli-0.8.5}/app_live_tests/config.py +0 -0
  23. {hap_cli-0.8.3 → hap_cli-0.8.5}/app_live_tests/harness.py +0 -0
  24. {hap_cli-0.8.3 → hap_cli-0.8.5}/app_live_tests/smoke.py +0 -0
  25. {hap_cli-0.8.3 → hap_cli-0.8.5}/app_live_tests/state.py +0 -0
  26. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/__init__.py +0 -0
  27. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/app_editor_cmd.py +0 -0
  28. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/auth_cmd.py +0 -0
  29. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/calendar_cmd.py +0 -0
  30. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/chart_cmd.py +0 -0
  31. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/chat_cmd.py +0 -0
  32. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/chatbot_cmd.py +0 -0
  33. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/config_cmd.py +0 -0
  34. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/contact_cmd.py +0 -0
  35. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/department_cmd.py +0 -0
  36. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/group_cmd.py +0 -0
  37. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/icon_cmd.py +0 -0
  38. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/instance_cmd.py +0 -0
  39. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/node_cmd.py +0 -0
  40. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/optionset_cmd.py +0 -0
  41. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/page_cmd.py +0 -0
  42. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/post_cmd.py +0 -0
  43. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/record_cmd.py +0 -0
  44. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/region_cmd.py +0 -0
  45. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/role_cmd.py +0 -0
  46. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/upload_cmd.py +0 -0
  47. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/v3_registry.py +0 -0
  48. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/workflow_cmd.py +0 -0
  49. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/commands/worksheet_cmd.py +0 -0
  50. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/context.py +0 -0
  51. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/__init__.py +0 -0
  52. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/action_spec_adapter.py +0 -0
  53. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/auth.py +0 -0
  54. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/calendar_mod.py +0 -0
  55. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/chart.py +0 -0
  56. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/chat.py +0 -0
  57. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/chatbot.py +0 -0
  58. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/contact.py +0 -0
  59. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/control_type_codes.py +0 -0
  60. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/department.py +0 -0
  61. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/__init__.py +0 -0
  62. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  63. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  64. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/apply.py +0 -0
  65. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/componentlower.py +0 -0
  66. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/editspec.py +0 -0
  67. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/errors.py +0 -0
  68. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/fieldlower.py +0 -0
  69. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  70. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/models.py +0 -0
  71. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/ops.py +0 -0
  72. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/planner.py +0 -0
  73. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/editor/reader.py +0 -0
  74. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/field_normalizer.py +0 -0
  75. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/field_spec_adapter.py +0 -0
  76. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/filter_translator.py +0 -0
  77. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/flow_node.py +0 -0
  78. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/global_meta.py +0 -0
  79. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/group.py +0 -0
  80. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/icon_index.py +0 -0
  81. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/instance.py +0 -0
  82. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/logger.py +0 -0
  83. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/optionset.py +0 -0
  84. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/page.py +0 -0
  85. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/post.py +0 -0
  86. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/record.py +0 -0
  87. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/response_crypto.py +0 -0
  88. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/role.py +0 -0
  89. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/role_perm_builder.py +0 -0
  90. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/session.py +0 -0
  91. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/token_crypto.py +0 -0
  92. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/upload.py +0 -0
  93. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/v3/__init__.py +0 -0
  94. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/v3/dispatcher.py +0 -0
  95. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/v3/render.py +0 -0
  96. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/v3/schema.py +0 -0
  97. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/view_spec_adapter.py +0 -0
  98. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/workflow.py +0 -0
  99. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/workflow_node_dsl.py +0 -0
  100. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/worksheet.py +0 -0
  101. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/core/worksheet_templates.py +0 -0
  102. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/i18n.py +0 -0
  103. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/locale/__init__.py +0 -0
  104. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/locale/messages.schema.json +0 -0
  105. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/__init__.py +0 -0
  106. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/conftest.py +0 -0
  107. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_app_editor.py +0 -0
  108. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_app_export_usage.py +0 -0
  109. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_auth_prerelease.py +0 -0
  110. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_bug_036430.py +0 -0
  111. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_chart.py +0 -0
  112. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_config_cmd.py +0 -0
  113. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
  114. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_full_e2e.py +0 -0
  115. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_global_meta.py +0 -0
  116. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_i18n.py +0 -0
  117. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_integration.py +0 -0
  118. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_integration_approval.py +0 -0
  119. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_integration_approval_actions.py +0 -0
  120. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_integration_calendar.py +0 -0
  121. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_integration_destructive.py +0 -0
  122. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_integration_misc.py +0 -0
  123. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_integration_post.py +0 -0
  124. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_integration_social.py +0 -0
  125. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_integration_v3.py +0 -0
  126. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_integration_workflow.py +0 -0
  127. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  128. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_multi_profile.py +0 -0
  129. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
  130. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_org_id_docs.py +0 -0
  131. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_parameter_conventions.py +0 -0
  132. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  133. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
  134. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
  135. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_semantic_types_registry.py +0 -0
  136. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  137. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  138. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  139. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_v3_session.py +0 -0
  140. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  141. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
  142. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  143. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
  144. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/utils/__init__.py +0 -0
  145. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/utils/formatting.py +0 -0
  146. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/utils/options.py +0 -0
  147. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli/utils/parameter_mapping.py +0 -0
  148. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli.egg-info/dependency_links.txt +0 -0
  149. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli.egg-info/entry_points.txt +0 -0
  150. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli.egg-info/requires.txt +0 -0
  151. {hap_cli-0.8.3 → hap_cli-0.8.5}/hap_cli.egg-info/top_level.txt +0 -0
  152. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/__init__.py +0 -0
  153. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/__main__.py +0 -0
  154. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/_wftest.py +0 -0
  155. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/charts.py +0 -0
  156. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/cleanup.py +0 -0
  157. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/compiler.py +0 -0
  158. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/config.py +0 -0
  159. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/errors.py +0 -0
  160. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/executor.py +0 -0
  161. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/fields.py +0 -0
  162. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/hap.py +0 -0
  163. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/recording/__init__.py +0 -0
  164. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/recording/console.py +0 -0
  165. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/recording/jsonl.py +0 -0
  166. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/recording/mirror.py +0 -0
  167. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/recording/report.py +0 -0
  168. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/schema.py +0 -0
  169. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/seed/__init__.py +0 -0
  170. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/seed/cli.py +0 -0
  171. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/seed/executor.py +0 -0
  172. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/seed/template.py +0 -0
  173. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/selftest.py +0 -0
  174. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/smoke.py +0 -0
  175. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/step.py +0 -0
  176. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/steps.py +0 -0
  177. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/store.py +0 -0
  178. {hap_cli-0.8.3 → hap_cli-0.8.5}/live_tests/workflow_dsl.py +0 -0
  179. {hap_cli-0.8.3 → hap_cli-0.8.5}/setup.cfg +0 -0
  180. {hap_cli-0.8.3 → hap_cli-0.8.5}/setup.py +0 -0
  181. {hap_cli-0.8.3 → hap_cli-0.8.5}/skills/hap-cli-app-editor/scripts/editspec/component.schema.json +0 -0
  182. {hap_cli-0.8.3 → hap_cli-0.8.5}/skills/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +0 -0
  183. {hap_cli-0.8.3 → hap_cli-0.8.5}/skills/hap-cli-app-editor/scripts/editspec/envelope.schema.json +0 -0
  184. {hap_cli-0.8.3 → hap_cli-0.8.5}/skills/hap-cli-app-editor/scripts/editspec/field.schema.json +0 -0
  185. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/INDEX.json +0 -0
  186. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  187. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  188. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/create_optionset.yaml +0 -0
  189. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/create_role.yaml +0 -0
  190. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  191. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/delete_record.yaml +0 -0
  192. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/delete_role.yaml +0 -0
  193. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  194. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_app_info.yaml +0 -0
  195. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  196. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  197. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  198. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_record_details.yaml +0 -0
  199. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  200. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_record_list.yaml +0 -0
  201. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  202. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  203. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  204. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  205. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_regions.yaml +0 -0
  206. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_role_details.yaml +0 -0
  207. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/get_role_list.yaml +0 -0
  208. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  209. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  210. {hap_cli-0.8.3 → hap_cli-0.8.5}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  211. {hap_cli-0.8.3 → hap_cli-0.8.5}/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.3
3
+ Version: 0.8.5
4
4
  Summary: CLI harness for MingDAO HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -155,7 +155,7 @@ discover every subcommand.
155
155
  | `app create-section / edit-section / delete-section` | Section management |
156
156
  | `app sort-sections / sort-worksheets` | Reorder sections / a section's worksheets |
157
157
  | `app create-item` | Batch-create empty worksheets / custom pages |
158
- | `app backup / restore / export / exports / backup-logs` | Backup & export |
158
+ | `app backup / export / exports / backup-logs` | Backup & export |
159
159
  | `app usage / logs` | Statistics and operation logs |
160
160
  | `app role …` | Role CRUD, permissions, members, leave-all, applications |
161
161
  | `app optionset …` | Option set CRUD and cross-app moves |
@@ -313,6 +313,13 @@ body, and a redacted `Authorization` header (first 16 chars). `password`
313
313
  and other sensitive keys are masked. `HAP_LOG_LEVEL=DEBUG` temporarily
314
314
  overrides the persisted level.
315
315
 
316
+ ### update — keep the CLI current
317
+
318
+ | Command | Description |
319
+ |---|---|
320
+ | `update` | Check PyPI for a newer release and upgrade in place, using the same tool the CLI was installed with (pip / pipx / uv) |
321
+ | `update --check` | Only report whether a newer version exists, without installing |
322
+
316
323
  ---
317
324
 
318
325
  ## Default app & organization
@@ -126,7 +126,7 @@ discover every subcommand.
126
126
  | `app create-section / edit-section / delete-section` | Section management |
127
127
  | `app sort-sections / sort-worksheets` | Reorder sections / a section's worksheets |
128
128
  | `app create-item` | Batch-create empty worksheets / custom pages |
129
- | `app backup / restore / export / exports / backup-logs` | Backup & export |
129
+ | `app backup / export / exports / backup-logs` | Backup & export |
130
130
  | `app usage / logs` | Statistics and operation logs |
131
131
  | `app role …` | Role CRUD, permissions, members, leave-all, applications |
132
132
  | `app optionset …` | Option set CRUD and cross-app moves |
@@ -284,6 +284,13 @@ body, and a redacted `Authorization` header (first 16 chars). `password`
284
284
  and other sensitive keys are masked. `HAP_LOG_LEVEL=DEBUG` temporarily
285
285
  overrides the persisted level.
286
286
 
287
+ ### update — keep the CLI current
288
+
289
+ | Command | Description |
290
+ |---|---|
291
+ | `update` | Check PyPI for a newer release and upgrade in place, using the same tool the CLI was installed with (pip / pipx / uv) |
292
+ | `update --check` | Only report whether a newer version exists, without installing |
293
+
287
294
  ---
288
295
 
289
296
  ## Default app & organization
@@ -126,7 +126,7 @@ discover every subcommand.
126
126
  | `app create-section / edit-section / delete-section` | Section management |
127
127
  | `app sort-sections / sort-worksheets` | Reorder sections / a section's worksheets |
128
128
  | `app create-item` | Batch-create empty worksheets / custom pages |
129
- | `app backup / restore / export / exports / backup-logs` | Backup & export |
129
+ | `app backup / export / exports / backup-logs` | Backup & export |
130
130
  | `app usage / logs` | Statistics and operation logs |
131
131
  | `app role …` | Role CRUD, permissions, members, leave-all, applications |
132
132
  | `app optionset …` | Option set CRUD and cross-app moves |
@@ -284,6 +284,13 @@ body, and a redacted `Authorization` header (first 16 chars). `password`
284
284
  and other sensitive keys are masked. `HAP_LOG_LEVEL=DEBUG` temporarily
285
285
  overrides the persisted level.
286
286
 
287
+ ### update — keep the CLI current
288
+
289
+ | Command | Description |
290
+ |---|---|
291
+ | `update` | Check PyPI for a newer release and upgrade in place, using the same tool the CLI was installed with (pip / pipx / uv) |
292
+ | `update --check` | Only report whether a newer version exists, without installing |
293
+
287
294
  ---
288
295
 
289
296
  ## Default app & organization
@@ -117,7 +117,7 @@ hap --json worksheet record list WORKSHEET_ID
117
117
  | `app create-section / edit-section / delete-section` | 分组管理 |
118
118
  | `app sort-sections / sort-worksheets` | 重排分组 / 分组内工作表顺序 |
119
119
  | `app create-item` | 批量创建空白工作表/自定义页面 |
120
- | `app backup / restore / export / exports / backup-logs` | 备份与导出 |
120
+ | `app backup / export / exports / backup-logs` | 备份与导出 |
121
121
  | `app usage / logs` | 使用统计与操作日志 |
122
122
  | `app role …` | 角色 CRUD、权限、成员、leave-all、申请 |
123
123
  | `app optionset …` | 选项集 CRUD 与跨应用迁移 |
@@ -260,6 +260,13 @@ hap --json worksheet record list WORKSHEET_ID
260
260
 
261
261
  日志默认写入 `~/.hap-cli/hap-cli.log`,10 MB × 3 滚动。开启后每次 API 调用以 `INFO` 级别记录 URL、请求体、响应体,以及脱敏后的 `Authorization` 头(仅前 16 字符);`password` 等敏感字段会被自动打码。临时调试可设 `HAP_LOG_LEVEL=DEBUG`。
262
262
 
263
+ ### update — 升级 CLI
264
+
265
+ | 命令 | 说明 |
266
+ |---|---|
267
+ | `update` | 检查 PyPI 上的最新版本,有新版则用安装时的工具(pip / pipx / uv)原地升级 |
268
+ | `update --check` | 只检查是否有新版本,不安装 |
269
+
263
270
  ---
264
271
 
265
272
  ## 默认应用与组织
@@ -1,3 +1,3 @@
1
1
  """CLI harness for MingDAO HAP (hap-cli)."""
2
2
 
3
- __version__ = "0.8.3"
3
+ __version__ = "0.8.5"
@@ -0,0 +1,11 @@
1
+ """Allow ``python -m hap_cli`` — used by the detached background
2
+ update child (``hap update --background``), which is spawned with
3
+ ``sys.executable -m hap_cli`` so it always runs in the same
4
+ environment as the parent process regardless of how the ``hap``
5
+ console script was installed.
6
+ """
7
+
8
+ from hap_cli.hap_cli import main
9
+
10
+ if __name__ == "__main__":
11
+ main()
@@ -410,37 +410,6 @@ def app_backup(ctx, app_id, org_id, no_data):
410
410
  ctx.handle_error(e)
411
411
 
412
412
 
413
- @app.command("restore")
414
- @click.argument("app_id")
415
- @org_id_option()
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")
419
- @click.option("--no-data", is_flag=True, help="Restore without data")
420
- @click.option("--as-new", is_flag=True, help="Restore as a new app")
421
- @pass_context
422
- def app_restore(ctx, app_id, org_id, backup_id, file_url, file_name, no_data, as_new):
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
- """
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
- )
433
- session = ctx.get_session()
434
- data = app_mod.restore_app(
435
- session, app_id, require_api_project_id(session, org_id),
436
- backup_id=backup_id, file_url=file_url, file_name=file_name,
437
- contain_data=not no_data, is_restore_new=as_new,
438
- )
439
- ctx.output(data, lambda d: click.echo(f"Restore started: {d}"))
440
- except Exception as e:
441
- ctx.handle_error(e)
442
-
443
-
444
413
  @app.command("export")
445
414
  @click.argument("app_ids", nargs=-1, required=True)
446
415
  @org_id_option()
@@ -0,0 +1,101 @@
1
+ """CLI command for self-updating hap-cli from PyPI.
2
+
3
+ The hidden ``--background`` flag is the entry point for the detached
4
+ child process spawned by the exit-time hook in ``hap_cli.py`` — it is
5
+ internal plumbing and intentionally absent from ``--help`` and docs.
6
+ """
7
+
8
+ import time
9
+
10
+ import click
11
+
12
+ from hap_cli.context import pass_context
13
+ from hap_cli.core import update as upd
14
+
15
+
16
+ @click.command("update")
17
+ @click.option(
18
+ "--check",
19
+ "check_only",
20
+ is_flag=True,
21
+ help="Only check whether a newer version exists; do not install.",
22
+ )
23
+ @click.option("--background", is_flag=True, hidden=True)
24
+ @pass_context
25
+ def update(ctx, check_only, background):
26
+ """Upgrade hap-cli to the latest version from PyPI.
27
+
28
+ Checks for a newer release and installs it using the same tool the
29
+ CLI was installed with (pip, pipx, or uv). Installs that are not
30
+ managed from PyPI are left untouched.
31
+ """
32
+ if background:
33
+ # Detached child: silent check-and-upgrade; results go to the
34
+ # state file and are surfaced on the next foreground run.
35
+ upd.run_background_update()
36
+ return
37
+
38
+ from hap_cli import __version__
39
+
40
+ kind = upd.get_install_kind()
41
+ if kind in ("editable", "unknown"):
42
+ ctx.output(
43
+ {"current": __version__, "install": kind, "updated": False},
44
+ lambda d: click.echo(
45
+ "This copy of hap-cli is not managed from PyPI "
46
+ f"(current version {__version__}) — nothing to update."
47
+ ),
48
+ )
49
+ return
50
+
51
+ latest = upd.fetch_latest_version()
52
+ if latest is None:
53
+ ctx.handle_error(Exception("Could not reach PyPI to check for updates."))
54
+ return
55
+
56
+ if not upd.is_newer(latest, __version__):
57
+ upd.save_state(
58
+ {
59
+ "last_check_at": time.time(),
60
+ "latest_version": latest,
61
+ "last_result": "up_to_date",
62
+ "notified": True,
63
+ }
64
+ )
65
+ ctx.output(
66
+ {"current": __version__, "latest": latest, "updated": False},
67
+ lambda d: click.echo(
68
+ f"hap-cli {__version__} is already the latest version."
69
+ ),
70
+ )
71
+ return
72
+
73
+ if check_only:
74
+ ctx.output(
75
+ {"current": __version__, "latest": latest, "updated": False},
76
+ lambda d: click.echo(
77
+ f"A newer version is available: {latest} "
78
+ f"(current {__version__}). Run `hap update` to upgrade."
79
+ ),
80
+ )
81
+ return
82
+
83
+ click.echo(f"Upgrading hap-cli {__version__} -> {latest} ...")
84
+ ok, output = upd.perform_upgrade(kind)
85
+ upd.save_state(
86
+ {
87
+ "last_check_at": time.time(),
88
+ "latest_version": latest,
89
+ "last_result": "upgraded" if ok else "upgrade_failed",
90
+ "upgraded_to": latest if ok else None,
91
+ "notified": True,
92
+ }
93
+ )
94
+ if ok:
95
+ ctx.output(
96
+ {"current": __version__, "latest": latest, "updated": True},
97
+ lambda d: click.echo(f"Upgraded to hap-cli {latest}."),
98
+ )
99
+ else:
100
+ click.echo(output, err=True)
101
+ ctx.handle_error(Exception(f"Upgrade to {latest} failed."))
@@ -620,55 +620,6 @@ def backup_app(
620
620
  )
621
621
 
622
622
 
623
- def restore_app(
624
- session: Session,
625
- app_id: str,
626
- project_id: str,
627
- backup_id: str = "",
628
- file_url: str = "",
629
- file_name: str = "",
630
- contain_data: bool = True,
631
- is_restore_new: bool = False,
632
- auto_end_maintain: bool = False,
633
- backup_current_version: bool = False,
634
- ) -> dict[str, Any]:
635
- """Restore an application from a backup log id or an uploaded file.
636
-
637
- The HAP UI has two mutually exclusive restore modes; the API field
638
- set differs between them, so we must send only one mode's fields:
639
-
640
- - Backup-log restore (``src/pages/AppSettings/components/
641
- appBackupRestore/components/BackupFiles.jsx``): sends ``id`` (the
642
- backup-restore operation-log id) plus ``autoEndMaintain``,
643
- ``backupCurrentVersion`` and ``isRestoreNew`` — no ``fileUrl``.
644
- - Uploaded-file restore (``.../BackupFromFiles.jsx``): sends
645
- ``fileUrl`` + ``fileName`` + ``containData`` plus the same three
646
- flags — no ``id``.
647
-
648
- Sending both at once (as an earlier wrapper did, with all three
649
- forced ``required``) made the command impossible to invoke. Exactly
650
- one of ``backup_id`` / ``file_url`` must be supplied.
651
- """
652
- if not backup_id and not file_url:
653
- raise ValueError("restore requires either backup_id or file_url")
654
-
655
- params: dict[str, Any] = {
656
- "appId": app_id,
657
- "projectId": project_id,
658
- "autoEndMaintain": auto_end_maintain,
659
- "backupCurrentVersion": backup_current_version,
660
- "isRestoreNew": is_restore_new,
661
- }
662
- if backup_id:
663
- # `id` is the backup-restore operation-log id (named `id` in the API).
664
- params["id"] = backup_id
665
- else:
666
- params["fileUrl"] = file_url
667
- params["fileName"] = file_name
668
- params["containData"] = contain_data
669
- return session.api_call("AppManagement", "Restore", params)
670
-
671
-
672
623
  def export_apps(
673
624
  session: Session,
674
625
  project_id: str,
@@ -0,0 +1,294 @@
1
+ """Self-update support for hap-cli.
2
+
3
+ Two entry paths share this module:
4
+
5
+ - ``hap update`` — synchronous, user-invoked check/upgrade.
6
+ - ``hap update --background`` — hidden; spawned as a detached child
7
+ process when a command exits and the last check is older than
8
+ ``UPDATE_CHECK_INTERVAL_SECONDS``. It checks PyPI, auto-upgrades if
9
+ newer, and records the outcome in the state file so the *next* run
10
+ can surface a one-line notice.
11
+
12
+ Editable installs (PEP 660 ``direct_url.json`` with
13
+ ``dir_info.editable: true``) and unrecognized install origins disable
14
+ the feature entirely — they are by definition not managed via PyPI.
15
+ """
16
+
17
+ import json
18
+ import logging
19
+ import os
20
+ import re
21
+ import subprocess
22
+ import sys
23
+ import time
24
+ from typing import Any, Optional
25
+
26
+ import requests
27
+
28
+ logger = logging.getLogger("hap_cli.update")
29
+
30
+ PYPI_PACKAGE = "hap-cli"
31
+ PYPI_JSON_URL = f"https://pypi.org/pypi/{PYPI_PACKAGE}/json"
32
+
33
+ # Deliberately short while the tool is iterating quickly; once releases
34
+ # stabilize, bump this in a regular release (it ships with the code, so
35
+ # every upgrade can retune the cadence).
36
+ UPDATE_CHECK_INTERVAL_SECONDS = 3600
37
+
38
+ # Only final releases (digits and dots) are auto-upgrade candidates;
39
+ # pre/dev/post markers ("0.9.0rc1", "1.0.dev2") are skipped.
40
+ _FINAL_RELEASE_RE = re.compile(r"^\d+(\.\d+)*$")
41
+
42
+
43
+ def _state_file():
44
+ # Resolve lazily through the session module so tests (and
45
+ # HAP_CLI_HOME) that repoint CONFIG_DIR are honored.
46
+ from hap_cli.core import session
47
+
48
+ return session.CONFIG_DIR / "update_check.json"
49
+
50
+
51
+ # ── Install-kind detection ───────────────────────────────────────────────
52
+
53
+
54
+ def get_install_kind() -> str:
55
+ """Classify how this copy of hap-cli was installed.
56
+
57
+ Returns one of ``editable`` / ``pip`` / ``pipx`` / ``uv`` /
58
+ ``unknown``. Only ``pip`` / ``pipx`` / ``uv`` are upgradable from
59
+ PyPI; the other two disable the update feature.
60
+ """
61
+ import importlib.metadata
62
+
63
+ try:
64
+ dist = importlib.metadata.distribution(PYPI_PACKAGE)
65
+ direct_url_text = dist.read_text("direct_url.json")
66
+ except Exception as e:
67
+ logger.warning("install-kind detection failed: %s", e)
68
+ return "unknown"
69
+
70
+ if direct_url_text:
71
+ try:
72
+ direct_url = json.loads(direct_url_text)
73
+ except ValueError:
74
+ return "unknown"
75
+ if direct_url.get("dir_info", {}).get("editable"):
76
+ return "editable"
77
+ # Installed from a direct URL/path but not editable — we don't
78
+ # know how to keep it current, so stay hands-off.
79
+ return "unknown"
80
+
81
+ prefix = sys.prefix.replace(os.sep, "/")
82
+ if "pipx/venvs" in prefix:
83
+ return "pipx"
84
+ if "uv/tools" in prefix:
85
+ return "uv"
86
+ return "pip"
87
+
88
+
89
+ def build_upgrade_command(kind: str) -> Optional[list]:
90
+ """Upgrade argv for an install kind, or None if not upgradable."""
91
+ if kind == "pip":
92
+ return [sys.executable, "-m", "pip", "install", "--upgrade", PYPI_PACKAGE]
93
+ if kind == "pipx":
94
+ return ["pipx", "upgrade", PYPI_PACKAGE]
95
+ if kind == "uv":
96
+ return ["uv", "tool", "upgrade", PYPI_PACKAGE]
97
+ return None
98
+
99
+
100
+ # ── Version handling ─────────────────────────────────────────────────────
101
+
102
+
103
+ def _version_tuple(version: str) -> tuple:
104
+ return tuple(int(part) for part in version.split("."))
105
+
106
+
107
+ def is_newer(latest: str, current: str) -> bool:
108
+ """True if ``latest`` is strictly newer than ``current``."""
109
+ try:
110
+ return _version_tuple(latest) > _version_tuple(current)
111
+ except ValueError:
112
+ logger.warning(
113
+ "cannot compare versions latest=%r current=%r", latest, current
114
+ )
115
+ return False
116
+
117
+
118
+ def fetch_latest_version(timeout: int = 5) -> Optional[str]:
119
+ """Latest final (non-prerelease, non-yanked) version on PyPI.
120
+
121
+ Returns None on any network/parse error — callers treat that as
122
+ "couldn't check", never as an error surfaced to the user.
123
+ """
124
+ from hap_cli.core.logger import format_response_for_log
125
+
126
+ logger.info("HAP GET %s params={} Authorization=<none>", PYPI_JSON_URL)
127
+ try:
128
+ response = requests.get(PYPI_JSON_URL, timeout=timeout)
129
+ logger.info(
130
+ "HAP GET %s -> %d %s",
131
+ PYPI_JSON_URL,
132
+ response.status_code,
133
+ format_response_for_log(getattr(response, "text", "") or ""),
134
+ )
135
+ response.raise_for_status()
136
+ payload = response.json()
137
+ except Exception as e:
138
+ logger.warning("PyPI version check failed: %s", e)
139
+ return None
140
+
141
+ candidates = []
142
+ for version, files in (payload.get("releases") or {}).items():
143
+ if not _FINAL_RELEASE_RE.match(version):
144
+ continue
145
+ if not files or all(f.get("yanked") for f in files):
146
+ continue
147
+ candidates.append(version)
148
+ if not candidates:
149
+ return None
150
+ return max(candidates, key=_version_tuple)
151
+
152
+
153
+ # ── State file ───────────────────────────────────────────────────────────
154
+
155
+
156
+ def load_state() -> dict:
157
+ """Read the update-check state; empty dict if missing/corrupt."""
158
+ try:
159
+ with open(_state_file(), encoding="utf-8") as f:
160
+ data = json.load(f)
161
+ return data if isinstance(data, dict) else {}
162
+ except FileNotFoundError:
163
+ return {}
164
+ except Exception as e:
165
+ logger.warning("corrupt update state file, resetting: %s", e)
166
+ return {}
167
+
168
+
169
+ def save_state(state: dict) -> None:
170
+ """Atomically persist the update-check state."""
171
+ path = _state_file()
172
+ path.parent.mkdir(parents=True, exist_ok=True)
173
+ tmp = path.with_suffix(".json.tmp")
174
+ tmp.write_text(json.dumps(state, ensure_ascii=False), encoding="utf-8")
175
+ os.replace(tmp, path)
176
+
177
+
178
+ # ── Background check orchestration ───────────────────────────────────────
179
+
180
+
181
+ def maybe_spawn_background_check() -> None:
182
+ """Spawn a detached background update check if it is due.
183
+
184
+ Called when a CLI command finishes. The timestamp is bumped
185
+ *before* spawning so rapid consecutive commands cannot stack
186
+ duplicate children — at worst a failed child costs one interval.
187
+ """
188
+ kind = get_install_kind()
189
+ if kind in ("editable", "unknown"):
190
+ return
191
+
192
+ state = load_state()
193
+ last = state.get("last_check_at") or 0
194
+ now = time.time()
195
+ if now - last < UPDATE_CHECK_INTERVAL_SECONDS:
196
+ return
197
+
198
+ state["last_check_at"] = now
199
+ save_state(state)
200
+
201
+ try:
202
+ subprocess.Popen(
203
+ [sys.executable, "-m", "hap_cli", "update", "--background"],
204
+ stdin=subprocess.DEVNULL,
205
+ stdout=subprocess.DEVNULL,
206
+ stderr=subprocess.DEVNULL,
207
+ start_new_session=True,
208
+ )
209
+ logger.info("spawned background update check (install kind: %s)", kind)
210
+ except Exception as e:
211
+ logger.warning("failed to spawn background update check: %s", e)
212
+
213
+
214
+ def perform_upgrade(kind: str) -> tuple:
215
+ """Run the upgrade command for ``kind``. Returns (ok, output)."""
216
+ cmd = build_upgrade_command(kind)
217
+ if cmd is None:
218
+ return False, f"no upgrade command for install kind {kind!r}"
219
+ logger.info("running upgrade command: %s", " ".join(cmd))
220
+ try:
221
+ proc = subprocess.run(
222
+ cmd, capture_output=True, text=True, timeout=600
223
+ )
224
+ except Exception as e:
225
+ logger.error("upgrade command failed to run: %s", e)
226
+ return False, str(e)
227
+ output = (proc.stdout or "") + (proc.stderr or "")
228
+ logger.info("upgrade command exited %d", proc.returncode)
229
+ return proc.returncode == 0, output
230
+
231
+
232
+ def run_background_update() -> None:
233
+ """Body of the hidden ``hap update --background`` child process.
234
+
235
+ Silent by design: outcomes go to the state file (surfaced by the
236
+ next foreground run) and the log, never to the terminal.
237
+ """
238
+ from hap_cli import __version__
239
+
240
+ state = load_state()
241
+ state["last_check_at"] = time.time()
242
+
243
+ latest = fetch_latest_version()
244
+ if latest is None:
245
+ state["last_result"] = "check_failed"
246
+ save_state(state)
247
+ return
248
+
249
+ state["latest_version"] = latest
250
+ if not is_newer(latest, __version__):
251
+ state["last_result"] = "up_to_date"
252
+ save_state(state)
253
+ return
254
+
255
+ ok, output = perform_upgrade(get_install_kind())
256
+ if ok:
257
+ state["last_result"] = "upgraded"
258
+ state["upgraded_to"] = latest
259
+ else:
260
+ state["last_result"] = "upgrade_failed"
261
+ state["error"] = output[-500:]
262
+ logger.error("background upgrade to %s failed: %s", latest, output)
263
+ state["notified"] = False
264
+ save_state(state)
265
+
266
+
267
+ def consume_startup_notice() -> Optional[str]:
268
+ """One-shot user notice from the last background run, if any.
269
+
270
+ Returns the message and marks it notified so it is shown exactly
271
+ once. Silent results (check_failed / up_to_date) return None.
272
+ """
273
+ state = load_state()
274
+ if state.get("notified", True):
275
+ return None
276
+
277
+ result = state.get("last_result")
278
+ if result == "upgraded":
279
+ msg = (
280
+ f"hap-cli was auto-upgraded to {state.get('upgraded_to')} "
281
+ "(takes effect from this run)."
282
+ )
283
+ elif result == "upgrade_failed":
284
+ latest = state.get("latest_version") or "a newer version"
285
+ msg = (
286
+ f"Automatic upgrade to {latest} failed — "
287
+ "run `hap update` to upgrade manually."
288
+ )
289
+ else:
290
+ return None
291
+
292
+ state["notified"] = True
293
+ save_state(state)
294
+ return msg
@@ -41,6 +41,11 @@ from hap_cli.commands.region_cmd import region
41
41
  from hap_cli.commands.icon_cmd import icon
42
42
  from hap_cli.commands.upload_cmd import upload
43
43
  from hap_cli.commands.app_editor_cmd import app_editor
44
+ from hap_cli.commands.update_cmd import update as update_cmd
45
+ from hap_cli.core.update import (
46
+ consume_startup_notice,
47
+ maybe_spawn_background_check,
48
+ )
44
49
 
45
50
 
46
51
  def clone_group(source, *, name=None, hidden=False):
@@ -71,6 +76,18 @@ def cli(ctx, json_mode, profile):
71
76
  ctx.obj.json_mode = json_mode
72
77
  ctx.obj.profile = profile
73
78
 
79
+ # Self-update plumbing. Both halves are local-file-only on the hot
80
+ # path: the notice reads a small JSON state file, and the spawn hook
81
+ # only forks a detached child when the last check is older than the
82
+ # interval (the child does the actual network I/O). Skipped for
83
+ # `update` itself (it manages the state directly) and `repl`
84
+ # (cli() re-runs per REPL line).
85
+ notice = consume_startup_notice()
86
+ if notice:
87
+ click.echo(notice, err=True)
88
+ if ctx.invoked_subcommand not in ("update", "repl"):
89
+ ctx.call_on_close(maybe_spawn_background_check)
90
+
74
91
 
75
92
  # Register command groups
76
93
  app.add_command(clone_group(role, name="role"))
@@ -127,6 +144,7 @@ cli.add_command(icon)
127
144
  cli.add_command(upload)
128
145
  cli.add_command(config_group)
129
146
  cli.add_command(app_editor)
147
+ cli.add_command(update_cmd)
130
148
 
131
149
 
132
150
  # Hidden top-level alias for the old `hap completion` command. The
@@ -900,27 +900,6 @@
900
900
  "app optionset update --options-json": {
901
901
  "en": "Option list. [JSON value] [required]"
902
902
  },
903
- "app restore": {
904
- "en": "Restore an application from backup."
905
- },
906
- "app restore --as-new": {
907
- "en": "Restore as a new app"
908
- },
909
- "app restore --backup-id": {
910
- "en": "Backup ID"
911
- },
912
- "app restore --file-name": {
913
- "en": "Backup file name"
914
- },
915
- "app restore --file-url": {
916
- "en": "Backup file URL"
917
- },
918
- "app restore --no-data": {
919
- "en": "Restore without data"
920
- },
921
- "app restore --org-id": {
922
- "en": "Organization ID"
923
- },
924
903
  "app role": {
925
904
  "en": "Role management."
926
905
  },