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.
- {hap_cli-0.9.5 → hap_cli-0.9.6}/PKG-INFO +2 -1
- {hap_cli-0.9.5/hap_cli → hap_cli-0.9.6}/README.md +1 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6/hap_cli}/README.md +1 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/README_CN.md +1 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/__init__.py +1 -1
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/config_cmd.py +42 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/context.py +26 -1
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/chat.py +36 -19
- hap_cli-0.9.6/hap_cli/core/relogin.py +255 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/session.py +50 -1
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/locale/messages.json +16 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/conftest.py +12 -0
- hap_cli-0.9.6/hap_cli/tests/test_relogin.py +234 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli.egg-info/PKG-INFO +2 -1
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli.egg-info/SOURCES.txt +2 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/MANIFEST.in +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/config.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/fixtures/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/fixtures/feedback_scenario.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/harness.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/smoke.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/app_live_tests/state.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/__main__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/_entry.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/app_creator_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/attachment_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/chart_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/guide_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/post_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/record_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/charts.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/cleanup.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/compiler.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/config.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/design/design.schema.json +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/errors.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/executor.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/fields.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/hap.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/merge.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/recording/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/recording/console.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/recording/mirror.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/recording/report.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/schema.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/executor.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/seed/template.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/selftest.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/smoke.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/steps.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/store.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/validate.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/attachment.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/chart.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/condition_dsl.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/department.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/editspec/component.schema.json +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/editspec/field.schema.json +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/filter_v3.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/group.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/guide.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/layout_check.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/page.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/post.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/record.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/record_defaults.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/record_form_checks.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/record_lookups.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/record_rules.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/relation_scope.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/role.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/rule_spec_adapter.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/update.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/worksheet_form.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/worksheet_reference.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/approval.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/chart.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/messages.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/overview.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/post.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/record.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/setup.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/skills.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/workflow.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/en/worksheet.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/index.json +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/approval.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/chart.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/messages.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/overview.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/post.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/record.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/setup.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/skills.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/workflow.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/guide/zh-Hans/worksheet.md +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/i18n.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_action_mdpublic_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_advanced_setting_guard.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_app_creator.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_app_light_color.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_app_section_name.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_approval_submit_fill_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_approval_wire.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_atomic_write.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_attachment_share.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_batch_add_atomic.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_bug_036750.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_calendar_date_range.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_calendar_discussion_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_chart.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_chart_filter_merge.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_chat_attachment_send.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_chat_messages_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_chat_recall_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_chat_send_socket.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_coded_responses.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_condition_dsl.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_condition_dsl_entries.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_condition_dsl_readback.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_content_language.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_control_lookup_guard.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_core.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_create_worksheet_partial.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_dsl_existing_node_refs.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_dsl_multi_record_guard.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_editor_inline_subtable.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_engine_core_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_arg_consistency.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_bidirectional.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_discussion_mentions.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_entry_module.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_field_record_ref.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_http_error_body.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_json_anywhere.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_json_file_arg.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_layout_check.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_node_get_detail.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_node_summary.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_post_cursor.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_publish_reasons.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_replicate_workflow.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_save_fields_shape.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_subgroups.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_subtable_read.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_worksheet_references.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_worksheet_visibility.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fb_worksheet_workflows.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_fields_mdpublic_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g1_chart_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g1_record_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g1_upload_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g1_worksheet_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g2_coverage_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g3_workflow_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g4_approval_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g5_low_frequency_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g6_advanced_fields.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g6_fields_roundtrip.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g7_misc_gaps.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g7b_permanent_delete.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g8_filter_operator.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g8b_pivot_operators.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g8c_empty_filter_and_help_truth.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_g9_appid_resolve.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_guide.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_guide_sections.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_inner_flow_batch_add.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_instance_envelope.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_jsonschema_mini_copy.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_noninteractive_confirm.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_page_filters_group_read.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_page_filters_group_update.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_page_save_page_id.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_post_attachments.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_post_scope_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_business_rules.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_defaults.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_doc_id_hidden.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_form_checks.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_run_action.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_record_write_fixes.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_relation_scope.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_rmw_mdpublic_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_role_app_scope.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_rule_filters_and_field_warning.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_save_action_silent_drop.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_save_rule_spec.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_select_add_option.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_subtable_mdpublic_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_update_mode.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_upload_allow_down.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_upload_host_resolution.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_upload_mdpublic_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_bool_flags.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_response_format_json.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_view_mdpublic_parity.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_config_keys.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_inner_scope.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_json_file_arg.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_node_delete_missing.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_save_action_dsl_fields.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_structure_listing.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_trigger_outcome.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wf_trigger_preserve.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_wo4_result_handling.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_code_node.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_condition_field_types.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_condition_slots.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_delete_cascade.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_lifecycle_wire.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_node_wire.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_publish_inner.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_worksheet_create_section.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_worksheet_filter_read.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_worksheet_switch.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/utils/atomic_write.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/utils/confirm.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/__main__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/_wftest.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/charts.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/cleanup.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/compiler.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/config.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/errors.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/executor.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/fields.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/hap.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/recording/console.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/recording/report.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/schema.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/seed/template.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/selftest.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/smoke.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/step.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/steps.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/store.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/setup.cfg +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/setup.py +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.9.5 → hap_cli-0.9.6}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {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.
|
|
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]` | 跟踪日志文件 |
|
|
@@ -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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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.
|