@salesforce/afv-skills 1.55.0 → 1.56.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 (220) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-generate/SKILL.md +8 -0
  3. package/skills/agentforce-generate/references/actions-reference.md +2 -2
  4. package/skills/agentforce-generate/references/voice-modality-reference.md +51 -15
  5. package/skills/agentforce-generate/references/voice-telephony-cli.md +193 -0
  6. package/skills/agentforce-observe/SKILL.md +13 -9
  7. package/skills/agentforce-observe/references/ahm-alerts.md +193 -14
  8. package/skills/automation-sandbox-post-copy-config-generate/SKILL.md +24 -15
  9. package/skills/automation-sandbox-post-copy-config-generate/assets/config_template.json +11 -0
  10. package/skills/automation-sandbox-post-copy-config-generate/assets/json_schema.json +33 -1
  11. package/skills/automation-sandbox-post-copy-config-generate/references/configuration_catalog.md +32 -1
  12. package/skills/automation-sandbox-post-copy-configure/SKILL.md +75 -71
  13. package/skills/automation-sandbox-post-copy-configure/assets/scheduled_apex_template.apex +13 -0
  14. package/skills/automation-sandbox-post-copy-configure/examples/sample_scheduled_apex_config.json +13 -0
  15. package/skills/automation-sandbox-post-copy-configure/references/scheduled_apex_path.md +155 -0
  16. package/skills/automation-sandbox-post-copy-configure/scripts/build-scheduled-apex.mjs +62 -0
  17. package/skills/automation-sandbox-post-copy-configure/scripts/soql-escape-job-name.mjs +28 -0
  18. package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +383 -0
  19. package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +324 -0
  20. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +63 -0
  21. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +100 -0
  22. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -0
  23. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +46 -0
  24. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/04-deploy-engine.js +198 -0
  25. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/05-deploy-tpm-accruals.js +231 -0
  26. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +82 -0
  27. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +517 -0
  28. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +416 -0
  29. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -0
  30. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +514 -0
  31. package/skills/consumer-goods-sync-management-configure/SKILL.md +137 -0
  32. package/skills/consumer-goods-sync-management-configure/examples/scenarios.md +294 -0
  33. package/skills/consumer-goods-sync-management-configure/references/act1-setup-sync.md +333 -0
  34. package/skills/consumer-goods-sync-management-configure/references/act2-assign-users.md +324 -0
  35. package/skills/consumer-goods-sync-management-configure/references/act3-plan-verify.md +113 -0
  36. package/skills/consumer-goods-sync-management-configure/references/readiness-and-enablement.md +164 -0
  37. package/skills/consumer-goods-sync-management-configure/references/sync-management-app-install-backbone.md +159 -0
  38. package/skills/consumer-goods-sync-management-configure/references/transports-and-namespace.md +220 -0
  39. package/skills/consumer-goods-sync-management-configure/references/verify-and-smoke.md +175 -0
  40. package/skills/dx-org-analyze/SKILL.md +0 -1
  41. package/skills/dx-org-analyze/scripts/collect_org_data.py +2 -2
  42. package/skills/dx-org-analyze/scripts/compute_diff.py +2 -4
  43. package/skills/dx-org-analyze/scripts/introspect_org.py +4 -40
  44. package/skills/dx-org-manage/SKILL.md +6 -50
  45. package/skills/dx-org-manage/examples/README.md +10 -16
  46. package/skills/dx-org-manage/references/scratch-org-create.md +1 -1
  47. package/skills/dx-org-manage/references/scratch-org-operations.md +1 -1
  48. package/skills/dx-org-shape-manage/SKILL.md +4 -3
  49. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +1 -0
  50. package/skills/experience-accessibility-validate/SKILL.md +8 -5
  51. package/skills/experience-accessibility-validate/references/reviewers/sc-1-1-1-non-text-content.md +1 -4
  52. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-i-lists.md +0 -4
  53. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-ii-tables.md +20 -85
  54. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iii-form-labels.md +4 -4
  55. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iv-regions.md +0 -4
  56. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-v-groups.md +2 -4
  57. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-5-identify-input.md +0 -4
  58. package/skills/experience-accessibility-validate/references/reviewers/sc-1-4-3-contrast.md +4 -14
  59. package/skills/experience-accessibility-validate/references/reviewers/sc-2-1-1-keyboard.md +9 -5
  60. package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-4-link-purpose.md +1 -4
  61. package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-6-headings-labels.md +0 -4
  62. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-1-pointer-gestures.md +0 -4
  63. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-2-pointer-cancellation.md +0 -4
  64. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-3-label-in-name.md +0 -4
  65. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-7-dragging-movement.md +0 -4
  66. package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-1-on-focus.md +0 -4
  67. package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-2-on-input.md +1 -4
  68. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-1-error-identification.md +1 -4
  69. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-2-labels-instructions.md +1 -4
  70. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-3-error-suggestion.md +1 -4
  71. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-i-name.md +4 -5
  72. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-ii-role.md +1 -4
  73. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-iii-value.md +0 -4
  74. package/skills/experience-lwc-accessibility-jest-run/SKILL.md +104 -25
  75. package/skills/experience-lwc-accessibility-jest-run/assets/jest.config.js +4 -0
  76. package/skills/experience-lwc-accessibility-jest-run/assets/sa11y-jest-setup.js +5 -0
  77. package/skills/experience-ui-bundle-app-coordinate/SKILL.md +5 -5
  78. package/skills/experience-ui-bundle-features-generate/SKILL.md +65 -36
  79. package/skills/experience-ui-bundle-features-generate/references/angular/features.md +74 -0
  80. package/skills/experience-ui-bundle-features-generate/references/react/features.md +59 -0
  81. package/skills/experience-ui-bundle-features-generate/scripts/detect-framework.sh +73 -0
  82. package/skills/experience-ui-bundle-frontend-generate/SKILL.md +2 -2
  83. package/skills/experience-ui-bundle-metadata-generate/SKILL.md +26 -14
  84. package/skills/experience-ui-bundle-metadata-generate/references/angular-metadata-generate.md +37 -0
  85. package/skills/experience-ui-bundle-metadata-generate/references/react-metadata-generate.md +32 -0
  86. package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.sh +73 -0
  87. package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.sh +30 -8
  88. package/skills/field-service-data-capture-migrate/SKILL.md +391 -0
  89. package/skills/field-service-data-capture-migrate/assets/sample-legacy-flow.flow-meta.xml +163 -0
  90. package/skills/field-service-data-capture-migrate/references/architecture-notes.md +40 -0
  91. package/skills/field-service-data-capture-migrate/references/component-mapping.md +278 -0
  92. package/skills/field-service-data-capture-migrate/references/converter-builder-contract.md +183 -0
  93. package/skills/field-service-data-capture-migrate/references/dc-platform-rules.md +130 -0
  94. package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +215 -0
  95. package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +36 -0
  96. package/skills/field-service-data-capture-migrate/references/migration-checklist.md +121 -0
  97. package/skills/field-service-data-capture-migrate/references/transformer-rules.md +132 -0
  98. package/skills/field-service-data-capture-migrate/scripts/analyze_flow.py +277 -0
  99. package/skills/field-service-data-capture-migrate/scripts/convert_to_dc_spec.py +1311 -0
  100. package/skills/field-service-data-capture-migrate/scripts/cud_analysis.py +402 -0
  101. package/skills/field-service-data-capture-migrate/scripts/deploy_flow.sh +230 -0
  102. package/skills/field-service-data-capture-migrate/scripts/discover_subflow_tree.py +230 -0
  103. package/skills/field-service-data-capture-migrate/scripts/flow_input.py +186 -0
  104. package/skills/field-service-data-capture-migrate/scripts/flow_xml_utils.py +39 -0
  105. package/skills/field-service-data-capture-migrate/scripts/fsm_dc_catalog.py +413 -0
  106. package/skills/field-service-data-capture-migrate/scripts/generate_summary.py +199 -0
  107. package/skills/field-service-data-capture-migrate/scripts/json_to_flow_xml.py +146 -0
  108. package/skills/field-service-data-capture-migrate/scripts/manual_review_flags.py +42 -0
  109. package/skills/field-service-data-capture-migrate/scripts/retrieve_flow_rest.sh +155 -0
  110. package/skills/field-service-data-capture-migrate/scripts/subflow_names.py +49 -0
  111. package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +1661 -0
  112. package/skills/field-service-data-capture-migrate/scripts/validate_flow.sh +147 -0
  113. package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +1258 -0
  114. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +1 -1
  115. package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +1 -1
  116. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +14 -1
  117. package/skills/mobile-apps-create/SKILL.md +1 -1
  118. package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/SKILL.md +7 -7
  119. package/skills/platform-sandbox-configure/SKILL.md +108 -133
  120. package/skills/platform-sandbox-configure/references/api-response-shapes.md +39 -0
  121. package/skills/platform-sandbox-configure/references/definition-file-approach.md +73 -0
  122. package/skills/platform-widget-generate/SKILL.md +26 -18
  123. package/skills/platform-widget-generate/examples/formulas.json +191 -0
  124. package/skills/platform-widget-generate/references/widget-formulas.md +123 -0
  125. package/skills/platform-widget-generate/references/widget-meta-directives.md +4 -4
  126. package/skills/sales-call-scoring-configure/SKILL.md +318 -0
  127. package/skills/sales-call-scoring-configure/assets/best-practice-competencies.json +67 -0
  128. package/skills/sales-call-scoring-configure/assets/ootb-competencies.json +58 -0
  129. package/skills/sales-call-scoring-configure/references/admin-communication.md +79 -0
  130. package/skills/sales-call-scoring-configure/references/competency-crud.md +130 -0
  131. package/skills/sales-call-scoring-configure/scripts/create-competency.sh +166 -0
  132. package/skills/sales-call-scoring-configure/scripts/create-custom-competency.sh +166 -0
  133. package/skills/sales-call-scoring-configure/scripts/edit-competency.sh +185 -0
  134. package/skills/sales-call-scoring-configure/scripts/edit-custom-competency.sh +190 -0
  135. package/skills/sales-call-scoring-configure/scripts/enable-call-scoring.sh +436 -0
  136. package/skills/sales-call-scoring-configure/scripts/get-competency.sh +72 -0
  137. package/skills/sales-call-scoring-configure/scripts/get-custom-competency.sh +78 -0
  138. package/skills/sales-call-scoring-configure/scripts/install-best-practice-competencies.sh +223 -0
  139. package/skills/sales-call-scoring-configure/scripts/install-ootb-competencies.sh +237 -0
  140. package/skills/sales-call-scoring-configure/scripts/list-competencies.sh +99 -0
  141. package/skills/sales-call-scoring-configure/scripts/shared/auth.sh +116 -0
  142. package/skills/sales-call-scoring-configure/scripts/shared/cap.sh +86 -0
  143. package/skills/sales-call-scoring-configure/scripts/shared/soap.sh +267 -0
  144. package/skills/sales-call-scoring-configure/scripts/shared/soql.sh +25 -0
  145. package/skills/sales-call-scoring-configure/scripts/toggle-competency.sh +112 -0
  146. package/skills/sales-call-scoring-configure/scripts/toggle-custom-competency.sh +125 -0
  147. package/skills/sales-call-scoring-configure/scripts/verify-ootb-competencies.sh +116 -0
  148. package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +1 -0
  149. package/skills/service-de-channel-activate/SKILL.md +6 -5
  150. package/skills/service-de-channel-create/SKILL.md +3 -3
  151. package/skills/service-de-channel-routing-configure/SKILL.md +3 -3
  152. package/skills/service-de-channel-settings-configure/SKILL.md +195 -0
  153. package/skills/service-de-channel-settings-configure/references/channel-settings-facets.md +152 -0
  154. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/gotchas.md +3 -3
  155. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/language-keywords.md +4 -4
  156. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/worked-examples.md +14 -14
  157. package/skills/service-de-headless-channel-configure/SKILL.md +6 -6
  158. package/skills/service-de-headless-channel-configure/references/worked-examples.md +1 -1
  159. package/skills/service-helpagent-coordinate/SKILL.md +3 -2
  160. package/skills/service-helpagent-coordinate/references/channel-voice.md +15 -4
  161. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +49 -31
  162. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +71 -6
  163. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +26 -4
  164. package/skills/service-itsm-agentic-setup-cmdb-access-assign/SKILL.md +35 -10
  165. package/skills/service-itsm-agentic-setup-cmdb-access-assign/references/mcp-invocation.md +25 -5
  166. package/skills/service-itsm-agentic-setup-cmdb-bundle-deploy/SKILL.md +1 -1
  167. package/skills/service-itsm-agentic-setup-cmdb-configure/SKILL.md +5 -5
  168. package/skills/service-itsm-agentic-setup-cmdb-configure/references/mcp-invocation.md +1 -1
  169. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +2 -2
  170. package/skills/service-itsm-agentic-setup-cmdb-coordinate/examples/output-templates.md +1 -1
  171. package/skills/service-itsm-agentic-setup-cmdb-discovery-configure/SKILL.md +25 -15
  172. package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -5
  173. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/report-format.md +4 -2
  174. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +1 -1
  175. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +29 -16
  176. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -5
  177. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/report-format.md +4 -2
  178. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +1 -1
  179. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +29 -16
  180. package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +138 -68
  181. package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +31 -15
  182. package/skills/service-itsm-agentic-setup-incident-management/references/incident-persona-psg.md +156 -0
  183. package/skills/service-itsm-agentic-setup-incident-management/references/incident-preferences.md +148 -0
  184. package/skills/service-itsm-agentic-setup-incident-management/references/major-incident-management.md +162 -0
  185. package/skills/service-itsm-agentic-setup-incident-management/references/service-mgmt-privilege.md +149 -0
  186. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +103 -89
  187. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +2 -2
  188. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +21 -39
  189. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +3 -3
  190. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +18 -1
  191. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +63 -37
  192. package/skills/service-omni-attribute-routing-configure/SKILL.md +53 -0
  193. package/skills/service-omni-attribute-routing-configure/references/api-notes.md +7 -0
  194. package/skills/service-omni-attribute-routing-configure/scripts/configure-and-report.sh +224 -0
  195. package/skills/service-omni-attribute-routing-configure/scripts/tests/test_attribute_routing_contracts.py +137 -0
  196. package/skills/service-omni-channel-inventory-analyze/SKILL.md +96 -0
  197. package/skills/service-omni-channel-inventory-analyze/references/api-notes.md +52 -0
  198. package/skills/service-omni-channel-inventory-analyze/scripts/analyze.sh +185 -0
  199. package/skills/service-omni-channel-inventory-analyze/scripts/tests/_bootstrap.py +84 -0
  200. package/skills/service-omni-channel-inventory-analyze/scripts/tests/test_inventory_analyze_contracts.py +141 -0
  201. package/skills/service-omni-channel-limits-analyze/SKILL.md +70 -0
  202. package/skills/service-omni-channel-limits-analyze/references/api-notes.md +22 -0
  203. package/skills/service-omni-channel-limits-analyze/scripts/analyze.sh +159 -0
  204. package/skills/service-omni-channel-limits-analyze/scripts/tests/_bootstrap.py +87 -0
  205. package/skills/service-omni-channel-limits-analyze/scripts/tests/test_limits_analyze_contracts.py +99 -0
  206. package/skills/service-omni-channel-setup-coordinate/SKILL.md +12 -20
  207. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +55 -5
  208. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +38 -0
  209. package/skills/service-omni-command-center-analyze/SKILL.md +2 -2
  210. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
  211. package/skills/dx-org-manage/examples/snapshots/error_output.json +0 -9
  212. package/skills/dx-org-manage/examples/snapshots/success_output.json +0 -15
  213. package/skills/dx-org-manage/references/cli_flags.md +0 -67
  214. package/skills/dx-org-manage/references/creating-snapshot.md +0 -103
  215. package/skills/dx-org-manage/references/snapshot_usage.md +0 -74
  216. package/skills/experience-accessibility-validate/scripts/contrast-ratio.py +0 -153
  217. package/skills/experience-lwc-accessibility-jest-run/references/running-sa11y-jest-tests.md +0 -205
  218. package/skills/experience-ui-bundle-features-generate/scripts/verify-react-bundle.sh +0 -14
  219. /package/skills/experience-ui-bundle-features-generate/references/{conflict-resolution-schema.json → common/conflict-resolution-schema.json} +0 -0
  220. /package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/references/api-response-schema.md +0 -0
@@ -0,0 +1,99 @@
1
+ #!/bin/bash
2
+
3
+ # List existing Coaching Competencies on the org.
4
+ # Reads EnablementCompetencyDef (setup entity, keyPrefix 1nA) via Tooling API.
5
+ #
6
+ # Output modes (per SKILL.md rule #5 — admin-safe by default):
7
+ # default — MasterLabel + IsActive + Scope + Description only.
8
+ # No record Ids, DeveloperNames, or Template enum keys.
9
+ # --verbose — Everything the default shows, PLUS DeveloperName, Id, and
10
+ # SourceCompetencyTemplate. Use when the admin explicitly
11
+ # asks to debug or share with support.
12
+ # --json — Raw record JSON for the calling agent to parse. Never
13
+ # forward this to the admin directly.
14
+ #
15
+ # Usage:
16
+ # ./list-competencies.sh <org-alias>
17
+ # ./list-competencies.sh <org-alias> --verbose
18
+ # ./list-competencies.sh <org-alias> --json
19
+
20
+ set -euo pipefail
21
+
22
+ export NO_COLOR=1
23
+ export FORCE_COLOR=0
24
+
25
+ ORG_ALIAS="${1:-}"
26
+ FORMAT="${2:-text}"
27
+
28
+ if [[ -z "$ORG_ALIAS" ]]; then
29
+ echo "Error: Missing org alias"
30
+ echo "Usage: $0 <org-alias> [--verbose|--json]"
31
+ exit 1
32
+ fi
33
+
34
+ case "$FORMAT" in
35
+ text|--verbose|--json) ;;
36
+ *) echo "Error: Unknown flag: $FORMAT (expected --verbose or --json)"; exit 1 ;;
37
+ esac
38
+
39
+ # ---------------------------------------------------------------------------
40
+ # Auth
41
+ # ---------------------------------------------------------------------------
42
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
43
+ # shellcheck source=shared/auth.sh
44
+ source "$SCRIPT_DIR/shared/auth.sh"
45
+
46
+ resolve_org_auth "$ORG_ALIAS" || exit 1
47
+
48
+ QUERY="SELECT Id, DeveloperName, MasterLabel, Description, IsActive, EvaluationScope, SourceCompetencyTemplate FROM EnablementCompetencyDef ORDER BY MasterLabel"
49
+
50
+ RAW=$(sf data query --target-org "$ORG_ALIAS" --use-tooling-api \
51
+ --query "$QUERY" --json 2>/dev/null)
52
+
53
+ # sf can prepend update-available warnings to stdout — extract the JSON body.
54
+ RAW_JSON=$(echo "$RAW" | awk '/^{/,EOF')
55
+
56
+ if ! echo "$RAW_JSON" | jq -e '.status == 0' >/dev/null 2>&1; then
57
+ echo "Error: Tooling API query failed:"
58
+ echo "$RAW" | jq -r '.message // .' 2>/dev/null || echo "$RAW"
59
+ exit 1
60
+ fi
61
+ RAW="$RAW_JSON"
62
+
63
+ if [[ "$FORMAT" == "--json" ]]; then
64
+ echo "$RAW" | jq '.result.records'
65
+ exit 0
66
+ fi
67
+
68
+ TOTAL=$(echo "$RAW" | jq -r '.result.totalSize')
69
+ ACTIVE=$(echo "$RAW" | jq -r '[.result.records[] | select(.IsActive == true)] | length')
70
+
71
+ echo "Coaching Competencies on $ORG_ALIAS"
72
+ echo ""
73
+ echo " Total: $TOTAL Active: $ACTIVE / 8 (max)"
74
+ echo ""
75
+
76
+ if [[ "$TOTAL" == "0" ]]; then
77
+ echo " (none configured yet — run install-ootb-competencies.sh to seed the OOTB set)"
78
+ exit 0
79
+ fi
80
+
81
+ if [[ "$FORMAT" == "--verbose" ]]; then
82
+ # Full technical detail — Ids, DeveloperNames, Template enum keys.
83
+ # Only show this when the admin explicitly asks to debug or share with support.
84
+ echo "$RAW" | jq -r '.result.records[] |
85
+ " \(if .IsActive then "" else "" end) \(.MasterLabel)
86
+ Scope: \(.EvaluationScope)
87
+ Description: \(.Description // "(none)")
88
+ DeveloperName: \(.DeveloperName)\(if .SourceCompetencyTemplate != null and .SourceCompetencyTemplate != "" then "
89
+ Template: \(.SourceCompetencyTemplate)" else "" end)
90
+ Id: \(.Id)
91
+ "'
92
+ else
93
+ # Default admin-safe view — no Ids, DeveloperNames, or Template enum keys.
94
+ echo "$RAW" | jq -r '.result.records[] |
95
+ " \(if .IsActive then "" else "" end) \(.MasterLabel)
96
+ Scope: \(.EvaluationScope)
97
+ Description: \(.Description // "(none)")
98
+ "'
99
+ fi
@@ -0,0 +1,116 @@
1
+ #!/bin/bash
2
+ # Shared library for org authentication resolution.
3
+ #
4
+ # Two entry points, deliberately separate:
5
+ #
6
+ # resolve_org_auth <alias> → validate alias, confirm reachable,
7
+ # confirm HTTPS. Does NOT extract
8
+ # ACCESS_TOKEN. Use this if the caller
9
+ # only ever hits Salesforce through
10
+ # `sf api request rest --target-org`
11
+ # or `sf data query --target-org`,
12
+ # which authenticate via the CLI's own
13
+ # session and never need the raw token
14
+ # in shell scope.
15
+ #
16
+ # resolve_org_auth_with_token <alias> → does everything above, PLUS extracts
17
+ # and exports ACCESS_TOKEN. Use this
18
+ # ONLY when a caller must embed the
19
+ # token in a request body (e.g. SOAP
20
+ # Metadata API sessionId — see
21
+ # enable-call-scoring.sh).
22
+ #
23
+ # The split exists because passing ACCESS_TOKEN on a curl command line
24
+ # ("curl -H 'Authorization: Bearer $ACCESS_TOKEN' ...") exposes the token in
25
+ # the OS process table (ps / /proc/*/cmdline). Callers that don't strictly
26
+ # need the raw token should not extract it. See W-23934530.
27
+ #
28
+ # Both functions cache results on RESOLVED_ORG_ALIAS so back-to-back invocations
29
+ # in the same shell don't re-hit `sf org display`.
30
+
31
+ # Common gate — alias validation + reachability + HTTPS. Exports INSTANCE_URL
32
+ # (needed by SOAP callers to build the endpoint). Does NOT export a token.
33
+ resolve_org_auth() {
34
+ local org_alias="$1"
35
+
36
+ if [[ -z "$org_alias" ]]; then
37
+ echo "Error: Missing org alias"
38
+ return 1
39
+ fi
40
+
41
+ if [[ ! "$org_alias" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
42
+ echo "Invalid org alias: '$org_alias'"
43
+ return 1
44
+ fi
45
+
46
+ # Cache hit — but re-check HTTPS in case the cached INSTANCE_URL was set by
47
+ # something other than a prior resolve_org_auth call.
48
+ if [[ -n "${INSTANCE_URL:-}" && "${RESOLVED_ORG_ALIAS:-}" == "$org_alias" ]]; then
49
+ if [[ ! "$INSTANCE_URL" =~ ^https:// ]]; then
50
+ echo "Cached instance URL is not HTTPS: $INSTANCE_URL"
51
+ echo " Refusing to authenticate over an insecure connection."
52
+ return 1
53
+ fi
54
+ return 0
55
+ fi
56
+
57
+ # Capture stderr too — some CLI versions emit auth errors there — then strip
58
+ # any warning lines the sf CLI prepends to stdout before the JSON body.
59
+ local org_display org_json
60
+ org_display=$(sf org display --target-org "$org_alias" --json 2>&1)
61
+ org_json=$(echo "$org_display" | awk '/^{/,EOF')
62
+
63
+ INSTANCE_URL=$(echo "$org_json" | jq -r '.result.instanceUrl // empty' 2>/dev/null)
64
+ if [[ -z "$INSTANCE_URL" ]]; then
65
+ echo "Cannot connect to org '$org_alias'. Run: sf org login web --alias $org_alias"
66
+ return 1
67
+ fi
68
+
69
+ if [[ ! "$INSTANCE_URL" =~ ^https:// ]]; then
70
+ echo "Instance URL is not HTTPS: $INSTANCE_URL"
71
+ echo " Refusing to authenticate over an insecure connection."
72
+ return 1
73
+ fi
74
+
75
+ export INSTANCE_URL
76
+ RESOLVED_ORG_ALIAS="$org_alias"
77
+ export RESOLVED_ORG_ALIAS
78
+ return 0
79
+ }
80
+
81
+ # Superset — resolve_org_auth + extract ACCESS_TOKEN. Used ONLY by callers
82
+ # that must place the token inside a request body (SOAP sessionId). New code
83
+ # should not use this; prefer `sf api request rest --target-org` instead.
84
+ resolve_org_auth_with_token() {
85
+ local org_alias="$1"
86
+
87
+ resolve_org_auth "$org_alias" || return 1
88
+
89
+ # Cache hit on token too.
90
+ if [[ -n "${ACCESS_TOKEN:-}" && "${ACCESS_TOKEN}" != "null" \
91
+ && "${RESOLVED_ORG_ALIAS:-}" == "$org_alias" \
92
+ && "${ACCESS_TOKEN_RESOLVED:-}" == "1" ]]; then
93
+ return 0
94
+ fi
95
+
96
+ local org_display org_json
97
+ org_display=$(sf org display --target-org "$org_alias" --json 2>&1)
98
+ org_json=$(echo "$org_display" | awk '/^{/,EOF')
99
+
100
+ ACCESS_TOKEN=$(echo "$org_json" | jq -r '.result.accessToken // empty' 2>/dev/null)
101
+ if [[ -z "$ACCESS_TOKEN" || "$ACCESS_TOKEN" == "null" || "$ACCESS_TOKEN" == *"REDACTED"* ]]; then
102
+ # CLI ~2.108+ redacts accessToken in `org display` — fall back to the
103
+ # dedicated command that always returns the real token.
104
+ ACCESS_TOKEN=$(echo "y" | sf org auth show-access-token --target-org "$org_alias" --no-prompt --json 2>/dev/null | jq -r '.result.accessToken // empty')
105
+ fi
106
+
107
+ if [[ -z "$ACCESS_TOKEN" || "$ACCESS_TOKEN" == "null" ]]; then
108
+ echo "Cannot extract access token. Re-authenticate: sf org login web --alias $org_alias"
109
+ return 1
110
+ fi
111
+
112
+ export ACCESS_TOKEN
113
+ ACCESS_TOKEN_RESOLVED="1"
114
+ export ACCESS_TOKEN_RESOLVED
115
+ return 0
116
+ }
@@ -0,0 +1,86 @@
1
+ #!/bin/bash
2
+ # Shared cap-diagnosis helpers for the 8-active EnablementCompetencyDef limit.
3
+ #
4
+ # Server-side, EnablementCompetencyDef enforces a hard cap of 8 IsActive=true
5
+ # records per org (CoachingCompetenciesFeature.MAX_ACTIVE_COMPETENCIES). All
6
+ # five write scripts (create-competency, create-custom-competency,
7
+ # toggle-custom-competency, install-ootb-competencies,
8
+ # install-best-practice-competencies) call these helpers to keep the
9
+ # admin-facing cap message identical across the surface.
10
+ #
11
+ # Contract for callers: source this file, then use
12
+ # query_active_count <ORG_ALIAS>
13
+ # print_cap_report <ORG_ALIAS> <CONTEXT>
14
+ # print_cap_footer_activate_hint <NEW_ID_OR_NAME>
15
+ #
16
+ # CONTEXT is a short human phrase used in the header, e.g.
17
+ # "creating '$MASTER_LABEL'" (create paths)
18
+ # "activating '$REC_LABEL'" (toggle paths)
19
+ # "installing OOTB competencies" (installer paths)
20
+ #
21
+ # All queries run through `sf api request rest --target-org` so the org's
22
+ # access token is never passed as curl argv (visible in the process table).
23
+ # Nothing here writes to the org — read-only diagnosis only. The decision to
24
+ # fall back to inactive (custom / installer paths) or refuse outright (toggle
25
+ # path — you can't activate to inactive) is the caller's.
26
+
27
+ MAX_ACTIVE_COMPETENCIES=8
28
+ CAP_TOOLING_BASE="services/data/v68.0/tooling"
29
+
30
+ # Return the current active count (or "0" on any failure — callers should
31
+ # treat a query failure as "cannot verify cap" and continue optimistically;
32
+ # the server-side hard cap still protects them).
33
+ query_active_count() {
34
+ local org_alias="$1"
35
+ (sf api request rest \
36
+ "${CAP_TOOLING_BASE}/query/?q=SELECT+COUNT()+FROM+EnablementCompetencyDef+WHERE+IsActive=true" \
37
+ --target-org "$org_alias" 2>/dev/null || true) \
38
+ | jq -r '.totalSize // 0' 2>/dev/null || echo "0"
39
+ }
40
+
41
+ # Print a rich, admin-actionable cap report to stderr. Lists the currently
42
+ # active competencies with their Ids (deactivate command is copy-pasteable)
43
+ # and explains next steps. Caller supplies the context string that goes at
44
+ # the top ("Cap reached while <context>").
45
+ print_cap_report() {
46
+ local org_alias="$1"
47
+ local context="$2"
48
+
49
+ local list_json
50
+ list_json=$(sf api request rest \
51
+ "${CAP_TOOLING_BASE}/query/?q=SELECT+Id,MasterLabel,DeveloperName,SourceCompetencyTemplate+FROM+EnablementCompetencyDef+WHERE+IsActive=true+ORDER+BY+MasterLabel" \
52
+ --target-org "$org_alias" 2>/dev/null || true)
53
+
54
+ {
55
+ echo ""
56
+ echo "Warning: Active Coaching Competency cap reached while ${context}."
57
+ echo ""
58
+ echo " Salesforce allows up to ${MAX_ACTIVE_COMPETENCIES} active competencies per org."
59
+ echo " This org currently has ${MAX_ACTIVE_COMPETENCIES} active — listed below."
60
+ echo ""
61
+ echo " Currently active:"
62
+ echo "$list_json" | jq -r '.records[]? | " • \(.MasterLabel) (\(.Id))"' 2>/dev/null || echo " (unable to list — the server-side cap check will still enforce this)"
63
+ echo ""
64
+ echo " To free a slot, deactivate one of the above:"
65
+ echo "$list_json" | jq -r '.records[0:1][]? | " ./skills/sales-call-scoring-configure/scripts/toggle-custom-competency.sh <alias> \(.Id) deactivate"' 2>/dev/null
66
+ echo ""
67
+ } >&2
68
+ }
69
+
70
+ # Print the second half of the fallback message (only used by paths that
71
+ # auto-create an inactive record). Explains what just happened and how to
72
+ # flip it on later.
73
+ #
74
+ # Args:
75
+ # $1 = record identifier the admin can copy-paste (Id preferred,
76
+ # MasterLabel acceptable)
77
+ print_cap_footer_activate_hint() {
78
+ local ident="$1"
79
+ {
80
+ echo " The new competency was created INACTIVE so your work isn't blocked."
81
+ echo " Once you deactivate an existing competency, activate the new one:"
82
+ echo ""
83
+ echo " ./skills/sales-call-scoring-configure/scripts/toggle-custom-competency.sh <alias> ${ident} activate"
84
+ echo ""
85
+ } >&2
86
+ }
@@ -0,0 +1,267 @@
1
+ #!/bin/bash
2
+ # Shared library for SOAP Metadata API response parsing
3
+ #
4
+ # Functions:
5
+ # redact_token <input> - Redacts session IDs and Bearer tokens from error messages
6
+ # parse_soap_response <xml> <field> <http_status> - Classifies SOAP responses
7
+ #
8
+ # Returns:
9
+ # "true" - field is enabled
10
+ # "false" - field is explicitly disabled or omitted (setting is off)
11
+ # "AUTH_ERROR:<fault>" - authentication/authorization failure
12
+ # "TYPE_UNAVAILABLE:<fault>" - org lacks license/edition for this feature
13
+ # "WRITE_ERROR:<statusCode>[:<message>]" - updateMetadata returned a well-formed
14
+ # response with <success>false</success> and an <errors> block. The server
15
+ # accepted the request and reports a specific reason for refusal (e.g.
16
+ # INSUFFICIENT_ACCESS_OR_READONLY during a provisioning window). Callers
17
+ # decide whether the statusCode is transient (retryable) or terminal.
18
+ # "NETWORK_ERROR" - network/service error (empty response, 5xx, malformed XML)
19
+
20
+ # Redact session IDs and Bearer tokens from error messages
21
+ # Args: $1 = input string
22
+ # Returns: string with tokens replaced by [REDACTED]
23
+ #
24
+ # Salesforce session IDs start with the 15/18-char org id (00D...) followed by
25
+ # '!' and a signature segment that contains '.', '=', '-', '+' in addition to
26
+ # alphanumerics. Match greedily up to the next whitespace or XML tag boundary
27
+ # ('<') so the whole token tail is redacted, not just its leading run.
28
+ redact_token() {
29
+ local input="$1"
30
+ printf '%s' "$input" | sed 's/00D[^ <]*/[REDACTED]/g; s/Bearer [^ ]*/Bearer [REDACTED]/g'
31
+ }
32
+
33
+ # Parse SOAP Metadata API response and classify the result
34
+ # Args:
35
+ # $1 = raw SOAP XML response
36
+ # $2 = field name to extract (e.g., "enableEinsteinGptPlatform")
37
+ # $3 = HTTP status code (e.g., "200", "503")
38
+ # Returns: classification string (see header comments)
39
+ parse_soap_response() {
40
+ local xml_response="$1"
41
+ local field_name="$2"
42
+ local http_status="$3"
43
+
44
+ # Validate field_name to prevent regex injection in grep patterns below
45
+ if [[ ! "$field_name" =~ ^[A-Za-z0-9_]+$ ]]; then
46
+ echo "NETWORK_ERROR"
47
+ return 0
48
+ fi
49
+
50
+ # Disable pipefail locally to allow grep failures without aborting
51
+ local old_opts="$-"
52
+ set +e
53
+
54
+ # An empty response body is always a network error, regardless of status.
55
+ if [[ -z "$xml_response" ]]; then
56
+ # Restore pipefail before returning
57
+ if [[ "$old_opts" =~ e ]]; then set -e; fi
58
+ echo "NETWORK_ERROR"
59
+ return 0
60
+ fi
61
+
62
+ # Check for SOAP Fault (auth, permission, or type errors) *before* the
63
+ # generic non-2xx-status check below — Salesforce commonly returns real
64
+ # SOAP Faults (invalid session, insufficient access, unlicensed type) with
65
+ # an HTTP 500 status, so gating on status first would silently reclassify
66
+ # every such fault as a generic "network/timeout error" and hide the
67
+ # actual auth/licensing diagnosis from the admin.
68
+ # Handle both standard and namespace-prefixed faults
69
+ local fault_code=""
70
+ if echo "$xml_response" | grep -q "<soapenv:Fault>\\|<sf:Fault>\\|<Fault>"; then
71
+ # Extract faultcode - try multiple patterns for different namespace conventions
72
+ fault_code=$(echo "$xml_response" | grep -o "<faultcode>[^<]*</faultcode>" | sed 's/<[^>]*>//g' | head -1)
73
+ if [[ -z "$fault_code" ]]; then
74
+ fault_code=$(echo "$xml_response" | grep -o "<sf:faultcode>[^<]*</sf:faultcode>" | sed 's/<[^>]*>//g' | head -1)
75
+ fi
76
+ if [[ -z "$fault_code" ]]; then
77
+ # Handle fully-qualified fault codes like "sf:INVALID_SESSION_ID"
78
+ fault_code=$(echo "$xml_response" | grep -oE "faultcode>[^<]*</.*faultcode" | sed 's/<[^>]*>//g' | sed 's/.*://' | head -1)
79
+ fi
80
+
81
+ if [[ -n "$fault_code" ]]; then
82
+ # Extract faultstring for context (will be redacted)
83
+ local fault_string=""
84
+ fault_string=$(echo "$xml_response" | grep -o "<faultstring>[^<]*</faultstring>" | sed 's/<[^>]*>//g' | head -1 || echo "")
85
+ if [[ -z "$fault_string" ]]; then
86
+ fault_string=$(echo "$xml_response" | grep -o "<sf:faultstring>[^<]*</sf:faultstring>" | sed 's/<[^>]*>//g' | head -1 || echo "")
87
+ fi
88
+
89
+ # Redact tokens from fault string
90
+ if [[ -n "$fault_string" ]]; then
91
+ fault_string=$(redact_token "$fault_string")
92
+ fi
93
+
94
+ # Classify by fault type
95
+ # INVALID_TYPE = org lacks license/edition - distinct from auth failures
96
+ if echo "$fault_code" | grep -qi "INVALID_TYPE"; then
97
+ if [[ "$old_opts" =~ e ]]; then set -e; fi
98
+ echo "TYPE_UNAVAILABLE:${fault_code}${fault_string:+:$fault_string}"
99
+ return 0
100
+ fi
101
+
102
+ # All other faults are auth/permission errors
103
+ if [[ "$old_opts" =~ e ]]; then set -e; fi
104
+ echo "AUTH_ERROR:${fault_code}${fault_string:+:$fault_string}"
105
+ return 0
106
+ fi
107
+ fi
108
+
109
+ # No fault matched above — a non-2xx status at this point is a genuine
110
+ # network/service error (not a classifiable SOAP-level failure).
111
+ if [[ "$http_status" != "200" && "$http_status" != "201" ]]; then
112
+ if [[ "$old_opts" =~ e ]]; then set -e; fi
113
+ echo "NETWORK_ERROR"
114
+ return 0
115
+ fi
116
+
117
+ # Check if response is valid XML with the expected structure
118
+ # If it's HTML or malformed XML, treat as network error
119
+ if ! echo "$xml_response" | grep -q "<readMetadataResponse>\\|<updateMetadataResponse>"; then
120
+ if [[ "$old_opts" =~ e ]]; then set -e; fi
121
+ echo "NETWORK_ERROR"
122
+ return 0
123
+ fi
124
+
125
+ # Check for closing envelope tag - if missing, XML is truncated
126
+ if ! echo "$xml_response" | grep -q "</soapenv:Envelope>"; then
127
+ if [[ "$old_opts" =~ e ]]; then set -e; fi
128
+ echo "NETWORK_ERROR"
129
+ return 0
130
+ fi
131
+
132
+ # Well-formed updateMetadataResponse with an <errors> block: the server
133
+ # accepted the request, reports success=false, and names the reason via
134
+ # <statusCode> (e.g. INSUFFICIENT_ACCESS_OR_READONLY when a dependent field
135
+ # is temporarily read-only during ECI's async provisioning window — see
136
+ # W-23968462). Without this branch, the fallback field-value grep below
137
+ # would find <success>false</success> and collapse to bare "false", losing
138
+ # the statusCode the admin needs to distinguish transient from terminal.
139
+ # Scoped to updateMetadataResponse to avoid a false positive on readMetadata
140
+ # responses that happen to contain an <errors> element in metadata content.
141
+ if echo "$xml_response" | grep -q "<updateMetadataResponse>" \
142
+ && echo "$xml_response" | grep -q "<errors>"; then
143
+ local status_code=""
144
+ local err_message=""
145
+ status_code=$(echo "$xml_response" | grep -o "<statusCode>[^<]*</statusCode>" | sed 's/<[^>]*>//g' | head -1)
146
+ err_message=$(echo "$xml_response" | grep -o "<message>[^<]*</message>" | sed 's/<[^>]*>//g' | head -1)
147
+ if [[ -n "$err_message" ]]; then
148
+ err_message=$(redact_token "$err_message")
149
+ fi
150
+ if [[ -n "$status_code" ]]; then
151
+ if [[ "$old_opts" =~ e ]]; then set -e; fi
152
+ echo "WRITE_ERROR:${status_code}${err_message:+:$err_message}"
153
+ return 0
154
+ fi
155
+ fi
156
+
157
+ # Extract field value - try to find <field>true/false</field>
158
+ # head -1 collapses multi-record responses to the first match so two records
159
+ # never produce a two-line value that fails the == comparisons below.
160
+ local field_value=""
161
+ field_value=$(echo "$xml_response" | grep -o "<${field_name}>[^<]*</${field_name}>" | grep -o "true\\|false" | head -1 || echo "")
162
+
163
+ if [[ "$field_value" == "true" ]]; then
164
+ if [[ "$old_opts" =~ e ]]; then set -e; fi
165
+ echo "true"
166
+ return 0
167
+ fi
168
+
169
+ # An update call reporting success=false has no SOAP fault, but the
170
+ # response still carries an <errors> block (statusCode + message) with the
171
+ # actual reason — e.g. INSUFFICIENT_ACCESS_OR_READONLY. Surface it instead
172
+ # of silently collapsing to a bare "false" the caller can't act on.
173
+ if [[ "$field_name" == "success" && "$field_value" == "false" ]]; then
174
+ local err_status_code="" err_message=""
175
+ err_status_code=$(echo "$xml_response" | grep -o "<statusCode>[^<]*</statusCode>" | sed 's/<[^>]*>//g' | head -1)
176
+ if [[ -z "$err_status_code" ]]; then
177
+ err_status_code=$(echo "$xml_response" | grep -o "<sf:statusCode>[^<]*</sf:statusCode>" | sed 's/<[^>]*>//g' | head -1)
178
+ fi
179
+ err_message=$(echo "$xml_response" | grep -o "<message>[^<]*</message>" | sed 's/<[^>]*>//g' | head -1)
180
+ if [[ -z "$err_message" ]]; then
181
+ err_message=$(echo "$xml_response" | grep -o "<sf:message>[^<]*</sf:message>" | sed 's/<[^>]*>//g' | head -1)
182
+ fi
183
+ if [[ -n "$err_message" ]]; then
184
+ err_message=$(redact_token "$err_message")
185
+ fi
186
+ if [[ -n "$err_status_code" || -n "$err_message" ]]; then
187
+ if [[ "$old_opts" =~ e ]]; then set -e; fi
188
+ echo "UPDATE_REJECTED:${err_status_code:-UNKNOWN}${err_message:+:$err_message}"
189
+ return 0
190
+ fi
191
+ fi
192
+
193
+ # If field is explicitly false OR field is omitted entirely, treat as disabled.
194
+ # Salesforce omits boolean settings fields when they're off rather than
195
+ # emitting <field>false</field>, so an absent field inside an otherwise valid
196
+ # readMetadataResponse is the disabled state, not an error.
197
+ if [[ "$field_value" == "false" ]] || [[ -z "$field_value" ]]; then
198
+ if [[ "$old_opts" =~ e ]]; then set -e; fi
199
+ echo "false"
200
+ return 0
201
+ fi
202
+
203
+ # Shouldn't reach here, but if we do, treat as network error (unexpected response format)
204
+ if [[ "$old_opts" =~ e ]]; then set -e; fi
205
+ echo "NETWORK_ERROR"
206
+ return 0
207
+ }
208
+
209
+ # Issues one SOAP Metadata API call. Requires $SOAP_ENDPOINT and
210
+ # $CURL_TIMEOUT to be set by the caller (see enable-call-scoring.sh /
211
+ # install-ootb-competencies.sh for the standard setup).
212
+ # Args: $1 = SOAPAction, $2 = request body
213
+ # Body is piped via stdin (-d @-) rather than argv so the SOAP sessionId
214
+ # — which embeds the raw ACCESS_TOKEN — is not visible in the OS process
215
+ # table (ps / /proc/*/cmdline). See W-23934530 / W-23974869.
216
+ soap_call() {
217
+ local action="$1"
218
+ local body="$2"
219
+ # `|| true` — a curl failure (timeout, DNS, refused connection) must not
220
+ # abort the script via set -e; empty output + empty status here falls
221
+ # through to parse_soap_response's NETWORK_ERROR classification below.
222
+ printf '%s' "$body" | curl -s -w '\nHTTPSTATUS:%{http_code}' --max-time "$CURL_TIMEOUT" -X POST "$SOAP_ENDPOINT" \
223
+ -H "Content-Type: text/xml; charset=utf-8" \
224
+ -H "SOAPAction: $action" \
225
+ --data-binary @- 2>/dev/null || true
226
+ }
227
+
228
+ # Splits a soap_call() response into HTTP status + body and classifies one
229
+ # field via parse_soap_response. Unlike a plain grep, this distinguishes
230
+ # *why* a read/update didn't come back "true": an explicit false/absent
231
+ # field, a SOAP fault (auth/permission), an org that isn't licensed for the
232
+ # type (INVALID_TYPE), or a network/timeout failure.
233
+ # Args: $1 = soap_call() response, $2 = field name
234
+ classify_field() {
235
+ local combined="$1"
236
+ local field="$2"
237
+ local http_status body
238
+ # `|| true` on the grep — an empty/malformed $combined (curl failure) has
239
+ # no HTTPSTATUS marker to match, and under pipefail that would otherwise
240
+ # abort the script instead of yielding an empty status for NETWORK_ERROR.
241
+ http_status=$( (echo "$combined" | grep -o 'HTTPSTATUS:[0-9]*$' || true) | cut -d: -f2)
242
+ body=$(echo "$combined" | sed 's/HTTPSTATUS:[0-9]*$//')
243
+ parse_soap_response "$body" "$field" "$http_status"
244
+ }
245
+
246
+ # Normalizes a classify_field() result to true/false, or "" for anything
247
+ # that isn't a clean field read (fault/error) — callers that need the fault
248
+ # detail should inspect the raw classification themselves.
249
+ normalize_class() {
250
+ case "$1" in
251
+ true|false) echo "$1" ;;
252
+ *) echo "" ;;
253
+ esac
254
+ }
255
+
256
+ # Turns a classify_field() classification into a one-line, token-redacted,
257
+ # admin-safe explanation of why a call failed.
258
+ describe_failure() {
259
+ case "$1" in
260
+ AUTH_ERROR:*) echo "authentication/permission error — ${1#AUTH_ERROR:}" ;;
261
+ TYPE_UNAVAILABLE:*) echo "org isn't licensed for this setting — ${1#TYPE_UNAVAILABLE:}" ;;
262
+ WRITE_ERROR:*) echo "server refused the change — ${1#WRITE_ERROR:}" ;;
263
+ NETWORK_ERROR*) echo "network/timeout error reaching Salesforce — try again" ;;
264
+ false) echo "call succeeded but reported failure (no SOAP fault returned)" ;;
265
+ *) echo "unexpected response: $1" ;;
266
+ esac
267
+ }
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bash
2
+ # soql.sh — SOQL string-literal escaping.
3
+ #
4
+ # soql_escape RAW → SAFE
5
+ # Prints RAW with every character that has meaning inside a SOQL string
6
+ # literal escaped so the caller can wrap the result in single quotes:
7
+ #
8
+ # WHERE MasterLabel='$(soql_escape "$LABEL")'
9
+ #
10
+ # Escapes, in order:
11
+ # \ → \\ (must happen first)
12
+ # ' → \'
13
+ #
14
+ # Order matters. If the quote-pass ran first, the backslash-pass would
15
+ # double-escape the backslash inserted for quotes and re-open the string.
16
+ #
17
+ # Implemented with sed rather than bash parameter expansion. The natural
18
+ # "${var//\'/\\\'}" pattern silently emits TWO backslashes in the
19
+ # replacement because of how backslashes are consumed by both the shell
20
+ # scanner and the parameter-expansion replacement string. sed sidesteps
21
+ # that quirk and its escape rules match SOQL's directly.
22
+
23
+ soql_escape() {
24
+ printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e "s/'/\\\\'/g"
25
+ }