@salesforce/afv-skills 1.47.0 → 1.49.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 (160) hide show
  1. package/package.json +1 -1
  2. package/skills/education-cloud-domain-configure/SKILL.md +234 -0
  3. package/skills/education-cloud-domain-configure/references/dependent-features.md +198 -0
  4. package/skills/education-cloud-domain-configure/references/domain-features/academic-operations.md +386 -0
  5. package/skills/education-cloud-domain-configure/references/domain-features/alumni-relations.md +155 -0
  6. package/skills/education-cloud-domain-configure/references/domain-features/cross-domain-dependencies.md +18 -0
  7. package/skills/education-cloud-domain-configure/references/domain-features/industriessettings-toggle-reference.md +43 -0
  8. package/skills/education-cloud-domain-configure/references/domain-features/mentoring.md +198 -0
  9. package/skills/education-cloud-domain-configure/references/domain-features/recruitment-admissions.md +315 -0
  10. package/skills/education-cloud-domain-configure/references/domain-features/student-management.md +155 -0
  11. package/skills/education-cloud-domain-configure/references/domain-features/student-success.md +249 -0
  12. package/skills/education-cloud-domain-configure/references/domain-features.md +27 -0
  13. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +23 -0
  14. package/skills/education-cloud-domain-configure/references/toggle-write-path.md +67 -0
  15. package/skills/education-cloud-domain-configure/scripts/check_license_provisioned.py +62 -0
  16. package/skills/education-cloud-domain-configure/scripts/check_org_edition.py +44 -0
  17. package/skills/education-cloud-domain-configure/scripts/check_permission_assigned.py +39 -0
  18. package/skills/education-cloud-multi-campus-configure/SKILL.md +2 -0
  19. package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +4 -3
  20. package/skills/service-agentforce-channel-configure/SKILL.md +33 -28
  21. package/skills/service-agentforce-human-escalation-configure/SKILL.md +205 -0
  22. package/skills/service-agentforce-human-escalation-configure/assets/escalation-thresholds.instructions.md +37 -0
  23. package/skills/service-agentforce-human-escalation-configure/assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml +173 -0
  24. package/skills/service-agentforce-human-escalation-configure/assets/package.xml +8 -0
  25. package/skills/service-agentforce-human-escalation-configure/assets/sfdx-project.json +5 -0
  26. package/skills/service-agentforce-human-escalation-configure/references/classifier-contracts.md +103 -0
  27. package/skills/service-agentforce-human-escalation-configure/references/escalation-mechanism.md +63 -0
  28. package/skills/service-agentforce-human-escalation-configure/references/failure-policy.md +44 -0
  29. package/skills/service-agentforce-human-escalation-configure/references/nga-escalation.md +57 -0
  30. package/skills/service-agentforce-human-escalation-configure/references/runtime-verification.md +72 -0
  31. package/skills/service-agentforce-human-escalation-configure/scripts/classify-agent-active.mjs +49 -0
  32. package/skills/service-agentforce-human-escalation-configure/scripts/classify-nga-escalation.mjs +166 -0
  33. package/skills/service-agentforce-human-escalation-configure/scripts/extract-outbound-route.mjs +67 -0
  34. package/skills/service-agentforce-human-escalation-configure/scripts/patch-escalation-surfaces.mjs +131 -0
  35. package/skills/service-agentforce-human-escalation-configure/scripts/tests/__init__.py +0 -0
  36. package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +249 -0
  37. package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +899 -0
  38. package/skills/service-agentforce-human-escalation-configure/scripts/verify-and-configure.sh +705 -0
  39. package/skills/service-agentforce-human-escalation-configure/scripts/verify-escalation-config.mjs +89 -0
  40. package/skills/service-de-channel-routing-configure/SKILL.md +13 -5
  41. package/skills/service-de-channel-routing-configure/scripts/tests/test_skill_contract.py +25 -0
  42. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +23 -10
  43. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -48
  44. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +6 -2
  45. package/skills/service-omni-agent-users-create/SKILL.md +96 -0
  46. package/skills/service-omni-agent-users-create/assets/create-users.apex.template +80 -0
  47. package/skills/service-omni-agent-users-create/references/apex-patterns.md +135 -0
  48. package/skills/service-omni-agent-users-create/references/apex-template-notes.md +40 -0
  49. package/skills/service-omni-agent-users-create/scripts/detect-and-create.sh +206 -0
  50. package/skills/service-omni-agent-users-create/scripts/detect-existing.sh +122 -0
  51. package/skills/service-omni-agent-users-create/scripts/run-create.sh +298 -0
  52. package/skills/service-omni-agent-users-create/scripts/tests/test_user_create_security.py +171 -0
  53. package/skills/service-omni-agent-work-sharing-configure/SKILL.md +78 -0
  54. package/skills/service-omni-agent-work-sharing-configure/references/api-notes.md +31 -0
  55. package/skills/service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh +186 -0
  56. package/skills/service-omni-agent-work-sharing-configure/scripts/tests/_bootstrap.py +120 -0
  57. package/skills/service-omni-agent-work-sharing-configure/scripts/tests/test_agent_work_sharing_contracts.py +93 -0
  58. package/skills/service-omni-base-settings-configure/SKILL.md +77 -0
  59. package/skills/service-omni-base-settings-configure/assets/force-app/main/default/settings/OmniChannel.settings-meta.xml +8 -0
  60. package/skills/service-omni-base-settings-configure/assets/package.xml +8 -0
  61. package/skills/service-omni-base-settings-configure/assets/sfdx-project.json +12 -0
  62. package/skills/service-omni-base-settings-configure/references/api-notes.md +121 -0
  63. package/skills/service-omni-base-settings-configure/scripts/configure-and-report.sh +164 -0
  64. package/skills/service-omni-base-settings-configure/scripts/detect-existing.sh +69 -0
  65. package/skills/service-omni-channel-setup-coordinate/SKILL.md +168 -0
  66. package/skills/service-omni-channel-setup-coordinate/examples/output-templates.md +161 -0
  67. package/skills/service-omni-channel-setup-coordinate/references/gap-catalog.md +99 -0
  68. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +1542 -0
  69. package/skills/service-omni-channel-setup-coordinate/scripts/tests/__init__.py +0 -0
  70. package/skills/service-omni-channel-setup-coordinate/scripts/tests/_bootstrap.py +223 -0
  71. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +804 -0
  72. package/skills/service-omni-command-center-analyze/SKILL.md +84 -0
  73. package/skills/service-omni-command-center-analyze/references/api-notes.md +54 -0
  74. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +198 -0
  75. package/skills/service-omni-command-center-analyze/scripts/tests/_bootstrap.py +115 -0
  76. package/skills/service-omni-command-center-analyze/scripts/tests/test_command_center_analyze_contracts.py +165 -0
  77. package/skills/service-omni-permission-set-assign/SKILL.md +88 -0
  78. package/skills/service-omni-permission-set-assign/assets/package.xml +8 -0
  79. package/skills/service-omni-permission-set-assign/references/api-notes.md +143 -0
  80. package/skills/service-omni-permission-set-assign/scripts/verify-and-assign.sh +397 -0
  81. package/skills/service-omni-presence-status-deploy/SKILL.md +87 -0
  82. package/skills/service-omni-presence-status-deploy/assets/package.xml +12 -0
  83. package/skills/service-omni-presence-status-deploy/references/api-notes.md +115 -0
  84. package/skills/service-omni-presence-status-deploy/scripts/deploy-and-report.sh +422 -0
  85. package/skills/service-omni-presence-user-config-deploy/SKILL.md +80 -0
  86. package/skills/service-omni-presence-user-config-deploy/references/api-notes.md +60 -0
  87. package/skills/service-omni-presence-user-config-deploy/scripts/deploy-and-report.sh +261 -0
  88. package/skills/service-omni-presence-user-config-deploy/scripts/tests/_bootstrap.py +106 -0
  89. package/skills/service-omni-presence-user-config-deploy/scripts/tests/test_presence_user_config_contracts.py +142 -0
  90. package/skills/service-omni-queue-deploy/SKILL.md +91 -0
  91. package/skills/service-omni-queue-deploy/references/api-notes.md +140 -0
  92. package/skills/service-omni-queue-deploy/scripts/verify-and-align.sh +470 -0
  93. package/skills/service-omni-queue-members-assign/SKILL.md +93 -0
  94. package/skills/service-omni-queue-members-assign/references/api-notes.md +144 -0
  95. package/skills/service-omni-queue-members-assign/scripts/tests/_bootstrap.py +67 -0
  96. package/skills/service-omni-queue-members-assign/scripts/tests/test_queue_members_contracts.py +73 -0
  97. package/skills/service-omni-queue-members-assign/scripts/verify-and-bind.sh +362 -0
  98. package/skills/service-omni-queue-routing-config-deploy/SKILL.md +101 -0
  99. package/skills/service-omni-queue-routing-config-deploy/references/api-notes.md +127 -0
  100. package/skills/service-omni-queue-routing-config-deploy/scripts/tests/_bootstrap.py +90 -0
  101. package/skills/service-omni-queue-routing-config-deploy/scripts/tests/test_queue_routing_config_contracts.py +70 -0
  102. package/skills/service-omni-queue-routing-config-deploy/scripts/upsert-and-report.sh +538 -0
  103. package/skills/service-omni-routing-flow-deploy/SKILL.md +111 -0
  104. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.SkillsBased.flow-meta.xml +94 -0
  105. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.flow-meta.xml +88 -0
  106. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Cases.flow-meta.xml +144 -0
  107. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.SkillsBased.flow-meta.xml +94 -0
  108. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.flow-meta.xml +88 -0
  109. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCalls.flow-meta.xml +144 -0
  110. package/skills/service-omni-routing-flow-deploy/assets/package.xml +11 -0
  111. package/skills/service-omni-routing-flow-deploy/assets/sfdx-project.json +11 -0
  112. package/skills/service-omni-routing-flow-deploy/references/api-notes.md +265 -0
  113. package/skills/service-omni-routing-flow-deploy/scripts/deploy-and-report.sh +684 -0
  114. package/skills/service-omni-service-channel-configure/SKILL.md +83 -0
  115. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Cases.serviceChannel-meta.xml +9 -0
  116. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Incidents.serviceChannel-meta.xml +9 -0
  117. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/MessagingSessions.serviceChannel-meta.xml +9 -0
  118. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/VoiceCalls.serviceChannel-meta.xml +9 -0
  119. package/skills/service-omni-service-channel-configure/assets/package.xml +11 -0
  120. package/skills/service-omni-service-channel-configure/assets/sfdx-project.json +12 -0
  121. package/skills/service-omni-service-channel-configure/references/api-notes.md +84 -0
  122. package/skills/service-omni-service-channel-configure/scripts/deploy-and-report.sh +383 -0
  123. package/skills/service-omni-sidebar-configure/SKILL.md +77 -0
  124. package/skills/service-omni-sidebar-configure/references/api-notes.md +51 -0
  125. package/skills/service-omni-sidebar-configure/scripts/enable-and-report.sh +315 -0
  126. package/skills/service-omni-sidebar-configure/scripts/tests/_bootstrap.py +142 -0
  127. package/skills/service-omni-sidebar-configure/scripts/tests/test_sidebar_contracts.py +139 -0
  128. package/skills/service-omni-skills-based-routing-configure/SKILL.md +81 -0
  129. package/skills/service-omni-skills-based-routing-configure/references/api-notes.md +50 -0
  130. package/skills/service-omni-skills-based-routing-configure/scripts/configure-and-report.sh +270 -0
  131. package/skills/service-omni-skills-based-routing-configure/scripts/tests/_bootstrap.py +120 -0
  132. package/skills/service-omni-skills-based-routing-configure/scripts/tests/test_sbr_contracts.py +129 -0
  133. package/skills/service-omni-supervisor-config-deploy/SKILL.md +97 -0
  134. package/skills/service-omni-supervisor-config-deploy/assets/force-app/main/default/omniSupervisorConfigs/Omni_Supervisor.omniSupervisorConfig-meta.xml +9 -0
  135. package/skills/service-omni-supervisor-config-deploy/assets/package.xml +8 -0
  136. package/skills/service-omni-supervisor-config-deploy/assets/sfdx-project.json +12 -0
  137. package/skills/service-omni-supervisor-config-deploy/references/api-notes.md +205 -0
  138. package/skills/service-omni-supervisor-config-deploy/scripts/deploy-and-report.sh +586 -0
  139. package/skills/service-omni-supervisor-config-deploy/scripts/tests/_bootstrap.py +153 -0
  140. package/skills/service-omni-supervisor-config-deploy/scripts/tests/test_supervisor_config_contracts.py +116 -0
  141. package/skills/service-omni-supervisor-permset-assign/SKILL.md +88 -0
  142. package/skills/service-omni-supervisor-permset-assign/references/api-notes.md +48 -0
  143. package/skills/service-omni-supervisor-permset-assign/scripts/verify-and-assign.sh +316 -0
  144. package/skills/service-omni-supervisor-surface-deploy/SKILL.md +85 -0
  145. package/skills/service-omni-supervisor-surface-deploy/references/api-notes.md +47 -0
  146. package/skills/service-omni-supervisor-surface-deploy/scripts/deploy-and-report.sh +216 -0
  147. package/skills/service-omni-supervisor-surface-deploy/scripts/tests/_bootstrap.py +95 -0
  148. package/skills/service-omni-supervisor-surface-deploy/scripts/tests/test_supervisor_surface_contracts.py +154 -0
  149. package/skills/service-omni-supervisor-users-create/SKILL.md +92 -0
  150. package/skills/service-omni-supervisor-users-create/assets/create-supervisors.apex.template +72 -0
  151. package/skills/service-omni-supervisor-users-create/references/apex-patterns.md +73 -0
  152. package/skills/service-omni-supervisor-users-create/references/apex-template-notes.md +41 -0
  153. package/skills/service-omni-supervisor-users-create/scripts/detect-and-create.sh +197 -0
  154. package/skills/service-omni-supervisor-users-create/scripts/detect-existing.sh +112 -0
  155. package/skills/service-omni-supervisor-users-create/scripts/run-create.sh +296 -0
  156. package/skills/service-omni-work-skill-routing-configure/SKILL.md +80 -0
  157. package/skills/service-omni-work-skill-routing-configure/references/api-notes.md +67 -0
  158. package/skills/service-omni-work-skill-routing-configure/scripts/configure-and-report.sh +262 -0
  159. package/skills/service-omni-work-skill-routing-configure/scripts/tests/_bootstrap.py +121 -0
  160. package/skills/service-omni-work-skill-routing-configure/scripts/tests/test_work_skill_routing_contracts.py +161 -0
@@ -0,0 +1,73 @@
1
+ # service-omni-supervisor-users-create — Apex patterns
2
+
3
+ Load this reference **only when the Apex template needs modification or debugging**. Under normal operation `run-create.sh` handles everything.
4
+
5
+ ## Why Anonymous Apex over REST User POST
6
+
7
+ Two forcing constraints:
8
+
9
+
10
+ 1. **`System.setPassword()` is Apex-only.** REST POST creates the user but requires an email-based password reset flow. That's a UX blocker for bootstrap scenarios.
11
+ 2. **In-transaction re-check for concurrency.** If two operators run this skill simultaneously against the same org, REST POSTs race and one hits `DUPLICATE_USERNAME`. Apex re-queries inside the transaction and skips already-existing indexes atomically.
12
+
13
+ ## Username pattern
14
+
15
+ `supervisor{i}.<orgsuffix>@example.com`
16
+
17
+ - `i` is 1..N (max 5 for supervisors)
18
+ - `orgsuffix` is deterministic — the last 8 chars of `Organization.Id`, lowercased
19
+ - `@example.com` is a reserved TLD, safe to use per RFC 2606
20
+
21
+ Deterministic pattern ⇒ any operator, any subsequent run, any skill can query for existing supervisors without shared state.
22
+
23
+ ## Alias construction
24
+
25
+ Salesforce Alias field: **max 8 chars, alphanumeric**.
26
+
27
+ Format: `omsu{i}{last-3-of-suffix}`
28
+
29
+ - `omsu` prefix identifies these as Omni Supervisor agent users
30
+ - `i` is 1 char (1..9 max — 5 supervisors max ensures we never hit 10+)
31
+ - 3 trailing suffix chars give per-org uniqueness
32
+ - Total: 8 chars exactly
33
+
34
+ ## Password pattern
35
+
36
+ `SuprV1<10-char-random-hex>` — 16 chars total.
37
+
38
+ Guaranteed to satisfy Salesforce's default password policy:
39
+ - ≥1 uppercase letter (`S`, `V`)
40
+ - ≥1 lowercase letter (from random)
41
+ - ≥1 digit (`1`)
42
+ - ≥1 special char (none required by default)
43
+ - ≥8 chars total (16)
44
+
45
+ Random source: `Crypto.generateAesKey(128)` → hex encode → first 10 chars.
46
+
47
+ ## Standard User profile
48
+
49
+ **Why Standard User instead of a Supervisor-specific profile?**
50
+
51
+ 1. **All standard editions ship it.** Custom profiles differ per org edition.
52
+ 2. **Supervisor perms live in a PermissionSet** (`Omni_Supervisor`, assigned by `service-omni-supervisor-permset-assign`), not in the profile.
53
+ 3. **Consistent with agent skill.** Same profile, different permset. Simpler mental model for reviewers.
54
+
55
+ ## Debug log parsing
56
+
57
+ Each successful user creation emits exactly one line to the Apex debug log:
58
+
59
+ ```text
60
+ SUPERVISOR_CREATED|<userId>|<username>|<password>|<email>
61
+ ```
62
+
63
+ `run-create.sh` greps this pattern with a strict regex anchored on the User Id shape (`005` + 15 alphanumerics = 18 chars) to filter out source-code echoes and comment lines that might appear in the log.
64
+
65
+ ## Failure modes surfaced to the skill-report
66
+
67
+ | Symptom | Cause | User-friendly translation |
68
+ |---------|-------|--------------------------|
69
+ | `LICENSE_LIMIT_EXCEEDED` | Org is out of user license slots | "Org has reached its user license limit. Free up a license slot or request a larger CDO." |
70
+ | `DUPLICATE_USERNAME` | Global collision (rare — deterministic suffix should prevent) | "Retry the skill; if it persists, escalate." |
71
+ | `INVALID_EMAIL_ADDRESS` | Should never happen with @example.com | "Bug in the skill template — escalate." |
72
+ | `INSUFFICIENT_ACCESS` | SF CLI user is not admin | "Ensure the authenticated user is System Administrator." |
73
+ | `INVALID_SESSION_ID` | Session expired | "Re-authenticate: sf org login web -a <alias>" |
@@ -0,0 +1,41 @@
1
+ # `create-supervisors.apex.template` notes
2
+
3
+ Anonymous Apex template for creating supervisor demo users. `run-create.sh` resolves the
4
+ tokens before execution and submits the rendered Apex.
5
+
6
+ ## Tokens
7
+
8
+ | token | meaning |
9
+ | --- | --- |
10
+ | `__COUNT__` | integer 1..5 — number of supervisor slots |
11
+ | `__PROFILE_ID__` | 18-char Profile Id (typically Standard User) |
12
+ | `__SUFFIX__` | 8-char lowercase hex string derived from `Organization.Id` |
13
+
14
+ ## Idempotency
15
+
16
+ The block re-queries existing users inside the transaction and inserts only the truly
17
+ missing indexes. Two concurrent runs can both pass the pre-query before either commits;
18
+ Salesforce's `DUPLICATE_USERNAME` constraint is the authoritative deduplicator, and
19
+ `run-create.sh` treats `DUPLICATE_USERNAME` as "another agent won the race" and retries a
20
+ re-detect.
21
+
22
+ ## Security note (password / TraceFlag)
23
+
24
+ This block only creates the `User` rows and never emits a password via `System.debug`. The
25
+ caller (`run-create.sh`) sets each password with a SEPARATE Anonymous Apex
26
+ `System.setPassword(id, pw)` submission per newly-created user. That plaintext lives in the
27
+ submitted Apex source, which appears in the inline `executeAnonymous` debug log and — ONLY
28
+ when a debug-log TraceFlag is active for the running user — in a queryable `ApexLog` row.
29
+ `run-create.sh` fails closed BEFORE the first `System.setPassword`: if it cannot prove no
30
+ active TraceFlag exists, it sets no password (the user is left `reset_required`) rather than
31
+ risk a queryable password log.
32
+
33
+ ## Output contract
34
+
35
+ For every user created this run, `System.debug` emits one line:
36
+
37
+ ```text
38
+ SUPERVISOR_CREATED|<userId>|<username>|<email>
39
+ ```
40
+
41
+ which `run-create.sh` greps from the log to build the skill's JSON return value.
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env bash
2
+ # detect-and-create.sh - canonical entry point: chains detect-existing.sh → run-create.sh (plan|run), idempotent, production-guarded. Args, JSON status contract, exit codes: SKILL.md.
3
+
4
+ set -euo pipefail
5
+
6
+ if [ $# -lt 2 ]; then
7
+ echo '{"status":"blocked","blocking_issue":"Missing required args","usage":"bash detect-and-create.sh <plan|run> <org-alias> [count=3] [profile-name=\"Standard User\"]"}' >&2
8
+ exit 1
9
+ fi
10
+
11
+ MODE="$1"
12
+ ORG="$2"
13
+ COUNT="${3:-3}"
14
+ PROFILE_NAME="${4:-Standard User}"
15
+
16
+ case "$MODE" in
17
+ plan|run) ;;
18
+ *)
19
+ echo "{\"status\":\"blocked\",\"blocking_issue\":\"Invalid mode '$MODE'. Expected 'plan' or 'run'.\"}" >&2
20
+ exit 1
21
+ ;;
22
+ esac
23
+
24
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
25
+ DETECT_SCRIPT="$SCRIPT_DIR/detect-existing.sh"
26
+ CREATE_SCRIPT="$SCRIPT_DIR/run-create.sh"
27
+
28
+ if [ ! -f "$DETECT_SCRIPT" ]; then
29
+ echo "{\"status\":\"blocked\",\"blocking_issue\":\"Sibling script not found: $DETECT_SCRIPT\"}" >&2
30
+ exit 1
31
+ fi
32
+ if [ "$MODE" = "run" ] && [ ! -f "$CREATE_SCRIPT" ]; then
33
+ echo "{\"status\":\"blocked\",\"blocking_issue\":\"Sibling script not found: $CREATE_SCRIPT\"}" >&2
34
+ exit 1
35
+ fi
36
+
37
+ # Step 1: detect
38
+ DETECT_EXIT=0
39
+ DETECT_JSON=$(bash "$DETECT_SCRIPT" "$ORG" "$COUNT" "$PROFILE_NAME" 2>&1) || DETECT_EXIT=$?
40
+
41
+ if [ "$DETECT_EXIT" -ne 0 ]; then
42
+ jq -n --arg raw "$DETECT_JSON" --argjson exit "$DETECT_EXIT" \
43
+ '{status:"blocked",blocking_issue:("detect-existing.sh exited " + ($exit|tostring)),detect_raw:$raw,create:null}'
44
+ exit 1
45
+ fi
46
+
47
+ if ! echo "$DETECT_JSON" | jq -e . >/dev/null 2>&1; then
48
+ jq -n --arg raw "$DETECT_JSON" \
49
+ '{status:"blocked",blocking_issue:"detect-existing.sh produced non-JSON output",detect_raw:$raw,create:null}'
50
+ exit 1
51
+ fi
52
+
53
+ MISSING_COUNT=$(echo "$DETECT_JSON" | jq -r '.missing_count // 0')
54
+ SAFE_TO_WRITE=$(echo "$DETECT_JSON" | jq -r '.safe_to_write // false')
55
+ PROFILE_ID=$(echo "$DETECT_JSON" | jq -r '.profile_id // ""')
56
+ ORG_SUFFIX=$(echo "$DETECT_JSON" | jq -r '.org_suffix // ""')
57
+ EXISTING_COUNT=$(echo "$DETECT_JSON" | jq -r '.existing_users | length')
58
+ # A deactivated user in a slot is neither reusable nor recreatable (globally-unique username);
59
+ # surface it as a manual action so a dead slot never passes as clean.
60
+ INACTIVE_COUNT=$(echo "$DETECT_JSON" | jq -r '.inactive_count // 0')
61
+ INACTIVE_ACTION=""
62
+ if [ "$INACTIVE_COUNT" -gt 0 ]; then
63
+ INACTIVE_ACTION="$INACTIVE_COUNT supervisor slot(s) are occupied by DEACTIVATED users (see inactive_users). Their usernames are globally unique so they cannot be recreated - reactivate them in Setup → Users, or free the usernames, before those slots can be used."
64
+ fi
65
+
66
+ # Step 2: early returns. Nothing missing => "reused" (or action_needed if a dead slot is present).
67
+ if [ "$MISSING_COUNT" = "0" ]; then
68
+ if [ "$INACTIVE_COUNT" -gt 0 ]; then
69
+ jq -n \
70
+ --argjson detect "$DETECT_JSON" \
71
+ --argjson reused_count "$EXISTING_COUNT" \
72
+ --arg action "$INACTIVE_ACTION" \
73
+ '{
74
+ status: "action_needed",
75
+ action_required: $action,
76
+ detect: $detect,
77
+ create: null,
78
+ created_count: 0,
79
+ reused_count: $reused_count,
80
+ total_present_after: $reused_count,
81
+ safe_to_write: $detect.safe_to_write
82
+ }'
83
+ exit 0
84
+ fi
85
+ jq -n \
86
+ --argjson detect "$DETECT_JSON" \
87
+ --argjson reused_count "$EXISTING_COUNT" \
88
+ '{
89
+ status: "reused",
90
+ detect: $detect,
91
+ create: null,
92
+ created_count: 0,
93
+ reused_count: $reused_count,
94
+ total_present_after: $reused_count,
95
+ safe_to_write: $detect.safe_to_write
96
+ }'
97
+ exit 0
98
+ fi
99
+
100
+ # --plan mode: never create. Report action_needed with the missing indexes (and any dead slots).
101
+ if [ "$MODE" = "plan" ]; then
102
+ jq -n \
103
+ --argjson detect "$DETECT_JSON" \
104
+ --argjson reused_count "$EXISTING_COUNT" \
105
+ --argjson missing_count "$MISSING_COUNT" \
106
+ --arg inactive_action "$INACTIVE_ACTION" \
107
+ '{
108
+ status: "action_needed",
109
+ action_required: (
110
+ "Missing " + ($missing_count|tostring) + " user(s) at indexes " + ($detect.missing_indexes | map(tostring) | join(",")) + ". Re-run in --run mode to create."
111
+ + (if $inactive_action == "" then "" else " ALSO: " + $inactive_action end)
112
+ ),
113
+ detect: $detect,
114
+ create: null,
115
+ created_count: 0,
116
+ reused_count: $reused_count,
117
+ total_present_after: $reused_count,
118
+ safe_to_write: $detect.safe_to_write
119
+ }'
120
+ exit 0
121
+ fi
122
+
123
+ # Step 3: --run mode with missing users - safe_to_write guard, then create.
124
+ if [ "$SAFE_TO_WRITE" != "true" ]; then
125
+ jq -n --argjson detect "$DETECT_JSON" \
126
+ '{status:"blocked",blocking_issue:"safe_to_write=false - refusing to create users on a production customer org.",detect:$detect,create:null,created_count:0,reused_count:0,total_present_after:0,safe_to_write:false}'
127
+ exit 1
128
+ fi
129
+
130
+ if [ -z "$PROFILE_ID" ] || [ -z "$ORG_SUFFIX" ]; then
131
+ jq -n --argjson detect "$DETECT_JSON" \
132
+ '{status:"blocked",blocking_issue:"detect-existing.sh did not resolve profile_id or org_suffix - cannot invoke create.",detect:$detect,create:null,created_count:0,reused_count:0,total_present_after:0,safe_to_write:$detect.safe_to_write}'
133
+ exit 1
134
+ fi
135
+
136
+ CREATE_EXIT=0
137
+ CREATE_JSON=$(bash "$CREATE_SCRIPT" "$ORG" "$COUNT" "$PROFILE_ID" "$ORG_SUFFIX" 2>&1) || CREATE_EXIT=$?
138
+
139
+ if [ "$CREATE_EXIT" -ne 0 ]; then
140
+ jq -n --argjson detect "$DETECT_JSON" --arg raw "$CREATE_JSON" --argjson exit "$CREATE_EXIT" \
141
+ '{status:"blocked",blocking_issue:("run-create.sh exited " + ($exit|tostring)),detect:$detect,create:null,create_raw:$raw,created_count:0,reused_count:0,total_present_after:0,safe_to_write:$detect.safe_to_write}'
142
+ exit 1
143
+ fi
144
+
145
+ if ! echo "$CREATE_JSON" | jq -e . >/dev/null 2>&1; then
146
+ jq -n --argjson detect "$DETECT_JSON" --arg raw "$CREATE_JSON" \
147
+ '{status:"blocked",blocking_issue:"run-create.sh produced non-JSON output",detect:$detect,create:null,create_raw:$raw,created_count:0,reused_count:0,total_present_after:0,safe_to_write:$detect.safe_to_write}'
148
+ exit 1
149
+ fi
150
+
151
+ CREATED_COUNT=$(echo "$CREATE_JSON" | jq -r '.created_count // 0')
152
+ ROLLED_BACK_COUNT=$(echo "$CREATE_JSON" | jq -r '.rolled_back_users | length')
153
+ # A created user without a working password (setPassword failed, non-destructive: stays active +
154
+ # reset_required) is not login-ready; any pending reset forces "partial" with a manual action.
155
+ PW_RESET_COUNT=$(echo "$CREATE_JSON" | jq -r '(.users_needing_password_reset // []) | length')
156
+
157
+ # Classify: created (all landed with a password) / partial (some landed, rollbacks, or pending
158
+ # password resets) / blocked (nothing landed).
159
+ if [ "$CREATED_COUNT" = "0" ]; then
160
+ STATUS="blocked"
161
+ elif [ "$ROLLED_BACK_COUNT" -gt 0 ] || [ "$CREATED_COUNT" -lt "$MISSING_COUNT" ] || [ "$PW_RESET_COUNT" -gt 0 ] || [ "$INACTIVE_COUNT" -gt 0 ]; then
162
+ STATUS="partial"
163
+ else
164
+ STATUS="created"
165
+ fi
166
+
167
+ jq -n \
168
+ --argjson detect "$DETECT_JSON" \
169
+ --argjson create "$CREATE_JSON" \
170
+ --argjson created_count "$CREATED_COUNT" \
171
+ --argjson reused_count "$EXISTING_COUNT" \
172
+ --argjson pw_reset_count "$PW_RESET_COUNT" \
173
+ --arg inactive_action "$INACTIVE_ACTION" \
174
+ --arg status "$STATUS" \
175
+ '{
176
+ status: $status,
177
+ detect: $detect,
178
+ create: $create,
179
+ created_count: $created_count,
180
+ reused_count: $reused_count,
181
+ total_present_after: ($created_count + $reused_count),
182
+ users_needing_password_reset: ($create.users_needing_password_reset // []),
183
+ action_required: (
184
+ [
185
+ (if $pw_reset_count > 0 then
186
+ ($pw_reset_count | tostring) + " supervisor user(s) were created but could NOT have a password set automatically and cannot log in yet. Manually reset their passwords (Setup → Users → Reset Password, or `sf org open` → user record) before use. See users_needing_password_reset and CREDENTIALS.json."
187
+ else empty end),
188
+ (if $inactive_action == "" then empty else $inactive_action end)
189
+ ] | if length == 0 then null else join(" ") end
190
+ ),
191
+ safe_to_write: $detect.safe_to_write
192
+ }'
193
+
194
+ case "$STATUS" in
195
+ created|partial) exit 0 ;;
196
+ blocked) exit 1 ;;
197
+ esac
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env bash
2
+ # detect-existing.sh - read-only probe for existing supervisor users (org/profile/existing/missing indexes); deterministic, no side effects. Args + JSON output: SKILL.md.
3
+
4
+ set -euo pipefail
5
+
6
+ if [ $# -lt 1 ]; then
7
+ echo '{"error":"Missing required argument: org-alias","usage":"bash detect-existing.sh <org-alias> [count=1] [profile-name=\"Standard User\"]"}' >&2
8
+ exit 1
9
+ fi
10
+
11
+ ORG="$1"
12
+ COUNT="${2:-1}"
13
+ PROFILE_NAME="${3:-Standard User}"
14
+
15
+ if ! [[ "$COUNT" =~ ^[1-5]$ ]]; then
16
+ echo "{\"error\":\"Count must be an integer in range 1..5, got: $COUNT\"}" >&2
17
+ exit 1
18
+ fi
19
+
20
+ if ! sf org display --target-org "$ORG" --json >/dev/null 2>&1; then
21
+ echo "{\"error\":\"Org alias '$ORG' is not authenticated. Run: sf org login web -a $ORG -r <my-domain-url>\"}" >&2
22
+ exit 1
23
+ fi
24
+
25
+ ORG_JSON=$(sf data query --target-org "$ORG" --query "SELECT Id, IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1" --json 2>/dev/null || true)
26
+ ORG_ID=$(echo "$ORG_JSON" | jq -r '.result.records[0].Id // empty')
27
+ IS_SANDBOX=$(echo "$ORG_JSON" | jq -r '.result.records[0].IsSandbox // false')
28
+ TRIAL_EXP=$(echo "$ORG_JSON" | jq -r '.result.records[0].TrialExpirationDate // "null"')
29
+ ORG_TYPE=$(echo "$ORG_JSON" | jq -r '.result.records[0].OrganizationType // "Unknown"')
30
+
31
+ if [ -z "$ORG_ID" ]; then
32
+ echo '{"error":"Failed to query Organization.Id - check org auth and API permissions."}' >&2
33
+ exit 1
34
+ fi
35
+
36
+ SAFE_TO_WRITE=false
37
+ if [ "$IS_SANDBOX" = "true" ] || [ "$TRIAL_EXP" != "null" ] || [ "$ORG_TYPE" = "Developer Edition" ] || [ "$ORG_TYPE" = "Base Edition" ]; then
38
+ SAFE_TO_WRITE=true
39
+ fi
40
+
41
+ SUFFIX=$(echo -n "${ORG_ID:10:8}" | tr '[:upper:]' '[:lower:]')
42
+
43
+ # Profile.Name is a display name; escape ' for the SOQL literal (apostrophe safety + no injection).
44
+ PROFILE_NAME_SOQL=${PROFILE_NAME//\'/\\\'}
45
+ PROFILE_JSON=$(sf data query --target-org "$ORG" --query "SELECT Id, Name FROM Profile WHERE Name = '$PROFILE_NAME_SOQL' LIMIT 1" --json 2>/dev/null || true)
46
+ PROFILE_ID=$(echo "$PROFILE_JSON" | jq -r '.result.records[0].Id // empty')
47
+
48
+ if [ -z "$PROFILE_ID" ]; then
49
+ echo "{\"error\":\"Profile '$PROFILE_NAME' not found on org '$ORG'. Check spelling - profile names are case-sensitive on some orgs and may be localized.\"}" >&2
50
+ exit 1
51
+ fi
52
+
53
+ USER_JSON=$(sf data query --target-org "$ORG" \
54
+ --query "SELECT Id, Username, IsActive FROM User WHERE Username LIKE 'supervisor%.${SUFFIX}@example.com'" \
55
+ --json 2>/dev/null || true)
56
+
57
+ EXPECTED_USERNAMES=$(seq 1 "$COUNT" | awk -v s="$SUFFIX" '{ printf "\"supervisor%s.%s@example.com\"\n", $1, s }' | jq -s '.')
58
+
59
+ FULL_JSON=$(jq -n \
60
+ --arg org_id "$ORG_ID" \
61
+ --arg org_suffix "$SUFFIX" \
62
+ --argjson is_sandbox "$IS_SANDBOX" \
63
+ --arg trial_expiration_date "$TRIAL_EXP" \
64
+ --arg organization_type "$ORG_TYPE" \
65
+ --argjson safe_to_write "$SAFE_TO_WRITE" \
66
+ --arg profile_id "$PROFILE_ID" \
67
+ --arg profile_name "$PROFILE_NAME" \
68
+ --argjson count "$COUNT" \
69
+ --argjson expected "$EXPECTED_USERNAMES" \
70
+ --argjson user_records "$(echo "$USER_JSON" | jq '.result.records // []')" \
71
+ '
72
+ ($user_records | map({ (.Username | ascii_downcase): {id: .Id, is_active: .IsActive} }) | add // {}) as $by_uname
73
+ |
74
+ [range(1; $count + 1) as $i
75
+ | ($expected[$i - 1] | ascii_downcase) as $uname
76
+ | if $by_uname[$uname] then
77
+ {index: $i, id: $by_uname[$uname].id, username: $uname, is_active: $by_uname[$uname].is_active}
78
+ else
79
+ null
80
+ end
81
+ ] as $slots
82
+ |
83
+ # occupied slots (any user present, active or not)
84
+ ($slots | map(select(. != null))) as $occupied
85
+ |
86
+ # existing_users: only active occupants are reusable; inactive users are surfaced separately
87
+ # (globally-unique username, not recreatable).
88
+ ($occupied | map(select(.is_active == true))) as $existing
89
+ |
90
+ ($occupied | map(select(.is_active != true))) as $inactive
91
+ |
92
+ ([range(1; $count + 1) as $i | if $slots[$i - 1] == null then $i else null end] | map(select(. != null))) as $missing
93
+ |
94
+ {
95
+ org_id: $org_id,
96
+ org_suffix: $org_suffix,
97
+ is_sandbox: $is_sandbox,
98
+ trial_expiration_date: (if $trial_expiration_date == "null" then null else $trial_expiration_date end),
99
+ organization_type: $organization_type,
100
+ safe_to_write: $safe_to_write,
101
+ profile_id: $profile_id,
102
+ profile_name: $profile_name,
103
+ requested_count: $count,
104
+ existing_users: $existing,
105
+ inactive_users: $inactive,
106
+ inactive_count: ($inactive | length),
107
+ missing_indexes: $missing,
108
+ missing_count: ($missing | length)
109
+ }
110
+ ')
111
+
112
+ echo "$FULL_JSON"