wxcli 1.2.0__py3-none-any.whl
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.
- wxcli/__init__.py +4 -0
- wxcli/_playbook/.claude/agents/migration-advisor.md +239 -0
- wxcli/_playbook/.claude/agents/wxc-calling-builder.md +946 -0
- wxcli/_playbook/.claude/rules/cleanup.md +29 -0
- wxcli/_playbook/.claude/rules/cucm-migration.md +58 -0
- wxcli/_playbook/.claude/rules/org-health.md +14 -0
- wxcli/_playbook/.claude/settings.json +8 -0
- wxcli/_playbook/.claude/skills/audit-compliance/SKILL.md +515 -0
- wxcli/_playbook/.claude/skills/call-control/SKILL.md +747 -0
- wxcli/_playbook/.claude/skills/configure-features/SKILL.md +623 -0
- wxcli/_playbook/.claude/skills/configure-routing/SKILL.md +627 -0
- wxcli/_playbook/.claude/skills/contact-center/SKILL.md +1181 -0
- wxcli/_playbook/.claude/skills/cucm-migrate/SKILL.md +820 -0
- wxcli/_playbook/.claude/skills/customer-assist/SKILL.md +443 -0
- wxcli/_playbook/.claude/skills/device-platform/SKILL.md +407 -0
- wxcli/_playbook/.claude/skills/manage-call-settings/SKILL.md +757 -0
- wxcli/_playbook/.claude/skills/manage-devices/SKILL.md +790 -0
- wxcli/_playbook/.claude/skills/manage-identity/SKILL.md +638 -0
- wxcli/_playbook/.claude/skills/manage-licensing/SKILL.md +528 -0
- wxcli/_playbook/.claude/skills/manage-meetings/SKILL.md +708 -0
- wxcli/_playbook/.claude/skills/messaging-bots/SKILL.md +365 -0
- wxcli/_playbook/.claude/skills/messaging-spaces/SKILL.md +520 -0
- wxcli/_playbook/.claude/skills/org-health/SKILL.md +159 -0
- wxcli/_playbook/.claude/skills/provision-calling/SKILL.md +421 -0
- wxcli/_playbook/.claude/skills/query-live/SKILL.md +183 -0
- wxcli/_playbook/.claude/skills/query-live/domains/call-flow-trace.md +223 -0
- wxcli/_playbook/.claude/skills/query-live/domains/call-history.md +44 -0
- wxcli/_playbook/.claude/skills/query-live/domains/features.md +169 -0
- wxcli/_playbook/.claude/skills/query-live/domains/numbers.md +93 -0
- wxcli/_playbook/.claude/skills/query-live/domains/people-and-settings.md +146 -0
- wxcli/_playbook/.claude/skills/query-live/domains/routing.md +124 -0
- wxcli/_playbook/.claude/skills/reporting/SKILL.md +501 -0
- wxcli/_playbook/.claude/skills/reporting/references/cdr-recipes.md +1628 -0
- wxcli/_playbook/.claude/skills/reporting-cc/SKILL.md +251 -0
- wxcli/_playbook/.claude/skills/reporting-meetings/SKILL.md +290 -0
- wxcli/_playbook/.claude/skills/teardown/SKILL.md +214 -0
- wxcli/_playbook/.claude/skills/video-mesh/SKILL.md +379 -0
- wxcli/_playbook/.claude/skills/wxc-calling-debug/SKILL.md +475 -0
- wxcli/_playbook/CLAUDE.md +373 -0
- wxcli/_playbook/docs/reference/CLAUDE.md +25 -0
- wxcli/_playbook/docs/reference/admin-apps-data.md +643 -0
- wxcli/_playbook/docs/reference/admin-audit-security.md +322 -0
- wxcli/_playbook/docs/reference/admin-hybrid.md +383 -0
- wxcli/_playbook/docs/reference/admin-identity-scim.md +1007 -0
- wxcli/_playbook/docs/reference/admin-licensing.md +414 -0
- wxcli/_playbook/docs/reference/admin-org-management.md +586 -0
- wxcli/_playbook/docs/reference/admin-partner.md +357 -0
- wxcli/_playbook/docs/reference/archive/wxc-sdk-patterns.md +1229 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-advanced.md +835 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-core.md +743 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-devices-workspaces.md +1305 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-features.md +849 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-locations.md +938 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-person.md +1255 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-routing.md +1262 -0
- wxcli/_playbook/docs/reference/authentication.md +965 -0
- wxcli/_playbook/docs/reference/call-control.md +1205 -0
- wxcli/_playbook/docs/reference/call-features-additional.md +2541 -0
- wxcli/_playbook/docs/reference/call-features-major.md +1320 -0
- wxcli/_playbook/docs/reference/call-routing.md +2304 -0
- wxcli/_playbook/docs/reference/contact-center-analytics.md +1049 -0
- wxcli/_playbook/docs/reference/contact-center-core.md +1614 -0
- wxcli/_playbook/docs/reference/contact-center-journey.md +457 -0
- wxcli/_playbook/docs/reference/contact-center-routing.md +1138 -0
- wxcli/_playbook/docs/reference/devices-core.md +1869 -0
- wxcli/_playbook/docs/reference/devices-dect.md +1150 -0
- wxcli/_playbook/docs/reference/devices-platform.md +467 -0
- wxcli/_playbook/docs/reference/devices-workspaces.md +1426 -0
- wxcli/_playbook/docs/reference/emergency-services.md +1122 -0
- wxcli/_playbook/docs/reference/location-calling-core.md +2037 -0
- wxcli/_playbook/docs/reference/location-calling-media.md +1334 -0
- wxcli/_playbook/docs/reference/location-recording-advanced.md +1482 -0
- wxcli/_playbook/docs/reference/meetings-content.md +473 -0
- wxcli/_playbook/docs/reference/meetings-core.md +903 -0
- wxcli/_playbook/docs/reference/meetings-infrastructure.md +899 -0
- wxcli/_playbook/docs/reference/meetings-settings.md +850 -0
- wxcli/_playbook/docs/reference/messaging-bots.md +1091 -0
- wxcli/_playbook/docs/reference/messaging-spaces.md +695 -0
- wxcli/_playbook/docs/reference/person-call-settings-behavior.md +1639 -0
- wxcli/_playbook/docs/reference/person-call-settings-handling.md +1394 -0
- wxcli/_playbook/docs/reference/person-call-settings-media.md +1898 -0
- wxcli/_playbook/docs/reference/person-call-settings-permissions.md +1329 -0
- wxcli/_playbook/docs/reference/provisioning.md +1366 -0
- wxcli/_playbook/docs/reference/reporting-analytics.md +1318 -0
- wxcli/_playbook/docs/reference/self-service-call-settings.md +1020 -0
- wxcli/_playbook/docs/reference/virtual-lines.md +1132 -0
- wxcli/_playbook/docs/reference/webhooks-events.md +1444 -0
- wxcli/_playbook/docs/reference/wxcadm-xsi-realtime.md +953 -0
- wxcli/_version.py +24 -0
- wxcli/auth.py +137 -0
- wxcli/commands/__init__.py +0 -0
- wxcli/commands/_registry.py +181 -0
- wxcli/commands/activation_email.py +103 -0
- wxcli/commands/admin_recordings.py +585 -0
- wxcli/commands/analytics.py +105 -0
- wxcli/commands/announcement_playlists.py +219 -0
- wxcli/commands/announcements.py +519 -0
- wxcli/commands/archive_users.py +36 -0
- wxcli/commands/attachment_actions.py +60 -0
- wxcli/commands/audit_events.py +83 -0
- wxcli/commands/authorizations.py +113 -0
- wxcli/commands/auto_attendant.py +642 -0
- wxcli/commands/broadworks_billing_reports.py +126 -0
- wxcli/commands/broadworks_enterprises.py +160 -0
- wxcli/commands/broadworks_subscribers.py +319 -0
- wxcli/commands/broadworks_workspaces.py +108 -0
- wxcli/commands/call_controls.py +1304 -0
- wxcli/commands/call_park.py +494 -0
- wxcli/commands/call_pickup.py +229 -0
- wxcli/commands/call_queue.py +1878 -0
- wxcli/commands/call_recording.py +707 -0
- wxcli/commands/call_routing.py +1610 -0
- wxcli/commands/call_settings_for_me_phase_5.py +212 -0
- wxcli/commands/caller_reputation.py +169 -0
- wxcli/commands/calling_service.py +305 -0
- wxcli/commands/cc_address_book.py +770 -0
- wxcli/commands/cc_agent_greetings.py +242 -0
- wxcli/commands/cc_agent_summaries.py +39 -0
- wxcli/commands/cc_agent_wellbeing.py +216 -0
- wxcli/commands/cc_agents.py +425 -0
- wxcli/commands/cc_ai_assistant.py +55 -0
- wxcli/commands/cc_ai_feature.py +294 -0
- wxcli/commands/cc_audio_files.py +149 -0
- wxcli/commands/cc_auto_csat.py +311 -0
- wxcli/commands/cc_aux_code.py +458 -0
- wxcli/commands/cc_business_hour.py +353 -0
- wxcli/commands/cc_call_monitoring.py +228 -0
- wxcli/commands/cc_callbacks.py +194 -0
- wxcli/commands/cc_campaign.py +212 -0
- wxcli/commands/cc_captures.py +39 -0
- wxcli/commands/cc_contact_list.py +169 -0
- wxcli/commands/cc_contact_number.py +295 -0
- wxcli/commands/cc_data_sources.py +235 -0
- wxcli/commands/cc_desktop_layout.py +380 -0
- wxcli/commands/cc_desktop_profile.py +322 -0
- wxcli/commands/cc_dial_number.py +511 -0
- wxcli/commands/cc_dial_plan.py +347 -0
- wxcli/commands/cc_dnc.py +101 -0
- wxcli/commands/cc_entry_point.py +545 -0
- wxcli/commands/cc_ewt.py +51 -0
- wxcli/commands/cc_flow.py +477 -0
- wxcli/commands/cc_global_vars.py +430 -0
- wxcli/commands/cc_holiday_list.py +335 -0
- wxcli/commands/cc_journey.py +1261 -0
- wxcli/commands/cc_legacy_flows.py +49 -0
- wxcli/commands/cc_multimedia_profile.py +436 -0
- wxcli/commands/cc_notification.py +51 -0
- wxcli/commands/cc_outdial_ani.py +604 -0
- wxcli/commands/cc_overrides.py +341 -0
- wxcli/commands/cc_queue.py +1083 -0
- wxcli/commands/cc_queue_stats.py +55 -0
- wxcli/commands/cc_realtime.py +51 -0
- wxcli/commands/cc_resource_collection.py +312 -0
- wxcli/commands/cc_search.py +46 -0
- wxcli/commands/cc_site.py +400 -0
- wxcli/commands/cc_skill.py +350 -0
- wxcli/commands/cc_skill_profile.py +289 -0
- wxcli/commands/cc_subscriptions.py +426 -0
- wxcli/commands/cc_summaries.py +130 -0
- wxcli/commands/cc_tasks.py +844 -0
- wxcli/commands/cc_team.py +325 -0
- wxcli/commands/cc_user_profiles.py +349 -0
- wxcli/commands/cc_users.py +701 -0
- wxcli/commands/cc_work_types.py +363 -0
- wxcli/commands/cdr.py +89 -0
- wxcli/commands/classifications.py +38 -0
- wxcli/commands/cleanup.py +1427 -0
- wxcli/commands/client_settings.py +72 -0
- wxcli/commands/conference.py +328 -0
- wxcli/commands/configure.py +72 -0
- wxcli/commands/converged_recordings.py +347 -0
- wxcli/commands/converged_recordings_export.py +310 -0
- wxcli/commands/cq_playlists.py +42 -0
- wxcli/commands/cucm.py +2794 -0
- wxcli/commands/cucm_config.py +129 -0
- wxcli/commands/customer_assist.py +373 -0
- wxcli/commands/data_sources.py +227 -0
- wxcli/commands/dect_devices.py +787 -0
- wxcli/commands/device_configurations.py +77 -0
- wxcli/commands/device_dynamic_settings.py +320 -0
- wxcli/commands/device_settings.py +1489 -0
- wxcli/commands/devices.py +280 -0
- wxcli/commands/domains.py +142 -0
- wxcli/commands/ecm.py +178 -0
- wxcli/commands/emergency_services.py +827 -0
- wxcli/commands/events.py +83 -0
- wxcli/commands/external_voicemail.py +52 -0
- wxcli/commands/groups.py +206 -0
- wxcli/commands/guest_management.py +76 -0
- wxcli/commands/hds.py +340 -0
- wxcli/commands/hot_desk.py +67 -0
- wxcli/commands/hot_desking_members.py +118 -0
- wxcli/commands/hot_desking_portal.py +124 -0
- wxcli/commands/hunt_group.py +577 -0
- wxcli/commands/hybrid_clusters.py +71 -0
- wxcli/commands/hybrid_connectors.py +67 -0
- wxcli/commands/identity_org.py +159 -0
- wxcli/commands/init_playbook.py +155 -0
- wxcli/commands/licenses.py +106 -0
- wxcli/commands/live_monitoring.py +40 -0
- wxcli/commands/location_call_handling.py +577 -0
- wxcli/commands/location_schedules.py +341 -0
- wxcli/commands/location_settings.py +1297 -0
- wxcli/commands/location_voicemail.py +494 -0
- wxcli/commands/locations.py +336 -0
- wxcli/commands/meeting_captions.py +110 -0
- wxcli/commands/meeting_chats.py +63 -0
- wxcli/commands/meeting_invitees.py +234 -0
- wxcli/commands/meeting_messages.py +28 -0
- wxcli/commands/meeting_participants.py +278 -0
- wxcli/commands/meeting_polls.py +111 -0
- wxcli/commands/meeting_preferences.py +515 -0
- wxcli/commands/meeting_qa.py +78 -0
- wxcli/commands/meeting_qualities.py +43 -0
- wxcli/commands/meeting_reports.py +101 -0
- wxcli/commands/meeting_session_types.py +105 -0
- wxcli/commands/meeting_site.py +57 -0
- wxcli/commands/meeting_slido.py +44 -0
- wxcli/commands/meeting_summaries.py +92 -0
- wxcli/commands/meeting_tracking_codes.py +233 -0
- wxcli/commands/meeting_transcripts.py +230 -0
- wxcli/commands/meetings.py +1997 -0
- wxcli/commands/memberships.py +164 -0
- wxcli/commands/messages.py +213 -0
- wxcli/commands/mode_management.py +244 -0
- wxcli/commands/my_call_settings.py +3577 -0
- wxcli/commands/numbers.py +405 -0
- wxcli/commands/operating_modes.py +410 -0
- wxcli/commands/org_contacts.py +284 -0
- wxcli/commands/org_health_cli.py +55 -0
- wxcli/commands/org_settings.py +71 -0
- wxcli/commands/organizations.py +83 -0
- wxcli/commands/paging_group.py +257 -0
- wxcli/commands/partner_admins.py +141 -0
- wxcli/commands/partner_reports.py +188 -0
- wxcli/commands/partner_tags.py +216 -0
- wxcli/commands/people.py +279 -0
- wxcli/commands/person_call_settings.py +71 -0
- wxcli/commands/pstn.py +280 -0
- wxcli/commands/recording_report.py +154 -0
- wxcli/commands/report_templates.py +38 -0
- wxcli/commands/reports.py +144 -0
- wxcli/commands/resource_group_memberships.py +160 -0
- wxcli/commands/resource_groups.py +67 -0
- wxcli/commands/roles.py +63 -0
- wxcli/commands/room_tabs.py +160 -0
- wxcli/commands/rooms.py +225 -0
- wxcli/commands/scim_bulk.py +46 -0
- wxcli/commands/scim_groups.py +246 -0
- wxcli/commands/scim_schemas.py +82 -0
- wxcli/commands/scim_users.py +283 -0
- wxcli/commands/security_audit.py +54 -0
- wxcli/commands/service_apps.py +51 -0
- wxcli/commands/single_number_reach.py +209 -0
- wxcli/commands/team_memberships.py +155 -0
- wxcli/commands/teams.py +153 -0
- wxcli/commands/ucm_profile.py +42 -0
- wxcli/commands/update.py +219 -0
- wxcli/commands/user_settings.py +5116 -0
- wxcli/commands/video_mesh.py +1090 -0
- wxcli/commands/virtual_extensions.py +502 -0
- wxcli/commands/virtual_line_settings.py +2072 -0
- wxcli/commands/webhooks.py +176 -0
- wxcli/commands/wholesale_billing_reports.py +147 -0
- wxcli/commands/wholesale_provisioning.py +523 -0
- wxcli/commands/workspace_locations.py +339 -0
- wxcli/commands/workspace_metrics.py +103 -0
- wxcli/commands/workspace_personalization.py +70 -0
- wxcli/commands/workspace_settings.py +3347 -0
- wxcli/commands/workspaces.py +279 -0
- wxcli/commands/xapi.py +114 -0
- wxcli/config.py +139 -0
- wxcli/errors.py +77 -0
- wxcli/main.py +210 -0
- wxcli/migration/CLAUDE.md +106 -0
- wxcli/migration/__init__.py +1 -0
- wxcli/migration/advisory/CLAUDE.md +196 -0
- wxcli/migration/advisory/__init__.py +46 -0
- wxcli/migration/advisory/advisor.py +107 -0
- wxcli/migration/advisory/advisory_patterns.py +2552 -0
- wxcli/migration/advisory/recommendation_rules.py +835 -0
- wxcli/migration/cucm/CLAUDE.md +74 -0
- wxcli/migration/cucm/__init__.py +5 -0
- wxcli/migration/cucm/connection.py +265 -0
- wxcli/migration/cucm/discovery.py +225 -0
- wxcli/migration/cucm/extractors/__init__.py +1 -0
- wxcli/migration/cucm/extractors/announcements.py +92 -0
- wxcli/migration/cucm/extractors/base.py +90 -0
- wxcli/migration/cucm/extractors/device_profiles.py +98 -0
- wxcli/migration/cucm/extractors/devices.py +193 -0
- wxcli/migration/cucm/extractors/e911.py +103 -0
- wxcli/migration/cucm/extractors/features.py +309 -0
- wxcli/migration/cucm/extractors/helpers.py +73 -0
- wxcli/migration/cucm/extractors/informational.py +295 -0
- wxcli/migration/cucm/extractors/locations.py +221 -0
- wxcli/migration/cucm/extractors/moh.py +100 -0
- wxcli/migration/cucm/extractors/remote_destinations.py +96 -0
- wxcli/migration/cucm/extractors/routing.py +493 -0
- wxcli/migration/cucm/extractors/shared_lines.py +146 -0
- wxcli/migration/cucm/extractors/templates.py +229 -0
- wxcli/migration/cucm/extractors/tier4.py +173 -0
- wxcli/migration/cucm/extractors/users.py +240 -0
- wxcli/migration/cucm/extractors/voicemail.py +243 -0
- wxcli/migration/cucm/extractors/workspaces.py +88 -0
- wxcli/migration/cucm/unity_connection.py +304 -0
- wxcli/migration/execute/CLAUDE.md +372 -0
- wxcli/migration/execute/__init__.py +303 -0
- wxcli/migration/execute/batch.py +277 -0
- wxcli/migration/execute/dependency.py +483 -0
- wxcli/migration/execute/engine.py +964 -0
- wxcli/migration/execute/handlers.py +2232 -0
- wxcli/migration/execute/planner.py +1939 -0
- wxcli/migration/execute/runtime.py +571 -0
- wxcli/migration/export/CLAUDE.md +49 -0
- wxcli/migration/export/__init__.py +8 -0
- wxcli/migration/export/csv_export.py +126 -0
- wxcli/migration/export/deployment_plan.py +479 -0
- wxcli/migration/export/json_export.py +60 -0
- wxcli/migration/models.py +941 -0
- wxcli/migration/phone_models.py +50 -0
- wxcli/migration/preflight/CLAUDE.md +52 -0
- wxcli/migration/preflight/__init__.py +89 -0
- wxcli/migration/preflight/checks.py +911 -0
- wxcli/migration/preflight/runner.py +293 -0
- wxcli/migration/rate_limiter.py +144 -0
- wxcli/migration/report/CLAUDE.md +245 -0
- wxcli/migration/report/__init__.py +7 -0
- wxcli/migration/report/appendix.py +2437 -0
- wxcli/migration/report/assembler.py +183 -0
- wxcli/migration/report/charts.py +263 -0
- wxcli/migration/report/executive.py +776 -0
- wxcli/migration/report/explainer.py +704 -0
- wxcli/migration/report/helpers.py +74 -0
- wxcli/migration/report/ingest.py +152 -0
- wxcli/migration/report/notice_templates.py +192 -0
- wxcli/migration/report/score.py +375 -0
- wxcli/migration/report/styles.py +1052 -0
- wxcli/migration/report/user_diff.py +1052 -0
- wxcli/migration/report/user_notice.py +425 -0
- wxcli/migration/state.py +139 -0
- wxcli/migration/store.py +860 -0
- wxcli/migration/transform/CLAUDE.md +107 -0
- wxcli/migration/transform/__init__.py +12 -0
- wxcli/migration/transform/analysis_pipeline.py +453 -0
- wxcli/migration/transform/analyzers/__init__.py +142 -0
- wxcli/migration/transform/analyzers/css_permission.py +189 -0
- wxcli/migration/transform/analyzers/css_routing.py +335 -0
- wxcli/migration/transform/analyzers/device_compatibility.py +122 -0
- wxcli/migration/transform/analyzers/dn_ambiguity.py +131 -0
- wxcli/migration/transform/analyzers/duplicate_user.py +221 -0
- wxcli/migration/transform/analyzers/extension_conflict.py +173 -0
- wxcli/migration/transform/analyzers/feature_approximation.py +227 -0
- wxcli/migration/transform/analyzers/layout_overflow.py +174 -0
- wxcli/migration/transform/analyzers/location_ambiguity.py +184 -0
- wxcli/migration/transform/analyzers/missing_data.py +300 -0
- wxcli/migration/transform/analyzers/selective_call_handling.py +394 -0
- wxcli/migration/transform/analyzers/shared_line.py +170 -0
- wxcli/migration/transform/analyzers/voicemail_compatibility.py +267 -0
- wxcli/migration/transform/analyzers/workspace_license.py +212 -0
- wxcli/migration/transform/cross_reference.py +1136 -0
- wxcli/migration/transform/cucm_pattern.py +246 -0
- wxcli/migration/transform/decisions.py +301 -0
- wxcli/migration/transform/e164.py +145 -0
- wxcli/migration/transform/engine.py +256 -0
- wxcli/migration/transform/mappers/CLAUDE.md +123 -0
- wxcli/migration/transform/mappers/__init__.py +76 -0
- wxcli/migration/transform/mappers/announcement_mapper.py +122 -0
- wxcli/migration/transform/mappers/base.py +190 -0
- wxcli/migration/transform/mappers/button_template_mapper.py +186 -0
- wxcli/migration/transform/mappers/call_forwarding_mapper.py +228 -0
- wxcli/migration/transform/mappers/call_settings_mapper.py +144 -0
- wxcli/migration/transform/mappers/css_mapper.py +868 -0
- wxcli/migration/transform/mappers/dect_mapper.py +367 -0
- wxcli/migration/transform/mappers/device_layout_mapper.py +289 -0
- wxcli/migration/transform/mappers/device_mapper.py +227 -0
- wxcli/migration/transform/mappers/device_profile_mapper.py +190 -0
- wxcli/migration/transform/mappers/device_settings_mapper.py +464 -0
- wxcli/migration/transform/mappers/e911_mapper.py +129 -0
- wxcli/migration/transform/mappers/ecbn_mapper.py +263 -0
- wxcli/migration/transform/mappers/executive_assistant_mapper.py +156 -0
- wxcli/migration/transform/mappers/feature_mapper.py +1179 -0
- wxcli/migration/transform/mappers/line_mapper.py +310 -0
- wxcli/migration/transform/mappers/location_mapper.py +235 -0
- wxcli/migration/transform/mappers/moh_mapper.py +114 -0
- wxcli/migration/transform/mappers/monitoring_mapper.py +221 -0
- wxcli/migration/transform/mappers/receptionist_mapper.py +245 -0
- wxcli/migration/transform/mappers/routing_mapper.py +821 -0
- wxcli/migration/transform/mappers/snr_mapper.py +201 -0
- wxcli/migration/transform/mappers/softkey_mapper.py +205 -0
- wxcli/migration/transform/mappers/user_mapper.py +264 -0
- wxcli/migration/transform/mappers/voicemail_group_mapper.py +286 -0
- wxcli/migration/transform/mappers/voicemail_mapper.py +514 -0
- wxcli/migration/transform/mappers/workspace_mapper.py +464 -0
- wxcli/migration/transform/normalizers.py +2008 -0
- wxcli/migration/transform/pattern_converter.py +106 -0
- wxcli/migration/transform/pipeline.py +271 -0
- wxcli/migration/transform/rules.py +255 -0
- wxcli/org_health/CLAUDE.md +63 -0
- wxcli/org_health/__init__.py +7 -0
- wxcli/org_health/analyze.py +93 -0
- wxcli/org_health/checks.py +458 -0
- wxcli/org_health/collector.py +62 -0
- wxcli/org_health/models.py +78 -0
- wxcli/org_health/report.py +300 -0
- wxcli/output.py +101 -0
- wxcli-1.2.0.dist-info/METADATA +505 -0
- wxcli-1.2.0.dist-info/RECORD +413 -0
- wxcli-1.2.0.dist-info/WHEEL +5 -0
- wxcli-1.2.0.dist-info/entry_points.txt +2 -0
- wxcli-1.2.0.dist-info/licenses/LICENSE +191 -0
- wxcli-1.2.0.dist-info/scm_file_list.json +751 -0
- wxcli-1.2.0.dist-info/scm_version.json +8 -0
- wxcli-1.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import typer
|
|
3
|
+
from wxcli.auth import get_api
|
|
4
|
+
from wxcli.errors import WebexError, handle_rest_error
|
|
5
|
+
from wxcli.output import print_table, print_json
|
|
6
|
+
from wxcli.config import get_org_id
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(help="Manage Webex Calling devices.")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command("list")
|
|
13
|
+
def cmd_list(
|
|
14
|
+
display_name: str = typer.Option(None, "--display-name", help="List devices with this display name."),
|
|
15
|
+
person_id: str = typer.Option(None, "--person-id", help="List devices by person ID."),
|
|
16
|
+
workspace_id: str = typer.Option(None, "--workspace-id", help="List devices by workspace ID."),
|
|
17
|
+
connection_status: str = typer.Option(None, "--connection-status", help="List devices with this connection status."),
|
|
18
|
+
product: str = typer.Option(None, "--product", help="Choices: DX-80, RoomKit, SX-80"),
|
|
19
|
+
type_param: str = typer.Option(None, "--type", help="Choices: roomdesk, phone, accessory, webexgo, unknown"),
|
|
20
|
+
serial: str = typer.Option(None, "--serial", help="List devices with this serial number."),
|
|
21
|
+
tag: str = typer.Option(None, "--tag", help="List devices which have a tag. Searching for multiple tags"),
|
|
22
|
+
software: str = typer.Option(None, "--software", help="List devices with this software version."),
|
|
23
|
+
upgrade_channel: str = typer.Option(None, "--upgrade-channel", help="List devices with this upgrade channel."),
|
|
24
|
+
error_code: str = typer.Option(None, "--error-code", help="List devices with this error code."),
|
|
25
|
+
capability: str = typer.Option(None, "--capability", help="Choices: xapi"),
|
|
26
|
+
permission: str = typer.Option(None, "--permission", help="List devices with this permission."),
|
|
27
|
+
location_id: str = typer.Option(None, "--location-id", help="List devices by location ID."),
|
|
28
|
+
workspace_location_id: str = typer.Option(None, "--workspace-location-id", help="List devices by workspace location ID. Deprecated, prefer `"),
|
|
29
|
+
mac: str = typer.Option(None, "--mac", help="List devices with this MAC address."),
|
|
30
|
+
device_platform: str = typer.Option(None, "--device-platform", help="Choices: cisco, microsoftTeamsRoom"),
|
|
31
|
+
planned_maintenance: str = typer.Option(None, "--planned-maintenance", help="Choices: off, on, upcoming"),
|
|
32
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
|
|
33
|
+
limit: int = typer.Option(0, "--limit", help="Max results (0=all for paginated endpoints, API default for non-paginated)"),
|
|
34
|
+
offset: int = typer.Option(0, "--offset", help="Start offset"),
|
|
35
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
36
|
+
):
|
|
37
|
+
"""List Devices."""
|
|
38
|
+
api = get_api(debug=debug)
|
|
39
|
+
url = f"https://webexapis.com/v1/devices"
|
|
40
|
+
params = {}
|
|
41
|
+
if display_name is not None:
|
|
42
|
+
params["displayName"] = display_name
|
|
43
|
+
if person_id is not None:
|
|
44
|
+
params["personId"] = person_id
|
|
45
|
+
if workspace_id is not None:
|
|
46
|
+
params["workspaceId"] = workspace_id
|
|
47
|
+
if connection_status is not None:
|
|
48
|
+
params["connectionStatus"] = connection_status
|
|
49
|
+
if product is not None:
|
|
50
|
+
params["product"] = product
|
|
51
|
+
if type_param is not None:
|
|
52
|
+
params["type"] = type_param
|
|
53
|
+
if serial is not None:
|
|
54
|
+
params["serial"] = serial
|
|
55
|
+
if tag is not None:
|
|
56
|
+
params["tag"] = tag
|
|
57
|
+
if software is not None:
|
|
58
|
+
params["software"] = software
|
|
59
|
+
if upgrade_channel is not None:
|
|
60
|
+
params["upgradeChannel"] = upgrade_channel
|
|
61
|
+
if error_code is not None:
|
|
62
|
+
params["errorCode"] = error_code
|
|
63
|
+
if capability is not None:
|
|
64
|
+
params["capability"] = capability
|
|
65
|
+
if permission is not None:
|
|
66
|
+
params["permission"] = permission
|
|
67
|
+
if location_id is not None:
|
|
68
|
+
params["locationId"] = location_id
|
|
69
|
+
if workspace_location_id is not None:
|
|
70
|
+
params["workspaceLocationId"] = workspace_location_id
|
|
71
|
+
if mac is not None:
|
|
72
|
+
params["mac"] = mac
|
|
73
|
+
if device_platform is not None:
|
|
74
|
+
params["devicePlatform"] = device_platform
|
|
75
|
+
if planned_maintenance is not None:
|
|
76
|
+
params["plannedMaintenance"] = planned_maintenance
|
|
77
|
+
if limit > 0:
|
|
78
|
+
params["max"] = limit
|
|
79
|
+
if offset > 0:
|
|
80
|
+
params["start"] = offset
|
|
81
|
+
org_id = get_org_id()
|
|
82
|
+
if org_id is not None:
|
|
83
|
+
params["orgId"] = org_id
|
|
84
|
+
try:
|
|
85
|
+
if limit > 0:
|
|
86
|
+
result = api.session.rest_get(url, params=params)
|
|
87
|
+
result = result or {}
|
|
88
|
+
items = result.get("items", result.get("data", result if isinstance(result, list) else [])) if isinstance(result, dict) else (result if isinstance(result, list) else [])
|
|
89
|
+
else:
|
|
90
|
+
items = list(api.session.follow_pagination(url=url, params=params, item_key="items"))
|
|
91
|
+
except WebexError as e:
|
|
92
|
+
handle_rest_error(e)
|
|
93
|
+
if output == "json":
|
|
94
|
+
print_json(items)
|
|
95
|
+
else:
|
|
96
|
+
print_table(items, columns=[('ID', 'id'), ('Display Name', 'displayName'), ('Product', 'product'), ('MAC', 'mac')], limit=limit)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@app.command("create")
|
|
101
|
+
def create(
|
|
102
|
+
mac: str = typer.Option(None, "--mac", help="(required) The MAC address of the device being created."),
|
|
103
|
+
model: str = typer.Option(None, "--model", help="(required) The model of the device being created. The corresponding dev"),
|
|
104
|
+
workspace_id: str = typer.Option(None, "--workspace-id", help="The ID of the workspace where the device will be created."),
|
|
105
|
+
person_id: str = typer.Option(None, "--person-id", help="The ID of the person who will own the device once created."),
|
|
106
|
+
password: str = typer.Option(None, "--password", help="SIP password to be configured for the phone, only required w"),
|
|
107
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
|
|
108
|
+
output: str = typer.Option("id", "--output", "-o", help="Output format: id|json"),
|
|
109
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
110
|
+
):
|
|
111
|
+
"""Create a Device by MAC Address."""
|
|
112
|
+
api = get_api(debug=debug)
|
|
113
|
+
url = f"https://webexapis.com/v1/devices"
|
|
114
|
+
params = {}
|
|
115
|
+
org_id = get_org_id()
|
|
116
|
+
if org_id is not None:
|
|
117
|
+
params["orgId"] = org_id
|
|
118
|
+
if json_body:
|
|
119
|
+
body = json.loads(json_body)
|
|
120
|
+
else:
|
|
121
|
+
body = {}
|
|
122
|
+
if mac is not None:
|
|
123
|
+
body["mac"] = mac
|
|
124
|
+
if model is not None:
|
|
125
|
+
body["model"] = model
|
|
126
|
+
if workspace_id is not None:
|
|
127
|
+
body["workspaceId"] = workspace_id
|
|
128
|
+
if person_id is not None:
|
|
129
|
+
body["personId"] = person_id
|
|
130
|
+
if password is not None:
|
|
131
|
+
body["password"] = password
|
|
132
|
+
_missing = [f for f in ['mac', 'model'] if f not in body or body[f] is None]
|
|
133
|
+
if _missing:
|
|
134
|
+
typer.echo("Error: Missing required fields: " + ", ".join(_missing), err=True)
|
|
135
|
+
raise typer.Exit(1)
|
|
136
|
+
try:
|
|
137
|
+
result = api.session.rest_post(url, json=body, params=params)
|
|
138
|
+
except WebexError as e:
|
|
139
|
+
handle_rest_error(e)
|
|
140
|
+
if output == "json":
|
|
141
|
+
print_json(result)
|
|
142
|
+
elif isinstance(result, dict) and "id" in result:
|
|
143
|
+
typer.echo(f"Created: {result['id']}")
|
|
144
|
+
elif not result or result == {}:
|
|
145
|
+
typer.echo("Created.")
|
|
146
|
+
else:
|
|
147
|
+
print_json(result)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@app.command("show")
|
|
152
|
+
def show(
|
|
153
|
+
device_id: str = typer.Argument(help="deviceId"),
|
|
154
|
+
output: str = typer.Option("json", "--output", "-o", help="Output format: table|json"),
|
|
155
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
156
|
+
):
|
|
157
|
+
"""Get Device Details."""
|
|
158
|
+
api = get_api(debug=debug)
|
|
159
|
+
url = f"https://webexapis.com/v1/devices/{device_id}"
|
|
160
|
+
params = {}
|
|
161
|
+
org_id = get_org_id()
|
|
162
|
+
if org_id is not None:
|
|
163
|
+
params["orgId"] = org_id
|
|
164
|
+
try:
|
|
165
|
+
result = api.session.rest_get(url, params=params)
|
|
166
|
+
except WebexError as e:
|
|
167
|
+
handle_rest_error(e)
|
|
168
|
+
if output == "json":
|
|
169
|
+
print_json(result)
|
|
170
|
+
else:
|
|
171
|
+
if isinstance(result, dict):
|
|
172
|
+
print_table([result], columns=[("Key", ""), ("Value", "")], limit=0)
|
|
173
|
+
elif isinstance(result, list):
|
|
174
|
+
print_table(result, columns=[("ID", "id"), ("Name", "name")], limit=0)
|
|
175
|
+
else:
|
|
176
|
+
print_json(result)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@app.command("update")
|
|
181
|
+
def update(
|
|
182
|
+
device_id: str = typer.Argument(help="deviceId"),
|
|
183
|
+
op: str = typer.Option(None, "--op", help="Choices: add, remove, replace"),
|
|
184
|
+
path: str = typer.Option(None, "--path", help="Only the tags path is supported to patch."),
|
|
185
|
+
value: str = typer.Option(None, "--value", help="Value for replace op (JSON-parsed: string, number, bool, or array)"),
|
|
186
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
|
|
187
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
188
|
+
):
|
|
189
|
+
"""Modify Device Tags\n\nExample --json-body:\n '{"op":"add","path":"...","value":["..."]}'."""
|
|
190
|
+
api = get_api(debug=debug)
|
|
191
|
+
url = f"https://webexapis.com/v1/devices/{device_id}"
|
|
192
|
+
params = {}
|
|
193
|
+
org_id = get_org_id()
|
|
194
|
+
if org_id is not None:
|
|
195
|
+
params["orgId"] = org_id
|
|
196
|
+
if json_body:
|
|
197
|
+
body = json.loads(json_body)
|
|
198
|
+
else:
|
|
199
|
+
patch_op = {}
|
|
200
|
+
if op is not None:
|
|
201
|
+
patch_op["op"] = op
|
|
202
|
+
if path is not None:
|
|
203
|
+
patch_op["path"] = path
|
|
204
|
+
if value is not None:
|
|
205
|
+
try:
|
|
206
|
+
patch_op["value"] = json.loads(value)
|
|
207
|
+
except json.JSONDecodeError:
|
|
208
|
+
patch_op["value"] = value
|
|
209
|
+
body = [patch_op]
|
|
210
|
+
try:
|
|
211
|
+
result = api.session.rest_patch(url, json=body, params=params, content_type="application/json-patch+json")
|
|
212
|
+
except WebexError as e:
|
|
213
|
+
handle_rest_error(e)
|
|
214
|
+
typer.echo(f"Updated.")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@app.command("delete")
|
|
219
|
+
def delete(
|
|
220
|
+
device_id: str = typer.Argument(help="deviceId"),
|
|
221
|
+
force: bool = typer.Option(False, "--force", help="Skip confirmation"),
|
|
222
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
223
|
+
):
|
|
224
|
+
"""Delete a Device."""
|
|
225
|
+
if not force:
|
|
226
|
+
typer.confirm(f"Delete {device_id}?", abort=True)
|
|
227
|
+
api = get_api(debug=debug)
|
|
228
|
+
url = f"https://webexapis.com/v1/devices/{device_id}"
|
|
229
|
+
params = {}
|
|
230
|
+
org_id = get_org_id()
|
|
231
|
+
if org_id is not None:
|
|
232
|
+
params["orgId"] = org_id
|
|
233
|
+
try:
|
|
234
|
+
api.session.rest_delete(url, params=params)
|
|
235
|
+
except WebexError as e:
|
|
236
|
+
handle_rest_error(e)
|
|
237
|
+
typer.echo(f"Deleted: {device_id}")
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@app.command("create-activation-code")
|
|
242
|
+
def create_activation_code(
|
|
243
|
+
workspace_id: str = typer.Option(None, "--workspace-id", help="The ID of the workspace where the device will be activated."),
|
|
244
|
+
person_id: str = typer.Option(None, "--person-id", help="The ID of the person who will own the device once activated."),
|
|
245
|
+
model: str = typer.Option(None, "--model", help="The model of the device being created. The corresponding dev"),
|
|
246
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
|
|
247
|
+
output: str = typer.Option("id", "--output", "-o", help="Output format: id|json"),
|
|
248
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
249
|
+
):
|
|
250
|
+
"""Create a Device Activation Code."""
|
|
251
|
+
api = get_api(debug=debug)
|
|
252
|
+
url = f"https://webexapis.com/v1/devices/activationCode"
|
|
253
|
+
params = {}
|
|
254
|
+
org_id = get_org_id()
|
|
255
|
+
if org_id is not None:
|
|
256
|
+
params["orgId"] = org_id
|
|
257
|
+
if json_body:
|
|
258
|
+
body = json.loads(json_body)
|
|
259
|
+
else:
|
|
260
|
+
body = {}
|
|
261
|
+
if workspace_id is not None:
|
|
262
|
+
body["workspaceId"] = workspace_id
|
|
263
|
+
if person_id is not None:
|
|
264
|
+
body["personId"] = person_id
|
|
265
|
+
if model is not None:
|
|
266
|
+
body["model"] = model
|
|
267
|
+
try:
|
|
268
|
+
result = api.session.rest_post(url, json=body, params=params)
|
|
269
|
+
except WebexError as e:
|
|
270
|
+
handle_rest_error(e)
|
|
271
|
+
if output == "json":
|
|
272
|
+
print_json(result)
|
|
273
|
+
elif isinstance(result, dict) and "id" in result:
|
|
274
|
+
typer.echo(f"Created: {result['id']}")
|
|
275
|
+
elif not result or result == {}:
|
|
276
|
+
typer.echo("Created.")
|
|
277
|
+
else:
|
|
278
|
+
print_json(result)
|
|
279
|
+
|
|
280
|
+
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import typer
|
|
3
|
+
from wxcli.auth import get_api
|
|
4
|
+
from wxcli.errors import WebexError, handle_rest_error
|
|
5
|
+
from wxcli.output import print_table, print_json
|
|
6
|
+
from wxcli.config import resolve_org_id
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(help="Manage Webex Calling domains.")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command("get-domain-verification")
|
|
13
|
+
def get_domain_verification(
|
|
14
|
+
domain: str = typer.Option(None, "--domain", help="A valid domain name."),
|
|
15
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body"),
|
|
16
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
17
|
+
):
|
|
18
|
+
"""Get Domain Verification Token."""
|
|
19
|
+
api = get_api(debug=debug)
|
|
20
|
+
org_id = resolve_org_id(api.session)
|
|
21
|
+
url = f"https://webexapis.com/identity/organizations/{org_id}/actions/getDomainVerificationToken"
|
|
22
|
+
if json_body:
|
|
23
|
+
body = json.loads(json_body)
|
|
24
|
+
else:
|
|
25
|
+
body = {}
|
|
26
|
+
if domain is not None:
|
|
27
|
+
body["domain"] = domain
|
|
28
|
+
try:
|
|
29
|
+
result = api.session.rest_post(url, json=body)
|
|
30
|
+
except WebexError as e:
|
|
31
|
+
handle_rest_error(e)
|
|
32
|
+
print_json(result)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@app.command("verify-domain")
|
|
37
|
+
def verify_domain(
|
|
38
|
+
domain: str = typer.Option(None, "--domain", help="The domain name to be verified."),
|
|
39
|
+
claim_domain: str = typer.Option(None, "--claim-domain", help="A boolean to specify whether the domain needs to be claimed."),
|
|
40
|
+
reserve_domain: str = typer.Option(None, "--reserve-domain", help="For FedRAMP only: If true, add the domain to the FedRAMP res"),
|
|
41
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body"),
|
|
42
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
43
|
+
):
|
|
44
|
+
"""Verify Domain."""
|
|
45
|
+
api = get_api(debug=debug)
|
|
46
|
+
org_id = resolve_org_id(api.session)
|
|
47
|
+
url = f"https://webexapis.com/identity/organizations/{org_id}/actions/verifyDomain"
|
|
48
|
+
if json_body:
|
|
49
|
+
body = json.loads(json_body)
|
|
50
|
+
else:
|
|
51
|
+
body = {}
|
|
52
|
+
if domain is not None:
|
|
53
|
+
body["domain"] = domain
|
|
54
|
+
if claim_domain is not None:
|
|
55
|
+
body["claimDomain"] = claim_domain
|
|
56
|
+
if reserve_domain is not None:
|
|
57
|
+
body["reserveDomain"] = reserve_domain
|
|
58
|
+
try:
|
|
59
|
+
result = api.session.rest_post(url, json=body)
|
|
60
|
+
except WebexError as e:
|
|
61
|
+
handle_rest_error(e)
|
|
62
|
+
print_json(result)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@app.command("claim-domain")
|
|
67
|
+
def claim_domain(
|
|
68
|
+
force_domain_claim: str = typer.Option(None, "--force-domain-claim", help="Indicate if the domain should be claimed when there are user"),
|
|
69
|
+
claim_domain_only: str = typer.Option(None, "--claim-domain-only", help="Indicate to just claim the domain only without searching/mar"),
|
|
70
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body"),
|
|
71
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
72
|
+
):
|
|
73
|
+
"""Claim Domain\n\nExample --json-body:\n '{"data":[{"domain":"..."}],"forceDomainClaim":true,"claimDomainOnly":true}'."""
|
|
74
|
+
api = get_api(debug=debug)
|
|
75
|
+
org_id = resolve_org_id(api.session)
|
|
76
|
+
url = f"https://webexapis.com/identity/organizations/{org_id}/actions/claimDomain"
|
|
77
|
+
if json_body:
|
|
78
|
+
body = json.loads(json_body)
|
|
79
|
+
else:
|
|
80
|
+
body = {}
|
|
81
|
+
if force_domain_claim is not None:
|
|
82
|
+
body["forceDomainClaim"] = force_domain_claim
|
|
83
|
+
if claim_domain_only is not None:
|
|
84
|
+
body["claimDomainOnly"] = claim_domain_only
|
|
85
|
+
try:
|
|
86
|
+
result = api.session.rest_post(url, json=body)
|
|
87
|
+
except WebexError as e:
|
|
88
|
+
handle_rest_error(e)
|
|
89
|
+
print_json(result)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@app.command("unverify-domain")
|
|
94
|
+
def unverify_domain(
|
|
95
|
+
domain: str = typer.Option(None, "--domain", help="Domain name to be verified."),
|
|
96
|
+
remove_pending: str = typer.Option(None, "--remove-pending", help="Specify whether to remove pending domain. Default is false ("),
|
|
97
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body"),
|
|
98
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
99
|
+
):
|
|
100
|
+
"""Unverify Domain."""
|
|
101
|
+
api = get_api(debug=debug)
|
|
102
|
+
org_id = resolve_org_id(api.session)
|
|
103
|
+
url = f"https://webexapis.com/identity/organizations/{org_id}/actions/unverifyDomain"
|
|
104
|
+
if json_body:
|
|
105
|
+
body = json.loads(json_body)
|
|
106
|
+
else:
|
|
107
|
+
body = {}
|
|
108
|
+
if domain is not None:
|
|
109
|
+
body["domain"] = domain
|
|
110
|
+
if remove_pending is not None:
|
|
111
|
+
body["removePending"] = remove_pending
|
|
112
|
+
try:
|
|
113
|
+
result = api.session.rest_post(url, json=body)
|
|
114
|
+
except WebexError as e:
|
|
115
|
+
handle_rest_error(e)
|
|
116
|
+
print_json(result)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@app.command("unclaim-domain")
|
|
121
|
+
def unclaim_domain(
|
|
122
|
+
domain: str = typer.Option(None, "--domain", help="A claimed domain."),
|
|
123
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body"),
|
|
124
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
125
|
+
):
|
|
126
|
+
"""Unclaim Domain."""
|
|
127
|
+
api = get_api(debug=debug)
|
|
128
|
+
org_id = resolve_org_id(api.session)
|
|
129
|
+
url = f"https://webexapis.com/identity/organizations/{org_id}/actions/unclaimDomain"
|
|
130
|
+
if json_body:
|
|
131
|
+
body = json.loads(json_body)
|
|
132
|
+
else:
|
|
133
|
+
body = {}
|
|
134
|
+
if domain is not None:
|
|
135
|
+
body["domain"] = domain
|
|
136
|
+
try:
|
|
137
|
+
result = api.session.rest_post(url, json=body)
|
|
138
|
+
except WebexError as e:
|
|
139
|
+
handle_rest_error(e)
|
|
140
|
+
print_json(result)
|
|
141
|
+
|
|
142
|
+
|
wxcli/commands/ecm.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import typer
|
|
3
|
+
from wxcli.auth import get_api
|
|
4
|
+
from wxcli.errors import WebexError, handle_rest_error
|
|
5
|
+
from wxcli.output import print_table, print_json
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(help="Manage Webex Calling ecm.")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.command("list")
|
|
12
|
+
def cmd_list(
|
|
13
|
+
room_id: str = typer.Option(..., "--room-id", help="ID of the room for which to list the ECM folder."),
|
|
14
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
|
|
15
|
+
limit: int = typer.Option(0, "--limit", help="Max results (0=all for paginated endpoints, API default for non-paginated)"),
|
|
16
|
+
offset: int = typer.Option(0, "--offset", help="Start offset"),
|
|
17
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
18
|
+
):
|
|
19
|
+
"""List ECM folder."""
|
|
20
|
+
api = get_api(debug=debug)
|
|
21
|
+
url = f"https://webexapis.com/v1/room/linkedFolders"
|
|
22
|
+
params = {}
|
|
23
|
+
if room_id is not None:
|
|
24
|
+
params["roomId"] = room_id
|
|
25
|
+
if limit > 0:
|
|
26
|
+
params["max"] = limit
|
|
27
|
+
if offset > 0:
|
|
28
|
+
params["start"] = offset
|
|
29
|
+
try:
|
|
30
|
+
if limit > 0:
|
|
31
|
+
result = api.session.rest_get(url, params=params)
|
|
32
|
+
result = result or {}
|
|
33
|
+
items = result.get("items", result.get("data", result if isinstance(result, list) else [])) if isinstance(result, dict) else (result if isinstance(result, list) else [])
|
|
34
|
+
else:
|
|
35
|
+
if "max" not in params:
|
|
36
|
+
params["max"] = 1000
|
|
37
|
+
items = list(api.session.follow_pagination(url=url, params=params, item_key="items"))
|
|
38
|
+
except WebexError as e:
|
|
39
|
+
handle_rest_error(e)
|
|
40
|
+
if output == "json":
|
|
41
|
+
print_json(items)
|
|
42
|
+
else:
|
|
43
|
+
print_table(items, columns=[("ID", "id"), ("Name", "name")], limit=limit)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.command("create")
|
|
48
|
+
def create(
|
|
49
|
+
room_id: str = typer.Option(None, "--room-id", help="(required) A unique identifier for the room."),
|
|
50
|
+
content_url: str = typer.Option(None, "--content-url", help="(required) URL of the ECM folder."),
|
|
51
|
+
display_name: str = typer.Option(None, "--display-name", help="(required) This should match the folder name in the ECM backend."),
|
|
52
|
+
drive_id: str = typer.Option(None, "--drive-id", help="(required) Sharepoint or OneDrive drive id. It can be queried via MS Gr"),
|
|
53
|
+
item_id: str = typer.Option(None, "--item-id", help="(required) Sharepoint or OneDrive item id. It can be queried via MS Gra"),
|
|
54
|
+
default_folder: str = typer.Option(None, "--default-folder", help="(required) Makes the folder the default storage for the space."),
|
|
55
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
|
|
56
|
+
output: str = typer.Option("id", "--output", "-o", help="Output format: id|json"),
|
|
57
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
58
|
+
):
|
|
59
|
+
"""Create an ECM folder configuration."""
|
|
60
|
+
api = get_api(debug=debug)
|
|
61
|
+
url = f"https://webexapis.com/v1/room/linkedFolders"
|
|
62
|
+
if json_body:
|
|
63
|
+
body = json.loads(json_body)
|
|
64
|
+
else:
|
|
65
|
+
body = {}
|
|
66
|
+
if room_id is not None:
|
|
67
|
+
body["roomId"] = room_id
|
|
68
|
+
if content_url is not None:
|
|
69
|
+
body["contentUrl"] = content_url
|
|
70
|
+
if display_name is not None:
|
|
71
|
+
body["displayName"] = display_name
|
|
72
|
+
if drive_id is not None:
|
|
73
|
+
body["driveId"] = drive_id
|
|
74
|
+
if item_id is not None:
|
|
75
|
+
body["itemId"] = item_id
|
|
76
|
+
if default_folder is not None:
|
|
77
|
+
body["defaultFolder"] = default_folder
|
|
78
|
+
_missing = [f for f in ['roomId', 'contentUrl', 'displayName', 'driveId', 'itemId', 'defaultFolder'] if f not in body or body[f] is None]
|
|
79
|
+
if _missing:
|
|
80
|
+
typer.echo("Error: Missing required fields: " + ", ".join(_missing), err=True)
|
|
81
|
+
raise typer.Exit(1)
|
|
82
|
+
try:
|
|
83
|
+
result = api.session.rest_post(url, json=body)
|
|
84
|
+
except WebexError as e:
|
|
85
|
+
handle_rest_error(e)
|
|
86
|
+
if output == "json":
|
|
87
|
+
print_json(result)
|
|
88
|
+
elif isinstance(result, dict) and "id" in result:
|
|
89
|
+
typer.echo(f"Created: {result['id']}")
|
|
90
|
+
elif not result or result == {}:
|
|
91
|
+
typer.echo("Created.")
|
|
92
|
+
else:
|
|
93
|
+
print_json(result)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.command("show")
|
|
98
|
+
def show(
|
|
99
|
+
id: str = typer.Argument(help="id"),
|
|
100
|
+
output: str = typer.Option("json", "--output", "-o", help="Output format: table|json"),
|
|
101
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
102
|
+
):
|
|
103
|
+
"""Get ECM Folder Details."""
|
|
104
|
+
api = get_api(debug=debug)
|
|
105
|
+
url = f"https://webexapis.com/v1/room/linkedFolders/{id}"
|
|
106
|
+
try:
|
|
107
|
+
result = api.session.rest_get(url)
|
|
108
|
+
except WebexError as e:
|
|
109
|
+
handle_rest_error(e)
|
|
110
|
+
if output == "json":
|
|
111
|
+
print_json(result)
|
|
112
|
+
else:
|
|
113
|
+
if isinstance(result, dict):
|
|
114
|
+
print_table([result], columns=[("Key", ""), ("Value", "")], limit=0)
|
|
115
|
+
elif isinstance(result, list):
|
|
116
|
+
print_table(result, columns=[("ID", "id"), ("Name", "name")], limit=0)
|
|
117
|
+
else:
|
|
118
|
+
print_json(result)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@app.command("update")
|
|
123
|
+
def update(
|
|
124
|
+
id: str = typer.Argument(help="id"),
|
|
125
|
+
room_id: str = typer.Option(None, "--room-id", help="ID of the room that contains the room tab in question."),
|
|
126
|
+
content_url: str = typer.Option(None, "--content-url", help="Content URL of the folder."),
|
|
127
|
+
display_name: str = typer.Option(None, "--display-name", help="This should match the folder name in the ECM backend."),
|
|
128
|
+
drive_id: str = typer.Option(None, "--drive-id", help="Sharepoint or OneDrive drive id. It can be queried via MS Gr"),
|
|
129
|
+
item_id: str = typer.Option(None, "--item-id", help="Sharepoint or OneDrive item id. It can be queried via MS Gra"),
|
|
130
|
+
default_folder: str = typer.Option(None, "--default-folder", help="Makes the folder the default storage for the space."),
|
|
131
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
|
|
132
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
133
|
+
):
|
|
134
|
+
"""Update an ECM Linked Folder."""
|
|
135
|
+
api = get_api(debug=debug)
|
|
136
|
+
url = f"https://webexapis.com/v1/room/linkedFolders/{id}"
|
|
137
|
+
if json_body:
|
|
138
|
+
body = json.loads(json_body)
|
|
139
|
+
else:
|
|
140
|
+
body = {}
|
|
141
|
+
if room_id is not None:
|
|
142
|
+
body["roomId"] = room_id
|
|
143
|
+
if content_url is not None:
|
|
144
|
+
body["contentUrl"] = content_url
|
|
145
|
+
if display_name is not None:
|
|
146
|
+
body["displayName"] = display_name
|
|
147
|
+
if drive_id is not None:
|
|
148
|
+
body["driveId"] = drive_id
|
|
149
|
+
if item_id is not None:
|
|
150
|
+
body["itemId"] = item_id
|
|
151
|
+
if default_folder is not None:
|
|
152
|
+
body["defaultFolder"] = default_folder
|
|
153
|
+
try:
|
|
154
|
+
result = api.session.rest_put(url, json=body)
|
|
155
|
+
except WebexError as e:
|
|
156
|
+
handle_rest_error(e)
|
|
157
|
+
typer.echo(f"Updated.")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@app.command("delete")
|
|
162
|
+
def delete(
|
|
163
|
+
id: str = typer.Argument(help="id"),
|
|
164
|
+
force: bool = typer.Option(False, "--force", help="Skip confirmation"),
|
|
165
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
166
|
+
):
|
|
167
|
+
"""Unlink an ECM linked folder."""
|
|
168
|
+
if not force:
|
|
169
|
+
typer.confirm(f"Delete {id}?", abort=True)
|
|
170
|
+
api = get_api(debug=debug)
|
|
171
|
+
url = f"https://webexapis.com/v1/room/linkedFolders/{id}"
|
|
172
|
+
try:
|
|
173
|
+
api.session.rest_delete(url)
|
|
174
|
+
except WebexError as e:
|
|
175
|
+
handle_rest_error(e)
|
|
176
|
+
typer.echo(f"Deleted: {id}")
|
|
177
|
+
|
|
178
|
+
|