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,67 @@
|
|
|
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 hybrid-connectors.")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command("list")
|
|
13
|
+
def cmd_list(
|
|
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 Hybrid Connectors."""
|
|
20
|
+
api = get_api(debug=debug)
|
|
21
|
+
url = f"https://webexapis.com/v1/hybrid/connectors"
|
|
22
|
+
params = {}
|
|
23
|
+
if limit > 0:
|
|
24
|
+
params["max"] = limit
|
|
25
|
+
if offset > 0:
|
|
26
|
+
params["start"] = offset
|
|
27
|
+
org_id = get_org_id()
|
|
28
|
+
if org_id is not None:
|
|
29
|
+
params["orgId"] = org_id
|
|
30
|
+
result = None
|
|
31
|
+
try:
|
|
32
|
+
result = api.session.rest_get(url, params=params)
|
|
33
|
+
except WebexError as e:
|
|
34
|
+
handle_rest_error(e)
|
|
35
|
+
result = result or []
|
|
36
|
+
items = result.get("items", result.get("data", result if isinstance(result, list) else [])) if isinstance(result, dict) else (result if isinstance(result, list) else [])
|
|
37
|
+
if output == "json":
|
|
38
|
+
print_json(items)
|
|
39
|
+
else:
|
|
40
|
+
print_table(items, columns=[("ID", "id"), ("Name", "name")], limit=limit)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@app.command("show")
|
|
45
|
+
def show(
|
|
46
|
+
connector_id: str = typer.Argument(help="connectorId"),
|
|
47
|
+
output: str = typer.Option("json", "--output", "-o", help="Output format: table|json"),
|
|
48
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
49
|
+
):
|
|
50
|
+
"""Get Hybrid Connector Details."""
|
|
51
|
+
api = get_api(debug=debug)
|
|
52
|
+
url = f"https://webexapis.com/v1/hybrid/connectors/{connector_id}"
|
|
53
|
+
try:
|
|
54
|
+
result = api.session.rest_get(url)
|
|
55
|
+
except WebexError as e:
|
|
56
|
+
handle_rest_error(e)
|
|
57
|
+
if output == "json":
|
|
58
|
+
print_json(result)
|
|
59
|
+
else:
|
|
60
|
+
if isinstance(result, dict):
|
|
61
|
+
print_table([result], columns=[("Key", ""), ("Value", "")], limit=0)
|
|
62
|
+
elif isinstance(result, list):
|
|
63
|
+
print_table(result, columns=[("ID", "id"), ("Name", "name")], limit=0)
|
|
64
|
+
else:
|
|
65
|
+
print_json(result)
|
|
66
|
+
|
|
67
|
+
|
|
@@ -0,0 +1,159 @@
|
|
|
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 identity-org.")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command("show")
|
|
13
|
+
def show(
|
|
14
|
+
output: str = typer.Option("json", "--output", "-o", help="Output format: table|json"),
|
|
15
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
16
|
+
):
|
|
17
|
+
"""Get an organization."""
|
|
18
|
+
api = get_api(debug=debug)
|
|
19
|
+
org_id = resolve_org_id(api.session)
|
|
20
|
+
url = f"https://webexapis.com/identity/organizations/{org_id}"
|
|
21
|
+
try:
|
|
22
|
+
result = api.session.rest_get(url)
|
|
23
|
+
except WebexError as e:
|
|
24
|
+
handle_rest_error(e)
|
|
25
|
+
if output == "json":
|
|
26
|
+
print_json(result)
|
|
27
|
+
else:
|
|
28
|
+
if isinstance(result, dict):
|
|
29
|
+
print_table([result], columns=[("Key", ""), ("Value", "")], limit=0)
|
|
30
|
+
elif isinstance(result, list):
|
|
31
|
+
print_table(result, columns=[("ID", "id"), ("Name", "name")], limit=0)
|
|
32
|
+
else:
|
|
33
|
+
print_json(result)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.command("update")
|
|
38
|
+
def update(
|
|
39
|
+
display_name: str = typer.Option(None, "--display-name", help="New full name of the organization."),
|
|
40
|
+
preferred_language: str = typer.Option(None, "--preferred-language", help="It is the default preferredLanguage for user creation in thi"),
|
|
41
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
|
|
42
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
43
|
+
):
|
|
44
|
+
"""Update an organization\n\nExample --json-body:\n '{"schemas":["..."],"displayName":"...","preferredLanguage":"..."}'."""
|
|
45
|
+
api = get_api(debug=debug)
|
|
46
|
+
org_id = resolve_org_id(api.session)
|
|
47
|
+
url = f"https://webexapis.com/identity/organizations/{org_id}"
|
|
48
|
+
if json_body:
|
|
49
|
+
body = json.loads(json_body)
|
|
50
|
+
else:
|
|
51
|
+
body = {}
|
|
52
|
+
if display_name is not None:
|
|
53
|
+
body["displayName"] = display_name
|
|
54
|
+
if preferred_language is not None:
|
|
55
|
+
body["preferredLanguage"] = preferred_language
|
|
56
|
+
try:
|
|
57
|
+
result = api.session.rest_patch(url, json=body)
|
|
58
|
+
except WebexError as e:
|
|
59
|
+
handle_rest_error(e)
|
|
60
|
+
typer.echo(f"Updated.")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@app.command("update-authentication-config")
|
|
65
|
+
def update_authentication_config(
|
|
66
|
+
remember_my_login_id: bool = typer.Option(None, "--remember-my-login-id/--no-remember-my-login-id", help="Login Id set to true if it should be remembered."),
|
|
67
|
+
remember_my_login_id_duration: str = typer.Option(None, "--remember-my-login-id-duration", help="Specifies the number of days the user's login ID is remember"),
|
|
68
|
+
mfa_enabled: bool = typer.Option(None, "--mfa-enabled/--no-mfa-enabled", help="Enable/ Disable multi-factor authentication on an organizati"),
|
|
69
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
|
|
70
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
71
|
+
):
|
|
72
|
+
"""Update Organization Authentication Configuration Settings\n\nExample --json-body:\n '{"schemas":["..."],"RememberMyLoginId":true,"RememberMyLoginIdDuration":0,"mfaEnabled":true}'."""
|
|
73
|
+
api = get_api(debug=debug)
|
|
74
|
+
org_id = resolve_org_id(api.session)
|
|
75
|
+
url = f"https://webexapis.com/identity/organizations/{org_id}/authenticationConfig"
|
|
76
|
+
if json_body:
|
|
77
|
+
body = json.loads(json_body)
|
|
78
|
+
else:
|
|
79
|
+
body = {}
|
|
80
|
+
if remember_my_login_id is not None:
|
|
81
|
+
body["RememberMyLoginId"] = remember_my_login_id
|
|
82
|
+
if remember_my_login_id_duration is not None:
|
|
83
|
+
body["RememberMyLoginIdDuration"] = remember_my_login_id_duration
|
|
84
|
+
if mfa_enabled is not None:
|
|
85
|
+
body["mfaEnabled"] = mfa_enabled
|
|
86
|
+
try:
|
|
87
|
+
result = api.session.rest_patch(url, json=body)
|
|
88
|
+
except WebexError as e:
|
|
89
|
+
handle_rest_error(e)
|
|
90
|
+
typer.echo(f"Updated.")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@app.command("update-password-policy")
|
|
95
|
+
def update_password_policy(
|
|
96
|
+
minimum_numeric: str = typer.Option(None, "--minimum-numeric", help="Minimum number of numeric characters in password"),
|
|
97
|
+
minimum_cap_alpha: str = typer.Option(None, "--minimum-cap-alpha", help="Minimum number of uppercase alphabetic character letters in"),
|
|
98
|
+
minimum_low_alpha: str = typer.Option(None, "--minimum-low-alpha", help="Minimum number of lowercase alphabetic character letters in"),
|
|
99
|
+
minimum_special: str = typer.Option(None, "--minimum-special", help="Minimum number of special characters included \"~!@#$%^&*()-_"),
|
|
100
|
+
minimum_length: str = typer.Option(None, "--minimum-length", help="Minimum length of password. Must be between 8 and 256, inclu"),
|
|
101
|
+
history_count: str = typer.Option(None, "--history-count", help="The number of former passwords in history, the new password"),
|
|
102
|
+
max_password_age: str = typer.Option(None, "--max-password-age", help="The password expired time, unit: day, that means user need t"),
|
|
103
|
+
not_acceptable_strings: str = typer.Option(None, "--not-acceptable-strings", help="The password can not be any one in this string list."),
|
|
104
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
|
|
105
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
106
|
+
):
|
|
107
|
+
"""Update Organization Password Policy\n\nExample --json-body:\n '{"schemas":["..."],"minimumNumeric":"...","minimumCapAlpha":"...","minimumLowAlpha":"...","minimumSpecial":"...","minimumLength":"...","historyCount":"...","maxPasswordAge":"..."}'."""
|
|
108
|
+
api = get_api(debug=debug)
|
|
109
|
+
org_id = resolve_org_id(api.session)
|
|
110
|
+
url = f"https://webexapis.com/identity/organizations/{org_id}/passwordPolicy"
|
|
111
|
+
if json_body:
|
|
112
|
+
body = json.loads(json_body)
|
|
113
|
+
else:
|
|
114
|
+
body = {}
|
|
115
|
+
if minimum_numeric is not None:
|
|
116
|
+
body["minimumNumeric"] = minimum_numeric
|
|
117
|
+
if minimum_cap_alpha is not None:
|
|
118
|
+
body["minimumCapAlpha"] = minimum_cap_alpha
|
|
119
|
+
if minimum_low_alpha is not None:
|
|
120
|
+
body["minimumLowAlpha"] = minimum_low_alpha
|
|
121
|
+
if minimum_special is not None:
|
|
122
|
+
body["minimumSpecial"] = minimum_special
|
|
123
|
+
if minimum_length is not None:
|
|
124
|
+
body["minimumLength"] = minimum_length
|
|
125
|
+
if history_count is not None:
|
|
126
|
+
body["historyCount"] = history_count
|
|
127
|
+
if max_password_age is not None:
|
|
128
|
+
body["maxPasswordAge"] = max_password_age
|
|
129
|
+
if not_acceptable_strings is not None:
|
|
130
|
+
body["notAcceptableStrings"] = not_acceptable_strings
|
|
131
|
+
try:
|
|
132
|
+
result = api.session.rest_patch(url, json=body)
|
|
133
|
+
except WebexError as e:
|
|
134
|
+
handle_rest_error(e)
|
|
135
|
+
typer.echo(f"Updated.")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@app.command("generate-otp")
|
|
140
|
+
def generate_otp(
|
|
141
|
+
user_id: str = typer.Argument(help="userId"),
|
|
142
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body"),
|
|
143
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
144
|
+
):
|
|
145
|
+
"""Generate OTP."""
|
|
146
|
+
api = get_api(debug=debug)
|
|
147
|
+
org_id = resolve_org_id(api.session)
|
|
148
|
+
url = f"https://webexapis.com/identity/organizations/{org_id}/users/{user_id}/actions/generateOtp"
|
|
149
|
+
if json_body:
|
|
150
|
+
body = json.loads(json_body)
|
|
151
|
+
else:
|
|
152
|
+
body = {}
|
|
153
|
+
try:
|
|
154
|
+
result = api.session.rest_post(url, json=body)
|
|
155
|
+
except WebexError as e:
|
|
156
|
+
handle_rest_error(e)
|
|
157
|
+
print_json(result)
|
|
158
|
+
|
|
159
|
+
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""wxcli init — materialize the bundled Claude Code playbook into a local folder.
|
|
2
|
+
|
|
3
|
+
The folder layout mirrors the repo (CLAUDE.md, .claude/, docs/reference/), so
|
|
4
|
+
Claude Code loads everything natively and no path is rewritten (spec §4.3).
|
|
5
|
+
A manifest records every written file; refresh and --uninstall touch ONLY
|
|
6
|
+
manifest-owned paths — user files are never clobbered.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
import wxcli
|
|
17
|
+
from wxcli import __version__
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(help="Materialize the bundled Claude Code playbook into a local project folder.")
|
|
20
|
+
|
|
21
|
+
MANIFEST_REL = Path(".claude") / ".wxops-manifest.json"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def bundle_root() -> Path:
|
|
25
|
+
"""The playbook shipped inside the installed package (test-injectable)."""
|
|
26
|
+
return Path(wxcli.__file__).resolve().parent / "_playbook"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def read_bundle(bundle: Path) -> dict[str, bytes]:
|
|
30
|
+
"""posix relpath -> content for every file in the installed bundle."""
|
|
31
|
+
return {
|
|
32
|
+
p.relative_to(bundle).as_posix(): p.read_bytes()
|
|
33
|
+
for p in sorted(bundle.rglob("*")) if p.is_file()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def sha256_hex(data: bytes) -> str:
|
|
38
|
+
return hashlib.sha256(data).hexdigest()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def load_manifest(folder: Path) -> dict | None:
|
|
42
|
+
path = folder / MANIFEST_REL
|
|
43
|
+
return json.loads(path.read_text()) if path.exists() else None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def save_manifest(folder: Path, hashes: dict[str, str]) -> None:
|
|
47
|
+
path = folder / MANIFEST_REL
|
|
48
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
path.write_text(json.dumps(
|
|
50
|
+
{"wxops_playbook": True, "version": __version__, "files": hashes}, indent=2
|
|
51
|
+
))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _prune_empty_dirs(start: Path, stop: Path) -> None:
|
|
55
|
+
d = start
|
|
56
|
+
while d != stop and d.is_dir():
|
|
57
|
+
try:
|
|
58
|
+
d.rmdir() # fails (OSError) if non-empty → stop pruning
|
|
59
|
+
except OSError:
|
|
60
|
+
return
|
|
61
|
+
d = d.parent
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def apply_bundle(
|
|
65
|
+
folder: Path, bundle_files: dict[str, bytes], owned: dict[str, str]
|
|
66
|
+
) -> tuple[list[str], list[str], dict[str, str]]:
|
|
67
|
+
"""Write/refresh the bundle. Returns (written, deleted, new_hashes).
|
|
68
|
+
|
|
69
|
+
Deletes manifest-owned paths absent from the new bundle (retired skills
|
|
70
|
+
don't linger); never touches paths it doesn't own (spec §4.3).
|
|
71
|
+
"""
|
|
72
|
+
written: list[str] = []
|
|
73
|
+
hashes: dict[str, str] = {}
|
|
74
|
+
for rel, content in bundle_files.items():
|
|
75
|
+
dest = folder / rel
|
|
76
|
+
if not dest.exists() or dest.read_bytes() != content:
|
|
77
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
dest.write_bytes(content)
|
|
79
|
+
written.append(rel)
|
|
80
|
+
hashes[rel] = sha256_hex(content)
|
|
81
|
+
deleted: list[str] = []
|
|
82
|
+
for rel in sorted(set(owned) - set(bundle_files)):
|
|
83
|
+
target = folder / rel
|
|
84
|
+
if target.exists():
|
|
85
|
+
target.unlink()
|
|
86
|
+
deleted.append(rel)
|
|
87
|
+
_prune_empty_dirs(target.parent, folder)
|
|
88
|
+
return written, deleted, hashes
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def do_uninstall(folder: Path) -> None:
|
|
92
|
+
manifest = load_manifest(folder)
|
|
93
|
+
if manifest is None:
|
|
94
|
+
typer.echo(f"No wxops manifest in {folder} — nothing to uninstall.", err=True)
|
|
95
|
+
raise typer.Exit(1)
|
|
96
|
+
for rel in sorted(manifest.get("files", {})):
|
|
97
|
+
target = folder / rel
|
|
98
|
+
if target.exists():
|
|
99
|
+
target.unlink()
|
|
100
|
+
_prune_empty_dirs(target.parent, folder)
|
|
101
|
+
(folder / MANIFEST_REL).unlink(missing_ok=True)
|
|
102
|
+
_prune_empty_dirs((folder / MANIFEST_REL).parent, folder)
|
|
103
|
+
typer.echo(f"Removed the wxops playbook from {folder}. Files you added were left in place.")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# A single @app.command() with no callback: Typer collapses the app into this
|
|
107
|
+
# command, so `wxcli init FOLDER --flags` parses positionals and options together.
|
|
108
|
+
# (A group callback would treat post-positional options as subcommand names.)
|
|
109
|
+
@app.command()
|
|
110
|
+
def init(
|
|
111
|
+
folder: Path = typer.Argument(Path("wxcli-playbook"), help="Target playbook folder."),
|
|
112
|
+
force: bool = typer.Option(False, "--force", help="Overwrite collisions / refresh without prompting."),
|
|
113
|
+
yes: bool = typer.Option(False, "--yes", help="Skip the confirmation prompt."),
|
|
114
|
+
uninstall: bool = typer.Option(False, "--uninstall", help="Remove every manifest-owned file, then the manifest."),
|
|
115
|
+
):
|
|
116
|
+
"""Materialize the bundled playbook (CLAUDE.md, .claude/, docs/reference/) into FOLDER."""
|
|
117
|
+
folder = folder.resolve()
|
|
118
|
+
if uninstall:
|
|
119
|
+
do_uninstall(folder)
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
bundle = bundle_root()
|
|
123
|
+
if not bundle.is_dir():
|
|
124
|
+
typer.echo("Bundled playbook not found in this wxcli install.", err=True)
|
|
125
|
+
raise typer.Exit(1)
|
|
126
|
+
bundle_files = read_bundle(bundle)
|
|
127
|
+
manifest = load_manifest(folder)
|
|
128
|
+
|
|
129
|
+
if manifest is None:
|
|
130
|
+
collisions = sorted(rel for rel in bundle_files if (folder / rel).exists())
|
|
131
|
+
if collisions and not force:
|
|
132
|
+
typer.echo(f"{folder} already contains files the playbook would overwrite:", err=True)
|
|
133
|
+
for rel in collisions:
|
|
134
|
+
typer.echo(f" {rel}", err=True)
|
|
135
|
+
typer.echo("Re-run with --force to overwrite them, or choose an empty folder.", err=True)
|
|
136
|
+
raise typer.Exit(1)
|
|
137
|
+
owned: dict[str, str] = {}
|
|
138
|
+
else:
|
|
139
|
+
owned = manifest.get("files", {})
|
|
140
|
+
if not (force or yes) and not typer.confirm(
|
|
141
|
+
f"Refresh the wxops playbook in {folder} (owned files only)?"
|
|
142
|
+
):
|
|
143
|
+
raise typer.Exit(0)
|
|
144
|
+
|
|
145
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
146
|
+
written, deleted, hashes = apply_bundle(folder, bundle_files, owned)
|
|
147
|
+
save_manifest(folder, hashes)
|
|
148
|
+
typer.echo(
|
|
149
|
+
f"Playbook v{__version__}: {len(written)} file(s) written, "
|
|
150
|
+
f"{len(deleted)} removed, in {folder}"
|
|
151
|
+
)
|
|
152
|
+
typer.echo(
|
|
153
|
+
"Open this folder in Claude Code (start a fresh session) — "
|
|
154
|
+
"skills and agents load at session start."
|
|
155
|
+
)
|
|
@@ -0,0 +1,106 @@
|
|
|
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 licenses.")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command("list")
|
|
13
|
+
def cmd_list(
|
|
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 Licenses."""
|
|
20
|
+
api = get_api(debug=debug)
|
|
21
|
+
url = f"https://webexapis.com/v1/licenses"
|
|
22
|
+
params = {}
|
|
23
|
+
if limit > 0:
|
|
24
|
+
params["max"] = limit
|
|
25
|
+
if offset > 0:
|
|
26
|
+
params["start"] = offset
|
|
27
|
+
org_id = get_org_id()
|
|
28
|
+
if org_id is not None:
|
|
29
|
+
params["orgId"] = org_id
|
|
30
|
+
result = None
|
|
31
|
+
try:
|
|
32
|
+
result = api.session.rest_get(url, params=params)
|
|
33
|
+
except WebexError as e:
|
|
34
|
+
handle_rest_error(e)
|
|
35
|
+
result = result or []
|
|
36
|
+
items = result.get("items", result.get("data", result if isinstance(result, list) else [])) if isinstance(result, dict) else (result if isinstance(result, list) else [])
|
|
37
|
+
if output == "json":
|
|
38
|
+
print_json(items)
|
|
39
|
+
else:
|
|
40
|
+
print_table(items, columns=[('ID', 'id'), ('Name', 'name'), ('Total Units', 'totalUnits'), ('Consumed', 'consumedUnits')], limit=limit)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@app.command("show")
|
|
45
|
+
def show(
|
|
46
|
+
license_id: str = typer.Argument(help="licenseId"),
|
|
47
|
+
include_assigned_to: str = typer.Option(None, "--include-assigned-to", help="Choices: user"),
|
|
48
|
+
next: str = typer.Option(None, "--next", help="List the next set of users. Applicable only if `includeAssig"),
|
|
49
|
+
limit: str = typer.Option(None, "--limit", help="A limit on the number of users to be returned in the respons"),
|
|
50
|
+
output: str = typer.Option("json", "--output", "-o", help="Output format: table|json"),
|
|
51
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
52
|
+
):
|
|
53
|
+
"""Get License Details."""
|
|
54
|
+
api = get_api(debug=debug)
|
|
55
|
+
url = f"https://webexapis.com/v1/licenses/{license_id}"
|
|
56
|
+
params = {}
|
|
57
|
+
if include_assigned_to is not None:
|
|
58
|
+
params["includeAssignedTo"] = include_assigned_to
|
|
59
|
+
if next is not None:
|
|
60
|
+
params["next"] = next
|
|
61
|
+
if limit is not None:
|
|
62
|
+
params["limit"] = limit
|
|
63
|
+
try:
|
|
64
|
+
result = api.session.rest_get(url, params=params)
|
|
65
|
+
except WebexError as e:
|
|
66
|
+
handle_rest_error(e)
|
|
67
|
+
if output == "json":
|
|
68
|
+
print_json(result)
|
|
69
|
+
else:
|
|
70
|
+
if isinstance(result, dict):
|
|
71
|
+
print_table([result], columns=[("Key", ""), ("Value", "")], limit=0)
|
|
72
|
+
elif isinstance(result, list):
|
|
73
|
+
print_table(result, columns=[("ID", "id"), ("Name", "name")], limit=0)
|
|
74
|
+
else:
|
|
75
|
+
print_json(result)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.command("update")
|
|
80
|
+
def update(
|
|
81
|
+
email: str = typer.Option(None, "--email", help="Email address of the user."),
|
|
82
|
+
person_id: str = typer.Option(None, "--person-id", help="A unique identifier for the user."),
|
|
83
|
+
org_id: str = typer.Option(None, "--org-id", help="The ID of the organization to which the licenses and siteUrl"),
|
|
84
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
|
|
85
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
86
|
+
):
|
|
87
|
+
"""Assign Licenses to Users\n\nExample --json-body:\n '{"email":"...","personId":"...","orgId":"...","licenses":[{"id":"...","operation":"...","properties":"..."}],"siteUrls":[{"siteUrl":"...","accountType":"...","operation":"..."}]}'."""
|
|
88
|
+
api = get_api(debug=debug)
|
|
89
|
+
url = f"https://webexapis.com/v1/licenses/users"
|
|
90
|
+
if json_body:
|
|
91
|
+
body = json.loads(json_body)
|
|
92
|
+
else:
|
|
93
|
+
body = {}
|
|
94
|
+
if email is not None:
|
|
95
|
+
body["email"] = email
|
|
96
|
+
if person_id is not None:
|
|
97
|
+
body["personId"] = person_id
|
|
98
|
+
if org_id is not None:
|
|
99
|
+
body["orgId"] = org_id
|
|
100
|
+
try:
|
|
101
|
+
result = api.session.rest_patch(url, json=body)
|
|
102
|
+
except WebexError as e:
|
|
103
|
+
handle_rest_error(e)
|
|
104
|
+
typer.echo(f"Updated.")
|
|
105
|
+
|
|
106
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
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 live-monitoring.")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.command("create")
|
|
12
|
+
def create(
|
|
13
|
+
site_url: str = typer.Option(None, "--site-url", help="A site URL."),
|
|
14
|
+
json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
|
|
15
|
+
output: str = typer.Option("id", "--output", "-o", help="Output format: id|json"),
|
|
16
|
+
debug: bool = typer.Option(False, "--debug"),
|
|
17
|
+
):
|
|
18
|
+
"""Get Live Meeting metrics categorized by Country\n\nExample --json-body:\n '{"siteIds":[0],"siteUrl":"..."}'."""
|
|
19
|
+
api = get_api(debug=debug)
|
|
20
|
+
url = f"https://webexapis.com/v1/livemonitoring/liveMeetingsByCountry"
|
|
21
|
+
if json_body:
|
|
22
|
+
body = json.loads(json_body)
|
|
23
|
+
else:
|
|
24
|
+
body = {}
|
|
25
|
+
if site_url is not None:
|
|
26
|
+
body["siteUrl"] = site_url
|
|
27
|
+
try:
|
|
28
|
+
result = api.session.rest_post(url, json=body)
|
|
29
|
+
except WebexError as e:
|
|
30
|
+
handle_rest_error(e)
|
|
31
|
+
if output == "json":
|
|
32
|
+
print_json(result)
|
|
33
|
+
elif isinstance(result, dict) and "id" in result:
|
|
34
|
+
typer.echo(f"Created: {result['id']}")
|
|
35
|
+
elif not result or result == {}:
|
|
36
|
+
typer.echo("Created.")
|
|
37
|
+
else:
|
|
38
|
+
print_json(result)
|
|
39
|
+
|
|
40
|
+
|