hap-cli 0.9.5__tar.gz → 0.9.6__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 (416) hide show
  1. {hap_cli-0.9.5 → hap_cli-0.9.6}/PKG-INFO +2 -1
  2. {hap_cli-0.9.5/hap_cli → hap_cli-0.9.6}/README.md +1 -0
  3. {hap_cli-0.9.5 → hap_cli-0.9.6/hap_cli}/README.md +1 -0
  4. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/README_CN.md +1 -0
  5. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/__init__.py +1 -1
  6. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/config_cmd.py +42 -0
  7. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/context.py +26 -1
  8. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/chat.py +36 -19
  9. hap_cli-0.9.6/hap_cli/core/relogin.py +255 -0
  10. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/session.py +50 -1
  11. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/locale/messages.json +16 -0
  12. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/conftest.py +12 -0
  13. hap_cli-0.9.6/hap_cli/tests/test_relogin.py +234 -0
  14. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli.egg-info/PKG-INFO +2 -1
  15. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli.egg-info/SOURCES.txt +2 -0
  16. {hap_cli-0.9.5 → hap_cli-0.9.6}/MANIFEST.in +0 -0
  17. {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/__init__.py +0 -0
  18. {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/__main__.py +0 -0
  19. {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/config.py +0 -0
  20. {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/fixtures/__init__.py +0 -0
  21. {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/fixtures/feedback_scenario.py +0 -0
  22. {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/harness.py +0 -0
  23. {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/smoke.py +0 -0
  24. {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/state.py +0 -0
  25. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/__main__.py +0 -0
  26. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/_entry.py +0 -0
  27. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/__init__.py +0 -0
  28. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/app_cmd.py +0 -0
  29. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/app_creator_cmd.py +0 -0
  30. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/app_editor_cmd.py +0 -0
  31. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/attachment_cmd.py +0 -0
  32. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/auth_cmd.py +0 -0
  33. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/calendar_cmd.py +0 -0
  34. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/chart_cmd.py +0 -0
  35. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/chat_cmd.py +0 -0
  36. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/chatbot_cmd.py +0 -0
  37. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/contact_cmd.py +0 -0
  38. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/department_cmd.py +0 -0
  39. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/group_cmd.py +0 -0
  40. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/guide_cmd.py +0 -0
  41. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/icon_cmd.py +0 -0
  42. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/instance_cmd.py +0 -0
  43. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/node_cmd.py +0 -0
  44. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/optionset_cmd.py +0 -0
  45. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/page_cmd.py +0 -0
  46. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/post_cmd.py +0 -0
  47. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/record_cmd.py +0 -0
  48. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/region_cmd.py +0 -0
  49. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/role_cmd.py +0 -0
  50. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/update_cmd.py +0 -0
  51. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/upload_cmd.py +0 -0
  52. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/v3_registry.py +0 -0
  53. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/workflow_cmd.py +0 -0
  54. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/worksheet_cmd.py +0 -0
  55. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/__init__.py +0 -0
  56. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/action_spec_adapter.py +0 -0
  57. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app.py +0 -0
  58. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/__init__.py +0 -0
  59. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
  60. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
  61. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
  62. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/charts.py +0 -0
  63. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/cleanup.py +0 -0
  64. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/compiler.py +0 -0
  65. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/config.py +0 -0
  66. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/design/design.schema.json +0 -0
  67. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/errors.py +0 -0
  68. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
  69. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/executor.py +0 -0
  70. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/fields.py +0 -0
  71. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/hap.py +0 -0
  72. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/merge.py +0 -0
  73. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/recording/__init__.py +0 -0
  74. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/recording/console.py +0 -0
  75. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
  76. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/recording/mirror.py +0 -0
  77. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/recording/report.py +0 -0
  78. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/schema.py +0 -0
  79. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
  80. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/__init__.py +0 -0
  81. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/cli.py +0 -0
  82. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/executor.py +0 -0
  83. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
  84. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
  85. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
  86. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
  87. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/template.py +0 -0
  88. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/selftest.py +0 -0
  89. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/smoke.py +0 -0
  90. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/steps.py +0 -0
  91. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/store.py +0 -0
  92. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/validate.py +0 -0
  93. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
  94. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/attachment.py +0 -0
  95. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/auth.py +0 -0
  96. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/calendar_mod.py +0 -0
  97. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/chart.py +0 -0
  98. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/chatbot.py +0 -0
  99. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/cli_access.py +0 -0
  100. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/condition_dsl.py +0 -0
  101. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/contact.py +0 -0
  102. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/control_type_codes.py +0 -0
  103. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/department.py +0 -0
  104. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/__init__.py +0 -0
  105. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  106. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  107. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/apply.py +0 -0
  108. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/componentlower.py +0 -0
  109. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/editspec/component.schema.json +0 -0
  110. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
  111. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
  112. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/editspec/field.schema.json +0 -0
  113. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/editspec.py +0 -0
  114. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/errors.py +0 -0
  115. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/fieldlower.py +0 -0
  116. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  117. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/models.py +0 -0
  118. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/ops.py +0 -0
  119. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/planner.py +0 -0
  120. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/reader.py +0 -0
  121. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/field_normalizer.py +0 -0
  122. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/field_spec_adapter.py +0 -0
  123. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/filter_translator.py +0 -0
  124. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/filter_v3.py +0 -0
  125. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/flow_node.py +0 -0
  126. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/global_meta.py +0 -0
  127. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/group.py +0 -0
  128. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/guide.py +0 -0
  129. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/icon_index.py +0 -0
  130. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/instance.py +0 -0
  131. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/layout_check.py +0 -0
  132. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/logger.py +0 -0
  133. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/optionset.py +0 -0
  134. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/page.py +0 -0
  135. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/post.py +0 -0
  136. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/record.py +0 -0
  137. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/record_defaults.py +0 -0
  138. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/record_form_checks.py +0 -0
  139. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/record_lookups.py +0 -0
  140. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/record_rules.py +0 -0
  141. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/relation_scope.py +0 -0
  142. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/response_crypto.py +0 -0
  143. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/role.py +0 -0
  144. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/role_perm_builder.py +0 -0
  145. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/rule_spec_adapter.py +0 -0
  146. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/token_crypto.py +0 -0
  147. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/update.py +0 -0
  148. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/upload.py +0 -0
  149. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/v3/__init__.py +0 -0
  150. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/v3/dispatcher.py +0 -0
  151. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/v3/render.py +0 -0
  152. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/v3/schema.py +0 -0
  153. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/view_spec_adapter.py +0 -0
  154. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/workflow.py +0 -0
  155. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/workflow_node_dsl.py +0 -0
  156. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/worksheet.py +0 -0
  157. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/worksheet_form.py +0 -0
  158. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/worksheet_reference.py +0 -0
  159. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/worksheet_templates.py +0 -0
  160. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/approval.md +0 -0
  161. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/chart.md +0 -0
  162. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/messages.md +0 -0
  163. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/overview.md +0 -0
  164. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/post.md +0 -0
  165. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/record.md +0 -0
  166. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/setup.md +0 -0
  167. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/skills.md +0 -0
  168. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/workflow.md +0 -0
  169. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/worksheet.md +0 -0
  170. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/index.json +0 -0
  171. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/approval.md +0 -0
  172. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/chart.md +0 -0
  173. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/messages.md +0 -0
  174. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/overview.md +0 -0
  175. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/post.md +0 -0
  176. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/record.md +0 -0
  177. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/setup.md +0 -0
  178. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/skills.md +0 -0
  179. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/workflow.md +0 -0
  180. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/worksheet.md +0 -0
  181. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/i18n.py +0 -0
  182. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/locale/__init__.py +0 -0
  183. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/locale/messages.schema.json +0 -0
  184. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/__init__.py +0 -0
  185. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_action_mdpublic_parity.py +0 -0
  186. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_advanced_setting_guard.py +0 -0
  187. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_app_creator.py +0 -0
  188. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_app_editor.py +0 -0
  189. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_app_export_usage.py +0 -0
  190. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_app_light_color.py +0 -0
  191. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_app_section_name.py +0 -0
  192. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_approval_submit_fill_cli.py +0 -0
  193. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_approval_wire.py +0 -0
  194. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_atomic_write.py +0 -0
  195. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_attachment_share.py +0 -0
  196. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_auth_prerelease.py +0 -0
  197. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_batch_add_atomic.py +0 -0
  198. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_bug_036430.py +0 -0
  199. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_bug_036750.py +0 -0
  200. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_calendar_date_range.py +0 -0
  201. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_calendar_discussion_cli.py +0 -0
  202. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_chart.py +0 -0
  203. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_chart_filter_merge.py +0 -0
  204. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_chat_attachment_send.py +0 -0
  205. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_chat_messages_cli.py +0 -0
  206. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_chat_recall_cli.py +0 -0
  207. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_chat_send_socket.py +0 -0
  208. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_cli_access.py +0 -0
  209. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_coded_responses.py +0 -0
  210. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_condition_dsl.py +0 -0
  211. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_condition_dsl_entries.py +0 -0
  212. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_condition_dsl_readback.py +0 -0
  213. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_config_cmd.py +0 -0
  214. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_content_language.py +0 -0
  215. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_control_lookup_guard.py +0 -0
  216. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_core.py +0 -0
  217. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_create_worksheet_partial.py +0 -0
  218. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
  219. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_dsl_existing_node_refs.py +0 -0
  220. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_dsl_multi_record_guard.py +0 -0
  221. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_editor_inline_subtable.py +0 -0
  222. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_engine_core_parity.py +0 -0
  223. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_arg_consistency.py +0 -0
  224. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_bidirectional.py +0 -0
  225. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_discussion_mentions.py +0 -0
  226. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_entry_module.py +0 -0
  227. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_field_record_ref.py +0 -0
  228. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_http_error_body.py +0 -0
  229. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_json_anywhere.py +0 -0
  230. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_json_file_arg.py +0 -0
  231. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_layout_check.py +0 -0
  232. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_node_get_detail.py +0 -0
  233. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_node_summary.py +0 -0
  234. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_post_cursor.py +0 -0
  235. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_publish_reasons.py +0 -0
  236. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_replicate_workflow.py +0 -0
  237. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_save_fields_shape.py +0 -0
  238. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_subgroups.py +0 -0
  239. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_subtable_read.py +0 -0
  240. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_worksheet_references.py +0 -0
  241. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_worksheet_visibility.py +0 -0
  242. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_worksheet_workflows.py +0 -0
  243. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fields_mdpublic_parity.py +0 -0
  244. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_full_e2e.py +0 -0
  245. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g1_chart_parity.py +0 -0
  246. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g1_record_parity.py +0 -0
  247. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g1_upload_parity.py +0 -0
  248. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g1_worksheet_parity.py +0 -0
  249. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g2_coverage_parity.py +0 -0
  250. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g3_workflow_parity.py +0 -0
  251. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g4_approval_parity.py +0 -0
  252. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g5_low_frequency_parity.py +0 -0
  253. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g6_advanced_fields.py +0 -0
  254. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g6_fields_roundtrip.py +0 -0
  255. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g7_misc_gaps.py +0 -0
  256. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g7b_permanent_delete.py +0 -0
  257. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g8_filter_operator.py +0 -0
  258. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g8b_pivot_operators.py +0 -0
  259. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g8c_empty_filter_and_help_truth.py +0 -0
  260. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g9_appid_resolve.py +0 -0
  261. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_global_meta.py +0 -0
  262. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_guide.py +0 -0
  263. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_guide_sections.py +0 -0
  264. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_i18n.py +0 -0
  265. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_inner_flow_batch_add.py +0 -0
  266. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_instance_envelope.py +0 -0
  267. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_instance_history_cli.py +0 -0
  268. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration.py +0 -0
  269. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_approval.py +0 -0
  270. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_approval_actions.py +0 -0
  271. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_calendar.py +0 -0
  272. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_destructive.py +0 -0
  273. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_misc.py +0 -0
  274. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_post.py +0 -0
  275. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_social.py +0 -0
  276. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_v3.py +0 -0
  277. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_workflow.py +0 -0
  278. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  279. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_jsonschema_mini_copy.py +0 -0
  280. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
  281. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_multi_profile.py +0 -0
  282. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
  283. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_noninteractive_confirm.py +0 -0
  284. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
  285. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_org_id_cli.py +0 -0
  286. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_org_id_docs.py +0 -0
  287. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_page_filters_group_read.py +0 -0
  288. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_page_filters_group_update.py +0 -0
  289. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_page_save_page_id.py +0 -0
  290. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_parameter_conventions.py +0 -0
  291. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  292. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_post_attachments.py +0 -0
  293. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_post_scope_cli.py +0 -0
  294. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
  295. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_business_rules.py +0 -0
  296. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_defaults.py +0 -0
  297. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_doc_id_hidden.py +0 -0
  298. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_field_alias.py +0 -0
  299. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_form_checks.py +0 -0
  300. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_run_action.py +0 -0
  301. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
  302. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_write_fixes.py +0 -0
  303. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_relation_scope.py +0 -0
  304. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_rmw_mdpublic_parity.py +0 -0
  305. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_role_app_scope.py +0 -0
  306. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_rule_filters_and_field_warning.py +0 -0
  307. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_save_action_silent_drop.py +0 -0
  308. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_save_rule_spec.py +0 -0
  309. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_select_add_option.py +0 -0
  310. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
  311. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_semantic_types_registry.py +0 -0
  312. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_subtable_mdpublic_parity.py +0 -0
  313. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_update.py +0 -0
  314. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_update_mode.py +0 -0
  315. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_upload_allow_down.py +0 -0
  316. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_upload_host_resolution.py +0 -0
  317. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_upload_mdpublic_parity.py +0 -0
  318. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  319. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_bool_flags.py +0 -0
  320. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  321. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  322. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_response_format_json.py +0 -0
  323. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_session.py +0 -0
  324. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  325. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_view_mdpublic_parity.py +0 -0
  326. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_config_keys.py +0 -0
  327. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_inner_scope.py +0 -0
  328. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_json_file_arg.py +0 -0
  329. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_node_delete_missing.py +0 -0
  330. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_save_action_dsl_fields.py +0 -0
  331. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_structure_listing.py +0 -0
  332. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_trigger_outcome.py +0 -0
  333. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_trigger_preserve.py +0 -0
  334. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wo4_result_handling.py +0 -0
  335. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_code_node.py +0 -0
  336. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_condition_field_types.py +0 -0
  337. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_condition_slots.py +0 -0
  338. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_delete_cascade.py +0 -0
  339. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
  340. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_lifecycle_wire.py +0 -0
  341. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_list_cli.py +0 -0
  342. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_node_wire.py +0 -0
  343. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_publish_inner.py +0 -0
  344. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
  345. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
  346. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_worksheet_create_section.py +0 -0
  347. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  348. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_worksheet_filter_read.py +0 -0
  349. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_worksheet_switch.py +0 -0
  350. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
  351. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/utils/__init__.py +0 -0
  352. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/utils/atomic_write.py +0 -0
  353. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/utils/confirm.py +0 -0
  354. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/utils/formatting.py +0 -0
  355. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/utils/options.py +0 -0
  356. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/utils/parameter_mapping.py +0 -0
  357. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli.egg-info/dependency_links.txt +0 -0
  358. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli.egg-info/entry_points.txt +0 -0
  359. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli.egg-info/requires.txt +0 -0
  360. {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli.egg-info/top_level.txt +0 -0
  361. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/__init__.py +0 -0
  362. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/__main__.py +0 -0
  363. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/_wftest.py +0 -0
  364. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/charts.py +0 -0
  365. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/cleanup.py +0 -0
  366. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/compiler.py +0 -0
  367. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/config.py +0 -0
  368. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/errors.py +0 -0
  369. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/executor.py +0 -0
  370. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/fields.py +0 -0
  371. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/hap.py +0 -0
  372. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/recording/__init__.py +0 -0
  373. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/recording/console.py +0 -0
  374. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/recording/jsonl.py +0 -0
  375. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/recording/mirror.py +0 -0
  376. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/recording/report.py +0 -0
  377. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/schema.py +0 -0
  378. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/seed/__init__.py +0 -0
  379. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/seed/cli.py +0 -0
  380. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/seed/executor.py +0 -0
  381. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/seed/template.py +0 -0
  382. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/selftest.py +0 -0
  383. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/smoke.py +0 -0
  384. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/step.py +0 -0
  385. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/steps.py +0 -0
  386. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/store.py +0 -0
  387. {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/workflow_dsl.py +0 -0
  388. {hap_cli-0.9.5 → hap_cli-0.9.6}/setup.cfg +0 -0
  389. {hap_cli-0.9.5 → hap_cli-0.9.6}/setup.py +0 -0
  390. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/INDEX.json +0 -0
  391. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  392. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  393. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/create_optionset.yaml +0 -0
  394. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/create_role.yaml +0 -0
  395. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  396. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/delete_record.yaml +0 -0
  397. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/delete_role.yaml +0 -0
  398. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  399. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_app_info.yaml +0 -0
  400. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  401. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  402. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  403. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_details.yaml +0 -0
  404. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  405. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_list.yaml +0 -0
  406. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  407. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  408. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  409. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  410. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_regions.yaml +0 -0
  411. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_role_details.yaml +0 -0
  412. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_role_list.yaml +0 -0
  413. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  414. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  415. {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  416. {hap_cli-0.9.5 → hap_cli-0.9.6}/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.9.5
3
+ Version: 0.9.6
4
4
  Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -339,6 +339,7 @@ discover every subcommand.
339
339
  | `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` completion |
340
340
  | `config language [LANG]` | Switch UI language (`en` / `zh-Hans`, or `auto` to follow the account language) |
341
341
  | `config update [auto\|notify\|off]` | Self-upgrade mode; `notify` only reports, `off` never checks |
342
+ | `config auto-login [on\|off]` | Renew an expired login in the browser (one click on Authorize) and carry on; `off` stops with "not logged in" |
342
343
  | `config log on / off / status` | Toggle and inspect API logging |
343
344
  | `config log level LEVEL` | `DEBUG / INFO / WARNING / ERROR / CRITICAL` |
344
345
  | `config log view [--lines N] [--no-follow]` | Tail the log file |
@@ -310,6 +310,7 @@ discover every subcommand.
310
310
  | `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` completion |
311
311
  | `config language [LANG]` | Switch UI language (`en` / `zh-Hans`, or `auto` to follow the account language) |
312
312
  | `config update [auto\|notify\|off]` | Self-upgrade mode; `notify` only reports, `off` never checks |
313
+ | `config auto-login [on\|off]` | Renew an expired login in the browser (one click on Authorize) and carry on; `off` stops with "not logged in" |
313
314
  | `config log on / off / status` | Toggle and inspect API logging |
314
315
  | `config log level LEVEL` | `DEBUG / INFO / WARNING / ERROR / CRITICAL` |
315
316
  | `config log view [--lines N] [--no-follow]` | Tail the log file |
@@ -310,6 +310,7 @@ discover every subcommand.
310
310
  | `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` completion |
311
311
  | `config language [LANG]` | Switch UI language (`en` / `zh-Hans`, or `auto` to follow the account language) |
312
312
  | `config update [auto\|notify\|off]` | Self-upgrade mode; `notify` only reports, `off` never checks |
313
+ | `config auto-login [on\|off]` | Renew an expired login in the browser (one click on Authorize) and carry on; `off` stops with "not logged in" |
313
314
  | `config log on / off / status` | Toggle and inspect API logging |
314
315
  | `config log level LEVEL` | `DEBUG / INFO / WARNING / ERROR / CRITICAL` |
315
316
  | `config log view [--lines N] [--no-follow]` | Tail the log file |
@@ -289,6 +289,7 @@ hap --json worksheet record list WORKSHEET_ID
289
289
  | `config completion [bash\|zsh\|fish] [--install]` | 启用 `<Tab>` 补全 |
290
290
  | `config language [LANG]` | 切换显示语言(`en` / `zh-Hans`;`auto` 跟随登录账号的语言) |
291
291
  | `config update [auto\|notify\|off]` | 自动升级开关;`notify` 只提示不安装,`off` 不检查 |
292
+ | `config auto-login [on\|off]` | 登录过期时自动打开浏览器授权页,点一下「授权」命令继续执行;`off` 则直接报未登录 |
292
293
  | `config log on / off / status` | 日志开关与状态 |
293
294
  | `config log level LEVEL` | 设置日志级别(`DEBUG/INFO/WARNING/ERROR/CRITICAL`) |
294
295
  | `config log view [--lines N] [--no-follow]` | 跟踪日志文件 |
@@ -3,7 +3,7 @@
3
3
  import sys as _sys
4
4
  import types as _types
5
5
 
6
- __version__ = "0.9.5"
6
+ __version__ = "0.9.6"
7
7
 
8
8
  # The command tree used to live in hap_cli/hap_cli.py. A module named
9
9
  # after the package it sits in shadows that package: run python with the
@@ -126,6 +126,7 @@ def config_completion(shell, install):
126
126
  @pass_context
127
127
  def config_show(ctx):
128
128
  """Display current harness configuration."""
129
+ from hap_cli.core.relogin import get_mode as get_auto_login_mode
129
130
  from hap_cli.core.update import get_update_mode
130
131
 
131
132
  log_cfg = load_logging_config()
@@ -136,6 +137,7 @@ def config_show(ctx):
136
137
  "file": str(resolve_log_path(log_cfg)),
137
138
  },
138
139
  "update": get_update_mode(),
140
+ "auto_login": get_auto_login_mode(),
139
141
  }
140
142
 
141
143
  def _human(d):
@@ -144,6 +146,7 @@ def config_show(ctx):
144
146
  click.echo(f" level : {d['logging']['level']}")
145
147
  click.echo(f" file : {d['logging']['file']}")
146
148
  click.echo(f"update : {d['update']}")
149
+ click.echo(f"auto-login: {d['auto_login']}")
147
150
 
148
151
  ctx.output(data, _human)
149
152
 
@@ -253,6 +256,45 @@ def config_update(ctx, mode):
253
256
  click.echo(f"Self-update set to: {mode}")
254
257
 
255
258
 
259
+ # ── `hap config auto-login` ───────────────────────────────────────────────
260
+
261
+
262
+ @config.command("auto-login")
263
+ @click.argument("mode", required=False, type=click.Choice(["on", "off"]))
264
+ @pass_context
265
+ def config_auto_login(ctx, mode):
266
+ """Control whether an expired login is renewed in the browser.
267
+
268
+ \b
269
+ on when a command finds your login expired, open the browser
270
+ authorization page, wait for you to click Authorize, then
271
+ carry on (default)
272
+ off stop with a "not logged in" error instead
273
+
274
+ Without an argument, shows the current setting. It never applies over
275
+ SSH, in CI, or where no browser can be shown. Setting the environment
276
+ variable HAP_AUTO_LOGIN=0 turns it off for one shell.
277
+ """
278
+ from hap_cli.core.relogin import CONFIG_KEY, get_mode
279
+ from hap_cli.core.session import save_config
280
+
281
+ if mode is None:
282
+ current = get_mode()
283
+ if ctx.json_mode:
284
+ from hap_cli.utils.formatting import output_json
285
+ output_json({"auto_login": current})
286
+ else:
287
+ click.echo(f"Auto-login: {current}")
288
+ return
289
+
290
+ save_config({CONFIG_KEY: mode})
291
+ if ctx.json_mode:
292
+ from hap_cli.utils.formatting import output_json
293
+ output_json({"status": "ok", "auto_login": mode})
294
+ else:
295
+ click.echo(f"Auto-login set to: {mode}")
296
+
297
+
256
298
  # ── `hap config language` ─────────────────────────────────────────────────
257
299
 
258
300
 
@@ -20,6 +20,27 @@ NOT_LOGGED_IN_HINT = (
20
20
  )
21
21
 
22
22
 
23
+ def _relogin_notice(login_url: str, timeout: int) -> None:
24
+ """Tell the user an authorization page is waiting for one click.
25
+
26
+ Written to stderr so ``--json`` output on stdout stays parseable; the
27
+ desktop notification is for callers (AI agents) whose terminal output
28
+ the user only sees after the command ends.
29
+ """
30
+ from hap_cli.core.relogin import desktop_notify
31
+ from hap_cli.i18n import t
32
+
33
+ click.echo(
34
+ t("Your HAP login has expired. A browser page is open — click "
35
+ "Authorize (授权) to continue (waiting up to {seconds}s).").format(
36
+ seconds=timeout),
37
+ err=True,
38
+ )
39
+ click.echo(f" {login_url}", err=True)
40
+ desktop_notify(t("Login expired — click Authorize (授权) in the browser "
41
+ "to let the command continue."))
42
+
43
+
23
44
  class Context:
24
45
  """Shared CLI context."""
25
46
 
@@ -43,6 +64,7 @@ class Context:
43
64
  """
44
65
  if self.session is None:
45
66
  self.session = Session.load(self.profile)
67
+ self.session.relogin_notice = _relogin_notice
46
68
  # One-time migration for sessions logged in before account_id
47
69
  # was persisted. Some HAP controllers (notably
48
70
  # Invitation/InviteUser) silently degrade when the AccountId
@@ -55,7 +77,10 @@ class Context:
55
77
  ):
56
78
  self.session._lazy_resolve_account_id()
57
79
  if require_auth and not self.session.auth_token:
58
- raise SessionError("Not logged in. Run 'hap auth login' first.")
80
+ # A saved environment without a token: authorize it again in
81
+ # the browser rather than stopping the command.
82
+ if not (self.session.login_url and self.session.recover_auth()):
83
+ raise SessionError("Not logged in. Run 'hap auth login' first.")
59
84
  return self.session
60
85
 
61
86
  def output(self, data, table_fn=None):
@@ -48,26 +48,43 @@ def _chatmq_get(
48
48
  # mirror the other transport hooks.
49
49
  from hap_cli.core import cli_access
50
50
 
51
+ from hap_cli.core.session import AuthenticationError
52
+
51
53
  cli_access.ensure_before_request(session, url)
52
- headers = {
53
- "Authorization": f"md_pss_id {session.auth_token}",
54
- "X-Requested-With": "XMLHttpRequest",
55
- "User-Agent": hap_user_agent(),
56
- }
57
- auth_for_log = redact_authorization(headers["Authorization"])
58
- logger.info(
59
- "HAP GET %s params=%s Authorization=%s",
60
- url,
61
- format_body_for_log(params) or "{}",
62
- auth_for_log,
63
- )
64
- response = requests.get(url, params=params, headers=headers, timeout=timeout)
65
- logger.info(
66
- "HAP GET %s -> %d %s",
67
- url,
68
- response.status_code,
69
- format_response_for_log(getattr(response, "text", "") or ""),
70
- )
54
+
55
+ def _send():
56
+ headers = {
57
+ "Authorization": f"md_pss_id {session.auth_token}",
58
+ "X-Requested-With": "XMLHttpRequest",
59
+ "User-Agent": hap_user_agent(),
60
+ }
61
+ auth_for_log = redact_authorization(headers["Authorization"])
62
+ logger.info(
63
+ "HAP GET %s params=%s Authorization=%s",
64
+ url,
65
+ format_body_for_log(params) or "{}",
66
+ auth_for_log,
67
+ )
68
+ response = requests.get(url, params=params, headers=headers, timeout=timeout)
69
+ logger.info(
70
+ "HAP GET %s -> %d %s",
71
+ url,
72
+ response.status_code,
73
+ format_response_for_log(getattr(response, "text", "") or ""),
74
+ )
75
+ return response
76
+
77
+ response = _send()
78
+ # An expired login gets the same one-shot browser re-authorization as
79
+ # the main transport (Session._post); the read is then resent once.
80
+ if response.status_code == 401:
81
+ if not session.recover_auth():
82
+ raise AuthenticationError(
83
+ "Authentication failed (HTTP 401). "
84
+ "Your session token is missing, invalid, or expired.",
85
+ status_code=401,
86
+ )
87
+ response = _send()
71
88
  response.raise_for_status()
72
89
  return response.json()
73
90
 
@@ -0,0 +1,255 @@
1
+ """Recover from an expired login by re-running the browser authorization.
2
+
3
+ The CLI token is the web session token (``md_pss_id``) handed over by the
4
+ ``/cliauth`` page, so it dies with the web session: after 24 hours without
5
+ activity every request starts failing with 401. Most callers are AI agents
6
+ running one command at a time, for whom a dead token means the whole task
7
+ stops until a human runs ``hap auth login``. Instead, the transport calls
8
+ :func:`recover` when the server rejects the token: it opens the same
9
+ authorization page (the user clicks 授权 once), waits a bounded time, saves
10
+ the new token to the same profile, and tells the caller to retry the
11
+ request once.
12
+
13
+ Guard rails, all deliberate:
14
+
15
+ - Only when a desktop browser can plausibly be shown (no SSH session, no
16
+ CI, a display on Linux) and the user has not turned it off
17
+ (``hap config auto-login off`` / ``HAP_AUTO_LOGIN=0``).
18
+ - Only for a session loaded from a saved profile with a login address —
19
+ a bare ``Session()`` (tests, library use) never opens a browser.
20
+ - At most once per process. A second rejection after a fresh login is a
21
+ real permission problem, not an expiry.
22
+ - A 401/403 is first confirmed against ``Global/GetGlobalMeta``: if the
23
+ current token still identifies the user, the rejection was about
24
+ permissions and no browser is opened.
25
+ - The browser may be signed in as somebody else; a token for a different
26
+ account is discarded rather than silently switching identities.
27
+ - Parallel commands share one login through a file lock; the ones that
28
+ waited pick up the token the first one saved.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import logging
34
+ import os
35
+ import subprocess
36
+ import sys
37
+ import time
38
+ from contextlib import contextmanager
39
+ from pathlib import Path
40
+ from typing import Any, Callable, Optional
41
+
42
+ logger = logging.getLogger("hap_cli.relogin")
43
+
44
+ # Seconds to wait for the user to click 授权. AI tool runners commonly
45
+ # kill a command after 120s, so this stays well inside that budget.
46
+ DEFAULT_TIMEOUT_SECONDS = 90
47
+
48
+ CONFIG_KEY = "auto_login"
49
+ ENV_SWITCH = "HAP_AUTO_LOGIN"
50
+ ENV_TIMEOUT = "HAP_AUTO_LOGIN_TIMEOUT"
51
+
52
+ _OFF_VALUES = {"0", "off", "false", "no"}
53
+
54
+ # Signals that tell us nobody is sitting in front of a browser.
55
+ _HEADLESS_ENV = ("CI", "SSH_CONNECTION", "SSH_TTY")
56
+
57
+
58
+ class ReloginTimeout(Exception):
59
+ """The browser authorization page was opened but not completed."""
60
+
61
+
62
+ def get_mode() -> str:
63
+ """``"on"`` or ``"off"`` as stored by ``hap config auto-login``."""
64
+ from hap_cli.core.session import load_config
65
+
66
+ value = str((load_config() or {}).get(CONFIG_KEY) or "on").lower()
67
+ return "off" if value in _OFF_VALUES else "on"
68
+
69
+
70
+ def timeout_seconds() -> int:
71
+ raw = os.environ.get(ENV_TIMEOUT)
72
+ if raw:
73
+ try:
74
+ value = int(raw)
75
+ if value > 0:
76
+ return value
77
+ except ValueError:
78
+ logger.warning("Ignoring non-integer %s=%r", ENV_TIMEOUT, raw)
79
+ return DEFAULT_TIMEOUT_SECONDS
80
+
81
+
82
+ def can_open_browser() -> bool:
83
+ """Best guess whether a browser window would reach a person."""
84
+ for name in _HEADLESS_ENV:
85
+ if os.environ.get(name):
86
+ logger.info("Auto-login skipped: %s is set", name)
87
+ return False
88
+ if sys.platform.startswith("linux"):
89
+ if not (os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY")):
90
+ logger.info("Auto-login skipped: no DISPLAY / WAYLAND_DISPLAY")
91
+ return False
92
+ return True
93
+
94
+
95
+ def enabled(session: Any) -> bool:
96
+ switch = os.environ.get(ENV_SWITCH)
97
+ if switch is not None and switch.strip().lower() in _OFF_VALUES:
98
+ return False
99
+ if get_mode() == "off":
100
+ return False
101
+ if not getattr(session, "_profile_name", "") or not getattr(session, "login_url", ""):
102
+ return False
103
+ if getattr(session, "_relogin_attempted", False):
104
+ return False
105
+ return can_open_browser()
106
+
107
+
108
+ def token_still_valid(login_url: str, token: str) -> bool:
109
+ """True when ``token`` still identifies a user.
110
+
111
+ Network or shape trouble counts as "cannot tell" and returns True, so
112
+ a flaky connection never pops a browser window.
113
+ """
114
+ from hap_cli.core import auth as auth_mod
115
+ from hap_cli.core.session import AuthenticationError
116
+
117
+ if not token:
118
+ return False
119
+ try:
120
+ auth_mod.user_info_from_meta(auth_mod.fetch_global_meta(login_url, token))
121
+ return True
122
+ except AuthenticationError:
123
+ return False
124
+ except Exception as exc:
125
+ logger.warning("Could not confirm whether the token expired: %s", exc)
126
+ return True
127
+
128
+
129
+ def _lock_path() -> Path:
130
+ from hap_cli.core.session import CONFIG_DIR
131
+
132
+ return Path(CONFIG_DIR) / "relogin.lock"
133
+
134
+
135
+ @contextmanager
136
+ def _login_lock():
137
+ """Serialise logins across processes. No-op where fcntl is missing."""
138
+ try:
139
+ import fcntl
140
+ except ImportError: # Windows
141
+ yield
142
+ return
143
+ path = _lock_path()
144
+ path.parent.mkdir(parents=True, exist_ok=True)
145
+ with open(path, "a+") as handle:
146
+ fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
147
+ try:
148
+ yield
149
+ finally:
150
+ fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
151
+
152
+
153
+ def desktop_notify(message: str) -> None:
154
+ """Raise an OS notification so the user notices the browser tab."""
155
+ try:
156
+ if sys.platform == "darwin":
157
+ script = 'display notification {} with title "hap-cli"'.format(
158
+ _applescript_quote(message)
159
+ )
160
+ subprocess.run(["osascript", "-e", script], timeout=5,
161
+ capture_output=True, check=False)
162
+ elif sys.platform.startswith("linux"):
163
+ subprocess.run(["notify-send", "hap-cli", message], timeout=5,
164
+ capture_output=True, check=False)
165
+ except Exception as exc:
166
+ logger.warning("Desktop notification failed: %s", exc)
167
+
168
+
169
+ def _applescript_quote(text: str) -> str:
170
+ return '"' + text.replace("\\", "\\\\").replace('"', '\\"') + '"'
171
+
172
+
173
+ def _adopt_saved_token(session: Any) -> bool:
174
+ """Pick up a token another process saved while we waited for the lock."""
175
+ from hap_cli.core.session import Session
176
+
177
+ # Session.load, not load_profile: the token is encrypted at rest.
178
+ token = Session.load(session._profile_name).auth_token or ""
179
+ if token and token != session.auth_token and token_still_valid(session.login_url, token):
180
+ logger.info("Auto-login: reusing token refreshed by another command")
181
+ session.auth_token = token
182
+ session._global_meta_cache = None
183
+ return True
184
+ return False
185
+
186
+
187
+ def _apply_login(session: Any, token: str, meta: dict, user_info: dict) -> None:
188
+ from hap_cli.core import global_meta as gm
189
+ from hap_cli.core.session import AuthenticationError
190
+
191
+ new_account = str(user_info.get("id") or "")
192
+ if session.account_id and new_account and new_account != session.account_id:
193
+ raise AuthenticationError(
194
+ "The browser is signed in to a different account "
195
+ f"({user_info.get('name') or new_account}); the saved login was "
196
+ "not changed. Sign in to the right account in the browser, or run "
197
+ "'hap auth login' to switch."
198
+ )
199
+ session.auth_token = token
200
+ if new_account:
201
+ session.account_id = new_account
202
+ endpoints = gm.extract_service_endpoints(meta)
203
+ if endpoints:
204
+ session.service_endpoints = endpoints
205
+ session._global_meta_cache = None
206
+ session.save(profile=session._profile_name)
207
+ logger.info("Auto-login: saved refreshed token to profile %s", session._profile_name)
208
+
209
+
210
+ def recover(
211
+ session: Any,
212
+ *,
213
+ login: Optional[Callable[..., tuple]] = None,
214
+ notify: Optional[Callable[[str, int], None]] = None,
215
+ ) -> bool:
216
+ """Try to replace an expired token. True means "retry the request".
217
+
218
+ Raises :class:`ReloginTimeout` when the authorization page was opened
219
+ but nobody clicked through in time, and ``AuthenticationError`` when
220
+ the browser handed back a different account. Every other way out is
221
+ ``False`` — the caller then raises its original error.
222
+ """
223
+ if not enabled(session):
224
+ return False
225
+ session._relogin_attempted = True
226
+
227
+ if token_still_valid(session.login_url, session.auth_token):
228
+ logger.info("Auto-login skipped: token is still valid, rejection is "
229
+ "not an expiry")
230
+ return False
231
+
232
+ from hap_cli.core import auth as auth_mod
233
+
234
+ login = login or auth_mod.login
235
+ notify = notify or getattr(session, "relogin_notice", None)
236
+ seconds = timeout_seconds()
237
+
238
+ with _login_lock():
239
+ if _adopt_saved_token(session):
240
+ return True
241
+ logger.info("Auto-login: opening browser authorization for %s "
242
+ "(timeout %ss)", session.login_url, seconds)
243
+ if notify:
244
+ try:
245
+ notify(session.login_url, seconds)
246
+ except Exception as exc:
247
+ logger.warning("Auto-login notice failed: %s", exc)
248
+ started = time.monotonic()
249
+ try:
250
+ token, _webui, user_info, meta = login(session.login_url, timeout=seconds)
251
+ except TimeoutError as exc:
252
+ logger.info("Auto-login timed out after %.0fs", time.monotonic() - started)
253
+ raise ReloginTimeout(str(exc)) from exc
254
+ _apply_login(session, token, meta, user_info)
255
+ return True
@@ -30,7 +30,7 @@ import logging
30
30
  import os
31
31
  import re
32
32
  from pathlib import Path
33
- from typing import Any, Optional
33
+ from typing import Any, Callable, Optional
34
34
  from urllib.parse import urlparse
35
35
 
36
36
  import requests
@@ -256,6 +256,11 @@ class Session:
256
256
  # status request goes through ``_post`` and must not re-trigger
257
257
  # the gate. Managed by ``hap_cli.core.cli_access``.
258
258
  self._cli_access_check_in_progress = False
259
+ # Expired-login recovery (hap_cli.core.relogin): runs at most once
260
+ # per process; ``relogin_notice(login_url, timeout)`` is set by the
261
+ # CLI layer to tell the user a browser page is waiting for them.
262
+ self._relogin_attempted = False
263
+ self.relogin_notice: Optional[Callable[[str, int], None]] = None
259
264
  self.default_app_id = ""
260
265
  self.default_project_id = ""
261
266
  self.default_project_name = ""
@@ -403,6 +408,50 @@ class Session:
403
408
  *,
404
409
  extra_headers: Optional[dict[str, str]] = None,
405
410
  query: Optional[dict] = None,
411
+ ) -> dict[str, Any]:
412
+ """Send a request; on an expired login, re-authorize and retry once.
413
+
414
+ A rejected token means the server never ran the request, so
415
+ resending it after a fresh login cannot apply a write twice.
416
+ """
417
+ try:
418
+ return self._send(url, data, timeout, method,
419
+ extra_headers=extra_headers, query=query)
420
+ except AuthenticationError:
421
+ if not self.recover_auth():
422
+ raise
423
+ logger.info("Retrying %s %s with the refreshed login", method.upper(), url)
424
+ return self._send(url, data, timeout, method,
425
+ extra_headers=extra_headers, query=query)
426
+
427
+ def recover_auth(self) -> bool:
428
+ """Re-run the browser login after the server rejected the token.
429
+
430
+ True means a fresh token is in place and the caller should resend
431
+ once. See :mod:`hap_cli.core.relogin` for when this declines.
432
+ """
433
+ from hap_cli.core import relogin
434
+
435
+ try:
436
+ return relogin.recover(self)
437
+ except relogin.ReloginTimeout:
438
+ raise AuthenticationError(
439
+ "Your login has expired. A browser authorization page was "
440
+ f"opened for {self.login_url} but was not confirmed within "
441
+ f"{relogin.timeout_seconds()}s. Run the command again and "
442
+ "click Authorize (授权) in the browser, or run 'hap auth login'.",
443
+ status_code=None,
444
+ ) from None
445
+
446
+ def _send(
447
+ self,
448
+ url: str,
449
+ data: Optional[dict],
450
+ timeout: int,
451
+ method: str = "POST",
452
+ *,
453
+ extra_headers: Optional[dict[str, str]] = None,
454
+ query: Optional[dict] = None,
406
455
  ) -> dict[str, Any]:
407
456
  """Execute an HTTP request with standard HAP headers.
408
457
 
@@ -326,6 +326,14 @@
326
326
  "✗ Publish failed: a workflow this one runs was not published, so this one was left alone.": {
327
327
  "en": "✗ Publish failed: a workflow this one runs was not published, so this one was left alone.",
328
328
  "zh-Hans": "✗ 发布失败:它调用的某条流程没有发布,所以这条也没有发布。"
329
+ },
330
+ "Your HAP login has expired. A browser page is open — click Authorize (授权) to continue (waiting up to {seconds}s).": {
331
+ "en": "Your HAP login has expired. A browser page is open — click Authorize (授权) to continue (waiting up to {seconds}s).",
332
+ "zh-Hans": "HAP 登录已过期,已在浏览器打开授权页——点击「授权」后命令会继续执行(最多等待 {seconds} 秒)。"
333
+ },
334
+ "Login expired — click Authorize (授权) in the browser to let the command continue.": {
335
+ "en": "Login expired — click Authorize (授权) in the browser to let the command continue.",
336
+ "zh-Hans": "登录已过期——请在浏览器中点击「授权」,命令即可继续。"
329
337
  }
330
338
  },
331
339
  "v3": {
@@ -3627,6 +3635,14 @@
3627
3635
  "en": "Output in JSON format",
3628
3636
  "zh-Hans": "以 JSON 格式输出"
3629
3637
  },
3638
+ "config auto-login": {
3639
+ "en": "Control whether an expired login is renewed in the browser.\n\n\b\n on when a command finds your login expired, open the browser\n authorization page, wait for you to click Authorize, then\n carry on (default)\n off stop with a \"not logged in\" error instead\n\nWithout an argument, shows the current setting. It never applies over\nSSH, in CI, or where no browser can be shown. Setting the environment\nvariable HAP_AUTO_LOGIN=0 turns it off for one shell.",
3640
+ "zh-Hans": "控制登录过期时是否在浏览器里自动续期。\n\n\b\n on 命令发现登录已过期时,打开浏览器授权页,等你点击「授权」后\n 继续执行(默认)\n off 直接报「未登录」错误\n\n不带参数时显示当前设置。通过 SSH、在 CI 里或无法显示浏览器时不会生效。设置环境变量 HAP_AUTO_LOGIN=0 可在当前 shell 里关闭。"
3641
+ },
3642
+ "config auto-login --json": {
3643
+ "en": "Output in JSON format",
3644
+ "zh-Hans": "以 JSON 格式输出"
3645
+ },
3630
3646
  "config completion": {
3631
3647
  "en": "Enable <Tab> completion for hap commands and options.\n\n\b\n hap config completion # show instructions for your shell\n hap config completion bash # show bash setup\n hap config completion zsh # show zsh setup\n hap config completion fish # show fish setup\n hap config completion --install # append the init line to your rc file",
3632
3648
  "zh-Hans": "为 hap 命令和选项启用 <Tab> 补全。\n\n\b\n hap config completion # 显示适用于当前 Shell 的说明\n hap config completion bash # 显示 bash 配置\n hap config completion zsh # 显示 zsh 配置\n hap config completion fish # 显示 fish 配置\n hap config completion --install # 将初始化行追加到 rc 文件"
@@ -101,6 +101,18 @@ def _disable_cli_access_gate(request, monkeypatch):
101
101
  cli_access._memo.clear()
102
102
 
103
103
 
104
+ @pytest.fixture(autouse=True)
105
+ def _disable_auto_login(monkeypatch):
106
+ """Never open a browser from a test.
107
+
108
+ A mocked 401 would otherwise send ``Session._post`` into the
109
+ expired-login recovery (``hap_cli.core.relogin``) and pop the
110
+ authorization page. ``test_relogin.py`` lifts this where it tests the
111
+ recovery itself.
112
+ """
113
+ monkeypatch.setenv("HAP_AUTO_LOGIN", "0")
114
+
115
+
104
116
  @pytest.fixture(autouse=True)
105
117
  def _forget_worksheet_rules():
106
118
  """Start every test with a cold business-rule cache.