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,577 @@
1
+ import json
2
+ import typer
3
+ from wxcli.auth import get_api
4
+ from wxcli.errors import WebexError, handle_rest_error
5
+ from wxcli.output import print_table, print_json
6
+ from wxcli.config import get_org_id
7
+
8
+
9
+ app = typer.Typer(help="Manage Webex Calling hunt-group.")
10
+
11
+
12
+ @app.command("list")
13
+ def cmd_list(
14
+ location_id: str = typer.Option(None, "--location-id", help="Only return hunt groups with matching location ID."),
15
+ name: str = typer.Option(None, "--name", help="Only return hunt groups with the matching name."),
16
+ phone_number: str = typer.Option(None, "--phone-number", help="Only return hunt groups with the matching primary phone numb"),
17
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
18
+ limit: int = typer.Option(0, "--limit", help="Max results (0=all for paginated endpoints, API default for non-paginated)"),
19
+ offset: int = typer.Option(0, "--offset", help="Start offset"),
20
+ debug: bool = typer.Option(False, "--debug"),
21
+ ):
22
+ """Read the List of Hunt Groups."""
23
+ api = get_api(debug=debug)
24
+ url = f"https://webexapis.com/v1/telephony/config/huntGroups"
25
+ params = {}
26
+ if location_id is not None:
27
+ params["locationId"] = location_id
28
+ if name is not None:
29
+ params["name"] = name
30
+ if phone_number is not None:
31
+ params["phoneNumber"] = phone_number
32
+ if limit > 0:
33
+ params["max"] = limit
34
+ if offset > 0:
35
+ params["start"] = offset
36
+ org_id = get_org_id()
37
+ if org_id is not None:
38
+ params["orgId"] = org_id
39
+ try:
40
+ if limit > 0:
41
+ result = api.session.rest_get(url, params=params)
42
+ result = result or {}
43
+ items = result.get("huntGroups", result.get("data", result if isinstance(result, list) else [])) if isinstance(result, dict) else (result if isinstance(result, list) else [])
44
+ else:
45
+ items = list(api.session.follow_pagination(url=url, params=params, item_key="huntGroups"))
46
+ except WebexError as e:
47
+ handle_rest_error(e)
48
+ if output == "json":
49
+ print_json(items)
50
+ else:
51
+ print_table(items, columns=[('ID', 'id'), ('Name', 'name'), ('Extension', 'extension'), ('Enabled', 'enabled')], limit=limit)
52
+
53
+
54
+
55
+ @app.command("create")
56
+ def create(
57
+ location_id: str = typer.Argument(help="locationId"),
58
+ name: str = typer.Option(None, "--name", help="(required) Unique name for the hunt group."),
59
+ phone_number: str = typer.Option(None, "--phone-number", help="Primary phone number of the hunt group. Either phone number"),
60
+ extension: str = typer.Option(None, "--extension", help="Primary phone extension of the hunt group. Either phone numb"),
61
+ language_code: str = typer.Option(None, "--language-code", help="Language code."),
62
+ first_name: str = typer.Option(None, "--first-name", help="First name to be shown when calls are forwarded out of this"),
63
+ last_name: str = typer.Option(None, "--last-name", help="Last name to be shown when calls are forwarded out of this h"),
64
+ time_zone: str = typer.Option(None, "--time-zone", help="Time zone for the hunt group."),
65
+ enabled: bool = typer.Option(None, "--enabled/--no-enabled", help="(required) Whether or not the hunt group is enabled."),
66
+ hunt_group_caller_id_for_outgoing_calls_enabled: bool = typer.Option(None, "--hunt-group-caller-id-for-outgoing-calls-enabled/--no-hunt-group-caller-id-for-outgoing-calls-enabled", help="Enable the hunt group to be used as the caller ID when the a"),
67
+ dial_by_name: str = typer.Option(None, "--dial-by-name", help="The name to be used for dial by name functions. Characters"),
68
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
69
+ output: str = typer.Option("id", "--output", "-o", help="Output format: id|json"),
70
+ debug: bool = typer.Option(False, "--debug"),
71
+ ):
72
+ """Create a Hunt Group\n\nExample --json-body:\n '{"name":"...","callPolicies":{"policy":"CIRCULAR","noAnswer":{"nextAgentEnabled":"...","nextAgentRings":"...","forwardEnabled":"...","numberOfRings":"...","destinationVoicemailEnabled":"...","destination":"..."},"waitingEnabled":true,"groupBusyEnabled":true,"allowMembersToControlGroupBusyEnabled":true,"busyRedirect":{"enabled":"...","destination":"...","destinationVoicemailEnabled":"..."},"businessContinuityRedirect":{"enabled":"...","destination":"...","destinationVoicemailEnabled":"..."}},"agents":[{"id":"...","weight":"..."}],"enabled":true,"phoneNumber":"...","extension":"...","languageCode":"...","firstName":"..."}'."""
73
+ api = get_api(debug=debug)
74
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups"
75
+ params = {}
76
+ org_id = get_org_id()
77
+ if org_id is not None:
78
+ params["orgId"] = org_id
79
+ if json_body:
80
+ body = json.loads(json_body)
81
+ else:
82
+ body = {}
83
+ if name is not None:
84
+ body["name"] = name
85
+ if phone_number is not None:
86
+ body["phoneNumber"] = phone_number
87
+ if extension is not None:
88
+ body["extension"] = extension
89
+ if language_code is not None:
90
+ body["languageCode"] = language_code
91
+ if first_name is not None:
92
+ body["firstName"] = first_name
93
+ if last_name is not None:
94
+ body["lastName"] = last_name
95
+ if time_zone is not None:
96
+ body["timeZone"] = time_zone
97
+ if enabled is not None:
98
+ body["enabled"] = enabled
99
+ if hunt_group_caller_id_for_outgoing_calls_enabled is not None:
100
+ body["huntGroupCallerIdForOutgoingCallsEnabled"] = hunt_group_caller_id_for_outgoing_calls_enabled
101
+ if dial_by_name is not None:
102
+ body["dialByName"] = dial_by_name
103
+ _missing = [f for f in ['name', 'enabled'] if f not in body or body[f] is None]
104
+ if _missing:
105
+ typer.echo("Error: Missing required fields: " + ", ".join(_missing), err=True)
106
+ raise typer.Exit(1)
107
+ body.setdefault('callPolicies', {'policy': 'CIRCULAR', 'noAnswer': {'nextAgentEnabled': True, 'nextAgentRings': 3, 'forwardEnabled': False, 'numberOfRings': 15, 'destinationVoicemailEnabled': False}})
108
+ body.setdefault('agents', [])
109
+ body.setdefault('enabled', True)
110
+ try:
111
+ result = api.session.rest_post(url, json=body, params=params)
112
+ except WebexError as e:
113
+ handle_rest_error(e)
114
+ if output == "json":
115
+ print_json(result)
116
+ elif isinstance(result, dict) and "id" in result:
117
+ typer.echo(f"Created: {result['id']}")
118
+ elif not result or result == {}:
119
+ typer.echo("Created.")
120
+ else:
121
+ print_json(result)
122
+
123
+
124
+
125
+ @app.command("show")
126
+ def show(
127
+ location_id: str = typer.Argument(help="locationId"),
128
+ hunt_group_id: str = typer.Argument(help="huntGroupId"),
129
+ output: str = typer.Option("json", "--output", "-o", help="Output format: table|json"),
130
+ debug: bool = typer.Option(False, "--debug"),
131
+ ):
132
+ """Get Details for a Hunt Group."""
133
+ api = get_api(debug=debug)
134
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/{hunt_group_id}"
135
+ params = {}
136
+ org_id = get_org_id()
137
+ if org_id is not None:
138
+ params["orgId"] = org_id
139
+ try:
140
+ result = api.session.rest_get(url, params=params)
141
+ except WebexError as e:
142
+ handle_rest_error(e)
143
+ if output == "json":
144
+ print_json(result)
145
+ else:
146
+ if isinstance(result, dict):
147
+ print_table([result], columns=[("Key", ""), ("Value", "")], limit=0)
148
+ elif isinstance(result, list):
149
+ print_table(result, columns=[("ID", "id"), ("Name", "name")], limit=0)
150
+ else:
151
+ print_json(result)
152
+
153
+
154
+
155
+ @app.command("update")
156
+ def update(
157
+ location_id: str = typer.Argument(help="locationId"),
158
+ hunt_group_id: str = typer.Argument(help="huntGroupId"),
159
+ enabled: bool = typer.Option(None, "--enabled/--no-enabled", help="Whether or not the hunt group is enabled."),
160
+ name: str = typer.Option(None, "--name", help="Unique name for the hunt group."),
161
+ phone_number: str = typer.Option(None, "--phone-number", help="Primary phone number of the hunt group."),
162
+ extension: str = typer.Option(None, "--extension", help="Primary phone extension of the hunt group."),
163
+ distinctive_ring: bool = typer.Option(None, "--distinctive-ring/--no-distinctive-ring", help="Whether or not the hunt group has the distinctive ring optio"),
164
+ language_code: str = typer.Option(None, "--language-code", help="Language code."),
165
+ first_name: str = typer.Option(None, "--first-name", help="First name to be shown when calls are forwarded out of this"),
166
+ last_name: str = typer.Option(None, "--last-name", help="Last name to be shown when calls are forwarded out of this h"),
167
+ time_zone: str = typer.Option(None, "--time-zone", help="Time zone for the hunt group."),
168
+ hunt_group_caller_id_for_outgoing_calls_enabled: bool = typer.Option(None, "--hunt-group-caller-id-for-outgoing-calls-enabled/--no-hunt-group-caller-id-for-outgoing-calls-enabled", help="Enable the hunt group to be used as the caller ID when the a"),
169
+ dial_by_name: str = typer.Option(None, "--dial-by-name", help="Sets or clears the name to be used for dial by name function"),
170
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
171
+ debug: bool = typer.Option(False, "--debug"),
172
+ ):
173
+ """Update a Hunt Group\n\nExample --json-body:\n '{"enabled":true,"name":"...","phoneNumber":"...","extension":"...","distinctiveRing":true,"alternateNumbers":[{"phoneNumber":"...","ringPattern":"..."}],"languageCode":"...","firstName":"..."}'."""
174
+ api = get_api(debug=debug)
175
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/{hunt_group_id}"
176
+ params = {}
177
+ org_id = get_org_id()
178
+ if org_id is not None:
179
+ params["orgId"] = org_id
180
+ if json_body:
181
+ body = json.loads(json_body)
182
+ else:
183
+ body = {}
184
+ if enabled is not None:
185
+ body["enabled"] = enabled
186
+ if name is not None:
187
+ body["name"] = name
188
+ if phone_number is not None:
189
+ body["phoneNumber"] = phone_number
190
+ if extension is not None:
191
+ body["extension"] = extension
192
+ if distinctive_ring is not None:
193
+ body["distinctiveRing"] = distinctive_ring
194
+ if language_code is not None:
195
+ body["languageCode"] = language_code
196
+ if first_name is not None:
197
+ body["firstName"] = first_name
198
+ if last_name is not None:
199
+ body["lastName"] = last_name
200
+ if time_zone is not None:
201
+ body["timeZone"] = time_zone
202
+ if hunt_group_caller_id_for_outgoing_calls_enabled is not None:
203
+ body["huntGroupCallerIdForOutgoingCallsEnabled"] = hunt_group_caller_id_for_outgoing_calls_enabled
204
+ if dial_by_name is not None:
205
+ body["dialByName"] = dial_by_name
206
+ try:
207
+ result = api.session.rest_put(url, json=body, params=params)
208
+ except WebexError as e:
209
+ handle_rest_error(e)
210
+ typer.echo(f"Updated.")
211
+
212
+
213
+
214
+ @app.command("delete")
215
+ def delete(
216
+ location_id: str = typer.Argument(help="locationId"),
217
+ hunt_group_id: str = typer.Argument(help="huntGroupId"),
218
+ force: bool = typer.Option(False, "--force", help="Skip confirmation"),
219
+ debug: bool = typer.Option(False, "--debug"),
220
+ ):
221
+ """Delete a Hunt Group."""
222
+ if not force:
223
+ typer.confirm(f"Delete {hunt_group_id}?", abort=True)
224
+ api = get_api(debug=debug)
225
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/{hunt_group_id}"
226
+ params = {}
227
+ org_id = get_org_id()
228
+ if org_id is not None:
229
+ params["orgId"] = org_id
230
+ try:
231
+ api.session.rest_delete(url, params=params)
232
+ except WebexError as e:
233
+ handle_rest_error(e)
234
+ typer.echo(f"Deleted: {hunt_group_id}")
235
+
236
+
237
+
238
+ @app.command("show-call-forwarding")
239
+ def show_call_forwarding(
240
+ location_id: str = typer.Argument(help="locationId"),
241
+ hunt_group_id: str = typer.Argument(help="huntGroupId"),
242
+ output: str = typer.Option("json", "--output", "-o", help="Output format: table|json"),
243
+ debug: bool = typer.Option(False, "--debug"),
244
+ ):
245
+ """Get Call Forwarding Settings for a Hunt Group."""
246
+ api = get_api(debug=debug)
247
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/{hunt_group_id}/callForwarding"
248
+ params = {}
249
+ org_id = get_org_id()
250
+ if org_id is not None:
251
+ params["orgId"] = org_id
252
+ try:
253
+ result = api.session.rest_get(url, params=params)
254
+ except WebexError as e:
255
+ handle_rest_error(e)
256
+ if output == "json":
257
+ print_json(result)
258
+ else:
259
+ if isinstance(result, dict):
260
+ print_table([result], columns=[("Key", ""), ("Value", "")], limit=0)
261
+ elif isinstance(result, list):
262
+ print_table(result, columns=[("ID", "id"), ("Name", "name")], limit=0)
263
+ else:
264
+ print_json(result)
265
+
266
+
267
+
268
+ @app.command("update-call-forwarding")
269
+ def update_call_forwarding(
270
+ location_id: str = typer.Argument(help="locationId"),
271
+ hunt_group_id: str = typer.Argument(help="huntGroupId"),
272
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
273
+ debug: bool = typer.Option(False, "--debug"),
274
+ ):
275
+ """Update Call Forwarding Settings for a Hunt Group\n\nExample --json-body:\n '{"callForwarding":{"always":{"enabled":"...","destination":"...","ringReminderEnabled":"...","destinationVoicemailEnabled":"..."},"selective":{"enabled":"...","destination":"...","ringReminderEnabled":"...","destinationVoicemailEnabled":"..."},"rules":["..."],"operatingModes":{"enabled":"...","modes":"..."}}}'."""
276
+ api = get_api(debug=debug)
277
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/{hunt_group_id}/callForwarding"
278
+ params = {}
279
+ org_id = get_org_id()
280
+ if org_id is not None:
281
+ params["orgId"] = org_id
282
+ if json_body:
283
+ body = json.loads(json_body)
284
+ else:
285
+ body = {}
286
+ try:
287
+ result = api.session.rest_put(url, json=body, params=params)
288
+ except WebexError as e:
289
+ handle_rest_error(e)
290
+ typer.echo(f"Updated.")
291
+
292
+
293
+
294
+ @app.command("create-selective-rules")
295
+ def create_selective_rules(
296
+ location_id: str = typer.Argument(help="locationId"),
297
+ hunt_group_id: str = typer.Argument(help="huntGroupId"),
298
+ name: str = typer.Option(None, "--name", help="(required) Unique name for the selective rule in the hunt group."),
299
+ enabled: bool = typer.Option(None, "--enabled/--no-enabled", help="Reflects if rule is enabled."),
300
+ holiday_schedule: str = typer.Option(None, "--holiday-schedule", help="Name of the location's holiday schedule which determines whe"),
301
+ business_schedule: str = typer.Option(None, "--business-schedule", help="Name of the location's business schedule which determines wh"),
302
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
303
+ output: str = typer.Option("id", "--output", "-o", help="Output format: id|json"),
304
+ debug: bool = typer.Option(False, "--debug"),
305
+ ):
306
+ """Create a Selective Call Forwarding Rule for a Hunt Group\n\nExample --json-body:\n '{"name":"...","callsFrom":{"selection":"ANY","customNumbers":{"privateNumberEnabled":"...","unavailableNumberEnabled":"...","numbers":"..."}},"callsTo":{"numbers":["..."]},"enabled":true,"holidaySchedule":"...","businessSchedule":"...","forwardTo":{"selection":"FORWARD_TO_DEFAULT_NUMBER","phoneNumber":"..."}}'."""
307
+ api = get_api(debug=debug)
308
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/{hunt_group_id}/callForwarding/selectiveRules"
309
+ params = {}
310
+ org_id = get_org_id()
311
+ if org_id is not None:
312
+ params["orgId"] = org_id
313
+ if json_body:
314
+ body = json.loads(json_body)
315
+ else:
316
+ body = {}
317
+ if name is not None:
318
+ body["name"] = name
319
+ if enabled is not None:
320
+ body["enabled"] = enabled
321
+ if holiday_schedule is not None:
322
+ body["holidaySchedule"] = holiday_schedule
323
+ if business_schedule is not None:
324
+ body["businessSchedule"] = business_schedule
325
+ _missing = [f for f in ['name'] if f not in body or body[f] is None]
326
+ if _missing:
327
+ typer.echo("Error: Missing required fields: " + ", ".join(_missing), err=True)
328
+ raise typer.Exit(1)
329
+ try:
330
+ result = api.session.rest_post(url, json=body, params=params)
331
+ except WebexError as e:
332
+ handle_rest_error(e)
333
+ if output == "json":
334
+ print_json(result)
335
+ elif isinstance(result, dict) and "id" in result:
336
+ typer.echo(f"Created: {result['id']}")
337
+ elif not result or result == {}:
338
+ typer.echo("Created.")
339
+ else:
340
+ print_json(result)
341
+
342
+
343
+
344
+ @app.command("show-selective-rules")
345
+ def show_selective_rules(
346
+ location_id: str = typer.Argument(help="locationId"),
347
+ hunt_group_id: str = typer.Argument(help="huntGroupId"),
348
+ rule_id: str = typer.Argument(help="ruleId"),
349
+ output: str = typer.Option("json", "--output", "-o", help="Output format: table|json"),
350
+ debug: bool = typer.Option(False, "--debug"),
351
+ ):
352
+ """Get Selective Call Forwarding Rule for a Hunt Group."""
353
+ api = get_api(debug=debug)
354
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/{hunt_group_id}/callForwarding/selectiveRules/{rule_id}"
355
+ params = {}
356
+ org_id = get_org_id()
357
+ if org_id is not None:
358
+ params["orgId"] = org_id
359
+ try:
360
+ result = api.session.rest_get(url, params=params)
361
+ except WebexError as e:
362
+ handle_rest_error(e)
363
+ if output == "json":
364
+ print_json(result)
365
+ else:
366
+ if isinstance(result, dict):
367
+ print_table([result], columns=[("Key", ""), ("Value", "")], limit=0)
368
+ elif isinstance(result, list):
369
+ print_table(result, columns=[("ID", "id"), ("Name", "name")], limit=0)
370
+ else:
371
+ print_json(result)
372
+
373
+
374
+
375
+ @app.command("update-selective-rules")
376
+ def update_selective_rules(
377
+ location_id: str = typer.Argument(help="locationId"),
378
+ hunt_group_id: str = typer.Argument(help="huntGroupId"),
379
+ rule_id: str = typer.Argument(help="ruleId"),
380
+ name: str = typer.Option(None, "--name", help="Unique name for the selective rule in the hunt group."),
381
+ enabled: bool = typer.Option(None, "--enabled/--no-enabled", help="Reflects if rule is enabled."),
382
+ holiday_schedule: str = typer.Option(None, "--holiday-schedule", help="Name of the location's holiday schedule which determines whe"),
383
+ business_schedule: str = typer.Option(None, "--business-schedule", help="Name of the location's business schedule which determines wh"),
384
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
385
+ debug: bool = typer.Option(False, "--debug"),
386
+ ):
387
+ """Update a Selective Call Forwarding Rule for a Hunt Group\n\nExample --json-body:\n '{"name":"...","enabled":true,"holidaySchedule":"...","businessSchedule":"...","forwardTo":{"selection":"FORWARD_TO_DEFAULT_NUMBER","phoneNumber":"..."},"callsFrom":{"selection":"ANY","customNumbers":{"privateNumberEnabled":"...","unavailableNumberEnabled":"...","numbers":"..."}},"callsTo":{"numbers":["..."]}}'."""
388
+ api = get_api(debug=debug)
389
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/{hunt_group_id}/callForwarding/selectiveRules/{rule_id}"
390
+ params = {}
391
+ org_id = get_org_id()
392
+ if org_id is not None:
393
+ params["orgId"] = org_id
394
+ if json_body:
395
+ body = json.loads(json_body)
396
+ else:
397
+ body = {}
398
+ if name is not None:
399
+ body["name"] = name
400
+ if enabled is not None:
401
+ body["enabled"] = enabled
402
+ if holiday_schedule is not None:
403
+ body["holidaySchedule"] = holiday_schedule
404
+ if business_schedule is not None:
405
+ body["businessSchedule"] = business_schedule
406
+ try:
407
+ result = api.session.rest_put(url, json=body, params=params)
408
+ except WebexError as e:
409
+ handle_rest_error(e)
410
+ typer.echo(f"Updated.")
411
+
412
+
413
+
414
+ @app.command("delete-selective-rules")
415
+ def delete_selective_rules(
416
+ location_id: str = typer.Argument(help="locationId"),
417
+ hunt_group_id: str = typer.Argument(help="huntGroupId"),
418
+ rule_id: str = typer.Argument(help="ruleId"),
419
+ force: bool = typer.Option(False, "--force", help="Skip confirmation"),
420
+ debug: bool = typer.Option(False, "--debug"),
421
+ ):
422
+ """Delete a Selective Call Forwarding Rule for a Hunt Group."""
423
+ if not force:
424
+ typer.confirm(f"Delete {rule_id}?", abort=True)
425
+ api = get_api(debug=debug)
426
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/{hunt_group_id}/callForwarding/selectiveRules/{rule_id}"
427
+ params = {}
428
+ org_id = get_org_id()
429
+ if org_id is not None:
430
+ params["orgId"] = org_id
431
+ try:
432
+ api.session.rest_delete(url, params=params)
433
+ except WebexError as e:
434
+ handle_rest_error(e)
435
+ typer.echo(f"Deleted: {rule_id}")
436
+
437
+
438
+
439
+ @app.command("list-available-numbers-hunt-groups")
440
+ def list_available_numbers_hunt_groups(
441
+ location_id: str = typer.Argument(help="locationId"),
442
+ phone_number: str = typer.Option(None, "--phone-number", help="Filter phone numbers based on the comma-separated list provi"),
443
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
444
+ limit: int = typer.Option(0, "--limit", help="Max results (0=all for paginated endpoints, API default for non-paginated)"),
445
+ offset: int = typer.Option(0, "--offset", help="Start offset"),
446
+ debug: bool = typer.Option(False, "--debug"),
447
+ ):
448
+ """Get Hunt Group Primary Available Phone Numbers."""
449
+ api = get_api(debug=debug)
450
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/availableNumbers"
451
+ params = {}
452
+ if phone_number is not None:
453
+ params["phoneNumber"] = phone_number
454
+ if limit > 0:
455
+ params["max"] = limit
456
+ if offset > 0:
457
+ params["start"] = offset
458
+ org_id = get_org_id()
459
+ if org_id is not None:
460
+ params["orgId"] = org_id
461
+ result = None
462
+ try:
463
+ result = api.session.rest_get(url, params=params)
464
+ except WebexError as e:
465
+ handle_rest_error(e)
466
+ result = result or []
467
+ items = result.get("phoneNumbers", result.get("data", result if isinstance(result, list) else [])) if isinstance(result, dict) else (result if isinstance(result, list) else [])
468
+ if output == "json":
469
+ print_json(items)
470
+ else:
471
+ print_table(items, columns=[('ID', 'id'), ('Name', 'name'), ('Extension', 'extension'), ('Enabled', 'enabled')], limit=limit)
472
+
473
+
474
+
475
+ @app.command("list-available-numbers-alternate")
476
+ def list_available_numbers_alternate(
477
+ location_id: str = typer.Argument(help="locationId"),
478
+ phone_number: str = typer.Option(None, "--phone-number", help="Filter phone numbers based on the comma-separated list provi"),
479
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
480
+ limit: int = typer.Option(0, "--limit", help="Max results (0=all for paginated endpoints, API default for non-paginated)"),
481
+ offset: int = typer.Option(0, "--offset", help="Start offset"),
482
+ debug: bool = typer.Option(False, "--debug"),
483
+ ):
484
+ """Get Hunt Group Alternate Available Phone Numbers."""
485
+ api = get_api(debug=debug)
486
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/alternate/availableNumbers"
487
+ params = {}
488
+ if phone_number is not None:
489
+ params["phoneNumber"] = phone_number
490
+ if limit > 0:
491
+ params["max"] = limit
492
+ if offset > 0:
493
+ params["start"] = offset
494
+ org_id = get_org_id()
495
+ if org_id is not None:
496
+ params["orgId"] = org_id
497
+ result = None
498
+ try:
499
+ result = api.session.rest_get(url, params=params)
500
+ except WebexError as e:
501
+ handle_rest_error(e)
502
+ result = result or []
503
+ items = result.get("phoneNumbers", result.get("data", result if isinstance(result, list) else [])) if isinstance(result, dict) else (result if isinstance(result, list) else [])
504
+ if output == "json":
505
+ print_json(items)
506
+ else:
507
+ print_table(items, columns=[('ID', 'id'), ('Name', 'name'), ('Extension', 'extension'), ('Enabled', 'enabled')], limit=limit)
508
+
509
+
510
+
511
+ @app.command("list-available-numbers-call-forwarding")
512
+ def list_available_numbers_call_forwarding(
513
+ location_id: str = typer.Argument(help="locationId"),
514
+ phone_number: str = typer.Option(None, "--phone-number", help="Filter phone numbers based on the comma-separated list provi"),
515
+ owner_name: str = typer.Option(None, "--owner-name", help="Return the list of phone numbers that are owned by the given"),
516
+ extension: str = typer.Option(None, "--extension", help="Returns the list of PSTN phone numbers with the given `exten"),
517
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
518
+ limit: int = typer.Option(0, "--limit", help="Max results (0=all for paginated endpoints, API default for non-paginated)"),
519
+ offset: int = typer.Option(0, "--offset", help="Start offset"),
520
+ debug: bool = typer.Option(False, "--debug"),
521
+ ):
522
+ """Get Hunt Group Call Forward Available Phone Numbers."""
523
+ api = get_api(debug=debug)
524
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/callForwarding/availableNumbers"
525
+ params = {}
526
+ if phone_number is not None:
527
+ params["phoneNumber"] = phone_number
528
+ if owner_name is not None:
529
+ params["ownerName"] = owner_name
530
+ if extension is not None:
531
+ params["extension"] = extension
532
+ if limit > 0:
533
+ params["max"] = limit
534
+ if offset > 0:
535
+ params["start"] = offset
536
+ org_id = get_org_id()
537
+ if org_id is not None:
538
+ params["orgId"] = org_id
539
+ result = None
540
+ try:
541
+ result = api.session.rest_get(url, params=params)
542
+ except WebexError as e:
543
+ handle_rest_error(e)
544
+ result = result or []
545
+ items = result.get("phoneNumbers", result.get("data", result if isinstance(result, list) else [])) if isinstance(result, dict) else (result if isinstance(result, list) else [])
546
+ if output == "json":
547
+ print_json(items)
548
+ else:
549
+ print_table(items, columns=[('ID', 'id'), ('Name', 'name'), ('Extension', 'extension'), ('Enabled', 'enabled')], limit=limit)
550
+
551
+
552
+
553
+ @app.command("switch-mode-for")
554
+ def switch_mode_for(
555
+ location_id: str = typer.Argument(help="locationId"),
556
+ hunt_group_id: str = typer.Argument(help="huntGroupId"),
557
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body"),
558
+ debug: bool = typer.Option(False, "--debug"),
559
+ ):
560
+ """Switch Mode for Call Forwarding Settings for a Hunt Group."""
561
+ api = get_api(debug=debug)
562
+ url = f"https://webexapis.com/v1/telephony/config/locations/{location_id}/huntGroups/{hunt_group_id}/callForwarding/actions/switchMode/invoke"
563
+ params = {}
564
+ org_id = get_org_id()
565
+ if org_id is not None:
566
+ params["orgId"] = org_id
567
+ if json_body:
568
+ body = json.loads(json_body)
569
+ else:
570
+ body = {}
571
+ try:
572
+ result = api.session.rest_post(url, json=body, params=params)
573
+ except WebexError as e:
574
+ handle_rest_error(e)
575
+ print_json(result)
576
+
577
+
@@ -0,0 +1,71 @@
1
+ import json
2
+ import typer
3
+ from wxcli.auth import get_api
4
+ from wxcli.errors import WebexError, handle_rest_error
5
+ from wxcli.output import print_table, print_json
6
+ from wxcli.config import get_org_id
7
+
8
+
9
+ app = typer.Typer(help="Manage Webex Calling hybrid-clusters.")
10
+
11
+
12
+ @app.command("list")
13
+ def cmd_list(
14
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
15
+ limit: int = typer.Option(0, "--limit", help="Max results (0=all for paginated endpoints, API default for non-paginated)"),
16
+ offset: int = typer.Option(0, "--offset", help="Start offset"),
17
+ debug: bool = typer.Option(False, "--debug"),
18
+ ):
19
+ """List Hybrid Clusters."""
20
+ api = get_api(debug=debug)
21
+ url = f"https://webexapis.com/v1/hybrid/clusters"
22
+ params = {}
23
+ if limit > 0:
24
+ params["max"] = limit
25
+ if offset > 0:
26
+ params["start"] = offset
27
+ org_id = get_org_id()
28
+ if org_id is not None:
29
+ params["orgId"] = org_id
30
+ result = None
31
+ try:
32
+ result = api.session.rest_get(url, params=params)
33
+ except WebexError as e:
34
+ handle_rest_error(e)
35
+ result = result or []
36
+ items = result.get("items", result.get("data", result if isinstance(result, list) else [])) if isinstance(result, dict) else (result if isinstance(result, list) else [])
37
+ if output == "json":
38
+ print_json(items)
39
+ else:
40
+ print_table(items, columns=[("ID", "id"), ("Name", "name")], limit=limit)
41
+
42
+
43
+
44
+ @app.command("show")
45
+ def show(
46
+ hybrid_cluster_id: str = typer.Argument(help="hybridClusterId"),
47
+ output: str = typer.Option("json", "--output", "-o", help="Output format: table|json"),
48
+ debug: bool = typer.Option(False, "--debug"),
49
+ ):
50
+ """Get Hybrid Cluster Details."""
51
+ api = get_api(debug=debug)
52
+ url = f"https://webexapis.com/v1/hybrid/clusters/{hybrid_cluster_id}"
53
+ params = {}
54
+ org_id = get_org_id()
55
+ if org_id is not None:
56
+ params["orgId"] = org_id
57
+ try:
58
+ result = api.session.rest_get(url, params=params)
59
+ except WebexError as e:
60
+ handle_rest_error(e)
61
+ if output == "json":
62
+ print_json(result)
63
+ else:
64
+ if isinstance(result, dict):
65
+ print_table([result], columns=[("Key", ""), ("Value", "")], limit=0)
66
+ elif isinstance(result, list):
67
+ print_table(result, columns=[("ID", "id"), ("Name", "name")], limit=0)
68
+ else:
69
+ print_json(result)
70
+
71
+