hap-cli 0.7.1__tar.gz → 0.8.0__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.7.1 → hap_cli-0.8.0}/MANIFEST.in +5 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/PKG-INFO +30 -6
- {hap_cli-0.7.1/hap_cli → hap_cli-0.8.0}/README.md +29 -5
- {hap_cli-0.7.1 → hap_cli-0.8.0}/app_live_tests/harness.py +7 -4
- {hap_cli-0.7.1 → hap_cli-0.8.0}/app_live_tests/smoke.py +85 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0/hap_cli}/README.md +29 -5
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/README_CN.md +27 -5
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/__init__.py +1 -1
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/app_cmd.py +63 -10
- hap_cli-0.8.0/hap_cli/commands/app_editor_cmd.py +137 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/auth_cmd.py +161 -36
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/chart_cmd.py +1 -1
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/context.py +5 -1
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/app.py +79 -21
- hap_cli-0.8.0/hap_cli/core/editor/__init__.py +12 -0
- hap_cli-0.8.0/hap_cli/core/editor/_hapmeta/__init__.py +5 -0
- hap_cli-0.8.0/hap_cli/core/editor/_hapmeta/control_type_codes.py +58 -0
- hap_cli-0.8.0/hap_cli/core/editor/apply.py +51 -0
- hap_cli-0.8.0/hap_cli/core/editor/componentlower.py +80 -0
- hap_cli-0.8.0/hap_cli/core/editor/editspec.py +119 -0
- hap_cli-0.8.0/hap_cli/core/editor/errors.py +55 -0
- hap_cli-0.8.0/hap_cli/core/editor/fieldlower.py +66 -0
- hap_cli-0.8.0/hap_cli/core/editor/jsonschema_mini.py +136 -0
- hap_cli-0.8.0/hap_cli/core/editor/models.py +39 -0
- hap_cli-0.8.0/hap_cli/core/editor/ops.py +543 -0
- hap_cli-0.8.0/hap_cli/core/editor/planner.py +53 -0
- hap_cli-0.8.0/hap_cli/core/editor/reader.py +291 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/session.py +268 -33
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/v3/dispatcher.py +18 -2
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/hap_cli.py +10 -1
- hap_cli-0.8.0/hap_cli/tests/test_app_editor.py +247 -0
- hap_cli-0.8.0/hap_cli/tests/test_app_export_usage.py +224 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_config_cmd.py +4 -1
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_core.py +2 -2
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_integration_misc.py +17 -14
- hap_cli-0.8.0/hap_cli/tests/test_multi_profile.py +434 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_v3_dispatcher.py +41 -4
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli.egg-info/PKG-INFO +30 -6
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli.egg-info/SOURCES.txt +29 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/setup.py +20 -5
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/application.schema.json +59 -0
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/chatbot.schema.json +52 -0
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/component.schema.json +65 -0
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +55 -0
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/custom-page.schema.json +49 -0
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/envelope.schema.json +86 -0
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/field.schema.json +84 -0
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/node.schema.json +73 -0
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/role.schema.json +90 -0
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/view.schema.json +64 -0
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/workflow.schema.json +60 -0
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/worksheet.schema.json +51 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/app_live_tests/config.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/app_live_tests/state.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/config_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/post_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/record_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/chart.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/chat.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/department.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/group.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/page.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/post.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/record.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/role.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/i18n.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/locale/messages.json +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_chart.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/__init__.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/__main__.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/_wftest.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/charts.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/cleanup.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/compiler.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/config.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/errors.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/executor.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/fields.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/hap.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/recording/console.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/recording/report.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/schema.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/seed/template.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/selftest.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/smoke.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/step.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/steps.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/store.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/setup.cfg +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.7.1 → hap_cli-0.8.0}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -11,3 +11,8 @@ include sources/v3-api-schema/INDEX.json
|
|
|
11
11
|
include hap_cli/README.md
|
|
12
12
|
include hap_cli/README_CN.md
|
|
13
13
|
recursive-include hap_cli/locale *.json
|
|
14
|
+
|
|
15
|
+
# edit-spec schemas are canonical in the hap-cli-app-editor skill; ship them
|
|
16
|
+
# in the sdist so setup.py's build step can mirror them into
|
|
17
|
+
# hap_cli/core/editor/editspec/ for the ``hap app-editor`` runtime loader.
|
|
18
|
+
recursive-include skills/hap-cli-app-editor/scripts/editspec *.json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hap-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: CLI harness for MingDAO HAP - Enterprise no-code platform
|
|
5
5
|
Author: hap-cli
|
|
6
6
|
License: Apache-2.0
|
|
@@ -63,10 +63,32 @@ command — it falls back to a paste prompt for the token shown on the success
|
|
|
63
63
|
page.
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
|
-
hap auth whoami # current user + current
|
|
67
|
-
hap auth logout #
|
|
66
|
+
hap auth whoami # current user + current environment/account + org
|
|
67
|
+
hap auth logout # log out the current account
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
#### Multiple environments / accounts
|
|
71
|
+
|
|
72
|
+
You can stay logged in to several environments (MingDAO SaaS, Nocoly, self-hosted) and
|
|
73
|
+
several accounts at once. Each is saved under a name and you switch between them:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
hap auth login mingdao --profile work-prod # log in and save under a name
|
|
77
|
+
hap auth login https://hap.example.com --profile onprem
|
|
78
|
+
# Omit --profile and the name is derived from the login address, so logging
|
|
79
|
+
# into a new environment never overwrites one you already saved.
|
|
80
|
+
|
|
81
|
+
hap auth accounts # list saved environments/accounts (current marked *)
|
|
82
|
+
hap auth use work-prod # switch the environment/account used next
|
|
83
|
+
hap --profile onprem app list # use one environment/account for a single command
|
|
84
|
+
HAP_PROFILE=onprem hap app list # default a whole shell session to one
|
|
85
|
+
|
|
86
|
+
hap auth logout -p onprem # log out a specific account
|
|
87
|
+
hap auth logout --all # log out every account
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Profile resolution order: `--profile` > the `HAP_PROFILE` env var > the current profile.
|
|
91
|
+
|
|
70
92
|
### 2. Pick an organization and app
|
|
71
93
|
|
|
72
94
|
```bash
|
|
@@ -113,9 +135,11 @@ discover every subcommand.
|
|
|
113
135
|
|
|
114
136
|
| Command | Description |
|
|
115
137
|
|---|---|
|
|
116
|
-
| `auth login [SERVER]` | Browser login (
|
|
117
|
-
| `auth
|
|
118
|
-
| `auth
|
|
138
|
+
| `auth login [SERVER]` | Browser login (`--token` for scripted login; `--profile NAME` saves under a name, auto-named if omitted; `--force` repoints an existing name) |
|
|
139
|
+
| `auth accounts` | List saved environments/accounts (current marked `*`) |
|
|
140
|
+
| `auth use NAME` | Switch the environment/account used next |
|
|
141
|
+
| `auth logout` | Log out the current account (`-p NAME` for a specific one, `-a/--all` for every one) |
|
|
142
|
+
| `auth whoami` | Show current user, current environment/account, and current organization |
|
|
119
143
|
| `auth list-my-orgs` | List every org for the current account (current marked `*`) |
|
|
120
144
|
| `auth set-current-org ORG_ID` | Switch the saved current organization |
|
|
121
145
|
|
|
@@ -34,10 +34,32 @@ command — it falls back to a paste prompt for the token shown on the success
|
|
|
34
34
|
page.
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
hap auth whoami # current user + current
|
|
38
|
-
hap auth logout #
|
|
37
|
+
hap auth whoami # current user + current environment/account + org
|
|
38
|
+
hap auth logout # log out the current account
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
#### Multiple environments / accounts
|
|
42
|
+
|
|
43
|
+
You can stay logged in to several environments (MingDAO SaaS, Nocoly, self-hosted) and
|
|
44
|
+
several accounts at once. Each is saved under a name and you switch between them:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
hap auth login mingdao --profile work-prod # log in and save under a name
|
|
48
|
+
hap auth login https://hap.example.com --profile onprem
|
|
49
|
+
# Omit --profile and the name is derived from the login address, so logging
|
|
50
|
+
# into a new environment never overwrites one you already saved.
|
|
51
|
+
|
|
52
|
+
hap auth accounts # list saved environments/accounts (current marked *)
|
|
53
|
+
hap auth use work-prod # switch the environment/account used next
|
|
54
|
+
hap --profile onprem app list # use one environment/account for a single command
|
|
55
|
+
HAP_PROFILE=onprem hap app list # default a whole shell session to one
|
|
56
|
+
|
|
57
|
+
hap auth logout -p onprem # log out a specific account
|
|
58
|
+
hap auth logout --all # log out every account
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Profile resolution order: `--profile` > the `HAP_PROFILE` env var > the current profile.
|
|
62
|
+
|
|
41
63
|
### 2. Pick an organization and app
|
|
42
64
|
|
|
43
65
|
```bash
|
|
@@ -84,9 +106,11 @@ discover every subcommand.
|
|
|
84
106
|
|
|
85
107
|
| Command | Description |
|
|
86
108
|
|---|---|
|
|
87
|
-
| `auth login [SERVER]` | Browser login (
|
|
88
|
-
| `auth
|
|
89
|
-
| `auth
|
|
109
|
+
| `auth login [SERVER]` | Browser login (`--token` for scripted login; `--profile NAME` saves under a name, auto-named if omitted; `--force` repoints an existing name) |
|
|
110
|
+
| `auth accounts` | List saved environments/accounts (current marked `*`) |
|
|
111
|
+
| `auth use NAME` | Switch the environment/account used next |
|
|
112
|
+
| `auth logout` | Log out the current account (`-p NAME` for a specific one, `-a/--all` for every one) |
|
|
113
|
+
| `auth whoami` | Show current user, current environment/account, and current organization |
|
|
90
114
|
| `auth list-my-orgs` | List every org for the current account (current marked `*`) |
|
|
91
115
|
| `auth set-current-org ORG_ID` | Switch the saved current organization |
|
|
92
116
|
|
|
@@ -53,11 +53,14 @@ def hap(args: list[str], *, check: bool = True) -> Any:
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def editor(args: list[str]) -> tuple[int, str, str]:
|
|
56
|
-
"""Run the editor
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
"""Run the editor via the installed CLI: ``hap app-editor <args>``.
|
|
57
|
+
|
|
58
|
+
The editor engine was migrated into hap-cli, so the entry point is now
|
|
59
|
+
the real command (not ``python -m scripts``).
|
|
60
|
+
"""
|
|
61
|
+
argv = [config.HAP_BIN, "app-editor", *args]
|
|
59
62
|
proc = subprocess.run(argv, capture_output=True, text=True,
|
|
60
|
-
|
|
63
|
+
timeout=config.TIMEOUT)
|
|
61
64
|
return proc.returncode, proc.stdout, proc.stderr
|
|
62
65
|
|
|
63
66
|
|
|
@@ -415,6 +415,88 @@ def case_application(app_id: str, section_id: str) -> None:
|
|
|
415
415
|
check("section gone after delete", renamed not in _sections(app_id))
|
|
416
416
|
|
|
417
417
|
|
|
418
|
+
def case_app_usage(app_id: str, section_id: str) -> None:
|
|
419
|
+
"""`hap app usage` — org overview + app-scoped + range variants.
|
|
420
|
+
|
|
421
|
+
Regression: --range used to be a raw day count passed straight through
|
|
422
|
+
as the dayRange enum (default 7 → out-of-range → 参数错误 / 服务异常).
|
|
423
|
+
"""
|
|
424
|
+
print("scenario: app usage (org overview + app-scoped + ranges)")
|
|
425
|
+
_, org_id = state.ensure_app()
|
|
426
|
+
|
|
427
|
+
overview = hap(["app", "usage", "--org-id", org_id])
|
|
428
|
+
check("org-wide usage returns a dict", isinstance(overview, dict),
|
|
429
|
+
f"got {type(overview).__name__}: {overview!r}")
|
|
430
|
+
check("org-wide usage has 'app' bucket",
|
|
431
|
+
isinstance(overview, dict) and "app" in overview,
|
|
432
|
+
f"keys={list(overview) if isinstance(overview, dict) else overview!r}")
|
|
433
|
+
|
|
434
|
+
scoped = hap(["app", "usage", "--org-id", org_id, "--app-id", app_id])
|
|
435
|
+
check("app-scoped usage returns a dict", isinstance(scoped, dict),
|
|
436
|
+
f"got {type(scoped).__name__}: {scoped!r}")
|
|
437
|
+
|
|
438
|
+
# Every range label must be accepted by the server (no 参数错误).
|
|
439
|
+
for label in ("7d", "30d", "90d", "6m", "1y"):
|
|
440
|
+
res = hap(["app", "usage", "--org-id", org_id, "--range", label], check=False)
|
|
441
|
+
check(f"usage --range {label} accepted", isinstance(res, dict),
|
|
442
|
+
f"got {res!r}")
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _exports(app_id: str) -> list[dict]:
|
|
446
|
+
data = hap(["app", "exports", app_id], check=False)
|
|
447
|
+
if isinstance(data, dict):
|
|
448
|
+
data = data.get("list", data.get("exports", []))
|
|
449
|
+
return data if isinstance(data, list) else []
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def case_app_export(app_id: str, section_id: str) -> None:
|
|
453
|
+
"""`hap app export` — structure-only batch export.
|
|
454
|
+
|
|
455
|
+
Regression: the wrapper sent {appIds:[...]} but BatchExportApp wants
|
|
456
|
+
{projectId, appConfigs:[{appId, exampleType, sheetConfig}]} → 服务异常.
|
|
457
|
+
"""
|
|
458
|
+
print("scenario: app export (structure only)")
|
|
459
|
+
_, org_id = state.ensure_app()
|
|
460
|
+
|
|
461
|
+
res = hap(["app", "export", app_id, "--org-id", org_id], check=False)
|
|
462
|
+
# The fix is proven by the call NOT raising 服务异常 and returning a
|
|
463
|
+
# truthy payload (the server accepts the export request).
|
|
464
|
+
check("export request accepted (no 服务异常)",
|
|
465
|
+
res not in (None, {}, False) or res is True,
|
|
466
|
+
f"got {res!r}")
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def case_app_logs(app_id: str, section_id: str) -> None:
|
|
470
|
+
"""`hap app logs APP_ID` must filter to that app, not return the whole org.
|
|
471
|
+
|
|
472
|
+
Regression: the wrapper called the legacy GetLogs with a singular appId
|
|
473
|
+
that GetGlobalLogs ignores, so every call returned the org-wide logs.
|
|
474
|
+
"""
|
|
475
|
+
print("scenario: app logs (app filter vs org-wide)")
|
|
476
|
+
_, org_id = state.ensure_app()
|
|
477
|
+
|
|
478
|
+
scoped = hap(["app", "logs", app_id, "--org-id", org_id, "-n", "10"])
|
|
479
|
+
org_wide = hap(["app", "logs", "--org-id", org_id, "-n", "10"])
|
|
480
|
+
|
|
481
|
+
check("logs envelope has list+allCount",
|
|
482
|
+
isinstance(scoped, dict) and "list" in scoped and "allCount" in scoped,
|
|
483
|
+
f"got {scoped!r}")
|
|
484
|
+
# The whole point: scoping to one app returns fewer rows than org-wide.
|
|
485
|
+
s_count = scoped.get("allCount", 0) if isinstance(scoped, dict) else 0
|
|
486
|
+
o_count = org_wide.get("allCount", 0) if isinstance(org_wide, dict) else 0
|
|
487
|
+
check("app filter narrows result (scoped < org-wide)", s_count < o_count,
|
|
488
|
+
f"scoped allCount={s_count} not < org-wide allCount={o_count}")
|
|
489
|
+
# No row may belong to a *different* app (some rows carry no app
|
|
490
|
+
# metadata at all — those are fine; foreign app ids are not).
|
|
491
|
+
rows = scoped.get("list", []) if isinstance(scoped, dict) else []
|
|
492
|
+
def _row_app(r):
|
|
493
|
+
a = r.get("application")
|
|
494
|
+
return a.get("id") if isinstance(a, dict) else a
|
|
495
|
+
foreign = {_row_app(r) for r in rows} - {app_id, None, ""}
|
|
496
|
+
check("no scoped row belongs to another app", not foreign,
|
|
497
|
+
f"foreign apps: {sorted(foreign)}")
|
|
498
|
+
|
|
499
|
+
|
|
418
500
|
_CASES = {
|
|
419
501
|
"worksheet": case_worksheet,
|
|
420
502
|
"field": case_field,
|
|
@@ -427,6 +509,9 @@ _CASES = {
|
|
|
427
509
|
"workflow": case_workflow,
|
|
428
510
|
"node": case_node,
|
|
429
511
|
"application": case_application,
|
|
512
|
+
"app-usage": case_app_usage,
|
|
513
|
+
"app-export": case_app_export,
|
|
514
|
+
"app-logs": case_app_logs,
|
|
430
515
|
}
|
|
431
516
|
|
|
432
517
|
|
|
@@ -34,10 +34,32 @@ command — it falls back to a paste prompt for the token shown on the success
|
|
|
34
34
|
page.
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
hap auth whoami # current user + current
|
|
38
|
-
hap auth logout #
|
|
37
|
+
hap auth whoami # current user + current environment/account + org
|
|
38
|
+
hap auth logout # log out the current account
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
#### Multiple environments / accounts
|
|
42
|
+
|
|
43
|
+
You can stay logged in to several environments (MingDAO SaaS, Nocoly, self-hosted) and
|
|
44
|
+
several accounts at once. Each is saved under a name and you switch between them:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
hap auth login mingdao --profile work-prod # log in and save under a name
|
|
48
|
+
hap auth login https://hap.example.com --profile onprem
|
|
49
|
+
# Omit --profile and the name is derived from the login address, so logging
|
|
50
|
+
# into a new environment never overwrites one you already saved.
|
|
51
|
+
|
|
52
|
+
hap auth accounts # list saved environments/accounts (current marked *)
|
|
53
|
+
hap auth use work-prod # switch the environment/account used next
|
|
54
|
+
hap --profile onprem app list # use one environment/account for a single command
|
|
55
|
+
HAP_PROFILE=onprem hap app list # default a whole shell session to one
|
|
56
|
+
|
|
57
|
+
hap auth logout -p onprem # log out a specific account
|
|
58
|
+
hap auth logout --all # log out every account
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Profile resolution order: `--profile` > the `HAP_PROFILE` env var > the current profile.
|
|
62
|
+
|
|
41
63
|
### 2. Pick an organization and app
|
|
42
64
|
|
|
43
65
|
```bash
|
|
@@ -84,9 +106,11 @@ discover every subcommand.
|
|
|
84
106
|
|
|
85
107
|
| Command | Description |
|
|
86
108
|
|---|---|
|
|
87
|
-
| `auth login [SERVER]` | Browser login (
|
|
88
|
-
| `auth
|
|
89
|
-
| `auth
|
|
109
|
+
| `auth login [SERVER]` | Browser login (`--token` for scripted login; `--profile NAME` saves under a name, auto-named if omitted; `--force` repoints an existing name) |
|
|
110
|
+
| `auth accounts` | List saved environments/accounts (current marked `*`) |
|
|
111
|
+
| `auth use NAME` | Switch the environment/account used next |
|
|
112
|
+
| `auth logout` | Log out the current account (`-p NAME` for a specific one, `-a/--all` for every one) |
|
|
113
|
+
| `auth whoami` | Show current user, current environment/account, and current organization |
|
|
90
114
|
| `auth list-my-orgs` | List every org for the current account (current marked `*`) |
|
|
91
115
|
| `auth set-current-org ORG_ID` | Switch the saved current organization |
|
|
92
116
|
|
|
@@ -30,10 +30,30 @@ hap auth login https://your-server.com --token YOUR_TOKEN
|
|
|
30
30
|
浏览器流程会打开 HAP 登录页并捕获令牌。如果 CLI 收不到回调(无 GUI、网络受限),中断命令即可回退到粘贴登录页显示的令牌。
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
hap auth whoami #
|
|
34
|
-
hap auth logout #
|
|
33
|
+
hap auth whoami # 当前用户、当前环境/账号与当前组织
|
|
34
|
+
hap auth logout # 登出当前账号
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
#### 多环境 / 多账号
|
|
38
|
+
|
|
39
|
+
可同时授权多个环境(MingDAO SaaS、Nocoly、私有部署)与多个账号,各存成一个具名档案,长期共存、随时切换:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
hap auth login mingdao --profile work-prod # 登录并起名保存
|
|
43
|
+
hap auth login https://hap.example.com --profile onprem
|
|
44
|
+
# 省略 --profile 时按登录地址自动起名,登录新环境不会覆盖已保存的
|
|
45
|
+
|
|
46
|
+
hap auth accounts # 列出所有环境/账号,当前的带 *
|
|
47
|
+
hap auth use work-prod # 切换当前使用的环境/账号
|
|
48
|
+
hap --profile onprem app list # 只让这一条命令临时用某个环境/账号
|
|
49
|
+
HAP_PROFILE=onprem hap app list # 整个终端会话默认用某个环境/账号
|
|
50
|
+
|
|
51
|
+
hap auth logout -p onprem # 登出指定账号
|
|
52
|
+
hap auth logout --all # 登出全部账号
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
档案选择优先级:`--profile` > 环境变量 `HAP_PROFILE` > 当前档案。
|
|
56
|
+
|
|
37
57
|
### 2. 选定组织和应用
|
|
38
58
|
|
|
39
59
|
```bash
|
|
@@ -77,9 +97,11 @@ hap --json worksheet record list WORKSHEET_ID
|
|
|
77
97
|
|
|
78
98
|
| 命令 | 说明 |
|
|
79
99
|
|---|---|
|
|
80
|
-
| `auth login [SERVER]` | 浏览器登录(`--token`
|
|
81
|
-
| `auth
|
|
82
|
-
| `auth
|
|
100
|
+
| `auth login [SERVER]` | 浏览器登录(`--token` 走非交互;`--profile NAME` 存成具名档案,省略则自动起名;`--force` 重指向已有名字) |
|
|
101
|
+
| `auth accounts` | 列出所有环境/账号,当前的标 `*` |
|
|
102
|
+
| `auth use NAME` | 切换当前使用的环境/账号 |
|
|
103
|
+
| `auth logout` | 登出当前账号(`-p NAME` 指定,`-a/--all` 全部) |
|
|
104
|
+
| `auth whoami` | 当前用户、当前环境/账号与当前组织 |
|
|
83
105
|
| `auth list-my-orgs` | 当前账号所属组织(当前组织标 `*`) |
|
|
84
106
|
| `auth set-current-org ORG_ID` | 切换默认组织 |
|
|
85
107
|
|
|
@@ -404,12 +404,34 @@ def app_restore(ctx, app_id, org_id, backup_id, file_url, file_name, no_data, as
|
|
|
404
404
|
|
|
405
405
|
@app.command("export")
|
|
406
406
|
@click.argument("app_ids", nargs=-1, required=True)
|
|
407
|
+
@org_id_option()
|
|
408
|
+
@click.option(
|
|
409
|
+
"--include-data/--no-data",
|
|
410
|
+
default=False,
|
|
411
|
+
help="Include up to 10000 records per table (default: structure only).",
|
|
412
|
+
)
|
|
413
|
+
@click.option(
|
|
414
|
+
"--password",
|
|
415
|
+
default="",
|
|
416
|
+
help="Protect the exported package with an import password.",
|
|
417
|
+
)
|
|
407
418
|
@pass_context
|
|
408
|
-
def app_export(ctx, app_ids):
|
|
409
|
-
"""Export applications
|
|
419
|
+
def app_export(ctx, app_ids, org_id, include_data, password):
|
|
420
|
+
"""Export one or more applications.
|
|
421
|
+
|
|
422
|
+
Exports run against an organization — pass --org-id or rely on the
|
|
423
|
+
current one. By default only the app structure is exported; use
|
|
424
|
+
--include-data to bundle sample records.
|
|
425
|
+
"""
|
|
410
426
|
try:
|
|
411
427
|
session = ctx.get_session()
|
|
412
|
-
data = app_mod.export_apps(
|
|
428
|
+
data = app_mod.export_apps(
|
|
429
|
+
session,
|
|
430
|
+
require_api_project_id(session, org_id),
|
|
431
|
+
list(app_ids),
|
|
432
|
+
example_type=1 if include_data else 0,
|
|
433
|
+
password=password,
|
|
434
|
+
)
|
|
413
435
|
ctx.output(data, lambda d: click.echo(f"Export started: {d}"))
|
|
414
436
|
except Exception as e:
|
|
415
437
|
ctx.handle_error(e)
|
|
@@ -447,16 +469,39 @@ def app_backup_logs(ctx, app_id, org_id, page_size, page):
|
|
|
447
469
|
# ── Usage & Logs ─────────────────────────────────────────────────────────
|
|
448
470
|
|
|
449
471
|
|
|
472
|
+
# CLI time-window labels → the server's dayRange enum (0=7d,1=month,
|
|
473
|
+
# 2=quarter,3=half-year,4=year). Evidence: pd-openweb
|
|
474
|
+
# src/pages/Admin/app/useAnalytics/util.js selectDateList.
|
|
475
|
+
_USAGE_RANGE = {"7d": 0, "30d": 1, "90d": 2, "6m": 3, "1y": 4}
|
|
476
|
+
|
|
477
|
+
|
|
450
478
|
@app.command("usage")
|
|
451
479
|
@org_id_option()
|
|
452
|
-
@click.option("--app-id", "-a", default="", help="
|
|
453
|
-
@click.option(
|
|
480
|
+
@click.option("--app-id", "-a", default="", help="Show stats for a single app (omit for the whole org)")
|
|
481
|
+
@click.option(
|
|
482
|
+
"--range",
|
|
483
|
+
"time_range",
|
|
484
|
+
type=click.Choice(list(_USAGE_RANGE.keys())),
|
|
485
|
+
default="7d",
|
|
486
|
+
help="Time window: 7d / 30d / 90d / 6m / 1y (default 7d)",
|
|
487
|
+
)
|
|
454
488
|
@pass_context
|
|
455
|
-
def app_usage(ctx, org_id, app_id,
|
|
456
|
-
"""Get application usage statistics.
|
|
489
|
+
def app_usage(ctx, org_id, app_id, time_range):
|
|
490
|
+
"""Get application usage statistics for an organization.
|
|
491
|
+
|
|
492
|
+
Without --app-id you get an organization-wide overview; pass --app-id
|
|
493
|
+
to focus on one application.
|
|
494
|
+
"""
|
|
457
495
|
try:
|
|
458
496
|
session = ctx.get_session()
|
|
459
|
-
data = app_mod.get_usage_stats(
|
|
497
|
+
data = app_mod.get_usage_stats(
|
|
498
|
+
session,
|
|
499
|
+
require_api_project_id(session, org_id),
|
|
500
|
+
app_id=app_id,
|
|
501
|
+
day_range=_USAGE_RANGE[time_range],
|
|
502
|
+
# isApp mirrors the frontend: true only when scoped to one app.
|
|
503
|
+
is_app=bool(app_id),
|
|
504
|
+
)
|
|
460
505
|
ctx.output(data, lambda d: output_json(d))
|
|
461
506
|
except Exception as e:
|
|
462
507
|
ctx.handle_error(e)
|
|
@@ -465,17 +510,25 @@ def app_usage(ctx, org_id, app_id, days):
|
|
|
465
510
|
@app.command("logs")
|
|
466
511
|
@click.argument("app_id", required=False, default="")
|
|
467
512
|
@click.option("--org-id", default="", help="Organization ID (falls back to default)")
|
|
513
|
+
@click.option("--start", default="", help="Window start 'YYYY-MM-DD HH:MM:SS' (default: last 30 days)")
|
|
514
|
+
@click.option("--end", default="", help="Window end 'YYYY-MM-DD HH:MM:SS' (default: now)")
|
|
468
515
|
@click.option("--page-size", "-n", default=50, help="Items per page")
|
|
469
516
|
@click.option("--page", "-p", default=1, help="Page number")
|
|
470
517
|
@pass_context
|
|
471
|
-
def app_logs(ctx, app_id, org_id, page_size, page):
|
|
472
|
-
"""Get
|
|
518
|
+
def app_logs(ctx, app_id, org_id, start, end, page_size, page):
|
|
519
|
+
"""Get operation logs for an organization.
|
|
520
|
+
|
|
521
|
+
Pass APP_ID to see only that app's logs; omit it for the whole
|
|
522
|
+
organization. Defaults to the last 30 days unless --start/--end given.
|
|
523
|
+
"""
|
|
473
524
|
try:
|
|
474
525
|
session = ctx.get_session()
|
|
475
526
|
data = app_mod.get_app_logs(
|
|
476
527
|
session,
|
|
477
528
|
project_id=require_api_project_id(session, org_id),
|
|
478
529
|
app_id=app_id,
|
|
530
|
+
start=start,
|
|
531
|
+
end=end,
|
|
479
532
|
page_index=page,
|
|
480
533
|
page_size=page_size,
|
|
481
534
|
)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""CLI commands for fine-grained HAP app element editing (``hap app-editor``).
|
|
2
|
+
|
|
3
|
+
Thin Click layer over the editor engine in ``hap_cli.core.editor``:
|
|
4
|
+
edit-spec validation, live inspection, dry-run planning, and applying
|
|
5
|
+
single-element changes (worksheet / field / view / role / custom-action /
|
|
6
|
+
workflow / custom-page / component / chatbot / node / app / section).
|
|
7
|
+
|
|
8
|
+
The edit-spec schemas are documented and shipped with the
|
|
9
|
+
``hap-cli-app-editor`` skill; this command resolves them at runtime.
|
|
10
|
+
"""
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
from hap_cli.context import pass_context
|
|
16
|
+
from hap_cli.core.editor import apply as apply_mod
|
|
17
|
+
from hap_cli.core.editor import editspec, planner
|
|
18
|
+
from hap_cli.core.editor.errors import EditorError, EditSpecError
|
|
19
|
+
from hap_cli.core.editor.reader import AppIndex
|
|
20
|
+
from hap_cli.utils.options import org_id_option
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.group("app-editor", help="Fine-grained edit of an existing HAP app's "
|
|
24
|
+
"elements via a structured edit-spec.")
|
|
25
|
+
def app_editor():
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app_editor.command("validate")
|
|
30
|
+
@click.argument("spec_path", type=click.Path(exists=True, dir_okay=False))
|
|
31
|
+
@pass_context
|
|
32
|
+
def app_editor_validate(ctx, spec_path):
|
|
33
|
+
"""Validate an edit-spec locally (no API calls)."""
|
|
34
|
+
try:
|
|
35
|
+
editspec.load_spec(spec_path)
|
|
36
|
+
except EditSpecError as exc:
|
|
37
|
+
if ctx.json_mode:
|
|
38
|
+
ctx.output({"valid": False, "problems": exc.problems})
|
|
39
|
+
else:
|
|
40
|
+
click.echo("edit-spec invalid:", err=True)
|
|
41
|
+
for p in exc.problems:
|
|
42
|
+
click.echo(f" - {p}", err=True)
|
|
43
|
+
raise SystemExit(2)
|
|
44
|
+
ctx.output({"valid": True}, lambda d: click.echo("edit-spec OK"))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _resolve_app_ref(spec, app_override):
|
|
48
|
+
return app_override or spec.get("app")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app_editor.command("inspect")
|
|
52
|
+
@click.argument("app_ref")
|
|
53
|
+
@org_id_option()
|
|
54
|
+
@pass_context
|
|
55
|
+
def app_editor_inspect(ctx, app_ref, org_id):
|
|
56
|
+
"""Print the live name->id structure of an app (worksheets, views,
|
|
57
|
+
roles, workflows, pages, ...) — author edit-specs against it."""
|
|
58
|
+
try:
|
|
59
|
+
session = ctx.get_session()
|
|
60
|
+
idx = AppIndex.load(session, app_ref, org_id)
|
|
61
|
+
ctx.output(idx.summary(),
|
|
62
|
+
lambda d: click.echo(json.dumps(d, ensure_ascii=False, indent=2)))
|
|
63
|
+
except EditorError as e:
|
|
64
|
+
ctx.handle_error(e)
|
|
65
|
+
except Exception as e: # noqa: BLE001
|
|
66
|
+
ctx.handle_error(e)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app_editor.command("plan")
|
|
70
|
+
@click.argument("spec_path", type=click.Path(exists=True, dir_okay=False))
|
|
71
|
+
@click.option("--app", "app_override", default="",
|
|
72
|
+
help="Override the spec's target app (id or name).")
|
|
73
|
+
@org_id_option()
|
|
74
|
+
@pass_context
|
|
75
|
+
def app_editor_plan(ctx, spec_path, app_override, org_id):
|
|
76
|
+
"""Dry run: validate, read live structure, and show what apply would do."""
|
|
77
|
+
try:
|
|
78
|
+
spec = editspec.load_spec(spec_path)
|
|
79
|
+
session = ctx.get_session()
|
|
80
|
+
idx = AppIndex.load(session, _resolve_app_ref(spec, app_override), org_id)
|
|
81
|
+
planned = planner.build_plan(spec, idx)
|
|
82
|
+
ctx.output(
|
|
83
|
+
{"ops": [{"index": p.index, "type": p.op.get("type"),
|
|
84
|
+
"actions": [a.description for a in p.actions]}
|
|
85
|
+
for p in planned]},
|
|
86
|
+
lambda d: click.echo(planner.render_plan(planned)))
|
|
87
|
+
except EditSpecError as exc:
|
|
88
|
+
for p in exc.problems:
|
|
89
|
+
click.echo(f" - {p}", err=True)
|
|
90
|
+
raise SystemExit(2)
|
|
91
|
+
except Exception as e: # noqa: BLE001
|
|
92
|
+
ctx.handle_error(e)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app_editor.command("apply")
|
|
96
|
+
@click.argument("spec_path", type=click.Path(exists=True, dir_okay=False))
|
|
97
|
+
@click.option("--app", "app_override", default="",
|
|
98
|
+
help="Override the spec's target app (id or name).")
|
|
99
|
+
@org_id_option()
|
|
100
|
+
@click.option("--continue", "cont", is_flag=True, default=False,
|
|
101
|
+
help="Keep going after an op fails (default: stop).")
|
|
102
|
+
@pass_context
|
|
103
|
+
def app_editor_apply(ctx, spec_path, app_override, org_id, cont):
|
|
104
|
+
"""Apply an edit-spec against the live app, op by op."""
|
|
105
|
+
try:
|
|
106
|
+
spec = editspec.load_spec(spec_path)
|
|
107
|
+
session = ctx.get_session()
|
|
108
|
+
idx = AppIndex.load(session, _resolve_app_ref(spec, app_override), org_id)
|
|
109
|
+
|
|
110
|
+
def _echo(outcome):
|
|
111
|
+
if not ctx.json_mode:
|
|
112
|
+
mark = "OK " if outcome.status == "ok" else "ERR"
|
|
113
|
+
tail = f" — {outcome.detail}" if outcome.detail else ""
|
|
114
|
+
click.echo(f" [{mark}] [{outcome.index}] {outcome.op_type}{tail}")
|
|
115
|
+
|
|
116
|
+
outcomes = apply_mod.apply_spec(spec, idx, continue_on_error=cont,
|
|
117
|
+
on_outcome=_echo)
|
|
118
|
+
errors = [o for o in outcomes if o.status == "error"]
|
|
119
|
+
ctx.output(
|
|
120
|
+
{"applied": len(outcomes) - len(errors), "total": len(outcomes),
|
|
121
|
+
"failed": len(errors),
|
|
122
|
+
"outcomes": [{"index": o.index, "type": o.op_type,
|
|
123
|
+
"status": o.status, "detail": o.detail}
|
|
124
|
+
for o in outcomes]},
|
|
125
|
+
lambda d: click.echo(
|
|
126
|
+
f"applied {d['applied']}/{d['total']} ops"
|
|
127
|
+
+ (f", {d['failed']} failed" if d['failed'] else "")))
|
|
128
|
+
if errors:
|
|
129
|
+
raise SystemExit(1)
|
|
130
|
+
except EditSpecError as exc:
|
|
131
|
+
for p in exc.problems:
|
|
132
|
+
click.echo(f" - {p}", err=True)
|
|
133
|
+
raise SystemExit(2)
|
|
134
|
+
except SystemExit:
|
|
135
|
+
raise
|
|
136
|
+
except Exception as e: # noqa: BLE001
|
|
137
|
+
ctx.handle_error(e)
|