mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-09 17:22:00 +08:00
Compare commits
6 Commits
5c43171a1c
...
0a2d1f7e66
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a2d1f7e66 | ||
|
|
84ee08cd47 | ||
|
|
9b9161959b | ||
|
|
9edf77bd6e | ||
|
|
585427019c | ||
|
|
452ccbd11a |
@ -122,7 +122,11 @@ sf data query --json -q "SELECT Username FROM User WHERE Profile.UserLicense.Nam
|
||||
|
||||
**If results are returned:** Ask which username to use. Record choice in the Agent Spec Configuration section. Verify permissions per [Agent User Setup & Permissions](agent-user-setup.md).
|
||||
|
||||
**If no results are returned:** STOP. Do NOT invent a username. Ask if you should create a new user, then read [Agent User Setup & Permissions](agent-user-setup.md) for user creation instructions.
|
||||
**If no results are returned:** STOP. Do NOT invent a username. Ask if you should create a new user. If yes, use:
|
||||
```bash
|
||||
sf org create agent-user --target-org TARGET_ORG --first-name <AgentName> --last-name Agent --json
|
||||
```
|
||||
Then read the generated username from `result.username`. See [Agent User Setup & Permissions](agent-user-setup.md) for the complete provisioning workflow.
|
||||
|
||||
**WRONG:** Fabricating a username when query returns nothing
|
||||
```
|
||||
|
||||
@ -33,32 +33,23 @@ sf data query --json \
|
||||
-o TARGET_ORG
|
||||
|
||||
# Step 2: Create Einstein Agent User (2 minutes)
|
||||
# Get Profile ID (read result.records[0].Id from JSON response)
|
||||
sf data query --json \
|
||||
--query "SELECT Id FROM Profile WHERE Name = 'Einstein Agent User'" \
|
||||
-o TARGET_ORG
|
||||
# Use the new dedicated command — works in all org types (scratch, sandbox, production)
|
||||
# Automatically assigns Einstein Agent User profile + AgentforceServiceAgentBase,
|
||||
# AgentforceServiceAgentUser, and EinsteinGPTPromptTemplateUser permission sets
|
||||
sf org create agent-user \
|
||||
--target-org TARGET_ORG \
|
||||
--first-name <AgentName> \
|
||||
--last-name Agent
|
||||
|
||||
# For Production/Sandbox (non-scratch org):
|
||||
# Use the ProfileId from the query above
|
||||
sf data create record --json --sobject User --values \
|
||||
"Username=<agent_name>_user@<orgId>.ext \
|
||||
LastName=<AgentName> \
|
||||
Email=admin@example.com \
|
||||
Alias=<alias> \
|
||||
TimeZoneSidKey=America/Los_Angeles \
|
||||
LocaleSidKey=en_US \
|
||||
EmailEncodingKey=UTF-8 \
|
||||
ProfileId=<PROFILE_ID> \
|
||||
LanguageLocaleKey=en_US" \
|
||||
-o TARGET_ORG
|
||||
|
||||
# For Scratch Orgs (use user definition file):
|
||||
# sf org create user --definition-file config/einstein-agent-user.json -o TARGET_ORG
|
||||
# Capture the username from the command output (result.username) — it is generated
|
||||
# with a unique GUID suffix, e.g. agent_agent@00Dxx.org.salesforce.com-abc123
|
||||
|
||||
# Step 3: Assign System Permission Set (1 minute)
|
||||
# NOTE: AgentforceServiceAgentUser is already assigned by sf org create agent-user.
|
||||
# This step is only needed if you created the user manually (legacy method).
|
||||
sf org assign permset --json \
|
||||
--name AgentforceServiceAgentUser \
|
||||
--on-behalf-of <agent_name>_user@<orgId>.ext \
|
||||
--on-behalf-of <agent_name_from_output> \
|
||||
-o TARGET_ORG
|
||||
|
||||
# Step 4: Deploy Custom Permission Set (3 minutes)
|
||||
@ -102,11 +93,12 @@ sf agent activate --json \
|
||||
```
|
||||
|
||||
Critical notes:
|
||||
- For **scratch orgs**, use `sf org create user --definition-file`
|
||||
- For **production/sandbox**, use `sf data create record` as shown above
|
||||
- `sf org create user` only works in scratch orgs — it will fail in production/sandbox
|
||||
- `sf org create agent-user` works in **all org types** (scratch, sandbox, production) — use it instead of the legacy `sf org create user` or `sf data create record` approach
|
||||
- The command auto-assigns `AgentforceServiceAgentBase`, `AgentforceServiceAgentUser`, and `EinsteinGPTPromptTemplateUser` — no separate permset assignment needed
|
||||
- The command may exit non-zero with `PermissionSetAssignmentError` if a permset license is exhausted, **even when the user was created successfully**. Always inspect `result.username` and `result.permissionSetErrors[]` from the JSON output. If the only failure is `EinsteinGPTPromptTemplateUser` and your agent has no Prompt Template actions, you can proceed. Otherwise, free a license (or use `--base-username` to retry against a different license pool) before continuing.
|
||||
- Capture the generated username from the command output (`result.username`) and use it for `default_agent_user`
|
||||
- Always test with preview BEFORE publishing to avoid version management overhead
|
||||
- Assign `AgentforceServiceAgentUser` BEFORE publishing to prevent "Internal Error"
|
||||
- Assign any custom permission sets (`{AgentName}_Access`) BEFORE publishing to prevent "Internal Error"
|
||||
- Publishing does NOT activate — you must run `sf agent activate` separately
|
||||
|
||||
---
|
||||
@ -117,12 +109,6 @@ Critical notes:
|
||||
|
||||
Service agents need a dedicated service account with consistent permissions.
|
||||
|
||||
**Get Org ID first** (needed for username format):
|
||||
```bash
|
||||
sf org display --json -o TARGET_ORG
|
||||
# Read result.id from the JSON response
|
||||
```
|
||||
|
||||
**Query existing Einstein Agent Users** (skip creation if one exists):
|
||||
```bash
|
||||
sf data query --json --query "SELECT Id, Username, IsActive FROM User WHERE Profile.Name = 'Einstein Agent User' AND IsActive = true" -o TARGET_ORG
|
||||
@ -130,58 +116,30 @@ sf data query --json --query "SELECT Id, Username, IsActive FROM User WHERE Prof
|
||||
|
||||
**Create the user** (if none exists):
|
||||
|
||||
1. Get the Einstein Agent User profile ID:
|
||||
```bash
|
||||
sf data query --json --query "SELECT Id FROM Profile WHERE Name = 'Einstein Agent User'" -o TARGET_ORG
|
||||
```
|
||||
Use the dedicated command — works in all org types (scratch, sandbox, production):
|
||||
|
||||
2. Create a user definition file (`config/einstein-agent-user.json`):
|
||||
```json
|
||||
{
|
||||
"Username": "{agent_name}_agent@{orgId}.ext",
|
||||
"LastName": "{AgentName} Agent",
|
||||
"Email": "placeholder@example.com",
|
||||
"Alias": "agntuser",
|
||||
"ProfileId": "<profile-id-from-step-1>",
|
||||
"TimeZoneSidKey": "America/Los_Angeles",
|
||||
"LocaleSidKey": "en_US",
|
||||
"EmailEncodingKey": "UTF-8",
|
||||
"LanguageLocaleKey": "en_US",
|
||||
"UserPermissionsKnowledgeUser": true
|
||||
}
|
||||
```
|
||||
```bash
|
||||
sf org create agent-user \
|
||||
--target-org TARGET_ORG \
|
||||
--first-name <AgentName> \
|
||||
--last-name Agent
|
||||
```
|
||||
|
||||
3. Create the user:
|
||||
This command:
|
||||
- Creates a user with the "Einstein Agent User" profile
|
||||
- Automatically assigns `AgentforceServiceAgentBase`, `AgentforceServiceAgentUser`, and `EinsteinGPTPromptTemplateUser` permission sets
|
||||
- Returns a unique generated username in `result.username` — record this for `default_agent_user`
|
||||
|
||||
**Option A: Scratch Org (Definition File)**
|
||||
```bash
|
||||
sf org create user --json \
|
||||
--definition-file config/einstein-agent-user.json \
|
||||
-o TARGET_ORG
|
||||
```
|
||||
Optional flags:
|
||||
- `--base-username <email>` — sets the base portion of the username (a unique suffix is always appended)
|
||||
- `--json` — output as JSON for scripting
|
||||
|
||||
**Option B: Production/Sandbox (Direct Record Creation)**
|
||||
```bash
|
||||
# Get Profile ID first
|
||||
# Get Profile ID (read result.records[0].Id from JSON response)
|
||||
sf data query --json \
|
||||
--query "SELECT Id FROM Profile WHERE Name = 'Einstein Agent User'" \
|
||||
-o TARGET_ORG
|
||||
**Verify creation:**
|
||||
```bash
|
||||
sf data query --json --query "SELECT Id, Username, IsActive FROM User WHERE Profile.Name = 'Einstein Agent User' AND IsActive = true ORDER BY CreatedDate DESC LIMIT 5" -o TARGET_ORG
|
||||
```
|
||||
|
||||
# Create user directly (use ProfileId from query above)
|
||||
sf data create record --json --sobject User --values \
|
||||
"Username='{agent_name}_agent@{orgId}.ext' LastName='{AgentName} Agent' Email='placeholder@example.com' Alias='agntuser' ProfileId='<PROFILE_ID>' TimeZoneSidKey='America/Los_Angeles' LocaleSidKey='en_US' EmailEncodingKey='UTF-8' LanguageLocaleKey='en_US'" \
|
||||
-o TARGET_ORG
|
||||
```
|
||||
|
||||
**Note**: `sf org create user` only works in scratch orgs. For production/sandbox, use `sf data create record`. Attempting `sf org create user` in a non-scratch org fails with an authorization error.
|
||||
|
||||
4. Verify creation:
|
||||
```bash
|
||||
sf data query --json --query "SELECT Id, Username, IsActive FROM User WHERE Username = '{agent_name}_agent@{orgId}.ext'" -o TARGET_ORG
|
||||
```
|
||||
|
||||
**Username format**: `{agent_name}_agent@{orgId}.ext` (production) or `{agent_name}.{suffix}@{orgfarm}.salesforce.com` (dev/scratch). Always query the target org to confirm the exact format.
|
||||
**Note**: The generated username has a GUID suffix for global uniqueness (e.g. `agentname_agent@orgid.salesforce.com-abc123`). Always read the username from command output rather than constructing it manually.
|
||||
|
||||
---
|
||||
|
||||
@ -189,18 +147,22 @@ sf data query --json --query "SELECT Id, Username, IsActive FROM User WHERE Prof
|
||||
|
||||
Critical: Must be assigned BEFORE publishing the agent. Without it, publish fails with "Internal Error".
|
||||
|
||||
**If you used `sf org create agent-user` (recommended):** `AgentforceServiceAgentUser` is assigned automatically — skip to Step 3.
|
||||
|
||||
**If you created the user manually (legacy):**
|
||||
|
||||
Via Setup UI:
|
||||
1. Setup > Permission Sets > search "AgentforceServiceAgentUser"
|
||||
2. Manage Assignments > Add Assignments > select the Einstein Agent User > Save
|
||||
|
||||
Via CLI:
|
||||
```bash
|
||||
sf org assign permset --json --name AgentforceServiceAgentUser --on-behalf-of "{agent_name}_agent@{orgId}.ext" -o TARGET_ORG
|
||||
sf org assign permset --json --name AgentforceServiceAgentUser --on-behalf-of "{agent_name_from_output}" -o TARGET_ORG
|
||||
```
|
||||
|
||||
Verify assignment:
|
||||
```bash
|
||||
sf data query --json --query "SELECT Id, PermissionSet.Name FROM PermissionSetAssignment WHERE Assignee.Username = '{agent_name}_agent@{orgId}.ext' AND PermissionSet.Name = 'AgentforceServiceAgentUser'" -o TARGET_ORG
|
||||
sf data query --json --query "SELECT Id, PermissionSet.Name FROM PermissionSetAssignment WHERE Assignee.Username = '{agent_name_from_output}' AND PermissionSet.Name = 'AgentforceServiceAgentUser'" -o TARGET_ORG
|
||||
```
|
||||
|
||||
---
|
||||
@ -460,9 +422,9 @@ Checklist:
|
||||
- **Prevention:** Deploy → Test → Publish workflow (Step 6.1-6.3)
|
||||
- **Result:** No version management overhead during development
|
||||
|
||||
### 4. Wrong User Creation Command
|
||||
- **Cause:** Using `sf org create user` in non-scratch orgs
|
||||
- **Prevention:** Step 1 provides correct commands for each org type (Option A vs B)
|
||||
### 4. Wrong User Creation Command (Legacy)
|
||||
- **Cause:** Using `sf org create user` in non-scratch orgs, or manually constructing user records with `sf data create record`
|
||||
- **Prevention:** Use `sf org create agent-user --target-org TARGET_ORG` — works in all org types and auto-assigns required permission sets
|
||||
- **Result:** User created successfully without authorization errors
|
||||
|
||||
### 5. Auto-Generated Permission Set Gaps
|
||||
@ -486,7 +448,7 @@ Checklist:
|
||||
| "invocable action does not exist" | Apex class not in custom PS (auto-generated PS incomplete) | Create custom `{AgentName}_Access` with all `<classAccesses>` (Step 3) |
|
||||
| "Invalid default_agent_user" | Username typo or user not active | Query Einstein Agent Users, verify exact username + `IsActive = true` |
|
||||
| Agent runs but returns wrong data | Employee agent using wrong user context | Verify `agent_type` — Service agents use dedicated user, Employee agents use logged-in user |
|
||||
| `sf org create user` fails | Used in production/sandbox org | Use `sf data create record` instead (Step 1, Option B) |
|
||||
| `sf org create user` fails | Used in production/sandbox org | Use `sf org create agent-user --target-org TARGET_ORG` instead (Step 1) |
|
||||
|
||||
---
|
||||
|
||||
@ -526,4 +488,6 @@ Checklist:
|
||||
|
||||
---
|
||||
|
||||
*Validated against: ORM1, ORM2, AutomotiveSupport, SalesforceProductAssistant agents. Last validated: 2026-03-07.*
|
||||
*Validated against: ORM1, ORM2, AutomotiveSupport, SalesforceProductAssistant agents. Last validated: 2026-03-07.
|
||||
Updated to use `sf org create agent-user` (SF CLI 2.131.7+)
|
||||
2026-04-30.*
|
||||
|
||||
@ -331,53 +331,27 @@ If `TotalLicenses > UsedLicenses`, a license is available and a new Einstein Age
|
||||
|
||||
### Creating an Einstein Agent User
|
||||
|
||||
#### Step 1: Query for the Einstein Agent User profile ID
|
||||
Use the dedicated command — works in all org types (scratch, sandbox, production). It automatically assigns the Einstein Agent User profile and the required permission sets (`AgentforceServiceAgentBase`, `AgentforceServiceAgentUser`, `EinsteinGPTPromptTemplateUser`).
|
||||
|
||||
```bash
|
||||
sf data query --json -q "SELECT Id FROM Profile WHERE Name = 'Einstein Agent User'"
|
||||
sf org create agent-user \
|
||||
--target-org <TARGET_ORG> \
|
||||
--first-name <AgentName> \
|
||||
--last-name Agent \
|
||||
--json
|
||||
```
|
||||
|
||||
#### Step 2: Create a User import JSON file (e.g., `data-import/User.json`)
|
||||
Optional: `--base-username <email>` sets the base portion of the username (a unique suffix is always appended).
|
||||
|
||||
```json
|
||||
{
|
||||
"records": [
|
||||
{
|
||||
"attributes": {
|
||||
"type": "User",
|
||||
"referenceId": "AgentUserRef1"
|
||||
},
|
||||
"ProfileId": "<PROFILE_ID_FROM_STEP_1>",
|
||||
"Username": "<UNIQUE_USERNAME>",
|
||||
"Alias": "AgntUsr",
|
||||
"CommunityNickname": "Agent User<UNIQUE_STRING>",
|
||||
"Email": "noreply@example.com",
|
||||
"FirstName": "Agent",
|
||||
"LastName": "User",
|
||||
"IsActive": true,
|
||||
"ForecastEnabled": false,
|
||||
"EmailEncodingKey": "UTF-8",
|
||||
"LanguageLocaleKey": "en_US",
|
||||
"LocaleSidKey": "en_US",
|
||||
"TimeZoneSidKey": "America/Los_Angeles"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
**Capture the generated username** from `result.username` in the output — use it as `default_agent_user` in the `.agent` config.
|
||||
|
||||
#### Step 3: Import the user record
|
||||
|
||||
```bash
|
||||
sf data import tree --json --files data-import/User.json
|
||||
```
|
||||
|
||||
#### Step 4: Verify the user was created
|
||||
#### Verify the user was created
|
||||
|
||||
```bash
|
||||
sf data query --json -q "SELECT Username FROM User WHERE Profile.UserLicense.Name = 'Einstein Agent' AND IsActive = true LIMIT 5"
|
||||
```
|
||||
|
||||
After creating the user, continue with permission setup in [Agent User Setup & Permissions](agent-user-setup.md).
|
||||
After creating the user, continue with custom permission set setup in [Agent User Setup & Permissions](agent-user-setup.md).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -83,8 +83,8 @@ sf data-code-extension function init --package-dir <directory>
|
||||
my-transform/ # Project root
|
||||
├── payload/ # CRITICAL: This is what --package-dir must point to for deploy
|
||||
│ ├── entrypoint.py # Main transformation code
|
||||
│ ├── requirements.txt # Python dependencies
|
||||
│ └── config.json # Code extension configuration
|
||||
├── requirements.txt # Python dependencies
|
||||
└── README.md
|
||||
```
|
||||
|
||||
|
||||
@ -139,8 +139,8 @@ After `init`, you'll have:
|
||||
my-transform/
|
||||
├── payload/
|
||||
│ ├── entrypoint.py # Your transformation code
|
||||
│ ├── config.json # Permissions and configuration
|
||||
│ └── requirements.txt # Python dependencies
|
||||
│ └── config.json # Permissions and configuration
|
||||
├── requirements.txt # Python dependencies
|
||||
└── README.md
|
||||
```
|
||||
|
||||
|
||||
@ -219,8 +219,8 @@ sf data-code-extension script scan --entrypoint ./payload/entrypoint.py
|
||||
my-project/
|
||||
├── payload/
|
||||
│ ├── entrypoint.py # Main code
|
||||
│ ├── config.json # Auto-generated permissions
|
||||
│ └── requirements.txt # Auto-generated dependencies
|
||||
│ └── config.json # Auto-generated permissions
|
||||
├── requirements.txt # Auto-generated dependencies
|
||||
└── README.md
|
||||
```
|
||||
|
||||
|
||||
73
skills/generating-images/SKILL.md
Normal file
73
skills/generating-images/SKILL.md
Normal file
@ -0,0 +1,73 @@
|
||||
---
|
||||
name: generating-images
|
||||
description: "Generates high-fidelity visual assets, logos, and UI mockups using the media-management MCP server. Trigger this skill whenever the user asks to generate an image, create a logo, produce a hero banner, design a UI icon, or build a visual asset. It is explicitly designed to handle technical specifications including file formats (PNG, JPEG, WEBP), specific dimensions (e.g. 1024x1024), and transparency requirements. Use this skill when the user needs to integrate generated imagery into application code or web pages. It ensures consistent output quality and provides a standardized SVG fallback if the generation tool is unavailable."
|
||||
metadata:
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
# Generating Images
|
||||
|
||||
## Goal
|
||||
|
||||
To programmatically generate, download, and preview visual assets requested by the user, ensuring specific format and quality standards are met while providing a robust fallback mechanism.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Check if the `media-management` MCP server is configured and its `create_image` tool is available.
|
||||
2. If available, use `create_image` to generate the image.
|
||||
3. If not available, use the placeholder fallback below.
|
||||
|
||||
## MCP: media-management
|
||||
|
||||
**Tool:** `create_image`
|
||||
|
||||
Check your available tools. If `create_image` is present, use it as the primary image generation method — pass the natural language prompt and applicable parameters from the table below. (`media-management` here refers to the MCP server name, not this skill.)
|
||||
|
||||
If `create_image` is not in your tool list, the `media-management` MCP is not configured — use the placeholder fallback below.
|
||||
|
||||
## Image Generation Parameters
|
||||
|
||||
Use these defaults unless the user specifies otherwise:
|
||||
|
||||
| Parameter | Default | Options |
|
||||
|---|---|---|
|
||||
| `model` | `Standard` | `Standard`, `Premium` |
|
||||
| `size` | `auto` | `auto`, `1024x1024`, `1536x1024`, `1024x1536` (pick closest to user-requested size) |
|
||||
| `quality` | `medium` | `low`, `medium`, `high` |
|
||||
| `outputCompression` | `75` | `0–100` (webp/jpeg only) |
|
||||
| `outputFormat` | `webp` | `webp`, `jpeg`, `png` |
|
||||
| `background` | `auto` | `auto`, `transparent`, `opaque` |
|
||||
|
||||
**Format rule:** If `outputFormat` is `png`, set `outputCompression` to `100`.
|
||||
|
||||
## After Successful Generation
|
||||
|
||||
Run `download-image.sh` (located in this skill's `scripts/` directory) to download and preview the image:
|
||||
|
||||
```bash
|
||||
bash scripts/download-image.sh \
|
||||
--url "<image_url_from_response>" \
|
||||
--id "<responseId>" \
|
||||
--format "<outputFormat>" \
|
||||
--preview
|
||||
```
|
||||
|
||||
The script handles credential retrieval, download, and VS Code preview. Pass `--output-dir <dir>` to override the default `generatedimages/` directory.
|
||||
|
||||
**Never resize or post-process the generated image with external tools.** To control display dimensions, use CSS properties (e.g. `width`, `height`, `object-fit`) at the point of use.
|
||||
|
||||
## Fallback: Use placeholder URL
|
||||
|
||||
If image generation fails or is not enabled, return the following URL as the image source — do not download it, do not save it locally:
|
||||
|
||||
```
|
||||
https://cdn.scs.static.lightning.force.com/content/assets/d5222d4a11e6c2b735152d7eea824ce4/placeholder.svg
|
||||
```
|
||||
|
||||
Use this URL directly wherever the image is referenced in code (e.g. as a `src` attribute or CSS `url()`).
|
||||
|
||||
## Placeholder Policy
|
||||
|
||||
There is only one placeholder URL. Do not download it, modify it, or generate alternative placeholders using Python, ImageMagick, or any other tool.
|
||||
|
||||
If the user requests a placeholder of a specific size or format, inform them that only this placeholder URL is available and direct them to use CSS properties (e.g. `width`, `height`, `object-fit`) to scale it at the point of use.
|
||||
84
skills/generating-images/scripts/download-image.sh
Executable file
84
skills/generating-images/scripts/download-image.sh
Executable file
@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") --url <image_url> --id <response_id> --format <output_format> [--output-dir <dir>] [--preview]
|
||||
|
||||
Required:
|
||||
--url URL of the generated image to download
|
||||
--id Response ID used as the output filename
|
||||
--format Output format (webp, jpeg, png)
|
||||
|
||||
Optional:
|
||||
--output-dir Directory to save the image (default: generatedimages)
|
||||
--preview Open the image in VS Code after download
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
OUTPUT_DIR="generatedimages"
|
||||
PREVIEW=false
|
||||
URL=""
|
||||
RESPONSE_ID=""
|
||||
FORMAT=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--url) URL="$2"; shift 2 ;;
|
||||
--id) RESPONSE_ID="$2"; shift 2 ;;
|
||||
--format) FORMAT="$2"; shift 2 ;;
|
||||
--output-dir) OUTPUT_DIR="$2"; shift 2 ;;
|
||||
--preview) PREVIEW=true; shift ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$URL" || -z "$RESPONSE_ID" || -z "$FORMAT" ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
case "$FORMAT" in
|
||||
webp|jpeg|png) ;;
|
||||
*) echo "Error: invalid format '$FORMAT'. Must be webp, jpeg, or png." >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
if [[ "$RESPONSE_ID" == */* || "$RESPONSE_ID" == *..* ]]; then
|
||||
echo "Error: invalid response ID '$RESPONSE_ID'. Must not contain '/' or '..'." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$URL" =~ ^https:// ]]; then
|
||||
echo "Error: URL must start with https://" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TARGET_ORG=$(sf config get target-org --json | jq -r '.result[0].value')
|
||||
|
||||
if [[ -z "$TARGET_ORG" || "$TARGET_ORG" == "null" ]]; then
|
||||
echo "Error: no target-org configured. Run 'sf config set target-org <org>'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ACCESS_TOKEN=$(sf org display --target-org "$TARGET_ORG" --json | jq -r '.result.accessToken')
|
||||
|
||||
if [[ -z "$ACCESS_TOKEN" || "$ACCESS_TOKEN" == "null" ]]; then
|
||||
echo "Error: failed to retrieve access token for org '$TARGET_ORG'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
OUTPUT_FILE="${OUTPUT_DIR}/${RESPONSE_ID}.${FORMAT}"
|
||||
|
||||
if ! curl -fsS -H "Authorization: Bearer $ACCESS_TOKEN" -o "$OUTPUT_FILE" -- "$URL"; then
|
||||
rm -f "$OUTPUT_FILE"
|
||||
echo "Error: failed to download image from $URL" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$OUTPUT_FILE"
|
||||
|
||||
if [[ "$PREVIEW" == true ]]; then
|
||||
code "$OUTPUT_FILE"
|
||||
fi
|
||||
@ -67,30 +67,28 @@ sf org login web --alias <org_alias>
|
||||
|
||||
### Step 3a: Execute DLO Schema Script
|
||||
|
||||
Use the Python script to retrieve DLO information:
|
||||
The Python scripts are bundled with this skill. They live in the `scripts/` subdirectory of the same directory that contains this SKILL.md file. Use the absolute path to that directory — do NOT use `./scripts/` as that resolves relative to the current working directory, not the skill directory.
|
||||
|
||||
**To list all DLOs:**
|
||||
```bash
|
||||
python3 ./scripts/get_dlo_schema.py <org_alias>
|
||||
python3 <skill_dir>/scripts/get_dlo_schema.py <org_alias>
|
||||
```
|
||||
|
||||
**To get specific DLO schema:**
|
||||
```bash
|
||||
python3 ./scripts/get_dlo_schema.py <org_alias> <dlo_name>
|
||||
python3 <skill_dir>/scripts/get_dlo_schema.py <org_alias> <dlo_name>
|
||||
```
|
||||
|
||||
### Step 3b: Execute DMO Schema Script
|
||||
|
||||
Use the Python script to retrieve DMO information:
|
||||
|
||||
**To list all DMOs:**
|
||||
```bash
|
||||
python3 ./scripts/get_dmo_schema.py <org_alias>
|
||||
python3 <skill_dir>/scripts/get_dmo_schema.py <org_alias>
|
||||
```
|
||||
|
||||
**To get specific DMO schema:**
|
||||
```bash
|
||||
python3 ./scripts/get_dmo_schema.py <org_alias> <dmo_name>
|
||||
python3 <skill_dir>/scripts/get_dmo_schema.py <org_alias> <dmo_name>
|
||||
```
|
||||
|
||||
### Step 4: Present Results
|
||||
@ -227,7 +225,7 @@ User: "Show me all DLOs in afvibe org"
|
||||
Response:
|
||||
1. Run sf org list to discover connected org alias
|
||||
2. Authenticate to afvibe
|
||||
3. Run: python3 ./scripts/get_dlo_schema.py afvibe
|
||||
3. Run: python3 <skill_dir>/scripts/get_dlo_schema.py afvibe
|
||||
4. Display formatted list of DLOs
|
||||
```
|
||||
|
||||
@ -238,7 +236,7 @@ User: "Get the schema for Employee__dll in afvibe"
|
||||
Response:
|
||||
1. Run sf org list to discover connected org alias
|
||||
2. Authenticate to afvibe
|
||||
3. Run: python3 ./scripts/get_dlo_schema.py afvibe Employee__dll
|
||||
3. Run: python3 <skill_dir>/scripts/get_dlo_schema.py afvibe Employee__dll
|
||||
4. Display field schema with types and metadata
|
||||
```
|
||||
|
||||
@ -261,7 +259,7 @@ User: "Show me all DMOs in afvibe org"
|
||||
Response:
|
||||
1. Run sf org list to discover connected org alias
|
||||
2. Authenticate to afvibe
|
||||
3. Run: python3 ./scripts/get_dmo_schema.py afvibe
|
||||
3. Run: python3 <skill_dir>/scripts/get_dmo_schema.py afvibe
|
||||
4. Display formatted list of DMOs
|
||||
```
|
||||
|
||||
@ -272,7 +270,7 @@ User: "Get the schema for Individual__dlm in afvibe"
|
||||
Response:
|
||||
1. Run sf org list to discover connected org alias
|
||||
2. Authenticate to afvibe
|
||||
3. Run: python3 ./scripts/get_dmo_schema.py afvibe Individual__dlm
|
||||
3. Run: python3 <skill_dir>/scripts/get_dmo_schema.py afvibe Individual__dlm
|
||||
4. Display field schema with types and metadata
|
||||
```
|
||||
|
||||
|
||||
354
skills/replacing-b2b-commerce-ootb-open-code-components/SKILL.md
Normal file
354
skills/replacing-b2b-commerce-ootb-open-code-components/SKILL.md
Normal file
@ -0,0 +1,354 @@
|
||||
---
|
||||
name: replacing-b2b-commerce-ootb-open-code-components
|
||||
description: Replace OOTB (out-of-the-box) B2B Commerce components with open source equivalents in site metadata content.json files, or look up the equivalent open code `site:` component for OOTB definitions. Use when users mention "replace OOTB components", "replace commerce components with open code", "swap OOTB for open source", "replace commerce_builder:", "replace OOTB in site", "replace component in site metadata", "replace component definition", "find open code equivalent", "equivalent open code component", "OOTB to open code mapping", "what is the site component for", components "in this view" or "for a given view", or a specific list of component names — and want to update or only discover mappings in their store metadata.
|
||||
license: Apache-2.0
|
||||
compatibility: Requires integrating-b2b-commerce-open-code-components skill as prerequisite
|
||||
allowed-tools: Bash Read Write
|
||||
metadata:
|
||||
author: afv-library
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when you need to:
|
||||
- Replace OOTB B2B Commerce components with open code equivalents
|
||||
- Update component definitions in site metadata `content.json` files
|
||||
- Swap out-of-the-box commerce components for open source versions
|
||||
- **Find the equivalent open code (`site:`) component** for one or more OOTB `commerce_builder:` / `commerce:` definitions — using the mapping table and verifying availability in the cloned open code repo
|
||||
- **Scope discovery or replacement to a given Experience Builder view** — scan only that view’s `sfdc_cms__view/<ViewName>/content.json` (or the paths the user names) instead of the whole site
|
||||
- **Answer “what open code component replaces X?”** when the user gives explicit component name(s) — look up each in the mapping table, report `site:` targets, and note unmapped entries or targets missing from the repo (no `content.json` edits unless the user also asks to replace)
|
||||
|
||||
**Trigger phrases:** “replace OOTB components with open code components”, “find equivalent open code”, “open code equivalent for OOTB”, “map commerce_builder to site”, “components in this view”, “for the Product Detail view”, “replace only these components: …”.
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Always explain before executing.** Before running any command, you MUST tell the user what the command does and why you are running it. Never just show a raw command and ask for permission.
|
||||
2. **ONLY use the mapping table in this skill.** The JSON mapping table below is the ONLY source of truth for OOTB-to-open-code component names. NEVER guess, infer, or hallucinate component names. If a component is not in the mapping table, tell the user there is no known mapping — do not make one up.
|
||||
3. **Use Read and Write tools for JSON files.** Use the Read tool to parse `content.json` files and the Write tool to update them. Do NOT use bash to parse or edit JSON — no sed, awk, perl, or regex on JSON content. Bash is only for **simple file discovery** (`grep -rl`, `find`, `ls`) — never for extracting or modifying JSON values.
|
||||
4. **Minimize commands.** Batch work into as few commands as possible. Use a single grep to scan all files, a single ls to verify the repo, and one Read/Write pass per file. Do NOT run a separate command for every component or every directory.
|
||||
5. **Follow the workflow steps exactly.** Do not invent additional options, policies, or frameworks. Execute each step and show the user the results before proceeding.
|
||||
6. **Always replace with `site:` after verifying in the open code repo.** For every replacement, the new `"definition"` MUST be the mapped value from the table below, which always uses the `site:` namespace (for example `site:productHeading`). Before changing `content.json`, verify the target exists in the cloned open code components repository — for example by confirming the corresponding bundle under `.tmp/b2b-commerce-open-source-components/force-app/main/default/sfdc_cms__lwc/` (or the path your integrating skill documents). If the mapped `site:` component is not present in the repo, **do not replace** — skip it and report it under “not in repo” (same as Step 1 categorization).
|
||||
|
||||
## Overview
|
||||
|
||||
This skill replaces OOTB B2B Commerce component definitions in site metadata `content.json` files with their open source equivalents. It uses an authoritative mapping table of 64 component pairs extracted from `ui-commerce-components/scripts/moduleConfig.js`.
|
||||
|
||||
**Modes:** **Full replace** runs the scan (Step 1), user selection if needed, then `content.json` updates (Step 2–3). **Lookup only** (user asks for equivalents but not to change files): use the same mapping table and repo verification (Rule 2 and Rule 6), report OOTB → `site:` for the named components or for definitions found in the scoped `content.json` — **do not** call Write unless the user confirms replacement. **View-scoped** work: limit file discovery and reads to `sfdc_cms__view/<ViewName>/` (or the path the user gives) instead of all views.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before replacing components, delegate to the **integrating-b2b-commerce-open-code-components** skill (`skills/integrating-b2b-commerce-open-code-components/SKILL.md`) to ensure:
|
||||
|
||||
1. Open source repository is cloned at `.tmp/b2b-commerce-open-source-components`
|
||||
2. Store is selected and site metadata is retrieved locally
|
||||
3. Open code components are copied to the store's site metadata
|
||||
|
||||
Tell user: "Before replacing components, I need to verify that the open code components are set up in your store. Let me check..."
|
||||
|
||||
If any prerequisite is not met, the integrating skill will handle it. Once all checks pass, proceed to the replacement workflow.
|
||||
|
||||
**Required state** after prerequisites:
|
||||
- **Store name** — e.g., `My_B2B_Store1`
|
||||
- **Site metadata path** — `force-app/main/default/digitalExperiences/site/<store-name>/`
|
||||
- **Repo path** — `.tmp/b2b-commerce-open-source-components/`
|
||||
|
||||
---
|
||||
|
||||
## Replacement Workflow
|
||||
|
||||
### Step 1: Scan Site and Cross-Reference Mapping
|
||||
|
||||
**This step is MANDATORY.** Always scan the site first before attempting any replacements.
|
||||
|
||||
Tell user: "I'm scanning your store's site metadata to find all OOTB commerce components currently in use and checking which have open code equivalents."
|
||||
|
||||
**Step 1a — Find affected files** (one command, simple literal match):
|
||||
|
||||
```bash
|
||||
grep -rl '"commerce' \
|
||||
force-app/main/default/digitalExperiences/site/<store-name>/sfdc_cms__view/ \
|
||||
force-app/main/default/digitalExperiences/site/<store-name>/sfdc_cms__themeLayout/ \
|
||||
--include="content.json"
|
||||
```
|
||||
|
||||
**Step 1b — Read and parse** each matched file using the **Read** tool. Extract all `"definition"` values that start with `commerce` (e.g., `commerce_builder:cartBadge`). Collect a deduplicated list of OOTB components across all files.
|
||||
|
||||
**Step 1c — List repo components** (one command):
|
||||
|
||||
```bash
|
||||
ls .tmp/b2b-commerce-open-source-components/force-app/main/default/sfdc_cms__lwc/
|
||||
```
|
||||
|
||||
Using the parsed definitions, the `ls` output, and the mapping table, categorize every discovered OOTB component into three groups:
|
||||
|
||||
**Show the user a breakdown and a selectable list:**
|
||||
|
||||
First, inform the user about skipped and unmapped components:
|
||||
```
|
||||
Found X OOTB components in your site:
|
||||
|
||||
In mapping table but NOT in repo (skipping):
|
||||
- commerce_builder:quoteSummary → site:quoteSummary (not found in repo)
|
||||
|
||||
No mapping available (not in mapping table):
|
||||
- commerce_builder:actionButtons
|
||||
- commerce_builder:layoutHeaderOne
|
||||
- commerce_builder:searchInputContainer
|
||||
- commerce_builder:myAccountMegaMenu
|
||||
```
|
||||
|
||||
Then present the replaceable components as a **multi-select list** using the AskQuestion tool (allow_multiple: true) so the user can pick from checkboxes instead of typing. Include an "All of the above" option:
|
||||
|
||||
```
|
||||
Which components would you like to replace?
|
||||
|
||||
☐ commerce_builder:heading → site:productHeading
|
||||
☐ commerce_builder:cartBadge → site:cartBadge
|
||||
☐ commerce_builder:searchInput → site:searchInput
|
||||
☐ All of the above
|
||||
```
|
||||
|
||||
If user provided specific component name(s) in the original request, pre-filter to those and skip the selection prompt.
|
||||
|
||||
### Step 2: Replace in content.json
|
||||
|
||||
Tell user: "I'm now replacing the selected OOTB component definitions with their open code equivalents in your site's content.json files."
|
||||
|
||||
The affected files are already known from Step 1. For each file that contains selected components:
|
||||
1. Use the **Read** tool to read the file
|
||||
2. For each selected OOTB component, confirm again that the mapped **`site:`** target from the mapping table exists in the open code repo (per Rule 6). Only proceed with replacements that pass this check.
|
||||
3. Replace all matching `"definition"` values with their mapped open code equivalents — **always** the exact `site:<name>` string from the mapping table
|
||||
- Example: `"definition": "commerce_builder:heading"` → `"definition": "site:productHeading"`
|
||||
4. Use the **Write** tool to save the updated file
|
||||
5. Preserve all other JSON properties — only `"definition"` values change
|
||||
|
||||
**Batch efficiently:** if a file contains multiple OOTB components, apply ALL replacements in a single Read → modify → Write pass. Do NOT read and write the same file multiple times.
|
||||
|
||||
### Step 3: Report
|
||||
|
||||
```
|
||||
✅ Replacement Complete!
|
||||
|
||||
Replaced X components across Y files:
|
||||
- commerce_builder:heading → site:productHeading (3 files)
|
||||
- commerce_builder:cartBadge → site:cartBadge (2 files)
|
||||
- commerce_builder:searchInput → site:searchInput (4 files)
|
||||
|
||||
Skipped (not in repo):
|
||||
- commerce_builder:quoteSummary
|
||||
|
||||
No mapping available (left unchanged):
|
||||
- commerce_builder:actionButtons
|
||||
- commerce_builder:layoutHeaderOne
|
||||
- commerce_builder:searchInputContainer
|
||||
|
||||
Modified files:
|
||||
- sfdc_cms__view/Home/content.json
|
||||
- sfdc_cms__view/Product_Detail/content.json
|
||||
- sfdc_cms__themeLayout/DefaultTheme/content.json
|
||||
|
||||
Next Steps:
|
||||
1. Deploy: sf project deploy start -d force-app/main/default/digitalExperiences/site/<store-name>
|
||||
2. Test the store thoroughly in Experience Builder
|
||||
3. Publish your site when ready
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## OOTB to Open Code Mapping
|
||||
|
||||
**Source:** ui-commerce-components/scripts/moduleConfig.js
|
||||
**Total Mappings:** 64
|
||||
|
||||
```json
|
||||
{
|
||||
"commerce_builder:actionButton": "site:commonButton",
|
||||
"commerce_builder:b2bCartContents": "site:cartB2bCartContents",
|
||||
"commerce_builder:cartAppliedPromotion": "site:cartPromotionApplied",
|
||||
"commerce_builder:cartApplyCoupon": "site:cartApplyCoupon",
|
||||
"commerce_builder:cartBadge": "site:cartBadge",
|
||||
"commerce_builder:cartPromotions": "site:cartPromotions",
|
||||
"commerce_builder:cartSummary": "site:cartSummary",
|
||||
"commerce_builder:checkoutButton": "site:checkoutButton",
|
||||
"commerce_builder:checkoutDeliveryAddress": "site:checkoutDeliveryAddress",
|
||||
"commerce_builder:checkoutDeliveryMethod": "site:checkoutDeliverymethod",
|
||||
"commerce_builder:checkoutGiftOptions": "site:checkoutGiftOptions",
|
||||
"commerce_builder:checkoutNotification": "site:checkoutNotification",
|
||||
"commerce_builder:checkoutPurchaseOrder": "site:checkoutPurchaseOrder",
|
||||
"commerce_builder:checkoutShippingInstructions": "site:checkoutShippingInstructions",
|
||||
"commerce_builder:checkoutSubscriptionPolicyDisclaimer": "site:checkoutSubscriptionPolicyDisclaimer",
|
||||
"commerce_builder:consentBlanket": "site:legalConsentBlanket",
|
||||
"commerce_builder:countryPickerV2": "site:commonCountryPicker",
|
||||
"commerce_builder:drilldownNavigation": "site:commonDrilldownNavigation",
|
||||
"commerce_builder:formattedCurrency": "site:commonFormattedCurrency",
|
||||
"commerce_builder:heading": "site:productHeading",
|
||||
"commerce_builder:layoutFooter": "site:layoutFooter",
|
||||
"commerce_builder:layoutHeaderSimple": "site:layoutHeaderSimple",
|
||||
"commerce_builder:linkList": "site:commonLinksList",
|
||||
"commerce_builder:myAccountAddressContainer": "site:myaccountAddress",
|
||||
"commerce_builder:navigationMenuItemList": "site:myaccountNavigationMenuItems",
|
||||
"commerce_builder:orderConfirmationBillingDetails": "site:orderConfirmationDetailsBilling",
|
||||
"commerce_builder:orderConfirmationDeliveryGroup": "site:orderConfirmationDeliverygroup",
|
||||
"commerce_builder:orderConfirmationErrorMessage": "site:orderConfirmationMessageError",
|
||||
"commerce_builder:orderConfirmationSuccessMessage": "site:orderConfirmationMessageSuccess",
|
||||
"commerce_builder:orderDetails": "site:orderDetails",
|
||||
"commerce_builder:orderList": "site:orderList",
|
||||
"commerce_builder:orderListDateFilter": "site:orderListDateFilter",
|
||||
"commerce_builder:orderProductsInfo": "site:orderProducts",
|
||||
"commerce_builder:orderPromotionsSummary": "site:orderPromotions",
|
||||
"commerce_builder:orderShipmentTracker": "site:orderShipmentTracker",
|
||||
"commerce_builder:paymentByExpress": "site:paymentByExpress",
|
||||
"commerce_builder:productAttachments": "site:productAttachments",
|
||||
"commerce_builder:productBundle": "site:productBundle",
|
||||
"commerce_builder:productBundleItem": "site:productBundleItem",
|
||||
"commerce_builder:productFieldsTable": "site:productFieldsTable",
|
||||
"commerce_builder:productFrequentlyBoughtTogether": "site:productFrequentlyBoughtTogether",
|
||||
"commerce_builder:productMediaGallery": "site:productMediaGallery",
|
||||
"commerce_builder:productPricingDetails": "site:productPricingDetails",
|
||||
"commerce_builder:productSellingModelSelector": "site:productSellingmodelSelector",
|
||||
"commerce_builder:productSet": "site:productSet",
|
||||
"commerce_builder:promotionDiscountsApproaching": "site:promotionDiscountsApproaching",
|
||||
"commerce_builder:purchaseOptions": "site:productPurchaseOptions",
|
||||
"commerce_builder:purchasedProducts": "site:productListPurchased",
|
||||
"commerce_builder:quickOrder": "site:orderQuickOrder",
|
||||
"commerce_builder:quoteSummary": "site:quoteSummary",
|
||||
"commerce_builder:searchFilters": "site:searchFilters",
|
||||
"commerce_builder:searchInput": "site:searchInput",
|
||||
"commerce_builder:searchNoResultsLayout": "site:searchResultsLayoutEmpty",
|
||||
"commerce_builder:searchResults": "site:searchResults",
|
||||
"commerce_builder:searchResultsLayout": "site:searchResultsLayout",
|
||||
"commerce_builder:searchSortMenu": "site:searchSortMenu",
|
||||
"commerce_builder:socialLinks": "site:commonLinksSocial",
|
||||
"commerce_builder:splitShipmentHeader": "site:cartSplitshipmentHeader",
|
||||
"commerce_builder:stickyContainer": "site:commonContainerSticky",
|
||||
"commerce_builder:subscriptions": "site:productSubscriptions",
|
||||
"commerce_builder:userProfileMenu": "site:myAccountUserProfileMenu",
|
||||
"commerce_builder:variantSelector": "site:productVariantSelector",
|
||||
"commerce_builder:wishlist": "site:productWishlist",
|
||||
"commerce_builder:wishlistShortcut": "site:productWishlistShortcut"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Interaction
|
||||
|
||||
**User:** "Replace OOTB components with open code versions"
|
||||
|
||||
**Agent:** "Before replacing components, I need to verify that the open code components are set up in your store. Let me check..."
|
||||
- ✓ Open source repo cloned
|
||||
- ✓ Store metadata available for My_B2B_Store1
|
||||
- ✓ Open code components are integrated
|
||||
|
||||
**Agent:** "I'm scanning your store's site metadata to find all OOTB commerce components and checking which have open code equivalents."
|
||||
|
||||
_(Runs grep -rl to find files with commerce definitions, Reads those files to extract component names, runs ls to list repo components, then cross-references with mapping table)_
|
||||
|
||||
```
|
||||
Found 8 OOTB components in your site:
|
||||
|
||||
No mapping available (not in mapping table):
|
||||
- commerce_builder:searchInputContainer
|
||||
- commerce_builder:layoutHeaderOne
|
||||
- commerce_builder:myAccountMegaMenu
|
||||
- commerce_builder:actionButtons
|
||||
```
|
||||
|
||||
**Agent** _(presents multi-select list via AskQuestion tool):_
|
||||
```
|
||||
Which components would you like to replace?
|
||||
|
||||
☐ commerce_builder:heading → site:productHeading
|
||||
☐ commerce_builder:cartBadge → site:cartBadge
|
||||
☐ commerce_builder:searchInput → site:searchInput
|
||||
☐ commerce_builder:cartSummary → site:cartSummary
|
||||
☐ All of the above
|
||||
```
|
||||
|
||||
**User:** _(selects heading and cartBadge)_
|
||||
|
||||
**Agent:** "I'm now replacing the selected OOTB component definitions with their open code equivalents in your site's content.json files."
|
||||
|
||||
_(Files already known from scan — one Read/Write pass per affected file, all replacements batched)_
|
||||
|
||||
```
|
||||
✅ Replacement Complete!
|
||||
|
||||
Replaced 2 components across 5 files:
|
||||
- commerce_builder:heading → site:productHeading (3 files)
|
||||
- commerce_builder:cartBadge → site:cartBadge (2 files)
|
||||
|
||||
No mapping available (left unchanged):
|
||||
- commerce_builder:searchInputContainer
|
||||
- commerce_builder:layoutHeaderOne
|
||||
- commerce_builder:myAccountMegaMenu
|
||||
- commerce_builder:actionButtons
|
||||
|
||||
Modified files:
|
||||
- sfdc_cms__view/Home/content.json
|
||||
- sfdc_cms__view/Product_Detail/content.json
|
||||
- sfdc_cms__themeLayout/DefaultTheme/content.json
|
||||
|
||||
Next Steps:
|
||||
1. Deploy: sf project deploy start -d force-app/main/default/digitalExperiences/site/My_B2B_Store1
|
||||
2. Test the store thoroughly in Experience Builder
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Message | Action |
|
||||
|-------|---------|--------|
|
||||
| Prerequisites not met | "Open code components are not integrated yet." | Run integrating skill first |
|
||||
| No mapping found | "No mapping found for '{component}'." | Show available mappings, report as unmapped |
|
||||
| Component not in repo | "Open code component '{name}' not found in cloned repo." | Skip and inform user |
|
||||
| No OOTB components in site | "No OOTB commerce components found in site metadata." | Inform user, nothing to replace |
|
||||
| No replaceable components | "All OOTB components found are unmapped — none can be replaced." | Show the unmapped list, suggest checking for updated mappings |
|
||||
| content.json parse error | "Failed to parse content.json: {file}" | Show error, skip file, continue with remaining files |
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
- [ ] Prerequisites verified via integrating skill (repo, store, components)
|
||||
- [ ] Site scanned + repo verified + mapping cross-referenced in minimal commands (Step 1)
|
||||
- [ ] Each replacement uses the exact mapped `site:` definition and was verified present in the open code repo before write (Rule 6)
|
||||
- [ ] Breakdown shown to user with three categories before proceeding
|
||||
- [ ] User selected components to replace (or provided names)
|
||||
- [ ] Each `content.json` file updated in a single Read → modify → Write pass
|
||||
- [ ] JSON structure preserved, no syntax errors introduced
|
||||
- [ ] User informed of skipped and unmapped components
|
||||
- [ ] Deployment command provided
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
**DO NOT:**
|
||||
- Skip the site scan step — ALWAYS scan first to discover actual OOTB components
|
||||
- Use bash (sed, perl, awk, grep -o) to **parse or edit** JSON — use bash only for file discovery (`grep -rl`, `find`, `ls`)
|
||||
- Run a separate command per component or per directory — batch into single commands
|
||||
- Read the same file multiple times — apply all replacements for a file in one Read/Write pass
|
||||
- Replace components without verifying the open code equivalent exists in the repo
|
||||
- Write a `"definition"` that is not the exact mapped `site:` value from the table, or use any namespace other than `site:` for the replacement target
|
||||
- Modify `content.json` structure beyond the `"definition"` value
|
||||
- Skip prerequisite checks
|
||||
- Replace components not in the mapping table
|
||||
- Guess or hallucinate component mappings
|
||||
- Invent additional options, policies, or frameworks beyond what this skill defines
|
||||
|
||||
**DO:**
|
||||
- Use `grep -rl` to find affected files, then Read tool to parse JSON reliably
|
||||
- Verify the repo with a single `ls` command
|
||||
- Show the user a clear breakdown of replaceable, skipped, and unmapped components
|
||||
- Use Read and Write tools for all JSON modifications
|
||||
- Batch all replacements for the same file into one Read → modify → Write pass
|
||||
- Explain each step before executing
|
||||
- Verify prerequisites via the integrating skill
|
||||
- Confirm each mapped `site:` component exists in the open code repo before replacing, and use only that exact `site:` string from the mapping table
|
||||
- Show user the full replacement plan before executing
|
||||
- Report all modified files and any skipped or unmapped components
|
||||
Loading…
Reference in New Issue
Block a user