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,280 @@
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 devices.")
10
+
11
+
12
+ @app.command("list")
13
+ def cmd_list(
14
+ display_name: str = typer.Option(None, "--display-name", help="List devices with this display name."),
15
+ person_id: str = typer.Option(None, "--person-id", help="List devices by person ID."),
16
+ workspace_id: str = typer.Option(None, "--workspace-id", help="List devices by workspace ID."),
17
+ connection_status: str = typer.Option(None, "--connection-status", help="List devices with this connection status."),
18
+ product: str = typer.Option(None, "--product", help="Choices: DX-80, RoomKit, SX-80"),
19
+ type_param: str = typer.Option(None, "--type", help="Choices: roomdesk, phone, accessory, webexgo, unknown"),
20
+ serial: str = typer.Option(None, "--serial", help="List devices with this serial number."),
21
+ tag: str = typer.Option(None, "--tag", help="List devices which have a tag. Searching for multiple tags"),
22
+ software: str = typer.Option(None, "--software", help="List devices with this software version."),
23
+ upgrade_channel: str = typer.Option(None, "--upgrade-channel", help="List devices with this upgrade channel."),
24
+ error_code: str = typer.Option(None, "--error-code", help="List devices with this error code."),
25
+ capability: str = typer.Option(None, "--capability", help="Choices: xapi"),
26
+ permission: str = typer.Option(None, "--permission", help="List devices with this permission."),
27
+ location_id: str = typer.Option(None, "--location-id", help="List devices by location ID."),
28
+ workspace_location_id: str = typer.Option(None, "--workspace-location-id", help="List devices by workspace location ID. Deprecated, prefer `"),
29
+ mac: str = typer.Option(None, "--mac", help="List devices with this MAC address."),
30
+ device_platform: str = typer.Option(None, "--device-platform", help="Choices: cisco, microsoftTeamsRoom"),
31
+ planned_maintenance: str = typer.Option(None, "--planned-maintenance", help="Choices: off, on, upcoming"),
32
+ output: str = typer.Option("table", "--output", "-o", help="Output format: table|json"),
33
+ limit: int = typer.Option(0, "--limit", help="Max results (0=all for paginated endpoints, API default for non-paginated)"),
34
+ offset: int = typer.Option(0, "--offset", help="Start offset"),
35
+ debug: bool = typer.Option(False, "--debug"),
36
+ ):
37
+ """List Devices."""
38
+ api = get_api(debug=debug)
39
+ url = f"https://webexapis.com/v1/devices"
40
+ params = {}
41
+ if display_name is not None:
42
+ params["displayName"] = display_name
43
+ if person_id is not None:
44
+ params["personId"] = person_id
45
+ if workspace_id is not None:
46
+ params["workspaceId"] = workspace_id
47
+ if connection_status is not None:
48
+ params["connectionStatus"] = connection_status
49
+ if product is not None:
50
+ params["product"] = product
51
+ if type_param is not None:
52
+ params["type"] = type_param
53
+ if serial is not None:
54
+ params["serial"] = serial
55
+ if tag is not None:
56
+ params["tag"] = tag
57
+ if software is not None:
58
+ params["software"] = software
59
+ if upgrade_channel is not None:
60
+ params["upgradeChannel"] = upgrade_channel
61
+ if error_code is not None:
62
+ params["errorCode"] = error_code
63
+ if capability is not None:
64
+ params["capability"] = capability
65
+ if permission is not None:
66
+ params["permission"] = permission
67
+ if location_id is not None:
68
+ params["locationId"] = location_id
69
+ if workspace_location_id is not None:
70
+ params["workspaceLocationId"] = workspace_location_id
71
+ if mac is not None:
72
+ params["mac"] = mac
73
+ if device_platform is not None:
74
+ params["devicePlatform"] = device_platform
75
+ if planned_maintenance is not None:
76
+ params["plannedMaintenance"] = planned_maintenance
77
+ if limit > 0:
78
+ params["max"] = limit
79
+ if offset > 0:
80
+ params["start"] = offset
81
+ org_id = get_org_id()
82
+ if org_id is not None:
83
+ params["orgId"] = org_id
84
+ try:
85
+ if limit > 0:
86
+ result = api.session.rest_get(url, params=params)
87
+ result = result or {}
88
+ items = result.get("items", result.get("data", result if isinstance(result, list) else [])) if isinstance(result, dict) else (result if isinstance(result, list) else [])
89
+ else:
90
+ items = list(api.session.follow_pagination(url=url, params=params, item_key="items"))
91
+ except WebexError as e:
92
+ handle_rest_error(e)
93
+ if output == "json":
94
+ print_json(items)
95
+ else:
96
+ print_table(items, columns=[('ID', 'id'), ('Display Name', 'displayName'), ('Product', 'product'), ('MAC', 'mac')], limit=limit)
97
+
98
+
99
+
100
+ @app.command("create")
101
+ def create(
102
+ mac: str = typer.Option(None, "--mac", help="(required) The MAC address of the device being created."),
103
+ model: str = typer.Option(None, "--model", help="(required) The model of the device being created. The corresponding dev"),
104
+ workspace_id: str = typer.Option(None, "--workspace-id", help="The ID of the workspace where the device will be created."),
105
+ person_id: str = typer.Option(None, "--person-id", help="The ID of the person who will own the device once created."),
106
+ password: str = typer.Option(None, "--password", help="SIP password to be configured for the phone, only required w"),
107
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
108
+ output: str = typer.Option("id", "--output", "-o", help="Output format: id|json"),
109
+ debug: bool = typer.Option(False, "--debug"),
110
+ ):
111
+ """Create a Device by MAC Address."""
112
+ api = get_api(debug=debug)
113
+ url = f"https://webexapis.com/v1/devices"
114
+ params = {}
115
+ org_id = get_org_id()
116
+ if org_id is not None:
117
+ params["orgId"] = org_id
118
+ if json_body:
119
+ body = json.loads(json_body)
120
+ else:
121
+ body = {}
122
+ if mac is not None:
123
+ body["mac"] = mac
124
+ if model is not None:
125
+ body["model"] = model
126
+ if workspace_id is not None:
127
+ body["workspaceId"] = workspace_id
128
+ if person_id is not None:
129
+ body["personId"] = person_id
130
+ if password is not None:
131
+ body["password"] = password
132
+ _missing = [f for f in ['mac', 'model'] if f not in body or body[f] is None]
133
+ if _missing:
134
+ typer.echo("Error: Missing required fields: " + ", ".join(_missing), err=True)
135
+ raise typer.Exit(1)
136
+ try:
137
+ result = api.session.rest_post(url, json=body, params=params)
138
+ except WebexError as e:
139
+ handle_rest_error(e)
140
+ if output == "json":
141
+ print_json(result)
142
+ elif isinstance(result, dict) and "id" in result:
143
+ typer.echo(f"Created: {result['id']}")
144
+ elif not result or result == {}:
145
+ typer.echo("Created.")
146
+ else:
147
+ print_json(result)
148
+
149
+
150
+
151
+ @app.command("show")
152
+ def show(
153
+ device_id: str = typer.Argument(help="deviceId"),
154
+ output: str = typer.Option("json", "--output", "-o", help="Output format: table|json"),
155
+ debug: bool = typer.Option(False, "--debug"),
156
+ ):
157
+ """Get Device Details."""
158
+ api = get_api(debug=debug)
159
+ url = f"https://webexapis.com/v1/devices/{device_id}"
160
+ params = {}
161
+ org_id = get_org_id()
162
+ if org_id is not None:
163
+ params["orgId"] = org_id
164
+ try:
165
+ result = api.session.rest_get(url, params=params)
166
+ except WebexError as e:
167
+ handle_rest_error(e)
168
+ if output == "json":
169
+ print_json(result)
170
+ else:
171
+ if isinstance(result, dict):
172
+ print_table([result], columns=[("Key", ""), ("Value", "")], limit=0)
173
+ elif isinstance(result, list):
174
+ print_table(result, columns=[("ID", "id"), ("Name", "name")], limit=0)
175
+ else:
176
+ print_json(result)
177
+
178
+
179
+
180
+ @app.command("update")
181
+ def update(
182
+ device_id: str = typer.Argument(help="deviceId"),
183
+ op: str = typer.Option(None, "--op", help="Choices: add, remove, replace"),
184
+ path: str = typer.Option(None, "--path", help="Only the tags path is supported to patch."),
185
+ value: str = typer.Option(None, "--value", help="Value for replace op (JSON-parsed: string, number, bool, or array)"),
186
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
187
+ debug: bool = typer.Option(False, "--debug"),
188
+ ):
189
+ """Modify Device Tags\n\nExample --json-body:\n '{"op":"add","path":"...","value":["..."]}'."""
190
+ api = get_api(debug=debug)
191
+ url = f"https://webexapis.com/v1/devices/{device_id}"
192
+ params = {}
193
+ org_id = get_org_id()
194
+ if org_id is not None:
195
+ params["orgId"] = org_id
196
+ if json_body:
197
+ body = json.loads(json_body)
198
+ else:
199
+ patch_op = {}
200
+ if op is not None:
201
+ patch_op["op"] = op
202
+ if path is not None:
203
+ patch_op["path"] = path
204
+ if value is not None:
205
+ try:
206
+ patch_op["value"] = json.loads(value)
207
+ except json.JSONDecodeError:
208
+ patch_op["value"] = value
209
+ body = [patch_op]
210
+ try:
211
+ result = api.session.rest_patch(url, json=body, params=params, content_type="application/json-patch+json")
212
+ except WebexError as e:
213
+ handle_rest_error(e)
214
+ typer.echo(f"Updated.")
215
+
216
+
217
+
218
+ @app.command("delete")
219
+ def delete(
220
+ device_id: str = typer.Argument(help="deviceId"),
221
+ force: bool = typer.Option(False, "--force", help="Skip confirmation"),
222
+ debug: bool = typer.Option(False, "--debug"),
223
+ ):
224
+ """Delete a Device."""
225
+ if not force:
226
+ typer.confirm(f"Delete {device_id}?", abort=True)
227
+ api = get_api(debug=debug)
228
+ url = f"https://webexapis.com/v1/devices/{device_id}"
229
+ params = {}
230
+ org_id = get_org_id()
231
+ if org_id is not None:
232
+ params["orgId"] = org_id
233
+ try:
234
+ api.session.rest_delete(url, params=params)
235
+ except WebexError as e:
236
+ handle_rest_error(e)
237
+ typer.echo(f"Deleted: {device_id}")
238
+
239
+
240
+
241
+ @app.command("create-activation-code")
242
+ def create_activation_code(
243
+ workspace_id: str = typer.Option(None, "--workspace-id", help="The ID of the workspace where the device will be activated."),
244
+ person_id: str = typer.Option(None, "--person-id", help="The ID of the person who will own the device once activated."),
245
+ model: str = typer.Option(None, "--model", help="The model of the device being created. The corresponding dev"),
246
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
247
+ output: str = typer.Option("id", "--output", "-o", help="Output format: id|json"),
248
+ debug: bool = typer.Option(False, "--debug"),
249
+ ):
250
+ """Create a Device Activation Code."""
251
+ api = get_api(debug=debug)
252
+ url = f"https://webexapis.com/v1/devices/activationCode"
253
+ params = {}
254
+ org_id = get_org_id()
255
+ if org_id is not None:
256
+ params["orgId"] = org_id
257
+ if json_body:
258
+ body = json.loads(json_body)
259
+ else:
260
+ body = {}
261
+ if workspace_id is not None:
262
+ body["workspaceId"] = workspace_id
263
+ if person_id is not None:
264
+ body["personId"] = person_id
265
+ if model is not None:
266
+ body["model"] = model
267
+ try:
268
+ result = api.session.rest_post(url, json=body, params=params)
269
+ except WebexError as e:
270
+ handle_rest_error(e)
271
+ if output == "json":
272
+ print_json(result)
273
+ elif isinstance(result, dict) and "id" in result:
274
+ typer.echo(f"Created: {result['id']}")
275
+ elif not result or result == {}:
276
+ typer.echo("Created.")
277
+ else:
278
+ print_json(result)
279
+
280
+
@@ -0,0 +1,142 @@
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 resolve_org_id
7
+
8
+
9
+ app = typer.Typer(help="Manage Webex Calling domains.")
10
+
11
+
12
+ @app.command("get-domain-verification")
13
+ def get_domain_verification(
14
+ domain: str = typer.Option(None, "--domain", help="A valid domain name."),
15
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body"),
16
+ debug: bool = typer.Option(False, "--debug"),
17
+ ):
18
+ """Get Domain Verification Token."""
19
+ api = get_api(debug=debug)
20
+ org_id = resolve_org_id(api.session)
21
+ url = f"https://webexapis.com/identity/organizations/{org_id}/actions/getDomainVerificationToken"
22
+ if json_body:
23
+ body = json.loads(json_body)
24
+ else:
25
+ body = {}
26
+ if domain is not None:
27
+ body["domain"] = domain
28
+ try:
29
+ result = api.session.rest_post(url, json=body)
30
+ except WebexError as e:
31
+ handle_rest_error(e)
32
+ print_json(result)
33
+
34
+
35
+
36
+ @app.command("verify-domain")
37
+ def verify_domain(
38
+ domain: str = typer.Option(None, "--domain", help="The domain name to be verified."),
39
+ claim_domain: str = typer.Option(None, "--claim-domain", help="A boolean to specify whether the domain needs to be claimed."),
40
+ reserve_domain: str = typer.Option(None, "--reserve-domain", help="For FedRAMP only: If true, add the domain to the FedRAMP res"),
41
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body"),
42
+ debug: bool = typer.Option(False, "--debug"),
43
+ ):
44
+ """Verify Domain."""
45
+ api = get_api(debug=debug)
46
+ org_id = resolve_org_id(api.session)
47
+ url = f"https://webexapis.com/identity/organizations/{org_id}/actions/verifyDomain"
48
+ if json_body:
49
+ body = json.loads(json_body)
50
+ else:
51
+ body = {}
52
+ if domain is not None:
53
+ body["domain"] = domain
54
+ if claim_domain is not None:
55
+ body["claimDomain"] = claim_domain
56
+ if reserve_domain is not None:
57
+ body["reserveDomain"] = reserve_domain
58
+ try:
59
+ result = api.session.rest_post(url, json=body)
60
+ except WebexError as e:
61
+ handle_rest_error(e)
62
+ print_json(result)
63
+
64
+
65
+
66
+ @app.command("claim-domain")
67
+ def claim_domain(
68
+ force_domain_claim: str = typer.Option(None, "--force-domain-claim", help="Indicate if the domain should be claimed when there are user"),
69
+ claim_domain_only: str = typer.Option(None, "--claim-domain-only", help="Indicate to just claim the domain only without searching/mar"),
70
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body"),
71
+ debug: bool = typer.Option(False, "--debug"),
72
+ ):
73
+ """Claim Domain\n\nExample --json-body:\n '{"data":[{"domain":"..."}],"forceDomainClaim":true,"claimDomainOnly":true}'."""
74
+ api = get_api(debug=debug)
75
+ org_id = resolve_org_id(api.session)
76
+ url = f"https://webexapis.com/identity/organizations/{org_id}/actions/claimDomain"
77
+ if json_body:
78
+ body = json.loads(json_body)
79
+ else:
80
+ body = {}
81
+ if force_domain_claim is not None:
82
+ body["forceDomainClaim"] = force_domain_claim
83
+ if claim_domain_only is not None:
84
+ body["claimDomainOnly"] = claim_domain_only
85
+ try:
86
+ result = api.session.rest_post(url, json=body)
87
+ except WebexError as e:
88
+ handle_rest_error(e)
89
+ print_json(result)
90
+
91
+
92
+
93
+ @app.command("unverify-domain")
94
+ def unverify_domain(
95
+ domain: str = typer.Option(None, "--domain", help="Domain name to be verified."),
96
+ remove_pending: str = typer.Option(None, "--remove-pending", help="Specify whether to remove pending domain. Default is false ("),
97
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body"),
98
+ debug: bool = typer.Option(False, "--debug"),
99
+ ):
100
+ """Unverify Domain."""
101
+ api = get_api(debug=debug)
102
+ org_id = resolve_org_id(api.session)
103
+ url = f"https://webexapis.com/identity/organizations/{org_id}/actions/unverifyDomain"
104
+ if json_body:
105
+ body = json.loads(json_body)
106
+ else:
107
+ body = {}
108
+ if domain is not None:
109
+ body["domain"] = domain
110
+ if remove_pending is not None:
111
+ body["removePending"] = remove_pending
112
+ try:
113
+ result = api.session.rest_post(url, json=body)
114
+ except WebexError as e:
115
+ handle_rest_error(e)
116
+ print_json(result)
117
+
118
+
119
+
120
+ @app.command("unclaim-domain")
121
+ def unclaim_domain(
122
+ domain: str = typer.Option(None, "--domain", help="A claimed domain."),
123
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body"),
124
+ debug: bool = typer.Option(False, "--debug"),
125
+ ):
126
+ """Unclaim Domain."""
127
+ api = get_api(debug=debug)
128
+ org_id = resolve_org_id(api.session)
129
+ url = f"https://webexapis.com/identity/organizations/{org_id}/actions/unclaimDomain"
130
+ if json_body:
131
+ body = json.loads(json_body)
132
+ else:
133
+ body = {}
134
+ if domain is not None:
135
+ body["domain"] = domain
136
+ try:
137
+ result = api.session.rest_post(url, json=body)
138
+ except WebexError as e:
139
+ handle_rest_error(e)
140
+ print_json(result)
141
+
142
+
wxcli/commands/ecm.py ADDED
@@ -0,0 +1,178 @@
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
+
7
+
8
+ app = typer.Typer(help="Manage Webex Calling ecm.")
9
+
10
+
11
+ @app.command("list")
12
+ def cmd_list(
13
+ room_id: str = typer.Option(..., "--room-id", help="ID of the room for which to list the ECM folder."),
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 ECM folder."""
20
+ api = get_api(debug=debug)
21
+ url = f"https://webexapis.com/v1/room/linkedFolders"
22
+ params = {}
23
+ if room_id is not None:
24
+ params["roomId"] = room_id
25
+ if limit > 0:
26
+ params["max"] = limit
27
+ if offset > 0:
28
+ params["start"] = offset
29
+ try:
30
+ if limit > 0:
31
+ result = api.session.rest_get(url, params=params)
32
+ result = result or {}
33
+ items = result.get("items", result.get("data", result if isinstance(result, list) else [])) if isinstance(result, dict) else (result if isinstance(result, list) else [])
34
+ else:
35
+ if "max" not in params:
36
+ params["max"] = 1000
37
+ items = list(api.session.follow_pagination(url=url, params=params, item_key="items"))
38
+ except WebexError as e:
39
+ handle_rest_error(e)
40
+ if output == "json":
41
+ print_json(items)
42
+ else:
43
+ print_table(items, columns=[("ID", "id"), ("Name", "name")], limit=limit)
44
+
45
+
46
+
47
+ @app.command("create")
48
+ def create(
49
+ room_id: str = typer.Option(None, "--room-id", help="(required) A unique identifier for the room."),
50
+ content_url: str = typer.Option(None, "--content-url", help="(required) URL of the ECM folder."),
51
+ display_name: str = typer.Option(None, "--display-name", help="(required) This should match the folder name in the ECM backend."),
52
+ drive_id: str = typer.Option(None, "--drive-id", help="(required) Sharepoint or OneDrive drive id. It can be queried via MS Gr"),
53
+ item_id: str = typer.Option(None, "--item-id", help="(required) Sharepoint or OneDrive item id. It can be queried via MS Gra"),
54
+ default_folder: str = typer.Option(None, "--default-folder", help="(required) Makes the folder the default storage for the space."),
55
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
56
+ output: str = typer.Option("id", "--output", "-o", help="Output format: id|json"),
57
+ debug: bool = typer.Option(False, "--debug"),
58
+ ):
59
+ """Create an ECM folder configuration."""
60
+ api = get_api(debug=debug)
61
+ url = f"https://webexapis.com/v1/room/linkedFolders"
62
+ if json_body:
63
+ body = json.loads(json_body)
64
+ else:
65
+ body = {}
66
+ if room_id is not None:
67
+ body["roomId"] = room_id
68
+ if content_url is not None:
69
+ body["contentUrl"] = content_url
70
+ if display_name is not None:
71
+ body["displayName"] = display_name
72
+ if drive_id is not None:
73
+ body["driveId"] = drive_id
74
+ if item_id is not None:
75
+ body["itemId"] = item_id
76
+ if default_folder is not None:
77
+ body["defaultFolder"] = default_folder
78
+ _missing = [f for f in ['roomId', 'contentUrl', 'displayName', 'driveId', 'itemId', 'defaultFolder'] if f not in body or body[f] is None]
79
+ if _missing:
80
+ typer.echo("Error: Missing required fields: " + ", ".join(_missing), err=True)
81
+ raise typer.Exit(1)
82
+ try:
83
+ result = api.session.rest_post(url, json=body)
84
+ except WebexError as e:
85
+ handle_rest_error(e)
86
+ if output == "json":
87
+ print_json(result)
88
+ elif isinstance(result, dict) and "id" in result:
89
+ typer.echo(f"Created: {result['id']}")
90
+ elif not result or result == {}:
91
+ typer.echo("Created.")
92
+ else:
93
+ print_json(result)
94
+
95
+
96
+
97
+ @app.command("show")
98
+ def show(
99
+ id: str = typer.Argument(help="id"),
100
+ output: str = typer.Option("json", "--output", "-o", help="Output format: table|json"),
101
+ debug: bool = typer.Option(False, "--debug"),
102
+ ):
103
+ """Get ECM Folder Details."""
104
+ api = get_api(debug=debug)
105
+ url = f"https://webexapis.com/v1/room/linkedFolders/{id}"
106
+ try:
107
+ result = api.session.rest_get(url)
108
+ except WebexError as e:
109
+ handle_rest_error(e)
110
+ if output == "json":
111
+ print_json(result)
112
+ else:
113
+ if isinstance(result, dict):
114
+ print_table([result], columns=[("Key", ""), ("Value", "")], limit=0)
115
+ elif isinstance(result, list):
116
+ print_table(result, columns=[("ID", "id"), ("Name", "name")], limit=0)
117
+ else:
118
+ print_json(result)
119
+
120
+
121
+
122
+ @app.command("update")
123
+ def update(
124
+ id: str = typer.Argument(help="id"),
125
+ room_id: str = typer.Option(None, "--room-id", help="ID of the room that contains the room tab in question."),
126
+ content_url: str = typer.Option(None, "--content-url", help="Content URL of the folder."),
127
+ display_name: str = typer.Option(None, "--display-name", help="This should match the folder name in the ECM backend."),
128
+ drive_id: str = typer.Option(None, "--drive-id", help="Sharepoint or OneDrive drive id. It can be queried via MS Gr"),
129
+ item_id: str = typer.Option(None, "--item-id", help="Sharepoint or OneDrive item id. It can be queried via MS Gra"),
130
+ default_folder: str = typer.Option(None, "--default-folder", help="Makes the folder the default storage for the space."),
131
+ json_body: str = typer.Option(None, "--json-body", help="Full JSON body (overrides other options)"),
132
+ debug: bool = typer.Option(False, "--debug"),
133
+ ):
134
+ """Update an ECM Linked Folder."""
135
+ api = get_api(debug=debug)
136
+ url = f"https://webexapis.com/v1/room/linkedFolders/{id}"
137
+ if json_body:
138
+ body = json.loads(json_body)
139
+ else:
140
+ body = {}
141
+ if room_id is not None:
142
+ body["roomId"] = room_id
143
+ if content_url is not None:
144
+ body["contentUrl"] = content_url
145
+ if display_name is not None:
146
+ body["displayName"] = display_name
147
+ if drive_id is not None:
148
+ body["driveId"] = drive_id
149
+ if item_id is not None:
150
+ body["itemId"] = item_id
151
+ if default_folder is not None:
152
+ body["defaultFolder"] = default_folder
153
+ try:
154
+ result = api.session.rest_put(url, json=body)
155
+ except WebexError as e:
156
+ handle_rest_error(e)
157
+ typer.echo(f"Updated.")
158
+
159
+
160
+
161
+ @app.command("delete")
162
+ def delete(
163
+ id: str = typer.Argument(help="id"),
164
+ force: bool = typer.Option(False, "--force", help="Skip confirmation"),
165
+ debug: bool = typer.Option(False, "--debug"),
166
+ ):
167
+ """Unlink an ECM linked folder."""
168
+ if not force:
169
+ typer.confirm(f"Delete {id}?", abort=True)
170
+ api = get_api(debug=debug)
171
+ url = f"https://webexapis.com/v1/room/linkedFolders/{id}"
172
+ try:
173
+ api.session.rest_delete(url)
174
+ except WebexError as e:
175
+ handle_rest_error(e)
176
+ typer.echo(f"Deleted: {id}")
177
+
178
+