@salesforce/afv-skills 1.46.0 → 1.48.0

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 (317) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-observe/SKILL.md +32 -4
  3. package/skills/agentforce-observe/references/ahm-alerts.md +719 -0
  4. package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +275 -0
  5. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/README.md +32 -0
  6. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls +75 -0
  7. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls-meta.xml +5 -0
  8. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/interview-answers.json +13 -0
  9. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/copy.json +10 -0
  10. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/create.json +20 -0
  11. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/update.json +16 -0
  12. package/skills/consumer-goods-promotion-bo-api-deploy/references/conventions-and-payload-rules.md +273 -0
  13. package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +236 -0
  14. package/skills/consumer-goods-promotion-bo-api-deploy/references/reference-example-set-comment-value.md +132 -0
  15. package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +211 -0
  16. package/skills/dx-devops-project-manage/SKILL.md +197 -0
  17. package/skills/dx-devops-project-manage/examples/common-workflows.md +197 -0
  18. package/skills/dx-devops-project-manage/references/cli-commands.md +295 -0
  19. package/skills/dx-devops-project-manage/scripts/create-project.sh +48 -0
  20. package/skills/dx-devops-project-manage/scripts/list-projects.sh +51 -0
  21. package/skills/dx-devops-project-manage/scripts/update-project.sh +96 -0
  22. package/skills/education-cloud-academic-calendar-generate/SKILL.md +225 -0
  23. package/skills/education-cloud-academic-calendar-generate/examples/quarter-calendar.json +47 -0
  24. package/skills/education-cloud-academic-calendar-generate/examples/sample-output.md +57 -0
  25. package/skills/education-cloud-academic-calendar-generate/examples/semester-calendar.json +54 -0
  26. package/skills/education-cloud-academic-calendar-generate/references/calendar-systems.md +127 -0
  27. package/skills/education-cloud-academic-calendar-generate/references/date-validation.md +222 -0
  28. package/skills/education-cloud-academic-calendar-generate/references/foundation_prerequisites.md +40 -0
  29. package/skills/education-cloud-academic-calendar-generate/scripts/validate_calendar_dates.py +143 -0
  30. package/skills/education-cloud-course-catalog-migrate/SKILL.md +321 -0
  31. package/skills/education-cloud-course-catalog-migrate/references/gotchas-detail.md +16 -0
  32. package/skills/education-cloud-course-catalog-migrate/references/gotchas.md +16 -0
  33. package/skills/education-cloud-course-catalog-migrate/references/large-catalog-handling.md +42 -0
  34. package/skills/education-cloud-course-catalog-migrate/scripts/batch_courses.py +36 -0
  35. package/skills/education-cloud-course-catalog-migrate/scripts/detect_linked_courses.py +51 -0
  36. package/skills/education-cloud-course-catalog-migrate/scripts/detect_modality_variants.py +48 -0
  37. package/skills/education-cloud-course-catalog-migrate/scripts/resolve_api_version.py +43 -0
  38. package/skills/education-cloud-course-catalog-migrate/scripts/split_course_code.py +39 -0
  39. package/skills/education-cloud-course-catalog-migrate/scripts/validate_completeness.py +54 -0
  40. package/skills/education-cloud-domain-configure/SKILL.md +234 -0
  41. package/skills/education-cloud-domain-configure/references/dependent-features.md +198 -0
  42. package/skills/education-cloud-domain-configure/references/domain-features/academic-operations.md +386 -0
  43. package/skills/education-cloud-domain-configure/references/domain-features/alumni-relations.md +155 -0
  44. package/skills/education-cloud-domain-configure/references/domain-features/cross-domain-dependencies.md +18 -0
  45. package/skills/education-cloud-domain-configure/references/domain-features/industriessettings-toggle-reference.md +43 -0
  46. package/skills/education-cloud-domain-configure/references/domain-features/mentoring.md +198 -0
  47. package/skills/education-cloud-domain-configure/references/domain-features/recruitment-admissions.md +315 -0
  48. package/skills/education-cloud-domain-configure/references/domain-features/student-management.md +155 -0
  49. package/skills/education-cloud-domain-configure/references/domain-features/student-success.md +249 -0
  50. package/skills/education-cloud-domain-configure/references/domain-features.md +27 -0
  51. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +23 -0
  52. package/skills/education-cloud-domain-configure/references/toggle-write-path.md +67 -0
  53. package/skills/education-cloud-domain-configure/scripts/check_license_provisioned.py +62 -0
  54. package/skills/education-cloud-domain-configure/scripts/check_org_edition.py +44 -0
  55. package/skills/education-cloud-domain-configure/scripts/check_permission_assigned.py +39 -0
  56. package/skills/education-cloud-multi-campus-configure/SKILL.md +7 -0
  57. package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +1 -2
  58. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +177 -0
  59. package/skills/education-cloud-student-recruitment-agent-configure/references/agent-and-subagents.md +151 -0
  60. package/skills/education-cloud-student-recruitment-agent-configure/references/customer-narration.md +34 -0
  61. package/skills/education-cloud-student-recruitment-agent-configure/references/execution-model.md +54 -0
  62. package/skills/education-cloud-student-recruitment-agent-configure/references/flows.md +82 -0
  63. package/skills/education-cloud-student-recruitment-agent-configure/references/grounding.md +199 -0
  64. package/skills/education-cloud-student-recruitment-agent-configure/references/permissions.md +183 -0
  65. package/skills/education-cloud-student-recruitment-agent-configure/references/platform-enablement.md +82 -0
  66. package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +158 -0
  67. package/skills/education-cloud-student-recruitment-agent-configure/references/routing.md +141 -0
  68. package/skills/experience-cms-brand-apply/SKILL.md +5 -5
  69. package/skills/experience-cms-brand-create/SKILL.md +2 -2
  70. package/skills/experience-cms-content-generate/SKILL.md +1 -0
  71. package/skills/experience-cms-content-render/SKILL.md +173 -0
  72. package/skills/experience-cms-content-render/assets/angular/DetailPage.component.ts +25 -0
  73. package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +133 -0
  74. package/skills/experience-cms-content-render/assets/angular/TypeList.component.ts +38 -0
  75. package/skills/experience-cms-content-render/assets/angular/TypeRenderer.component.ts +90 -0
  76. package/skills/experience-cms-content-render/assets/angular/cms-content.component.ts +248 -0
  77. package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +100 -0
  78. package/skills/experience-cms-content-render/assets/react/DetailPage.tsx +20 -0
  79. package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +129 -0
  80. package/skills/experience-cms-content-render/assets/react/TypeList.tsx +40 -0
  81. package/skills/experience-cms-content-render/assets/react/TypeRenderer.tsx +64 -0
  82. package/skills/experience-cms-content-render/assets/react/heuristicRenderer.tsx +310 -0
  83. package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +129 -0
  84. package/skills/experience-cms-content-render/assets/shared/cmsContentType.ts +49 -0
  85. package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +96 -0
  86. package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +55 -0
  87. package/skills/experience-cms-content-render/references/bulk-loading.md +60 -0
  88. package/skills/experience-cms-content-render/references/codegen-guardrails.md +111 -0
  89. package/skills/experience-cms-content-render/references/detail-pages.md +87 -0
  90. package/skills/experience-cms-content-render/references/embed-recipes.md +127 -0
  91. package/skills/experience-cms-content-render/references/failure-modes.md +96 -0
  92. package/skills/experience-cms-content-render/references/heuristic-render-rules.md +131 -0
  93. package/skills/experience-cms-content-render/references/init-scaffold.md +122 -0
  94. package/skills/experience-cms-content-render/references/interaction-model.md +173 -0
  95. package/skills/experience-cms-content-render/references/package-api.md +106 -0
  96. package/skills/experience-cms-content-render/references/schema-sync.md +114 -0
  97. package/skills/experience-cms-content-render/references/styling-scopes.md +65 -0
  98. package/skills/experience-cms-content-render/references/verify.md +49 -0
  99. package/skills/experience-cms-content-type-generate/SKILL.md +2 -2
  100. package/skills/experience-content-media-stock-image-search/SKILL.md +5 -4
  101. package/skills/experience-search-coordinate/SKILL.md +198 -0
  102. package/skills/experience-search-coordinate/assets/search-payload-template.json +25 -0
  103. package/skills/experience-search-coordinate/references/content-route.md +313 -0
  104. package/skills/experience-search-coordinate/references/content-type-discovery.md +57 -0
  105. package/skills/experience-search-coordinate/references/media-route.md +172 -0
  106. package/skills/experience-search-coordinate/references/scope-resolution.md +14 -0
  107. package/skills/experience-ui-bundle-localize/SKILL.md +1 -1
  108. package/skills/experience-ui-bundle-localize/references/i18n-setup.md +5 -3
  109. package/skills/experience-ui-bundle-project-generate/SKILL.md +18 -14
  110. package/skills/experience-ui-bundle-project-generate/references/angular-project-generate.md +22 -0
  111. package/skills/experience-ui-bundle-project-generate/references/react-project-generate.md +20 -0
  112. package/skills/experience-ui-bundle-salesforce-data-access/SKILL.md +58 -54
  113. package/skills/experience-ui-bundle-salesforce-data-access/references/caching.md +6 -0
  114. package/skills/experience-ui-bundle-salesforce-data-access/references/graphiti-cli.md +2 -2
  115. package/skills/experience-ui-bundle-salesforce-data-access/references/migration.md +6 -0
  116. package/skills/experience-ui-bundle-salesforce-data-access/references/rest-and-integration.md +2 -1
  117. package/skills/experience-ui-bundle-salesforce-data-access/references/sdk-api.md +6 -0
  118. package/skills/experience-ui-bundle-site-generate/SKILL.md +59 -8
  119. package/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md +8 -3
  120. package/skills/experience-ui-bundle-site-generate/references/configure-metadata-language-settings.md +120 -0
  121. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +336 -0
  122. package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +143 -0
  123. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +127 -0
  124. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-deploy-commands.md +116 -0
  125. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +111 -0
  126. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-overview.md +312 -0
  127. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +171 -0
  128. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-state-tracking.md +64 -0
  129. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-trigger-handlers.md +122 -0
  130. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-overview.md +335 -0
  131. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-user-provisioning-details.md +140 -0
  132. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +196 -0
  133. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-metadata-cache-generation.md +155 -0
  134. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +307 -0
  135. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +211 -0
  136. package/skills/life-sciences-fieldsalesrep-coordinate/references/state-machine-and-changes.md +108 -0
  137. package/skills/life-sciences-kam-coordinate/SKILL.md +241 -0
  138. package/skills/life-sciences-kam-coordinate/references/orchestration-flow.md +152 -0
  139. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +79 -0
  140. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-deploy-commands.md +131 -0
  141. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-kam-config-records.md +85 -0
  142. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +112 -0
  143. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-overview.md +202 -0
  144. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +67 -0
  145. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-state-tracking.md +65 -0
  146. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-trigger-handlers.md +123 -0
  147. package/skills/life-sciences-kam-coordinate/references/stage-4-participant-role-and-sprint.md +89 -0
  148. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +337 -0
  149. package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +248 -0
  150. package/skills/life-sciences-kam-coordinate/references/stage-6-ipad-validation-script.md +35 -0
  151. package/skills/life-sciences-kam-coordinate/references/stage-6-metadata-cache-generation.md +155 -0
  152. package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-details.md +146 -0
  153. package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-overview.md +89 -0
  154. package/skills/life-sciences-kam-coordinate/references/state-machine-and-changes.md +114 -0
  155. package/skills/life-sciences-prerequisites-validate/SKILL.md +138 -0
  156. package/skills/life-sciences-prerequisites-validate/references/checks-org-settings.md +190 -0
  157. package/skills/life-sciences-prerequisites-validate/references/checks-user-and-package.md +211 -0
  158. package/skills/life-sciences-territory-configure/SKILL.md +217 -0
  159. package/skills/life-sciences-territory-configure/references/territory-metadata.md +262 -0
  160. package/skills/platform-dsar-policy-manage/SKILL.md +272 -0
  161. package/skills/platform-dsar-policy-manage/references/configure.md +106 -0
  162. package/skills/platform-dsar-policy-manage/references/export-and-history.md +123 -0
  163. package/skills/platform-dsar-policy-manage/references/gap-analysis-guide.md +150 -0
  164. package/skills/platform-dsar-policy-manage/references/gap-scan.md +129 -0
  165. package/skills/platform-dsar-policy-manage/references/headless-sor.md +59 -0
  166. package/skills/platform-dsar-policy-manage/references/report-format.md +59 -0
  167. package/skills/platform-dsar-policy-manage/scripts/tests/__init__.py +0 -0
  168. package/skills/platform-dsar-policy-manage/scripts/tests/test_validate_policy_tree.py +76 -0
  169. package/skills/platform-dsar-policy-manage/scripts/validate-policy-tree.py +130 -0
  170. package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +359 -0
  171. package/skills/platform-salesforce-connect-adapter-generate/references/official-examples.md +69 -0
  172. package/skills/platform-salesforce-connect-adapter-generate/references/scenarios.md +187 -0
  173. package/skills/service-agentforce-channel-configure/SKILL.md +33 -28
  174. package/skills/service-agentforce-human-escalation-configure/SKILL.md +205 -0
  175. package/skills/service-agentforce-human-escalation-configure/assets/escalation-thresholds.instructions.md +37 -0
  176. package/skills/service-agentforce-human-escalation-configure/assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml +173 -0
  177. package/skills/service-agentforce-human-escalation-configure/assets/package.xml +8 -0
  178. package/skills/service-agentforce-human-escalation-configure/assets/sfdx-project.json +5 -0
  179. package/skills/service-agentforce-human-escalation-configure/references/classifier-contracts.md +103 -0
  180. package/skills/service-agentforce-human-escalation-configure/references/escalation-mechanism.md +63 -0
  181. package/skills/service-agentforce-human-escalation-configure/references/failure-policy.md +44 -0
  182. package/skills/service-agentforce-human-escalation-configure/references/nga-escalation.md +57 -0
  183. package/skills/service-agentforce-human-escalation-configure/references/runtime-verification.md +72 -0
  184. package/skills/service-agentforce-human-escalation-configure/scripts/classify-agent-active.mjs +49 -0
  185. package/skills/service-agentforce-human-escalation-configure/scripts/classify-nga-escalation.mjs +166 -0
  186. package/skills/service-agentforce-human-escalation-configure/scripts/extract-outbound-route.mjs +67 -0
  187. package/skills/service-agentforce-human-escalation-configure/scripts/patch-escalation-surfaces.mjs +131 -0
  188. package/skills/service-agentforce-human-escalation-configure/scripts/tests/__init__.py +0 -0
  189. package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +249 -0
  190. package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +899 -0
  191. package/skills/service-agentforce-human-escalation-configure/scripts/verify-and-configure.sh +705 -0
  192. package/skills/service-agentforce-human-escalation-configure/scripts/verify-escalation-config.mjs +89 -0
  193. package/skills/service-de-channel-routing-configure/SKILL.md +13 -5
  194. package/skills/service-de-channel-routing-configure/scripts/tests/test_skill_contract.py +25 -0
  195. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +23 -10
  196. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -48
  197. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +6 -2
  198. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +20 -27
  199. package/skills/service-native-voice-recording-transcription-configure/SKILL.md +47 -27
  200. package/skills/service-native-voice-recording-transcription-configure/references/thunderbird-voice-settings.md +13 -9
  201. package/skills/service-native-voice-recording-transcription-configure/scripts/enable-recording-transcription.sh +104 -45
  202. package/skills/service-omni-agent-users-create/SKILL.md +96 -0
  203. package/skills/service-omni-agent-users-create/assets/create-users.apex.template +80 -0
  204. package/skills/service-omni-agent-users-create/references/apex-patterns.md +135 -0
  205. package/skills/service-omni-agent-users-create/references/apex-template-notes.md +40 -0
  206. package/skills/service-omni-agent-users-create/scripts/detect-and-create.sh +206 -0
  207. package/skills/service-omni-agent-users-create/scripts/detect-existing.sh +122 -0
  208. package/skills/service-omni-agent-users-create/scripts/run-create.sh +298 -0
  209. package/skills/service-omni-agent-users-create/scripts/tests/test_user_create_security.py +171 -0
  210. package/skills/service-omni-agent-work-sharing-configure/SKILL.md +78 -0
  211. package/skills/service-omni-agent-work-sharing-configure/references/api-notes.md +31 -0
  212. package/skills/service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh +186 -0
  213. package/skills/service-omni-agent-work-sharing-configure/scripts/tests/_bootstrap.py +120 -0
  214. package/skills/service-omni-agent-work-sharing-configure/scripts/tests/test_agent_work_sharing_contracts.py +93 -0
  215. package/skills/service-omni-base-settings-configure/SKILL.md +77 -0
  216. package/skills/service-omni-base-settings-configure/assets/force-app/main/default/settings/OmniChannel.settings-meta.xml +8 -0
  217. package/skills/service-omni-base-settings-configure/assets/package.xml +8 -0
  218. package/skills/service-omni-base-settings-configure/assets/sfdx-project.json +12 -0
  219. package/skills/service-omni-base-settings-configure/references/api-notes.md +121 -0
  220. package/skills/service-omni-base-settings-configure/scripts/configure-and-report.sh +164 -0
  221. package/skills/service-omni-base-settings-configure/scripts/detect-existing.sh +69 -0
  222. package/skills/service-omni-channel-setup-coordinate/SKILL.md +168 -0
  223. package/skills/service-omni-channel-setup-coordinate/examples/output-templates.md +161 -0
  224. package/skills/service-omni-channel-setup-coordinate/references/gap-catalog.md +99 -0
  225. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +1542 -0
  226. package/skills/service-omni-channel-setup-coordinate/scripts/tests/__init__.py +0 -0
  227. package/skills/service-omni-channel-setup-coordinate/scripts/tests/_bootstrap.py +223 -0
  228. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +804 -0
  229. package/skills/service-omni-command-center-analyze/SKILL.md +84 -0
  230. package/skills/service-omni-command-center-analyze/references/api-notes.md +54 -0
  231. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +198 -0
  232. package/skills/service-omni-command-center-analyze/scripts/tests/_bootstrap.py +115 -0
  233. package/skills/service-omni-command-center-analyze/scripts/tests/test_command_center_analyze_contracts.py +165 -0
  234. package/skills/service-omni-permission-set-assign/SKILL.md +88 -0
  235. package/skills/service-omni-permission-set-assign/assets/package.xml +8 -0
  236. package/skills/service-omni-permission-set-assign/references/api-notes.md +143 -0
  237. package/skills/service-omni-permission-set-assign/scripts/verify-and-assign.sh +397 -0
  238. package/skills/service-omni-presence-status-deploy/SKILL.md +87 -0
  239. package/skills/service-omni-presence-status-deploy/assets/package.xml +12 -0
  240. package/skills/service-omni-presence-status-deploy/references/api-notes.md +115 -0
  241. package/skills/service-omni-presence-status-deploy/scripts/deploy-and-report.sh +422 -0
  242. package/skills/service-omni-presence-user-config-deploy/SKILL.md +80 -0
  243. package/skills/service-omni-presence-user-config-deploy/references/api-notes.md +60 -0
  244. package/skills/service-omni-presence-user-config-deploy/scripts/deploy-and-report.sh +261 -0
  245. package/skills/service-omni-presence-user-config-deploy/scripts/tests/_bootstrap.py +106 -0
  246. package/skills/service-omni-presence-user-config-deploy/scripts/tests/test_presence_user_config_contracts.py +142 -0
  247. package/skills/service-omni-queue-deploy/SKILL.md +91 -0
  248. package/skills/service-omni-queue-deploy/references/api-notes.md +140 -0
  249. package/skills/service-omni-queue-deploy/scripts/verify-and-align.sh +470 -0
  250. package/skills/service-omni-queue-members-assign/SKILL.md +93 -0
  251. package/skills/service-omni-queue-members-assign/references/api-notes.md +144 -0
  252. package/skills/service-omni-queue-members-assign/scripts/tests/_bootstrap.py +67 -0
  253. package/skills/service-omni-queue-members-assign/scripts/tests/test_queue_members_contracts.py +73 -0
  254. package/skills/service-omni-queue-members-assign/scripts/verify-and-bind.sh +362 -0
  255. package/skills/service-omni-queue-routing-config-deploy/SKILL.md +101 -0
  256. package/skills/service-omni-queue-routing-config-deploy/references/api-notes.md +127 -0
  257. package/skills/service-omni-queue-routing-config-deploy/scripts/tests/_bootstrap.py +90 -0
  258. package/skills/service-omni-queue-routing-config-deploy/scripts/tests/test_queue_routing_config_contracts.py +70 -0
  259. package/skills/service-omni-queue-routing-config-deploy/scripts/upsert-and-report.sh +538 -0
  260. package/skills/service-omni-routing-flow-deploy/SKILL.md +111 -0
  261. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.SkillsBased.flow-meta.xml +94 -0
  262. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.flow-meta.xml +88 -0
  263. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Cases.flow-meta.xml +144 -0
  264. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.SkillsBased.flow-meta.xml +94 -0
  265. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.flow-meta.xml +88 -0
  266. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCalls.flow-meta.xml +144 -0
  267. package/skills/service-omni-routing-flow-deploy/assets/package.xml +11 -0
  268. package/skills/service-omni-routing-flow-deploy/assets/sfdx-project.json +11 -0
  269. package/skills/service-omni-routing-flow-deploy/references/api-notes.md +265 -0
  270. package/skills/service-omni-routing-flow-deploy/scripts/deploy-and-report.sh +684 -0
  271. package/skills/service-omni-service-channel-configure/SKILL.md +83 -0
  272. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Cases.serviceChannel-meta.xml +9 -0
  273. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Incidents.serviceChannel-meta.xml +9 -0
  274. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/MessagingSessions.serviceChannel-meta.xml +9 -0
  275. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/VoiceCalls.serviceChannel-meta.xml +9 -0
  276. package/skills/service-omni-service-channel-configure/assets/package.xml +11 -0
  277. package/skills/service-omni-service-channel-configure/assets/sfdx-project.json +12 -0
  278. package/skills/service-omni-service-channel-configure/references/api-notes.md +84 -0
  279. package/skills/service-omni-service-channel-configure/scripts/deploy-and-report.sh +383 -0
  280. package/skills/service-omni-sidebar-configure/SKILL.md +77 -0
  281. package/skills/service-omni-sidebar-configure/references/api-notes.md +51 -0
  282. package/skills/service-omni-sidebar-configure/scripts/enable-and-report.sh +315 -0
  283. package/skills/service-omni-sidebar-configure/scripts/tests/_bootstrap.py +142 -0
  284. package/skills/service-omni-sidebar-configure/scripts/tests/test_sidebar_contracts.py +139 -0
  285. package/skills/service-omni-skills-based-routing-configure/SKILL.md +81 -0
  286. package/skills/service-omni-skills-based-routing-configure/references/api-notes.md +50 -0
  287. package/skills/service-omni-skills-based-routing-configure/scripts/configure-and-report.sh +270 -0
  288. package/skills/service-omni-skills-based-routing-configure/scripts/tests/_bootstrap.py +120 -0
  289. package/skills/service-omni-skills-based-routing-configure/scripts/tests/test_sbr_contracts.py +129 -0
  290. package/skills/service-omni-supervisor-config-deploy/SKILL.md +97 -0
  291. package/skills/service-omni-supervisor-config-deploy/assets/force-app/main/default/omniSupervisorConfigs/Omni_Supervisor.omniSupervisorConfig-meta.xml +9 -0
  292. package/skills/service-omni-supervisor-config-deploy/assets/package.xml +8 -0
  293. package/skills/service-omni-supervisor-config-deploy/assets/sfdx-project.json +12 -0
  294. package/skills/service-omni-supervisor-config-deploy/references/api-notes.md +205 -0
  295. package/skills/service-omni-supervisor-config-deploy/scripts/deploy-and-report.sh +586 -0
  296. package/skills/service-omni-supervisor-config-deploy/scripts/tests/_bootstrap.py +153 -0
  297. package/skills/service-omni-supervisor-config-deploy/scripts/tests/test_supervisor_config_contracts.py +116 -0
  298. package/skills/service-omni-supervisor-permset-assign/SKILL.md +88 -0
  299. package/skills/service-omni-supervisor-permset-assign/references/api-notes.md +48 -0
  300. package/skills/service-omni-supervisor-permset-assign/scripts/verify-and-assign.sh +316 -0
  301. package/skills/service-omni-supervisor-surface-deploy/SKILL.md +85 -0
  302. package/skills/service-omni-supervisor-surface-deploy/references/api-notes.md +47 -0
  303. package/skills/service-omni-supervisor-surface-deploy/scripts/deploy-and-report.sh +216 -0
  304. package/skills/service-omni-supervisor-surface-deploy/scripts/tests/_bootstrap.py +95 -0
  305. package/skills/service-omni-supervisor-surface-deploy/scripts/tests/test_supervisor_surface_contracts.py +154 -0
  306. package/skills/service-omni-supervisor-users-create/SKILL.md +92 -0
  307. package/skills/service-omni-supervisor-users-create/assets/create-supervisors.apex.template +72 -0
  308. package/skills/service-omni-supervisor-users-create/references/apex-patterns.md +73 -0
  309. package/skills/service-omni-supervisor-users-create/references/apex-template-notes.md +41 -0
  310. package/skills/service-omni-supervisor-users-create/scripts/detect-and-create.sh +197 -0
  311. package/skills/service-omni-supervisor-users-create/scripts/detect-existing.sh +112 -0
  312. package/skills/service-omni-supervisor-users-create/scripts/run-create.sh +296 -0
  313. package/skills/service-omni-work-skill-routing-configure/SKILL.md +80 -0
  314. package/skills/service-omni-work-skill-routing-configure/references/api-notes.md +67 -0
  315. package/skills/service-omni-work-skill-routing-configure/scripts/configure-and-report.sh +262 -0
  316. package/skills/service-omni-work-skill-routing-configure/scripts/tests/_bootstrap.py +121 -0
  317. package/skills/service-omni-work-skill-routing-configure/scripts/tests/test_work_skill_routing_contracts.py +161 -0
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env python3
2
+ """Contract tests for service-omni-sidebar-configure/scripts/enable-and-report.sh.
3
+
4
+ Run standalone: ``python3 test_sidebar_contracts.py`` (or under pytest / unittest discovery).
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import sys
10
+ import unittest
11
+ from pathlib import Path
12
+
13
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
14
+ from _bootstrap import ( # noqa: E402
15
+ FakeSf,
16
+ _FAKE_SF_PROD,
17
+ _FAKE_SF_SANDBOX,
18
+ run,
19
+ )
20
+
21
+
22
+ def _last_json(out: str) -> dict:
23
+ """Parse the last JSON object printed to stdout+stderr."""
24
+ dec = json.JSONDecoder()
25
+ obj, i, n = None, 0, len(out)
26
+ while i < n:
27
+ ch = out[i]
28
+ if ch in "{[":
29
+ try:
30
+ cand, end = dec.raw_decode(out, i)
31
+ if isinstance(cand, dict):
32
+ obj = cand
33
+ i = end
34
+ continue
35
+ except json.JSONDecodeError:
36
+ pass
37
+ i += 1
38
+ assert obj is not None, f"no JSON object found in output:\n{out}"
39
+ return obj
40
+
41
+
42
+ class InputContractTests(unittest.TestCase):
43
+ """Argument guarantees must hold before any `sf` call (PATH has no sf)."""
44
+
45
+ def test_missing_org_rejected(self):
46
+ rc, out = run([], scrub_sf=True)
47
+ self.assertEqual(rc, 1, out)
48
+ self.assertIn("Usage", out)
49
+
50
+ def test_injection_shaped_app_name_rejected(self):
51
+ rc, out = run(["myorg", "Bad Name; DROP"], scrub_sf=True)
52
+ self.assertEqual(rc, 1, out)
53
+ self.assertIn("Invalid app_developer_name", out)
54
+
55
+
56
+ class ProductionGuardTests(unittest.TestCase):
57
+ def test_refuses_production_org(self):
58
+ with FakeSf(_FAKE_SF_PROD) as p:
59
+ rc, out = run(["prodorg", "Demo_Console"], path_prefix=p)
60
+ self.assertEqual(rc, 1, out)
61
+ v = _last_json(out)
62
+ self.assertEqual(v["status"], "blocked", out)
63
+ self.assertIn("production", v["blocking_issue"].lower())
64
+
65
+
66
+ class AutoDetectTests(unittest.TestCase):
67
+ def test_blocks_when_no_console_app(self):
68
+ with FakeSf(_FAKE_SF_SANDBOX) as p:
69
+ rc, out = run(["sandbox"], path_prefix=p, extra_env={"FAKE_CONSOLE_APPS": "0"})
70
+ self.assertEqual(rc, 1, out)
71
+ v = _last_json(out)
72
+ self.assertEqual(v["status"], "blocked", out)
73
+ self.assertIn("No deployable custom Lightning console app", v["blocking_issue"])
74
+
75
+ def test_blocks_when_multiple_console_apps(self):
76
+ with FakeSf(_FAKE_SF_SANDBOX) as p:
77
+ rc, out = run(["sandbox"], path_prefix=p, extra_env={"FAKE_CONSOLE_APPS": "2"})
78
+ self.assertEqual(rc, 1, out)
79
+ v = _last_json(out)
80
+ self.assertEqual(v["status"], "blocked", out)
81
+ self.assertIn("explicit app_developer_name", v["blocking_issue"])
82
+
83
+ def test_adopts_single_console_app(self):
84
+ with FakeSf(_FAKE_SF_SANDBOX) as p:
85
+ rc, out = run(["sandbox"], path_prefix=p, extra_env={"FAKE_CONSOLE_APPS": "1"})
86
+ self.assertEqual(rc, 0, out)
87
+ v = _last_json(out)
88
+ self.assertEqual(v["app_developer_name"], "Demo_Console", out)
89
+
90
+ def test_autodetect_uses_deployable_custom_application_metadata(self):
91
+ src = (Path(__file__).resolve().parents[1] / "enable-and-report.sh").read_text()
92
+ start = src.index("APP_METADATA_JSON=$(sf org list metadata")
93
+ block = src[start:src.index("APP_COUNT=", start)]
94
+ self.assertIn("--metadata-type CustomApplication", block)
95
+ self.assertIn('startswith("standard__") | not', block)
96
+ self.assertIn("<navType>Console</navType>", block)
97
+
98
+
99
+ class EnableAndReuseTests(unittest.TestCase):
100
+ def test_enables_when_absent_and_verifies_roundtrip(self):
101
+ with FakeSf(_FAKE_SF_SANDBOX) as p:
102
+ rc, out = run(["sandbox", "Demo_Console"], path_prefix=p,
103
+ extra_env={"FAKE_APP_PINNED": "0"})
104
+ self.assertEqual(rc, 0, out)
105
+ v = _last_json(out)
106
+ self.assertEqual(v["status"], "enabled", out)
107
+ self.assertFalse(v["before"], out)
108
+ self.assertTrue(v["after"], out)
109
+ self.assertIsNotNone(v["deploy_id"], out)
110
+
111
+ def test_reused_when_already_pinned_without_deploy(self):
112
+ with FakeSf(_FAKE_SF_SANDBOX) as p:
113
+ calllog = str(Path(p) / "calls.log")
114
+ rc, out = run(["sandbox", "Demo_Console"], path_prefix=p,
115
+ extra_env={"FAKE_APP_PINNED": "1", "FAKE_SF_CALLLOG": calllog})
116
+ log = Path(calllog).read_text() if Path(calllog).exists() else ""
117
+ self.assertEqual(rc, 0, out)
118
+ v = _last_json(out)
119
+ self.assertEqual(v["status"], "reused", out)
120
+ self.assertTrue(v["before"], out)
121
+ self.assertTrue(v["after"], out)
122
+ self.assertNotIn("project deploy", log, "reused path must not deploy")
123
+
124
+ def test_plan_only_reports_action_without_writing(self):
125
+ with FakeSf(_FAKE_SF_SANDBOX) as p:
126
+ calllog = str(Path(p) / "calls.log")
127
+ rc, out = run(["sandbox", "Demo_Console"], path_prefix=p,
128
+ extra_env={"FAKE_APP_PINNED": "0", "PLAN_ONLY": "1",
129
+ "FAKE_SF_CALLLOG": calllog})
130
+ log = Path(calllog).read_text() if Path(calllog).exists() else ""
131
+ self.assertEqual(rc, 0, out)
132
+ v = _last_json(out)
133
+ self.assertTrue(v.get("plan_mode"), out)
134
+ self.assertEqual(v["status"], "action_needed", out)
135
+ self.assertNotIn("project deploy", log, "plan mode must not deploy")
136
+
137
+
138
+ if __name__ == "__main__":
139
+ unittest.main(verbosity=2)
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: service-omni-skills-based-routing-configure
3
+ description: "Use to make agents routable by Omni skills-based routing: ensure an Omni Skill exists, then assign it to each active agent through the classic Omni-Channel SkillUser junction. Idempotent — existing skills and user bindings are reused, and only missing bindings are created. Works on Service Cloud orgs without requiring Field Service. Triggers: set up skills-based routing agents, assign Omni skills to agents, make agents skill-routable, create an Omni skill and bind users, repair missing agent skill assignments. Do not use to author WorkSkillRouting rules, create agent users, or configure queues."
4
+ allowed-tools: Bash Read Write Edit Glob Grep
5
+ metadata:
6
+ version: "1.0"
7
+ domains: ["Service"]
8
+ minApiVersion: "66.0"
9
+ relatedSkills:
10
+ - "service-omni-agent-users-create"
11
+ - "service-omni-base-settings-configure"
12
+ accessCheck:
13
+ - type: license
14
+ value: ServiceCloud
15
+ cliTools:
16
+ - tool: ["jq"]
17
+ semver: ">=1.6"
18
+ - tool: ["python3"]
19
+ semver: ">=3.8"
20
+ - tool: ["sf"]
21
+ semver: ">=2.139.6"
22
+ ---
23
+
24
+ # service-omni-skills-based-routing-configure
25
+
26
+ Make agents routable by Omni **skills-based routing (SBR)**. A routable agent needs the **Skill** and a **SkillUser** junction assigning that skill to the agent's user. `SkillUser` is the classic Omni-Channel model written by Setup > Skills > Assign Users and evaluated when work has skill requirements. This skill ensures both, idempotently, for one skill across a set of active agent users.
27
+
28
+ `ServiceResource` and `ServiceResourceSkill` are the Field Service skill model. On a plain Service Cloud org, `ServiceResource.IsActive` is not writable or queryable, so a newly created resource remains inactive and the junction is rejected. This skill therefore uses the supported, license-appropriate `SkillUser` path.
29
+
30
+ ## Inputs
31
+
32
+ ```bash
33
+ bash scripts/configure-and-report.sh <org-alias> <skill_developer_name> [agent_members_csv] [skill_master_label]
34
+ ```
35
+
36
+ - `org-alias` (required).
37
+ - `skill_developer_name` (required). The Omni `Skill` DeveloperName to ensure and bind (e.g. `Omni_Demo_Voice`).
38
+ - `agent_members_csv` (optional). Comma-separated agents to make skill-routable — each token a Username (`…@…`) or a 15/18-char User Id (`005…`). May also be supplied via `AGENT_MEMBERS_CSV`. When omitted, the skill blocks (it will not guess which agents to skill).
39
+ - `skill_master_label` (optional). MasterLabel used only when the skill must be created; defaults to a title-cased form of the DeveloperName.
40
+
41
+ ## Preconditions and safety
42
+
43
+ - Target org authenticated via `sf` CLI, Service Cloud license, `sf` CLI ≥ 2.139.6.
44
+ - Omni-Channel base settings enabled with **skills-based routing** turned on (`service-omni-base-settings-configure`).
45
+ - The named agents exist and are active (`service-omni-agent-users-create`).
46
+ - The three-way `safe_to_write` production guard applies.
47
+
48
+ ## Run
49
+
50
+ `configure-and-report.sh` proceeds in two idempotent phases, each detect-before-write:
51
+
52
+ 1. **Skill** — query `Skill` by DeveloperName. Present → reused; absent → deploy a `Skill` component via the Metadata API and re-query for its Id.
53
+ 2. **SkillUser** — query existing `SkillUser` records for the Skill and resolved users, then create only missing (`SkillId`, `UserId`) bindings via REST DML. A concurrent `DUPLICATE_VALUE` is reconciled as reused.
54
+
55
+ ## Behavior
56
+
57
+ **Idempotent + non-destructive.** Existing skills and bindings are reused; the skill never deletes or reassigns anything it did not create, so re-runs converge without duplication. Only **active** users are resolved — an inactive user cannot be routed work, so it is reported rather than silently skilled.
58
+
59
+ **Partial safety.** If some bindings succeed and others fail, the status is `partial` with per-agent errors in `blocking_issue`; the successful bindings are left in place (each junction is an independent write).
60
+
61
+ ## Output contract
62
+
63
+ A single JSON object: `status` ∈ `configured` | `reused` | `partial` | `blocked`, `skill` (`{developer_name, id, state}`), `agents` (array of `{username, user_id, skill_binding_id, skill_binding_state}`), `created_counts`, `reused_counts`, `manual_actions`, `blocking_issue`.
64
+
65
+ - `reused` — skill and all bindings already existed.
66
+ - `configured` — at least one component created and all requested bindings now exist.
67
+ - `partial` / `blocked` — see `blocking_issue`; `manual_actions` names the prerequisite skill when the cause is a missing precondition.
68
+
69
+ ## Limitations
70
+
71
+ - One skill per invocation; run again per skill an agent set needs.
72
+ - Does not author `WorkSkillRouting` rules or field-based skill criteria — that is a separate skill.
73
+ - Does not create agent users, queues, or presence configuration.
74
+ - `SkillUser.SkillLevel` is left at the platform default; forking the DML body is required to set explicit proficiency.
75
+
76
+ ## References
77
+
78
+ | File | When to read |
79
+ |---|---|
80
+ | `references/api-notes.md` | Skill metadata vs Skill sObject, the SkillUser agent-binding model, and SBR base-setting prerequisites |
81
+ | `scripts/tests/test_sbr_contracts.py` | When validating changes — run `python3 scripts/tests/test_sbr_contracts.py` from this skill directory |
@@ -0,0 +1,50 @@
1
+ # API notes — service-omni-skills-based-routing-configure
2
+
3
+ ## The two objects skills-based routing needs
4
+
5
+ | Object | API | Purpose |
6
+ |---|---|---|
7
+ | `Skill` | Metadata API (`Skill`) to create; `Skill` sObject to read | The skill definition. Skills are metadata-backed — they cannot be `INSERT`ed via DML, so a missing skill is created by deploying a `Skill` component, then re-queried for its Id. |
8
+ | `SkillUser` | REST DML | Junction assigning a `Skill` to an agent `User`, matching Setup > Skills > Assign Users. Required: `SkillId`, `UserId`. |
9
+
10
+ ## Skill: metadata create, sObject read
11
+
12
+ The `Skill` sObject exposes `Id`, `DeveloperName`, `MasterLabel` and is queryable, so detection is a
13
+ SOQL read by DeveloperName. Creation is a Metadata API deploy of:
14
+
15
+ ```xml
16
+ <?xml version="1.0" encoding="UTF-8"?>
17
+ <Skill xmlns="http://soap.sforce.com/2006/04/metadata">
18
+ <description>…</description>
19
+ <label>…</label>
20
+ </Skill>
21
+ ```
22
+
23
+ After the deploy the skill's Id is obtained by re-querying the sObject (the deploy response carries
24
+ the fullName, not the record Id). A freshly-deployed skill may lag a moment before it is queryable;
25
+ if the re-query returns nothing the skill blocks and asks for a re-run rather than binding to an
26
+ empty Id.
27
+
28
+ ## SkillUser
29
+
30
+ - The skill deduplicates `SkillUser` by `(SkillId, UserId)` and creates only missing bindings for
31
+ active users. `SkillLevel` is optional and remains at the platform default.
32
+ - `SkillUser` is created through REST DML. A concurrent create can return `DUPLICATE_VALUE`; the
33
+ script reconciles that response as reused because the desired binding now exists.
34
+ - `ServiceResourceSkill` is not used. It belongs to the Field Service model and requires an active
35
+ `ServiceResource`. Plain Service Cloud orgs do not expose a writable or queryable
36
+ `ServiceResource.IsActive`, so that path produces inactive resources and rejected bindings.
37
+
38
+ ## Prerequisite: base settings
39
+
40
+ Skills-based routing must be enabled in Omni-Channel settings
41
+ (`service-omni-base-settings-configure`) before skills route work. A Skill deploy can succeed while
42
+ SBR is off, so the skill points the operator at the base-settings skill when the deploy fails with
43
+ an SBR-disabled error.
44
+
45
+ ## Out of scope: WorkSkillRouting
46
+
47
+ Authoring `WorkSkillRouting` rules (field-based skill criteria such as "add Voice when Case
48
+ Priority = Medium") is a **separate** capability — it is a Tooling-API rule header
49
+ (`WorkSkillRoutingStandardEntity`: `MasterLabel`, `DeveloperName`, `IsActive`) with skill-requirement
50
+ and field-criteria children, and only one rule is permitted per entity. It is not created here.
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env bash
2
+ # configure-and-report.sh - make agents routable via skills-based routing: ensure a Skill and a SkillUser
3
+ # binding per agent (the classic Omni-Channel agent-skill model); detect-before-write, idempotent.
4
+
5
+ set -euo pipefail
6
+
7
+ if [ $# -lt 2 ]; then
8
+ echo '{"error":"Usage: bash configure-and-report.sh <org-alias> <skill_developer_name> [agent_members_csv] [skill_master_label]"}' >&2
9
+ exit 1
10
+ fi
11
+
12
+ ORG="$1"
13
+ SKILL_DN="$2"
14
+ AGENTS_CSV="${3:-${AGENT_MEMBERS_CSV:-}}"
15
+ SKILL_LABEL_ARG="${4:-}"
16
+ API_VERSION="v66.0"
17
+
18
+ # Skill DeveloperName grammar: leading letter, then letters/digits/underscore, max 80.
19
+ if ! [[ "$SKILL_DN" =~ ^[A-Za-z][A-Za-z0-9_]{0,79}$ ]]; then
20
+ echo "{\"error\":\"Invalid skill_developer_name '$SKILL_DN'. Must start with a letter and contain only letters, digits, and underscores (max 80).\"}" >&2
21
+ exit 1
22
+ fi
23
+
24
+ if [ -z "$AGENTS_CSV" ]; then
25
+ echo '{"error":"No agents supplied. Pass agent_members_csv (Usernames and/or 005... User Ids) or set AGENT_MEMBERS_CSV."}' >&2
26
+ exit 1
27
+ fi
28
+
29
+ if ! sf org display --target-org "$ORG" --json >/dev/null 2>&1; then
30
+ echo "{\"error\":\"Org alias '$ORG' is not authenticated. Run: sf org login web -a $ORG -r <my-domain-url>\"}" >&2
31
+ exit 1
32
+ fi
33
+
34
+ # Default MasterLabel from DeveloperName (underscores -> spaces) when not supplied.
35
+ if [ -n "$SKILL_LABEL_ARG" ]; then
36
+ SKILL_LABEL="$SKILL_LABEL_ARG"
37
+ else
38
+ SKILL_LABEL=$(printf '%s' "$SKILL_DN" | tr '_' ' ')
39
+ fi
40
+
41
+ # Escape the five XML predefined entities so a caller-supplied SKILL_LABEL with & < > " ' cannot
42
+ # break or inject into the hand-built Skill metadata XML below.
43
+ xml_escape() {
44
+ local s="$1"
45
+ s="${s//&/&amp;}"; s="${s//</&lt;}"; s="${s//>/&gt;}"; s="${s//\"/&quot;}"; s="${s//\'/&apos;}"
46
+ printf '%s' "$s"
47
+ }
48
+
49
+ emit_blocked() {
50
+ local msg="$1"; local target_skill="${2:-}"
51
+ local manual_actions='[]'
52
+ if [ -n "$target_skill" ]; then
53
+ manual_actions=$(jq -n --arg ts "$target_skill" '[{id:"MISSING_PREREQ",title:"Run prerequisite skill",target_skill:$ts}]')
54
+ fi
55
+ jq -n --arg msg "$msg" --arg dn "$SKILL_DN" --argjson ma "$manual_actions" \
56
+ '{skill:"service-omni-skills-based-routing-configure",status:"blocked",
57
+ skill_configured:{developer_name:$dn,id:null,state:"not_created"},
58
+ agents:[],created_counts:{},reused_counts:{},manual_actions:$ma,blocking_issue:$msg}'
59
+ exit 1
60
+ }
61
+
62
+ emit_plan() {
63
+ local status="$1"; local detail="$2"
64
+ jq -n --arg status "$status" --arg detail "$detail" --arg dn "$SKILL_DN" \
65
+ '{skill:"service-omni-skills-based-routing-configure",status:$status,plan_mode:true,
66
+ plan_detail:$detail,skill_configured:{developer_name:$dn},manual_actions:[],blocking_issue:null}'
67
+ exit 0
68
+ }
69
+
70
+ # REST POST helper: echoes the created Id on success, empty on failure. Error detail is written to a
71
+ # file because this function is called inside command substitution and shell variables would be lost.
72
+ POST_ERR_FILE="$(mktemp)"
73
+ trap 'rm -f "$POST_ERR_FILE"' EXIT
74
+ post_err() { cat "$POST_ERR_FILE" 2>/dev/null || true; }
75
+ rest_post() {
76
+ local sobject="$1"; local body="$2"
77
+ local stderr_f; stderr_f="$(mktemp)"
78
+ local res
79
+ res=$(sf api request rest --target-org "$ORG" \
80
+ "/services/data/$API_VERSION/sobjects/$sobject" --method POST --body "$body" 2>"$stderr_f" || true)
81
+ local ok; ok=$(echo "$res" | jq -r '.success // false' 2>/dev/null || echo false)
82
+ if [ "$ok" = "true" ]; then
83
+ : > "$POST_ERR_FILE"
84
+ echo "$res" | jq -r '.id'
85
+ else
86
+ local code msg
87
+ code=$(echo "$res" | jq -r 'if type=="array" then .[0].errorCode else .errorCode // "UNKNOWN" end' 2>/dev/null || echo UNKNOWN)
88
+ msg=$(echo "$res" | jq -r 'if type=="array" then .[0].message else .message // "" end' 2>/dev/null || echo "")
89
+ if [ -z "$msg" ] || [ "$msg" = "null" ]; then
90
+ msg=$(printf '%s' "$res" | tr '\n' ' ' | head -c 200)
91
+ [ -n "$msg" ] || msg=$(grep -v '^Warning:' "$stderr_f" | head -1)
92
+ [ -n "$msg" ] || msg="no error detail returned by the sf CLI"
93
+ fi
94
+ printf '%s - %s' "$code" "$msg" > "$POST_ERR_FILE"
95
+ echo ""
96
+ fi
97
+ rm -f "$stderr_f"
98
+ }
99
+
100
+ # Step 1 - Organization + safe_to_write guard
101
+ ORG_JSON=$(sf data query --target-org "$ORG" \
102
+ --query "SELECT IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1" --json 2>/dev/null) \
103
+ || emit_blocked "Failed to query Organization for the safe_to_write guard."
104
+ IS_SANDBOX=$(echo "$ORG_JSON" | jq -r '.result.records[0].IsSandbox')
105
+ TRIAL_EXP=$(echo "$ORG_JSON" | jq -r '.result.records[0].TrialExpirationDate // "null"')
106
+ ORG_TYPE=$(echo "$ORG_JSON" | jq -r '.result.records[0].OrganizationType')
107
+ SAFE_TO_WRITE=false
108
+ if [ "$IS_SANDBOX" = "true" ] || [ "$TRIAL_EXP" != "null" ] \
109
+ || [ "$ORG_TYPE" = "Developer Edition" ] || [ "$ORG_TYPE" = "Base Edition" ]; then
110
+ SAFE_TO_WRITE=true
111
+ fi
112
+ [ "$SAFE_TO_WRITE" = "true" ] || emit_blocked "Refusing to configure skills-based routing on a real production customer org (IsSandbox=$IS_SANDBOX, TrialExpirationDate=$TRIAL_EXP, OrganizationType=$ORG_TYPE). Target a sandbox, trial CDO, or Developer Edition org."
113
+
114
+ # Step 2 - Resolve active agents from the CSV (Usernames and/or 005... Ids)
115
+ USERNAME_IN=""; ID_IN=""; BADTOK=""
116
+ while IFS= read -r tok; do
117
+ [ -z "$tok" ] && continue
118
+ if [[ "$tok" =~ ^005[A-Za-z0-9]{12}([A-Za-z0-9]{3})?$ ]]; then
119
+ ID_IN="${ID_IN:+$ID_IN,}'$tok'"
120
+ elif [[ "$tok" =~ ^[^,\'\"[:space:]]+@[^,\'\"[:space:]]+$ ]]; then
121
+ USERNAME_IN="${USERNAME_IN:+$USERNAME_IN,}'$tok'"
122
+ else
123
+ BADTOK="$BADTOK '$tok'"
124
+ fi
125
+ done <<< "$(printf '%s' "$AGENTS_CSV" | tr ',' '\n' | sed 's/^ *//;s/ *$//')"
126
+ [ -z "$BADTOK" ] || emit_blocked "agent_members_csv contains token(s) that are neither a Username (...@...) nor a 15/18-char User Id (005...):$BADTOK"
127
+
128
+ WHERE_PARTS=""
129
+ [ -n "$USERNAME_IN" ] && WHERE_PARTS="Username IN ($USERNAME_IN)"
130
+ [ -n "$ID_IN" ] && WHERE_PARTS="${WHERE_PARTS:+$WHERE_PARTS OR }Id IN ($ID_IN)"
131
+
132
+ # Fail-closed with structured JSON: without `|| emit_blocked`, a non-zero query makes the command
133
+ # substitution fail and set -e would exit before any blocked output. The jq -e guard separates a real
134
+ # zero-result from an inconclusive/unparseable read so the two get distinct remediation.
135
+ U_JSON=$(sf data query --target-org "$ORG" \
136
+ --query "SELECT Id, Username, Name FROM User WHERE ($WHERE_PARTS) AND IsActive=true" --json 2>/dev/null) \
137
+ || emit_blocked "Could not query User to resolve agent_members_csv (query failed: authentication, network, or API access). Re-run with a reachable org." "service-omni-agent-users-create"
138
+ echo "$U_JSON" | jq -e '.result.totalSize | numbers' >/dev/null 2>&1 \
139
+ || emit_blocked "User resolution returned no parseable result (inconclusive). Re-run against a reachable org." "service-omni-agent-users-create"
140
+ U_COUNT=$(echo "$U_JSON" | jq -r '.result.totalSize')
141
+ [ "$U_COUNT" -gt 0 ] || emit_blocked "No ACTIVE users resolved from agent_members_csv. Verify the usernames/IDs are correct and the users are active." "service-omni-agent-users-create"
142
+
143
+ USERS_JSON=$(echo "$U_JSON" | jq -c '[.result.records[] | {id:.Id, username:.Username, name:.Name}]')
144
+ USER_IDS_IN=$(echo "$USERS_JSON" | jq -r '[.[].id] | map("'"'"'" + . + "'"'"'") | join(",")')
145
+
146
+ # Step 3 - Ensure the Skill exists (reuse sObject; else deploy Skill metadata, then re-query)
147
+ skill_id_by_name() {
148
+ sf data query --target-org "$ORG" --json \
149
+ --query "SELECT Id FROM Skill WHERE DeveloperName='$SKILL_DN'" 2>/dev/null \
150
+ | jq -r '.result.records[0].Id // ""' 2>/dev/null
151
+ }
152
+ SKILL_ID=$(skill_id_by_name)
153
+ SKILL_STATE="reused"
154
+
155
+ # PLAN_ONLY - report intent from read-only detection and stop before any write.
156
+ if [ "${PLAN_ONLY:-}" = "1" ]; then
157
+ if [ -n "$SKILL_ID" ]; then
158
+ emit_plan "action_needed" "Skill '$SKILL_DN' exists; would ensure a SkillUser binding for $U_COUNT active agent(s)."
159
+ else
160
+ emit_plan "action_needed" "Would deploy Skill '$SKILL_DN' and ensure a SkillUser binding for $U_COUNT active agent(s)."
161
+ fi
162
+ fi
163
+
164
+ if [ -z "$SKILL_ID" ]; then
165
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
166
+ WORK="$(mktemp -d)"; trap 'rm -rf "$WORK" "$POST_ERR_FILE"' EXIT
167
+ cp "$SCRIPT_DIR/../assets/sfdx-project.json" "$WORK/sfdx-project.json" 2>/dev/null \
168
+ || echo '{"packageDirectories":[{"path":"force-app","default":true}],"sourceApiVersion":"66.0"}' > "$WORK/sfdx-project.json"
169
+ SKILL_DIR="$WORK/force-app/main/default/skills"; mkdir -p "$SKILL_DIR"
170
+ cat > "$SKILL_DIR/${SKILL_DN}.skill-meta.xml" <<XML
171
+ <?xml version="1.0" encoding="UTF-8"?>
172
+ <Skill xmlns="http://soap.sforce.com/2006/04/metadata">
173
+ <description>Created by service-omni-skills-based-routing-configure.</description>
174
+ <label>$(xml_escape "$SKILL_LABEL")</label>
175
+ </Skill>
176
+ XML
177
+ DEP_JSON=$(cd "$WORK" && sf project deploy start --target-org "$ORG" \
178
+ --metadata "Skill:${SKILL_DN}" --json 2>/dev/null || true)
179
+ DEP_OK=$(echo "$DEP_JSON" | jq -r '.result.success // false' 2>/dev/null || echo false)
180
+ if [ "$DEP_OK" != "true" ]; then
181
+ FAIL=$(echo "$DEP_JSON" | jq -r '.result.details.componentFailures // [] | map(.problem) | join("; ")' 2>/dev/null)
182
+ [ -n "$FAIL" ] && [ "$FAIL" != "null" ] || FAIL=$(echo "$DEP_JSON" | jq -r '.message // "Unknown deploy error"' 2>/dev/null)
183
+ emit_blocked "Failed to deploy Skill '$SKILL_DN': $FAIL. Ensure skills-based routing is enabled in Omni-Channel settings." "service-omni-base-settings-configure"
184
+ fi
185
+ SKILL_ID=$(skill_id_by_name)
186
+ SKILL_STATE="created"
187
+ [ -n "$SKILL_ID" ] || emit_blocked "Deployed Skill '$SKILL_DN' but could not re-query its Id (org may still be indexing). Re-run to bind agents."
188
+ fi
189
+
190
+ # Step 4 - Ensure a SkillUser binding (SkillId, UserId) per active agent. This is the classic
191
+ # Omni-Channel agent-skill model used by Setup > Skills > Assign Users. ServiceResourceSkill is a
192
+ # Field Service model and cannot be used on a plain Service Cloud org where ServiceResource.IsActive
193
+ # is unavailable.
194
+ SU_JSON=$(sf data query --target-org "$ORG" --json \
195
+ --query "SELECT Id, UserId FROM SkillUser WHERE SkillId='$SKILL_ID' AND UserId IN ($USER_IDS_IN)" 2>/dev/null || echo '{}')
196
+ EXISTING_SU=$(echo "$SU_JSON" | jq -c '[.result.records[]? | {user_id:.UserId, su_id:.Id}]' 2>/dev/null || echo '[]')
197
+
198
+ AGENTS_OUT='[]'
199
+ BIND_CREATED=0; BIND_REUSED=0
200
+ PARTIAL_ERRORS=()
201
+
202
+ # For each agent: ensure a SkillUser binding.
203
+ while IFS= read -r row; do
204
+ [ -z "$row" ] && continue
205
+ USR_ID=$(echo "$row" | jq -r '.id')
206
+ UNAME=$(echo "$row" | jq -r '.username')
207
+
208
+ SU_ID=$(echo "$EXISTING_SU" | jq -r --arg u "$USR_ID" '.[] | select(.user_id==$u) | .su_id' | head -1)
209
+ BIND_STATE="reused"
210
+ if [ -z "$SU_ID" ] || [ "$SU_ID" = "null" ]; then
211
+ SU_ID=$(rest_post "SkillUser" "{\"SkillId\":\"$SKILL_ID\",\"UserId\":\"$USR_ID\"}")
212
+ if [ -z "$SU_ID" ]; then
213
+ ERR=$(post_err)
214
+ if printf '%s' "$ERR" | grep -q "DUPLICATE_VALUE"; then
215
+ BIND_STATE="reused"; BIND_REUSED=$((BIND_REUSED+1))
216
+ SU_ID=""
217
+ else
218
+ PARTIAL_ERRORS+=("$UNAME: skill binding failed - $ERR")
219
+ BIND_STATE="not_created"
220
+ fi
221
+ else
222
+ BIND_STATE="created"; BIND_CREATED=$((BIND_CREATED+1))
223
+ fi
224
+ else
225
+ BIND_REUSED=$((BIND_REUSED+1))
226
+ fi
227
+
228
+ SU_JQ='null'; [ -n "$SU_ID" ] && [ "$SU_ID" != "null" ] && SU_JQ=$(jq -n --arg s "$SU_ID" '$s')
229
+ AGENTS_OUT=$(echo "$AGENTS_OUT" | jq -c \
230
+ --arg u "$UNAME" --arg uid "$USR_ID" --argjson su "$SU_JQ" --arg bs "$BIND_STATE" \
231
+ '. + [{username:$u,user_id:$uid,skill_binding_id:$su,skill_binding_state:$bs}]')
232
+ done < <(echo "$USERS_JSON" | jq -c '.[]')
233
+
234
+ # Step 5 - Status + report
235
+ if [ ${#PARTIAL_ERRORS[@]} -gt 0 ]; then
236
+ # Any success at all → partial; nothing succeeded → blocked.
237
+ if [ "$BIND_CREATED" -gt 0 ] || [ "$BIND_REUSED" -gt 0 ]; then
238
+ STATUS="partial"
239
+ else
240
+ STATUS="blocked"
241
+ fi
242
+ BLOCKING="Partial skills-based-routing configuration. Failures: ${PARTIAL_ERRORS[*]}"
243
+ elif [ "$BIND_CREATED" -eq 0 ] && [ "$SKILL_STATE" = "reused" ]; then
244
+ STATUS="reused"; BLOCKING=""
245
+ else
246
+ STATUS="configured"; BLOCKING=""
247
+ fi
248
+
249
+ BLOCKING_JQ='null'
250
+ [ -n "$BLOCKING" ] && BLOCKING_JQ=$(jq -n --arg s "$BLOCKING" '$s')
251
+
252
+ jq -n \
253
+ --arg status "$STATUS" \
254
+ --arg dn "$SKILL_DN" \
255
+ --arg sid "$SKILL_ID" \
256
+ --arg sstate "$SKILL_STATE" \
257
+ --argjson agents "$AGENTS_OUT" \
258
+ --argjson bind_created "$BIND_CREATED" \
259
+ --argjson bind_reused "$BIND_REUSED" \
260
+ --argjson blocking "$BLOCKING_JQ" \
261
+ '{
262
+ skill: "service-omni-skills-based-routing-configure",
263
+ status: $status,
264
+ skill_configured: {developer_name:$dn, id:$sid, state:$sstate},
265
+ agents: $agents,
266
+ created_counts: {skill_bindings:$bind_created},
267
+ reused_counts: {skill_bindings:$bind_reused},
268
+ manual_actions: [],
269
+ blocking_issue: $blocking
270
+ }'
@@ -0,0 +1,120 @@
1
+ """Shared paths + fake-`sf` harness for the skills-based-routing contract suite.
2
+
3
+ Drives ``configure-and-report.sh`` through ``subprocess`` and asserts on exit codes + emitted JSON.
4
+ Org-free and network-free. ``jq`` comes from the real PATH.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ import subprocess
10
+ import tempfile
11
+ from pathlib import Path
12
+
13
+ SKILLS_ROOT = Path(__file__).resolve().parents[3]
14
+ SKILL_DIR = "service-omni-skills-based-routing-configure"
15
+ SCRIPT = f"{SKILL_DIR}/scripts/configure-and-report.sh"
16
+
17
+ # PRODUCTION org - safe_to_write must refuse before any write.
18
+ _FAKE_SF_PROD = r"""#!/usr/bin/env bash
19
+ sub="${1:-} ${2:-}"; args="$*"
20
+ case "$sub" in
21
+ "org display") echo '{"status":0,"result":{"id":"00Dxx0000000000EAA"}}'; exit 0;;
22
+ "data query")
23
+ if printf '%s' "$args" | grep -q "FROM Organization"; then
24
+ echo '{"status":0,"result":{"records":[{"IsSandbox":false,"TrialExpirationDate":null,"OrganizationType":"Enterprise Edition"}]}}'
25
+ else
26
+ echo '{"status":0,"result":{"records":[],"totalSize":0}}'
27
+ fi
28
+ exit 0;;
29
+ "project deploy") echo '{"status":1,"result":{"success":false},"message":"FAKE_SF_DEPLOY_SHOULD_NOT_BE_REACHED"}'; exit 1;;
30
+ "api request") echo '{"success":false,"errorCode":"FAKE_POST_SHOULD_NOT_BE_REACHED"}'; exit 0;;
31
+ *) echo '{"status":0,"result":{}}'; exit 0;;
32
+ esac
33
+ """
34
+
35
+ # SANDBOX org. Tunables (all default to the "everything must be created" path):
36
+ # FAKE_SKILL_PRESENT=1 → the Skill already exists (reused); else absent → deploy then re-query.
37
+ # FAKE_BIND_PRESENT=1 → a SkillUser binding already exists for each user (reused); else create.
38
+ # FAKE_BIND_POST_FAIL=1 → the SkillUser POST fails for the second agent.
39
+ # FAKE_BIND_DUPLICATE=1 → the SkillUser POST returns DUPLICATE_VALUE and is reconciled as reused.
40
+ # Two active users (005...0001 / 005...0002) are always resolved from the User query.
41
+ _FAKE_SF_SANDBOX = r"""#!/usr/bin/env bash
42
+ sub="${1:-} ${2:-}"; args="$*"
43
+ SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
44
+ SKILL_PRESENT="${FAKE_SKILL_PRESENT:-0}"
45
+ BIND_PRESENT="${FAKE_BIND_PRESENT:-0}"
46
+ BIND_POST_FAIL="${FAKE_BIND_POST_FAIL:-0}"
47
+ BIND_DUPLICATE="${FAKE_BIND_DUPLICATE:-0}"
48
+ if [ -n "${FAKE_SF_CALLLOG:-}" ]; then printf '%s\n' "$args" >> "$FAKE_SF_CALLLOG"; fi
49
+ case "$sub" in
50
+ "org display") echo '{"status":0,"result":{"id":"00Dxx0000000000EAA"}}'; exit 0;;
51
+ "data query")
52
+ if printf '%s' "$args" | grep -q "FROM Organization"; then
53
+ echo '{"status":0,"result":{"records":[{"IsSandbox":true,"TrialExpirationDate":null,"OrganizationType":"Developer Edition"}]}}'
54
+ elif printf '%s' "$args" | grep -q "FROM User"; then
55
+ echo '{"status":0,"result":{"records":[{"Id":"0050000000000001AAA","Username":"agent1@example.com","Name":"Agent One"},{"Id":"0050000000000002AAA","Username":"agent2@example.com","Name":"Agent Two"}],"totalSize":2}}'
56
+ elif printf '%s' "$args" | grep -q "FROM SkillUser"; then
57
+ if [ "$BIND_PRESENT" = "1" ]; then
58
+ echo '{"status":0,"result":{"records":[{"Id":"0C90000000000001AAA","UserId":"0050000000000001AAA"},{"Id":"0C90000000000002AAA","UserId":"0050000000000002AAA"}],"totalSize":2}}'
59
+ else echo '{"status":0,"result":{"records":[],"totalSize":0}}'; fi
60
+ elif printf '%s' "$args" | grep -q "FROM Skill"; then
61
+ if [ "$SKILL_PRESENT" = "1" ] || [ -f "$SELF_DIR/.skill_deployed" ]; then
62
+ echo '{"status":0,"result":{"records":[{"Id":"0PS0000000000001AAA"}],"totalSize":1}}'
63
+ else echo '{"status":0,"result":{"records":[],"totalSize":0}}'; fi
64
+ else
65
+ echo '{"status":0,"result":{"records":[],"totalSize":0}}'
66
+ fi
67
+ exit 0;;
68
+ "project deploy")
69
+ touch "$SELF_DIR/.skill_deployed"
70
+ echo '{"status":0,"result":{"success":true,"files":[{"fullName":"'"$(printf '%s' "$args" | sed -n 's/.*Skill:\([A-Za-z0-9_]*\).*/\1/p')"'","type":"Skill","state":"Created"}]}}'
71
+ exit 0;;
72
+ "api request")
73
+ if printf '%s' "$args" | grep -q "sobjects/SkillUser"; then
74
+ if [ "$BIND_DUPLICATE" = "1" ]; then
75
+ echo '[{"errorCode":"DUPLICATE_VALUE","message":"duplicate value found: unique constraint (SkillId, UserId)"}]';
76
+ elif [ "$BIND_POST_FAIL" = "1" ] && printf '%s' "$args" | grep -q "0050000000000002AAA"; then
77
+ echo '[{"errorCode":"FIELD_CUSTOM_VALIDATION_EXCEPTION","message":"forced binding failure"}]';
78
+ else echo '{"success":true,"id":"0C90000000000009AAA"}'; fi
79
+ else
80
+ echo '{"success":true,"id":"000000000000000AAA"}'
81
+ fi
82
+ exit 0;;
83
+ *) echo '{"status":0,"result":{}}'; exit 0;;
84
+ esac
85
+ """
86
+
87
+
88
+ def run(args, path_prefix=None, scrub_sf=False, extra_env=None):
89
+ env = dict(os.environ)
90
+ if scrub_sf:
91
+ kept = [d for d in env.get("PATH", "").split(os.pathsep)
92
+ if d and not os.path.exists(os.path.join(d, "sf"))]
93
+ env["PATH"] = os.pathsep.join(kept)
94
+ if path_prefix:
95
+ env["PATH"] = path_prefix + os.pathsep + env.get("PATH", "")
96
+ if extra_env:
97
+ env.update({k: str(v) for k, v in extra_env.items()})
98
+ proc = subprocess.run(
99
+ ["bash", str(SKILLS_ROOT / SCRIPT), *args],
100
+ capture_output=True, text=True, env=env, cwd=str(SKILLS_ROOT),
101
+ )
102
+ return proc.returncode, (proc.stdout or "") + (proc.stderr or "")
103
+
104
+
105
+ class FakeSf:
106
+ def __init__(self, body=_FAKE_SF_PROD):
107
+ self._body = body
108
+ self._tmp = None
109
+
110
+ def __enter__(self):
111
+ self._tmp = tempfile.mkdtemp(prefix="sbr-fake-sf-")
112
+ sf = Path(self._tmp) / "sf"
113
+ sf.write_text(self._body)
114
+ sf.chmod(0o755)
115
+ return self._tmp
116
+
117
+ def __exit__(self, *exc):
118
+ import shutil
119
+ if self._tmp:
120
+ shutil.rmtree(self._tmp, ignore_errors=True)