@salesforce/afv-skills 1.66.0 → 1.68.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 (165) hide show
  1. package/README.md +55 -5
  2. package/package.json +2 -4
  3. package/skills/agentforce-offline-experimentation-run/SKILL.md +26 -49
  4. package/skills/agentforce-offline-experimentation-run/scripts/bt_compare.py +81 -0
  5. package/skills/agentforce-offline-experimentation-run/scripts/bt_fetch.py +358 -0
  6. package/skills/agentforce-offline-experimentation-run/scripts/bt_patch_agent.py +110 -0
  7. package/skills/agentforce-offline-experimentation-run/scripts/btlib/__init__.py +34 -0
  8. package/skills/agentforce-offline-experimentation-run/scripts/btlib/agentpatch.py +483 -0
  9. package/skills/agentforce-offline-experimentation-run/scripts/btlib/agentsrc.py +565 -0
  10. package/skills/agentforce-offline-experimentation-run/scripts/btlib/cache.py +95 -0
  11. package/skills/agentforce-offline-experimentation-run/scripts/btlib/compare.py +617 -0
  12. package/skills/agentforce-offline-experimentation-run/scripts/btlib/fetch.py +1012 -0
  13. package/skills/agentforce-offline-experimentation-run/scripts/btlib/judge.py +148 -0
  14. package/skills/agentforce-offline-experimentation-run/scripts/btlib/messages.py +92 -0
  15. package/skills/agentforce-offline-experimentation-run/scripts/btlib/patch.py +327 -0
  16. package/skills/agentforce-offline-experimentation-run/scripts/btlib/redact.py +40 -0
  17. package/skills/agentforce-offline-experimentation-run/scripts/btlib/results.py +288 -0
  18. package/skills/agentforce-offline-experimentation-run/scripts/btlib/scoring.py +340 -0
  19. package/skills/agentforce-offline-experimentation-run/scripts/btlib/sfap.py +238 -0
  20. package/skills/agentforce-offline-experimentation-run/scripts/btlib/splits.py +136 -0
  21. package/skills/agentforce-offline-experimentation-run/scripts/btlib/stdm.py +98 -0
  22. package/skills/agentforce-offline-experimentation-run/scripts/btlib/tokens.py +173 -0
  23. package/skills/agentforce-offline-experimentation-run/scripts/btlib/units.py +199 -0
  24. package/skills/agentforce-offline-experimentation-run/scripts/btlib/versions.py +194 -0
  25. package/skills/agentforce-offline-experimentation-run/scripts/build_report.py +366 -378
  26. package/skills/agentforce-offline-experimentation-run/scripts/resolve-token.sh +14 -4
  27. package/skills/agentforce-offline-experimentation-run/scripts/sfap_replay.py +472 -796
  28. package/skills/commerce-react-storefront-create/SKILL.md +350 -0
  29. package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +2 -2
  30. package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +28 -16
  31. package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +18 -1
  32. package/skills/dx-lifecycle-center/SKILL.md +5 -2
  33. package/skills/dx-lifecycle-center/assets/widgets/connect-connected/connect-connected.json +65 -0
  34. package/skills/dx-lifecycle-center/assets/widgets/connect-connected/schema.json +32 -0
  35. package/skills/dx-lifecycle-center/assets/widgets/connect-sign-in/connect-sign-in.json +57 -0
  36. package/skills/dx-lifecycle-center/assets/widgets/connect-sign-in/schema.json +21 -0
  37. package/skills/dx-lifecycle-center/examples/workflows.md +20 -6
  38. package/skills/dx-lifecycle-center/references/connect-org.md +21 -2
  39. package/skills/dx-lifecycle-center/references/deploy.md +2 -2
  40. package/skills/dx-lifecycle-center/references/list-orgs.md +1 -1
  41. package/skills/dx-lifecycle-center/scripts/card-policy.jq +71 -0
  42. package/skills/dx-lifecycle-center/scripts/fill-widget.jq +29 -0
  43. package/skills/dx-lifecycle-center/scripts/widget.sh +28 -0
  44. package/skills/dx-org-devhub-configure/SKILL.md +85 -84
  45. package/skills/dx-org-devhub-configure/examples/status-output.md +25 -2
  46. package/skills/dx-org-devhub-configure/scripts/devhub.sh +265 -140
  47. package/skills/dx-org-devhub-configure/scripts/test-devhub.sh +100 -15
  48. package/skills/dx-packaging-move-to-2gp-migrate/SKILL.md +89 -0
  49. package/skills/dx-packaging-move-to-2gp-migrate/references/api-operations.md +164 -0
  50. package/skills/dx-scratch-def-configure/SKILL.md +163 -0
  51. package/skills/dx-scratch-def-configure/examples/confirm-card.md +53 -0
  52. package/skills/dx-scratch-def-configure/examples/report-recipes.md +100 -0
  53. package/skills/dx-scratch-def-configure/references/features.json +3270 -0
  54. package/skills/dx-scratch-def-configure/references/project-scratch-def.schema.json +1841 -0
  55. package/skills/dx-scratch-def-configure/references/settings-map.json +682 -0
  56. package/skills/dx-scratch-def-configure/scripts/lib_scratch_def.py +1306 -0
  57. package/skills/dx-scratch-def-configure/scripts/requirements.txt +3 -0
  58. package/skills/dx-scratch-def-configure/scripts/scrape_config_values.py +848 -0
  59. package/skills/education-cloud-domain-configure/SKILL.md +22 -24
  60. package/skills/education-cloud-domain-configure/references/dependent-features.md +9 -0
  61. package/skills/education-cloud-domain-configure/references/domain-features.md +15 -0
  62. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +8 -0
  63. package/skills/education-cloud-domain-configure/references/setup-widgets.md +123 -0
  64. package/skills/education-cloud-multi-campus-configure/SKILL.md +1 -1
  65. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +1 -1
  66. package/skills/experience-design-validate/SKILL.md +1 -1
  67. package/skills/experience-search-coordinate/SKILL.md +19 -13
  68. package/skills/experience-search-coordinate/references/content-route.md +79 -1
  69. package/skills/experience-search-coordinate/references/media-route.md +30 -4
  70. package/skills/experience-ui-bundle-features-generate/SKILL.md +1 -1
  71. package/skills/experience-ui-bundle-mfa-configure/SKILL.md +1 -1
  72. package/skills/field-service-data-capture-migrate/SKILL.md +1 -1
  73. package/skills/platform-apex-generate/SKILL.md +14 -11
  74. package/skills/platform-apex-test-generate/SKILL.md +2 -1
  75. package/skills/platform-datamask-run/SKILL.md +52 -71
  76. package/skills/platform-datamask-run/references/api-surface.md +10 -4
  77. package/skills/platform-datamask-run/references/policy-authoring.md +56 -7
  78. package/skills/platform-datamask-run/references/run-and-abort.md +17 -10
  79. package/skills/platform-datamask-run/scripts/poll-job.sh +15 -25
  80. package/skills/platform-dataspace-access-configure/SKILL.md +19 -1
  81. package/skills/platform-metadata-deploy/README.md +1 -1
  82. package/skills/platform-metadata-deploy/SKILL.md +114 -15
  83. package/skills/platform-metadata-deploy/references/agent-deployment-guide.md +3 -4
  84. package/skills/platform-metadata-deploy/references/orchestration.md +2 -3
  85. package/skills/platform-metadata-discovery-get/SKILL.md +185 -0
  86. package/skills/platform-metadata-discovery-get/references/api-map.md +25 -0
  87. package/skills/platform-metadata-discovery-get/references/h360-routes.md +30 -0
  88. package/skills/platform-metadata-discovery-get/references/scoped-soql.md +52 -0
  89. package/skills/platform-metadata-grounding-get/SKILL.md +170 -0
  90. package/skills/platform-policy-rule-generate/SKILL.md +84 -232
  91. package/skills/platform-policy-rule-generate/references/condition-model.md +47 -0
  92. package/skills/platform-policy-rule-generate/references/condition-templates.md +89 -0
  93. package/skills/platform-policy-rule-generate/references/conjunction-compiler.md +25 -0
  94. package/skills/platform-policy-rule-generate/references/conjunction-expression.md +22 -0
  95. package/skills/platform-policy-rule-generate/references/consumer-all.md +9 -0
  96. package/skills/platform-policy-rule-generate/references/consumer-datacloud-ui.md +91 -0
  97. package/skills/platform-policy-rule-generate/references/deploy-errors.md +24 -36
  98. package/skills/platform-policy-rule-generate/references/examples/ExistsJoinRLS/package.xml +12 -0
  99. package/skills/platform-policy-rule-generate/references/examples/ExistsJoinRLS/policyRuleDefinitionSets/ExistsJoinRLS.policyRuleDefinitionSet +6 -0
  100. package/skills/platform-policy-rule-generate/references/examples/ExistsJoinRLS/policyRuleDefinitions/ExistsJoinRLS_Rule_1.policyRuleDefinition +91 -0
  101. package/skills/platform-policy-rule-generate/references/examples/FieldReadDeny/package.xml +12 -0
  102. package/skills/platform-policy-rule-generate/references/examples/FieldReadDeny/policyRuleDefinitionSets/FieldReadDeny.policyRuleDefinitionSet +6 -0
  103. package/skills/platform-policy-rule-generate/references/examples/FieldReadDeny/policyRuleDefinitions/FieldReadDeny_Rule_1.policyRuleDefinition +72 -0
  104. package/skills/platform-policy-rule-generate/references/examples/LastCharactersFieldMasking/package.xml +12 -0
  105. package/skills/platform-policy-rule-generate/references/examples/LastCharactersFieldMasking/policyRuleDefinitionSets/LastCharactersFieldMasking.policyRuleDefinitionSet +6 -0
  106. package/skills/platform-policy-rule-generate/references/examples/LastCharactersFieldMasking/policyRuleDefinitions/LastCharactersFieldMasking_Rule_1.policyRuleDefinition +77 -0
  107. package/skills/platform-policy-rule-generate/references/examples/PrincipalFieldMatchRLS/package.xml +12 -0
  108. package/skills/platform-policy-rule-generate/references/examples/PrincipalFieldMatchRLS/policyRuleDefinitionSets/PrincipalFieldMatchRLS.policyRuleDefinitionSet +6 -0
  109. package/skills/platform-policy-rule-generate/references/examples/PrincipalFieldMatchRLS/policyRuleDefinitions/PrincipalFieldMatchRLS_Rule_1.policyRuleDefinition +77 -0
  110. package/skills/platform-policy-rule-generate/references/examples/TaggedObjectAccess/package.xml +12 -0
  111. package/skills/platform-policy-rule-generate/references/examples/TaggedObjectAccess/policyRuleDefinitionSets/TaggedObjectAccess.policyRuleDefinitionSet +4 -0
  112. package/skills/platform-policy-rule-generate/references/examples/TaggedObjectAccess/policyRuleDefinitions/TaggedObjectAccess_Rule_1.policyRuleDefinition +65 -0
  113. package/skills/platform-policy-rule-generate/references/headless-transport.md +87 -0
  114. package/skills/platform-policy-rule-generate/references/policy-schema-full.md +17 -10
  115. package/skills/platform-policy-rule-generate/references/policy-shapes.md +74 -0
  116. package/skills/platform-policy-rule-generate/references/principal-expression.md +31 -0
  117. package/skills/platform-policy-rule-generate/references/reference-resolution.md +51 -0
  118. package/skills/platform-policy-rule-generate/references/resource-expression.md +48 -0
  119. package/skills/platform-policy-rule-generate/references/runtime-context.md +9 -0
  120. package/skills/platform-policy-rule-generate/references/templates-access.md +11 -9
  121. package/skills/platform-policy-rule-generate/references/templates-advanced.md +4 -2
  122. package/skills/platform-policy-rule-generate/references/templates-record.md +37 -22
  123. package/skills/platform-policy-rule-generate/references/templates-transform.md +10 -8
  124. package/skills/platform-policy-rule-generate/references/templates.md +2 -2
  125. package/skills/platform-policy-rule-generate/references/ui-compatibility.md +35 -17
  126. package/skills/platform-report-generate/SKILL.md +9 -4
  127. package/skills/platform-report-generate/references/chart-types.md +26 -2
  128. package/skills/platform-report-generate/references/column-names.md +43 -0
  129. package/skills/platform-report-generate/references/errors-and-troubleshooting.md +1 -1
  130. package/skills/platform-report-generate/references/verification-checklist.md +10 -2
  131. package/skills/platform-value-set-generate/SKILL.md +19 -19
  132. package/skills/platform-widget-generate/SKILL.md +25 -51
  133. package/skills/platform-widget-generate/examples/formulas.json +19 -2
  134. package/skills/platform-widget-generate/examples/localized-labels.json +57 -0
  135. package/skills/platform-widget-generate/references/widget-actions.md +29 -0
  136. package/skills/platform-widget-generate/references/widget-bundle-validation.md +1 -0
  137. package/skills/platform-widget-generate/references/widget-formulas.md +24 -7
  138. package/skills/platform-widget-generate/references/widget-gotchas.md +10 -0
  139. package/skills/platform-widget-generate/references/widget-labels.md +78 -0
  140. package/skills/platform-widget-generate/references/widget-styling.md +12 -0
  141. package/skills/sales-call-scoring-configure/SKILL.md +17 -10
  142. package/skills/sales-call-scoring-configure/scripts/enable-call-scoring.sh +55 -33
  143. package/skills/service-digital-engagement-channel-configure/SKILL.md +11 -7
  144. package/skills/service-digital-engagement-channel-configure/assets/messaging_channel_template.xml +33 -0
  145. package/skills/service-digital-engagement-channel-configure/references/channel_settings.md +123 -16
  146. package/skills/service-digital-engagement-deployment-configure/SKILL.md +37 -100
  147. package/skills/service-digital-engagement-deployment-configure/assets/esd_web_update_template.xml +83 -8
  148. package/skills/service-digital-engagement-deployment-configure/examples/esd_web_prechat_fields.xml +74 -0
  149. package/skills/service-digital-engagement-deployment-configure/references/connect_api_creation.md +13 -3
  150. package/skills/service-digital-engagement-deployment-configure/references/deployment_settings.md +13 -78
  151. package/skills/service-digital-engagement-deployment-configure/references/onboarding_contract.md +5 -6
  152. package/skills/service-digital-engagement-deployment-configure/references/prechat_forms.md +245 -0
  153. package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +164 -120
  154. package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +61 -37
  155. package/skills/service-itsm-agentic-setup-incident-management/references/audit-tracking.md +42 -0
  156. package/skills/service-itsm-agentic-setup-incident-management/references/broadcast-communications.md +23 -0
  157. package/skills/service-itsm-agentic-setup-incident-management/references/email-to-incident.md +23 -0
  158. package/skills/service-itsm-agentic-setup-incident-management/references/incident-dashboards.md +25 -0
  159. package/skills/service-itsm-agentic-setup-incident-management/references/incident-persona-psg.md +30 -146
  160. package/skills/service-itsm-agentic-setup-incident-management/references/incident-preferences.md +32 -136
  161. package/skills/service-itsm-agentic-setup-incident-management/references/major-incident-management.md +32 -155
  162. package/skills/service-itsm-agentic-setup-incident-management/references/manage-incidents-with-ai.md +45 -0
  163. package/skills/service-itsm-agentic-setup-incident-management/references/priority-matrix.md +32 -0
  164. package/skills/service-itsm-agentic-setup-incident-management/references/service-mgmt-privilege.md +30 -140
  165. package/skills/service-itsm-agentic-setup-incident-management/references/sla-milestones.md +110 -0
@@ -5,9 +5,13 @@
5
5
  #
6
6
  # Dev Hub is the org feature that lets you create and manage scratch orgs and
7
7
  # second-generation (2GP)/unlocked packages. This script reports whether Dev Hub
8
- # is enabled, ENABLES it (or configures its sub-preferences) via a Metadata API
9
- # deploy, shows the org's scratch org ALLOCATION (Active/Daily limits and how
10
- # many remain), and lists the active scratch orgs created from the Dev Hub.
8
+ # is enabled, shows the org's scratch org ALLOCATION (Active/Daily limits and how
9
+ # many remain, with pre-flight warnings), reports the org EDITION, reads the
10
+ # current DevHubSettings PREFERENCES, ENABLES Dev Hub (or configures its
11
+ # sub-preferences) via a Metadata API deploy, and lists the active scratch orgs
12
+ # created from the Dev Hub. It can also list the authenticated orgs on this
13
+ # machine. It does NOT authenticate new orgs — that is a precondition; the script
14
+ # only prints the login command as guidance.
11
15
  #
12
16
  # How enablement works: the Dev Hub master switch is the deployable metadata
13
17
  # field DevHubSettings.enableScratchOrgManagementPref (there is NO "enableDevHub"
@@ -25,29 +29,30 @@
25
29
  # reliable enablement test.)
26
30
  #
27
31
  # Usage:
28
- # devhub.sh [org] # status: is Dev Hub on? + allocation
29
- # devhub.sh [org] --allocation # scratch org allocation only
32
+ # devhub.sh [org] # status: is Dev Hub on? + edition + allocation
33
+ # devhub.sh [org] --allocation # scratch org allocation only (with warnings)
34
+ # devhub.sh [org] --show-settings # read current DevHubSettings preferences
30
35
  # devhub.sh [org] --list-scratch # list this Dev Hub's active scratch orgs
36
+ # devhub.sh --list-orgs # list authenticated orgs on this machine
31
37
  # devhub.sh [org] --enable # validate enabling Dev Hub (dry run)
32
38
  # devhub.sh [org] --enable --apply # actually enable Dev Hub (irreversible)
33
39
  # devhub.sh [org] --enable --packaging --apply # enable + turn on 2GP
34
40
  # devhub.sh [org] --configure --snapshots --apply # set a sub-pref (Dev Hub already on)
35
41
  # devhub.sh [org] --json # machine-readable status
36
42
  # devhub.sh --fail-if-disabled # exit 3 if Dev Hub is not enabled (CI gate)
37
- # devhub.sh [alias] --instance-url https://my-domain.my.salesforce.com --enable --apply
38
- # # log in to that instance, then enable Dev Hub
39
- # devhub.sh https://my-domain.my.salesforce.com # bare URL: log in to that instance
40
- # devhub.sh myalias --login # just authenticate an org via the browser
41
43
  #
42
- # "org" is an alias or username. If omitted, the default target-org is used,
43
- # and if that is unset, the default target-dev-hub. With --login/--instance-url,
44
- # a supplied positional is used as the CLI alias for the org being logged in to.
45
- # A bare positional that looks like a URL (contains "://") is treated as an
46
- # --instance-url to log in to, not as an org alias.
44
+ # "org" is an alias or username. If omitted, the default target-org is used, and
45
+ # if that is unset, the default target-dev-hub. If neither is set, the script
46
+ # lists the authenticated orgs and asks you to pass one or set a default.
47
47
  #
48
48
  # Options:
49
- # --allocation, -A Show only the scratch org allocation (Active/Daily).
49
+ # --allocation, -A Show only the scratch org allocation (Active/Daily),
50
+ # with a WARN when <2 remain and a NOTE above 80% usage.
51
+ # --show-settings, -s Read and show which DevHubSettings preferences are
52
+ # currently enabled/disabled (Tooling API). Dev Hub must
53
+ # be on.
50
54
  # --list-scratch, -l List active scratch orgs created from this Dev Hub.
55
+ # --list-orgs List the orgs authenticated with the Salesforce CLI.
51
56
  # --enable, -e Enable Dev Hub (deploys enableScratchOrgManagementPref
52
57
  # =true). Defaults to a validate-only dry run; add
53
58
  # --apply to actually enable. May be combined with the
@@ -63,15 +68,14 @@
63
68
  # "enable"; VALUE is true|false). Repeatable.
64
69
  # --apply Actually deploy (default for --enable/--configure is a
65
70
  # validate-only dry run).
66
- # --login Authenticate an org first via `sf org login web`
67
- # (opens a browser). Runs before the requested action.
68
- # --instance-url URL Log in to a Salesforce instance (My Domain, sandbox,
69
- # or pre-release/scratch instance) with
70
- # `sf org login web --instance-url URL`. Implies --login.
71
71
  # --json Emit machine-readable JSON.
72
72
  # --fail-if-disabled Exit 3 if Dev Hub is not enabled on the target org.
73
73
  # --help, -h Show this help.
74
74
  #
75
+ # Authentication is a precondition. If an org is not authenticated, the script
76
+ # prints the exact `sf org login web` command to run — it never opens a browser
77
+ # or logs in for you.
78
+ #
75
79
  # Exit codes:
76
80
  # 0 success
77
81
  # 1 the org could not be queried, or a deploy failed (auth/connection/deploy)
@@ -98,21 +102,21 @@ command -v jq >/dev/null 2>&1 || die "'jq' not found on PATH."
98
102
  export SF_AUTOUPDATE_DISABLE=true
99
103
 
100
104
  # --- constants -----------------------------------------------------------
101
- SETTINGS_API_VERSION="47.0" # DevHubSettings requires API >= 47.0
105
+ SETTINGS_API_VERSION="62.0" # DevHubSettings: base fields >=47.0, but
106
+ # enableShapeExportPref needs >=55.0 and
107
+ # enableScratchOrgSnapshotPref needs >=61.0,
108
+ # so read/deploy at a version that has them all.
102
109
  DEVHUB_SWITCH="enableScratchOrgManagementPref"
103
110
 
104
111
  # --- option state --------------------------------------------------------
105
112
  TARGET="" # org alias/username (positional or resolved default)
106
113
  TARGET_EXPLICIT=0 # 1 when the caller supplied an org alias/username
107
- MODE="status" # status | allocation | list | deploy
108
- MODE_EXPLICIT=0 # 1 when the caller selected an operation
114
+ MODE="status" # status | allocation | settings | list | listorgs | deploy
109
115
  ENABLING=0 # 1 when --enable was passed (seeds the master switch)
110
116
  OUTPUT="human" # human | json
111
117
  APPLY=0 # deploy: 0 = dry run (validate only), 1 = real deploy
112
118
  FAIL_IF_DISABLED=0
113
119
  PREFS=() # requested DevHubSettings prefs (KEY=VALUE)
114
- DO_LOGIN=0 # 1 when --login or --instance-url requests a browser login
115
- LOGIN_INSTANCE_URL="" # instance URL for `sf org login web --instance-url`
116
120
 
117
121
  # --- run state -----------------------------------------------------------
118
122
  DEVHUB_STATE="" # enabled | disabled | auth_error
@@ -120,6 +124,9 @@ DEVHUB_ERRNAME="" # error .name when auth_error
120
124
  DEVHUB_ERRMSG=""
121
125
  SCRATCH_COUNT="" # ScratchOrgInfo count when enabled
122
126
  INSTANCE_URL="" # resolved org instance URL (for the Setup deep link)
127
+ ORG_EDITION="" # Organization.OrganizationType (best effort)
128
+ ORG_IS_SANDBOX="" # true|false (best effort)
129
+ ORG_FACTS_DONE=0 # 1 once org_facts has run
123
130
 
124
131
  usage() {
125
132
  sed -n '2,/^set -uo/p' "$0" | sed '/^set -uo/d; s/^# \{0,1\}//; s/^#//'
@@ -128,7 +135,7 @@ usage() {
128
135
  # Set MODE, rejecting a second conflicting mode selection.
129
136
  set_mode() {
130
137
  if [ "$MODE" != "status" ] && [ "$MODE" != "$1" ]; then
131
- die "Choose only one of --allocation, --list-scratch, --enable/--configure."
138
+ die "Choose only one of --allocation, --show-settings, --list-scratch, --list-orgs, --enable/--configure."
132
139
  fi
133
140
  MODE="$1"
134
141
  }
@@ -174,44 +181,31 @@ add_pref() {
174
181
  # --- argument parsing ----------------------------------------------------
175
182
  while [ $# -gt 0 ]; do
176
183
  case "$1" in
177
- --allocation|-A) MODE_EXPLICIT=1; set_mode allocation; shift ;;
178
- --list-scratch|-l) MODE_EXPLICIT=1; set_mode list; shift ;;
179
- --enable|-e) MODE_EXPLICIT=1; ENABLING=1; set_mode deploy; shift ;;
180
- --configure|-c) MODE_EXPLICIT=1; set_mode deploy; shift ;;
181
- --packaging) MODE_EXPLICIT=1; add_pref "enablePackaging2=true"; set_mode deploy; shift ;;
182
- --snapshots) MODE_EXPLICIT=1; add_pref "enableScratchOrgSnapshotPref=true"; set_mode deploy; shift ;;
183
- --shape) MODE_EXPLICIT=1; add_pref "enableShapeExportPref=true"; set_mode deploy; shift ;;
184
- --scratch-management) MODE_EXPLICIT=1; add_pref "$DEVHUB_SWITCH=true"; set_mode deploy; shift ;;
184
+ --allocation|-A) set_mode allocation; shift ;;
185
+ --show-settings|-s) set_mode settings; shift ;;
186
+ --list-scratch|-l) set_mode list; shift ;;
187
+ --list-orgs) set_mode listorgs; shift ;;
188
+ --enable|-e) ENABLING=1; set_mode deploy; shift ;;
189
+ --configure|-c) set_mode deploy; shift ;;
190
+ --packaging) add_pref "enablePackaging2=true"; set_mode deploy; shift ;;
191
+ --snapshots) add_pref "enableScratchOrgSnapshotPref=true"; set_mode deploy; shift ;;
192
+ --shape) add_pref "enableShapeExportPref=true"; set_mode deploy; shift ;;
193
+ --scratch-management) add_pref "$DEVHUB_SWITCH=true"; set_mode deploy; shift ;;
185
194
  --pref)
186
195
  shift
187
196
  [ $# -gt 0 ] || die "--pref requires a KEY=VALUE argument (e.g. --pref enablePackaging2=true)."
188
197
  case "$1" in *=*) : ;; *) die "--pref expects KEY=VALUE (got: '$1')." ;; esac
189
- MODE_EXPLICIT=1; add_pref "$1"; set_mode deploy; shift ;;
198
+ add_pref "$1"; set_mode deploy; shift ;;
190
199
  --pref=*)
191
200
  kv="${1#*=}"
192
201
  case "$kv" in *=*) : ;; *) die "--pref expects KEY=VALUE (got: '$kv')." ;; esac
193
- MODE_EXPLICIT=1; add_pref "$kv"; set_mode deploy; shift ;;
202
+ add_pref "$kv"; set_mode deploy; shift ;;
194
203
  --apply) APPLY=1; shift ;;
195
- --login) DO_LOGIN=1; shift ;;
196
- --instance-url)
197
- shift
198
- [ $# -gt 0 ] || die "--instance-url requires a URL (e.g. --instance-url https://my-domain.my.salesforce.com)."
199
- [ -n "$LOGIN_INSTANCE_URL" ] && die "Specify only one instance URL. Got extra: $1"
200
- LOGIN_INSTANCE_URL="$1"; DO_LOGIN=1; shift ;;
201
- --instance-url=*)
202
- [ -n "$LOGIN_INSTANCE_URL" ] && die "Specify only one instance URL. Got extra: ${1#*=}"
203
- LOGIN_INSTANCE_URL="${1#*=}"; DO_LOGIN=1; shift ;;
204
204
  --json) OUTPUT="json"; shift ;;
205
205
  --fail-if-disabled) FAIL_IF_DISABLED=1; shift ;;
206
206
  --help|-h) usage; exit 0 ;;
207
207
  --) shift; break ;;
208
208
  -*) die "Unknown option: $1 (try --help)" ;;
209
- *://*)
210
- # A bare URL positional is an instance URL to log in to (My Domain,
211
- # sandbox, or pre-release/scratch instance), not an org alias — an alias or
212
- # username never contains "://". Route it to the login path.
213
- [ -n "$LOGIN_INSTANCE_URL" ] && die "Specify only one instance URL. Got extra: $1"
214
- LOGIN_INSTANCE_URL="$1"; DO_LOGIN=1; shift ;;
215
209
  *)
216
210
  [ -n "$TARGET" ] && die "Specify only one org. Got extra: $1"
217
211
  TARGET="$1"; TARGET_EXPLICIT=1; shift ;;
@@ -235,13 +229,63 @@ if [ "$APPLY" -eq 1 ] && [ "$TARGET_EXPLICIT" -ne 1 ]; then
235
229
  die "--apply requires an explicit org alias or username; refusing to mutate an implicit default org."
236
230
  fi
237
231
 
238
- # A supplied instance URL must use an HTTPS Salesforce-owned hostname.
239
- if [ -n "$LOGIN_INSTANCE_URL" ]; then
240
- case "$LOGIN_INSTANCE_URL" in
241
- https://*.salesforce.com|https://*.force.com|https://*.salesforce.mil) : ;;
242
- *) die "--instance-url must use an https:// Salesforce hostname (got: '$LOGIN_INSTANCE_URL')." ;;
243
- esac
244
- fi
232
+ # Bound one Salesforce CLI call. `sf org list` can wait on a keychain or
233
+ # network prompt longer than the eval harness tool timeout, and the agent
234
+ # then stops before it writes report.md. A parent-side wait is used because
235
+ # an alarm set before exec is not reliable on every platform.
236
+ sf_bounded() {
237
+ local out_file pid i
238
+ out_file="$(mktemp)"
239
+ sf "$@" >"$out_file" 2>/dev/null &
240
+ pid=$!
241
+ i=0
242
+ while [ "$i" -lt 20 ]; do
243
+ if ! kill -0 "$pid" 2>/dev/null; then
244
+ wait "$pid"
245
+ local rc=$?
246
+ cat "$out_file"
247
+ rm -f "$out_file"
248
+ return "$rc"
249
+ fi
250
+ sleep 1
251
+ i=$((i + 1))
252
+ done
253
+ kill "$pid" 2>/dev/null || true
254
+ pkill -P "$pid" 2>/dev/null || true
255
+ wait "$pid" 2>/dev/null || true
256
+ rm -f "$out_file"
257
+ return 142
258
+ }
259
+
260
+ # --- org discovery -------------------------------------------------------
261
+ # List the orgs authenticated with the Salesforce CLI. Handles both the newer
262
+ # {result:{nonScratchOrgs,scratchOrgs}} shape and the older {result:[...]}.
263
+ list_orgs() {
264
+ local out
265
+ out="$(sf_bounded org list --json 2>/dev/null)" || { echo "Could not list authenticated orgs (is the Salesforce CLI installed and working?)." >&2; return 1; }
266
+ local norm
267
+ norm="$(printf '%s' "$out" | jq -c '
268
+ [ ((.result.nonScratchOrgs // []) + (.result.scratchOrgs // []) + (.result.other // []) +
269
+ (if (.result|type)=="array" then .result else [] end))[]
270
+ | {alias:(.alias // null), username:(.username // null),
271
+ connectedStatus:(.connectedStatus // .status // null),
272
+ isDefault:((.isDefaultUsername // .isDefault) // false)} ]' 2>/dev/null)"
273
+ if [ "$OUTPUT" = "json" ]; then
274
+ printf '%s\n' "${norm:-[]}"
275
+ return 0
276
+ fi
277
+ local n
278
+ n="$(printf '%s' "${norm:-[]}" | jq 'length' 2>/dev/null)"
279
+ if [ -z "$n" ] || [ "$n" = "0" ]; then
280
+ echo "No orgs are authenticated with the Salesforce CLI."
281
+ echo " Log in with: sf org login web --alias <my-alias>"
282
+ return 0
283
+ fi
284
+ echo "Authenticated orgs:"
285
+ printf '%s' "$norm" | jq -r '.[]
286
+ | " \(.alias // .username // "?")\t\(.username // "?")\t\(.connectedStatus // "?")\(if .isDefault then " (default)" else "" end)"' \
287
+ | while IFS=$'\t' read -r a u s; do printf " %-24s %-32s %s\n" "$a" "$u" "$s"; done
288
+ }
245
289
 
246
290
  # --- org resolution ------------------------------------------------------
247
291
  resolve_target() {
@@ -252,56 +296,16 @@ resolve_target() {
252
296
  [ -n "$TARGET" ] && return 0
253
297
  echo "No org specified and no default target-org / target-dev-hub is set." >&2
254
298
  echo >&2
299
+ if [ "$OUTPUT" != "json" ]; then
300
+ list_orgs >&2 || true
301
+ echo >&2
302
+ fi
255
303
  echo "Pass an org alias/username, or set a default:" >&2
256
304
  echo " sf org login web --alias <my-alias> # authenticate an org" >&2
257
305
  echo " sf config set target-org <my-alias> # make it the default" >&2
258
306
  exit 2
259
307
  }
260
308
 
261
- # --- interactive login ---------------------------------------------------
262
- # Authenticate an org with `sf org login web`, honoring an optional
263
- # --instance-url (My Domain, sandbox, or pre-release/scratch instance) and, when
264
- # an org identifier was supplied, using it as the CLI alias. Opens a browser for
265
- # the OAuth flow; returns non-zero if the login fails or times out.
266
- do_login() {
267
- local args=(org login web)
268
- [ -n "$LOGIN_INSTANCE_URL" ] && args+=(--instance-url "$LOGIN_INSTANCE_URL")
269
- [ -n "$TARGET" ] && args+=(--alias "$TARGET")
270
-
271
- if [ "$OUTPUT" != "json" ]; then
272
- echo "Authenticating with the Salesforce CLI (a browser window will open)..."
273
- [ -n "$LOGIN_INSTANCE_URL" ] && echo " instance URL: $LOGIN_INSTANCE_URL"
274
- [ -n "$TARGET" ] && echo " alias: $TARGET"
275
- echo " Complete the login in the browser promptly (the session times out)."
276
- echo
277
- fi
278
-
279
- local out ok
280
- out="$(sf "${args[@]}" --json 2>/dev/null)"
281
- ok="$(printf '%s' "$out" | jq -r 'if .status==0 then "true" else "false" end' 2>/dev/null)"
282
-
283
- if [ "$ok" != "true" ]; then
284
- local msg
285
- msg="$(printf '%s' "$out" | jq -r '.message // empty' 2>/dev/null)"
286
- if [ "$OUTPUT" = "json" ]; then
287
- jq -n --arg msg "${msg:-login failed}" '{status:"login_failed", error:$msg}'
288
- else
289
- echo "Login failed: ${msg:-unknown error}." >&2
290
- echo " Retry in your own terminal, or use the device flow if the browser flow keeps timing out:" >&2
291
- echo " sf org login device${LOGIN_INSTANCE_URL:+ --instance-url $(shell_quote "$LOGIN_INSTANCE_URL")}${TARGET:+ --alias $(shell_quote "$TARGET")}" >&2
292
- fi
293
- return 1
294
- fi
295
-
296
- # Adopt the authenticated username as the target when none was supplied, so the
297
- # rest of the run operates on the org the user just logged in to.
298
- if [ -z "$TARGET" ]; then
299
- TARGET="$(printf '%s' "$out" | jq -r '.result.username // empty' 2>/dev/null)"
300
- fi
301
- [ "$OUTPUT" != "json" ] && echo "Authenticated: ${TARGET:-(org)}" && echo
302
- return 0
303
- }
304
-
305
309
  # Render one dynamic value as a safe, copy-pasteable shell argument.
306
310
  shell_quote() {
307
311
  printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"
@@ -332,6 +336,61 @@ detect_devhub() {
332
336
  esac
333
337
  }
334
338
 
339
+ # --- org facts (edition / sandbox) ---------------------------------------
340
+ # Best effort: populate ORG_EDITION, ORG_IS_SANDBOX. Never aborts.
341
+ org_facts() {
342
+ [ "$ORG_FACTS_DONE" -eq 1 ] && return 0
343
+ ORG_FACTS_DONE=1
344
+ local out
345
+ out="$(sf data query --query "SELECT OrganizationType, IsSandbox FROM Organization" \
346
+ --target-org "$TARGET" --json 2>/dev/null)" || return 0
347
+ ORG_EDITION="$(printf '%s' "$out" | jq -r '.result.records[0].OrganizationType // empty' 2>/dev/null)"
348
+ # Use an explicit null-check rather than `// empty`: IsSandbox is a boolean, and
349
+ # `false // empty` would drop a legitimate `false`, reporting a non-sandbox org
350
+ # as "unknown" (isSandbox:null) instead of false.
351
+ ORG_IS_SANDBOX="$(printf '%s' "$out" | jq -r '(.result.records[0].IsSandbox) as $s | if $s==null then empty else ($s|tostring) end' 2>/dev/null)"
352
+ return 0
353
+ }
354
+
355
+ # --- DevHubSettings preferences read -------------------------------------
356
+ # Reads the current DevHubSettings via the Tooling API and echoes TSV rows
357
+ # "key<TAB>true|false" for each enable* preference. Returns non-zero on failure.
358
+ read_devhub_settings() {
359
+ local out
360
+ out="$(sf api request rest "/services/data/v${SETTINGS_API_VERSION}/tooling/sobjects/DevHubSettings/DevHub" \
361
+ --target-org "$TARGET" --json 2>/dev/null)" || return 1
362
+ # `sf api request rest --json` returns the raw REST body; be robust to a
363
+ # possible {result:{...}} wrapper too.
364
+ printf '%s' "$out" | jq -r '
365
+ (.Metadata // .result.Metadata // {})
366
+ | to_entries[]? | select(.key|startswith("enable"))
367
+ | [.key, (.value|tostring)] | @tsv' 2>/dev/null
368
+ }
369
+
370
+ render_settings() {
371
+ local rows
372
+ rows="$(read_devhub_settings)"
373
+ if [ -z "$rows" ]; then
374
+ if [ "$OUTPUT" = "json" ]; then
375
+ jq -n --arg org "$TARGET" '{org:$org, settings:{}, note:"Could not read DevHubSettings (Dev Hub may be off, or you lack access)."}'
376
+ else
377
+ echo "Could not read DevHubSettings for '$TARGET' — Dev Hub may be off, or you may lack access." >&2
378
+ fi
379
+ return 1
380
+ fi
381
+ if [ "$OUTPUT" = "json" ]; then
382
+ printf '%s\n' "$rows" | jq -R -s --arg org "$TARGET" '
383
+ {org:$org,
384
+ settings:( [ split("\n")[] | select(length>0) | split("\t") | {(.[0]): (.[1]=="true")} ] | add // {} )}'
385
+ return 0
386
+ fi
387
+ echo "DevHub preferences for '$TARGET':"
388
+ printf '%s\n' "$rows" | while IFS=$'\t' read -r k v; do
389
+ [ -n "$k" ] || continue
390
+ if [ "$v" = "true" ]; then printf " %-34s ON\n" "$k"; else printf " %-34s OFF\n" "$k"; fi
391
+ done
392
+ }
393
+
335
394
  # Echoes TSV rows "name<TAB>remaining<TAB>max" for the two scratch org limits,
336
395
  # or nothing if the org could not be queried.
337
396
  allocation_rows() {
@@ -343,6 +402,13 @@ allocation_rows() {
343
402
  | [.name, (.remaining|tostring), (.max|tostring)] | @tsv'
344
403
  }
345
404
 
405
+ # Integer percentage used = (max-rem)*100/max, or empty when max is empty/0.
406
+ pct_used() {
407
+ local max="$1" rem="$2"
408
+ { [ -n "$max" ] && [ "$max" -gt 0 ] 2>/dev/null; } || { echo ""; return; }
409
+ echo $(( (max - rem) * 100 / max ))
410
+ }
411
+
346
412
  # Resolve the org instance URL for the Setup deep link (best effort).
347
413
  resolve_instance_url() {
348
414
  [ -n "$INSTANCE_URL" ] && return 0
@@ -351,6 +417,7 @@ resolve_instance_url() {
351
417
  }
352
418
 
353
419
  # --- auth error reporting ------------------------------------------------
420
+ # Guidance only: print the exact login command to run. Never logs in.
354
421
  print_auth_error() {
355
422
  if [ "$OUTPUT" = "json" ]; then
356
423
  jq -n --arg org "$TARGET" --arg name "$DEVHUB_ERRNAME" --arg msg "$DEVHUB_ERRMSG" \
@@ -360,22 +427,11 @@ print_auth_error() {
360
427
  printf " %-30s could not query org (%s)\n" "$TARGET" "${DEVHUB_ERRNAME:-error}"
361
428
  [ -n "$DEVHUB_ERRMSG" ] && printf " %-30s (%s)\n" "" "$DEVHUB_ERRMSG"
362
429
  echo
363
- if [ -n "$LOGIN_INSTANCE_URL" ]; then
364
- # An instance URL was provided — show the exact command for it, and offer to
365
- # let the script run it directly via --login.
366
- echo " To authenticate against $LOGIN_INSTANCE_URL, run:"
367
- echo " sf org login web --instance-url $(shell_quote "$LOGIN_INSTANCE_URL")${TARGET:+ --alias $(shell_quote "$TARGET")}"
368
- echo " Or let this script log you in and continue in one step:"
369
- echo " devhub.sh ${TARGET:+$(shell_quote "$TARGET") }--instance-url $(shell_quote "$LOGIN_INSTANCE_URL") --login"
370
- else
371
- echo " To authenticate, run one of:"
372
- echo " sf org login web --alias $(shell_quote "$TARGET") # interactive browser login"
373
- echo " sf org login web --alias <my-alias> --instance-url https://MYDOMAIN.my.salesforce.com # sandbox/My Domain"
374
- echo " Or let this script log you in and continue in one step:"
375
- echo " devhub.sh $(shell_quote "$TARGET") --login # prod/DE/trial"
376
- echo " devhub.sh <my-alias> --instance-url https://MYDOMAIN.my.salesforce.com --login # sandbox/My Domain"
377
- fi
378
- echo " Then re-run. List existing logins with: sf org list"
430
+ echo " This org is not authenticated (or the session expired). Authentication is"
431
+ echo " a precondition for this skill — run one of these yourself, then re-run:"
432
+ echo " sf org login web --alias $(shell_quote "$TARGET") # prod / DE / trial"
433
+ echo " sf org login web --alias <my-alias> --instance-url https://MYDOMAIN.my.salesforce.com # sandbox / My Domain"
434
+ echo " List existing logins with: sf org list"
379
435
  }
380
436
 
381
437
  # --- Setup UI fallback text ----------------------------------------------
@@ -391,22 +447,36 @@ print_ui_fallback() {
391
447
 
392
448
  # --- allocation rendering ------------------------------------------------
393
449
  render_allocation() {
394
- local rows a_max a_rem d_max d_rem
450
+ local rows a_max a_rem d_max d_rem a_pct d_pct
395
451
  rows="$(allocation_rows)" || { echo "Could not read org limits for '$TARGET'." >&2; return 1; }
396
452
  a_rem="$(printf '%s\n' "$rows" | awk -F'\t' '$1=="ActiveScratchOrgs"{print $2}')"
397
453
  a_max="$(printf '%s\n' "$rows" | awk -F'\t' '$1=="ActiveScratchOrgs"{print $3}')"
398
454
  d_rem="$(printf '%s\n' "$rows" | awk -F'\t' '$1=="DailyScratchOrgs"{print $2}')"
399
455
  d_max="$(printf '%s\n' "$rows" | awk -F'\t' '$1=="DailyScratchOrgs"{print $3}')"
456
+ a_pct="$(pct_used "$a_max" "$a_rem")"
457
+ d_pct="$(pct_used "$d_max" "$d_rem")"
400
458
 
401
459
  if [ "$OUTPUT" = "json" ]; then
460
+ local warns=()
461
+ if [ -n "$a_max" ] && [ "$a_max" -gt 0 ] 2>/dev/null && [ "$a_rem" -lt 2 ] 2>/dev/null; then
462
+ warns+=("only $a_rem active scratch org(s) remaining")
463
+ fi
464
+ { [ -n "$a_pct" ] && [ "$a_pct" -gt 80 ] 2>/dev/null; } && warns+=("Active scratch org usage at ${a_pct}%")
465
+ { [ -n "$d_pct" ] && [ "$d_pct" -gt 80 ] 2>/dev/null; } && warns+=("Daily scratch org usage at ${d_pct}%")
466
+ local warn_json="[]"
467
+ if [ "${#warns[@]}" -gt 0 ]; then
468
+ warn_json="$(printf '%s\n' "${warns[@]}" | jq -R . | jq -s .)"
469
+ fi
402
470
  jq -n --arg org "$TARGET" \
403
- --argjson am "${a_max:-null}" --argjson ar "${a_rem:-null}" \
404
- --argjson dm "${d_max:-null}" --argjson dr "${d_rem:-null}" '
471
+ --argjson am "${a_max:-null}" --argjson ar "${a_rem:-null}" --argjson ap "${a_pct:-null}" \
472
+ --argjson dm "${d_max:-null}" --argjson dr "${d_rem:-null}" --argjson dp "${d_pct:-null}" \
473
+ --argjson warnings "$warn_json" '
405
474
  {org:$org,
406
475
  allocation:{
407
- activeScratchOrgs:{max:$am, remaining:$ar, used:(if $am!=null and $ar!=null then ($am-$ar) else null end)},
408
- dailyScratchOrgs:{max:$dm, remaining:$dr, used:(if $dm!=null and $dr!=null then ($dm-$dr) else null end)}
409
- }}'
476
+ activeScratchOrgs:{max:$am, remaining:$ar, used:(if $am!=null and $ar!=null then ($am-$ar) else null end), usedPct:$ap},
477
+ dailyScratchOrgs:{max:$dm, remaining:$dr, used:(if $dm!=null and $dr!=null then ($dm-$dr) else null end), usedPct:$dp}
478
+ },
479
+ warnings:$warnings}'
410
480
  return 0
411
481
  fi
412
482
 
@@ -418,6 +488,12 @@ render_allocation() {
418
488
  printf " %-22s %10s %10s %10s\n" "Limit" "Used" "Remaining" "Max"
419
489
  [ -n "$a_max" ] && printf " %-22s %10s %10s %10s\n" "Active scratch orgs" "$(( a_max - a_rem ))" "$a_rem" "$a_max"
420
490
  [ -n "$d_max" ] && printf " %-22s %10s %10s %10s\n" "Daily scratch orgs" "$(( d_max - d_rem ))" "$d_rem" "$d_max"
491
+ # Pre-flight warnings.
492
+ if [ -n "$a_max" ] && [ "$a_max" -gt 0 ] 2>/dev/null && [ "$a_rem" -lt 2 ] 2>/dev/null; then
493
+ echo " WARN: only $a_rem active scratch org(s) remaining."
494
+ fi
495
+ { [ -n "$a_pct" ] && [ "$a_pct" -gt 80 ] 2>/dev/null; } && echo " NOTE: Active scratch org usage at ${a_pct}% ($(( a_max - a_rem ))/$a_max)."
496
+ { [ -n "$d_pct" ] && [ "$d_pct" -gt 80 ] 2>/dev/null; } && echo " NOTE: Daily scratch org usage at ${d_pct}% ($(( d_max - d_rem ))/$d_max)."
421
497
  echo
422
498
  echo " Active = orgs alive right now; Daily = successful creations in a rolling 24h window."
423
499
  echo " Allocation is set by the Dev Hub's edition (e.g. Developer/trial 3 & 6;"
@@ -459,6 +535,23 @@ cleanup_deploy_temp() {
459
535
  }
460
536
 
461
537
  render_deploy() {
538
+ # Enabling Dev Hub cannot happen on a sandbox. When actually applying an
539
+ # enablement, verify eligibility first and refuse before touching the org.
540
+ if [ "$ENABLING" -eq 1 ] && [ "$APPLY" -eq 1 ]; then
541
+ org_facts
542
+ if [ "$ORG_IS_SANDBOX" = "true" ]; then
543
+ if [ "$OUTPUT" = "json" ]; then
544
+ jq -n --arg org "$TARGET" '{applied:false, enabling:true, success:false, message:"Refused: Dev Hub cannot be enabled on a sandbox. Use a production, Developer Edition, or trial org."}'
545
+ else
546
+ echo "Refused — '$TARGET' is a sandbox, and Dev Hub cannot be enabled on a sandbox."
547
+ echo " Dev Hub must be a production/business org (or a Developer Edition / trial org)."
548
+ echo
549
+ print_ui_fallback
550
+ fi
551
+ return 1
552
+ fi
553
+ fi
554
+
462
555
  # Sub-prefs (packaging, snapshots, shape) require Dev Hub to be on. If the
463
556
  # user is configuring one without also enabling, and Dev Hub is off, warn.
464
557
  if [ "$ENABLING" -eq 0 ] && [ "$DEVHUB_STATE" = "disabled" ] && [ "$OUTPUT" != "json" ]; then
@@ -572,13 +665,17 @@ render_status() {
572
665
  a_max="$(printf '%s\n' "$rows" | awk -F'\t' '$1=="ActiveScratchOrgs"{print $3}')"
573
666
  d_rem="$(printf '%s\n' "$rows" | awk -F'\t' '$1=="DailyScratchOrgs"{print $2}')"
574
667
  d_max="$(printf '%s\n' "$rows" | awk -F'\t' '$1=="DailyScratchOrgs"{print $3}')"
668
+ org_facts
575
669
  jq -n --arg org "$TARGET" --arg state "$DEVHUB_STATE" \
576
670
  --argjson count "${SCRATCH_COUNT:-null}" \
671
+ --arg edition "${ORG_EDITION}" --arg sandbox "${ORG_IS_SANDBOX}" \
577
672
  --argjson am "${a_max:-null}" --argjson ar "${a_rem:-null}" \
578
673
  --argjson dm "${d_max:-null}" --argjson dr "${d_rem:-null}" '
579
674
  {org:$org,
580
675
  devHubEnabled:($state=="enabled"),
581
676
  status:$state,
677
+ edition:(if $edition=="" then null else $edition end),
678
+ isSandbox:(if $sandbox=="" then null else ($sandbox=="true") end),
582
679
  activeScratchOrgSignups:$count,
583
680
  allocation:{
584
681
  activeScratchOrgs:{max:$am, remaining:$ar},
@@ -587,42 +684,56 @@ render_status() {
587
684
  return 0
588
685
  fi
589
686
 
687
+ org_facts
590
688
  echo "Dev Hub status for '$TARGET':"
591
689
  if [ "$DEVHUB_STATE" = "enabled" ]; then
592
690
  echo " Dev Hub: ENABLED (ScratchOrgInfo is queryable)"
691
+ echo " Detection: ScratchOrgInfo is queryable only when Dev Hub is on. A successful count means ENABLED. An INVALID_TYPE error means NOT ENABLED. Scratch org limits are not used as the enablement check."
692
+ [ -n "$ORG_EDITION" ] && echo " Edition: $ORG_EDITION"
693
+ [ "$ORG_IS_SANDBOX" = "true" ] && echo " (Sandbox org)"
593
694
  [ -n "$SCRATCH_COUNT" ] && echo " Scratch org signup records: $SCRATCH_COUNT"
695
+ # Best-effort current-preference summary; never let it break status.
696
+ local prefs
697
+ prefs="$(read_devhub_settings 2>/dev/null | awk -F'\t' '$2=="true"{printf "%s ", $1}')"
698
+ [ -n "$prefs" ] && echo " Enabled preferences: $prefs"
594
699
  echo
595
700
  render_allocation
596
701
  else
597
702
  echo " Dev Hub: NOT ENABLED"
703
+ echo " Detection: ScratchOrgInfo is queryable only when Dev Hub is on. A successful count means ENABLED. An INVALID_TYPE error means NOT ENABLED. Scratch org limits are not used, because they are reported even when Dev Hub is off."
704
+ [ -n "$ORG_EDITION" ] && echo " Edition: $ORG_EDITION"
705
+ [ "$ORG_IS_SANDBOX" = "true" ] && echo " (Sandbox org — cannot host Dev Hub)"
598
706
  echo
599
- echo " Enable it (deploys $DEVHUB_SWITCH=true; irreversible):"
600
- echo " devhub.sh \"$TARGET\" --enable # validate first (dry run)"
601
- echo " devhub.sh \"$TARGET\" --enable --apply # actually enable"
602
- echo " Requires ModifyAllData or ModifyMetadata (System Administrator);"
603
- echo " not allowed in a sandbox or an org with a registered namespace."
707
+ echo " Scratch org allocation, Dev Hub preferences, and the scratch org list are not available until Dev Hub is enabled. An empty allocation or a failed exit from those modes means Dev Hub is off, not that the org is unauthenticated."
604
708
  echo
709
+ if [ "$ORG_IS_SANDBOX" = "true" ]; then
710
+ echo " This is a sandbox — Dev Hub cannot be enabled here. Use a production,"
711
+ echo " Developer Edition, or trial org instead."
712
+ echo
713
+ else
714
+ echo " Enable it (deploys $DEVHUB_SWITCH=true; irreversible):"
715
+ echo " devhub.sh \"$TARGET\" --enable # validate first (dry run)"
716
+ echo " devhub.sh \"$TARGET\" --enable --apply # actually enable"
717
+ echo " Requires ModifyAllData or ModifyMetadata (System Administrator);"
718
+ echo " not allowed in a sandbox or an org with a registered namespace."
719
+ echo
720
+ fi
605
721
  print_ui_fallback
606
722
  fi
607
723
  }
608
724
 
609
725
  # --- dispatch ------------------------------------------------------------
610
- # Authenticate first when requested. Login can supply the org, so it runs before
611
- # resolve_target's "no org" guard; a bare positional (no other mode) with
612
- # --login/--instance-url is treated as the alias to log in under.
613
- if [ "$DO_LOGIN" -eq 1 ]; then
614
- do_login || exit 1
615
- if [ "$MODE_EXPLICIT" -eq 0 ] && [ "$FAIL_IF_DISABLED" -eq 0 ]; then
616
- exit 0
617
- fi
726
+ # --list-orgs needs no target and no auth check — handle it first.
727
+ if [ "$MODE" = "listorgs" ]; then
728
+ list_orgs; exit $?
618
729
  fi
619
730
 
620
731
  resolve_target
621
732
 
622
- # status/deploy need the enablement state; allocation and list query the org
623
- # directly and surface their own errors.
733
+ # status/deploy/settings need the enablement state; allocation and list query
734
+ # the org directly and surface their own errors.
624
735
  case "$MODE" in
625
- status|deploy)
736
+ status|deploy|settings)
626
737
  if ! detect_devhub && [ "$DEVHUB_STATE" = "auth_error" ]; then
627
738
  print_auth_error
628
739
  exit 1
@@ -630,10 +741,24 @@ case "$MODE" in
630
741
  ;;
631
742
  esac
632
743
 
744
+ # --show-settings requires Dev Hub to be on.
745
+ if [ "$MODE" = "settings" ] && [ "$DEVHUB_STATE" != "enabled" ]; then
746
+ if [ "$OUTPUT" = "json" ]; then
747
+ jq -n --arg org "$TARGET" '{org:$org, settings:{}, note:"Dev Hub is not enabled; there are no DevHubSettings to read."}'
748
+ else
749
+ echo "Dev Hub is not enabled on '$TARGET' — there are no DevHubSettings to read." >&2
750
+ echo " Packaging (enablePackaging2), org shape export (enableShapeExportPref), and scratch org snapshots (enableScratchOrgSnapshotPref) cannot be read until Dev Hub is enabled. Do not guess ON or OFF." >&2
751
+ echo " Validate with a dry run first. Enabling is irreversible and needs a System Administrator (ModifyAllData or ModifyMetadata)." >&2
752
+ echo " Enable it first with: devhub.sh \"$TARGET\" --enable --apply" >&2
753
+ fi
754
+ exit 1
755
+ fi
756
+
633
757
  rc=0
634
758
  case "$MODE" in
635
759
  status) render_status || rc=1 ;;
636
760
  allocation) render_allocation || rc=1 ;;
761
+ settings) render_settings || rc=1 ;;
637
762
  list) render_list || rc=1 ;;
638
763
  deploy) render_deploy || rc=1 ;;
639
764
  esac