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.
Files changed (413) hide show
  1. wxcli/__init__.py +4 -0
  2. wxcli/_playbook/.claude/agents/migration-advisor.md +239 -0
  3. wxcli/_playbook/.claude/agents/wxc-calling-builder.md +946 -0
  4. wxcli/_playbook/.claude/rules/cleanup.md +29 -0
  5. wxcli/_playbook/.claude/rules/cucm-migration.md +58 -0
  6. wxcli/_playbook/.claude/rules/org-health.md +14 -0
  7. wxcli/_playbook/.claude/settings.json +8 -0
  8. wxcli/_playbook/.claude/skills/audit-compliance/SKILL.md +515 -0
  9. wxcli/_playbook/.claude/skills/call-control/SKILL.md +747 -0
  10. wxcli/_playbook/.claude/skills/configure-features/SKILL.md +623 -0
  11. wxcli/_playbook/.claude/skills/configure-routing/SKILL.md +627 -0
  12. wxcli/_playbook/.claude/skills/contact-center/SKILL.md +1181 -0
  13. wxcli/_playbook/.claude/skills/cucm-migrate/SKILL.md +820 -0
  14. wxcli/_playbook/.claude/skills/customer-assist/SKILL.md +443 -0
  15. wxcli/_playbook/.claude/skills/device-platform/SKILL.md +407 -0
  16. wxcli/_playbook/.claude/skills/manage-call-settings/SKILL.md +757 -0
  17. wxcli/_playbook/.claude/skills/manage-devices/SKILL.md +790 -0
  18. wxcli/_playbook/.claude/skills/manage-identity/SKILL.md +638 -0
  19. wxcli/_playbook/.claude/skills/manage-licensing/SKILL.md +528 -0
  20. wxcli/_playbook/.claude/skills/manage-meetings/SKILL.md +708 -0
  21. wxcli/_playbook/.claude/skills/messaging-bots/SKILL.md +365 -0
  22. wxcli/_playbook/.claude/skills/messaging-spaces/SKILL.md +520 -0
  23. wxcli/_playbook/.claude/skills/org-health/SKILL.md +159 -0
  24. wxcli/_playbook/.claude/skills/provision-calling/SKILL.md +421 -0
  25. wxcli/_playbook/.claude/skills/query-live/SKILL.md +183 -0
  26. wxcli/_playbook/.claude/skills/query-live/domains/call-flow-trace.md +223 -0
  27. wxcli/_playbook/.claude/skills/query-live/domains/call-history.md +44 -0
  28. wxcli/_playbook/.claude/skills/query-live/domains/features.md +169 -0
  29. wxcli/_playbook/.claude/skills/query-live/domains/numbers.md +93 -0
  30. wxcli/_playbook/.claude/skills/query-live/domains/people-and-settings.md +146 -0
  31. wxcli/_playbook/.claude/skills/query-live/domains/routing.md +124 -0
  32. wxcli/_playbook/.claude/skills/reporting/SKILL.md +501 -0
  33. wxcli/_playbook/.claude/skills/reporting/references/cdr-recipes.md +1628 -0
  34. wxcli/_playbook/.claude/skills/reporting-cc/SKILL.md +251 -0
  35. wxcli/_playbook/.claude/skills/reporting-meetings/SKILL.md +290 -0
  36. wxcli/_playbook/.claude/skills/teardown/SKILL.md +214 -0
  37. wxcli/_playbook/.claude/skills/video-mesh/SKILL.md +379 -0
  38. wxcli/_playbook/.claude/skills/wxc-calling-debug/SKILL.md +475 -0
  39. wxcli/_playbook/CLAUDE.md +373 -0
  40. wxcli/_playbook/docs/reference/CLAUDE.md +25 -0
  41. wxcli/_playbook/docs/reference/admin-apps-data.md +643 -0
  42. wxcli/_playbook/docs/reference/admin-audit-security.md +322 -0
  43. wxcli/_playbook/docs/reference/admin-hybrid.md +383 -0
  44. wxcli/_playbook/docs/reference/admin-identity-scim.md +1007 -0
  45. wxcli/_playbook/docs/reference/admin-licensing.md +414 -0
  46. wxcli/_playbook/docs/reference/admin-org-management.md +586 -0
  47. wxcli/_playbook/docs/reference/admin-partner.md +357 -0
  48. wxcli/_playbook/docs/reference/archive/wxc-sdk-patterns.md +1229 -0
  49. wxcli/_playbook/docs/reference/archive/wxcadm-advanced.md +835 -0
  50. wxcli/_playbook/docs/reference/archive/wxcadm-core.md +743 -0
  51. wxcli/_playbook/docs/reference/archive/wxcadm-devices-workspaces.md +1305 -0
  52. wxcli/_playbook/docs/reference/archive/wxcadm-features.md +849 -0
  53. wxcli/_playbook/docs/reference/archive/wxcadm-locations.md +938 -0
  54. wxcli/_playbook/docs/reference/archive/wxcadm-person.md +1255 -0
  55. wxcli/_playbook/docs/reference/archive/wxcadm-routing.md +1262 -0
  56. wxcli/_playbook/docs/reference/authentication.md +965 -0
  57. wxcli/_playbook/docs/reference/call-control.md +1205 -0
  58. wxcli/_playbook/docs/reference/call-features-additional.md +2541 -0
  59. wxcli/_playbook/docs/reference/call-features-major.md +1320 -0
  60. wxcli/_playbook/docs/reference/call-routing.md +2304 -0
  61. wxcli/_playbook/docs/reference/contact-center-analytics.md +1049 -0
  62. wxcli/_playbook/docs/reference/contact-center-core.md +1614 -0
  63. wxcli/_playbook/docs/reference/contact-center-journey.md +457 -0
  64. wxcli/_playbook/docs/reference/contact-center-routing.md +1138 -0
  65. wxcli/_playbook/docs/reference/devices-core.md +1869 -0
  66. wxcli/_playbook/docs/reference/devices-dect.md +1150 -0
  67. wxcli/_playbook/docs/reference/devices-platform.md +467 -0
  68. wxcli/_playbook/docs/reference/devices-workspaces.md +1426 -0
  69. wxcli/_playbook/docs/reference/emergency-services.md +1122 -0
  70. wxcli/_playbook/docs/reference/location-calling-core.md +2037 -0
  71. wxcli/_playbook/docs/reference/location-calling-media.md +1334 -0
  72. wxcli/_playbook/docs/reference/location-recording-advanced.md +1482 -0
  73. wxcli/_playbook/docs/reference/meetings-content.md +473 -0
  74. wxcli/_playbook/docs/reference/meetings-core.md +903 -0
  75. wxcli/_playbook/docs/reference/meetings-infrastructure.md +899 -0
  76. wxcli/_playbook/docs/reference/meetings-settings.md +850 -0
  77. wxcli/_playbook/docs/reference/messaging-bots.md +1091 -0
  78. wxcli/_playbook/docs/reference/messaging-spaces.md +695 -0
  79. wxcli/_playbook/docs/reference/person-call-settings-behavior.md +1639 -0
  80. wxcli/_playbook/docs/reference/person-call-settings-handling.md +1394 -0
  81. wxcli/_playbook/docs/reference/person-call-settings-media.md +1898 -0
  82. wxcli/_playbook/docs/reference/person-call-settings-permissions.md +1329 -0
  83. wxcli/_playbook/docs/reference/provisioning.md +1366 -0
  84. wxcli/_playbook/docs/reference/reporting-analytics.md +1318 -0
  85. wxcli/_playbook/docs/reference/self-service-call-settings.md +1020 -0
  86. wxcli/_playbook/docs/reference/virtual-lines.md +1132 -0
  87. wxcli/_playbook/docs/reference/webhooks-events.md +1444 -0
  88. wxcli/_playbook/docs/reference/wxcadm-xsi-realtime.md +953 -0
  89. wxcli/_version.py +24 -0
  90. wxcli/auth.py +137 -0
  91. wxcli/commands/__init__.py +0 -0
  92. wxcli/commands/_registry.py +181 -0
  93. wxcli/commands/activation_email.py +103 -0
  94. wxcli/commands/admin_recordings.py +585 -0
  95. wxcli/commands/analytics.py +105 -0
  96. wxcli/commands/announcement_playlists.py +219 -0
  97. wxcli/commands/announcements.py +519 -0
  98. wxcli/commands/archive_users.py +36 -0
  99. wxcli/commands/attachment_actions.py +60 -0
  100. wxcli/commands/audit_events.py +83 -0
  101. wxcli/commands/authorizations.py +113 -0
  102. wxcli/commands/auto_attendant.py +642 -0
  103. wxcli/commands/broadworks_billing_reports.py +126 -0
  104. wxcli/commands/broadworks_enterprises.py +160 -0
  105. wxcli/commands/broadworks_subscribers.py +319 -0
  106. wxcli/commands/broadworks_workspaces.py +108 -0
  107. wxcli/commands/call_controls.py +1304 -0
  108. wxcli/commands/call_park.py +494 -0
  109. wxcli/commands/call_pickup.py +229 -0
  110. wxcli/commands/call_queue.py +1878 -0
  111. wxcli/commands/call_recording.py +707 -0
  112. wxcli/commands/call_routing.py +1610 -0
  113. wxcli/commands/call_settings_for_me_phase_5.py +212 -0
  114. wxcli/commands/caller_reputation.py +169 -0
  115. wxcli/commands/calling_service.py +305 -0
  116. wxcli/commands/cc_address_book.py +770 -0
  117. wxcli/commands/cc_agent_greetings.py +242 -0
  118. wxcli/commands/cc_agent_summaries.py +39 -0
  119. wxcli/commands/cc_agent_wellbeing.py +216 -0
  120. wxcli/commands/cc_agents.py +425 -0
  121. wxcli/commands/cc_ai_assistant.py +55 -0
  122. wxcli/commands/cc_ai_feature.py +294 -0
  123. wxcli/commands/cc_audio_files.py +149 -0
  124. wxcli/commands/cc_auto_csat.py +311 -0
  125. wxcli/commands/cc_aux_code.py +458 -0
  126. wxcli/commands/cc_business_hour.py +353 -0
  127. wxcli/commands/cc_call_monitoring.py +228 -0
  128. wxcli/commands/cc_callbacks.py +194 -0
  129. wxcli/commands/cc_campaign.py +212 -0
  130. wxcli/commands/cc_captures.py +39 -0
  131. wxcli/commands/cc_contact_list.py +169 -0
  132. wxcli/commands/cc_contact_number.py +295 -0
  133. wxcli/commands/cc_data_sources.py +235 -0
  134. wxcli/commands/cc_desktop_layout.py +380 -0
  135. wxcli/commands/cc_desktop_profile.py +322 -0
  136. wxcli/commands/cc_dial_number.py +511 -0
  137. wxcli/commands/cc_dial_plan.py +347 -0
  138. wxcli/commands/cc_dnc.py +101 -0
  139. wxcli/commands/cc_entry_point.py +545 -0
  140. wxcli/commands/cc_ewt.py +51 -0
  141. wxcli/commands/cc_flow.py +477 -0
  142. wxcli/commands/cc_global_vars.py +430 -0
  143. wxcli/commands/cc_holiday_list.py +335 -0
  144. wxcli/commands/cc_journey.py +1261 -0
  145. wxcli/commands/cc_legacy_flows.py +49 -0
  146. wxcli/commands/cc_multimedia_profile.py +436 -0
  147. wxcli/commands/cc_notification.py +51 -0
  148. wxcli/commands/cc_outdial_ani.py +604 -0
  149. wxcli/commands/cc_overrides.py +341 -0
  150. wxcli/commands/cc_queue.py +1083 -0
  151. wxcli/commands/cc_queue_stats.py +55 -0
  152. wxcli/commands/cc_realtime.py +51 -0
  153. wxcli/commands/cc_resource_collection.py +312 -0
  154. wxcli/commands/cc_search.py +46 -0
  155. wxcli/commands/cc_site.py +400 -0
  156. wxcli/commands/cc_skill.py +350 -0
  157. wxcli/commands/cc_skill_profile.py +289 -0
  158. wxcli/commands/cc_subscriptions.py +426 -0
  159. wxcli/commands/cc_summaries.py +130 -0
  160. wxcli/commands/cc_tasks.py +844 -0
  161. wxcli/commands/cc_team.py +325 -0
  162. wxcli/commands/cc_user_profiles.py +349 -0
  163. wxcli/commands/cc_users.py +701 -0
  164. wxcli/commands/cc_work_types.py +363 -0
  165. wxcli/commands/cdr.py +89 -0
  166. wxcli/commands/classifications.py +38 -0
  167. wxcli/commands/cleanup.py +1427 -0
  168. wxcli/commands/client_settings.py +72 -0
  169. wxcli/commands/conference.py +328 -0
  170. wxcli/commands/configure.py +72 -0
  171. wxcli/commands/converged_recordings.py +347 -0
  172. wxcli/commands/converged_recordings_export.py +310 -0
  173. wxcli/commands/cq_playlists.py +42 -0
  174. wxcli/commands/cucm.py +2794 -0
  175. wxcli/commands/cucm_config.py +129 -0
  176. wxcli/commands/customer_assist.py +373 -0
  177. wxcli/commands/data_sources.py +227 -0
  178. wxcli/commands/dect_devices.py +787 -0
  179. wxcli/commands/device_configurations.py +77 -0
  180. wxcli/commands/device_dynamic_settings.py +320 -0
  181. wxcli/commands/device_settings.py +1489 -0
  182. wxcli/commands/devices.py +280 -0
  183. wxcli/commands/domains.py +142 -0
  184. wxcli/commands/ecm.py +178 -0
  185. wxcli/commands/emergency_services.py +827 -0
  186. wxcli/commands/events.py +83 -0
  187. wxcli/commands/external_voicemail.py +52 -0
  188. wxcli/commands/groups.py +206 -0
  189. wxcli/commands/guest_management.py +76 -0
  190. wxcli/commands/hds.py +340 -0
  191. wxcli/commands/hot_desk.py +67 -0
  192. wxcli/commands/hot_desking_members.py +118 -0
  193. wxcli/commands/hot_desking_portal.py +124 -0
  194. wxcli/commands/hunt_group.py +577 -0
  195. wxcli/commands/hybrid_clusters.py +71 -0
  196. wxcli/commands/hybrid_connectors.py +67 -0
  197. wxcli/commands/identity_org.py +159 -0
  198. wxcli/commands/init_playbook.py +155 -0
  199. wxcli/commands/licenses.py +106 -0
  200. wxcli/commands/live_monitoring.py +40 -0
  201. wxcli/commands/location_call_handling.py +577 -0
  202. wxcli/commands/location_schedules.py +341 -0
  203. wxcli/commands/location_settings.py +1297 -0
  204. wxcli/commands/location_voicemail.py +494 -0
  205. wxcli/commands/locations.py +336 -0
  206. wxcli/commands/meeting_captions.py +110 -0
  207. wxcli/commands/meeting_chats.py +63 -0
  208. wxcli/commands/meeting_invitees.py +234 -0
  209. wxcli/commands/meeting_messages.py +28 -0
  210. wxcli/commands/meeting_participants.py +278 -0
  211. wxcli/commands/meeting_polls.py +111 -0
  212. wxcli/commands/meeting_preferences.py +515 -0
  213. wxcli/commands/meeting_qa.py +78 -0
  214. wxcli/commands/meeting_qualities.py +43 -0
  215. wxcli/commands/meeting_reports.py +101 -0
  216. wxcli/commands/meeting_session_types.py +105 -0
  217. wxcli/commands/meeting_site.py +57 -0
  218. wxcli/commands/meeting_slido.py +44 -0
  219. wxcli/commands/meeting_summaries.py +92 -0
  220. wxcli/commands/meeting_tracking_codes.py +233 -0
  221. wxcli/commands/meeting_transcripts.py +230 -0
  222. wxcli/commands/meetings.py +1997 -0
  223. wxcli/commands/memberships.py +164 -0
  224. wxcli/commands/messages.py +213 -0
  225. wxcli/commands/mode_management.py +244 -0
  226. wxcli/commands/my_call_settings.py +3577 -0
  227. wxcli/commands/numbers.py +405 -0
  228. wxcli/commands/operating_modes.py +410 -0
  229. wxcli/commands/org_contacts.py +284 -0
  230. wxcli/commands/org_health_cli.py +55 -0
  231. wxcli/commands/org_settings.py +71 -0
  232. wxcli/commands/organizations.py +83 -0
  233. wxcli/commands/paging_group.py +257 -0
  234. wxcli/commands/partner_admins.py +141 -0
  235. wxcli/commands/partner_reports.py +188 -0
  236. wxcli/commands/partner_tags.py +216 -0
  237. wxcli/commands/people.py +279 -0
  238. wxcli/commands/person_call_settings.py +71 -0
  239. wxcli/commands/pstn.py +280 -0
  240. wxcli/commands/recording_report.py +154 -0
  241. wxcli/commands/report_templates.py +38 -0
  242. wxcli/commands/reports.py +144 -0
  243. wxcli/commands/resource_group_memberships.py +160 -0
  244. wxcli/commands/resource_groups.py +67 -0
  245. wxcli/commands/roles.py +63 -0
  246. wxcli/commands/room_tabs.py +160 -0
  247. wxcli/commands/rooms.py +225 -0
  248. wxcli/commands/scim_bulk.py +46 -0
  249. wxcli/commands/scim_groups.py +246 -0
  250. wxcli/commands/scim_schemas.py +82 -0
  251. wxcli/commands/scim_users.py +283 -0
  252. wxcli/commands/security_audit.py +54 -0
  253. wxcli/commands/service_apps.py +51 -0
  254. wxcli/commands/single_number_reach.py +209 -0
  255. wxcli/commands/team_memberships.py +155 -0
  256. wxcli/commands/teams.py +153 -0
  257. wxcli/commands/ucm_profile.py +42 -0
  258. wxcli/commands/update.py +219 -0
  259. wxcli/commands/user_settings.py +5116 -0
  260. wxcli/commands/video_mesh.py +1090 -0
  261. wxcli/commands/virtual_extensions.py +502 -0
  262. wxcli/commands/virtual_line_settings.py +2072 -0
  263. wxcli/commands/webhooks.py +176 -0
  264. wxcli/commands/wholesale_billing_reports.py +147 -0
  265. wxcli/commands/wholesale_provisioning.py +523 -0
  266. wxcli/commands/workspace_locations.py +339 -0
  267. wxcli/commands/workspace_metrics.py +103 -0
  268. wxcli/commands/workspace_personalization.py +70 -0
  269. wxcli/commands/workspace_settings.py +3347 -0
  270. wxcli/commands/workspaces.py +279 -0
  271. wxcli/commands/xapi.py +114 -0
  272. wxcli/config.py +139 -0
  273. wxcli/errors.py +77 -0
  274. wxcli/main.py +210 -0
  275. wxcli/migration/CLAUDE.md +106 -0
  276. wxcli/migration/__init__.py +1 -0
  277. wxcli/migration/advisory/CLAUDE.md +196 -0
  278. wxcli/migration/advisory/__init__.py +46 -0
  279. wxcli/migration/advisory/advisor.py +107 -0
  280. wxcli/migration/advisory/advisory_patterns.py +2552 -0
  281. wxcli/migration/advisory/recommendation_rules.py +835 -0
  282. wxcli/migration/cucm/CLAUDE.md +74 -0
  283. wxcli/migration/cucm/__init__.py +5 -0
  284. wxcli/migration/cucm/connection.py +265 -0
  285. wxcli/migration/cucm/discovery.py +225 -0
  286. wxcli/migration/cucm/extractors/__init__.py +1 -0
  287. wxcli/migration/cucm/extractors/announcements.py +92 -0
  288. wxcli/migration/cucm/extractors/base.py +90 -0
  289. wxcli/migration/cucm/extractors/device_profiles.py +98 -0
  290. wxcli/migration/cucm/extractors/devices.py +193 -0
  291. wxcli/migration/cucm/extractors/e911.py +103 -0
  292. wxcli/migration/cucm/extractors/features.py +309 -0
  293. wxcli/migration/cucm/extractors/helpers.py +73 -0
  294. wxcli/migration/cucm/extractors/informational.py +295 -0
  295. wxcli/migration/cucm/extractors/locations.py +221 -0
  296. wxcli/migration/cucm/extractors/moh.py +100 -0
  297. wxcli/migration/cucm/extractors/remote_destinations.py +96 -0
  298. wxcli/migration/cucm/extractors/routing.py +493 -0
  299. wxcli/migration/cucm/extractors/shared_lines.py +146 -0
  300. wxcli/migration/cucm/extractors/templates.py +229 -0
  301. wxcli/migration/cucm/extractors/tier4.py +173 -0
  302. wxcli/migration/cucm/extractors/users.py +240 -0
  303. wxcli/migration/cucm/extractors/voicemail.py +243 -0
  304. wxcli/migration/cucm/extractors/workspaces.py +88 -0
  305. wxcli/migration/cucm/unity_connection.py +304 -0
  306. wxcli/migration/execute/CLAUDE.md +372 -0
  307. wxcli/migration/execute/__init__.py +303 -0
  308. wxcli/migration/execute/batch.py +277 -0
  309. wxcli/migration/execute/dependency.py +483 -0
  310. wxcli/migration/execute/engine.py +964 -0
  311. wxcli/migration/execute/handlers.py +2232 -0
  312. wxcli/migration/execute/planner.py +1939 -0
  313. wxcli/migration/execute/runtime.py +571 -0
  314. wxcli/migration/export/CLAUDE.md +49 -0
  315. wxcli/migration/export/__init__.py +8 -0
  316. wxcli/migration/export/csv_export.py +126 -0
  317. wxcli/migration/export/deployment_plan.py +479 -0
  318. wxcli/migration/export/json_export.py +60 -0
  319. wxcli/migration/models.py +941 -0
  320. wxcli/migration/phone_models.py +50 -0
  321. wxcli/migration/preflight/CLAUDE.md +52 -0
  322. wxcli/migration/preflight/__init__.py +89 -0
  323. wxcli/migration/preflight/checks.py +911 -0
  324. wxcli/migration/preflight/runner.py +293 -0
  325. wxcli/migration/rate_limiter.py +144 -0
  326. wxcli/migration/report/CLAUDE.md +245 -0
  327. wxcli/migration/report/__init__.py +7 -0
  328. wxcli/migration/report/appendix.py +2437 -0
  329. wxcli/migration/report/assembler.py +183 -0
  330. wxcli/migration/report/charts.py +263 -0
  331. wxcli/migration/report/executive.py +776 -0
  332. wxcli/migration/report/explainer.py +704 -0
  333. wxcli/migration/report/helpers.py +74 -0
  334. wxcli/migration/report/ingest.py +152 -0
  335. wxcli/migration/report/notice_templates.py +192 -0
  336. wxcli/migration/report/score.py +375 -0
  337. wxcli/migration/report/styles.py +1052 -0
  338. wxcli/migration/report/user_diff.py +1052 -0
  339. wxcli/migration/report/user_notice.py +425 -0
  340. wxcli/migration/state.py +139 -0
  341. wxcli/migration/store.py +860 -0
  342. wxcli/migration/transform/CLAUDE.md +107 -0
  343. wxcli/migration/transform/__init__.py +12 -0
  344. wxcli/migration/transform/analysis_pipeline.py +453 -0
  345. wxcli/migration/transform/analyzers/__init__.py +142 -0
  346. wxcli/migration/transform/analyzers/css_permission.py +189 -0
  347. wxcli/migration/transform/analyzers/css_routing.py +335 -0
  348. wxcli/migration/transform/analyzers/device_compatibility.py +122 -0
  349. wxcli/migration/transform/analyzers/dn_ambiguity.py +131 -0
  350. wxcli/migration/transform/analyzers/duplicate_user.py +221 -0
  351. wxcli/migration/transform/analyzers/extension_conflict.py +173 -0
  352. wxcli/migration/transform/analyzers/feature_approximation.py +227 -0
  353. wxcli/migration/transform/analyzers/layout_overflow.py +174 -0
  354. wxcli/migration/transform/analyzers/location_ambiguity.py +184 -0
  355. wxcli/migration/transform/analyzers/missing_data.py +300 -0
  356. wxcli/migration/transform/analyzers/selective_call_handling.py +394 -0
  357. wxcli/migration/transform/analyzers/shared_line.py +170 -0
  358. wxcli/migration/transform/analyzers/voicemail_compatibility.py +267 -0
  359. wxcli/migration/transform/analyzers/workspace_license.py +212 -0
  360. wxcli/migration/transform/cross_reference.py +1136 -0
  361. wxcli/migration/transform/cucm_pattern.py +246 -0
  362. wxcli/migration/transform/decisions.py +301 -0
  363. wxcli/migration/transform/e164.py +145 -0
  364. wxcli/migration/transform/engine.py +256 -0
  365. wxcli/migration/transform/mappers/CLAUDE.md +123 -0
  366. wxcli/migration/transform/mappers/__init__.py +76 -0
  367. wxcli/migration/transform/mappers/announcement_mapper.py +122 -0
  368. wxcli/migration/transform/mappers/base.py +190 -0
  369. wxcli/migration/transform/mappers/button_template_mapper.py +186 -0
  370. wxcli/migration/transform/mappers/call_forwarding_mapper.py +228 -0
  371. wxcli/migration/transform/mappers/call_settings_mapper.py +144 -0
  372. wxcli/migration/transform/mappers/css_mapper.py +868 -0
  373. wxcli/migration/transform/mappers/dect_mapper.py +367 -0
  374. wxcli/migration/transform/mappers/device_layout_mapper.py +289 -0
  375. wxcli/migration/transform/mappers/device_mapper.py +227 -0
  376. wxcli/migration/transform/mappers/device_profile_mapper.py +190 -0
  377. wxcli/migration/transform/mappers/device_settings_mapper.py +464 -0
  378. wxcli/migration/transform/mappers/e911_mapper.py +129 -0
  379. wxcli/migration/transform/mappers/ecbn_mapper.py +263 -0
  380. wxcli/migration/transform/mappers/executive_assistant_mapper.py +156 -0
  381. wxcli/migration/transform/mappers/feature_mapper.py +1179 -0
  382. wxcli/migration/transform/mappers/line_mapper.py +310 -0
  383. wxcli/migration/transform/mappers/location_mapper.py +235 -0
  384. wxcli/migration/transform/mappers/moh_mapper.py +114 -0
  385. wxcli/migration/transform/mappers/monitoring_mapper.py +221 -0
  386. wxcli/migration/transform/mappers/receptionist_mapper.py +245 -0
  387. wxcli/migration/transform/mappers/routing_mapper.py +821 -0
  388. wxcli/migration/transform/mappers/snr_mapper.py +201 -0
  389. wxcli/migration/transform/mappers/softkey_mapper.py +205 -0
  390. wxcli/migration/transform/mappers/user_mapper.py +264 -0
  391. wxcli/migration/transform/mappers/voicemail_group_mapper.py +286 -0
  392. wxcli/migration/transform/mappers/voicemail_mapper.py +514 -0
  393. wxcli/migration/transform/mappers/workspace_mapper.py +464 -0
  394. wxcli/migration/transform/normalizers.py +2008 -0
  395. wxcli/migration/transform/pattern_converter.py +106 -0
  396. wxcli/migration/transform/pipeline.py +271 -0
  397. wxcli/migration/transform/rules.py +255 -0
  398. wxcli/org_health/CLAUDE.md +63 -0
  399. wxcli/org_health/__init__.py +7 -0
  400. wxcli/org_health/analyze.py +93 -0
  401. wxcli/org_health/checks.py +458 -0
  402. wxcli/org_health/collector.py +62 -0
  403. wxcli/org_health/models.py +78 -0
  404. wxcli/org_health/report.py +300 -0
  405. wxcli/output.py +101 -0
  406. wxcli-1.2.0.dist-info/METADATA +505 -0
  407. wxcli-1.2.0.dist-info/RECORD +413 -0
  408. wxcli-1.2.0.dist-info/WHEEL +5 -0
  409. wxcli-1.2.0.dist-info/entry_points.txt +2 -0
  410. wxcli-1.2.0.dist-info/licenses/LICENSE +191 -0
  411. wxcli-1.2.0.dist-info/scm_file_list.json +751 -0
  412. wxcli-1.2.0.dist-info/scm_version.json +8 -0
  413. wxcli-1.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,293 @@
1
+ """Preflight runner — orchestrates all checks and manages shared data.
2
+
3
+ Fetches shared Webex data once (licenses, locations, numbers, people, trunks),
4
+ runs all checks, merges decisions, and stores results in state.json.
5
+
6
+ (from 05a-preflight-checks.md, PreflightRunner class)
7
+ (from phase-10-preflight.md, runner orchestrator)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from wxcli.migration.preflight import (
17
+ CheckResult,
18
+ CheckStatus,
19
+ PreflightError,
20
+ PreflightResult,
21
+ _run_wxcli,
22
+ )
23
+ from wxcli.migration.preflight.checks import (
24
+ check_bulk_device_job_support,
25
+ check_duplicate_users,
26
+ check_e911_readiness,
27
+ check_feature_entitlements,
28
+ check_licenses,
29
+ check_locations,
30
+ check_number_conflicts,
31
+ check_rate_limit_budget,
32
+ check_trunks,
33
+ check_workspace_licenses,
34
+ )
35
+ from wxcli.migration.store import MigrationStore
36
+
37
+
38
+ # Priority order for overall result (worst wins)
39
+ _STATUS_PRIORITY = {
40
+ CheckStatus.FAIL: 3,
41
+ CheckStatus.WARN: 2,
42
+ CheckStatus.PASS: 1,
43
+ CheckStatus.SKIP: 0,
44
+ }
45
+
46
+
47
+ def _worst_status(results: list[CheckResult]) -> CheckStatus:
48
+ """Return the worst status across all results."""
49
+ worst = CheckStatus.SKIP
50
+ for r in results:
51
+ if _STATUS_PRIORITY.get(r.status, 0) > _STATUS_PRIORITY.get(worst, 0):
52
+ worst = r.status
53
+ return worst
54
+
55
+
56
+ class PreflightRunner:
57
+ """Orchestrates all 7 preflight checks + DUPLICATE_USER detection.
58
+
59
+ Fetches shared data once before running individual checks.
60
+ (from 05a-preflight-checks.md, Shared Data Between Checks)
61
+ """
62
+
63
+ def __init__(self, config: dict | None = None):
64
+ self.config = config or {}
65
+
66
+ def run(
67
+ self,
68
+ store: MigrationStore,
69
+ check_filter: str | None = None,
70
+ dry_run: bool = False,
71
+ ) -> PreflightResult:
72
+ """Run all preflight checks (or a single check by name).
73
+
74
+ Args:
75
+ store: Migration store with planned objects.
76
+ check_filter: Run only this check (e.g., "numbers", "licenses").
77
+ dry_run: Show what would be checked without querying Webex.
78
+ """
79
+ if dry_run:
80
+ return self._dry_run(store, check_filter)
81
+
82
+ # Fetch shared data — only what's needed for the filtered check
83
+ # (from 05a-preflight-checks.md, Shared Data Between Checks)
84
+ def _needs(*checks: str) -> bool:
85
+ return check_filter is None or check_filter in checks
86
+
87
+ licenses = self._fetch("licenses", ["licenses-api", "list", "--output", "json"]) if _needs("licenses", "workspace-licenses") else []
88
+ locations = self._fetch("locations", ["locations", "list"]) if _needs("locations") else []
89
+ numbers = self._fetch("numbers", ["numbers", "list", "--limit", "0"]) if _needs("numbers") else []
90
+ people = self._fetch("people", ["users", "list", "--calling-data", "true", "--limit", "0"]) if _needs("users") else []
91
+ trunks = self._fetch("trunks", ["call-routing", "list-trunks"]) if _needs("trunks") else []
92
+
93
+ # Fetch PSTN connection data per location
94
+ pstn_by_loc: dict[str, dict] = {}
95
+ if locations and _needs("locations"):
96
+ for loc in locations:
97
+ loc_id = loc.get("id")
98
+ if loc_id:
99
+ conn = self._fetch(f"pstn-{loc_id}",
100
+ ["pstn", "list-connection", loc_id, "--output", "json"])
101
+ if conn:
102
+ pstn_by_loc[loc_id] = conn[0] if isinstance(conn, list) and conn else conn if isinstance(conn, dict) else {}
103
+
104
+ existing_features: dict[str, int] = {}
105
+ if _needs("features"):
106
+ existing_features = self._count_existing_features()
107
+
108
+ # Build bulk-job probe only when the plan contains bulk ops
109
+ # (avoids initialising a WebexSimpleApi session otherwise).
110
+ probe_fn = None
111
+ if _needs("bulk-job-support"):
112
+ probe_fn = self._build_bulk_job_probe()
113
+
114
+ # Run checks
115
+ all_checks = {
116
+ "licenses": lambda: check_licenses(store, licenses),
117
+ "workspace-licenses": lambda: check_workspace_licenses(store, licenses),
118
+ "locations": lambda: check_locations(store, locations, pstn_connections=pstn_by_loc if locations else None),
119
+ "trunks": lambda: check_trunks(store, trunks),
120
+ "features": lambda: check_feature_entitlements(store, existing_features),
121
+ "numbers": lambda: self._run_number_check(store, numbers),
122
+ "users": lambda: self._run_duplicate_check(store, people),
123
+ "rate-limit": lambda: check_rate_limit_budget(store, self.config),
124
+ "e911-readiness": lambda: check_e911_readiness(store),
125
+ "bulk-job-support": lambda: check_bulk_device_job_support(store, probe_fn),
126
+ }
127
+
128
+ results: list[CheckResult] = []
129
+ all_decisions: list[dict] = []
130
+ self._number_decisions: list[dict] = []
131
+ self._user_decisions: list[dict] = []
132
+
133
+ if check_filter:
134
+ if check_filter not in all_checks:
135
+ valid = ", ".join(sorted(all_checks.keys()))
136
+ raise PreflightError(f"Unknown check '{check_filter}'. Valid: {valid}")
137
+ result = all_checks[check_filter]()
138
+ results.append(result)
139
+ else:
140
+ for name, check_fn in all_checks.items():
141
+ try:
142
+ result = check_fn()
143
+ results.append(result)
144
+ except PreflightError as e:
145
+ results.append(CheckResult(
146
+ name=name,
147
+ status=CheckStatus.FAIL,
148
+ detail=str(e),
149
+ ))
150
+
151
+ all_decisions = self._number_decisions + self._user_decisions
152
+
153
+ # Merge decisions into store — scoped to preflight types only
154
+ # to avoid stale-marking analyzer decisions
155
+ # (from 05a-preflight-checks.md lines 920-929)
156
+ merge_result: dict[str, int] = {}
157
+ new_decision_ids: list[str] = []
158
+ if all_decisions:
159
+ merge_result = store.merge_decisions(
160
+ all_decisions,
161
+ decision_types=["NUMBER_CONFLICT", "DUPLICATE_USER"],
162
+ stage="preflight",
163
+ )
164
+ new_decision_ids = [d["decision_id"] for d in all_decisions]
165
+
166
+ overall = _worst_status(results)
167
+
168
+ return PreflightResult(
169
+ overall=overall,
170
+ checks=results,
171
+ new_decision_ids=new_decision_ids,
172
+ merge_result=merge_result,
173
+ )
174
+
175
+ def _run_number_check(self, store: MigrationStore, numbers: list[dict]) -> CheckResult:
176
+ """Run number conflict check and capture decisions."""
177
+ result, decisions = check_number_conflicts(store, numbers)
178
+ self._number_decisions = decisions
179
+ return result
180
+
181
+ def _run_duplicate_check(self, store: MigrationStore, people: list[dict]) -> CheckResult:
182
+ """Run duplicate user check and capture decisions."""
183
+ result, decisions = check_duplicate_users(store, people)
184
+ self._user_decisions = decisions
185
+ return result
186
+
187
+ def _fetch(self, label: str, args: list[str]) -> list[dict]:
188
+ """Fetch data from wxcli, returning empty list on failure."""
189
+ try:
190
+ return _run_wxcli(args)
191
+ except PreflightError:
192
+ return []
193
+
194
+ def _build_bulk_job_probe(self):
195
+ """Return a zero-arg callable that probes the bulk device job endpoint.
196
+
197
+ Returns ``None`` if auth is unavailable — the check then SKIPs.
198
+ On invocation the callable returns ``(status_code, error_message)``.
199
+ Uses ``WebexSimpleApi`` so auth/orgId injection matches the rest of
200
+ the CLI.
201
+
202
+ Design note (Finding #11): this preflight module's convention is
203
+ **subprocess, not import** — checks normally shell out to ``wxcli``
204
+ via ``_run_wxcli`` to reuse the CLI's auth, pagination, and error
205
+ handling (see the module CLAUDE.md). This probe deliberately deviates
206
+ and calls ``api.session.get`` directly because there is no equivalent
207
+ ``wxcli`` subcommand that lists bulk device jobs — generating one
208
+ just to satisfy the convention would be overkill for a single
209
+ read-only probe. If ``wxcli`` ever gains a
210
+ ``bulk-device-jobs list --job-type callDeviceSettings --max 1``
211
+ command, this probe should be re-routed through ``_run_wxcli`` to
212
+ restore convention parity.
213
+ (Wave 4, Issue #9)
214
+ """
215
+ try:
216
+ from wxcli.auth import get_api
217
+ except Exception:
218
+ return None
219
+
220
+ try:
221
+ api = get_api()
222
+ except SystemExit:
223
+ # get_api raises typer.Exit when no token is configured
224
+ return None
225
+ except Exception:
226
+ return None
227
+
228
+ def _probe() -> tuple[int, str]:
229
+ import requests
230
+
231
+ url = api.session.ep("telephony/config/jobs/devices/callDeviceSettings")
232
+ params: dict[str, str] = {"max": "1"}
233
+ org_id = self.config.get("orgId") if isinstance(self.config, dict) else None
234
+ if org_id:
235
+ params["orgId"] = org_id
236
+ try:
237
+ resp = api.session.get(url, params=params)
238
+ except requests.RequestException as exc:
239
+ return 0, str(exc)
240
+ return resp.status_code, getattr(resp, "text", "") or ""
241
+
242
+ return _probe
243
+
244
+ def _count_existing_features(self) -> dict[str, int]:
245
+ """Count existing features in the Webex org."""
246
+ counts = {}
247
+ cmd_map = {
248
+ "auto_attendant": ["auto-attendant", "list"],
249
+ "call_queue": ["call-queue", "list"],
250
+ "hunt_group": ["hunt-group", "list"],
251
+ "paging_group": ["paging-group", "list"],
252
+ }
253
+ for obj_type, args in cmd_map.items():
254
+ try:
255
+ data = _run_wxcli(args)
256
+ counts[obj_type] = len(data)
257
+ except PreflightError:
258
+ counts[obj_type] = 0
259
+ return counts
260
+
261
+ def _dry_run(
262
+ self,
263
+ store: MigrationStore,
264
+ check_filter: str | None,
265
+ ) -> PreflightResult:
266
+ """Show what would be checked without querying Webex."""
267
+ checks_info = [
268
+ ("licenses", "User licenses", f"{store.count_by_type('user')} users to check"),
269
+ ("workspace-licenses", "Workspace licenses", f"{store.count_by_type('workspace')} workspaces to check"),
270
+ ("locations", "Locations", f"{len(store.get_objects('location'))} locations to verify"),
271
+ ("trunks", "Trunks", f"{len(store.get_objects('trunk'))} trunks to verify"),
272
+ ("features", "Feature entitlements", "AA, CQ, HG, Paging counts"),
273
+ ("numbers", "Number conflicts", "E.164 and extension collision check"),
274
+ ("users", "Duplicate users", f"{store.count_by_type('user')} users to check against Webex"),
275
+ ("rate-limit", "Rate limit budget", "API call estimate from plan_operations"),
276
+ ("e911-readiness", "E911 readiness", f"{store.count_by_type('user')} users ECBN candidate check"),
277
+ ("bulk-job-support", "Bulk device job support", "Probe for FedRAMP bulk device job support"),
278
+ ]
279
+
280
+ results = []
281
+ for key, name, detail in checks_info:
282
+ if check_filter and check_filter != key:
283
+ continue
284
+ results.append(CheckResult(
285
+ name=name,
286
+ status=CheckStatus.SKIP,
287
+ detail=f"[dry-run] Would check: {detail}",
288
+ ))
289
+
290
+ return PreflightResult(
291
+ overall=CheckStatus.SKIP,
292
+ checks=results,
293
+ )
@@ -0,0 +1,144 @@
1
+ """Per-endpoint rate limiter with exponential backoff.
2
+
3
+ Enforces max concurrent requests and handles 429 (Too Many Requests) responses
4
+ with exponential backoff. Configurable via migration settings.
5
+
6
+ (from cucm-wxc-migration.md lines 443-444,
7
+ 05-dependency-graph.md rate limit budget section)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import time
14
+ from dataclasses import dataclass, field
15
+
16
+
17
+ @dataclass
18
+ class RateLimitConfig:
19
+ """Configuration for the rate limiter."""
20
+ max_concurrent: int = 5
21
+ base_delay: float = 1.0 # seconds
22
+ max_delay: float = 60.0 # seconds
23
+ backoff_factor: float = 2.0
24
+ max_retries: int = 5
25
+
26
+
27
+ @dataclass
28
+ class _EndpointState:
29
+ """Tracks backoff state for a single endpoint."""
30
+ consecutive_429s: int = 0
31
+ next_allowed_at: float = 0.0
32
+
33
+
34
+ class RateLimiter:
35
+ """Per-endpoint rate limiter with exponential backoff on 429 responses.
36
+
37
+ Usage (sync):
38
+ limiter = RateLimiter()
39
+ with limiter.acquire("/v1/people"):
40
+ response = make_api_call()
41
+ if response.status_code == 429:
42
+ limiter.record_429("/v1/people")
43
+
44
+ Usage (async):
45
+ limiter = RateLimiter()
46
+ async with limiter.acquire_async("/v1/people"):
47
+ response = await make_api_call()
48
+ if response.status_code == 429:
49
+ limiter.record_429("/v1/people")
50
+ """
51
+
52
+ def __init__(self, config: RateLimitConfig | None = None) -> None:
53
+ self.config = config or RateLimitConfig()
54
+ self._semaphore = asyncio.Semaphore(self.config.max_concurrent)
55
+ self._sync_count: int = 0
56
+ self._sync_count_lock = None # lazy init for threading
57
+ self._endpoints: dict[str, _EndpointState] = {}
58
+
59
+ def _get_endpoint(self, endpoint: str) -> _EndpointState:
60
+ if endpoint not in self._endpoints:
61
+ self._endpoints[endpoint] = _EndpointState()
62
+ return self._endpoints[endpoint]
63
+
64
+ def record_429(self, endpoint: str) -> float:
65
+ """Record a 429 response. Returns the delay (seconds) before next retry."""
66
+ state = self._get_endpoint(endpoint)
67
+ state.consecutive_429s += 1
68
+ delay = min(
69
+ self.config.base_delay * (self.config.backoff_factor ** state.consecutive_429s),
70
+ self.config.max_delay,
71
+ )
72
+ state.next_allowed_at = time.monotonic() + delay
73
+ return delay
74
+
75
+ def record_success(self, endpoint: str) -> None:
76
+ """Record a successful response, resetting backoff."""
77
+ state = self._get_endpoint(endpoint)
78
+ state.consecutive_429s = 0
79
+ state.next_allowed_at = 0.0
80
+
81
+ def should_retry(self, endpoint: str) -> bool:
82
+ """Check if retries are still allowed for this endpoint."""
83
+ state = self._get_endpoint(endpoint)
84
+ return state.consecutive_429s < self.config.max_retries
85
+
86
+ def wait_time(self, endpoint: str) -> float:
87
+ """Seconds to wait before the next request to this endpoint."""
88
+ state = self._get_endpoint(endpoint)
89
+ remaining = state.next_allowed_at - time.monotonic()
90
+ return max(0.0, remaining)
91
+
92
+ # -- Sync context manager --
93
+
94
+ class _SyncSlot:
95
+ """Sync concurrency slot.
96
+
97
+ Note: The sync acquire path uses a simple counter and is NOT
98
+ thread-safe. For threaded usage, use ``acquire_async`` with an
99
+ asyncio event loop. The executor uses the async path for
100
+ concurrent API requests.
101
+ """
102
+
103
+ def __init__(self, limiter: RateLimiter, endpoint: str) -> None:
104
+ self._limiter = limiter
105
+ self._endpoint = endpoint
106
+
107
+ def __enter__(self) -> _SyncSlot:
108
+ # Wait for per-endpoint backoff
109
+ wait = self._limiter.wait_time(self._endpoint)
110
+ if wait > 0:
111
+ time.sleep(wait)
112
+ # Enforce concurrency (simple counter — not thread-safe)
113
+ while self._limiter._sync_count >= self._limiter.config.max_concurrent:
114
+ time.sleep(0.05)
115
+ self._limiter._sync_count += 1
116
+ return self
117
+
118
+ def __exit__(self, *args: object) -> None:
119
+ self._limiter._sync_count -= 1
120
+
121
+ def acquire(self, endpoint: str) -> _SyncSlot:
122
+ """Sync context manager that enforces rate limits."""
123
+ return self._SyncSlot(self, endpoint)
124
+
125
+ # -- Async context manager --
126
+
127
+ class _AsyncSlot:
128
+ def __init__(self, limiter: RateLimiter, endpoint: str) -> None:
129
+ self._limiter = limiter
130
+ self._endpoint = endpoint
131
+
132
+ async def __aenter__(self) -> _AsyncSlot:
133
+ wait = self._limiter.wait_time(self._endpoint)
134
+ if wait > 0:
135
+ await asyncio.sleep(wait)
136
+ await self._limiter._semaphore.acquire()
137
+ return self
138
+
139
+ async def __aexit__(self, *args: object) -> None:
140
+ self._limiter._semaphore.release()
141
+
142
+ def acquire_async(self, endpoint: str) -> _AsyncSlot:
143
+ """Async context manager that enforces rate limits."""
144
+ return self._AsyncSlot(self, endpoint)