@salesforce/afv-skills 1.59.0 → 1.60.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 (173) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-architecture-analyze/SKILL.md +66 -17
  3. package/skills/agentforce-architecture-analyze/assets/cli/show_access_token.yaml +4 -7
  4. package/skills/agentforce-architecture-analyze/references/contract.json +1 -1
  5. package/skills/agentforce-architecture-analyze/scripts/emit_env.py +2 -2
  6. package/skills/agentforce-architecture-analyze/scripts/main.py +40 -44
  7. package/skills/agentforce-architecture-analyze/scripts/rest_client.py +11 -3
  8. package/skills/agentforce-architecture-analyze/scripts/sf_cli.py +77 -10
  9. package/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py +1 -0
  10. package/skills/agentforce-architecture-analyze/scripts/tests/test_flow_children_inflation.py +1 -0
  11. package/skills/agentforce-architecture-analyze/scripts/tests/test_main_pipeline.py +53 -2
  12. package/skills/agentforce-architecture-analyze/scripts/tests/test_resolve_creds.py +184 -36
  13. package/skills/agentforce-architecture-analyze/scripts/tests/test_rest_client.py +17 -0
  14. package/skills/agentforce-architecture-analyze/scripts/tests/test_sf_cli.py +165 -11
  15. package/skills/agentforce-architecture-analyze/scripts/tests/test_skill_md_pipeline_block.py +319 -0
  16. package/skills/agentforce-d360-analyze/SKILL.md +28 -10
  17. package/skills/agentforce-d360-analyze/scripts/dc.py +200 -50
  18. package/skills/agentforce-d360-analyze/scripts/discover_sessions.py +10 -6
  19. package/skills/agentforce-d360-analyze/scripts/fetch_dc.py +22 -6
  20. package/skills/agentforce-d360-analyze/scripts/resolve_session.py +21 -13
  21. package/skills/agentforce-d360-analyze/scripts/tests/test_dc_and_resolve_session.py +298 -76
  22. package/skills/agentforce-d360-analyze/scripts/tests/test_default_target_org.py +432 -0
  23. package/skills/agentforce-d360-analyze/scripts/tests/test_discover_sessions.py +1 -1
  24. package/skills/agentforce-d360-analyze/scripts/tests/test_resolve_session_main.py +9 -3
  25. package/skills/agentforce-d360-analyze/scripts/tests/test_skill_md_prefix_block.py +176 -0
  26. package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +17 -27
  27. package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +7 -17
  28. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +47 -63
  29. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +10 -13
  30. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -46
  31. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +4 -21
  32. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +58 -82
  33. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +16 -315
  34. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +9 -24
  35. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -44
  36. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +11 -63
  37. package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +2 -1
  38. package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +2 -1
  39. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +2 -1
  40. package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +2 -1
  41. package/skills/data360-schema-get/SKILL.md +3 -3
  42. package/skills/design-systems-slds-validate/references/report-format.md +1 -1
  43. package/skills/dx-org-manage/SKILL.md +1 -0
  44. package/skills/dx-org-shape-manage/SKILL.md +1 -0
  45. package/skills/dx-org-snapshot-manage/SKILL.md +172 -0
  46. package/skills/dx-org-snapshot-manage/examples/create_error_output.json +16 -0
  47. package/skills/dx-org-snapshot-manage/examples/create_success_output.json +19 -0
  48. package/skills/dx-org-snapshot-manage/examples/delete_output.json +9 -0
  49. package/skills/dx-org-snapshot-manage/examples/get_output.json +19 -0
  50. package/skills/dx-org-snapshot-manage/examples/list_output.json +36 -0
  51. package/skills/dx-org-snapshot-manage/references/cli_flags.md +179 -0
  52. package/skills/dx-org-snapshot-manage/references/snapshot_usage.md +55 -0
  53. package/skills/dx-org-trial-expiration-check/SKILL.md +1 -1
  54. package/skills/dx-org-trial-expiration-check/scripts/check_expiration.sh +2 -2
  55. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +7 -5
  56. package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +11 -11
  57. package/skills/experience-content-media-image-generate/SKILL.md +83 -0
  58. package/skills/experience-content-media-image-generate/scripts/download-image.sh +84 -0
  59. package/skills/external-diagram-mermaid-generate/SKILL.md +12 -12
  60. package/skills/field-service-data-capture-migrate/references/component-mapping.md +1 -1
  61. package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +1 -2
  62. package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +1 -0
  63. package/skills/field-service-data-capture-migrate/references/transformer-rules.md +1 -1
  64. package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +12 -1
  65. package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +4 -1
  66. package/skills/field-service-setup-orchestrator-get/SKILL.md +0 -1
  67. package/skills/insurance-brokerage-agency-billing-configure/SKILL.md +233 -0
  68. package/skills/insurance-brokerage-agency-billing-configure/references/accounting-setup.md +117 -0
  69. package/skills/insurance-brokerage-agency-billing-configure/references/billing-rules-config.md +237 -0
  70. package/skills/insurance-brokerage-agency-billing-configure/references/billing-settings.md +271 -0
  71. package/skills/insurance-brokerage-agency-billing-configure/references/demo-policy-config.md +138 -0
  72. package/skills/insurance-brokerage-agency-billing-configure/references/org-defaults.md +64 -0
  73. package/skills/insurance-brokerage-agency-billing-configure/references/permissions-config.md +226 -0
  74. package/skills/insurance-brokerage-agency-billing-configure/references/prerequisites.md +142 -0
  75. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +3 -2
  76. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +3 -3
  77. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +68 -45
  78. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +17 -18
  79. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +16 -3
  80. package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +16 -13
  81. package/skills/marketing-message-test/SKILL.md +194 -0
  82. package/skills/marketing-message-test/references/api-contract.md +333 -0
  83. package/skills/omnistudio-callable-apex-generate/SKILL.md +3 -3
  84. package/skills/omnistudio-datamapper-generate/SKILL.md +6 -6
  85. package/skills/omnistudio-datapacks-deploy/SKILL.md +11 -11
  86. package/skills/omnistudio-dependencies-analyze/SKILL.md +5 -5
  87. package/skills/omnistudio-epc-catalog-generate/SKILL.md +6 -6
  88. package/skills/omnistudio-flexcard-generate/SKILL.md +6 -6
  89. package/skills/omnistudio-integration-procedure-generate/SKILL.md +6 -6
  90. package/skills/omnistudio-omniscript-generate/SKILL.md +6 -5
  91. package/skills/platform-apex-logs-debug/SKILL.md +1 -0
  92. package/skills/platform-metadata-deploy/SKILL.md +1 -0
  93. package/skills/platform-models-api-configure/SKILL.md +5 -5
  94. package/skills/platform-soql-query/SKILL.md +1 -0
  95. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +17 -6
  96. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +107 -192
  97. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +6 -6
  98. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +49 -20
  99. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +4 -4
  100. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +224 -290
  101. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/workflow.md +295 -0
  102. package/skills/service-itsm-incident-priority-configure/SKILL.md +1 -1
  103. package/skills/service-itsm-slack-itservice-configure/SKILL.md +2 -1
  104. package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +2 -2
  105. package/skills/service-omni-channel-setup-coordinate/SKILL.md +20 -13
  106. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +5 -55
  107. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +0 -38
  108. package/skills/service-omni-command-center-analyze/SKILL.md +2 -3
  109. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
  110. package/skills/tableau-next-asset-create/README.md +65 -0
  111. package/skills/tableau-next-asset-create/SKILL.md +409 -0
  112. package/skills/tableau-next-asset-create/references/admin-users.md +149 -0
  113. package/skills/tableau-next-asset-create/references/ai-readiness.md +355 -0
  114. package/skills/tableau-next-asset-create/references/alerts.md +111 -0
  115. package/skills/tableau-next-asset-create/references/build-workflow.md +819 -0
  116. package/skills/tableau-next-asset-create/references/dashboard-authoring.md +302 -0
  117. package/skills/tableau-next-asset-create/references/dashboard-design-principles.md +352 -0
  118. package/skills/tableau-next-asset-create/references/data-transform-gotchas.md +210 -0
  119. package/skills/tableau-next-asset-create/references/data-understanding.md +387 -0
  120. package/skills/tableau-next-asset-create/references/deep-reference-index.md +74 -0
  121. package/skills/tableau-next-asset-create/references/destructive-operations.md +62 -0
  122. package/skills/tableau-next-asset-create/references/edit-dashboard.md +273 -0
  123. package/skills/tableau-next-asset-create/references/edit-visualization.md +529 -0
  124. package/skills/tableau-next-asset-create/references/empty-source-handling.md +104 -0
  125. package/skills/tableau-next-asset-create/references/ingest-and-metric-gotchas.md +638 -0
  126. package/skills/tableau-next-asset-create/references/insight-bundle.md +69 -0
  127. package/skills/tableau-next-asset-create/references/large-flat-file-handling.md +331 -0
  128. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_envelope.json +54 -0
  129. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_shared-widget-fields.json +122 -0
  130. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/add_page.json +29 -0
  131. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/delete_page.json +27 -0
  132. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/discard_dashboard.json +11 -0
  133. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget.json +30 -0
  134. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget_to_page.json +30 -0
  135. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/place_widget_on_page.json +30 -0
  136. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_dashboard.json +16 -0
  137. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_page.json +26 -0
  138. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/rename_page.json +28 -0
  139. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/save_dashboard.json +18 -0
  140. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/undo.json +11 -0
  141. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_button_widget.json +69 -0
  142. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_container_widget.json +88 -0
  143. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_filter_widget.json +146 -0
  144. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_metric_widget.json +78 -0
  145. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_navigation_widget.json +80 -0
  146. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_parameter_widget.json +160 -0
  147. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_text_widget.json +103 -0
  148. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_visualization_widget.json +68 -0
  149. package/skills/tableau-next-asset-create/references/sdm-tool-reference.md +721 -0
  150. package/skills/tableau-next-asset-create/references/semantic-query-and-enrichment.md +355 -0
  151. package/skills/tableau-next-asset-create/references/shared-gates.md +322 -0
  152. package/skills/tableau-next-asset-create/references/sharing-and-promotion.md +306 -0
  153. package/skills/tableau-next-asset-create/references/tasks/ai-readiness-audit.md +51 -0
  154. package/skills/tableau-next-asset-create/references/tasks/analyze-data.md +48 -0
  155. package/skills/tableau-next-asset-create/references/tasks/build-dashboard.md +66 -0
  156. package/skills/tableau-next-asset-create/references/tasks/build-end-to-end.md +73 -0
  157. package/skills/tableau-next-asset-create/references/tasks/create-data-transform.md +51 -0
  158. package/skills/tableau-next-asset-create/references/tasks/create-viz.md +58 -0
  159. package/skills/tableau-next-asset-create/references/tasks/edit-dashboard.md +80 -0
  160. package/skills/tableau-next-asset-create/references/tasks/edit-sdm.md +106 -0
  161. package/skills/tableau-next-asset-create/references/tasks/edit-viz.md +40 -0
  162. package/skills/tableau-next-asset-create/references/tasks/enrich-model.md +68 -0
  163. package/skills/tableau-next-asset-create/references/tasks/ingest-flat-file.md +68 -0
  164. package/skills/tableau-next-asset-create/references/tasks/manage-alert.md +35 -0
  165. package/skills/tableau-next-asset-create/references/tasks/profile-dataset.md +47 -0
  166. package/skills/tableau-next-asset-create/references/tasks/promote-or-reuse.md +46 -0
  167. package/skills/tableau-next-asset-create/references/tasks/provision-user.md +39 -0
  168. package/skills/tableau-next-asset-create/references/tasks/query-model.md +92 -0
  169. package/skills/tableau-next-asset-create/references/tasks/review-verified-questions.md +42 -0
  170. package/skills/tableau-next-asset-create/references/tasks/share-asset.md +37 -0
  171. package/skills/tableau-next-asset-create/references/verified-questions.md +125 -0
  172. package/skills/tableau-next-asset-create/references/viz-authoring.md +992 -0
  173. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +0 -102
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/afv-skills",
3
- "version": "1.59.0",
3
+ "version": "1.60.0",
4
4
  "description": "Salesforce skills for Agentforce Vibes",
5
5
  "license": "CC-BY-NC-4.0",
6
6
  "files": [
@@ -22,9 +22,23 @@ Design-time metadata tree for one Agentforce agent: planner → topics → actio
22
22
 
23
23
  Runs **inline** — no subagent. Every phase is deterministic file processing.
24
24
 
25
- ## If the user hasn't given enough to proceed
25
+ ## If the user hasn't named an agent
26
26
 
27
- When invoked with no `agent_api_name` AND no org alias, print the following block **verbatim** — do not paraphrase, do not pre-run any script. Trigger condition: `$ARGUMENTS` is empty OR names no agent (no `--agent` flag and no known agent API name in the prose) OR names no org (no `--org` flag and no known alias).
27
+ Trigger condition: `$ARGUMENTS` names no agent (no `--agent` flag and no exact `BotDefinition` API name in the prose). Never invent an agent name. Instead, discover what the org actually contains — one read-only query, no pipeline yet. Use the `--org` alias if given, otherwise the default target org (omit `--target-org`):
28
+
29
+ ```bash
30
+ sf data query --query "SELECT DeveloperName, MasterLabel FROM BotDefinition ORDER BY DeveloperName" \
31
+ [--target-org <alias>] --json
32
+ ```
33
+
34
+ If the user gave no org alias, the pipeline block defaults `--org` to the CLI default target org itself (`sf config get target-org --json` → `.result[0].value`) — you don't need to resolve it.
35
+
36
+ Then branch on the rows returned:
37
+
38
+ - **Zero agents** — tell the user the org has no Agentforce agents and stop. Do not fabricate an agent or a tree.
39
+ - **Exactly one agent, or exactly one whose `DeveloperName`/`MasterLabel` matches the user's description** (e.g. "support", "service", "sales") — use it, say explicitly which agent you picked and why, then run the pipeline block below with `ARG_AGENT=<DeveloperName>` (and `ARG_ORG=<alias>` if the user named one) set as its first lines, e.g. `ARG_AGENT=Customer_Support_Agent`. The block reads `ARG_*` from the environment; flags in `$ARGUMENTS` still override.
40
+ - **Otherwise** — print the block below, with the discovered agents listed as a numbered `DeveloperName — MasterLabel` table under the first line, and wait for the user to pick.
41
+ - **Query fails** (no default org, auth error) — print the block below without the list.
28
42
 
29
43
  > Which agent should I document, and in which org?
30
44
  >
@@ -41,7 +55,7 @@ When invoked with no `agent_api_name` AND no org alias, print the following bloc
41
55
 
42
56
  ## Pipeline invocation
43
57
 
44
- When the user has supplied `--org <alias>` + `--agent <api_name>` (plus any optional flags), run this block. One `python3` invocation drives the full pipeline. `main.py` writes `.emit_ctx.json`; `emit_result.py` reads it and prints the final `=== RESULT ===` block last to stdout.
58
+ Run this block once the agent is known — from `--agent <api_name>` in `$ARGUMENTS`, or preset as `ARG_AGENT=<DeveloperName>` after discovery. `--org` (or `ARG_ORG`) is optional; without it the CLI default target org is used. One `python3` invocation drives the full pipeline. `main.py` writes `.emit_ctx.json`; `emit_result.py` reads it and prints the final `=== RESULT ===` block last to stdout.
45
59
 
46
60
  ```bash
47
61
  set -euo pipefail
@@ -53,20 +67,44 @@ set -euo pipefail
53
67
  # matching the bash shebang's expectation. No-op under bash.
54
68
  [ -n "${ZSH_VERSION:-}" ] && setopt KSH_ARRAYS
55
69
 
56
- SKILL_ROOT="${SKILL_ROOT:-${PLUGIN_ROOT:-$HOME/.vibe/skills}/agentforce-architecture-analyze}"
70
+ # <SKILL_DIR> = absolute path of this skill's own directory (the folder holding
71
+ # this SKILL.md); substitute it from the skill path in context. Runtimes install
72
+ # skills in different places, so also probe the known install roots and use the
73
+ # first that actually contains this skill's scripts. An exported SKILL_ROOT wins.
74
+ _skill=agentforce-architecture-analyze
75
+ for _c in "${SKILL_ROOT:-}" "<SKILL_DIR>" \
76
+ "${CLAUDE_PLUGIN_ROOT:+$CLAUDE_PLUGIN_ROOT/skills/$_skill}" \
77
+ "${VIBES_SKILLS_DIR:+$VIBES_SKILLS_DIR/$_skill}" \
78
+ "$HOME/.claude/skills/$_skill" "$PWD/.claude/skills/$_skill" \
79
+ "${PLUGIN_ROOT:-$HOME/.vibe/skills}/$_skill"; do
80
+ [ -n "$_c" ] || continue
81
+ # Some stagers (ADK eval) nest the bundled files under <skill>/artifacts/.
82
+ for _r in "$_c" "$_c/artifacts"; do
83
+ [ -f "$_r/scripts/main.py" ] && { SKILL_ROOT="$_r"; break 2; }
84
+ done
85
+ done
86
+ [ -f "${SKILL_ROOT:-}/scripts/main.py" ] || { echo "$_skill: scripts not found — set SKILL_ROOT to this skill's directory" >&2; exit 1; }
57
87
 
58
88
  # Argument parser. Accepts both `--org foo` and `--org=foo`.
59
- # `$ARGUMENTS` is the raw user input Claude Code substitutes.
60
- ARG_ORG=""
61
- ARG_AGENT=""
62
- ARG_VERSION=""
63
- ARG_FORCE=""
64
- ARG_REPROBE=""
65
- ARG_PARALLELISM=""
66
- ARG_MAX_MERMAID=""
67
-
89
+ # `$ARGUMENTS` is the raw user input Claude Code substitutes; it may be unset
90
+ # or plain prose. Each ARG_* starts from the environment, so a value resolved
91
+ # during discovery can be preset (e.g. `ARG_AGENT=MyAgent` on the first line);
92
+ # flags in $ARGUMENTS still override.
93
+ ARG_ORG="${ARG_ORG:-}"
94
+ ARG_AGENT="${ARG_AGENT:-}"
95
+ ARG_VERSION="${ARG_VERSION:-}"
96
+ ARG_FORCE="${ARG_FORCE:-}"
97
+ ARG_REPROBE="${ARG_REPROBE:-}"
98
+ ARG_PARALLELISM="${ARG_PARALLELISM:-}"
99
+ ARG_MAX_MERMAID="${ARG_MAX_MERMAID:-}"
100
+
101
+ # Word-split $ARGUMENTS (zsh needs SH_WORD_SPLIT for that) with globbing off
102
+ # so prose like `*` never expands to filenames.
103
+ [ -n "${ZSH_VERSION:-}" ] && setopt SH_WORD_SPLIT
104
+ set -f
68
105
  # shellcheck disable=SC2206
69
- _args=($ARGUMENTS)
106
+ _args=(${ARGUMENTS:-})
107
+ set +f
70
108
  i=0
71
109
  while [ $i -lt ${#_args[@]} ]; do
72
110
  tok="${_args[$i]}"
@@ -87,6 +125,16 @@ while [ $i -lt ${#_args[@]} ]; do
87
125
  i=$((i+1))
88
126
  done
89
127
 
128
+ # No org named anywhere: fall back to the sf CLI default target org
129
+ # (`sf config get target-org --json` -> .result[0].value). Extracted with sed
130
+ # so the block needs neither jq nor an inline interpreter; any failure leaves
131
+ # ARG_ORG empty and the usage block below fires.
132
+ if [ -z "$ARG_ORG" ] && command -v sf >/dev/null 2>&1; then
133
+ _cfg="$(sf config get target-org --json 2>/dev/null || true)"
134
+ _cfg="$(printf '%s\n' "$_cfg" | sed -n 's/.*"value"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' || true)"
135
+ ARG_ORG="${_cfg%%$'\n'*}"
136
+ fi
137
+
90
138
  # Usage block if required flags missing. Agent reads stderr,
91
139
  # prints verbatim, and stops — does NOT pre-run main.py.
92
140
  if [ -z "$ARG_ORG" ] || [ -z "$ARG_AGENT" ]; then
@@ -95,7 +143,8 @@ if [ -z "$ARG_ORG" ] || [ -z "$ARG_AGENT" ]; then
95
143
  >
96
144
  > I need:
97
145
  > - **Agent API name** — the BotDefinition.DeveloperName (e.g. `MyAgent`)
98
- > - **Org alias** — for `sf` CLI auth (the alias you configured with `sf org login`)
146
+ > - **Org alias** — for `sf` CLI auth (the alias you configured with `sf org login`);
147
+ > only needed when no default target org is set (`sf config set target-org <alias>`)
99
148
  >
100
149
  > Optional flags:
101
150
  > - `--version v5` — pin a specific BotVersion (default: Active+highest)
@@ -152,7 +201,7 @@ exit "$_rc"
152
201
 
153
202
  | Input | Flag | Required | Default |
154
203
  |---|---|---|---|
155
- | `org_alias` | `--org` | yes | — |
204
+ | `org_alias` | `--org` | no | sf CLI default target org (`sf config get target-org`) |
156
205
  | `agent_api_name` | `--agent` | yes | — |
157
206
  | `agent_version_api_name` | `--version` | no | active BotVersion |
158
207
  | `force_refresh` | `--force` | no | false (honor cache) |
@@ -219,7 +268,7 @@ The ID-prefix router in `resolve_invocation_target.py` distinguishes the two: NG
219
268
 
220
269
  | Tool | Required |
221
270
  |---|---|
222
- | `sf` CLI (authenticated against the target org) | yes — `sf org login web --alias <alias>` |
271
+ | `sf` CLI (authenticated against the target org) | yes — `sf org login web --alias <alias>`, and the CLI must provide `sf org auth show-access-token` (startup preflight enforces this) |
223
272
  | Python 3.10+ | yes |
224
273
 
225
274
  ## Reference docs to load when needed
@@ -1,15 +1,12 @@
1
1
  name: show_access_token
2
- # Primary access-token retrieval path per forcedotcom/cli#3560 (effective
3
- # 2026-05-27). The `sf org display --json` field is now redacted to a
4
- # placeholder string by default; the dedicated `sf org auth show-access-token`
5
- # command is the long-term replacement.
2
+ # Access-token retrieval per forcedotcom/cli#3560. The `sf org display --json`
3
+ # accessToken field is redacted to a placeholder string; the dedicated
4
+ # `sf org auth show-access-token` command is the replacement.
6
5
  #
7
6
  # `--no-prompt` skips the interactive confirmation banner so the command
8
7
  # behaves identically under `--json`.
9
8
  #
10
- # The legacy fallback (`sf org display` with `SF_TEMP_SHOW_SECRETS=true`)
11
- # stays wired for sf CLI versions that don't ship show-access-token; that
12
- # fallback is orchestrated in `main.py::_resolve_creds`, not here.
9
+ # This command is the only supported runtime token-retrieval path.
13
10
  argv:
14
11
  - sf
15
12
  - org
@@ -6,7 +6,7 @@
6
6
  "required": {
7
7
  "org_alias": {
8
8
  "type": "string",
9
- "description": "sf CLI alias (e.g. 'my-org-alias'). Auth is always derived via `sf org display --target-org $ORG_ALIAS --json`."
9
+ "description": "sf CLI alias (e.g. 'my-org-alias'). Auth is always derived via `sf org display --target-org $ORG_ALIAS --json` (instance URL) and `sf org auth show-access-token --target-org $ORG_ALIAS --json --no-prompt` (access token)."
10
10
  },
11
11
  "agent_api_name": {
12
12
  "type": "string",
@@ -24,9 +24,9 @@ Any path component is a dictionary key OR an integer array index
24
24
  is the empty string — caller decides whether that's OK.
25
25
 
26
26
  Usage:
27
- # Parse `sf org display --json` output via stdin
27
+ # Parse `sf org display --json` output via stdin (org metadata only —
28
+ # access tokens come only from `sf org auth show-access-token --json --no-prompt`)
28
29
  eval "$(printf '%s' "$_SF_JSON" | python3 emit_env.py stdin \\
29
- 'ACCESS_TOKEN:result.accessToken' \\
30
30
  'INSTANCE_URL:result.instanceUrl' \\
31
31
  'ORG_ID_18:result.id' \\
32
32
  'ORG_ID_15:result.id[:15]')"
@@ -46,7 +46,12 @@ from config import (
46
46
  build_agent_data_dir,
47
47
  )
48
48
  from rest_client import RestClientError, redact_error
49
- from sf_cli import AuthRequired, SfCliError, run_sf
49
+ from sf_cli import (
50
+ AuthRequired,
51
+ SfCliError,
52
+ assert_show_access_token_capability,
53
+ run_sf,
54
+ )
50
55
  from soql_loader import SoqlParamError
51
56
 
52
57
  # Phase 2 Batch 1 imports — functional pipeline primitives.
@@ -180,9 +185,12 @@ def _build_creds_plumbing(
180
185
  on every call so a refresh actually lands in the NEXT SOQL attempt.
181
186
  `refresh_fn` is serialized by an internal `threading.Lock` + a
182
187
  `time.monotonic()` dedupe window — N concurrent 401s against the same
183
- stale token collapse to ONE `resolve_creds()` spawn per window. That
184
- caps real `sf org display` spawns at roughly 1/second regardless of
185
- pool size.
188
+ stale token collapse to ONE `resolve_creds()` call per window. The
189
+ window is stamped when `resolve_creds()` RETURNS, so waiters queued
190
+ behind a slow refresh (several sf CLI spawns: `sf org display` +
191
+ `sf org auth show-access-token`) reuse its result instead of
192
+ re-refreshing. That caps full credential refreshes at roughly one per
193
+ window regardless of pool size.
186
194
 
187
195
  Extracted from `main()` so the closure is testable without threading
188
196
  a whole pipeline run through argparse + mocks.
@@ -205,7 +213,9 @@ def _build_creds_plumbing(
205
213
  # Another thread refreshed within the dedupe window — reuse.
206
214
  return creds_cell[0]
207
215
  creds_cell[0] = resolve_creds()
208
- last_refresh_mono[0] = now
216
+ # Stamp AFTER the (possibly slow) refresh so threads that queued
217
+ # on the lock during it fall inside the window and reuse it.
218
+ last_refresh_mono[0] = time.monotonic()
209
219
  return creds_cell[0]
210
220
 
211
221
  return creds_provider, refresh_fn, creds_cell
@@ -222,25 +232,25 @@ been stable since cli#3560 landed."""
222
232
  def _resolve_creds(org_alias: str) -> Tuple[str, str]:
223
233
  """Resolve (instance_url, access_token) for ``org_alias``.
224
234
 
225
- Two-path strategy per forcedotcom/cli#3560 (effective 2026-05-27):
226
-
227
- 1. **Primary** — call ``sf org display`` for instanceUrl, then
228
- ``sf org auth show-access-token`` for the access token. This is
229
- the upstream long-term path; ``SF_TEMP_SHOW_SECRETS`` is
230
- decommissioned in summer 2026.
231
- 2. **Fallback** — if the dedicated command isn't shipped in the
232
- installed sf CLI version (older releases), fall back to the
233
- legacy ``sf org display --verbose`` with the
234
- ``SF_TEMP_SHOW_SECRETS=true`` env var that ``run_sf`` injects
235
- unconditionally. The fallback is detected by the redaction
236
- placeholder appearing in the ``accessToken`` field — exact match
237
- on the substring ``show-access-token`` (see
238
- ``_REDACTION_MARKER_FRAGMENT``).
235
+ Runtime token retrieval is fail-fast (forcedotcom/cli#3560): the
236
+ access token comes ONLY from ``sf org auth show-access-token``.
237
+ ``sf org display`` is still used for non-secret metadata
238
+ (instanceUrl). A missing command, an empty token, or the redaction
239
+ placeholder (see ``_REDACTION_MARKER_FRAGMENT``) all raise
240
+ ``AuthRequired``.
239
241
 
240
242
  sf_cli.run_sf already redacts stderr on failure paths. Any
241
243
  exception surfaces as `AuthRequired` / `SfCliError`; caller routes
242
244
  through `_emit_fail`.
243
245
  """
246
+ # Capability preflight first (cached once it passes, see sf_cli) so a
247
+ # missing sf binary / command surfaces the actionable preflight error
248
+ # rather than a generic spawn failure from `sf org display`.
249
+ try:
250
+ assert_show_access_token_capability()
251
+ except SfCliError as exc:
252
+ raise AuthRequired(str(exc)) from exc
253
+
244
254
  display_data = run_sf("org_display", ORG_ALIAS=org_alias)
245
255
  display_result = display_data.get("result") or {}
246
256
  instance_url = display_result.get("instanceUrl") or ""
@@ -249,37 +259,23 @@ def _resolve_creds(org_alias: str) -> Tuple[str, str]:
249
259
  "sf org display returned an incomplete payload (missing instanceUrl)"
250
260
  )
251
261
 
252
- # Primary path: dedicated command. Catches both the unknown-command
253
- # case (older sf CLI) and any AuthRequired surfaced from the recipe.
254
262
  try:
255
263
  token_data = run_sf("show_access_token", ORG_ALIAS=org_alias)
256
- except SfCliError:
257
- # Older sf CLI versions emit "is not a sf command" on stderr; the
258
- # recipe surfaces that as SfCliError. Fall back to the
259
- # SF_TEMP_SHOW_SECRETS path via the org_display payload we
260
- # already have.
261
- access_token = display_result.get("accessToken") or ""
262
- else:
263
- token_result = token_data.get("result") or {}
264
- access_token = token_result.get("accessToken") or ""
265
-
266
- # Defensive fallback: if the dedicated command silently returned an
267
- # empty token OR sf CLI redacted the org_display token to the
268
- # placeholder, try the org_display payload directly. SF_TEMP_SHOW_SECRETS
269
- # is set unconditionally by run_sf so this works as long as the env
270
- # var is still honoured by the installed sf CLI.
271
- if not access_token or _REDACTION_MARKER_FRAGMENT in access_token:
272
- legacy_token = display_result.get("accessToken") or ""
273
- if legacy_token and _REDACTION_MARKER_FRAGMENT not in legacy_token:
274
- access_token = legacy_token
264
+ except (SfCliError, AuthRequired) as exc:
265
+ raise AuthRequired(
266
+ "could not retrieve access token via sf org auth "
267
+ "show-access-token; ensure sf CLI is logged in for this org "
268
+ f"and supports the dedicated command. Detail: {exc}"
269
+ ) from exc
270
+
271
+ token_result = token_data.get("result") or {}
272
+ access_token = token_result.get("accessToken") or ""
275
273
 
276
274
  if not access_token or _REDACTION_MARKER_FRAGMENT in access_token:
277
275
  raise AuthRequired(
278
276
  "could not retrieve a usable access token via sf org auth "
279
- "show-access-token (primary) or sf org display (fallback); "
280
- "ensure sf CLI is logged in for this org and either the "
281
- "dedicated command is available or SF_TEMP_SHOW_SECRETS is "
282
- "still honoured."
277
+ "show-access-token; ensure sf CLI is logged in for this org "
278
+ "and the command returned a real token."
283
279
  )
284
280
 
285
281
  return instance_url, access_token
@@ -46,12 +46,14 @@ class RestClientError(RuntimeError):
46
46
  # -----------------------------------------------------------------------------
47
47
  # error redaction
48
48
  # -----------------------------------------------------------------------------
49
- # Three patterns cover the bulk of token-leakage surfaces:
49
+ # Four patterns cover the bulk of token-leakage surfaces:
50
50
  # 1. `Authorization: Bearer <token>` in any string context (header echo,
51
51
  # exception repr, stringified HTTP error).
52
- # 2. `accessToken=<token>` / `access_token=<token>` in URL-encoded bodies
52
+ # 2. A bare `Bearer <token>` with no `Authorization:` prefix (curl-style
53
+ # traces, CLI stderr, hand-built header dicts).
54
+ # 3. `accessToken=<token>` / `access_token=<token>` in URL-encoded bodies
53
55
  # or query strings (`sf org display` errors sometimes echo these).
54
- # 3. `"accessToken":"<token>"` or `"access_token":"<token>"` in JSON
56
+ # 4. `"accessToken":"<token>"` or `"access_token":"<token>"` in JSON
55
57
  # payload echoes.
56
58
  #
57
59
  # Regexes are intentionally permissive on the token character class
@@ -62,6 +64,11 @@ _AUTH_HEADER_RE = re.compile(
62
64
  r"(Authorization\s*:\s*Bearer\s+)\S+",
63
65
  flags=re.IGNORECASE,
64
66
  )
67
+ # Matches a bare `Bearer <value>`; stops at whitespace or a quote.
68
+ _BEARER_RE = re.compile(
69
+ r"(\bBearer\s+)[^\s\"']+",
70
+ flags=re.IGNORECASE,
71
+ )
65
72
  # Matches access[_]?Token=<value> in url-encoded form; stops at & or whitespace.
66
73
  _ACCESS_TOKEN_QS_RE = re.compile(
67
74
  r"(access[_]?token\s*=\s*)[^&\s\"']+",
@@ -91,6 +98,7 @@ def redact_text(text: str) -> str:
91
98
  if not text:
92
99
  return text
93
100
  text = _AUTH_HEADER_RE.sub(r"\1<redacted>", text)
101
+ text = _BEARER_RE.sub(r"\1<redacted>", text)
94
102
  text = _ACCESS_TOKEN_QS_RE.sub(r"\1<redacted>", text)
95
103
  text = _ACCESS_TOKEN_JSON_RE.sub(r'\1<redacted>', text)
96
104
  return text
@@ -18,7 +18,6 @@ into logs or RESULT blocks.
18
18
  from __future__ import annotations
19
19
 
20
20
  import json
21
- import os
22
21
  import re
23
22
  import subprocess
24
23
  from pathlib import Path
@@ -57,6 +56,83 @@ class SfCliError(RuntimeError):
57
56
  """
58
57
 
59
58
 
59
+ _show_access_token_capability_ok = False
60
+ """Set once the preflight succeeds; the check then never reruns in this
61
+ process (401-triggered creds refreshes skip it). Failures are NOT cached,
62
+ so a fixed PATH/upgrade is picked up on the next call."""
63
+
64
+ _SHOW_ACCESS_TOKEN_HELP_SIGNATURE_RE = re.compile(
65
+ r"\$\s*sf\s+org\s+auth\s+show-access-token\b"
66
+ )
67
+ """USAGE-line form that real ``--help`` output always contains
68
+ (``$ sf org auth show-access-token -o <value> ...``). The bare command name
69
+ is not enough: unknown-command notices echo it too
70
+ (``Command org:auth:show-access-token not found``,
71
+ ``show-access-token is not a sf command``). A zero-exit probe whose output
72
+ lacks the USAGE form is treated as a failed check."""
73
+
74
+ _ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]")
75
+
76
+
77
+ def assert_show_access_token_capability() -> None:
78
+ """Fail fast when the installed sf CLI lacks ``sf org auth show-access-token``.
79
+
80
+ That command is the only supported runtime token-retrieval path
81
+ (forcedotcom/cli#3560). Probing ``--help`` is cheap, needs no org auth,
82
+ and gives the operator an actionable upgrade message instead of a
83
+ confusing downstream auth failure. A successful probe is cached for
84
+ the process lifetime.
85
+
86
+ Exit status alone is not trusted: a CLI build that answers an unknown
87
+ command with exit 0 (e.g. printing top-level help or a "not found"
88
+ notice) would otherwise pass. The probe therefore also requires the
89
+ command's USAGE line (``$ sf org auth show-access-token``) in the
90
+ ANSI-stripped stdout+stderr; if it is absent the check fails and is not
91
+ cached.
92
+ """
93
+ global _show_access_token_capability_ok
94
+ if _show_access_token_capability_ok:
95
+ return
96
+ try:
97
+ proc = subprocess.run(
98
+ ["sf", "org", "auth", "show-access-token", "--help"],
99
+ capture_output=True,
100
+ text=True,
101
+ timeout=30,
102
+ check=True,
103
+ )
104
+ except FileNotFoundError as exc:
105
+ raise SfCliError(
106
+ "sf CLI not found on PATH — install Salesforce CLI first"
107
+ ) from exc
108
+ except subprocess.CalledProcessError as exc:
109
+ safe_detail = _redact_subprocess_stderr(exc.stderr or exc.stdout or "")
110
+ raise SfCliError(
111
+ "sf CLI is missing required command 'sf org auth show-access-token'. "
112
+ "Upgrade/reinstall Salesforce CLI to a version that provides this "
113
+ f"command. Preflight detail: {safe_detail or 'unknown error'}"
114
+ ) from exc
115
+ except subprocess.TimeoutExpired as exc:
116
+ raise SfCliError(
117
+ f"sf CLI capability preflight timed out: {redact_error(exc)}"
118
+ ) from exc
119
+ help_text = _ANSI_ESCAPE_RE.sub(
120
+ "", f"{proc.stdout or ''}\n{proc.stderr or ''}",
121
+ )
122
+ if not _SHOW_ACCESS_TOKEN_HELP_SIGNATURE_RE.search(help_text):
123
+ # Zero exit without the command's help signature — the CLI did not
124
+ # recognise the command. Fail (uncached) like a non-zero exit.
125
+ # Redact before truncating so a token can never be split past the regex.
126
+ safe_detail = _redact_subprocess_stderr(help_text.strip())[:500]
127
+ raise SfCliError(
128
+ "sf CLI is missing required command 'sf org auth show-access-token'. "
129
+ "Upgrade/reinstall Salesforce CLI to a version that provides this "
130
+ "command. Preflight detail: "
131
+ f"{safe_detail or 'help output lacked the command signature'}"
132
+ )
133
+ _show_access_token_capability_ok = True
134
+
135
+
60
136
  def _load_recipe(name: str) -> Dict[str, Any]:
61
137
  """Read and parse a CLI recipe file with yaml.safe_load.
62
138
 
@@ -175,14 +251,6 @@ def run_sf(name: str, **params: str) -> Dict[str, Any]:
175
251
  timeout = int(recipe.get("timeout_seconds", 60))
176
252
  auth_patterns = recipe.get("auth_required_stderr_patterns") or []
177
253
 
178
- # SF_TEMP_SHOW_SECRETS=true is required for `sf org display --verbose
179
- # --json` to emit `accessToken` instead of the literal redaction string
180
- # `"[REDACTED] Use 'sf org auth show-access-token' to view"` introduced
181
- # in sf CLI v2. Without it, downstream callers receive the redaction
182
- # string as the bearer token and every Tooling/REST call returns
183
- # INVALID_AUTH_HEADER 401. Set on every recipe — recipes that don't
184
- # touch tokens are unaffected.
185
- env = {**os.environ, "SF_TEMP_SHOW_SECRETS": "true"}
186
254
  try:
187
255
  cp = subprocess.run(
188
256
  argv,
@@ -190,7 +258,6 @@ def run_sf(name: str, **params: str) -> Dict[str, Any]:
190
258
  text=True,
191
259
  timeout=timeout,
192
260
  check=False,
193
- env=env,
194
261
  )
195
262
  except subprocess.TimeoutExpired as e:
196
263
  # timeout exceptions carry stderr via e.stderr — redact.
@@ -87,6 +87,7 @@ def _mock_auth_probe(probe_result=None):
87
87
  }
88
88
  return [
89
89
  mock.patch.object(main, "run_sf", return_value=org_display_payload),
90
+ mock.patch.object(main, "assert_show_access_token_capability"),
90
91
  mock.patch.object(main, "probe_channels", return_value=probe_result),
91
92
  ]
92
93
 
@@ -244,6 +244,7 @@ def _mock_auth_probe():
244
244
  }
245
245
  return [
246
246
  mock.patch.object(main, "run_sf", return_value=org_display_payload),
247
+ mock.patch.object(main, "assert_show_access_token_capability"),
247
248
  mock.patch.object(main, "probe_channels", return_value=fx.probe_ok_payload()),
248
249
  ]
249
250
 
@@ -131,6 +131,7 @@ def _mock_auth_probe(probe_result=None):
131
131
  org_display_payload["result"]["id"] = "00Dxx0000000000AAA"
132
132
  return [
133
133
  mock.patch.object(main, "run_sf", return_value=org_display_payload),
134
+ mock.patch.object(main, "assert_show_access_token_capability"),
134
135
  mock.patch.object(main, "probe_channels", return_value=probe_result),
135
136
  ]
136
137
 
@@ -852,11 +853,13 @@ class RenderFailureIntegrationTests(unittest.TestCase):
852
853
 
853
854
  # Drive emit_result against this ctx and confirm the RESULT
854
855
  # block reflects the render failure.
855
- tools_dir = Path(__file__).resolve().parent.parent.parent / "tools"
856
+ # emit_result.py ships under scripts/ (SKILL.md invokes
857
+ # "$SKILL_ROOT/scripts/emit_result.py"), not the hub-era tools/.
858
+ scripts_dir = Path(__file__).resolve().parent.parent
856
859
  import subprocess, sys as _sys
857
860
  env = {**os.environ, "WORK_DIR": str(work_dir)}
858
861
  r = subprocess.run(
859
- [_sys.executable, str(tools_dir / "emit_result.py")],
862
+ [_sys.executable, str(scripts_dir / "emit_result.py")],
860
863
  env=env, capture_output=True, text=True, timeout=30,
861
864
  )
862
865
  self.assertEqual(r.returncode, 0, msg=r.stderr)
@@ -1012,6 +1015,53 @@ class ThreadSafeRefreshTests(unittest.TestCase):
1012
1015
 
1013
1016
  self.assertEqual(call_count["n"], 2)
1014
1017
 
1018
+ def test_slow_refresh_waiters_do_not_re_refresh(self):
1019
+ """A refresh that takes LONGER than the dedupe window must still
1020
+ satisfy threads that queued on the lock while it ran. The window
1021
+ is stamped when `resolve_creds()` returns, not when it starts —
1022
+ otherwise every queued waiter re-refreshes after a slow
1023
+ multi-command resolve.
1024
+ """
1025
+ import threading as _t
1026
+ import time as _time
1027
+
1028
+ calls = {"n": 0}
1029
+ entered = _t.Event()
1030
+
1031
+ def slow_resolve():
1032
+ calls["n"] += 1
1033
+ entered.set()
1034
+ _time.sleep(0.3) # longer than the 0.1s window below
1035
+ return ("url", f"tok_{calls['n']}")
1036
+
1037
+ _provider, refresh, _cell = main._build_creds_plumbing(
1038
+ ("url", "tok_0"),
1039
+ resolve_creds=slow_resolve,
1040
+ dedupe_window_s=0.1,
1041
+ )
1042
+
1043
+ results: list = []
1044
+ results_lock = _t.Lock()
1045
+
1046
+ def _worker():
1047
+ r = refresh()
1048
+ with results_lock:
1049
+ results.append(r)
1050
+
1051
+ first = _t.Thread(target=_worker)
1052
+ first.start()
1053
+ self.assertTrue(entered.wait(timeout=5))
1054
+ # These all block on the refresh lock while slow_resolve runs.
1055
+ waiters = [_t.Thread(target=_worker) for _ in range(4)]
1056
+ for t in waiters:
1057
+ t.start()
1058
+ first.join()
1059
+ for t in waiters:
1060
+ t.join()
1061
+
1062
+ self.assertEqual(calls["n"], 1)
1063
+ self.assertEqual(results, [("url", "tok_1")] * 5)
1064
+
1015
1065
  def test_provider_reads_cell_after_refresh(self):
1016
1066
  """After refresh mutates the cell, the NEXT `creds_provider()`
1017
1067
  call returns the fresh tuple (the contract).
@@ -1512,6 +1562,7 @@ class ApiVersionEndToEndTests(unittest.TestCase):
1512
1562
  ]
1513
1563
  patches = [
1514
1564
  mock.patch.object(main, "run_sf", return_value=org_display_payload),
1565
+ mock.patch.object(main, "assert_show_access_token_capability"),
1515
1566
  mock.patch.object(main, "probe_channels",
1516
1567
  return_value=fx.probe_ok_payload()),
1517
1568
  *_mock_bot_resolution(),