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
wxcli/main.py ADDED
@@ -0,0 +1,210 @@
1
+ import typer
2
+ from datetime import datetime, timezone
3
+
4
+ from wxcli import __version__
5
+ from wxcli.auth import get_api, resolve_token, WebexApi, WebexSession
6
+ from wxcli.config import get_expires_at, get_org_id, get_org_name, save_org
7
+
8
+ app = typer.Typer(
9
+ name="wxcli",
10
+ help="Webex Calling CLI — provision and manage Webex Calling from the terminal.",
11
+ no_args_is_help=True,
12
+ )
13
+
14
+
15
+ def version_callback(value: bool):
16
+ if value:
17
+ typer.echo(f"wxcli {__version__}")
18
+ raise typer.Exit()
19
+
20
+
21
+ @app.callback()
22
+ def main(
23
+ version: bool = typer.Option(False, "--version", callback=version_callback, is_eager=True),
24
+ ):
25
+ pass
26
+
27
+
28
+ @app.command()
29
+ def whoami(
30
+ debug: bool = typer.Option(False, "--debug", help="Show debug output"),
31
+ ):
32
+ """Show current authenticated user and org."""
33
+ api = get_api(debug=debug)
34
+ me = api.session.rest_get("https://webexapis.com/v1/people/me")
35
+
36
+ display_name = me.get("displayName", "")
37
+ email = (me.get("emails") or ["unknown"])[0]
38
+ org_id = me.get("orgId", "")
39
+ typer.echo(f"User: {display_name} ({email})")
40
+ typer.echo(f"Org: {org_id}")
41
+
42
+ target_org_id = get_org_id()
43
+ target_org_name = get_org_name()
44
+ if target_org_id:
45
+ typer.echo(f"Target: {target_org_id} ({target_org_name})")
46
+
47
+ roles = me.get("roles")
48
+ if roles:
49
+ typer.echo(f"Roles: {', '.join(roles)}")
50
+
51
+ expires = get_expires_at()
52
+ if expires:
53
+ try:
54
+ exp_dt = datetime.fromisoformat(expires)
55
+ now = datetime.now(timezone.utc)
56
+ remaining = exp_dt - now
57
+ if remaining.total_seconds() > 0:
58
+ hours = int(remaining.total_seconds() // 3600)
59
+ minutes = int((remaining.total_seconds() % 3600) // 60)
60
+ if hours < 2:
61
+ typer.echo(f"Token: expires in {hours}h {minutes}m — consider refreshing soon")
62
+ else:
63
+ typer.echo(f"Token: expires in {hours}h {minutes}m")
64
+ except ValueError:
65
+ pass
66
+
67
+
68
+ @app.command("switch-org")
69
+ def switch_org(
70
+ org_id: str = typer.Argument(None, help="orgId to switch to (skip interactive prompt)"),
71
+ debug: bool = typer.Option(False, "--debug"),
72
+ ):
73
+ """Switch target organization for partner multi-org tokens."""
74
+ token = resolve_token()
75
+ if not token:
76
+ typer.echo("Error: No token found. Run 'wxcli configure' first.", err=True)
77
+ raise typer.Exit(1)
78
+
79
+ api = WebexApi(WebexSession(token))
80
+
81
+ if org_id:
82
+ # Direct switch — resolve org name
83
+ try:
84
+ org = api.session.rest_get(f"https://webexapis.com/v1/organizations/{org_id}")
85
+ org_name = org.get("displayName", "Unknown")
86
+ except Exception:
87
+ org_name = "Unknown"
88
+ save_org(org_id, org_name)
89
+ typer.echo(f"Target org set: {org_name} ({org_id})")
90
+ return
91
+
92
+ # Interactive — list orgs
93
+ try:
94
+ result = api.session.rest_get("https://webexapis.com/v1/organizations")
95
+ items = result.get("items", []) if isinstance(result, dict) else []
96
+ except Exception as e:
97
+ typer.echo(f"Error listing organizations: {e}", err=True)
98
+ raise typer.Exit(1)
99
+
100
+ if len(items) <= 1:
101
+ typer.echo("Single-org token — no org switching needed.")
102
+ return
103
+
104
+ typer.echo(f"\nAvailable organizations:\n")
105
+ for i, org in enumerate(items, 1):
106
+ name = org.get("displayName", "Unknown")
107
+ oid = org.get("id", "")
108
+ typer.echo(f" {i}. {name:<30s} ({oid})")
109
+
110
+ typer.echo()
111
+ choice = typer.prompt(f"Select target org [1-{len(items)}]", type=int)
112
+ if choice < 1 or choice > len(items):
113
+ typer.echo("Invalid selection.", err=True)
114
+ raise typer.Exit(1)
115
+
116
+ selected = items[choice - 1]
117
+ save_org(selected.get("id"), selected.get("displayName"))
118
+ typer.echo(f"\nTarget org set: {selected.get('displayName')} ({selected.get('id')})")
119
+
120
+
121
+ @app.command("clear-org")
122
+ def clear_org():
123
+ """Clear target organization — commands will target your own org."""
124
+ save_org(None, None)
125
+ typer.echo("Cleared target org. Commands will now target your own organization.")
126
+
127
+
128
+ @app.command("set-cc-region")
129
+ def set_cc_region(
130
+ region: str = typer.Argument(
131
+ help="Contact Center region: us1, eu1, eu2, anz1, ca1, jp1, sg1"
132
+ ),
133
+ ):
134
+ """Set the Contact Center API region for cc-* commands."""
135
+ from wxcli.config import CC_REGIONS, save_cc_region
136
+ if region not in CC_REGIONS:
137
+ typer.echo(f"Error: Unknown region '{region}'. Valid: {', '.join(sorted(CC_REGIONS))}", err=True)
138
+ raise typer.Exit(1)
139
+ save_cc_region(region)
140
+ typer.echo(f"CC region set: {region} ({CC_REGIONS[region]})")
141
+
142
+
143
+ # Hand-coded modules. locations.py/numbers.py are no longer registered here —
144
+ # they turned out to be generator-owned on disk (regenerated byte-identical at
145
+ # the 2026-07-01 spec sync). licenses.py was the last hand-written legacy
146
+ # module, retired 2026-07-02 (S3.1) after a live-verified parity check with
147
+ # the generated twin — `licenses` is now generated and manifest-registered.
148
+ from wxcli.commands.update import app as update_app
149
+ from wxcli.commands.configure import app as configure_app
150
+ from wxcli.commands.cucm import app as cucm_app
151
+
152
+ app.add_typer(update_app, name="update")
153
+ app.add_typer(configure_app, name="configure")
154
+ app.add_typer(cucm_app, name="cucm")
155
+
156
+ from wxcli.commands.cleanup import app as cleanup_app
157
+ app.add_typer(cleanup_app, name="cleanup")
158
+
159
+ from wxcli.commands.org_health_cli import app as org_health_app
160
+ app.add_typer(org_health_app, name="org-health")
161
+
162
+ from wxcli.commands.init_playbook import init as init_command
163
+ app.command(name="init")(init_command)
164
+
165
+ # Auto-generated groups — registration manifest emitted by tools/generate_commands.py.
166
+ # Explicit registrations above/below this loop are reserved for hand-written
167
+ # seams, aliases, and the guarded dev-only fs_* block.
168
+ from importlib import import_module
169
+ from wxcli.commands._registry import GENERATED_GROUPS
170
+
171
+ _generated_apps = {}
172
+ for _module, _group in GENERATED_GROUPS:
173
+ _generated_apps[_group] = import_module(f"wxcli.commands.{_module}").app
174
+ app.add_typer(_generated_apps[_group], name=_group)
175
+
176
+ # Hand-written download/export mounted onto the generated converged-recordings group
177
+ from wxcli.commands import converged_recordings_export
178
+ converged_recordings_export.register(_generated_apps["converged-recordings"])
179
+
180
+ # Aliases
181
+ app.add_typer(_generated_apps["customer-assist"], name="cx-essentials") # alias: cx-essentials → customer-assist
182
+ app.add_typer(_generated_apps["people"], name="users") # alias: users → people (replaced hand-coded users.py)
183
+ app.add_typer(_generated_apps["licenses"], name="licenses-api") # alias for one release: licenses-api → licenses (S3.1, 2026-07-02)
184
+
185
+ # Dev-only: Flow Store CLI (gitignored, generated from specs/webex-flow-store.json)
186
+ try:
187
+ from wxcli.commands.fs_flows import app as fs_flows_app
188
+ app.add_typer(fs_flows_app, name="fs-flows")
189
+ from wxcli.commands.fs_resources import app as fs_resources_app
190
+ app.add_typer(fs_resources_app, name="fs-resources")
191
+ from wxcli.commands.fs_templates import app as fs_templates_app
192
+ app.add_typer(fs_templates_app, name="fs-templates")
193
+ from wxcli.commands.fs_tracing import app as fs_tracing_app
194
+ app.add_typer(fs_tracing_app, name="fs-tracing")
195
+ from wxcli.commands.fs_flows_v2 import app as fs_flows_v2_app
196
+ app.add_typer(fs_flows_v2_app, name="fs-flows-v2")
197
+ from wxcli.commands.fs_user_prefs import app as fs_user_prefs_app
198
+ app.add_typer(fs_user_prefs_app, name="fs-user-prefs")
199
+ from wxcli.commands.fs_connectors import app as fs_connectors_app
200
+ app.add_typer(fs_connectors_app, name="fs-connectors")
201
+ from wxcli.commands.fs_expression_test import app as fs_expression_test_app
202
+ app.add_typer(fs_expression_test_app, name="fs-expression-test")
203
+ from wxcli.commands.fs_flow_props import app as fs_flow_props_app
204
+ app.add_typer(fs_flow_props_app, name="fs-flow-props")
205
+ from wxcli.commands.fs_flow_versions import app as fs_flow_versions_app
206
+ app.add_typer(fs_flow_versions_app, name="fs-flow-versions")
207
+ from wxcli.commands.fs_projects import app as fs_projects_app
208
+ app.add_typer(fs_projects_app, name="fs-projects")
209
+ except ImportError:
210
+ pass
@@ -0,0 +1,106 @@
1
+ # migration/ — CUCM→Webex Migration Tool
2
+
3
+ All 11 phases complete. **2535 tests passing.** Wired into the CLI as `wxcli cucm <command>`. Does NOT use the auto-generator.
4
+
5
+ ## File Map
6
+
7
+ | Path | Purpose |
8
+ |------|---------|
9
+ | `docs/plans/cucm-migration-roadmap.md` | **Master roadmap** — what's done, what's ready, what's next. Start here. |
10
+ | `docs/plans/cucm-pipeline-architecture.md` | Pipeline architecture summary — SQLite store, two-pass ELT, linter-pattern analyzers, NetworkX DAG |
11
+ | `docs/plans/cucm-pipeline/01-07 + 03b` | 8 detailed architecture docs |
12
+ | `src/wxcli/commands/cucm.py` | Phases 08+10 — CLI: 16 commands (init, discover, normalize, map, analyze, plan, preflight, decisions, decide, export, inventory, status, config, user-diff, user-notice, report) |
13
+ | `src/wxcli/commands/cucm_config.py` | Phase 08 — Config management helpers |
14
+ | `models.py` | Canonical data models — 38 types, DecisionType (27 values), Decision, MapperResult, TransformResult |
15
+ | `store.py` | SQLite-backed store — objects, cross_refs, decisions, journal, merge_log, merge_decisions() |
16
+ | `cucm/` | Phase 03 — AXL connection, 14 extractors, discovery pipeline |
17
+ | `transform/normalizers.py` | Phase 04 — 42 Pass 1 normalizers |
18
+ | `transform/cross_reference.py` | Phase 04 — CrossReferenceBuilder (31 relationships + 3 enrichments) |
19
+ | `transform/pipeline.py` | Phase 04 — `normalize_discovery()` entry point |
20
+ | `transform/mappers/` | Phase 05 — 26 mappers + base.py (announcement, button_template, call_forwarding, call_settings, css, dect, device, device_layout, device_profile, e911, ecbn, executive_assistant, feature, line, location, moh, monitoring, receptionist, routing, snr, softkey, user, voicemail, voicemail_group, workspace, device_settings) |
21
+ | `transform/analyzers/` | Phase 06 — 13 analyzers (css_permission, css_routing, device_compatibility, dn_ambiguity, duplicate_user, extension_conflict, feature_approximation, layout_overflow, location_ambiguity, missing_data, shared_line, voicemail_compatibility, workspace_license) |
22
+ | `transform/analysis_pipeline.py` | Phase 06 — Orchestrator: run analyzers → merge → auto-rules + resolve_and_cascade() |
23
+ | `execute/` | Phase 07 — planner.py, dependency.py (NetworkX DAG), batch.py |
24
+ | `export/` | Phase 09 — deployment_plan.py, json/csv exports (command_builder.py removed in Phase 12b) |
25
+ | `preflight/` | Phase 10 — checks.py (9 preflight checks), runner.py (orchestrator), CLI `wxcli cucm preflight` |
26
+ | `advisory/` | Phase 13 — Advisory system: per-decision recommendations (19 rules) + cross-cutting advisor (30 patterns) |
27
+ | `report/` | Assessment report generator — complexity score, SVG charts, executive summary + appendix → HTML/PDF. See its CLAUDE.md. |
28
+ | `.claude/skills/cucm-migrate/SKILL.md` | Phase 11 — 6-step execution skill: preflight → plan summary → batch execute → delegate → report |
29
+
30
+ **Where the design spec and pipeline architecture docs conflict, the pipeline architecture docs are authoritative.**
31
+
32
+ Each subdirectory has its own CLAUDE.md (local context) and TODO.md (outstanding work).
33
+ See `docs/plans/cucm-migration-roadmap.md` for the master project status.
34
+
35
+ ## Known Issues
36
+
37
+ 1. **CUCM CallPickupGroup creation with members fails on CUCM 15.0.** The AXL `addCallPickupGroup` operation with `<members>` containing `<directoryNumber>` fails with a null priority foreign key constraint (`pickupgroupmember.priority`). Workaround: create the pickup group empty, then use `updateLine` with `callPickupGroupName` to assign members at the line level. Affects both wxcadm and raw AXL calls. <!-- Verified on CUCM 15.0.1.13901(2), 2026-03-24 -->
38
+
39
+ ## Pipeline Commands
40
+
41
+ ## Feature Forwarding / Holiday / Night Service Migration
42
+
43
+ The `FeatureMapper` extracts CUCM hunt pilot forwarding destinations
44
+ (`forwardHuntNoAnswer`, `forwardHuntBusy`) and queue overflow targets
45
+ (`queueCalls.queueFullDestination`, `queueCalls.maxWaitTimeDestination`,
46
+ `queueCalls.noAgentDestination`) onto the canonical hunt group / call queue
47
+ objects, and CTI Route Point `callForwardAll` onto the canonical auto attendant.
48
+ The planner emits 6 tier-5 `configure_*` ops (`hunt_group:configure_forwarding`,
49
+ `call_queue:configure_forwarding`, `call_queue:configure_holiday_service`,
50
+ `call_queue:configure_night_service`, `call_queue:configure_stranded_calls`,
51
+ `auto_attendant:configure_forwarding`) that PUT the new settings via per-feature
52
+ endpoints once the feature has been created. Empty / None data short-circuits
53
+ the planner so unaffected features produce no extra ops. Schedules referenced
54
+ by holiday or night service are resolved by name + level (LOCATION /
55
+ ORGANIZATION) — operating modes and location schedules are already created at
56
+ tier 1, so no additional dependency edges are needed. Spec at
57
+ `docs/superpowers/specs/2026-04-10-feature-forwarding-night-service.md`.
58
+
59
+ ## Hoteling / Hot Desking Migration
60
+
61
+ The `DeviceProfileMapper` produces execution-ready hoteling data alongside the
62
+ existing `FEATURE_APPROXIMATION` decisions. For each CUCM Extension Mobility
63
+ device profile it sets `hoteling_guest_enabled=True` (when the user is resolved),
64
+ populates `location_canonical_id` (from device_pool → location cross-ref), and
65
+ creates a `hoteling_location:{loc_cid}` MigrationObject for each unique location
66
+ with EM phones. Three new execution handlers (`enable_hoteling_guest`,
67
+ `enable_hoteling_host`, `enable_hotdesking`) configure Webex hoteling guest,
68
+ device host, and location-level voice portal sign-in respectively. Advisory
69
+ pattern 20 (`detect_extension_mobility_usage`) escalates severity to MEDIUM when
70
+ profiles contain multi-line or BLF features that Webex hot desking cannot
71
+ replicate, and `recommend_feature_approximation` always recommends "accept" for
72
+ EM decisions (no alternative architecture exists). Spec at
73
+ `docs/superpowers/specs/2026-04-10-hoteling-migration.md`.
74
+
75
+ ## Voicemail Groups Migration
76
+
77
+ Unity Connection shared/group mailboxes (department voicemail — sales@, support@,
78
+ billing@ — typically reachable via a hunt pilot) are extracted via a new
79
+ `UnityConnectionClient.extract_shared_mailboxes()` method and normalized into
80
+ `voicemail_group:{name}` MigrationObjects. `VoicemailGroupMapper` (tier 4,
81
+ depends on `location_mapper` + `feature_mapper`) resolves each mailbox's
82
+ location (user extension match first, then single-location fallback;
83
+ multi-location without match → `LOCATION_AMBIGUOUS`),
84
+ generates a placeholder passcode (`MISSING_DATA`), flags custom greetings
85
+ (`AUDIO_ASSET_MANUAL`), and detects extension conflicts (`EXTENSION_CONFLICT`).
86
+ The `handle_voicemail_group_create` handler POSTs to
87
+ `/telephony/config/locations/{id}/voicemailGroups`. Overflow linkage (Phase C):
88
+ the `FeatureMapper` writes `feature_forwards_to_voicemail_group` cross-refs
89
+ when a hunt group/call queue forwarding destination matches a VM group extension,
90
+ and `dependency.py` enforces `voicemail_group:create` before
91
+ `hunt_group:configure_forwarding` / `call_queue:configure_forwarding` /
92
+ `call_queue:configure_stranded_calls`. The forwarding handlers already PUT the
93
+ destination extension — the dependency edge ensures the VM group exists first.
94
+ Spec at `docs/superpowers/specs/2026-04-10-voicemail-groups.md`.
95
+
96
+ ## DECT Migration
97
+
98
+ CUCM DECT handsets (6823, 6825, 6825ip) are classified as `DeviceCompatibilityTier.DECT` by `DeviceMapper` — they are neither incompatible nor convertible, just require DECT network provisioning. `normalize_dect_group()` (Pass 1) groups DECT handsets by CUCM device pool into `dect_network:` MigrationObjects, each carrying a `handset_assignments` list. `DECTMapper` (Phase 05, depends on `location_mapper`, `device_mapper`, `user_mapper`) resolves each network's location via `device_pool_to_location` cross-refs, auto-selects the Webex model (`DBS-110` for ≤30 handsets, `DBS-210` for >30), enriches handset assignments with owner canonical IDs, and generates `DECT_NETWORK_DESIGN` (missing location, no base station inventory, or multi-zone ambiguity) and `DECT_HANDSET_ASSIGNMENT` (unowned handsets) decisions. Base station MAC addresses are supplied via `--dect-inventory <csv>` on `wxcli cucm discover` (columns: `coverage_zone`, `base_station_mac`, `base_station_model`); without them, `DECT_NETWORK_DESIGN` is raised at MEDIUM severity. The planner expands each `dect_network` into 3 ops: `create` → `create_base_stations` → `assign_handsets`, each with proper dependency edges. Three handlers in `execute/handlers.py` implement these ops: `handle_dect_network_create` (POST `/dectNetworks`), `handle_dect_base_station_create` (POST `/dectNetworks/{id}/baseStations`), `handle_dect_handset_assign` (POST `/dectNetworks/{id}/handsets/bulk`). DECT is not supported for FedRAMP tenants. Spec at `docs/superpowers/specs/2026-04-10-dect-migration.md`.
99
+
100
+ **To run a migration:** `wxcli cucm init` → `discover` → `normalize` → `map` → `analyze` → `decisions` → `plan` → `preflight` → `export` → then invoke `/cucm-migrate`.
101
+
102
+ **To generate an assessment report:** `wxcli cucm init` → `discover` (or `discover --from-file`) → `normalize` → `map` → `analyze` → `report --brand "..." --prepared-by "..."`. Does not require plan/preflight/export — the report reads directly from the post-analyze store.
103
+
104
+ **To generate a per-user diff:** `wxcli cucm init` → `discover` → `normalize` → `map` → `analyze` → `user-diff`. Does not require plan/preflight/export.
105
+
106
+ **To generate a user communication notice:** `wxcli cucm init` → `discover` → `normalize` → `map` → `analyze` → `user-notice --brand "..." --migration-date "..." --helpdesk "..."`. Does not require plan/preflight/export. Generates an email-ready notice covering detected scenarios.
@@ -0,0 +1 @@
1
+ """CUCM-to-Webex Calling migration tool."""
@@ -0,0 +1,196 @@
1
+ # Migration Advisory System
2
+
3
+ Practitioner-level architectural advisory for CUCM-to-Webex Calling migrations. Two layers that add judgment to the pipeline's mechanical decision-making.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ analysis_pipeline.py run()
9
+
10
+ ├── Phase 1: 12 existing analyzers → merge decisions into store
11
+
12
+ ├── Phase 2: ArchitectureAdvisor → reads merged decisions + full inventory
13
+ │ → produces ARCHITECTURE_ADVISORY decisions → merge (separate stage)
14
+
15
+ └── Phase 3: populate_recommendations() → enriches ALL decisions with recommendations
16
+
17
+
18
+ advisory/
19
+ ├── recommendation_rules.py (Layer 1: per-decision)
20
+ ├── advisory_patterns.py (Layer 2: cross-cutting)
21
+ └── advisor.py (ArchitectureAdvisor class)
22
+ ```
23
+
24
+ **Layer 1 — Per-Decision Recommendations.** Every decision the pipeline produces (from mappers and analyzers) gets an optional `recommendation` field: which option the system advises, and `recommendation_reasoning`: why. Populated by `populate_recommendations()` which calls into `recommendation_rules.py`. One function per DecisionType (16 total). Returns `(option_id, reasoning)` or `None` for genuinely ambiguous cases.
25
+
26
+ **Layer 2 — Cross-Cutting Advisor.** The `ArchitectureAdvisor` runs after all 13 analyzers have merged their decisions. It reads the full canonical model plus all prior decisions and produces `ARCHITECTURE_ADVISORY` decisions for patterns spanning multiple objects — things like "6 of your 14 CSSes are restriction-only and should be calling permissions, not dial plans" or "your trunk topology indicates Local Gateway, not Cloud Connected PSTN." 33 pattern detector functions total.
27
+
28
+ ## Why Two Phases
29
+
30
+ The ArchitectureAdvisor needs to read decisions from the first 13 analyzers (e.g., Pattern 4 groups DEVICE_INCOMPATIBLE decisions by model for bulk upgrade planning). In a single-phase design, analyzer decisions aren't in the store until after ALL analyzers run — so the ArchitectureAdvisor would see nothing. The two-phase approach merges Phase 1 decisions first, then runs Phase 2 against the populated store.
31
+
32
+ Advisory decisions are merged separately using `decision_types=[ARCHITECTURE_ADVISORY]` and `stage="advisory"` so they don't stale-mark the Phase 1 decisions.
33
+
34
+ ## Files
35
+
36
+ | File | Purpose |
37
+ |------|---------|
38
+ | `__init__.py` | Exports `populate_recommendations()` and `ArchitectureAdvisor` |
39
+ | `recommendation_rules.py` | 21 recommendation functions (one per DecisionType) + `RECOMMENDATION_DISPATCH` dict |
40
+ | `advisory_patterns.py` | 34 cross-cutting pattern detectors + `AdvisoryFinding` dataclass + `ALL_ADVISORY_PATTERNS` list |
41
+ | `advisor.py` | `ArchitectureAdvisor` class (extends Analyzer ABC) |
42
+
43
+ ## Decision Model Fields
44
+
45
+ Two fields added to `Decision` in `models.py`:
46
+ - `recommendation: str | None` — the `id` of the recommended option (e.g., `"call_queue"`, `"convert"`)
47
+ - `recommendation_reasoning: str | None` — 1-3 sentences explaining why
48
+
49
+ Both are display-only — NOT part of the fingerprint. They can change across runs without invalidating resolved decisions. Stored in the `decisions` table as nullable TEXT columns.
50
+
51
+ One enum value added: `DecisionType.ARCHITECTURE_ADVISORY`
52
+
53
+ ## Per-Decision Recommendations (Layer 1)
54
+
55
+ `recommendation_rules.py` has one function per DecisionType:
56
+
57
+ ```python
58
+ def recommend_{type_name}(context: dict, options: list[dict]) -> tuple[str, str] | None:
59
+ ```
60
+
61
+ The `RECOMMENDATION_DISPATCH` dict maps type strings to functions. `populate_recommendations()` iterates all decisions in the store, calls the dispatch function, and writes results via `store.update_recommendation()`.
62
+
63
+ **Key rules by type:**
64
+ - `FEATURE_APPROXIMATION`: Queue features enabled → Call Queue. >8 agents → Call Queue. ≤4 agents top-down → Hunt Group. 5-8 ambiguous → None.
65
+ - `DEVICE_INCOMPATIBLE`: Model-to-replacement lookup (7811→9841, 7832→room device, ATA 190→ATA 192). Includes firmware type (MPP vs RoomOS) in reasoning.
66
+ - `CSS_ROUTING_MISMATCH`: Partition ordering dependency → "manual" with explanation. Scope difference → "use_union" unless it creates conflicts.
67
+ - `MISSING_DATA`: Almost always "skip" with remediation guidance. Trunk password → "generate".
68
+
69
+ Ambiguous cases return `None`. Honest uncertainty is a feature.
70
+
71
+ ## Cross-Cutting Advisory Patterns (Layer 2)
72
+
73
+ `advisory_patterns.py` has 33 pattern detector functions. Each takes a `MigrationStore` and returns `list[AdvisoryFinding]`.
74
+
75
+ **Critical patterns (highest migration impact):**
76
+ 1. **Partition Ordering Loss** — CSSes that depend on partition ordering to resolve overlapping patterns. Webex uses longest-match routing — no ordering equivalent. Calls may route differently after migration.
77
+ 2. **CPN Transformation Chain** — Route patterns and trunks with calling/called party transformations. CUCM chains transformations across 5 levels. Webex has flat caller ID per user/location.
78
+ 3. **PSTN Connection Type** — Classifies trunk topology into Local Gateway, Cloud Connected PSTN, or Premises-based PSTN. Drives the entire Webex routing architecture.
79
+
80
+ **Eliminate patterns (CUCM workarounds to remove):**
81
+ 4. Restriction CSS Consolidation — CSSes with only blocking patterns → calling permission policies
82
+ 5. Translation Pattern Elimination — Digit normalization handled natively by Webex location settings
83
+ 6. Partition-Based Time Routing — Time schedule→partition→CSS chain → AA business hours schedule
84
+ 7. Over-Engineered Dial Plan — Patterns matching Webex's built-in extension routing
85
+ 8. Voicemail Pilot Simplification — Multiple pilots to same VM system → Webex location-level voicemail
86
+
87
+ **Rebuild patterns (use Webex-native approach):**
88
+ 9. Hunt Pilot Behavioral Reclassification — HGs with queue-like behavior → Call Queues
89
+ 10. Location Consolidation — Multiple device pools same tz+region → fewer Webex locations
90
+ 11. Shared Line Simplification — Monitoring-only appearances → virtual extensions
91
+ 12. Trunk Destination Consolidation — Multiple trunks same destination → single Webex trunk
92
+
93
+ **Informational / out-of-scope:**
94
+ 13. Device Bulk Upgrade Planning — Groups incompatible devices by model with replacement options
95
+ 14. Globalized vs. Localized Dial Plan — Detects dial plan style to guide migration approach
96
+ 15. Media Resource Scope Removal — MRGLs, conference bridges, transcoders → cloud-managed, skip
97
+ 16. E911/CER Migration Flag — Emergency services require separate workstream
98
+
99
+ **Tier 4 feature gap patterns:**
100
+ 17. Recording Enabled Users — phones with recordingFlag enabled → configure Webex recording
101
+ 18. SNR Configured Users — remote destination profiles → manual Webex SNR setup
102
+ 19. Transformation Patterns — calling/called party transformations → manual caller ID review
103
+ 20. Extension Mobility Usage — device profiles → Webex hot desking configuration
104
+ 21. Call Intercept Candidates — heuristically detected blocked partitions + CFA-to-voicemail → manual Webex intercept setup
105
+ 22. Selective Call Handling Opportunities — CSS/partition patterns suggesting per-caller routing → Webex Selective Forward / Accept / Reject (admin) or Priority Alert (user-only)
106
+
107
+ **Gap patterns (silent failures / platform limits):**
108
+ 23. Mixed CSS — CSSes mixing routing and restriction partitions (silent gap in Pattern 1)
109
+ 24. Cumulative Virtual Line Consumption — count VLs recommended across all decisions, warn if approaching limits
110
+ 25. User OAuth Required — features that require per-user OAuth (admin tokens can't set them)
111
+ 26. Trunk Type Selection — trunk type (REGISTERING vs CERTIFICATE_BASED) is immutable post-creation
112
+ 27. Intercluster Trunks — CUCM ICT disposition decision (no Webex equivalent)
113
+ 28. Legacy Gateway Protocols — MGCP and H.323 gateways need SIP conversion before migration
114
+
115
+ **User communication:**
116
+ 29. Voicemail Greeting Re-Recording — counts users with custom greetings, produces user communication template
117
+
118
+ **Audio migration:**
119
+ 30. Custom Audio Assets — custom MoH sources + announcements requiring manual download/upload
120
+
121
+ **Feature migration gaps:**
122
+ 31. DECT Deployment — DECT networks present without base station inventory → operator must supply MAC CSV
123
+ 32. Feature Forwarding Gaps — hunt groups / call queues / AAs with CUCM forwarding destinations that aren't mapped to Webex forwarding fields (MEDIUM/rebuild)
124
+ 33. Workspace Call Settings Gaps — workspaces with empty call_settings but CUCM device had custom caller ID, forwarding, or DND config (LOW/migrate_as_is)
125
+
126
+ ## AdvisoryFinding Dataclass
127
+
128
+ ```python
129
+ @dataclass
130
+ class AdvisoryFinding:
131
+ pattern_name: str # "restriction_css_consolidation"
132
+ severity: str # "CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"
133
+ summary: str # One-liner for decision summary
134
+ detail: str # 2-5 sentences of reasoning
135
+ affected_objects: list[str] # canonical_ids
136
+ recommendation: str # Always "accept" for advisories
137
+ recommendation_reasoning: str
138
+ category: str # "migrate_as_is" | "rebuild" | "eliminate" | "out_of_scope"
139
+ ```
140
+
141
+ The `category` field classifies advisories into the migration decision framework: migrate as-is, rebuild using Webex-native patterns, eliminate (CUCM workaround), or out-of-scope (separate workstream).
142
+
143
+ ## Implementation Notes
144
+
145
+ **Pattern 1 uses `blockEnable`, not `classify_block_pattern()`.** The spec says to use `classify_block_pattern()` from `cucm_pattern.py`, but that function requires a `category_rules` list from the migration config. Using `pre_migration_state.blockEnable` is more direct and robust — it detects blocking patterns without config dependency. Code review approved this deviation.
146
+
147
+ **Pattern 9 groups by voicemail system before firing.** Uses the first 3 digits of pilot number + CSS name as a grouping key. Only fires when all pilots share the same key (i.e., point to the same VM system). Prevents false positives on deployments with genuinely independent voicemail systems.
148
+
149
+ **Pattern 11 `affected_objects` includes CSS + partition + route pattern IDs.** Not just CSS IDs — the partitions and route patterns involved in ordering conflicts are also tracked for traceability.
150
+
151
+ **Pattern 16 (E911) always fires**, even on empty stores. When no E911 signals are detected, it produces a warning that CER data may not be visible via AXL. This is by design per the spec's "if detection data is sparse" guidance. Pattern 16 now includes quantified ECBN readiness counts (DIRECT_LINE / LOCATION_ECBN / ambiguous / mismatch).
152
+
153
+ **Test count:** 87 tests (35 pattern + 7 advisor + 45 new-pattern/rule tests). The prompt estimated ~50; the actual count is lower because some patterns share positive/negative cases and the simpler patterns need fewer test scenarios. Pattern 27 (voicemail greeting re-recording) added 6 tests.
154
+
155
+ ## Pipeline Integration (Phase 13d — COMPLETE)
156
+
157
+ In `analysis_pipeline.py`, the `run()` method has steps 5-6 after the original 4:
158
+ 1. Run 12 existing analyzers (unchanged)
159
+ 2. Convert to store dicts and merge (unchanged)
160
+ 3. Apply auto-rules (unchanged)
161
+ 4. Apply auto-resolution rules (unchanged)
162
+ 5. Run ArchitectureAdvisor → merge advisory decisions separately (`decision_types=[ARCHITECTURE_ADVISORY]`, `stage="advisory"`)
163
+ 6. `populate_recommendations()` → enrich all decisions
164
+
165
+ Advisory decisions do NOT trigger cascades. Resolving an advisory does not re-run any analyzer. Stats dict includes `architecture_advisor` key.
166
+
167
+ ## CLI Integration (Phase 13d — COMPLETE)
168
+
169
+ - `wxcli cucm decisions` shows `[REC: option]` tag on decisions with recommendations (in a Recommendations section below the table)
170
+ - `wxcli cucm decisions --type advisory` filters to ARCHITECTURE_ADVISORY only (shorthand mapping)
171
+ - JSON export includes `recommendation` and `recommendation_reasoning` fields
172
+
173
+ ## Skill Integration (Phase 13d — COMPLETE)
174
+
175
+ The `cucm-migrate` skill's decision review has two phases:
176
+ - **Phase A:** Present advisories grouped by category (eliminate, rebuild, out_of_scope, migrate_as_is) with bulk accept
177
+ - **Phase B:** Present per-decision items as auto_apply, recommended (with bulk accept and reasoning), or needs_input
178
+
179
+ ## Adding New Patterns
180
+
181
+ To add a new advisory pattern:
182
+ 1. Write a function `detect_{name}(store: MigrationStore) -> list[AdvisoryFinding]` in `advisory_patterns.py`
183
+ 2. Register it in `ALL_ADVISORY_PATTERNS`
184
+ 3. Add tests in `tests/migration/advisory/test_advisory_patterns.py`
185
+
186
+ To add a new recommendation rule:
187
+ 1. Add logic to the existing function for that DecisionType in `recommendation_rules.py`
188
+ 2. Add tests in `tests/migration/advisory/test_recommendation_rules.py`
189
+
190
+ No framework changes needed for either. The knowledge base grows by adding functions.
191
+
192
+ ## Design Spec
193
+
194
+ Full design: `docs/superpowers/specs/2026-03-24-migration-advisory-design.md`
195
+
196
+ Build prompts: `docs/prompts/phase-13a` through `phase-13d`
@@ -0,0 +1,46 @@
1
+ """Migration advisory system — practitioner-level recommendations.
2
+
3
+ Layer 1 (recommendation_rules): per-decision recommendations
4
+ Layer 2 (advisory_patterns + advisor): cross-cutting observations
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import logging
9
+ from typing import Any
10
+
11
+ from wxcli.migration.advisory.advisor import ArchitectureAdvisor
12
+ from wxcli.migration.advisory.recommendation_rules import RECOMMENDATION_DISPATCH
13
+ from wxcli.migration.store import MigrationStore
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def populate_recommendations(store: MigrationStore) -> int:
19
+ """Enrich all decisions in the store with recommendations.
20
+
21
+ Iterates all decisions, calls the recommendation function for each type,
22
+ and writes results via store.update_recommendation().
23
+
24
+ Returns count of recommendations added.
25
+ """
26
+ all_decisions = store.get_all_decisions()
27
+ count = 0
28
+ for dec in all_decisions:
29
+ if dec.get("chosen_option") == "__stale__":
30
+ continue
31
+ dec_type = dec.get("type", "")
32
+ fn = RECOMMENDATION_DISPATCH.get(dec_type)
33
+ if fn is None:
34
+ continue
35
+ context = dec.get("context", {})
36
+ options = dec.get("options", [])
37
+ try:
38
+ result = fn(context, options)
39
+ except Exception as exc:
40
+ logger.warning("Recommendation %s failed: %s", fn.__name__, exc)
41
+ continue
42
+ if result is not None:
43
+ option_id, reasoning = result
44
+ store.update_recommendation(dec["decision_id"], option_id, reasoning)
45
+ count += 1
46
+ return count