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,221 @@
1
+ """MonitoringMapper — CUCM BLF → Webex per-person monitoring list.
2
+
3
+ Reads phone objects from the store (which contain busyLampFields from getPhone),
4
+ resolves user ownership via device_owned_by_user cross-ref, resolves BLF
5
+ targets to user canonical IDs via line cross-refs, and produces
6
+ CanonicalMonitoringList objects.
7
+
8
+ Speed dials are NOT mapped — they stay in phone metadata for reporting.
9
+ Webex monitoring list max is 50 entries; overflow produces FEATURE_APPROXIMATION.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ from typing import Any
16
+
17
+ from wxcli.migration.models import (
18
+ CanonicalMonitoringList,
19
+ DecisionType,
20
+ MigrationStatus,
21
+ )
22
+ from wxcli.migration.store import MigrationStore
23
+ from wxcli.migration.models import MapperResult
24
+ from wxcli.migration.transform.mappers.base import (
25
+ Mapper,
26
+ accept_option,
27
+ extract_provenance,
28
+ skip_option,
29
+ )
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ WEBEX_MONITORING_MAX = 50
34
+
35
+
36
+ def _extract_blf_entries(state: dict[str, Any]) -> list[dict[str, Any]]:
37
+ """Extract BLF entries from phone pre_migration_state.
38
+
39
+ Handles both raw AXL format (nested dict) and pre-normalized list.
40
+ """
41
+ blf = state.get("busyLampFields")
42
+ if blf is None:
43
+ return []
44
+ if isinstance(blf, list):
45
+ return blf
46
+ if isinstance(blf, dict):
47
+ entries = blf.get("busyLampField", [])
48
+ if isinstance(entries, list):
49
+ return entries
50
+ if isinstance(entries, dict):
51
+ return [entries]
52
+ return []
53
+
54
+
55
+ def _extract_speed_dials(state: dict[str, Any]) -> list[dict[str, Any]]:
56
+ """Extract speed dial entries from phone pre_migration_state."""
57
+ sd = state.get("speeddials")
58
+ if sd is None:
59
+ return []
60
+ if isinstance(sd, list):
61
+ return sd
62
+ if isinstance(sd, dict):
63
+ entries = sd.get("speeddial", [])
64
+ if isinstance(entries, list):
65
+ return entries
66
+ if isinstance(entries, dict):
67
+ return [entries]
68
+ return []
69
+
70
+
71
+ class MonitoringMapper(Mapper):
72
+ """Map CUCM BLF entries to Webex per-person monitoring lists.
73
+
74
+ Tier 2 expansion: produces CanonicalMonitoringList objects and
75
+ FEATURE_APPROXIMATION decisions for overflow (>50 entries).
76
+ """
77
+
78
+ name = "monitoring_mapper"
79
+ depends_on = ["user_mapper", "line_mapper"]
80
+
81
+ def map(self, store: MigrationStore) -> MapperResult:
82
+ result = MapperResult()
83
+
84
+ for phone_data in store.get_objects("phone"):
85
+ phone_id = phone_data["canonical_id"]
86
+ state = phone_data.get("pre_migration_state") or {}
87
+
88
+ blf_entries = _extract_blf_entries(state)
89
+ if not blf_entries:
90
+ continue
91
+
92
+ # Resolve owner user
93
+ user_refs = store.find_cross_refs(phone_id, "device_owned_by_user")
94
+ if not user_refs:
95
+ continue
96
+ user_id = user_refs[0]
97
+ user_name = user_id.split(":", 1)[-1]
98
+
99
+ # Skip if already created for this user (multi-device)
100
+ existing = store.get_object(f"monitoring_list:{user_name}")
101
+ if existing:
102
+ continue
103
+
104
+ # Resolve BLF targets
105
+ monitored_members = []
106
+ for entry in blf_entries:
107
+ if not isinstance(entry, dict):
108
+ continue
109
+ blf_dest = entry.get("blfDest") or entry.get("blfDirn") or ""
110
+ label = entry.get("label") or blf_dest
111
+
112
+ # Try to resolve BLF destination to a user
113
+ target_id = self._resolve_blf_target(store, blf_dest)
114
+ if target_id:
115
+ monitored_members.append({
116
+ "target_canonical_id": target_id,
117
+ "display_label": label,
118
+ })
119
+ else:
120
+ # Still add with unresolved marker
121
+ monitored_members.append({
122
+ "target_canonical_id": None,
123
+ "display_label": label,
124
+ "unresolved_dn": blf_dest,
125
+ })
126
+
127
+ # Check overflow
128
+ truncated = False
129
+ total_count = len(monitored_members)
130
+ if total_count > WEBEX_MONITORING_MAX:
131
+ truncated = True
132
+ monitored_members = monitored_members[:WEBEX_MONITORING_MAX]
133
+
134
+ # Build canonical monitoring list
135
+ prov = extract_provenance(phone_data)
136
+ ml = CanonicalMonitoringList(
137
+ canonical_id=f"monitoring_list:{user_name}",
138
+ provenance=prov,
139
+ status=MigrationStatus.ANALYZED,
140
+ user_canonical_id=user_id,
141
+ monitored_members=monitored_members,
142
+ )
143
+ store.upsert_object(ml)
144
+ result.objects_created += 1
145
+
146
+ # Write cross-refs
147
+ store.add_cross_ref(user_id, ml.canonical_id, "user_has_monitoring_list")
148
+ for member in monitored_members:
149
+ target = member.get("target_canonical_id")
150
+ if target:
151
+ store.add_cross_ref(ml.canonical_id, target, "monitoring_watches")
152
+
153
+ # Decision: unresolved BLF targets
154
+ unresolved = [m for m in monitored_members if m.get("target_canonical_id") is None]
155
+ if unresolved:
156
+ decision = self._create_decision(
157
+ store=store,
158
+ decision_type=DecisionType.MISSING_DATA,
159
+ severity="LOW",
160
+ summary=(
161
+ f"User '{user_name}' has {len(unresolved)} BLF "
162
+ f"target(s) that cannot be resolved to a Webex user"
163
+ ),
164
+ context={
165
+ "user_id": user_id,
166
+ "unresolved_dns": [m.get("unresolved_dn", "") for m in unresolved],
167
+ "total_blf_entries": total_count,
168
+ },
169
+ options=[
170
+ accept_option("Skip unresolved entries — exclude from monitoring list"),
171
+ skip_option("Skip entire monitoring list migration for this user"),
172
+ ],
173
+ affected_objects=[user_id, ml.canonical_id],
174
+ )
175
+ result.decisions.append(decision)
176
+
177
+ # Decision: overflow
178
+ if truncated:
179
+ decision = self._create_decision(
180
+ store=store,
181
+ decision_type=DecisionType.FEATURE_APPROXIMATION,
182
+ severity="MEDIUM",
183
+ summary=(
184
+ f"User '{user_name}' has {total_count} BLF entries, "
185
+ f"exceeding Webex monitoring list max of {WEBEX_MONITORING_MAX}"
186
+ ),
187
+ context={
188
+ "user_id": user_id,
189
+ "total_blf_entries": total_count,
190
+ "max_allowed": WEBEX_MONITORING_MAX,
191
+ "truncated_count": total_count - WEBEX_MONITORING_MAX,
192
+ },
193
+ options=[
194
+ accept_option(f"Truncate to first {WEBEX_MONITORING_MAX} entries"),
195
+ skip_option("Skip monitoring list migration for this user"),
196
+ ],
197
+ affected_objects=[user_id, ml.canonical_id],
198
+ )
199
+ result.decisions.append(decision)
200
+
201
+ return result
202
+
203
+ def _resolve_blf_target(self, store: MigrationStore, dn: str) -> str | None:
204
+ """Try to resolve a BLF DN to a user canonical_id via line cross-refs."""
205
+ if not dn:
206
+ return None
207
+ # Look for a line object matching this DN
208
+ for line_data in store.get_objects("line"):
209
+ line_state = line_data.get("pre_migration_state") or {}
210
+ line_ext = (
211
+ line_data.get("extension")
212
+ or line_state.get("extension")
213
+ or line_state.get("pattern")
214
+ )
215
+ if line_ext == dn:
216
+ # Found the line — now find its owner via cross-ref
217
+ line_id = line_data["canonical_id"]
218
+ user_refs = store.find_cross_refs(line_id, "line_assigned_to_user")
219
+ if user_refs:
220
+ return user_refs[0]
221
+ return None
@@ -0,0 +1,245 @@
1
+ """ReceptionistMapper — detect CUCM receptionist users from phone layout signals.
2
+
3
+ Scores each phone owner for receptionist likelihood using 4 signals:
4
+ 1. BLF count (10+ = 2pts, 20+ = 3pts)
5
+ 2. KEM presence (2pts)
6
+ 3. Template name keywords (2pts)
7
+ 4. Main number assignment (1pt)
8
+
9
+ Users scoring >= 3 produce a CanonicalReceptionistConfig object. The mapper
10
+ reads the existing MonitoringMapper output to populate monitored_members and
11
+ DeviceLayoutMapper output to detect KEM presence.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ from typing import Any
18
+
19
+ from wxcli.migration.models import (
20
+ CanonicalReceptionistConfig,
21
+ MapperResult,
22
+ MigrationStatus,
23
+ )
24
+ from wxcli.migration.store import MigrationStore
25
+ from wxcli.migration.transform.mappers.base import (
26
+ Mapper,
27
+ extract_provenance,
28
+ )
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ RECEPTIONIST_THRESHOLD = 3
33
+
34
+ _TEMPLATE_KEYWORDS = ("attendant", "reception", "lobby", "operator", "front desk")
35
+
36
+
37
+ def _extract_blf_entries(state: dict[str, Any]) -> list[dict[str, Any]]:
38
+ """Extract BLF entries from phone pre_migration_state."""
39
+ blf = state.get("busyLampFields")
40
+ if blf is None:
41
+ return []
42
+ if isinstance(blf, list):
43
+ return blf
44
+ if isinstance(blf, dict):
45
+ entries = blf.get("busyLampField", [])
46
+ if isinstance(entries, list):
47
+ return entries
48
+ if isinstance(entries, dict):
49
+ return [entries]
50
+ return []
51
+
52
+
53
+ class ReceptionistMapper(Mapper):
54
+ """Detect CUCM receptionist users and produce CanonicalReceptionistConfig."""
55
+
56
+ name = "receptionist_mapper"
57
+ depends_on = [
58
+ "monitoring_mapper",
59
+ "device_layout_mapper",
60
+ "location_mapper",
61
+ "line_mapper",
62
+ "user_mapper",
63
+ ]
64
+
65
+ def map(self, store: MigrationStore) -> MapperResult:
66
+ result = MapperResult()
67
+ seen_users: set[str] = set()
68
+
69
+ for phone_data in store.get_objects("phone"):
70
+ phone_id = phone_data["canonical_id"]
71
+ state = phone_data.get("pre_migration_state") or {}
72
+
73
+ # Resolve owner
74
+ user_refs = store.find_cross_refs(phone_id, "device_owned_by_user")
75
+ if not user_refs:
76
+ continue
77
+ user_id = user_refs[0]
78
+ user_name = user_id.split(":", 1)[-1]
79
+
80
+ # Skip duplicate (multi-device)
81
+ if user_name in seen_users:
82
+ continue
83
+ seen_users.add(user_name)
84
+
85
+ # Score receptionist likelihood
86
+ score, reasons = self._score(store, user_id, phone_id, state)
87
+ if score < RECEPTIONIST_THRESHOLD:
88
+ continue
89
+
90
+ blf_entries = _extract_blf_entries(state)
91
+ blf_count = len(blf_entries)
92
+
93
+ # Get KEM info from device layout
94
+ phone_name = phone_id.split(":", 1)[-1]
95
+ layout = store.get_object(f"device_layout:{phone_name}")
96
+ layout_data = layout or {}
97
+ kem_keys = (
98
+ layout_data.get("resolved_kem_keys")
99
+ or (layout_data.get("pre_migration_state") or {}).get("resolved_kem_keys")
100
+ or []
101
+ )
102
+
103
+ # Get monitored members from existing monitoring list
104
+ monitored_members = self._get_monitored_members(store, user_name)
105
+
106
+ # Check main number
107
+ is_main = self._is_main_number_holder(store, user_id)
108
+
109
+ # Get location
110
+ user_obj = store.get_object(user_id)
111
+ user_data = user_obj or {}
112
+ location_cid = (
113
+ user_data.get("location_id")
114
+ or (user_data.get("pre_migration_state") or {}).get("location_id")
115
+ or ""
116
+ )
117
+
118
+ template_name = state.get("phoneTemplateName") or ""
119
+
120
+ prov = extract_provenance(phone_data)
121
+ config = CanonicalReceptionistConfig(
122
+ canonical_id=f"receptionist_config:{user_name}",
123
+ provenance=prov,
124
+ status=MigrationStatus.ANALYZED,
125
+ user_canonical_id=user_id,
126
+ location_canonical_id=location_cid,
127
+ blf_count=blf_count,
128
+ has_kem=bool(kem_keys),
129
+ kem_key_count=len(kem_keys),
130
+ template_name=template_name,
131
+ detection_score=score,
132
+ detection_reasons=reasons,
133
+ monitored_members=monitored_members,
134
+ is_main_number_holder=is_main,
135
+ )
136
+ store.upsert_object(config)
137
+ result.objects_created += 1
138
+
139
+ # Cross-refs
140
+ store.add_cross_ref(user_id, config.canonical_id, "user_has_receptionist_config")
141
+
142
+ return result
143
+
144
+ def _score(
145
+ self,
146
+ store: MigrationStore,
147
+ user_id: str,
148
+ phone_id: str,
149
+ state: dict[str, Any],
150
+ ) -> tuple[int, list[str]]:
151
+ """Score a phone owner for receptionist likelihood."""
152
+ score = 0
153
+ reasons: list[str] = []
154
+
155
+ # Signal 1: BLF count
156
+ blf_entries = _extract_blf_entries(state)
157
+ blf_count = len(blf_entries)
158
+ if blf_count >= 20:
159
+ score += 3
160
+ reasons.append(f"{blf_count} BLF entries (20+)")
161
+ elif blf_count >= 10:
162
+ score += 2
163
+ reasons.append(f"{blf_count} BLF entries (10+)")
164
+
165
+ # Signal 2: KEM presence (via device layout)
166
+ phone_name = phone_id.split(":", 1)[-1]
167
+ layout = store.get_object(f"device_layout:{phone_name}")
168
+ if layout:
169
+ kem_keys = (
170
+ layout.get("resolved_kem_keys")
171
+ or (layout.get("pre_migration_state") or {}).get("resolved_kem_keys")
172
+ or []
173
+ )
174
+ if kem_keys:
175
+ score += 2
176
+ reasons.append(f"KEM with {len(kem_keys)} keys")
177
+
178
+ # Signal 3: Template name keywords
179
+ # AXL returns reference fields as dicts with _value_1 + uuid keys
180
+ raw_template = state.get("phoneTemplateName")
181
+ if isinstance(raw_template, dict):
182
+ raw_template = raw_template.get("_value_1", "")
183
+ template_name = (raw_template or "").lower()
184
+ for keyword in _TEMPLATE_KEYWORDS:
185
+ if keyword in template_name:
186
+ score += 2
187
+ reasons.append(f"Template name contains '{keyword}'")
188
+ break
189
+
190
+ # Signal 4: Main number assignment
191
+ if self._is_main_number_holder(store, user_id):
192
+ score += 1
193
+ reasons.append("Holds location main number")
194
+
195
+ return score, reasons
196
+
197
+ def _is_main_number_holder(self, store: MigrationStore, user_id: str) -> bool:
198
+ """Check if the user holds a line matching their location's main number."""
199
+ loc_refs = store.find_cross_refs(user_id, "user_in_location")
200
+ if not loc_refs:
201
+ return False
202
+ loc_id = loc_refs[0]
203
+ loc_obj = store.get_object(loc_id)
204
+ if not loc_obj:
205
+ return False
206
+ main_number = (
207
+ loc_obj.get("main_number")
208
+ or (loc_obj.get("pre_migration_state") or {}).get("main_number")
209
+ or ""
210
+ )
211
+ if not main_number:
212
+ return False
213
+ # Check via user's own extension first
214
+ user_obj = store.get_object(user_id)
215
+ if user_obj:
216
+ user_state = user_obj.get("pre_migration_state") or {}
217
+ user_ext = user_state.get("extension") or ""
218
+ if user_ext == main_number:
219
+ return True
220
+ # Fallback: check via line cross-refs
221
+ for line_data in store.get_objects("line"):
222
+ line_state = line_data.get("pre_migration_state") or {}
223
+ line_ext = line_state.get("extension") or line_state.get("pattern") or ""
224
+ if line_ext == main_number:
225
+ line_id = line_data["canonical_id"]
226
+ user_refs = store.find_cross_refs(line_id, "line_assigned_to_user")
227
+ if user_id in user_refs:
228
+ return True
229
+ return False
230
+
231
+ def _get_monitored_members(self, store: MigrationStore, user_name: str) -> list[str]:
232
+ """Get resolved monitored member canonical IDs from the monitoring list."""
233
+ ml = store.get_object(f"monitoring_list:{user_name}")
234
+ if not ml:
235
+ return []
236
+ members = (
237
+ ml.get("monitored_members")
238
+ or (ml.get("pre_migration_state") or {}).get("monitored_members")
239
+ or []
240
+ )
241
+ return [
242
+ m["target_canonical_id"]
243
+ for m in members
244
+ if isinstance(m, dict) and m.get("target_canonical_id")
245
+ ]