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,953 @@
1
+ <!-- Not directly verified via CLI — documents wxcadm library (not generated CLI commands) -->
2
+ <!-- wxcadm v4.6.1 review 2026-03-27: All v4.5.0–v4.6.1 changes verified present. XSI(org, person) constructor signature, RealtimeClass(api, data_url, api_fields) signature, org.api.headers usage for XSI auth — all accurately documented. No discrepancies found. -->
3
+ # wxcadm XSI & Real-time Call Monitoring
4
+
5
+ ## Why This Matters
6
+
7
+ XSI (eXtended Services Interface) is the one thing wxcadm can do that wxc_sdk cannot. While wxc_sdk covers the public Webex REST APIs for provisioning, configuration, and admin tasks, it has no access to the real-time call control plane. XSI connects directly to the BroadWorks call control back-end that powers Webex Calling, giving you:
8
+
9
+ - **Real-time event streams** — every call placed, received, held, transferred, parked, and released fires an event you can consume
10
+ - **Programmatic call control** — originate, hold, resume, transfer, conference, park, record, and hang up calls from code
11
+ - **Live call state** — query active calls, get call status, see remote party info and endpoint details
12
+ - **Directory access** — search Enterprise, Group, and Personal directories directly from the call control platform
13
+ - **Service configuration reads** — pull BLF/monitoring, Executive/Assistant, SNR, anonymous call rejection settings live from the platform (not cached admin data)
14
+
15
+ None of this is available through wxc_sdk or the standard Webex REST APIs.
16
+
17
+ ---
18
+
19
+ <!-- Updated by playbook session 2026-03-18 -->
20
+ ## When to Use wxcadm vs Raw HTTP
21
+
22
+ **XSI is wxcadm's killer feature. There is no raw HTTP alternative for any of this.**
23
+
24
+ | Use wxcadm when | Use raw HTTP when |
25
+ |---|---|
26
+ | Real-time call event streaming (XSI-Events) | **Never** -- XSI-Events requires persistent HTTP streaming with heartbeats, auto-refresh, and DNS SRV discovery that wxcadm handles entirely |
27
+ | Programmatic call control (originate, hold, transfer, park, record) | **Never** -- XSI-Actions endpoints are on the BroadWorks XSP, not the standard Webex REST API |
28
+ | Live call state queries, directory search, service config reads | **Never** -- these hit BroadWorks directly via XSI, not available through `api.webex.com` |
29
+ | CRM screen pops, real-time dashboards, call center queue routing | **Never** -- all require XSI event streaming |
30
+
31
+ > **Note:** The playbook uses raw HTTP via `api.session.rest_*()` for standard CRUD operations against `api.webex.com`. XSI operates on an entirely different infrastructure (BroadWorks XSP) and **cannot** be replaced by raw HTTP calls to the Webex REST API. If you need real-time call monitoring or programmatic call control, wxcadm is the only option.
32
+
33
+ ---
34
+
35
+ ## XSI Architecture Overview
36
+
37
+ ```
38
+ Your App (wxcadm)
39
+ |
40
+ |--- XSI-Actions (REST calls → call control, profile, services, directory)
41
+ |--- XSI-Events (long-running HTTP streaming → real-time event delivery)
42
+ |
43
+ v
44
+ Webex Calling XSP (eXtended Services Platform)
45
+ |
46
+ v
47
+ BroadWorks Call Control Back-end
48
+ ```
49
+
50
+ XSI exposes two distinct interfaces:
51
+
52
+ | Interface | Transport | Purpose |
53
+ |-----------|-----------|---------|
54
+ | **XSI-Actions** | Standard REST (JSON/XML) | Call control commands, profile/service reads, directory search |
55
+ | **XSI-Events** | Long-running HTTP stream | Asynchronous event delivery for call state changes |
56
+
57
+ The XSI endpoints are unique per Org. wxcadm discovers them via the `v1/organizations/{id}?callingData=true` API call, which returns:
58
+ - `xsiActionsEndpoint` — base URL for call control and data queries
59
+ - `xsiEventsEndpoint` — base URL for event subscription management
60
+ - `xsiEventsChannelEndpoint` — base URL for opening streaming channels
61
+ - `xsiDomain` — domain used for SRV record lookups to find all XSPs
62
+
63
+ ---
64
+
65
+ ## Setup & Authentication
66
+
67
+ ### Prerequisites
68
+
69
+ 1. **XSI must be enabled on your Webex Org.** This is not on by default. Contact Cisco TAC to request activation.
70
+ 2. **API token scopes**: Your Integration or access token must include:
71
+ - `spark-admin:xsi` — admin-level XSI access
72
+ - `spark:xsi` — user-level XSI access (if enabled)
73
+ - The 12-hour developer token from developer.webex.com includes all necessary scopes automatically.
74
+
75
+ ### Initializing XSI
76
+
77
+ Two approaches to get XSI endpoints:
78
+
79
+ ```python
80
+ import wxcadm
81
+
82
+ access_token = "Your API Access Token"
83
+
84
+ # Option 1: Get XSI endpoints at Webex init time
85
+ webex = wxcadm.Webex(access_token, get_xsi=True)
86
+
87
+ # Option 2: Get XSI endpoints later
88
+ webex = wxcadm.Webex(access_token)
89
+ webex.org.get_xsi_endpoints()
90
+ ```
91
+
92
+ `get_xsi_endpoints()` calls the Org API with `callingData=true` and populates `webex.org.xsi` with the four endpoint URLs. If XSI is not enabled, it returns `None`.
93
+
94
+ ### Starting an XSI Session for a Person
95
+
96
+ XSI operates per-user. You must start an XSI session for each Person you want to control or query:
97
+
98
+ ```python
99
+ person = webex.org.get_person_by_email("user@domain.com")
100
+ person.start_xsi() # Creates person.xsi (an XSI instance)
101
+ ```
102
+
103
+ **Signature:**
104
+ ```python
105
+ Person.start_xsi(get_profile: bool = False, cache: bool = False) -> XSI
106
+ ```
107
+
108
+ - `get_profile=True` — automatically fetches the XSI profile (phone number, extension, etc.) during init
109
+ - `cache=True` — caches XSI data so subsequent property accesses don't re-query the platform. Default is live (uncached).
110
+
111
+ The `start_xsi()` method creates an `XSI` instance and stores it at `person.xsi`. The XSI instance sets up:
112
+ - HTTP headers with `X-BroadWorks-Protocol-Version: 25.0`, JSON accept/content-type, plus the Webex auth token
113
+ - Query params with `format=json`
114
+ - A user ID derived from the Person's Spark ID (the BroadWorks user identifier)
115
+
116
+ ---
117
+
118
+ ## XSI Profile & Services (XSI-Actions)
119
+
120
+ Once `person.start_xsi()` has been called, you can read data directly from the call control platform.
121
+
122
+ ### Profile
123
+
124
+ ```python
125
+ person.start_xsi(get_profile=True)
126
+ print(person.xsi.profile)
127
+ ```
128
+
129
+ Returns a dict with:
130
+
131
+ | Key | Description |
132
+ |-----|-------------|
133
+ | `country_code` | Country dialing code |
134
+ | `number` | Phone number (if assigned) |
135
+ | `extension` | Extension (if assigned) |
136
+ | `user_id` | BroadWorks user ID |
137
+ | `group_id` | BroadWorks group ID (maps to Webex Calling Location) |
138
+ | `service_provider` | Service provider identifier |
139
+ | `registrations_url` | URL to query device registrations |
140
+ | `schedule_url` | URL to query user schedules |
141
+ | `fac_url` | URL to query Feature Access Codes |
142
+ | `raw` | The complete raw profile response |
143
+
144
+ ### Device Registrations
145
+
146
+ ```python
147
+ registrations = person.xsi.registrations
148
+ ```
149
+
150
+ Returns the device registrations associated with the person (phones, Webex app, etc.). Requires the profile to have been fetched first (auto-fetches if needed).
151
+
152
+ ### Services
153
+
154
+ ```python
155
+ services = person.xsi.get_services()
156
+ ```
157
+
158
+ Queries all assigned services and their configurations. Returns a dict keyed by service name with the full service config as the value. Services without configuration data are stored as `True`.
159
+
160
+ ### Individual Service Properties
161
+
162
+ These are available as properties with lazy loading (fetched on first access, or every access if `cache=False`):
163
+
164
+ ```python
165
+ person.xsi.executive # Executive settings
166
+ person.xsi.executive_assistant # Executive Assistant settings
167
+ person.xsi.monitoring # BLF/Busy Lamp Field settings
168
+ person.xsi.single_number_reach # SNR / Office Anywhere settings
169
+ person.xsi.anonymous_call_rejection # Anonymous Call Rejection settings
170
+ person.xsi.alternate_numbers # Alternate Numbers
171
+ ```
172
+
173
+ Each property queries: `GET /v2.0/user/{userId}/services/{ServiceName}`
174
+
175
+ ### Feature Access Codes
176
+
177
+ ```python
178
+ fac_list = person.xsi.fac_list
179
+ for fac in fac_list:
180
+ print(f"{fac.feature}: {fac.code} (alt: {fac.alternate_code})")
181
+ ```
182
+
183
+ Returns a list of `FeatureAccessCode` objects with `.feature`, `.code`, and `.alternate_code` attributes.
184
+
185
+ ### Directory Search
186
+
187
+ ```python
188
+ results = person.xsi.directory(
189
+ type='Enterprise', # or 'Group', 'Personal'
190
+ first_name='John',
191
+ last_name='Smith',
192
+ any_match=False # True = OR logic across filters
193
+ )
194
+ ```
195
+
196
+ **Signature:**
197
+ ```python
198
+ XSI.directory(
199
+ type: str = 'Enterprise',
200
+ first_name: str = None,
201
+ last_name: str = None,
202
+ name: str = None,
203
+ user_id: str = None,
204
+ group_id: str = None,
205
+ number: str = None,
206
+ extension: str = None,
207
+ mobile_number: str = None,
208
+ department: str = None,
209
+ email: str = None,
210
+ any_match: bool = False
211
+ ) -> list[dict]
212
+ ```
213
+
214
+ **Filter availability by directory type:**
215
+
216
+ | Directory Type | Available Filters |
217
+ |----------------|-------------------|
218
+ | Enterprise | first_name, last_name, name, user_id, group_id, number, extension, mobile_number, department, email |
219
+ | Group | first_name, last_name, name, user_id, group_id, number, extension, mobile_number, department, email |
220
+ | Personal | name, number |
221
+
222
+ **Case-insensitive search:** Append `/i` to the search string, e.g. `first_name='John/i'` matches "John", "john", "JOHN".
223
+
224
+ Handles pagination automatically (fetches 50 records at a time until all results are retrieved).
225
+
226
+ ---
227
+
228
+ ## Call Control via XSI (XSI-Actions)
229
+
230
+ ### Creating and Originating Calls
231
+
232
+ ```python
233
+ person.start_xsi()
234
+
235
+ # Create a call object, then originate
236
+ call = person.xsi.new_call()
237
+ call.originate("17192662837")
238
+
239
+ # Or create and originate in one step
240
+ call = person.xsi.new_call(address="17192662837")
241
+
242
+ # One-liner for simple click-to-dial
243
+ person.start_xsi().new_call().originate("17192662837")
244
+ ```
245
+
246
+ **`XSI.new_call()` signature:**
247
+ ```python
248
+ XSI.new_call(
249
+ address: str = None, # Phone number to dial
250
+ phone: str = 'All', # 'All', 'Primary', or 'SharedCallAppearance'
251
+ aor: str = None # Address-of-record for shared devices
252
+ ) -> Call
253
+ ```
254
+
255
+ **`Call.originate()` signature:**
256
+ ```python
257
+ Call.originate(
258
+ address: str, # Phone number or extension to call
259
+ comment: str = "", # Text comment attached to the call
260
+ phone: str = 'All', # Device selection
261
+ aor: str = None, # Address-of-record for shared devices
262
+ executive: str = None # Extension/number of executive (for assistant calls)
263
+ ) -> bool
264
+ ```
265
+
266
+ On success, the `Call` object gets populated with:
267
+ - `call.id` — the XSI call ID
268
+ - `call._external_tracking_id` — XSI external tracking ID
269
+
270
+ ### Querying Active Calls
271
+
272
+ ```python
273
+ active_calls = person.xsi.calls # Returns list[Call]
274
+ ```
275
+
276
+ The `calls` property queries `GET /v2.0/user/{userId}/calls` and creates fresh `Call` instances for each active call. Previous instances are destroyed on each access.
277
+
278
+ ### Call Status
279
+
280
+ ```python
281
+ status = call.status
282
+ ```
283
+
284
+ Returns a dict:
285
+ ```python
286
+ {
287
+ 'network_call_id': str, # Unique network-side call ID
288
+ 'personality': str, # 'Originator' or 'Terminator'
289
+ 'state': str, # 'Active', 'Held', 'Alerting', etc.
290
+ 'remote_party': {
291
+ 'address': str, # Remote party's address
292
+ 'call_type': str # Call type
293
+ },
294
+ 'endpoint': {
295
+ 'type': str, # Endpoint type
296
+ 'aor': str # Address of Record
297
+ },
298
+ 'appearance': str, # Call Appearance number
299
+ 'diversion_inhibited': bool,
300
+ 'start_time': str, # UNIX timestamp
301
+ 'answer_time': str, # UNIX timestamp (None if unanswered)
302
+ 'status_time': int # UNIX timestamp of this status query
303
+ }
304
+ ```
305
+
306
+ ### Hold and Resume
307
+
308
+ ```python
309
+ call.hold() # Places the call on hold; sets call.held = True
310
+ call.resume() # Takes the call off hold; sets call.held = False
311
+ ```
312
+
313
+ Both return an `XSIResponse` object (truthy on success, falsy on failure).
314
+
315
+ ### Answer an Incoming Call
316
+
317
+ ```python
318
+ # Answer the most recent incoming call
319
+ answered_call = person.xsi.answer_call()
320
+
321
+ # Or answer a specific call
322
+ answered_call = person.xsi.answer_call(call_id="the-call-id")
323
+ ```
324
+
325
+ ### Hang Up
326
+
327
+ ```python
328
+ call.hangup() # Normal hangup
329
+ call.hangup(decline=True) # Decline/reject the call
330
+ ```
331
+
332
+ ### Transfer
333
+
334
+ ```python
335
+ # Blind transfer
336
+ call.transfer("2345")
337
+
338
+ # VM transfer (sends directly to target's voicemail)
339
+ call.transfer("2345", type="vm")
340
+
341
+ # Mute transfer
342
+ call.transfer("2345", type="mute")
343
+
344
+ # Attended transfer (puts current call on hold, dials target)
345
+ call.transfer("2345", type="attended")
346
+ # ... agent and target talk ...
347
+ call.finish_transfer() # Completes the transfer
348
+ ```
349
+
350
+ **For Call Queue calls:** the address parameter is sent as `phoneno` instead of `address` (handled automatically).
351
+
352
+ ### Conference
353
+
354
+ ```python
355
+ # Conference from an attended transfer (all parties joined)
356
+ call.transfer("2345", type="attended")
357
+ # ... talk to the target ...
358
+ call.conference() # Bridges all parties
359
+ call.finish_transfer() # Drop the initiator, leave others connected
360
+
361
+ # Conference with a new party (holds current call, dials, bridges on answer)
362
+ conference = call.conference(address="5678")
363
+ ```
364
+
365
+ **`Conference` class methods:**
366
+
367
+ ```python
368
+ conference.mute(call_id) # Mute a participant (they can still hear)
369
+ conference.deaf(call_id) # Deaf a participant (they can still be heard)
370
+ ```
371
+
372
+ ### Call Park
373
+
374
+ ```python
375
+ # Group Call Park (auto-assigned extension; user must be in a Park Group)
376
+ parked_extension = call.park()
377
+
378
+ # Park at a specific Call Park Extension
379
+ parked_extension = call.park(extension="8001")
380
+ ```
381
+
382
+ Returns the extension where the call was parked. Raises `NotAllowed` if the user isn't in a Park Group or the extension is busy.
383
+
384
+ ### Call Recording Control
385
+
386
+ ```python
387
+ call.recording("start") # Start recording
388
+ call.recording("stop") # Stop recording (On Demand mode only)
389
+ call.recording("pause") # Pause recording
390
+ call.recording("resume") # Resume paused recording
391
+ ```
392
+
393
+ Requires Call Recording to be enabled for the user. Raises `NotAllowed` if not enabled.
394
+
395
+ **Known bug in source (verified 2026-03-18):** The `recording()` method has a duplicate `elif action.lower() == "resume"` at line 1528 that was meant to be `"pause"`. The first `"resume"` branch (line 1524) correctly calls `ResumeRecording`. The second (line 1528) maps to `PauseRecording` but is dead code (shadowed by line 1524). This means `recording("pause")` falls through to the `else` branch and raises `ValueError`. **Workaround:** Call the XSI PauseRecording endpoint directly: `requests.put(url + f"/{call_id}/PauseRecording", headers=headers)`.
396
+
397
+ ### DTMF
398
+
399
+ ```python
400
+ call.send_dtmf("12345#") # Send digits
401
+ call.send_dtmf("23456#,123") # Comma = pause between digits
402
+ ```
403
+
404
+ ### Executive/Assistant Call Handling
405
+
406
+ ```python
407
+ # Assistant places a call on behalf of the Executive
408
+ assistant = webex.org.get_person_by_email("assistant@company.com")
409
+ assistant.start_xsi()
410
+ call = assistant.xsi.new_call()
411
+ call.originate("7192662837", executive="1234") # 1234 = exec's extension
412
+
413
+ # Push the call to the Executive's devices
414
+ call.exec_push()
415
+ ```
416
+
417
+ ### Reconnect
418
+
419
+ ```python
420
+ call.reconnect() # Retrieves call from hold AND releases all other calls
421
+ ```
422
+
423
+ ### Attach to an Existing Call
424
+
425
+ When you know a call ID (e.g., from an XSI Event), you can create a `Call` object for it:
426
+
427
+ ```python
428
+ # From an XSI instance
429
+ call = person.xsi.attach_call(call_id="known-call-id")
430
+
431
+ # From an XSICallQueue
432
+ queue = wxcadm.XSICallQueue(call_queue_id="queue-id", org=webex.org)
433
+ call = queue.attach_call(call_id="known-call-id")
434
+ ```
435
+
436
+ ---
437
+
438
+ ## XSI Response Handling
439
+
440
+ All call control methods (hold, resume, transfer, hangup, etc.) return an `XSIResponse` object:
441
+
442
+ ```python
443
+ response = call.hold()
444
+ if response: # Truthy when HTTP status is OK
445
+ print("Hold successful")
446
+ else:
447
+ print(f"Failed: {response.summary}")
448
+
449
+ response.success # bool
450
+ response.raw_response # dict or text
451
+ response.summary # Error summary text (if error)
452
+ ```
453
+
454
+ ---
455
+
456
+ ## XSI-Events: Real-time Call Monitoring
457
+
458
+ This is the core differentiator. XSI-Events provides a persistent, streaming connection that delivers every call event in the organization (or for a specific user) as it happens.
459
+
460
+ ### Architecture
461
+
462
+ ```
463
+ XSIEvents (org-level)
464
+ └─ XSIEventsChannelSet (groups channels + manages subscriptions)
465
+ ├─ XSIEventsChannel (long-running HTTP stream to XSP #1)
466
+ │ ├─ _channel_daemon thread (reads streaming events)
467
+ │ └─ _heartbeat_daemon thread (sends heartbeats every 15s)
468
+ ├─ XSIEventsChannel (long-running HTTP stream to XSP #2)
469
+ │ ├─ _channel_daemon thread
470
+ │ └─ _heartbeat_daemon thread
471
+ └─ XSIEventsSubscription(s) (what events to receive)
472
+ ```
473
+
474
+ Key design decisions in wxcadm:
475
+ - **One channel per XSP**: wxcadm does a DNS SRV lookup on the XSI domain (`_xsi-client._tcp.{domain}`) to discover all XSP servers, then opens a streaming channel to each one
476
+ - **Daemon threads**: Each channel runs two daemon threads — one reading the stream, one sending heartbeats
477
+ - **Python Queue**: All events are delivered into a standard `queue.Queue` you provide, decoupling event production from consumption
478
+ - **Automatic ACKs**: wxcadm automatically acknowledges each event to the server (required by the XSI protocol)
479
+ - **Auto-refresh**: Channels and subscriptions are refreshed every hour (7200-second expiry) to stay alive
480
+
481
+ ### Basic Event Monitoring Pattern
482
+
483
+ ```python
484
+ import queue
485
+ import wxcadm
486
+
487
+ access_token = "Your API Access Token"
488
+ webex = wxcadm.Webex(access_token, get_xsi=True)
489
+
490
+ # Create the XSIEvents instance (org-level)
491
+ events = wxcadm.XSIEvents(webex.org)
492
+
493
+ # Create a queue — set maxsize to prevent unbounded memory growth
494
+ events_queue = queue.Queue(maxsize=500)
495
+
496
+ # Open a channel set (auto-discovers XSPs, creates channels + heartbeat threads)
497
+ channel = events.open_channel(events_queue)
498
+
499
+ # Subscribe to an event package
500
+ subscription = channel.subscribe(["Advanced Call"])
501
+
502
+ # Process events as they arrive
503
+ while True:
504
+ event = events_queue.get()
505
+ event_type = event['xsi:Event']['xsi:eventData']['@xsi1:type']
506
+ print(f"Event: {event_type}")
507
+ ```
508
+
509
+ ### Event Packages
510
+
511
+ The `subscribe()` method accepts an event package name. The primary package:
512
+
513
+ | Package | Description |
514
+ |---------|-------------|
515
+ | `Advanced Call` | All call-related events (originate, ring, answer, hold, transfer, release, hook status, etc.) |
516
+ | `Call Center Queue` | Call Queue / ACD events (calls entering queue, queue state changes, etc.) |
517
+
518
+ > **Note:** wxcadm does not validate event package names — it passes whatever string you provide to the XSI server. The two packages shown above are confirmed in wxcadm examples. Additional BroadWorks-defined packages may be available depending on your org's configuration.
519
+
520
+ ### Subscription Scopes
521
+
522
+ ```python
523
+ # Org-wide: monitor ALL calls across the entire organization
524
+ subscription = channel.subscribe(["Advanced Call"])
525
+
526
+ # Per-user: monitor calls for a specific person only
527
+ person = webex.org.get_person_by_email("user@domain.com")
528
+ subscription = channel.subscribe(["Advanced Call"], person=person)
529
+ ```
530
+
531
+ Org-wide subscription uses the `serviceprovider/{enterpriseId}` endpoint. Per-user subscription uses the `user/{userId}` endpoint.
532
+
533
+ ### XSI Event Types
534
+
535
+ A single outbound call from a Webex Calling desk phone generates **six events**:
536
+
537
+ | Event Type | When It Fires |
538
+ |------------|---------------|
539
+ | `xsi:HookStatusEvent` | Phone goes off-hook |
540
+ | `xsi:CallOriginatedEvent` | Call is initiated (dialing) |
541
+ | `xsi:CallUpdatedEvent` | Call state changes (ringing, etc.) |
542
+ | `xsi:CallAnsweredEvent` | Remote party answers |
543
+ | `xsi:CallReleasedEvent` | Call ends |
544
+ | `xsi:HookStatusEvent` | Phone goes on-hook |
545
+
546
+ Additional event types you will see in practice:
547
+
548
+ | Event Type | When It Fires |
549
+ |------------|---------------|
550
+ | `xsi:CallReceivedEvent` | Incoming call arrives |
551
+ | `xsi:CallHeldEvent` | Call placed on hold |
552
+ | `xsi:CallRetrievedEvent` | Call taken off hold |
553
+ | `xsi:CallTransferredEvent` | Call is transferred |
554
+ | `xsi:CallSubscriptionEvent` | Subscription state change |
555
+ | `xsi:ChannelTerminatedEvent` | Channel was closed (not queued — handled internally) |
556
+ | `xsi:ACDCallAddedEvent` | Call added to a Call Queue (requires "Call Center Queue" subscription) |
557
+
558
+ > **Note:** The event types listed above are confirmed from wxcadm source and documentation. The full set of possible event types is defined by the BroadWorks XSI schema and may include additional types not shown here. Use the `@xsi1:type` field in each event for runtime discovery.
559
+
560
+ ### Event Payload Structure
561
+
562
+ Events are delivered as Python OrderedDicts (parsed from XML via `xmltodict`). The general structure:
563
+
564
+ ```python
565
+ {
566
+ 'xsi:Event': {
567
+ '@xsi1:type': 'xsi:CallOriginatedEvent', # Event type
568
+ 'xsi:eventID': '12345-abcde', # Unique event ID (used for ACK)
569
+ 'xsi:sequenceNumber': '42', # Sequence number
570
+ 'xsi:userId': 'user@domain.com', # User the event is for
571
+ 'xsi:externalApplicationId': 'app-uuid', # Your application ID
572
+ 'xsi:subscriptionId': 'sub-uuid', # Subscription that matched
573
+ 'xsi:channelId': 'channel-uuid', # Channel it arrived on
574
+ 'xsi:eventData': {
575
+ '@xsi1:type': 'xsi:CallEvent', # Data type
576
+ 'xsi:call': {
577
+ 'xsi:callId': 'callhalf-123',
578
+ 'xsi:extTrackingId': 'tracking-456',
579
+ 'xsi:personality': 'Originator', # or 'Terminator'
580
+ 'xsi:state': 'Alerting', # Call state
581
+ 'xsi:remoteParty': {
582
+ 'xsi:address': {
583
+ 'xsi:type': 'E164',
584
+ '#text': '+17192662837'
585
+ },
586
+ 'xsi:callType': 'Network'
587
+ },
588
+ 'xsi:startTime': '1710000000000',
589
+ 'xsi:answerTime': '1710000005000',
590
+ # ... additional fields vary by event type
591
+ }
592
+ }
593
+ }
594
+ }
595
+ ```
596
+
597
+ ### Event Processing Best Practices
598
+
599
+ **Queue sizing:** A busy org can generate hundreds of events per second. Set `maxsize` large enough to absorb bursts but not so large it consumes all memory. Start with 500-1000 for a small org.
600
+
601
+ **Threaded consumer:** For production, process the queue in its own thread:
602
+
603
+ ```python
604
+ import queue
605
+ import threading
606
+ import wxcadm
607
+
608
+ def event_processor(q):
609
+ """Process events in a dedicated thread."""
610
+ while True:
611
+ try:
612
+ event = q.get(timeout=5)
613
+ except queue.Empty:
614
+ continue
615
+
616
+ event_data = event['xsi:Event']
617
+ event_type = event_data['xsi:eventData']['@xsi1:type']
618
+ user_id = event_data.get('xsi:userId', 'unknown')
619
+
620
+ if event_type == 'xsi:CallOriginatedEvent':
621
+ handle_new_call(event_data)
622
+ elif event_type == 'xsi:CallAnsweredEvent':
623
+ handle_answered(event_data)
624
+ elif event_type == 'xsi:CallReleasedEvent':
625
+ handle_released(event_data)
626
+ # ... etc
627
+
628
+ q.task_done()
629
+
630
+ # Setup
631
+ webex = wxcadm.Webex(access_token, get_xsi=True)
632
+ events = wxcadm.XSIEvents(webex.org)
633
+ events_queue = queue.Queue(maxsize=1000)
634
+ channel = events.open_channel(events_queue)
635
+ channel.subscribe(["Advanced Call"])
636
+
637
+ # Start processor thread
638
+ processor = threading.Thread(target=event_processor, args=(events_queue,), daemon=True)
639
+ processor.start()
640
+
641
+ # Keep main thread alive
642
+ import time
643
+ while True:
644
+ time.sleep(60)
645
+ ```
646
+
647
+ ---
648
+
649
+ ## Channel Management
650
+
651
+ ### Channel Lifecycle
652
+
653
+ When `open_channel()` is called, wxcadm:
654
+
655
+ 1. Does a DNS SRV lookup: `_xsi-client._tcp.{xsi_domain}`
656
+ 2. Opens one `XSIEventsChannel` per SRV record (per XSP server)
657
+ 3. Each channel sends an XML `<Channel>` POST to `{endpoint}/v2.0/channel` with:
658
+ - `channelSetId` — groups channels together
659
+ - `applicationId` — unique per XSIEvents instance
660
+ - `priority=1`, `weight=50`, `expires=7200` (2 hours)
661
+ 4. The POST is a **streaming request** — the connection stays open and events flow through it
662
+ 5. A heartbeat thread sends `PUT .../channel/{id}/heartbeat` every 15 seconds
663
+ 6. Channels auto-refresh every hour (before the 7200s expiry)
664
+
665
+ ### Channel Failure Recovery
666
+
667
+ wxcadm has built-in resilience:
668
+
669
+ - If a channel fails to establish (no response within 40 seconds / 20 retries), it is marked inactive and a restart is attempted
670
+ - If a heartbeat gets HTTP 404 or 401, the channel is marked inactive and restarted
671
+ - If the streaming connection drops, the channel loop ends and triggers `restart_failed_channel()` (with a 60-second backoff)
672
+ - `restart_failed_channel()` creates a new channel to the same endpoint and sends a DELETE on the old one
673
+
674
+ ### Checking Active Channels
675
+
676
+ ```python
677
+ active = channel.active_channels # List of XSIEventsChannel with active=True
678
+ ```
679
+
680
+ ### Closing Everything
681
+
682
+ ```python
683
+ channel.close() # Deletes all subscriptions, then deletes all channels
684
+ ```
685
+
686
+ ---
687
+
688
+ ## Subscription Management
689
+
690
+ ### Subscribe
691
+
692
+ ```python
693
+ # Org-wide
694
+ subscription = channel.subscribe(["Advanced Call"])
695
+
696
+ # Per-user
697
+ subscription = channel.subscribe(["Advanced Call"], person=some_person)
698
+ ```
699
+
700
+ Returns an `XSIEventsSubscription` instance (or `False` if subscription fails). The subscription has:
701
+ - `subscription.id` — server-assigned subscription ID
702
+ - `subscription.active` — boolean
703
+ - `subscription.event_package` — what was subscribed to
704
+
705
+ ### Unsubscribe
706
+
707
+ ```python
708
+ # Unsubscribe a specific subscription
709
+ channel.unsubscribe(subscription.id)
710
+
711
+ # Unsubscribe all
712
+ channel.unsubscribe("all")
713
+ ```
714
+
715
+ ### Subscription Refresh
716
+
717
+ Subscriptions auto-refresh every hour (checked during heartbeat processing). The refresh sends a PUT with a new 7200-second expiry.
718
+
719
+ ---
720
+
721
+ ## XSICallQueue: Call Queue Call Control
722
+
723
+ When monitoring XSI Events for a Call Queue, incoming calls have a `targetId` identifying the queue. Use `XSICallQueue` to control those calls:
724
+
725
+ ```python
726
+ # Create a Call Queue controller from an event's targetId
727
+ call_queue = wxcadm.XSICallQueue(
728
+ call_queue_id="queue-id-from-event",
729
+ org=webex.org
730
+ )
731
+
732
+ # Attach to a specific call (from event's callId)
733
+ call = call_queue.attach_call(call_id="call-id-from-event")
734
+
735
+ # Now you can control the call
736
+ call.transfer("2345") # Transfer to an agent
737
+ call.hangup() # Release the call
738
+ ```
739
+
740
+ Call Queue calls support basic call control (transfer, hangup) but not all operations available to regular user calls. The wxcadm source confirms that Call Queue calls use a different URL base (`/v2.0/callcenter/{id}/calls` vs `/v2.0/user/{id}/calls`) and that the `transfer()` method sends the address as `phoneno` instead of `address` for Call Queue calls. The exact subset of operations the XSI server accepts on the callcenter endpoint is server-determined.
741
+
742
+ ---
743
+
744
+ ## Monitoring Features (REST API)
745
+
746
+ Separate from XSI-Events, wxcadm provides a `MonitoringList` class for managing BLF (Busy Lamp Field) monitoring configurations through the Webex REST API:
747
+
748
+ ```python
749
+ # Get a person's monitoring list
750
+ monitoring = person.monitoring # Returns MonitoringList
751
+
752
+ # Monitored elements (resolved to Person, Workspace, VirtualLine, or CallParkExtension)
753
+ for element in monitoring.monitored_elements:
754
+ print(element)
755
+
756
+ # Add a monitored element
757
+ monitoring.add(another_person)
758
+
759
+ # Remove a monitored element
760
+ monitoring.remove(another_person)
761
+
762
+ # Copy monitoring config to another user
763
+ monitoring.copy_to(other_person)
764
+
765
+ # Copy from another user
766
+ monitoring.copy_from(source_person)
767
+
768
+ # Replace entire monitoring config
769
+ monitoring.replace(other_monitoring_list)
770
+
771
+ # Clear all monitoring
772
+ monitoring.clear()
773
+ ```
774
+
775
+ The `MonitoringList` resolves monitored element IDs to actual wxcadm objects (Person, Workspace, VirtualLine, CallParkExtension) automatically.
776
+
777
+ **Note:** This is the REST API `MonitoringList` class, not the XSI `monitoring` property. The XSI `person.xsi.monitoring` property reads BLF settings directly from the call control platform via XSI.
778
+
779
+ ---
780
+
781
+ ## Use Cases
782
+
783
+ ### 1. Real-time Agent Dashboard
784
+
785
+ Monitor all call events org-wide, track which agents are on calls, display live call states:
786
+
787
+ ```python
788
+ import queue
789
+ import wxcadm
790
+
791
+ webex = wxcadm.Webex(token, get_xsi=True)
792
+ events = wxcadm.XSIEvents(webex.org)
793
+ q = queue.Queue(maxsize=1000)
794
+ channel = events.open_channel(q)
795
+ channel.subscribe(["Advanced Call"])
796
+
797
+ active_calls = {} # Track calls by user
798
+
799
+ while True:
800
+ event = q.get()
801
+ evt = event['xsi:Event']
802
+ evt_type = evt['xsi:eventData']['@xsi1:type']
803
+ user = evt.get('xsi:userId', '')
804
+
805
+ if 'Originated' in evt_type or 'Received' in evt_type:
806
+ active_calls[user] = evt['xsi:eventData']
807
+ elif 'Released' in evt_type:
808
+ active_calls.pop(user, None)
809
+
810
+ # Push active_calls to your dashboard via WebSocket, SSE, etc.
811
+ update_dashboard(active_calls)
812
+ ```
813
+
814
+ ### 2. CRM Screen Pop
815
+
816
+ When a call comes in, look up the caller in your CRM and pop the record:
817
+
818
+ ```python
819
+ while True:
820
+ event = q.get()
821
+ evt = event['xsi:Event']
822
+ if evt['xsi:eventData']['@xsi1:type'] == 'xsi:CallReceivedEvent':
823
+ caller = evt['xsi:eventData']['xsi:call']['xsi:remoteParty']
824
+ phone = caller['xsi:address'].get('#text', '')
825
+ agent = evt.get('xsi:userId', '')
826
+ crm_record = lookup_crm(phone)
827
+ send_screen_pop(agent, crm_record)
828
+ ```
829
+
830
+ ### 3. Call Center Queue Management
831
+
832
+ Monitor Call Queue events and programmatically route calls:
833
+
834
+ ```python
835
+ while True:
836
+ event = q.get()
837
+ evt = event['xsi:Event']
838
+ target_id = evt.get('xsi:targetId', '')
839
+
840
+ if is_call_queue(target_id):
841
+ call_id = evt['xsi:eventData']['xsi:call']['xsi:callId']
842
+ cq = wxcadm.XSICallQueue(target_id, webex.org)
843
+ call = cq.attach_call(call_id)
844
+
845
+ # Route based on business logic
846
+ best_agent = find_available_agent()
847
+ call.transfer(best_agent.extension)
848
+ ```
849
+
850
+ ### 4. Compliance / Call Logging
851
+
852
+ Capture every call event for audit:
853
+
854
+ ```python
855
+ import json
856
+ from datetime import datetime
857
+
858
+ while True:
859
+ event = q.get()
860
+ evt = event['xsi:Event']
861
+ log_entry = {
862
+ 'timestamp': datetime.utcnow().isoformat(),
863
+ 'event_type': evt['xsi:eventData']['@xsi1:type'],
864
+ 'user': evt.get('xsi:userId', ''),
865
+ 'event_id': evt.get('xsi:eventID', ''),
866
+ 'raw': json.dumps(event, default=str)
867
+ }
868
+ write_to_audit_log(log_entry)
869
+ ```
870
+
871
+ ### 5. Click-to-Dial Integration
872
+
873
+ Embed calling into any web app or tool:
874
+
875
+ ```python
876
+ def click_to_dial(agent_email: str, destination: str):
877
+ person = webex.org.get_person_by_email(agent_email)
878
+ person.start_xsi()
879
+ call = person.xsi.new_call(address=destination)
880
+ return call.id # Return call ID for tracking/control
881
+ ```
882
+
883
+ ### 6. Automated Call Recording Control
884
+
885
+ Start/stop recording based on business rules:
886
+
887
+ ```python
888
+ while True:
889
+ event = q.get()
890
+ evt = event['xsi:Event']
891
+ if evt['xsi:eventData']['@xsi1:type'] == 'xsi:CallAnsweredEvent':
892
+ user_id = evt.get('xsi:userId', '')
893
+ call_id = evt['xsi:eventData']['xsi:call']['xsi:callId']
894
+
895
+ if should_record(user_id):
896
+ person = webex.org.get_person_by_email(user_id)
897
+ person.start_xsi()
898
+ call = person.xsi.attach_call(call_id)
899
+ call.recording("start")
900
+ ```
901
+
902
+ ---
903
+
904
+ ## Class Reference Summary
905
+
906
+ | Class | Purpose | Key Methods/Properties |
907
+ |-------|---------|----------------------|
908
+ | `XSI` | Per-user XSI session | `new_call()`, `answer_call()`, `calls`, `profile`, `directory()`, `get_services()`, `attach_call()` |
909
+ | `XSIEvents` | Org-level event manager | `open_channel(queue)` |
910
+ | `XSIEventsChannelSet` | Channel group + subscriptions | `subscribe()`, `unsubscribe()`, `close()`, `active_channels` |
911
+ | `XSIEventsChannel` | Single streaming connection | Managed automatically; `active`, `delete()` |
912
+ | `XSIEventsSubscription` | Event subscription | `id`, `active`, `delete()` |
913
+ | `XSICallQueue` | Call Queue call control | `attach_call(call_id)` |
914
+ | `Call` | Individual call control | `originate()`, `hold()`, `resume()`, `transfer()`, `finish_transfer()`, `conference()`, `park()`, `hangup()`, `status`, `recording()`, `send_dtmf()`, `exec_push()`, `answer()`, `reconnect()` |
915
+ | `Conference` | Multi-party conference | `mute(call_id)`, `deaf(call_id)` |
916
+ | `XSIResponse` | API response wrapper | `success`, `summary`, `raw_response`, truthy/falsy |
917
+ | `FeatureAccessCode` | FAC definition | `feature`, `code`, `alternate_code` |
918
+ | `MonitoringList` | BLF monitoring config (REST) | `add()`, `remove()`, `replace()`, `copy_to()`, `copy_from()`, `clear()` |
919
+
920
+ ---
921
+
922
+ ## Gotchas & Tips
923
+
924
+ 1. **XSI must be enabled by Cisco TAC** before any of this works. There is no self-service toggle.
925
+
926
+ 2. **Events volume is high.** A single call generates 6 events. A busy org with 100 concurrent calls can produce hundreds of events per second. Size your queue and process events quickly.
927
+
928
+ 3. **Events are XML, not JSON.** wxcadm parses them with `xmltodict` into OrderedDicts. The `$` key holds text content (e.g., `config['code']['$']`). The `@` prefix denotes attributes.
929
+
930
+ 4. **ChannelTerminatedEvent is NOT queued.** wxcadm filters this out before putting events in your queue, since it's an infrastructure event, not a business event.
931
+
932
+ 5. **Channel auto-recovery is built in** but not instant. A failed channel waits 60 seconds before restarting. During that window, events for that XSP are lost.
933
+
934
+ 6. **Heartbeats every 15 seconds.** If a heartbeat fails, the retry interval drops to 10 seconds. If it gets a 404/401, the channel restarts.
935
+
936
+ 7. **7200-second expiry.** Both channels and subscriptions expire after 2 hours. wxcadm auto-refreshes them hourly.
937
+
938
+ 8. **XSI profile vs. REST profile.** The XSI profile comes from the call control back-end and may differ from the REST API profile (which comes from Common Identity / Active Directory).
939
+
940
+ 9. **Call IDs are transient.** They only exist while the call is active. Use `attach_call()` promptly after receiving an event.
941
+
942
+ 10. **The `recording()` method has a bug (verified 2026-03-18).** Line 1528 has a duplicate `elif action.lower() == "resume"` that should be `"pause"`. The first `"resume"` (line 1524, → `ResumeRecording`) works correctly. The duplicate (line 1528, → `PauseRecording`) is dead code. As a result, `recording("pause")` raises `ValueError`. Workaround: call `PauseRecording` XSI endpoint directly.
943
+
944
+ 11. **SRV lookup dependency.** wxcadm uses the `srvlookup` library to find XSP servers. If DNS SRV records are unreachable, channel creation will fail.
945
+
946
+ 12. **Session cookies.** Each channel uses a `requests.Session` and preserves cookies from the initial streaming POST. This ensures subsequent heartbeats and ACKs hit the correct server instance behind load balancers.
947
+
948
+ ---
949
+
950
+ ## See Also
951
+
952
+ - [call-control.md](call-control.md) — wxc_sdk call control APIs (REST-based, not XSI) for comparison with wxcadm's XSI call control
953
+ - [webhooks-events.md](webhooks-events.md) — wxc_sdk webhook and event subscription patterns (REST-based alternative to XSI-Events for some use cases)