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,1305 @@
1
+ <!-- Not directly verified via CLI — documents wxcadm library (not generated CLI commands) -->
2
+ <!-- wxcadm v4.6.1 review 2026-03-27: All v4.5.0–v4.6.1 changes verified present. DeviceList(org, parent) signature, Device.calling_id property, DeviceMember.esn/routing_prefix/location_id, VirtualLine.name, Workspace.unassign_wxc(), WorkspaceLocation removal — all accurately documented. No discrepancies found. -->
3
+ # wxcadm: Devices, DECT, Workspaces & Virtual Lines
4
+
5
+ Reference for device management, DECT networks, workspaces, virtual lines, and number management in wxcadm.
6
+
7
+ ---
8
+
9
+ <!-- Updated by playbook session 2026-03-18 -->
10
+ ## When to Use wxcadm vs Raw HTTP
11
+
12
+ | Use wxcadm when | Use raw HTTP when |
13
+ |---|---|
14
+ | Device activation workflows (activation code generation, SIP credential retrieval) | Standard device/workspace/virtual line CRUD (list, get, create, update, delete) |
15
+ | Object-graph navigation (org -> location -> workspace -> device -> members) without tracking IDs manually | You already have the resource IDs and need a targeted API call |
16
+ | Bulk operations that benefit from stateful `DeviceList`/`WorkspaceList` filtering (e.g., `webex_calling()`, `get_by_status()`) | Single-resource reads or writes where the SDK wrapper adds no value |
17
+
18
+ > **Note:** The playbook uses raw HTTP via `api.session.rest_*()` for standard CRUD operations. wxcadm is only needed here for its device activation workflow convenience and stateful object-graph navigation.
19
+
20
+ ---
21
+
22
+ ## Table of Contents
23
+
24
+ 1. [Device Class](#device-class)
25
+ 2. [Device Members & Layout](#device-members--layout)
26
+ 3. [DECT Networks](#dect-networks)
27
+ 4. [Workspace Class](#workspace-class)
28
+ 5. [Virtual Lines](#virtual-lines)
29
+ 6. [Number Management](#number-management)
30
+ 7. [wxcadm vs wxc_sdk](#wxcadm-vs-wxc_sdk)
31
+
32
+ ---
33
+
34
+ ## Device Class
35
+
36
+ Source: `wxcadm/device.py`
37
+
38
+ ### Access Patterns
39
+
40
+ Devices can be accessed from three scopes:
41
+
42
+ ```python
43
+ # Org-level — all devices in the org
44
+ all_devices = org.devices
45
+
46
+ # Person-level — devices for a specific person
47
+ person_devices = person.devices
48
+
49
+ # Workspace-level — devices for a specific workspace
50
+ ws_devices = workspace.devices
51
+
52
+ # Location-level — devices at a location
53
+ loc_devices = DeviceList(org, parent=location)
54
+ ```
55
+
56
+ All return a `DeviceList` (subclass of `UserList`).
57
+
58
+ ### DeviceList Methods
59
+
60
+ #### `DeviceList.get(...)`
61
+
62
+ Find a device by various criteria. Returns a single `Device` or `None` (except `connection_status`, which returns a list).
63
+
64
+ ```python
65
+ DeviceList.get(
66
+ id: Optional[str] = None,
67
+ name: Optional[str] = None, # Case-insensitive
68
+ mac_address: Optional[str] = None, # Accepts colons, dashes, or raw
69
+ spark_id: Optional[str] = None,
70
+ connection_status: Optional[str] = None # Returns a list
71
+ ) -> Optional[Device]
72
+ ```
73
+
74
+ ```python
75
+ device = org.devices.get(mac_address="AA:BB:CC:DD:EE:FF")
76
+ device = org.devices.get(name="Lobby Phone")
77
+ disconnected = org.devices.get(connection_status="disconnected")
78
+ ```
79
+
80
+ #### `DeviceList.create(...)`
81
+
82
+ Add a new device. Requires a `SupportedDevice` model reference.
83
+
84
+ ```python
85
+ DeviceList.create(
86
+ model: SupportedDevice,
87
+ mac: Optional[str] = None, # Omit to generate activation code
88
+ password: Optional[str] = None, # For non-Cisco-managed devices
89
+ person: Optional[Person] = None, # Required when DeviceList is Org-level
90
+ workspace: Optional[Workspace] = None # Required when DeviceList is Org-level
91
+ ) -> dict
92
+ ```
93
+
94
+ Return value varies by device type:
95
+
96
+ | Scenario | Keys in returned dict |
97
+ |---|---|
98
+ | Activation code (no MAC) | `device_id`, `activation_code` |
99
+ | MAC-based, Cisco-managed | `device_id`, `mac`, `device_object` |
100
+ | MAC-based, partner/customer-managed | `device_id`, `mac`, `device_object`, `sip_auth_user`, `line_port`, `password`, `sip_userpart`, `sip_hostpart`, `sip_outbound_proxy`, `sip_outbound_proxy_srv` |
101
+
102
+ ```python
103
+ # Get a supported device model
104
+ model_8865 = org.devices.supported_devices.get("Cisco 8865")
105
+
106
+ # Create with activation code
107
+ result = workspace.devices.create(model=model_8865)
108
+ print(result['activation_code'])
109
+
110
+ # Create with MAC
111
+ result = workspace.devices.create(
112
+ model=model_8865,
113
+ mac="AABBCCDDEEFF"
114
+ )
115
+ print(result['device_id'])
116
+ ```
117
+
118
+ **Scope note:** Your token needs `identity:placeonetimepassword_create` to generate activation codes.
119
+
120
+ #### `DeviceList.refresh()`
121
+
122
+ Re-fetches the device list from Webex.
123
+
124
+ #### `DeviceList.webex_calling(enabled=True)`
125
+
126
+ Filter to devices with Webex Calling enabled or disabled.
127
+
128
+ ```python
129
+ wxc_devices = org.devices.webex_calling()
130
+ non_wxc = org.devices.webex_calling(enabled=False)
131
+ ```
132
+
133
+ #### `DeviceList.get_by_status(status: str)`
134
+
135
+ Filter devices by connection status. Accepts raw API values plus grouping shortcuts:
136
+
137
+ | Value | Matches |
138
+ |---|---|
139
+ | `"online"` | `connected`, `connected_with_issues` |
140
+ | `"offline"` | `disconnected`, `offline_expired`, `offline_deep_sleep` |
141
+ | `"connected"` | Exact match |
142
+ | `"disconnected"` | Exact match |
143
+ | `"activating"` | Exact match |
144
+ | `"unknown"` | Exact match |
145
+
146
+ ```python
147
+ offline_devices = org.devices.get_by_status("offline")
148
+ ```
149
+
150
+ ### Device Attributes
151
+
152
+ | Attribute | Type | Description |
153
+ |---|---|---|
154
+ | `id` | `str` | Device ID |
155
+ | `model` | `str` | Model name (e.g. `"DMS Cisco 8865"`) |
156
+ | `mac` | `str` | MAC address (uppercase, no delimiters) |
157
+ | `display_name` | `str` | Display name in Webex |
158
+ | `ip_address` | `str` | Device IP |
159
+ | `activation_state` | `str` | Activation state |
160
+ | `type` | `str` | Type of device association |
161
+ | `is_owner` | `bool` | Whether parent is primary owner |
162
+ | `connection_status` | `str` | Online/offline status |
163
+ | `serial` | `str` | Serial number |
164
+ | `software` | `str` | OS/firmware version |
165
+ | `upgrade_channel` | `str` | Assigned upgrade channel |
166
+ | `tags` | `list` | Device tags |
167
+ | `capabilities` | `list` | Device capabilities |
168
+ | `user_permissions` | `list` | User permissions (e.g. `"xapi"`) |
169
+ | `owner` | `Person\|Workspace\|None` | Resolved primary owner |
170
+ | `location_id` | `str` | Location ID |
171
+ | `created` | `str` | Creation timestamp |
172
+ | `first_seen` | `str` | First online timestamp |
173
+ | `last_seen` | `str` | Last online timestamp |
174
+
175
+ ### Device Properties & Methods
176
+
177
+ #### `Device.calling_id` (property)
178
+
179
+ The telephony-side device ID. Used internally for `/v1/telephony/config/devices/` API calls. Fetched lazily from the telephony config endpoint if not present in the device record.
180
+
181
+ #### `Device.config` (property)
182
+
183
+ Returns the full telephony device configuration as a dict.
184
+
185
+ #### `Device.settings` (property, settable)
186
+
187
+ Read or write the device settings. The structure varies by device model -- the raw dict from Webex is returned.
188
+
189
+ ```python
190
+ # Read
191
+ settings = device.settings
192
+
193
+ # Write
194
+ device.settings = updated_settings_dict
195
+ ```
196
+
197
+ #### `Device.layout` (property)
198
+
199
+ Returns the `DeviceLayout` for the device. See [DeviceLayout](#devicelayout) below.
200
+
201
+ #### `Device.members` (property)
202
+
203
+ Returns a `DeviceMemberList` of configured lines on the device. See [Device Members](#device-members--layout).
204
+
205
+ #### `Device.apply_changes() -> bool`
206
+
207
+ Issues a request to the device to download and apply configuration changes.
208
+
209
+ ```python
210
+ device.apply_changes()
211
+ ```
212
+
213
+ #### `Device.delete() -> bool`
214
+
215
+ Deletes the device from Webex Calling and Control Hub entirely.
216
+
217
+ #### `Device.change_tags(operation, tag=None)`
218
+
219
+ Modify the device tags.
220
+
221
+ ```python
222
+ Device.change_tags(
223
+ operation: str, # "add", "remove", or "replace"
224
+ tag: Optional[str | list] = None
225
+ ) -> bool
226
+ ```
227
+
228
+ ```python
229
+ device.change_tags("add", "floor-3")
230
+ device.change_tags("replace", ["lobby", "reception"])
231
+ device.change_tags("remove")
232
+ ```
233
+
234
+ ### SupportedDevice & SupportedDeviceList
235
+
236
+ `SupportedDeviceList` enumerates all device models Webex supports. Access via:
237
+
238
+ ```python
239
+ supported = org.devices.supported_devices
240
+ ```
241
+
242
+ #### `SupportedDeviceList.get(model_name: str)`
243
+
244
+ Partial, case-insensitive match. Returns a single `SupportedDevice` if one match, or a list if multiple.
245
+
246
+ ```python
247
+ model = org.devices.supported_devices.get("Cisco 8865")
248
+ # Returns a SupportedDevice dataclass
249
+
250
+ all_cisco_models = org.devices.supported_devices.get("Cisco")
251
+ # Returns a list of SupportedDevice
252
+ ```
253
+
254
+ #### SupportedDevice Attributes
255
+
256
+ | Attribute | Type | Description |
257
+ |---|---|---|
258
+ | `model` | `str` | Model string (e.g. `"DMS Cisco 8865"`) |
259
+ | `display_name` | `str` | Human-friendly display name |
260
+ | `type` | `str` | Device type |
261
+ | `manufacturer` | `str` | Manufacturer name |
262
+ | `managed_by` | `str` | `"CISCO"` or third-party |
263
+ | `supported_for` | `list` | `["PEOPLE"]`, `["PLACE"]`, or both |
264
+ | `onboarding_method` | `list` | e.g. `["ACTIVATION_CODE", "MAC_ADDRESS"]` |
265
+ | `number_of_line_ports` | `int` | Number of line ports |
266
+ | `kem_support_enabled` | `bool` | KEM module support |
267
+ | `kem_module_count` | `int\|None` | Number of KEM modules supported |
268
+ | `kem_module_type` | `list\|None` | KEM module types |
269
+ | `allow_configure_layout_enabled` | `bool` | Layout configuration support |
270
+ | `allow_configure_ports_enabled` | `bool` | Port configuration support |
271
+ | `customizable_line_label_enabled` | `bool` | Custom line labels support |
272
+ | `upgrade_channel_enabled` | `bool` | Multiple upgrade channels |
273
+ | `customized_behaviors_enabled` | `bool` | Custom behaviors support |
274
+ | `default_upgrade_channel` | `str\|None` | Default upgrade channel |
275
+ | `additional_primary_line_appearances_enabled` | `bool\|None` | Additional PLA support |
276
+ | `basic_emergency_nomadic_enabled` | `bool\|None` | HELD support |
277
+
278
+ ---
279
+
280
+ ## Device Members & Layout
281
+
282
+ ### DeviceMemberList
283
+
284
+ Each device has a `members` property returning a `DeviceMemberList` -- the configured lines on the phone.
285
+
286
+ ```python
287
+ members = device.members
288
+ print(members.max_line_count) # e.g. 10
289
+ ```
290
+
291
+ #### `DeviceMemberList.add(...)`
292
+
293
+ Add a configured line (shared line appearance, primary line, etc.) to the device.
294
+
295
+ ```python
296
+ DeviceMemberList.add(
297
+ members: Person | Workspace | VirtualLine | list,
298
+ line_type: str = 'shared', # "primary" or "shared"
299
+ line_label: Optional[str] = None, # MPP only via this API; 9800-series uses Phone.LineKeyLabel PhoneOS config key instead
300
+ hotline_enabled: bool = False,
301
+ hotline_destination: Optional[str] = None,
302
+ allow_call_decline: bool = False
303
+ ) -> bool
304
+ ```
305
+
306
+ ```python
307
+ # Add a shared line appearance
308
+ device.members.add(some_person, line_type='shared', line_label="Sales")
309
+
310
+ # Add multiple members at once (all get same settings)
311
+ device.members.add([person_a, person_b], line_type='shared')
312
+ ```
313
+
314
+ **ATA note:** For Cisco 191/192 ATAs, the `t38FaxCompression` field is automatically set to `False` in the payload.
315
+
316
+ #### `DeviceMemberList.available_members()`
317
+
318
+ Returns a list of People and Workspaces eligible for assignment to this device.
319
+
320
+ #### `DeviceMemberList.ports_available()`
321
+
322
+ Returns a list of available port numbers.
323
+
324
+ #### `DeviceMemberList.port_map()`
325
+
326
+ Returns a dict mapping every port number to its assigned `DeviceMember` (or `None` if unassigned).
327
+
328
+ ```python
329
+ port_map = device.members.port_map()
330
+ # {1: <DeviceMember>, 2: None, 3: None, ...}
331
+ ```
332
+
333
+ #### `DeviceMemberList.get(person=None, workspace=None)`
334
+
335
+ Find a specific member on the device.
336
+
337
+ #### `DeviceMemberList.refresh()`
338
+
339
+ Re-fetch configured lines from Webex.
340
+
341
+ ### DeviceMember
342
+
343
+ Represents a single configured line on a device.
344
+
345
+ #### DeviceMember Attributes
346
+
347
+ | Attribute | Type | Description |
348
+ |---|---|---|
349
+ | `id` | `str` | Member ID (Person/Workspace/VirtualLine ID) |
350
+ | `member_type` | `str` | `"person"` or `"workspace"` |
351
+ | `port` | `int` | Port/line number on the device |
352
+ | `primary_owner` | `bool` | Whether this is the primary owner |
353
+ | `line_type` | `str` | `"PRIMARY"` or `"SHARED_CALL_APPEARANCE"` |
354
+ | `line_weight` | `int` | Number of ports this line consumes |
355
+ | `hotline_enabled` | `bool` | Hotline status |
356
+ | `hotline_destination` | `str\|None` | Hotline destination number |
357
+ | `call_decline_all` | `bool` | Decline-all-devices behavior |
358
+ | `line_label` | `str\|None` | Custom line label (MPP only via this API — 9800-series uses `Phone.LineKeyLabel` / `Phone.LineKeySecondaryLabel` PhoneOS config keys via `device-configurations` instead) |
359
+ | `first_name` | `str\|None` | Member first name |
360
+ | `last_name` | `str\|None` | Member last name |
361
+ | `phone_number` | `str\|None` | Member phone number |
362
+ | `extension` | `str\|None` | Member extension |
363
+ | `esn` | `str\|None` | Enterprise Significant Number |
364
+ | `location_id` | `str\|None` | Location ID |
365
+
366
+ #### `DeviceMember.set_line_label(label: str) -> DeviceMember`
367
+
368
+ Set or update the line label on the device. MPP devices only.
369
+
370
+ ```python
371
+ member = device.members.get(person=some_person)
372
+ member.set_line_label("Front Desk")
373
+ ```
374
+
375
+ #### `DeviceMember.set_hotline(enabled: bool, destination: Optional[str]) -> DeviceMember`
376
+
377
+ Enable or disable hotline for the line.
378
+
379
+ ```python
380
+ member.set_hotline(enabled=True, destination="+15551234567")
381
+ member.set_hotline(enabled=False)
382
+ ```
383
+
384
+ #### `DeviceMember.set_call_decline_all(enabled: bool) -> DeviceMember`
385
+
386
+ Control decline behavior. When `True`, declining on this device declines on all devices. When `False`, only silences this device.
387
+
388
+ ### DeviceLayout
389
+
390
+ Manages the key layout on a device (line keys and KEM keys).
391
+
392
+ #### DeviceLayout Attributes
393
+
394
+ | Attribute | Type | Description |
395
+ |---|---|---|
396
+ | `layout_mode` | `str` | Layout mode |
397
+ | `user_reorder_enabled` | `bool` | Whether user can reorder |
398
+ | `line_keys` | `list` | Line key configuration |
399
+ | `kem_type` | `str\|None` | KEM module type |
400
+ | `kem_keys` | `list\|None` | KEM key configuration |
401
+
402
+ #### Reading and Setting Layout
403
+
404
+ ```python
405
+ layout = device.layout
406
+ print(layout.line_keys)
407
+
408
+ # Modify and apply
409
+ layout.line_keys = [...] # See Webex Developer docs for format
410
+ device.set_layout(layout)
411
+ ```
412
+
413
+ The `line_keys` and `kem_keys` lists use the raw format from the Webex Developer documentation. See: https://developer.webex.com/docs/api/v1/device-call-settings/modify-device-layout-by-device-id
414
+
415
+ ---
416
+
417
+ ## DECT Networks
418
+
419
+ Source: `wxcadm/dect.py`
420
+
421
+ DECT networks manage Cisco DBS110/DBS210 base stations and their associated handsets.
422
+
423
+ ### Access Pattern
424
+
425
+ ```python
426
+ # Org-level — all DECT networks
427
+ dect_networks = DECTNetworkList(org)
428
+
429
+ # Location-level — DECT networks at a specific location
430
+ dect_networks = DECTNetworkList(org, location=location)
431
+ ```
432
+
433
+ **Note:** Both `org.dect_networks` and `location.dect_networks` property accessors exist and return `DECTNetworkList` instances. Direct instantiation (`DECTNetworkList(org)` or `DECTNetworkList(org, location=location)`) also works.
434
+
435
+ ### DECTNetworkList Methods
436
+
437
+ #### `DECTNetworkList.create(...)`
438
+
439
+ Create a new DECT network.
440
+
441
+ ```python
442
+ DECTNetworkList.create(
443
+ name: str,
444
+ model: str, # "DBS110", "110", "DBS210", or "210"
445
+ default_access_code: Optional[str] = None,
446
+ handset_display_name: Optional[str] = None,
447
+ location: Optional[Location] = None # Required for Org-level lists
448
+ ) -> DECTNetwork
449
+ ```
450
+
451
+ The `model` parameter is normalized internally:
452
+ - `"110"` or `"DBS110"` becomes `"DMS Cisco DBS110"`
453
+ - `"210"` or `"DBS210"` becomes `"DMS Cisco DBS210"`
454
+
455
+ ```python
456
+ network = dect_networks.create(
457
+ name="Building A DECT",
458
+ model="DBS210",
459
+ default_access_code="1234",
460
+ location=building_a_location
461
+ )
462
+ ```
463
+
464
+ #### `DECTNetworkList.delete(network: DECTNetwork)`
465
+
466
+ Delete a DECT network. Returns the updated `DECTNetworkList`.
467
+
468
+ #### `DECTNetworkList.refresh()`
469
+
470
+ Re-query the list from Webex.
471
+
472
+ #### `DECTNetworkList.with_base_stations(count=1)`
473
+
474
+ Filter to networks with at least `count` base stations.
475
+
476
+ #### `DECTNetworkList.with_handsets(count=1)`
477
+
478
+ Filter to networks with at least `count` handsets assigned.
479
+
480
+ ### DECTNetwork Attributes
481
+
482
+ | Attribute | Type | Description |
483
+ |---|---|---|
484
+ | `id` | `str` | Network ID |
485
+ | `name` | `str` | Network name |
486
+ | `display_name` | `str` | Display name |
487
+ | `chain_id` | `str` | Chain ID |
488
+ | `model` | `str` | Base station model |
489
+ | `default_access_code_enabled` | `bool` | Whether default access code is on |
490
+ | `default_access_code` | `str` | The default access code |
491
+ | `number_of_base_stations` | `int` | Count of base stations |
492
+ | `number_of_handsets_assigned` | `int` | Count of assigned handsets |
493
+ | `number_of_lines` | `int` | Count of lines |
494
+ | `location_id` | `str` | Location ID |
495
+
496
+ ### DECTNetwork Methods
497
+
498
+ #### `DECTNetwork.base_stations` (property)
499
+
500
+ Returns a list of `DECTBaseStation` instances.
501
+
502
+ #### `DECTNetwork.handsets` (property)
503
+
504
+ Returns a list of `DECTHandset` instances.
505
+
506
+ #### `DECTNetwork.add_base_stations(mac_list: list) -> list[DECTBaseStation]`
507
+
508
+ Add one or more base stations by MAC address. Returns the full list of base stations (including pre-existing ones).
509
+
510
+ ```python
511
+ stations = network.add_base_stations(["AABBCCDDEEFF", "112233445566"])
512
+ ```
513
+
514
+ #### `DECTNetwork.delete_base_station(base_station: str | DECTBaseStation) -> list`
515
+
516
+ Delete a base station by `DECTBaseStation` instance or MAC address string. Returns remaining base stations.
517
+
518
+ ```python
519
+ network.delete_base_station("AABBCCDDEEFF")
520
+ ```
521
+
522
+ #### `DECTNetwork.get_base_station(mac: str) -> Optional[DECTBaseStation]`
523
+
524
+ Find a base station by MAC address.
525
+
526
+ #### `DECTNetwork.add_handset(...)`
527
+
528
+ Add a handset to the network.
529
+
530
+ ```python
531
+ DECTNetwork.add_handset(
532
+ display_name: str, # 16 chars max
533
+ line1: Person | Workspace, # Required
534
+ line2: Optional[Person | Workspace] # Optional second line
535
+ ) -> bool
536
+ ```
537
+
538
+ ```python
539
+ network.add_handset(
540
+ display_name="Warehouse 1",
541
+ line1=warehouse_workspace,
542
+ line2=manager_person
543
+ )
544
+ ```
545
+
546
+ #### `DECTNetwork.delete_handset(handset: DECTHandset) -> list`
547
+
548
+ Delete a handset. Returns remaining handsets.
549
+
550
+ #### `DECTNetwork.set_name(name: str, display_name: Optional[str]) -> bool`
551
+
552
+ Change the network name (and optionally display name).
553
+
554
+ #### `DECTNetwork.set_display_name(display_name: str) -> bool`
555
+
556
+ Change only the display name.
557
+
558
+ #### `DECTNetwork.enable_default_access_code(access_code: Optional[str]) -> bool`
559
+
560
+ Enable the default access code. If the code was previously set and disabled, it can be re-enabled without passing the code again.
561
+
562
+ Raises `ValueError` if no code exists and none is provided.
563
+
564
+ #### `DECTNetwork.disable_default_access_code() -> bool`
565
+
566
+ Disable the default access code (preserves the stored code value).
567
+
568
+ #### `DECTNetwork.delete() -> bool`
569
+
570
+ Delete the entire DECT network.
571
+
572
+ ### DECTBaseStation
573
+
574
+ | Attribute | Type | Description |
575
+ |---|---|---|
576
+ | `id` | `str` | Base station ID |
577
+ | `mac` | `str` | MAC address (uppercase) |
578
+ | `number_of_lines_registered` | `int` | Lines registered to this base |
579
+
580
+ #### `DECTBaseStation.delete() -> bool`
581
+
582
+ Delete the base station.
583
+
584
+ #### `DECTBaseStation.get_handsets() -> list[DECTHandset]`
585
+
586
+ Get handsets associated with this specific base station.
587
+
588
+ ### DECTHandset
589
+
590
+ | Attribute | Type | Description |
591
+ |---|---|---|
592
+ | `id` | `str` | Handset ID |
593
+ | `index` | `str` | Handset index |
594
+ | `display_name` | `str` | Display name |
595
+ | `access_code` | `str` | Access code |
596
+ | `lines` | `list` | Configured lines |
597
+ | `mac` | `str` (property) | MAC address (lazy-loaded) |
598
+
599
+ #### `DECTHandset.delete() -> bool`
600
+
601
+ Delete the handset.
602
+
603
+ #### `DECTHandset.set_handset_display_name(display_name: str) -> bool`
604
+
605
+ Change the text displayed on the handset screen.
606
+
607
+ ---
608
+
609
+ ## Workspace Class
610
+
611
+ Source: `wxcadm/workspace.py`
612
+
613
+ Workspaces represent shared physical locations (conference rooms, lobby phones, common areas) in Webex Calling.
614
+
615
+ ### Access Pattern
616
+
617
+ ```python
618
+ # Org-level
619
+ workspaces = org.workspaces # WorkspaceList
620
+
621
+ # Location-level
622
+ workspaces = WorkspaceList(org, location=location)
623
+ ```
624
+
625
+ ### WorkspaceList Methods
626
+
627
+ #### `WorkspaceList.create(...)`
628
+
629
+ Create a new Workspace with Webex Calling enabled.
630
+
631
+ ```python
632
+ WorkspaceList.create(
633
+ location: Location,
634
+ name: str,
635
+ floor: Optional[LocationFloor] = None,
636
+ capacity: Optional[int] = None,
637
+ type: Optional[str] = 'notSet', # See types below
638
+ phone_number: Optional[str] = None,
639
+ extension: Optional[str] = None,
640
+ notes: Optional[str] = None,
641
+ hotdesking: Optional[bool] = False,
642
+ supported_devices: Optional[str] = 'phones', # "phones" or "collaborationDevices"
643
+ license_type: str = 'workspace', # "workspace", "professional", or "hotdesk"
644
+ ignore_license_overage: Optional[bool] = True
645
+ ) -> Workspace
646
+ ```
647
+
648
+ **Workspace types:** `"notSet"`, `"focus"`, `"huddle"`, `"meetingRoom"`, `"open"`, `"desk"`, `"other"`
649
+
650
+ **License types:**
651
+ - `"workspace"` -- Standard Workspace license
652
+ - `"professional"` -- Professional license (full calling features)
653
+ - `"hotdesk"` -- Hot Desking license (requires `hotdesking=True`)
654
+
655
+ ```python
656
+ lobby = workspaces.create(
657
+ location=hq_location,
658
+ name="Main Lobby",
659
+ extension="8100",
660
+ phone_number="+15559998100",
661
+ type="open",
662
+ supported_devices="phones",
663
+ license_type="workspace"
664
+ )
665
+ ```
666
+
667
+ Raises:
668
+ - `ValueError` if neither `extension` nor `phone_number` is provided (unless `hotdesk` license)
669
+ - `wxcadm.NotSubscribedForLicenseError` if the license type is not available
670
+ - `wxcadm.LicenseOverageError` if `ignore_license_overage=False` and assignment would exceed count
671
+
672
+ #### `WorkspaceList.get(id=None, name=None, uuid=None)`
673
+
674
+ Find a workspace by ID, name, or UUID.
675
+
676
+ ```python
677
+ ws = workspaces.get(name="Main Lobby")
678
+ ws = workspaces.get(id="some-workspace-id")
679
+ ```
680
+
681
+ #### `WorkspaceList.get_by_id(id: str)`
682
+
683
+ Find a workspace by ID only.
684
+
685
+ #### `WorkspaceList.webex_calling()`
686
+
687
+ Return only workspaces with Webex Calling enabled.
688
+
689
+ #### `WorkspaceList.professional()`
690
+
691
+ Return only workspaces with a Professional license.
692
+
693
+ #### `WorkspaceList.refresh()`
694
+
695
+ Re-query the workspace list from Webex.
696
+
697
+ ### Workspace Attributes
698
+
699
+ | Attribute | Type | Description |
700
+ |---|---|---|
701
+ | `id` | `str` | Workspace ID |
702
+ | `name` | `str` | Display name |
703
+ | `org` | `Org` | Parent org |
704
+ | `location` | `Location\|None` | Assigned location |
705
+ | `capacity` | `int` | Room capacity |
706
+ | `type` | `str` | Workspace type |
707
+ | `sip_address` | `str` | SIP URI |
708
+ | `calling` | `str` | Calling type (`webexCalling`, `freeCalling`, etc.) |
709
+ | `calendar` | `dict\|None` | Calendar connector info |
710
+ | `notes` | `str` | Free-form notes |
711
+ | `licenses` | `list` | Assigned license IDs |
712
+ | `created` | `str` | Creation timestamp |
713
+
714
+ ### Workspace Properties
715
+
716
+ #### `Workspace.number` (property)
717
+
718
+ Returns the phone number. Single number returns a string; multiple numbers returns a list. Makes an API call on first access.
719
+
720
+ #### `Workspace.extension` (property)
721
+
722
+ Returns the extension. Same single/list behavior as `number`.
723
+
724
+ #### `Workspace.esn` (property)
725
+
726
+ The Enterprise Significant Number.
727
+
728
+ #### `Workspace.devices` (property)
729
+
730
+ Returns the `DeviceList` for this workspace.
731
+
732
+ #### `Workspace.caller_id` (property)
733
+
734
+ Returns the Caller ID settings as a dict.
735
+
736
+ #### `Workspace.ecbn` (property)
737
+
738
+ Returns the Emergency Callback Number details.
739
+
740
+ #### `Workspace.barge_in` (property)
741
+
742
+ Returns the `BargeInSettings` for the workspace, or `None` if not configured.
743
+
744
+ #### `Workspace.monitoring` (property)
745
+
746
+ Returns the `MonitoringList` associated with the workspace.
747
+
748
+ #### `Workspace.license_type` (property)
749
+
750
+ Returns the license type: `"WORKSPACE"`, `"PROFESSIONAL"`, `"HOTDESK"`, `"MULTIPLE"`, or `None`.
751
+
752
+ #### `Workspace.spark_id` (property)
753
+
754
+ The internal Webex identifier (decoded from base64).
755
+
756
+ ### Workspace Methods
757
+
758
+ #### `Workspace.set_caller_id(name, number, block_for_received_calls=False) -> bool`
759
+
760
+ Set the Caller ID for the workspace.
761
+
762
+ ```python
763
+ Workspace.set_caller_id(
764
+ name: str, # "direct", "location", or custom string
765
+ number: str, # "direct", "location", or custom number
766
+ block_for_received_calls: Optional[bool] = False
767
+ ) -> bool
768
+ ```
769
+
770
+ ```python
771
+ workspace.set_caller_id(name="Main Lobby", number="direct")
772
+ workspace.set_caller_id(name="location", number="location")
773
+ workspace.set_caller_id(name="Custom Name", number="+15551234567")
774
+ ```
775
+
776
+ #### `Workspace.set_ecbn(value) -> bool`
777
+
778
+ Set the Emergency Callback Number.
779
+
780
+ ```python
781
+ # Use the workspace's own direct line
782
+ workspace.set_ecbn("direct")
783
+
784
+ # Use the location ECBN
785
+ workspace.set_ecbn("location")
786
+
787
+ # Use another member's number
788
+ workspace.set_ecbn(some_person) # Person, Workspace, or VirtualLine
789
+ ```
790
+
791
+ #### `Workspace.assign_wxc(...) -> bool`
792
+
793
+ Enable Webex Calling for a workspace that does not currently have it.
794
+
795
+ ```python
796
+ Workspace.assign_wxc(
797
+ location: Location,
798
+ phone_number: Optional[str] = None,
799
+ extension: Optional[str] = None,
800
+ license_type: str = 'workspace',
801
+ ignore_license_overage: bool = True
802
+ ) -> bool
803
+ ```
804
+
805
+ #### `Workspace.unassign_wxc() -> bool`
806
+
807
+ Disable Webex Calling. Only works for workspaces with `supportedDevices="collaborationDevices"`. Phone-based workspaces must be deleted and recreated.
808
+
809
+ #### `Workspace.set_professional_license() -> bool`
810
+
811
+ Upgrade the workspace to a Professional license. No-op if already Professional.
812
+
813
+ #### `Workspace.set_hotdesk(enabled=True) -> bool`
814
+
815
+ Enable or disable hot desking.
816
+
817
+ #### `Workspace.delete() -> bool`
818
+
819
+ Delete the workspace entirely.
820
+
821
+ #### `Workspace.get_config()`
822
+
823
+ Refresh the workspace config from the API.
824
+
825
+ #### `Workspace.get_monitored_by()`
826
+
827
+ Returns a list of users/workspaces that are monitoring this workspace.
828
+
829
+ ---
830
+
831
+ ## Virtual Lines
832
+
833
+ Source: `wxcadm/virtual_line.py`
834
+
835
+ Virtual Lines represent phone identities that are not tied to a specific person -- used for shared numbers, lobby lines, or lines that appear on multiple devices.
836
+
837
+ ### Access Pattern
838
+
839
+ ```python
840
+ # Org-level
841
+ virtual_lines = org.virtual_lines # VirtualLineList
842
+
843
+ # Location-level
844
+ virtual_lines = VirtualLineList(org, location=location)
845
+ ```
846
+
847
+ ### VirtualLineList Methods
848
+
849
+ #### `VirtualLineList.create(...)`
850
+
851
+ Create a new Virtual Line.
852
+
853
+ ```python
854
+ VirtualLineList.create(
855
+ first_name: str,
856
+ last_name: str,
857
+ phone_number: Optional[str] = None,
858
+ extension: Optional[str] = None,
859
+ location: Optional[Location | str] = None, # Required for Org-level lists
860
+ caller_id_first_name: Optional[str] = None,
861
+ caller_id_last_name: Optional[str] = None,
862
+ caller_id_number: Optional[str] = None
863
+ ) -> VirtualLine
864
+ ```
865
+
866
+ At least one of `phone_number` or `extension` is required.
867
+
868
+ ```python
869
+ vl = virtual_lines.create(
870
+ first_name="Sales",
871
+ last_name="Line",
872
+ extension="8200",
873
+ phone_number="+15559998200",
874
+ location=hq_location
875
+ )
876
+ ```
877
+
878
+ **Note:** The returned `VirtualLine` only has the `id` populated initially. Call `vl.refresh_config()` to populate all fields.
879
+
880
+ #### `VirtualLineList.get(...)`
881
+
882
+ Find a virtual line by ID or name.
883
+
884
+ ```python
885
+ VirtualLineList.get(
886
+ id: Optional[str] = None,
887
+ first_name: Optional[str] = None,
888
+ last_name: Optional[str] = None,
889
+ name: Optional[str] = None # "first last" shortcut
890
+ ) -> Optional[VirtualLine]
891
+ ```
892
+
893
+ ```python
894
+ vl = virtual_lines.get(name="Sales Line")
895
+ vl = virtual_lines.get(first_name="Sales", last_name="Line")
896
+ ```
897
+
898
+ Name matching is case-insensitive. Both `first_name` and `last_name` are required when searching by name (unless using the `name` shortcut).
899
+
900
+ #### `VirtualLineList.refresh()`
901
+
902
+ Re-query from Webex.
903
+
904
+ ### VirtualLine Attributes
905
+
906
+ | Attribute | Type | Description |
907
+ |---|---|---|
908
+ | `id` | `str` | Virtual Line ID |
909
+ | `first_name` | `str` | First name |
910
+ | `last_name` | `str` | Last name |
911
+ | `caller_id_first_name` | `str` | Caller ID first name |
912
+ | `caller_id_last_name` | `str` | Caller ID last name |
913
+ | `caller_id_number` | `str` | Caller ID number |
914
+ | `external_caller_id_policy` | `str` | External caller ID policy |
915
+ | `extension` | `str\|None` | Extension |
916
+ | `esn` | `str\|None` | Enterprise Significant Number |
917
+ | `phone_number` | `str\|None` | DID phone number |
918
+ | `location_id` | `str` | Location ID |
919
+ | `billing_plan` | `str` | Billing plan |
920
+ | `num_devices_assigned` | `int` | Count of assigned devices |
921
+
922
+ ### VirtualLine Properties (Lazy-Loaded)
923
+
924
+ These trigger a detail API call on first access:
925
+
926
+ | Property | Type | Description |
927
+ |---|---|---|
928
+ | `display_name` | `str` | Full display name |
929
+ | `name` | `str` | Alias for `display_name` |
930
+ | `directory_search_enabled` | `bool` | Directory search visibility |
931
+ | `announcement_language` | `str` | Announcement language |
932
+ | `time_zone` | `str` | Time zone |
933
+ | `ecbn` | `dict` | Emergency Callback Number details |
934
+
935
+ ### VirtualLine Methods
936
+
937
+ #### `VirtualLine.update(**kwargs) -> bool`
938
+
939
+ Update any attribute(s) with keyword arguments.
940
+
941
+ ```python
942
+ vl.update(first_name="Reception", last_name="Desk")
943
+ vl.update(phone_number="+15559998201")
944
+ vl.update(extension="8201", time_zone="America/New_York")
945
+ ```
946
+
947
+ Raises `AttributeError` if you pass a non-existing attribute.
948
+
949
+ #### `VirtualLine.set_ecbn(value)`
950
+
951
+ Set the Emergency Callback Number. Same interface as `Workspace.set_ecbn()`.
952
+
953
+ ```python
954
+ vl.set_ecbn("direct")
955
+ vl.set_ecbn("location")
956
+ vl.set_ecbn(some_person)
957
+ ```
958
+
959
+ #### `VirtualLine.enable_call_recording(...) -> bool`
960
+
961
+ Enable and configure call recording.
962
+
963
+ ```python
964
+ VirtualLine.enable_call_recording(
965
+ type: str, # "always", "never", "always_with_pause", "on_demand"
966
+ record_vm: bool = False,
967
+ announcement_enabled: bool = False,
968
+ reminder_tone: bool = False,
969
+ reminder_interval: int = 30,
970
+ can_play: bool = True,
971
+ can_download: bool = True,
972
+ can_delete: bool = True,
973
+ can_share: bool = True,
974
+ transcribe: bool = True,
975
+ ai_summary: bool = True
976
+ ) -> bool
977
+ ```
978
+
979
+ ```python
980
+ vl.enable_call_recording(
981
+ type="always",
982
+ announcement_enabled=True,
983
+ transcribe=True,
984
+ ai_summary=True
985
+ )
986
+ ```
987
+
988
+ **Note:** The `transcribe`, `can_play`, etc. parameters only apply to Webex-platform recording. Third-party recording providers ignore them.
989
+
990
+ #### `VirtualLine.disable_call_recording() -> bool`
991
+
992
+ Disable call recording. Returns `True` even if recording was not previously enabled.
993
+
994
+ #### `VirtualLine.get_call_recording() -> dict`
995
+
996
+ Returns the current call recording configuration.
997
+
998
+ **Bug:** This method has a confirmed bug in the source -- it passes `'get'` as the first positional argument to `self.org.api.get()`, where the first parameter is `endpoint`. The actual URL `v1/telephony/config/virtualLines/{id}/callRecording` is passed as the second argument, which maps to `params`. This will cause an incorrect API call. Use raw HTTP as a workaround: `GET /v1/telephony/config/virtualLines/{id}/callRecording`.
999
+
1000
+ #### `VirtualLine.refresh_config() -> bool`
1001
+
1002
+ Re-fetch the Virtual Line config from Webex. Useful after `create()` since the initial instance only has the ID.
1003
+
1004
+ #### `VirtualLine.delete() -> bool`
1005
+
1006
+ Delete the Virtual Line.
1007
+
1008
+ **Bug:** The `delete()` method returns `False` on success. This is a confirmed bug in the source -- the method explicitly `return False` after the API call succeeds, while other `delete()` methods in wxcadm return `True`.
1009
+
1010
+ #### `VirtualLine.get_monitored_by()`
1011
+
1012
+ Returns a list of users/workspaces monitoring this virtual line.
1013
+
1014
+ ---
1015
+
1016
+ ## Number Management
1017
+
1018
+ Source: `wxcadm/number.py`
1019
+
1020
+ ### Access Pattern
1021
+
1022
+ ```python
1023
+ # Org-level
1024
+ numbers = org.numbers # NumberList
1025
+
1026
+ # Location-level
1027
+ numbers = NumberList(org, location=location)
1028
+ ```
1029
+
1030
+ ### Number Dataclass
1031
+
1032
+ `Number` is a `@dataclass_json` decorated dataclass with automatic camelCase mapping.
1033
+
1034
+ | Attribute | Type | Description |
1035
+ |---|---|---|
1036
+ | `phone_number` | `str\|None` | The phone number (E.164) |
1037
+ | `extension` | `str\|None` | Extension |
1038
+ | `routing_prefix` | `str\|None` | Location routing prefix |
1039
+ | `esn` | `str\|None` | Enterprise Significant Number |
1040
+ | `state` | `str\|None` | Number state (`"ACTIVE"`, `"INACTIVE"`) |
1041
+ | `phone_number_type` | `str\|None` | Number type |
1042
+ | `included_telephony_types` | `str\|None` | Telephony types included |
1043
+ | `main_number` | `bool\|None` | Whether this is a location main number |
1044
+ | `toll_free_number` | `bool\|None` | Whether toll-free |
1045
+ | `location` | `Location\|dict` (property) | Resolved Location or raw dict |
1046
+ | `owner` | `Person\|Workspace\|VirtualLine\|...` (property) | Resolved owner |
1047
+
1048
+ The `owner` property resolves across multiple types: `PEOPLE`, `PLACE` (Workspace), `VIRTUAL_LINE`, `CALL_QUEUE`, `HUNT_GROUP`, `AUTO_ATTENDANT`, `PAGING_GROUP`, `VOICEMAIL_GROUP`, and `VOICE_MESSAGING` (voice portal).
1049
+
1050
+ #### `Number.activate() -> bool`
1051
+
1052
+ Activate the phone number at its location.
1053
+
1054
+ ```python
1055
+ number = numbers.get(phone_number="+15551234567")
1056
+ number.activate()
1057
+ ```
1058
+
1059
+ ### NumberList Methods
1060
+
1061
+ #### `NumberList.get(...)`
1062
+
1063
+ Find numbers by various criteria.
1064
+
1065
+ ```python
1066
+ NumberList.get(
1067
+ phone_number: Optional[str] = None, # Partial match supported
1068
+ extension: Optional[str] = None,
1069
+ esn: Optional[str] = None,
1070
+ state: Optional[str] = None,
1071
+ location: Optional[Location] = None
1072
+ ) -> Optional[Number | list[Number]]
1073
+ ```
1074
+
1075
+ Return type depends on the search:
1076
+ - `phone_number` or `esn`: Returns a single `Number` or `None`
1077
+ - `extension` + `location`: Returns a single `Number` or `None`
1078
+ - `extension` alone: Returns a `Number` if one match, `list` if multiple
1079
+ - `state` or `location` alone: Returns a `list`
1080
+
1081
+ ```python
1082
+ num = numbers.get(phone_number="+15551234567")
1083
+ nums = numbers.get(state="ACTIVE")
1084
+ nums = numbers.get(location=hq_location)
1085
+ ```
1086
+
1087
+ #### `NumberList.get_by_owner(owner)`
1088
+
1089
+ Find a number by its owner (Person, Workspace, VirtualLine, etc.).
1090
+
1091
+ ```python
1092
+ num = numbers.get_by_owner(some_person)
1093
+ ```
1094
+
1095
+ #### `NumberList.add(...)`
1096
+
1097
+ Add numbers to a location. Only supported for Local Gateway or Non-integrated PSTN locations.
1098
+
1099
+ ```python
1100
+ NumberList.add(
1101
+ location: Location,
1102
+ numbers: list, # List of number strings
1103
+ number_type: Optional[str] = 'DID', # "DID" or "TOLLFREE"
1104
+ state: Optional[str] = 'ACTIVE' # "ACTIVE" or "INACTIVE"
1105
+ ) -> NumberList
1106
+ ```
1107
+
1108
+ ```python
1109
+ numbers.add(
1110
+ location=hq_location,
1111
+ numbers=["+15551110001", "+15551110002", "+15551110003"],
1112
+ number_type="DID",
1113
+ state="ACTIVE"
1114
+ )
1115
+ ```
1116
+
1117
+ Returns the refreshed `NumberList`.
1118
+
1119
+ #### `NumberList.validate(numbers: list) -> dict`
1120
+
1121
+ Validate phone numbers before adding. Returns the Webex validation response.
1122
+
1123
+ ```python
1124
+ result = numbers.validate(["+15551110001", "+15551110002"])
1125
+ ```
1126
+
1127
+ #### `NumberList.refresh()`
1128
+
1129
+ Re-fetch the number list from Webex.
1130
+
1131
+ ---
1132
+
1133
+ ## wxcadm vs wxc_sdk
1134
+
1135
+ Both libraries wrap the same Webex Calling APIs, but they differ in design philosophy and usage patterns.
1136
+
1137
+ ### Design Philosophy
1138
+
1139
+ | Aspect | wxcadm | wxc_sdk |
1140
+ |---|---|---|
1141
+ | Pattern | Object-oriented, stateful | Flat API wrapper, mostly stateless |
1142
+ | Data model | Rich classes with methods (e.g. `Device.apply_changes()`) | Pydantic models as data containers + separate API methods |
1143
+ | Lists | `UserList` subclasses with built-in filtering | API methods return plain lists of models |
1144
+ | Lazy loading | Properties fetch on first access (e.g. `Device.calling_id`, `VirtualLine.display_name`) | Explicit API calls required |
1145
+ | Authentication | `Org` object holds the API session | `WebexSimpleApi` holds the token |
1146
+
1147
+ ### Device Management Comparison
1148
+
1149
+ ```python
1150
+ # wxcadm — object-oriented
1151
+ device = workspace.devices.get(mac_address="AABBCCDDEEFF")
1152
+ device.settings = new_settings
1153
+ device.apply_changes()
1154
+
1155
+ # wxc_sdk — functional
1156
+ api = WebexSimpleApi(tokens=token)
1157
+ devices = api.devices.list(workspace_id=ws_id)
1158
+ device = next(d for d in devices if d.mac == "AABBCCDDEEFF")
1159
+ api.telephony.devices.update_settings(device_id=device.device_id, settings=new_settings)
1160
+ api.telephony.devices.apply_changes(device_id=device.device_id)
1161
+ ```
1162
+
1163
+ ### Workspace Comparison
1164
+
1165
+ ```python
1166
+ # wxcadm — create workspace with calling
1167
+ ws = org.workspaces.create(
1168
+ location=location, name="Lobby", extension="8100",
1169
+ license_type="workspace"
1170
+ )
1171
+ # License selection is automatic
1172
+
1173
+ # wxc_sdk — more manual steps
1174
+ ws = api.workspaces.create(settings=WorkspaceSettings(...))
1175
+ # License assignment often separate or embedded in settings
1176
+ ```
1177
+
1178
+ ### Key Differences for Device/Workspace Operations
1179
+
1180
+ | Operation | wxcadm | wxc_sdk |
1181
+ |---|---|---|
1182
+ | List supported devices | `org.devices.supported_devices` | `api.telephony.supported_devices()` |
1183
+ | Create device (activation code) | `workspace.devices.create(model=m)` | `api.devices.create_by_activation_code(...)` |
1184
+ | Get device members/lines | `device.members` (property) | `api.telephony.devices.members(device_id=...)` |
1185
+ | Add shared line | `device.members.add(person)` | `api.telephony.devices.update_members(device_id=..., members=[...])` |
1186
+ | Create DECT network | `dect_list.create(name=..., model=...)` | `api.telephony.dect_devices.create_dect_network(...)` |
1187
+ | Virtual line CRUD | `org.virtual_lines.create(...)` / `vl.update(...)` / `vl.delete()` | `api.telephony.virtual_lines.create(...)` / `.update(...)` / `.delete(...)` |
1188
+
1189
+ ### When to Use Which
1190
+
1191
+ - **wxcadm**: Better for scripts that navigate the object graph (org -> location -> workspace -> device -> members). The stateful design means fewer IDs to track.
1192
+ - **wxc_sdk**: Better for targeted API operations when you already have IDs, or when you need access to newer API endpoints that wxcadm may not yet wrap. Also has Pydantic validation on inputs.
1193
+
1194
+ ---
1195
+
1196
+ ## Common Patterns
1197
+
1198
+ ### Provision a Workspace with a Phone
1199
+
1200
+ ```python
1201
+ import wxcadm
1202
+
1203
+ org = wxcadm.Org(access_token="...")
1204
+ location = org.locations.get(name="Headquarters")
1205
+
1206
+ # 1. Create workspace
1207
+ ws = org.workspaces.create(
1208
+ location=location,
1209
+ name="Conference Room B",
1210
+ extension="8150",
1211
+ type="meetingRoom",
1212
+ license_type="workspace"
1213
+ )
1214
+
1215
+ # 2. Get the supported device model
1216
+ model = org.devices.supported_devices.get("Cisco 8845")
1217
+
1218
+ # 3. Create device with activation code
1219
+ result = ws.devices.create(model=model)
1220
+ print(f"Activation Code: {result['activation_code']}")
1221
+ ```
1222
+
1223
+ ### Add a Shared Line Appearance
1224
+
1225
+ ```python
1226
+ device = person.devices[0]
1227
+ target_workspace = org.workspaces.get(name="Lobby Phone")
1228
+
1229
+ device.members.add(
1230
+ target_workspace,
1231
+ line_type="shared",
1232
+ line_label="Lobby"
1233
+ )
1234
+ device.apply_changes()
1235
+ ```
1236
+
1237
+ ### Set Up a DECT Network
1238
+
1239
+ ```python
1240
+ location = org.locations.get(name="Warehouse")
1241
+ dect_list = DECTNetworkList(org, location=location)
1242
+
1243
+ # Create network
1244
+ network = dect_list.create(
1245
+ name="Warehouse DECT",
1246
+ model="DBS210",
1247
+ default_access_code="5678",
1248
+ location=location
1249
+ )
1250
+
1251
+ # Add base stations
1252
+ network.add_base_stations(["AABBCCDDEEFF", "112233445566"])
1253
+
1254
+ # Add a handset
1255
+ warehouse_ws = org.workspaces.get(name="Warehouse Floor")
1256
+ network.add_handset(
1257
+ display_name="Floor 1",
1258
+ line1=warehouse_ws
1259
+ )
1260
+ ```
1261
+
1262
+ ### Bulk Number Add and Virtual Line Creation
1263
+
1264
+ ```python
1265
+ location = org.locations.get(name="Branch Office")
1266
+
1267
+ # Add numbers to the location (Local Gateway/Non-integrated PSTN only)
1268
+ org.numbers.add(
1269
+ location=location,
1270
+ numbers=["+15559990001", "+15559990002"]
1271
+ )
1272
+
1273
+ # Create a virtual line with one of the new numbers
1274
+ vl = org.virtual_lines.create(
1275
+ first_name="Reception",
1276
+ last_name="Line",
1277
+ phone_number="+15559990001",
1278
+ extension="9001",
1279
+ location=location
1280
+ )
1281
+ vl.refresh_config()
1282
+
1283
+ # Assign to a device as a shared line
1284
+ device = org.devices.get(mac_address="FFEEDDCCBBAA")
1285
+ device.members.add(vl, line_type="shared", line_label="Reception")
1286
+ ```
1287
+
1288
+ ---
1289
+
1290
+ ## Gotchas
1291
+
1292
+ - **VirtualLine.get_call_recording() is broken.** The source passes `'get'` as the first positional argument to `self.org.api.get()`, where the first parameter is `endpoint`. The actual URL ends up as `params`. Use raw HTTP instead: `GET /v1/telephony/config/virtualLines/{id}/callRecording`.
1293
+ - **VirtualLine.delete() returns False on success.** Confirmed bug — the source explicitly `return False` after a successful API call, while other `delete()` methods return `True`.
1294
+ - **VirtualLine.create() returns a sparse object.** The returned instance only has `id` populated. Call `vl.refresh_config()` to populate all fields before accessing other attributes.
1295
+ - **DECTNetworkList has three access patterns.** Both `org.dect_networks` and `location.dect_networks` property accessors exist (lazy-loaded), and direct instantiation (`DECTNetworkList(org, location=location)`) also works.
1296
+ - **Workspace.unassign_wxc() only works for collaboration devices.** Phone-based workspaces (`supportedDevices="phones"`) cannot have WxC unassigned — they must be deleted and recreated.
1297
+ - **wxc_sdk DECT method name corrected.** The DECT network creation method is `api.telephony.dect_devices.create_dect_network(...)`, not `create_network()`. Virtual line methods (`api.telephony.virtual_lines.create/update/delete`) are correct as documented.
1298
+
1299
+ ---
1300
+
1301
+ ## See Also
1302
+
1303
+ - [devices-core.md](devices-core.md) — wxc_sdk device management APIs (listing, creation, configuration, members)
1304
+ - [devices-dect.md](devices-dect.md) — wxc_sdk DECT network management for comparison with wxcadm's `DECTNetworkList`
1305
+ - [devices-workspaces.md](devices-workspaces.md) — wxc_sdk workspace device provisioning and workspace call settings