hap-cli 0.8.19__tar.gz → 0.8.21__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.8.19 → hap_cli-0.8.21}/PKG-INFO +2 -1
- {hap_cli-0.8.19/hap_cli → hap_cli-0.8.21}/README.md +1 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21/hap_cli}/README.md +1 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/README_CN.md +1 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/__init__.py +1 -1
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/config_cmd.py +44 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/action_spec_adapter.py +9 -3
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/flow_node.py +41 -18
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/record.py +80 -5
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/session.py +33 -14
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/update.py +39 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/upload.py +31 -13
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/worksheet.py +57 -7
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/i18n.py +49 -0
- hap_cli-0.8.21/hap_cli/tests/test_content_language.py +120 -0
- hap_cli-0.8.21/hap_cli/tests/test_control_lookup_guard.py +179 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_core.py +22 -6
- hap_cli-0.8.21/hap_cli/tests/test_no_workflow_unsupported.py +92 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +20 -10
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_record_v3_semantic_cli.py +3 -1
- hap_cli-0.8.21/hap_cli/tests/test_update_mode.py +90 -0
- hap_cli-0.8.21/hap_cli/tests/test_upload_host_resolution.py +93 -0
- hap_cli-0.8.21/hap_cli/tests/test_workflow_envelope_errors.py +112 -0
- hap_cli-0.8.21/hap_cli/tests/test_workflow_read_write_keys.py +71 -0
- hap_cli-0.8.21/hap_cli/tests/test_worksheet_create_section.py +91 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +4 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli.egg-info/PKG-INFO +2 -1
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli.egg-info/SOURCES.txt +8 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/MANIFEST.in +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/app_live_tests/config.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/app_live_tests/harness.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/app_live_tests/smoke.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/app_live_tests/state.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/__main__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/app_creator_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/chart_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/post_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/record_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/context.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/charts.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/cleanup.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/compiler.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/config.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/design/design.schema.json +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/errors.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/executor.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/fields.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/hap.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/merge.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/recording/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/recording/console.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/recording/mirror.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/recording/report.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/schema.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/cli.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/executor.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/seed/template.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/selftest.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/smoke.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/steps.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/store.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/validate.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/chart.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/chat.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/department.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/group.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/page.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/post.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/role.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/locale/messages.json +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_app_creator.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_bug_036750.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_chart.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/__main__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/_wftest.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/charts.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/cleanup.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/compiler.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/config.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/errors.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/executor.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/fields.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/hap.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/recording/console.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/recording/report.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/schema.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/seed/template.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/selftest.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/smoke.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/step.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/steps.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/store.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/setup.cfg +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/setup.py +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/skills/cli/hap-cli-app-editor/scripts/editspec/component.schema.json +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/skills/cli/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/skills/cli/hap-cli-app-editor/scripts/editspec/envelope.schema.json +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/skills/cli/hap-cli-app-editor/scripts/editspec/field.schema.json +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.8.19 → hap_cli-0.8.21}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hap-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.21
|
|
4
4
|
Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
|
|
5
5
|
Author: hap-cli
|
|
6
6
|
License: Apache-2.0
|
|
@@ -313,6 +313,7 @@ discover every subcommand.
|
|
|
313
313
|
| `config show` | Display the current configuration summary |
|
|
314
314
|
| `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` completion |
|
|
315
315
|
| `config language [LANG]` | Switch UI language (`en` / `zh-Hans` / `zh-Hant` / `ja`) |
|
|
316
|
+
| `config update [auto\|notify\|off]` | Self-upgrade mode; `notify` only reports, `off` never checks |
|
|
316
317
|
| `config log on / off / status` | Toggle and inspect API logging |
|
|
317
318
|
| `config log level LEVEL` | `DEBUG / INFO / WARNING / ERROR / CRITICAL` |
|
|
318
319
|
| `config log view [--lines N] [--no-follow]` | Tail the log file |
|
|
@@ -284,6 +284,7 @@ discover every subcommand.
|
|
|
284
284
|
| `config show` | Display the current configuration summary |
|
|
285
285
|
| `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` completion |
|
|
286
286
|
| `config language [LANG]` | Switch UI language (`en` / `zh-Hans` / `zh-Hant` / `ja`) |
|
|
287
|
+
| `config update [auto\|notify\|off]` | Self-upgrade mode; `notify` only reports, `off` never checks |
|
|
287
288
|
| `config log on / off / status` | Toggle and inspect API logging |
|
|
288
289
|
| `config log level LEVEL` | `DEBUG / INFO / WARNING / ERROR / CRITICAL` |
|
|
289
290
|
| `config log view [--lines N] [--no-follow]` | Tail the log file |
|
|
@@ -284,6 +284,7 @@ discover every subcommand.
|
|
|
284
284
|
| `config show` | Display the current configuration summary |
|
|
285
285
|
| `config completion [bash\|zsh\|fish] [--install]` | Enable `<Tab>` completion |
|
|
286
286
|
| `config language [LANG]` | Switch UI language (`en` / `zh-Hans` / `zh-Hant` / `ja`) |
|
|
287
|
+
| `config update [auto\|notify\|off]` | Self-upgrade mode; `notify` only reports, `off` never checks |
|
|
287
288
|
| `config log on / off / status` | Toggle and inspect API logging |
|
|
288
289
|
| `config log level LEVEL` | `DEBUG / INFO / WARNING / ERROR / CRITICAL` |
|
|
289
290
|
| `config log view [--lines N] [--no-follow]` | Tail the log file |
|
|
@@ -258,6 +258,7 @@ hap --json worksheet record list WORKSHEET_ID
|
|
|
258
258
|
| `config show` | 显示 CLI 配置摘要 |
|
|
259
259
|
| `config completion [bash\|zsh\|fish] [--install]` | 启用 `<Tab>` 补全 |
|
|
260
260
|
| `config language [LANG]` | 切换显示语言(`en` / `zh-Hans` / `zh-Hant` / `ja`) |
|
|
261
|
+
| `config update [auto\|notify\|off]` | 自动升级开关;`notify` 只提示不安装,`off` 不检查 |
|
|
261
262
|
| `config log on / off / status` | 日志开关与状态 |
|
|
262
263
|
| `config log level LEVEL` | 设置日志级别(`DEBUG/INFO/WARNING/ERROR/CRITICAL`) |
|
|
263
264
|
| `config log view [--lines N] [--no-follow]` | 跟踪日志文件 |
|
|
@@ -122,6 +122,8 @@ def config_completion(shell, install):
|
|
|
122
122
|
@pass_context
|
|
123
123
|
def config_show(ctx):
|
|
124
124
|
"""Display current harness configuration."""
|
|
125
|
+
from hap_cli.core.update import get_update_mode
|
|
126
|
+
|
|
125
127
|
log_cfg = load_logging_config()
|
|
126
128
|
data = {
|
|
127
129
|
"logging": {
|
|
@@ -129,6 +131,7 @@ def config_show(ctx):
|
|
|
129
131
|
"level": log_cfg["level"],
|
|
130
132
|
"file": str(resolve_log_path(log_cfg)),
|
|
131
133
|
},
|
|
134
|
+
"update": get_update_mode(),
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
def _human(d):
|
|
@@ -136,6 +139,7 @@ def config_show(ctx):
|
|
|
136
139
|
click.echo(f" enabled : {d['logging']['enabled']}")
|
|
137
140
|
click.echo(f" level : {d['logging']['level']}")
|
|
138
141
|
click.echo(f" file : {d['logging']['file']}")
|
|
142
|
+
click.echo(f"update : {d['update']}")
|
|
139
143
|
|
|
140
144
|
ctx.output(data, _human)
|
|
141
145
|
|
|
@@ -205,6 +209,46 @@ def log_status(ctx):
|
|
|
205
209
|
_echo_log_status(ctx, cfg)
|
|
206
210
|
|
|
207
211
|
|
|
212
|
+
# ── `hap config update` ───────────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@config.command("update")
|
|
216
|
+
@click.argument(
|
|
217
|
+
"mode", required=False, type=click.Choice(["auto", "notify", "off"]),
|
|
218
|
+
)
|
|
219
|
+
@pass_context
|
|
220
|
+
def config_update(ctx, mode):
|
|
221
|
+
"""Control whether hap-cli upgrades itself.
|
|
222
|
+
|
|
223
|
+
\b
|
|
224
|
+
auto check hourly and install new versions (default)
|
|
225
|
+
notify check hourly and tell you, but never install
|
|
226
|
+
off never check
|
|
227
|
+
|
|
228
|
+
Use ``notify`` or ``off`` to pin the version in scripted or
|
|
229
|
+
production environments. Without an argument, shows the current
|
|
230
|
+
setting. ``hap update`` still upgrades on demand in every mode.
|
|
231
|
+
"""
|
|
232
|
+
from hap_cli.core.session import load_config, save_config
|
|
233
|
+
from hap_cli.core.update import get_update_mode
|
|
234
|
+
|
|
235
|
+
if mode is None:
|
|
236
|
+
current = get_update_mode()
|
|
237
|
+
if ctx.json_mode:
|
|
238
|
+
from hap_cli.utils.formatting import output_json
|
|
239
|
+
output_json({"update": current})
|
|
240
|
+
else:
|
|
241
|
+
click.echo(f"Self-update: {current}")
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
save_config({"update": mode})
|
|
245
|
+
if ctx.json_mode:
|
|
246
|
+
from hap_cli.utils.formatting import output_json
|
|
247
|
+
output_json({"status": "ok", "update": mode})
|
|
248
|
+
else:
|
|
249
|
+
click.echo(f"Self-update set to: {mode}")
|
|
250
|
+
|
|
251
|
+
|
|
208
252
|
# ── `hap config language` ─────────────────────────────────────────────────
|
|
209
253
|
|
|
210
254
|
|
|
@@ -110,9 +110,15 @@ def build_button_payload(spec: dict[str, Any]) -> dict[str, Any]:
|
|
|
110
110
|
# in the UI; for the dedicated 二次确认 button use clickType 2.
|
|
111
111
|
if out["clickType"] == 1 and confirm:
|
|
112
112
|
out["clickType"] = 2
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
# Dialog text is stored on the button itself, so it must be
|
|
114
|
+
# written in the application's language, not ours. Follows the
|
|
115
|
+
# active CLI language; an explicit value always wins.
|
|
116
|
+
from hap_cli.i18n import content_default
|
|
117
|
+
|
|
118
|
+
out["confirmMsg"] = confirm_msg or content_default("button.confirmMsg")
|
|
119
|
+
out["sureName"] = spec.get("sureName") or content_default("button.sure")
|
|
120
|
+
out["cancelName"] = (
|
|
121
|
+
spec.get("cancelName") or content_default("button.cancel"))
|
|
116
122
|
|
|
117
123
|
# enableWhen → filters (reuse the shared filter translator).
|
|
118
124
|
enable_when = spec.get("enableWhen")
|
|
@@ -394,27 +394,50 @@ def save_node(
|
|
|
394
394
|
if select_node_id:
|
|
395
395
|
data["selectNodeId"] = select_node_id
|
|
396
396
|
data.update(config)
|
|
397
|
-
|
|
398
|
-
# their filter: getNodeDetail returns it as ``conditions`` (see
|
|
399
|
-
# pd-openweb Branch/index.jsx, which reads ``result.conditions``), but
|
|
400
|
-
# saveNode only persists it under ``operateCondition`` (the same file
|
|
401
|
-
# sends ``saveNode({ operateCondition: data })``). The value shape is
|
|
402
|
-
# identical. Saving with the read-side ``conditions`` key is silently
|
|
403
|
-
# ignored by the server — the save still reports success but the
|
|
404
|
-
# condition is dropped. Accept the read key so copying a node's
|
|
405
|
-
# read structure straight back round-trips correctly.
|
|
406
|
-
if flow_node_type == 2 and "conditions" in data:
|
|
407
|
-
if "operateCondition" not in data:
|
|
408
|
-
data["operateCondition"] = data["conditions"]
|
|
409
|
-
logger.info(
|
|
410
|
-
"save_node: branch item %s — mapped read-side key "
|
|
411
|
-
"'conditions' to write-side 'operateCondition'", node_id)
|
|
412
|
-
# ``conditions`` is not a saveNode key; drop it so it doesn't ride
|
|
413
|
-
# along as dead weight.
|
|
414
|
-
data.pop("conditions", None)
|
|
397
|
+
_map_read_side_keys(data, node_id)
|
|
415
398
|
return session.workflow_call("flowNode/saveNode", data)
|
|
416
399
|
|
|
417
400
|
|
|
401
|
+
# Node config keys whose read form differs from their write form. Reading
|
|
402
|
+
# a node, editing one value and writing it back is the natural way to work
|
|
403
|
+
# with these APIs, but the server ignores the read-side key on save — the
|
|
404
|
+
# call still reports success while the value is dropped.
|
|
405
|
+
#
|
|
406
|
+
# The filter is asymmetric on every filtering node in pd-openweb, not just
|
|
407
|
+
# on branches: the component holds it as ``conditions`` and posts it as
|
|
408
|
+
# ``operateCondition``, with an identical value shape.
|
|
409
|
+
# Branch/index.jsx:184 saveNode({ operateCondition: conditions })
|
|
410
|
+
# GetMoreRecord/index.jsx:184 saveNode({ operateCondition: conditions })
|
|
411
|
+
# Search/index.jsx:190 saveNode({ operateCondition: conditions })
|
|
412
|
+
# FindSystem/index.jsx:110 saveNode({ operateCondition: conditions })
|
|
413
|
+
# ``conditions`` is never itself a saveNode key, so translating it is safe
|
|
414
|
+
# for any node type.
|
|
415
|
+
_READ_TO_WRITE_KEYS = {
|
|
416
|
+
"conditions": "operateCondition",
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _map_read_side_keys(data: dict[str, Any], node_id: str) -> None:
|
|
421
|
+
"""Rewrite read-side config keys to the keys saveNode persists.
|
|
422
|
+
|
|
423
|
+
An explicit write-side key always wins; the read-side key is then
|
|
424
|
+
dropped either way so it cannot ride along as dead weight.
|
|
425
|
+
"""
|
|
426
|
+
for read_key, write_key in _READ_TO_WRITE_KEYS.items():
|
|
427
|
+
if read_key not in data:
|
|
428
|
+
continue
|
|
429
|
+
if write_key in data:
|
|
430
|
+
logger.info(
|
|
431
|
+
"save_node: node %s carries both '%s' and '%s'; keeping "
|
|
432
|
+
"the write-side value", node_id, read_key, write_key)
|
|
433
|
+
else:
|
|
434
|
+
data[write_key] = data[read_key]
|
|
435
|
+
logger.info(
|
|
436
|
+
"save_node: node %s — mapped read-side key '%s' to "
|
|
437
|
+
"write-side '%s'", node_id, read_key, write_key)
|
|
438
|
+
data.pop(read_key, None)
|
|
439
|
+
|
|
440
|
+
|
|
418
441
|
# ── Testing ──────────────────────────────────────────────────────────────
|
|
419
442
|
|
|
420
443
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Record CRUD module for Mingdao HAP."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import logging
|
|
4
5
|
import uuid
|
|
5
6
|
from typing import Any, Optional
|
|
6
7
|
|
|
@@ -8,6 +9,8 @@ from hap_cli.core.session import Session, APIError
|
|
|
8
9
|
from hap_cli.core import worksheet as ws_mod
|
|
9
10
|
from hap_cli.core import global_meta as gm
|
|
10
11
|
|
|
12
|
+
logger = logging.getLogger("hap_cli.record")
|
|
13
|
+
|
|
11
14
|
# Discussion source types — pd-openweb src/components/comment/config.js
|
|
12
15
|
# WORKSHEET = 7 whole-worksheet discussion
|
|
13
16
|
# WORKSHEETROW = 8 single record/row discussion
|
|
@@ -383,6 +386,41 @@ def _legacy_cell_value(ctrl: dict[str, Any], value: Any) -> str:
|
|
|
383
386
|
return "" if value is None else str(value)
|
|
384
387
|
|
|
385
388
|
|
|
389
|
+
def _controls_for_write(
|
|
390
|
+
session: Session, worksheet_id: str,
|
|
391
|
+
) -> list[dict[str, Any]]:
|
|
392
|
+
"""Fetch the worksheet's controls for a write, refusing an empty list.
|
|
393
|
+
|
|
394
|
+
Every write path type-encodes its cells against this metadata, so an
|
|
395
|
+
empty list silently disables the encoder: option/member/relation/
|
|
396
|
+
sub-table values would all take the "unknown field -> send the raw
|
|
397
|
+
value" fallback and reach the server in whatever shape the caller
|
|
398
|
+
happened to type. That is how a hand-written 1-D sub-table value
|
|
399
|
+
(``[{childControlId: value}, ...]``) escaped to the server and failed
|
|
400
|
+
deserialization into ``List<Cell>`` — the encoder would otherwise have
|
|
401
|
+
rewritten it to the 2-D form.
|
|
402
|
+
|
|
403
|
+
``get_worksheet_controls`` answers ``[]`` for any response that does
|
|
404
|
+
not carry a ``controls`` list — a soft failure, ``data: null``, or a
|
|
405
|
+
permission-degraded envelope — none of which raise. A real worksheet
|
|
406
|
+
always has controls, so an empty list means "we could not read the
|
|
407
|
+
fields", never "this worksheet has none". Abort before writing.
|
|
408
|
+
"""
|
|
409
|
+
controls = ws_mod.get_worksheet_controls(session, worksheet_id)
|
|
410
|
+
if not controls:
|
|
411
|
+
logger.warning(
|
|
412
|
+
"GetWorksheetControls returned no fields for worksheet %s; "
|
|
413
|
+
"aborting the write instead of sending unencoded cells",
|
|
414
|
+
worksheet_id,
|
|
415
|
+
)
|
|
416
|
+
raise ValueError(
|
|
417
|
+
f"Could not read the fields of worksheet {worksheet_id}, so "
|
|
418
|
+
"nothing was written. Check the worksheet id and that your "
|
|
419
|
+
"account can open it, then try again."
|
|
420
|
+
)
|
|
421
|
+
return controls
|
|
422
|
+
|
|
423
|
+
|
|
386
424
|
def encode_controls_by_id(
|
|
387
425
|
session: Session,
|
|
388
426
|
worksheet_id: str,
|
|
@@ -419,7 +457,7 @@ def encode_controls_by_id(
|
|
|
419
457
|
(in-place edits keyed by ``rowid``, fresh adds, and deletes — see
|
|
420
458
|
:func:`_sublist_update_value`).
|
|
421
459
|
"""
|
|
422
|
-
controls =
|
|
460
|
+
controls = _controls_for_write(session, worksheet_id)
|
|
423
461
|
by_id: dict[str, dict[str, Any]] = {
|
|
424
462
|
c.get("controlId"): c for c in controls if c.get("controlId")
|
|
425
463
|
}
|
|
@@ -434,6 +472,14 @@ def encode_controls_by_id(
|
|
|
434
472
|
raw = rc.get("value")
|
|
435
473
|
ctrl = by_id.get(cid) or by_ref.get(cid)
|
|
436
474
|
if ctrl is None:
|
|
475
|
+
# Deliberate escape hatch (see docstring), but a value that
|
|
476
|
+
# skips the type encoder is exactly how a malformed cell
|
|
477
|
+
# reaches the server, so leave a trace: this is the only
|
|
478
|
+
# place a write can go out unencoded.
|
|
479
|
+
logger.warning(
|
|
480
|
+
"Field %r is not on worksheet %s; its value is sent "
|
|
481
|
+
"unencoded (no type conversion applied)", cid, worksheet_id,
|
|
482
|
+
)
|
|
437
483
|
out.append({"controlId": cid, "value": raw})
|
|
438
484
|
continue
|
|
439
485
|
out.append({
|
|
@@ -603,7 +649,7 @@ def batch_create_by_name(
|
|
|
603
649
|
by child controlId; single/multi select -> option keys; etc.).
|
|
604
650
|
Evidence: hap_cli/tests/answer_create_record.md.
|
|
605
651
|
"""
|
|
606
|
-
controls =
|
|
652
|
+
controls = _controls_for_write(session, worksheet_id)
|
|
607
653
|
by_name: dict[str, dict[str, Any]] = {}
|
|
608
654
|
for c in controls:
|
|
609
655
|
for key in (c.get("controlName"), c.get("alias"),
|
|
@@ -662,7 +708,7 @@ def batch_update_by_name(
|
|
|
662
708
|
:func:`_sublist_update_value`). Record updates never use the V3
|
|
663
709
|
rows endpoint — V3 silently drops relation / sub-table cells.
|
|
664
710
|
"""
|
|
665
|
-
controls =
|
|
711
|
+
controls = _controls_for_write(session, worksheet_id)
|
|
666
712
|
by_name: dict[str, dict[str, Any]] = {}
|
|
667
713
|
for c in controls:
|
|
668
714
|
for key in (c.get("controlName"), c.get("alias"),
|
|
@@ -785,6 +831,35 @@ def get_record(
|
|
|
785
831
|
return session.api_call("Worksheet", "GetRowDetail", data)
|
|
786
832
|
|
|
787
833
|
|
|
834
|
+
# The main-site record endpoints have no workflow-suppression parameter.
|
|
835
|
+
# pd-openweb documents UpdateWorksheetRow as worksheetId / rowId /
|
|
836
|
+
# newOldControl / viewId / instanceId / workId / btnId / btnRemark /
|
|
837
|
+
# btnWorksheetId / btnRowId / pushUniqueId / rowStatus / updateType
|
|
838
|
+
# (src/api/worksheet.js:535-548), and AddWorksheetRow likewise has none.
|
|
839
|
+
# ``triggerWorkflow`` belongs to the open API v2 (worksheet/addRow,
|
|
840
|
+
# worksheet/editRow) — a different transport. We used to send it anyway;
|
|
841
|
+
# the main site accepted the body and ignored the flag, so the workflows
|
|
842
|
+
# ran while the caller believed they had been suppressed (confirmed live:
|
|
843
|
+
# a record-update flow stamped its field on an update sent with
|
|
844
|
+
# triggerWorkflow: false).
|
|
845
|
+
#
|
|
846
|
+
# Record writes cannot simply move to the open API: it silently drops
|
|
847
|
+
# RELATE_SHEET / SUB_LIST cells. Until there is a real way to suppress,
|
|
848
|
+
# refusing is the only honest answer — anyone reaching for this is trying
|
|
849
|
+
# to edit data *safely*, and quietly doing the unsafe thing is worse than
|
|
850
|
+
# failing.
|
|
851
|
+
def _reject_workflow_suppression(trigger_workflow: bool) -> None:
|
|
852
|
+
if trigger_workflow:
|
|
853
|
+
return
|
|
854
|
+
raise ValueError(
|
|
855
|
+
"Workflows cannot be suppressed for this write — they will run "
|
|
856
|
+
"whatever this command does, so the option is refused rather than "
|
|
857
|
+
"ignored. To edit records without them, turn the workflow off "
|
|
858
|
+
"first (hap workflow publish <id> --disable), make the change, "
|
|
859
|
+
"then turn it back on."
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
|
|
788
863
|
def create_record(
|
|
789
864
|
session: Session,
|
|
790
865
|
worksheet_id: str,
|
|
@@ -802,13 +877,13 @@ def create_record(
|
|
|
802
877
|
Returns:
|
|
803
878
|
Created record info
|
|
804
879
|
"""
|
|
880
|
+
_reject_workflow_suppression(trigger_workflow)
|
|
805
881
|
return session.api_call(
|
|
806
882
|
"Worksheet",
|
|
807
883
|
"AddWorksheetRow",
|
|
808
884
|
{
|
|
809
885
|
"worksheetId": worksheet_id,
|
|
810
886
|
"receiveControls": controls,
|
|
811
|
-
"triggerWorkflow": trigger_workflow,
|
|
812
887
|
},
|
|
813
888
|
)
|
|
814
889
|
|
|
@@ -832,6 +907,7 @@ def update_record(
|
|
|
832
907
|
Returns:
|
|
833
908
|
Update result
|
|
834
909
|
"""
|
|
910
|
+
_reject_workflow_suppression(trigger_workflow)
|
|
835
911
|
return session.api_call(
|
|
836
912
|
"Worksheet",
|
|
837
913
|
"UpdateWorksheetRow",
|
|
@@ -839,7 +915,6 @@ def update_record(
|
|
|
839
915
|
"worksheetId": worksheet_id,
|
|
840
916
|
"rowId": row_id,
|
|
841
917
|
"newOldControl": controls,
|
|
842
|
-
"triggerWorkflow": trigger_workflow,
|
|
843
918
|
},
|
|
844
919
|
)
|
|
845
920
|
|
|
@@ -360,11 +360,19 @@ class Session:
|
|
|
360
360
|
from hap_cli.core import cli_access
|
|
361
361
|
|
|
362
362
|
cli_access.ensure_before_request(self, url)
|
|
363
|
+
from hap_cli.i18n import get_active_language
|
|
364
|
+
|
|
363
365
|
headers = {
|
|
364
366
|
"Content-Type": "application/json",
|
|
365
367
|
"Authorization": f"md_pss_id {self.auth_token}",
|
|
366
368
|
"X-Requested-With": "XMLHttpRequest",
|
|
367
369
|
"User-Agent": hap_user_agent(),
|
|
370
|
+
# Give the server the language context it otherwise has to
|
|
371
|
+
# guess at. Defaults the server fills in (hints, template
|
|
372
|
+
# text) were coming back in Simplified Chinese for
|
|
373
|
+
# all-English applications because API requests carried no
|
|
374
|
+
# language at all — the browser always sends one.
|
|
375
|
+
"Accept-Language": get_active_language(),
|
|
368
376
|
}
|
|
369
377
|
# pd-openweb sends the caller's own accountId on every request
|
|
370
378
|
# (``src/common/global.js`` ``disposeRequestParams``). Some
|
|
@@ -529,28 +537,39 @@ class Session:
|
|
|
529
537
|
# Detect each and unwrap accordingly.
|
|
530
538
|
if not isinstance(result, dict):
|
|
531
539
|
return result
|
|
540
|
+
# A non-empty ``exception`` means the call failed, whatever envelope
|
|
541
|
+
# it arrived in. This is the frontend's own and only test — mdyAPI
|
|
542
|
+
# rejects on ``if (responseData.exception)`` alone and never looks at
|
|
543
|
+
# ``status`` (pd-openweb src/common/global.js:846-849). Checking it
|
|
544
|
+
# before any unwrapping is what makes workflow failures visible: the
|
|
545
|
+
# workflow envelope has no ``state``, so it used to skip the error
|
|
546
|
+
# branch entirely and hand the caller a "successful" empty result.
|
|
547
|
+
exception_msg = str(result.get("exception", "") or "")
|
|
548
|
+
if exception_msg:
|
|
549
|
+
code = result.get("code", result.get("state"))
|
|
550
|
+
# Some HAP deployments tunnel auth failures through the exception
|
|
551
|
+
# text/code instead of HTTP 401. Surface those as
|
|
552
|
+
# AuthenticationError so the CLI shows the login hint.
|
|
553
|
+
if _looks_like_auth_failure(code, exception_msg):
|
|
554
|
+
raise AuthenticationError(exception_msg, status_code=None)
|
|
555
|
+
raise APIError(exception_msg, code=code)
|
|
532
556
|
if "status" in result and "data" in result and "state" not in result:
|
|
533
|
-
# Workflow envelope
|
|
534
|
-
#
|
|
535
|
-
#
|
|
557
|
+
# Workflow envelope: ``status`` carries no success/failure
|
|
558
|
+
# semantics (the frontend ignores it), and a failure would have
|
|
559
|
+
# been caught by the exception check above.
|
|
536
560
|
return result.get("data", result)
|
|
537
561
|
if "state" not in result:
|
|
538
562
|
return result
|
|
539
563
|
if result.get("state") == 0:
|
|
540
|
-
exception_msg = str(result.get("exception", "") or "")
|
|
541
564
|
code = result.get("code")
|
|
542
|
-
#
|
|
543
|
-
#
|
|
544
|
-
|
|
545
|
-
if _looks_like_auth_failure(code, exception_msg):
|
|
565
|
+
# state=0 with no exception text — still a failure, just an
|
|
566
|
+
# unlabelled one.
|
|
567
|
+
if _looks_like_auth_failure(code, ""):
|
|
546
568
|
raise AuthenticationError(
|
|
547
|
-
|
|
569
|
+
"Authentication failed (upstream reported login required).",
|
|
548
570
|
status_code=None,
|
|
549
571
|
)
|
|
550
|
-
raise APIError(
|
|
551
|
-
exception_msg or "Unknown API error",
|
|
552
|
-
code=code,
|
|
553
|
-
)
|
|
572
|
+
raise APIError("Unknown API error", code=code)
|
|
554
573
|
return result.get("data", result)
|
|
555
574
|
|
|
556
575
|
# ── API Call Methods ──────────────────────────────────────────────────
|
|
@@ -994,7 +1013,7 @@ PROFILE_FIELDS = (
|
|
|
994
1013
|
# are properties of the human at the terminal / the install, not of any
|
|
995
1014
|
# one account, so logger and the language commands keep reading/writing
|
|
996
1015
|
# them via the plain load_config()/save_config() top-level accessors.
|
|
997
|
-
GLOBAL_FIELDS = ("logging", "language", "language_source")
|
|
1016
|
+
GLOBAL_FIELDS = ("logging", "language", "language_source", "update")
|
|
998
1017
|
|
|
999
1018
|
|
|
1000
1019
|
def _empty_v2_doc() -> dict:
|
|
@@ -25,6 +25,8 @@ from typing import Any, Optional
|
|
|
25
25
|
|
|
26
26
|
import requests
|
|
27
27
|
|
|
28
|
+
from hap_cli.core.session import load_config
|
|
29
|
+
|
|
28
30
|
logger = logging.getLogger("hap_cli.update")
|
|
29
31
|
|
|
30
32
|
PYPI_PACKAGE = "hap-cli"
|
|
@@ -172,6 +174,27 @@ def save_state(state: dict) -> None:
|
|
|
172
174
|
# ── Background check orchestration ───────────────────────────────────────
|
|
173
175
|
|
|
174
176
|
|
|
177
|
+
UPDATE_MODES = ("auto", "notify", "off")
|
|
178
|
+
DEFAULT_UPDATE_MODE = "auto"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def get_update_mode() -> str:
|
|
182
|
+
"""Resolve the self-update mode from config.
|
|
183
|
+
|
|
184
|
+
``auto`` checks and upgrades, ``notify`` checks and reports without
|
|
185
|
+
upgrading, ``off`` does not check at all. Anything unrecognised — or
|
|
186
|
+
no setting — is ``auto``, the behaviour that shipped before this was
|
|
187
|
+
configurable.
|
|
188
|
+
"""
|
|
189
|
+
try:
|
|
190
|
+
cfg = load_config() or {}
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.warning("could not read the update mode from config: %s", e)
|
|
193
|
+
return DEFAULT_UPDATE_MODE
|
|
194
|
+
mode = cfg.get("update")
|
|
195
|
+
return mode if mode in UPDATE_MODES else DEFAULT_UPDATE_MODE
|
|
196
|
+
|
|
197
|
+
|
|
175
198
|
def maybe_spawn_background_check() -> None:
|
|
176
199
|
"""Spawn a detached background update check if it is due.
|
|
177
200
|
|
|
@@ -179,6 +202,8 @@ def maybe_spawn_background_check() -> None:
|
|
|
179
202
|
*before* spawning so rapid consecutive commands cannot stack
|
|
180
203
|
duplicate children — at worst a failed child costs one interval.
|
|
181
204
|
"""
|
|
205
|
+
if get_update_mode() == "off":
|
|
206
|
+
return
|
|
182
207
|
kind = get_install_kind()
|
|
183
208
|
if kind in ("editable", "unknown"):
|
|
184
209
|
return
|
|
@@ -246,6 +271,15 @@ def run_background_update() -> None:
|
|
|
246
271
|
save_state(state)
|
|
247
272
|
return
|
|
248
273
|
|
|
274
|
+
if get_update_mode() == "notify":
|
|
275
|
+
# Report it and stop — the point of this mode is that the
|
|
276
|
+
# installed version never changes underneath a running script.
|
|
277
|
+
state["last_result"] = "update_available"
|
|
278
|
+
state["notified"] = False
|
|
279
|
+
save_state(state)
|
|
280
|
+
logger.info("update to %s available (notify-only mode)", latest)
|
|
281
|
+
return
|
|
282
|
+
|
|
249
283
|
ok, output = perform_upgrade(get_install_kind())
|
|
250
284
|
if ok:
|
|
251
285
|
state["last_result"] = "upgraded"
|
|
@@ -280,6 +314,11 @@ def consume_startup_notice() -> Optional[str]:
|
|
|
280
314
|
f"Automatic upgrade to {latest} failed — "
|
|
281
315
|
"run `hap update` to upgrade manually."
|
|
282
316
|
)
|
|
317
|
+
elif result == "update_available":
|
|
318
|
+
latest = state.get("latest_version") or "a newer version"
|
|
319
|
+
msg = (
|
|
320
|
+
f"hap-cli {latest} is available — run `hap update` to install it."
|
|
321
|
+
)
|
|
283
322
|
else:
|
|
284
323
|
return None
|
|
285
324
|
|
|
@@ -30,12 +30,11 @@ from urllib.parse import urlparse
|
|
|
30
30
|
|
|
31
31
|
import requests
|
|
32
32
|
|
|
33
|
-
from hap_cli.core.session import Session
|
|
33
|
+
from hap_cli.core.session import Session, SessionError
|
|
34
34
|
logger = logging.getLogger("hap_cli.upload")
|
|
35
35
|
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
_PUBLIC_UPLOAD_HOST = "https://upload.qiniup.com/"
|
|
36
|
+
# The upload endpoint is always read from Global/GetGlobalMeta — there is
|
|
37
|
+
# deliberately no constant to fall back to, see upload_host().
|
|
39
38
|
|
|
40
39
|
# bucket ids (pd-openweb kc/utils): 4=picture(mdpic), 3=document(mdoc).
|
|
41
40
|
_PICTURE_EXTS = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg",
|
|
@@ -67,17 +66,36 @@ def _bucket_for_ext(ext: str) -> int:
|
|
|
67
66
|
|
|
68
67
|
|
|
69
68
|
def upload_host(session: Session) -> str:
|
|
70
|
-
"""Resolve
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
"""Resolve this environment's file-store upload endpoint.
|
|
70
|
+
|
|
71
|
+
Global/GetGlobalMeta answers ``{config: {...}, "md.global": {...}}``
|
|
72
|
+
and publishes the file store under ``md.global.FileStoreConfig`` —
|
|
73
|
+
the same object every frontend upload posts to
|
|
74
|
+
(``md.global.FileStoreConfig.uploadHost``, pd-openweb
|
|
75
|
+
src/library/plupload/createUploader.js:43 and 12 sibling call sites,
|
|
76
|
+
none of which has a fallback). We used to read ``FileStoreConfig``
|
|
77
|
+
at the top level, so it was never found on any environment and a
|
|
78
|
+
hardcoded public-qiniu constant was used instead. That constant
|
|
79
|
+
equals the public SaaS's own uploadHost, which hid the bug there and
|
|
80
|
+
broke every environment with its own file store: the bytes went to
|
|
81
|
+
public qiniu while the upload token came from the environment's
|
|
82
|
+
store, so the store answered 401 with nothing to point at.
|
|
83
|
+
|
|
84
|
+
A missing host is an error rather than a guess — guessing is what
|
|
85
|
+
turned a routing bug into an unexplainable 401.
|
|
86
|
+
"""
|
|
87
|
+
meta = session.get_global_meta()
|
|
88
|
+
for scope in (meta.get("md.global"), meta):
|
|
89
|
+
if not isinstance(scope, dict):
|
|
90
|
+
continue
|
|
91
|
+
host = (scope.get("FileStoreConfig") or {}).get("uploadHost")
|
|
76
92
|
if host:
|
|
77
93
|
return host
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
raise SessionError(
|
|
95
|
+
"This server did not publish a file upload address, so the file "
|
|
96
|
+
"cannot be uploaded. Check that you are signed in to the right "
|
|
97
|
+
"server, then try again."
|
|
98
|
+
)
|
|
81
99
|
|
|
82
100
|
|
|
83
101
|
def get_upload_token(
|