hap-cli 0.8.26__tar.gz → 0.8.28__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.26 → hap_cli-0.8.28}/PKG-INFO +1 -1
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/__init__.py +1 -1
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/chat_cmd.py +80 -4
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/instance_cmd.py +102 -2
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/post_cmd.py +69 -9
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/record_cmd.py +11 -1
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/chat.py +153 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/post.py +23 -7
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/record.py +22 -9
- hap_cli-0.8.28/hap_cli/guide/en/approval.md +118 -0
- hap_cli-0.8.28/hap_cli/guide/en/post.md +101 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/index.json +16 -0
- hap_cli-0.8.28/hap_cli/guide/zh-Hans/approval.md +105 -0
- hap_cli-0.8.28/hap_cli/guide/zh-Hans/post.md +89 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/i18n.py +12 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/locale/messages.json +64 -12
- hap_cli-0.8.28/hap_cli/tests/test_approval_submit_fill_cli.py +143 -0
- hap_cli-0.8.28/hap_cli/tests/test_chat_recall_cli.py +159 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_core.py +25 -0
- hap_cli-0.8.28/hap_cli/tests/test_post_scope_cli.py +203 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli.egg-info/PKG-INFO +1 -1
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli.egg-info/SOURCES.txt +7 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/MANIFEST.in +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/README.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/app_live_tests/config.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/app_live_tests/harness.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/app_live_tests/smoke.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/app_live_tests/state.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/README.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/README_CN.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/__main__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/app_creator_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/attachment_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/chart_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/config_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/guide_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/context.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/charts.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/cleanup.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/compiler.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/config.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/design/design.schema.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/errors.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/executor.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/fields.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/hap.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/merge.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/recording/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/recording/console.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/recording/mirror.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/recording/report.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/schema.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/executor.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/template.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/selftest.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/smoke.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/steps.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/store.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/validate.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/attachment.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/chart.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/department.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/editspec/component.schema.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/editspec/field.schema.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/group.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/guide.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/page.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/role.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/session.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/update.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/en/chart.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/en/messages.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/en/overview.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/en/record.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/en/setup.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/en/skills.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/chart.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/messages.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/overview.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/record.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/setup.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/skills.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_app_creator.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_attachment_share.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_bug_036750.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_chart.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_chat_attachment_send.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_content_language.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_control_lookup_guard.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_guide.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_record_write_fixes.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_select_add_option.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_update_mode.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_upload_host_resolution.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_worksheet_create_section.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/__main__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/_wftest.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/charts.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/cleanup.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/compiler.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/config.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/errors.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/executor.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/fields.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/hap.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/recording/console.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/recording/report.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/schema.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/seed/template.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/selftest.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/smoke.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/step.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/steps.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/store.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/setup.cfg +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/setup.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.28}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -274,8 +274,11 @@ def chat_send_to_one(ctx, to, message, files, dry_run):
|
|
|
274
274
|
\b
|
|
275
275
|
hap chat send-to-one -t <accountId> -m "spec attached" -f ./spec.pdf
|
|
276
276
|
|
|
277
|
-
Text and each file arrive as separate messages, in that order.
|
|
278
|
-
|
|
277
|
+
Text and each file arrive as separate messages, in that order.
|
|
278
|
+
|
|
279
|
+
To message yourself, send to the file-transfer assistant with
|
|
280
|
+
-t file-transfer. Your own account id is not a valid target and is
|
|
281
|
+
skipped.
|
|
279
282
|
"""
|
|
280
283
|
if not to:
|
|
281
284
|
ctx.handle_error(click.UsageError(
|
|
@@ -302,9 +305,20 @@ def chat_send_to_one(ctx, to, message, files, dry_run):
|
|
|
302
305
|
return
|
|
303
306
|
try:
|
|
304
307
|
session = ctx.get_session()
|
|
308
|
+
# The file-transfer assistant is a conversation id, not an account
|
|
309
|
+
# id, so the account-addressed endpoint drops it silently. It goes
|
|
310
|
+
# over the socket path the chat panel uses instead.
|
|
311
|
+
socket_targets = [t for t in to if t == chat_mod.FILE_TRANSFER_ID]
|
|
312
|
+
account_targets = [t for t in to if t != chat_mod.FILE_TRANSFER_ID]
|
|
305
313
|
if not sources:
|
|
306
|
-
|
|
307
|
-
|
|
314
|
+
results = {}
|
|
315
|
+
if account_targets:
|
|
316
|
+
results["accounts"] = chat_mod.send_message(
|
|
317
|
+
session, account_targets, message)
|
|
318
|
+
for target in socket_targets:
|
|
319
|
+
results[target] = chat_mod.send_user_message(
|
|
320
|
+
session, target, message)
|
|
321
|
+
ctx.output(results, lambda d: click.echo("Message sent."))
|
|
308
322
|
return
|
|
309
323
|
descriptors = chat_mod.upload_chat_files(session, sources)
|
|
310
324
|
acks = chat_mod.send_files(session, list(to), descriptors, message)
|
|
@@ -430,3 +444,65 @@ def chat_card_detail(ctx, task, post, calendar, kcfile, worksheet, worksheetrow)
|
|
|
430
444
|
except Exception as e:
|
|
431
445
|
ctx.handle_error(e)
|
|
432
446
|
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
@chat.command("recall")
|
|
450
|
+
@click.argument("message_id")
|
|
451
|
+
@click.option("--group-id", "group_id", default="", help="Group id the message is in")
|
|
452
|
+
@click.option("--with-user", "with_user", default="",
|
|
453
|
+
help="Peer accountId for a 1-on-1 message")
|
|
454
|
+
@click.option("--time", "msg_time", default="",
|
|
455
|
+
help="Send time of the message. Looked up from the conversation "
|
|
456
|
+
"when omitted.")
|
|
457
|
+
@click.option("--as-admin", is_flag=True,
|
|
458
|
+
help="Recall as a group admin. Admins may recall another "
|
|
459
|
+
"member's message; ordinary recall only covers your own.")
|
|
460
|
+
@click.option("--dry-run", is_flag=True,
|
|
461
|
+
help="Show which message would be recalled, without recalling it.")
|
|
462
|
+
@pass_context
|
|
463
|
+
def chat_recall(ctx, message_id, group_id, with_user, msg_time, as_admin, dry_run):
|
|
464
|
+
"""Recall a message you sent.
|
|
465
|
+
|
|
466
|
+
You can recall your own message within 5 minutes of sending it; a group
|
|
467
|
+
admin can recall another member's message with no time limit. A recall
|
|
468
|
+
cannot be undone.
|
|
469
|
+
"""
|
|
470
|
+
if bool(group_id) == bool(with_user):
|
|
471
|
+
raise click.UsageError("Pass exactly one of --group-id or --with-user.")
|
|
472
|
+
try:
|
|
473
|
+
session = ctx.get_session()
|
|
474
|
+
found = None
|
|
475
|
+
if not msg_time:
|
|
476
|
+
# The recall payload carries the message's send time; look it
|
|
477
|
+
# up so the caller only needs the message id.
|
|
478
|
+
history = (
|
|
479
|
+
chat_mod.get_group_messages(session, group_id, num=50)
|
|
480
|
+
if group_id else
|
|
481
|
+
chat_mod.get_user_messages(session, with_user, num=50)
|
|
482
|
+
) or []
|
|
483
|
+
for m in history:
|
|
484
|
+
if str(m.get("id")) == str(message_id):
|
|
485
|
+
found = m
|
|
486
|
+
msg_time = m.get("time", "")
|
|
487
|
+
break
|
|
488
|
+
if dry_run:
|
|
489
|
+
ctx.output(
|
|
490
|
+
{"messageId": message_id,
|
|
491
|
+
"groupId": group_id, "withUser": with_user,
|
|
492
|
+
"time": msg_time,
|
|
493
|
+
"preview": ((found or {}).get("msg") or {}).get("con"),
|
|
494
|
+
"recalled": False},
|
|
495
|
+
lambda d: click.echo(
|
|
496
|
+
f"Would recall {d['messageId']}"
|
|
497
|
+
+ (f" ({d['preview']!r})" if d.get("preview") else "")),
|
|
498
|
+
)
|
|
499
|
+
return
|
|
500
|
+
admin_id = session.account_id if as_admin else ""
|
|
501
|
+
data = chat_mod.withdraw_message(
|
|
502
|
+
session, message_id,
|
|
503
|
+
group_id=group_id, to_user=with_user,
|
|
504
|
+
time=msg_time, admin_account_id=admin_id,
|
|
505
|
+
)
|
|
506
|
+
ctx.output(data, lambda d: click.echo("Message recalled."))
|
|
507
|
+
except Exception as e:
|
|
508
|
+
ctx.handle_error(e)
|
|
@@ -6,6 +6,11 @@ import click
|
|
|
6
6
|
|
|
7
7
|
from hap_cli.context import pass_context
|
|
8
8
|
from hap_cli.core import instance as inst_mod
|
|
9
|
+
from hap_cli.core import record as rec_mod
|
|
10
|
+
from hap_cli.commands.record_cmd import (
|
|
11
|
+
_fields_json_to_raw as _rec_fields_json_to_raw,
|
|
12
|
+
_parse_fields as _rec_parse_fields,
|
|
13
|
+
)
|
|
9
14
|
from hap_cli.utils.formatting import output_json, output_table, output_kv
|
|
10
15
|
from hap_cli.i18n import t
|
|
11
16
|
|
|
@@ -165,15 +170,110 @@ def instance_sign(ctx, instance_id, sign_to, before, work_id, opinion):
|
|
|
165
170
|
ctx.handle_error(e)
|
|
166
171
|
|
|
167
172
|
|
|
173
|
+
def _unwritten_fields(session, worksheet_id, row_id, controls):
|
|
174
|
+
"""Names of controls whose non-empty write did not land on the record.
|
|
175
|
+
|
|
176
|
+
A fill-in node only accepts the fields on its own form; anything else is
|
|
177
|
+
dropped silently. Only non-empty writes are checked, so deliberately
|
|
178
|
+
clearing a field is never reported.
|
|
179
|
+
"""
|
|
180
|
+
intended = [
|
|
181
|
+
c["controlId"] for c in controls
|
|
182
|
+
if str(c.get("value") or "").strip() not in ("", "[]")
|
|
183
|
+
]
|
|
184
|
+
if not intended:
|
|
185
|
+
return []
|
|
186
|
+
row = rec_mod.get_record(session, worksheet_id, row_id) or {}
|
|
187
|
+
# get_record returns the cell values as a JSON *string* under rowData.
|
|
188
|
+
values = row.get("rowData") or {}
|
|
189
|
+
if isinstance(values, str):
|
|
190
|
+
try:
|
|
191
|
+
values = _json.loads(values)
|
|
192
|
+
except ValueError:
|
|
193
|
+
return [] # unreadable read-back: don't block on a guess
|
|
194
|
+
if not isinstance(values, dict):
|
|
195
|
+
return []
|
|
196
|
+
return [cid for cid in intended
|
|
197
|
+
if str(values.get(cid) or "").strip() in ("", "[]")]
|
|
198
|
+
|
|
199
|
+
|
|
168
200
|
@instance.command("submit")
|
|
169
201
|
@click.argument("instance_id")
|
|
170
202
|
@click.option("--work-id", "-w", default="", help="Work item ID")
|
|
171
203
|
@click.option("--opinion", "-o", default="", help="Submit comment")
|
|
204
|
+
@click.option("--field", "-f", multiple=True,
|
|
205
|
+
help="Field value to fill in before submitting: FIELD=VALUE "
|
|
206
|
+
"(repeatable). FIELD is a field id, alias or name.")
|
|
207
|
+
@click.option("--fields-json", default="",
|
|
208
|
+
help="Field values to fill in before submitting, as one JSON "
|
|
209
|
+
"array: [{\"id\": \"<field id or alias>\", \"value\": ...}, ...]. "
|
|
210
|
+
"Use either this or -f, not both. Value formats per field "
|
|
211
|
+
"type are in `hap guide record`.")
|
|
212
|
+
@click.option("--dry-run", is_flag=True,
|
|
213
|
+
help="Resolve the task's record and show what would be written, "
|
|
214
|
+
"without filling anything in or submitting.")
|
|
172
215
|
@pass_context
|
|
173
|
-
def instance_submit(ctx, instance_id, work_id, opinion):
|
|
174
|
-
"""Submit a fill-in task.
|
|
216
|
+
def instance_submit(ctx, instance_id, work_id, opinion, field, fields_json, dry_run):
|
|
217
|
+
"""Submit a fill-in task, optionally filling in its form first.
|
|
218
|
+
|
|
219
|
+
The fields of a fill-in task are writable only while you hold the task,
|
|
220
|
+
so pass them here rather than editing the record separately. Working
|
|
221
|
+
through todos end to end is covered in `hap guide approval`.
|
|
222
|
+
"""
|
|
223
|
+
if field and fields_json:
|
|
224
|
+
raise click.UsageError("Pass exactly one of -f or --fields-json.")
|
|
175
225
|
try:
|
|
176
226
|
session = ctx.get_session()
|
|
227
|
+
raw = None
|
|
228
|
+
if field or fields_json:
|
|
229
|
+
raw = (_rec_fields_json_to_raw(fields_json) if fields_json
|
|
230
|
+
else _rec_parse_fields(field))
|
|
231
|
+
# Read-only resolution first: a fill-in task carries the record it
|
|
232
|
+
# writes to, which the caller should not have to look up.
|
|
233
|
+
target = None
|
|
234
|
+
if raw is not None:
|
|
235
|
+
target = inst_mod.get_work_item(session, instance_id, work_id=work_id)
|
|
236
|
+
if not (target or {}).get("worksheetId"):
|
|
237
|
+
raise click.ClickException(
|
|
238
|
+
"Could not resolve the record behind this task, so there "
|
|
239
|
+
"is nothing to fill in. Check the instance and work id.")
|
|
240
|
+
if dry_run:
|
|
241
|
+
ctx.output(
|
|
242
|
+
{"instanceId": instance_id, "workId": work_id,
|
|
243
|
+
"target": target, "fields": raw, "submitted": False},
|
|
244
|
+
lambda d: click.echo(
|
|
245
|
+
f"Would fill {len(raw or [])} field(s) on "
|
|
246
|
+
f"{(target or {}).get('worksheetId', '-')}/"
|
|
247
|
+
f"{(target or {}).get('rowId', '-')} and submit."),
|
|
248
|
+
)
|
|
249
|
+
return
|
|
250
|
+
if raw is not None:
|
|
251
|
+
ws_id, row_id = target["worksheetId"], target.get("rowId", "")
|
|
252
|
+
controls = rec_mod.encode_controls_by_id(
|
|
253
|
+
session, ws_id, raw, for_update=True)
|
|
254
|
+
# The record itself may be read-only outside the task; the
|
|
255
|
+
# instance/work pair is what grants the write.
|
|
256
|
+
rec_mod.update_record(
|
|
257
|
+
session, ws_id, row_id, controls,
|
|
258
|
+
instance_id=instance_id, work_id=work_id,
|
|
259
|
+
)
|
|
260
|
+
# A field the task's form does not contain is dropped by the
|
|
261
|
+
# server without any error, so the write must be read back
|
|
262
|
+
# before the task is submitted — otherwise the value is lost
|
|
263
|
+
# and the task is gone with it.
|
|
264
|
+
dropped = _unwritten_fields(session, ws_id, row_id, controls)
|
|
265
|
+
if dropped:
|
|
266
|
+
# Report the names the caller typed, not internal ids.
|
|
267
|
+
labels = dict(zip(
|
|
268
|
+
[c.get("controlId") for c in controls],
|
|
269
|
+
[str(r.get("controlId")) for r in raw],
|
|
270
|
+
)) if len(controls) == len(raw) else {}
|
|
271
|
+
shown = [labels.get(cid, cid) for cid in dropped]
|
|
272
|
+
raise click.ClickException(
|
|
273
|
+
"These values were not saved, so the task was NOT "
|
|
274
|
+
"submitted: " + ", ".join(shown) + ". A fill-in task "
|
|
275
|
+
"only accepts the fields on its own form; fill the rest "
|
|
276
|
+
"separately, or drop them and submit again.")
|
|
177
277
|
data = inst_mod.submit(session, instance_id, work_id, opinion)
|
|
178
278
|
ctx.output(data, lambda d: click.echo("Submitted."))
|
|
179
279
|
except Exception as e:
|
|
@@ -644,25 +644,74 @@ def post_get(ctx, post_id):
|
|
|
644
644
|
help="Post type: 0=text, 3=link, 5=vote, 7=file",
|
|
645
645
|
)
|
|
646
646
|
@click.option(
|
|
647
|
-
"--
|
|
647
|
+
"--to-group",
|
|
648
648
|
"share_group_ids",
|
|
649
649
|
multiple=True,
|
|
650
|
-
help="
|
|
650
|
+
help="Audience: members of this group can see the post (repeatable). "
|
|
651
|
+
"Naming a group does not also publish to the organization.",
|
|
651
652
|
)
|
|
652
653
|
@click.option(
|
|
653
|
-
"--
|
|
654
|
+
"--to-org",
|
|
654
655
|
"share_org_ids",
|
|
655
656
|
multiple=True,
|
|
656
|
-
help=
|
|
657
|
-
|
|
658
|
-
"goes only to --org-id."
|
|
659
|
-
),
|
|
657
|
+
help="Audience: everyone in this organization can see the post "
|
|
658
|
+
"(repeatable). This is the company-wide feed.",
|
|
660
659
|
)
|
|
660
|
+
@click.option("--share-group", "legacy_group_ids", multiple=True, hidden=True)
|
|
661
|
+
@click.option("--share-org", "legacy_org_ids", multiple=True, hidden=True)
|
|
662
|
+
@click.option("--card-to-group", "card_group_ids", multiple=True,
|
|
663
|
+
help="After publishing, also send a card linking to the post "
|
|
664
|
+
"into this group chat (repeatable). This is a chat "
|
|
665
|
+
"message and does not change who can see the post.")
|
|
666
|
+
@click.option("--dry-run", is_flag=True,
|
|
667
|
+
help="Show the audience and what would be sent, without publishing.")
|
|
661
668
|
@pass_context
|
|
662
|
-
def post_create(ctx, org_id, message, post_type, share_group_ids, share_org_ids
|
|
663
|
-
|
|
669
|
+
def post_create(ctx, org_id, message, post_type, share_group_ids, share_org_ids,
|
|
670
|
+
legacy_group_ids, legacy_org_ids, card_group_ids, dry_run):
|
|
671
|
+
"""Create a new post in the given organization.
|
|
672
|
+
|
|
673
|
+
Audience is set with --to-group / --to-org and defaults to everyone in
|
|
674
|
+
--org-id when neither is given. Who can see a post, and sending a card
|
|
675
|
+
about it to a group chat, are covered in `hap guide post`.
|
|
676
|
+
"""
|
|
664
677
|
try:
|
|
678
|
+
share_group_ids = tuple(share_group_ids) + tuple(legacy_group_ids)
|
|
679
|
+
share_org_ids = tuple(share_org_ids) + tuple(legacy_org_ids)
|
|
680
|
+
if dry_run:
|
|
681
|
+
audience = (
|
|
682
|
+
[f"group:{g}" for g in share_group_ids]
|
|
683
|
+
+ [f"org:{o}" for o in share_org_ids]
|
|
684
|
+
) or [f"org:{org_id or '<current>'} (everyone, default)"]
|
|
685
|
+
ctx.output(
|
|
686
|
+
{"audience": audience, "cardToGroups": list(card_group_ids),
|
|
687
|
+
"message": message, "published": False},
|
|
688
|
+
lambda d: click.echo(
|
|
689
|
+
"Would publish to: " + ", ".join(d["audience"])
|
|
690
|
+
+ (f"; card to groups: {', '.join(d['cardToGroups'])}"
|
|
691
|
+
if d["cardToGroups"] else "")),
|
|
692
|
+
)
|
|
693
|
+
return
|
|
665
694
|
session = ctx.get_session()
|
|
695
|
+
# Only a group with the feed turned on can be a post audience. The
|
|
696
|
+
# server rejects the rest with "选择了没有加入的群组" (you picked a
|
|
697
|
+
# group you have not joined), which is misleading when you are in
|
|
698
|
+
# the group — it is a plain chat group with no feed. Check first so
|
|
699
|
+
# the message names the real reason.
|
|
700
|
+
if share_group_ids:
|
|
701
|
+
from hap_cli.core import group as group_mod
|
|
702
|
+
no_feed = []
|
|
703
|
+
for gid in share_group_ids:
|
|
704
|
+
info = group_mod.get_group_info(session, gid) or {}
|
|
705
|
+
if isinstance(info.get("data"), dict):
|
|
706
|
+
info = info["data"]
|
|
707
|
+
if not info.get("isPost"):
|
|
708
|
+
no_feed.append(info.get("name") or gid)
|
|
709
|
+
if no_feed:
|
|
710
|
+
raise click.ClickException(
|
|
711
|
+
"These groups cannot receive a post because they are chat "
|
|
712
|
+
"groups with no feed enabled: " + ", ".join(no_feed)
|
|
713
|
+
+ ". Pick a group whose feed is on, or send a card to the "
|
|
714
|
+
"chat instead with --card-to-group.")
|
|
666
715
|
pid = require_api_project_id(session, org_id)
|
|
667
716
|
raw = post_mod.add_post(
|
|
668
717
|
session,
|
|
@@ -674,10 +723,21 @@ def post_create(ctx, org_id, message, post_type, share_group_ids, share_org_ids)
|
|
|
674
723
|
)
|
|
675
724
|
post_obj = raw.get("post") if isinstance(raw, dict) else None
|
|
676
725
|
summary = _simplify_post(post_obj) if post_obj else {"raw": raw}
|
|
726
|
+
if card_group_ids and summary.get("id"):
|
|
727
|
+
from hap_cli.core import chat as chat_mod
|
|
728
|
+
sent = []
|
|
729
|
+
for gid in card_group_ids:
|
|
730
|
+
chat_mod.send_group_post_card(
|
|
731
|
+
session, gid, summary["id"], message,
|
|
732
|
+
post_type=post_type)
|
|
733
|
+
sent.append(gid)
|
|
734
|
+
summary["cardSentToGroups"] = sent
|
|
677
735
|
def _render(d):
|
|
678
736
|
click.echo(f"Post created: {d.get('id')}")
|
|
679
737
|
if d.get("text"):
|
|
680
738
|
click.echo(f" {d['text']}")
|
|
739
|
+
if d.get("cardSentToGroups"):
|
|
740
|
+
click.echo(f" Card sent to: {', '.join(d['cardSentToGroups'])}")
|
|
681
741
|
ctx.output(summary, _render)
|
|
682
742
|
except Exception as e:
|
|
683
743
|
ctx.handle_error(e)
|
|
@@ -162,8 +162,17 @@ def _fields_json_to_raw(fields_json):
|
|
|
162
162
|
"--app-id", "-a", default="",
|
|
163
163
|
help="Application ID. Optional — the worksheet id is enough on its own; this only scopes the field lookup.",
|
|
164
164
|
)
|
|
165
|
+
@click.option("--instance-id", default="",
|
|
166
|
+
help="Workflow instance the edit belongs to. Pass together with "
|
|
167
|
+
"--work-id to fill in the form of a fill-in task, whose "
|
|
168
|
+
"fields are only writable while you hold that task. To fill "
|
|
169
|
+
"a task and submit it in one step use `approval submit -f`; "
|
|
170
|
+
"see `hap guide approval`.")
|
|
171
|
+
@click.option("--work-id", default="",
|
|
172
|
+
help="Workflow work-item the edit belongs to. Use with --instance-id.")
|
|
165
173
|
@pass_context
|
|
166
|
-
def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow, app_id
|
|
174
|
+
def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow, app_id,
|
|
175
|
+
instance_id, work_id):
|
|
167
176
|
"""Update a record.
|
|
168
177
|
|
|
169
178
|
Pass values either as repeated -f CONTROL_ID=VALUE pairs or as one
|
|
@@ -193,6 +202,7 @@ def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow, ap
|
|
|
193
202
|
data = rec_mod.update_record(
|
|
194
203
|
session, worksheet_id, row_id, controls,
|
|
195
204
|
trigger_workflow=not no_workflow, app_id=app_id,
|
|
205
|
+
instance_id=instance_id, work_id=work_id,
|
|
196
206
|
)
|
|
197
207
|
ctx.output(data, lambda d: click.echo(f"Updated: {d}"))
|
|
198
208
|
except Exception as e:
|
|
@@ -438,6 +438,159 @@ def send_group_message(
|
|
|
438
438
|
return ack_data
|
|
439
439
|
|
|
440
440
|
|
|
441
|
+
# The file-transfer assistant (文件传输助手) is a real 1-on-1 conversation
|
|
442
|
+
# with a fixed pseudo-account id, not a shortcut for your own account
|
|
443
|
+
# (pd-openweb src/pages/chat/utils/constant.js `FILE_TRANSFER.id`). It is
|
|
444
|
+
# how the web client lets you message yourself.
|
|
445
|
+
FILE_TRANSFER_ID = "file-transfer"
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def send_user_message(
|
|
449
|
+
session: Session,
|
|
450
|
+
to_user: str,
|
|
451
|
+
message: str,
|
|
452
|
+
) -> Any:
|
|
453
|
+
"""Send a 1-on-1 chat message over the socket, as the chat panel does.
|
|
454
|
+
|
|
455
|
+
``send_message`` posts to the main-site ``Message.SendMessageToAccountIds``,
|
|
456
|
+
which addresses real *account ids* and silently drops anything else —
|
|
457
|
+
the caller's own id, and the file-transfer assistant, both come back
|
|
458
|
+
as ``successCount: 0`` with nothing stored.
|
|
459
|
+
|
|
460
|
+
The chat panel never uses that endpoint: it emits the socket.io event
|
|
461
|
+
``'send message'`` with ``touser`` set to the conversation id
|
|
462
|
+
(``src/pages/chat/utils/socket.js`` ``Message.send`` USER branch;
|
|
463
|
+
``containers/ChatPanelSession/index.js:152,334,357`` set
|
|
464
|
+
``sendMsg.touser = session.id``). Because the target is a
|
|
465
|
+
conversation id rather than an account id, this path also reaches
|
|
466
|
+
:data:`FILE_TRANSFER_ID`, which is what "send to myself" means in the
|
|
467
|
+
web client.
|
|
468
|
+
"""
|
|
469
|
+
import uuid
|
|
470
|
+
|
|
471
|
+
if not to_user:
|
|
472
|
+
raise ValueError("to_user is required")
|
|
473
|
+
if not message:
|
|
474
|
+
raise ValueError("message must not be empty")
|
|
475
|
+
payload = {
|
|
476
|
+
"type": MSGTYPE_TEXT,
|
|
477
|
+
"msg": message,
|
|
478
|
+
"touser": to_user,
|
|
479
|
+
"waitingid": str(uuid.uuid4()),
|
|
480
|
+
}
|
|
481
|
+
ack = _socket_emit(session, "send message", payload, strict=True)
|
|
482
|
+
if isinstance(ack, tuple) and len(ack) >= 2:
|
|
483
|
+
return ack[1]
|
|
484
|
+
return ack
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def withdraw_message(
|
|
488
|
+
session: Session,
|
|
489
|
+
message_id: str,
|
|
490
|
+
*,
|
|
491
|
+
group_id: str = "",
|
|
492
|
+
to_user: str = "",
|
|
493
|
+
time: str = "",
|
|
494
|
+
admin_account_id: str = "",
|
|
495
|
+
) -> Any:
|
|
496
|
+
"""Recall (撤回) a message you sent, in a group or a 1-on-1 chat.
|
|
497
|
+
|
|
498
|
+
Recall has no REST endpoint — pd-openweb emits a socket.io event
|
|
499
|
+
(``src/pages/chat/utils/socket.js`` ``Message.sendWithdrawMessgae``):
|
|
500
|
+
``'withdraw group message'`` for a group, ``'withdraw user message'``
|
|
501
|
+
for a 1-on-1. The parameter object is built by
|
|
502
|
+
``containers/Message/index.js::handleWithdrawMessage`` as
|
|
503
|
+
``{messageId, groupid | touser, time}`` — note the lowercase
|
|
504
|
+
``groupid`` / ``touser`` keys — plus ``adminid`` when a group admin
|
|
505
|
+
recalls somebody else's message.
|
|
506
|
+
|
|
507
|
+
Server-side limits, surfaced by the UI rather than by us: an author
|
|
508
|
+
can only recall within 5 minutes of sending
|
|
509
|
+
(``components/Message/MessageToolbar/index.js:285``), while a group
|
|
510
|
+
admin may recall another member's message with no time limit
|
|
511
|
+
(``:275-276``). A recall past the window is refused by the server.
|
|
512
|
+
|
|
513
|
+
Exactly one of ``group_id`` / ``to_user`` identifies the conversation.
|
|
514
|
+
"""
|
|
515
|
+
if not message_id:
|
|
516
|
+
raise ValueError("message_id is required")
|
|
517
|
+
if bool(group_id) == bool(to_user):
|
|
518
|
+
raise ValueError("pass exactly one of group_id or to_user")
|
|
519
|
+
param: dict[str, Any] = {"messageId": message_id, "time": time or ""}
|
|
520
|
+
if group_id:
|
|
521
|
+
param["groupid"] = group_id
|
|
522
|
+
event = "withdraw group message"
|
|
523
|
+
else:
|
|
524
|
+
param["touser"] = to_user
|
|
525
|
+
event = "withdraw user message"
|
|
526
|
+
if admin_account_id:
|
|
527
|
+
param["adminid"] = admin_account_id
|
|
528
|
+
ack = _socket_emit(session, event, param, strict=True)
|
|
529
|
+
if isinstance(ack, tuple) and len(ack) >= 2:
|
|
530
|
+
return ack[1]
|
|
531
|
+
return ack
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def send_group_post_card(
|
|
535
|
+
session: Session,
|
|
536
|
+
group_id: str,
|
|
537
|
+
post_id: str,
|
|
538
|
+
title: str,
|
|
539
|
+
*,
|
|
540
|
+
post_type: int = 0,
|
|
541
|
+
server_url: str = "",
|
|
542
|
+
) -> Any:
|
|
543
|
+
"""Send a card linking to a post into a group chat.
|
|
544
|
+
|
|
545
|
+
This is a chat message about a post; it does not change who can see
|
|
546
|
+
the post itself (that is the post's own audience — see
|
|
547
|
+
``hap_cli.core.post.add_post``).
|
|
548
|
+
|
|
549
|
+
Wire shape mirrors ``pd-openweb/src/pages/chat/utils/cardSender.js``
|
|
550
|
+
(``_initPost``): message ``type`` 5 (``Constant.MSGTYPE_CARD``) with a
|
|
551
|
+
``card`` of ``{md, entityid, url, title, text}``, where ``md`` is
|
|
552
|
+
``vote`` for a vote post (postType 7) and ``post`` otherwise, and the
|
|
553
|
+
url is ``/feeddetail?itemID=<postId>``. The title is capped at 300
|
|
554
|
+
characters, as the frontend does.
|
|
555
|
+
|
|
556
|
+
The ``msg`` line is the conversation-list summary, built the way
|
|
557
|
+
``ChatPanelSession/index.js:277-278`` builds it: ``[<kind>] <title>``,
|
|
558
|
+
where the kind comes from the *localized* card name
|
|
559
|
+
(``cardDisposeName`` → ``_l('动态')`` / ``_l('投票')``). It is read by
|
|
560
|
+
other people in the chat, so it goes through the content-default
|
|
561
|
+
catalog rather than being hardcoded in one language.
|
|
562
|
+
"""
|
|
563
|
+
import uuid
|
|
564
|
+
|
|
565
|
+
from hap_cli.i18n import content_default
|
|
566
|
+
|
|
567
|
+
if not group_id:
|
|
568
|
+
raise ValueError("group_id is required")
|
|
569
|
+
if not post_id:
|
|
570
|
+
raise ValueError("post_id is required")
|
|
571
|
+
card_title = (title or "")[:300]
|
|
572
|
+
is_vote = str(post_type) == "7"
|
|
573
|
+
kind = content_default("chat.vote" if is_vote else "chat.post")
|
|
574
|
+
base = (server_url or getattr(session, "server_url", "") or "").rstrip("/")
|
|
575
|
+
payload = {
|
|
576
|
+
"type": 5,
|
|
577
|
+
"msg": f"{kind} {card_title}",
|
|
578
|
+
"card": {
|
|
579
|
+
"md": "vote" if is_vote else "post",
|
|
580
|
+
"entityid": post_id,
|
|
581
|
+
"url": f"{base}/feeddetail?itemID={post_id}",
|
|
582
|
+
"title": card_title,
|
|
583
|
+
"text": "",
|
|
584
|
+
},
|
|
585
|
+
"togroup": group_id,
|
|
586
|
+
"waitingid": str(uuid.uuid4()),
|
|
587
|
+
}
|
|
588
|
+
ack = _socket_emit(session, "send group message", payload, strict=True)
|
|
589
|
+
if isinstance(ack, tuple) and len(ack) >= 2:
|
|
590
|
+
return ack[1]
|
|
591
|
+
return ack
|
|
592
|
+
|
|
593
|
+
|
|
441
594
|
def send_group_files(
|
|
442
595
|
session: Session,
|
|
443
596
|
group_id: str,
|
|
@@ -207,19 +207,35 @@ def add_post(
|
|
|
207
207
|
) -> dict[str, Any]:
|
|
208
208
|
"""Create a post via ``Post.AddPost``.
|
|
209
209
|
|
|
210
|
-
``scope`` is a ``PostShareScopeReq`` object — not an int
|
|
211
|
-
frontend
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
210
|
+
``scope`` is a ``PostShareScopeReq`` object — not an int — and it is
|
|
211
|
+
what decides **who can see the post**. The frontend
|
|
212
|
+
(``pd-openweb/src/pages/feed/components/createFeed/index.js``) posts
|
|
213
|
+
``{radioProjectIds, shareGroupIds, shareProjectIds}``:
|
|
214
|
+
|
|
215
|
+
* ``shareProjectIds: [<orgId>]`` — everyone in that organization.
|
|
216
|
+
This is the org-wide feed (index.js:267-270).
|
|
217
|
+
* ``shareGroupIds: [<groupId>]`` with ``shareProjectIds: []`` — only
|
|
218
|
+
that group's members. Posting into a group builds exactly this
|
|
219
|
+
(index.js:647-650).
|
|
220
|
+
|
|
221
|
+
``project_id`` is the organization the post belongs to and is always
|
|
222
|
+
sent as ``projectId``; on its own it is *not* an audience. Audience
|
|
223
|
+
defaults to the whole organization only when the caller names no
|
|
224
|
+
audience at all — naming a group must never silently widen the post
|
|
225
|
+
to everyone, which is what an unconditional ``or [project_id]``
|
|
226
|
+
fallback used to do.
|
|
215
227
|
|
|
216
228
|
Returns the raw ``{post, success}`` envelope.
|
|
217
229
|
"""
|
|
218
230
|
if scope is None:
|
|
231
|
+
groups = list(share_group_ids or [])
|
|
232
|
+
orgs = list(share_project_ids or [])
|
|
233
|
+
if not groups and not orgs:
|
|
234
|
+
orgs = [project_id]
|
|
219
235
|
scope = {
|
|
220
236
|
"radioProjectIds": radio_project_ids,
|
|
221
|
-
"shareGroupIds":
|
|
222
|
-
"shareProjectIds":
|
|
237
|
+
"shareGroupIds": groups,
|
|
238
|
+
"shareProjectIds": orgs,
|
|
223
239
|
}
|
|
224
240
|
return session.api_call(
|
|
225
241
|
"Post", "AddPost",
|
|
@@ -965,6 +965,8 @@ def update_record(
|
|
|
965
965
|
controls: list[dict[str, Any]],
|
|
966
966
|
trigger_workflow: bool = True,
|
|
967
967
|
app_id: str = "",
|
|
968
|
+
instance_id: str = "",
|
|
969
|
+
work_id: str = "",
|
|
968
970
|
) -> dict[str, Any]:
|
|
969
971
|
"""Update an existing record.
|
|
970
972
|
|
|
@@ -973,6 +975,14 @@ def update_record(
|
|
|
973
975
|
AddWorksheetRow, UpdateWorksheetRow's schema has no ``appId``, and
|
|
974
976
|
inventing one would diverge from the frontend.
|
|
975
977
|
|
|
978
|
+
``instance_id`` / ``work_id`` identify a workflow step the caller is
|
|
979
|
+
acting inside. A fill-in node (填写节点) grants write permission on the
|
|
980
|
+
fields of its own form for the duration of that step; the record itself
|
|
981
|
+
may well be read-only otherwise, in which case a plain update answers
|
|
982
|
+
"权限不足". Passing the pair makes the write happen under the node's
|
|
983
|
+
permission, exactly as the web form does (UpdateWorksheetRow's
|
|
984
|
+
``instanceId`` / ``workId`` params).
|
|
985
|
+
|
|
976
986
|
Args:
|
|
977
987
|
session: Active session
|
|
978
988
|
worksheet_id: Worksheet ID
|
|
@@ -980,21 +990,24 @@ def update_record(
|
|
|
980
990
|
controls: List of {controlId, value} dicts
|
|
981
991
|
trigger_workflow: Whether to trigger workflows
|
|
982
992
|
app_id: Optional application ID (metadata scope only)
|
|
993
|
+
instance_id: Workflow instance ID the write belongs to
|
|
994
|
+
work_id: Workflow work-item ID the write belongs to
|
|
983
995
|
|
|
984
996
|
Returns:
|
|
985
997
|
Update result
|
|
986
998
|
"""
|
|
987
999
|
_reject_workflow_suppression(trigger_workflow)
|
|
1000
|
+
payload: dict[str, Any] = {
|
|
1001
|
+
"worksheetId": worksheet_id,
|
|
1002
|
+
"rowId": row_id,
|
|
1003
|
+
"newOldControl": controls,
|
|
1004
|
+
}
|
|
1005
|
+
if instance_id:
|
|
1006
|
+
payload["instanceId"] = instance_id
|
|
1007
|
+
if work_id:
|
|
1008
|
+
payload["workId"] = work_id
|
|
988
1009
|
return _raise_on_failed_write(
|
|
989
|
-
session.api_call(
|
|
990
|
-
"Worksheet",
|
|
991
|
-
"UpdateWorksheetRow",
|
|
992
|
-
{
|
|
993
|
-
"worksheetId": worksheet_id,
|
|
994
|
-
"rowId": row_id,
|
|
995
|
-
"newOldControl": controls,
|
|
996
|
-
},
|
|
997
|
-
),
|
|
1010
|
+
session.api_call("Worksheet", "UpdateWorksheetRow", payload),
|
|
998
1011
|
"update",
|
|
999
1012
|
)
|
|
1000
1013
|
|