Compare commits

...

3 Commits

Author SHA1 Message Date
npiccolo
da8e3ffacc
Merge branch 'develop' into npz/createAgent 2026-05-04 10:17:31 -03:00
Ayush Gupta
f5e63fd06a
@W-22252932 [Meta Skill] Skill Creator (#232)
* @W-22252932 [Meta Skill] Skill Creator

* @W-22252932 [Meta Skill] Skill Creator

* @W-22252932 [Meta Skill] Skill Creator

* @W-22252932 [Meta Skill] Skill Creator

* @W-22252932 [Meta Skill] Skill Creator

* @W-22252932 [Meta Skill] Skill Creator

* @W-22252932 [Meta Skill] Skill Creator

* @W-22252932 [Meta Skill] Skill Creator
2026-05-04 11:34:12 +05:30
chandresh-patelsf
8f70982668
feat: add Data Cloud Code Extension and Schema skills (#180)
* feat: add Data Cloud Code Extension and Schema skills
 Add two new skills for Salesforce Data Cloud:
 - datacloud_code_extension: develop, test, and deploy Python code extensions
 - datacloud_schema: retrieve DLO and DMO schema information via REST APIs

* modify the skills name from  - datacloud_code_extension → developing-datacloud-code-extension  and datacloud_schema → getting-datacloud-schema

* updated  getting-datacloud-schema

* fix: address PR #180 review feedback and validation errors

- Wrap description values in double quotes in both SKILL.md files
- Trim developing-datacloud-code-extension SKILL.md from 755 to 319 lines
- Move README.md and quick-reference.md to references/ directory
- Remove "Claude Code" references and hardcoded local paths
- Use relative script paths (scripts/get_dlo_schema.py)
- Move getting-datacloud-schema docs/README.md to references/

---------

Co-authored-by: Hemant Singh Bisht <hsinghbisht@salesforce.com>
2026-05-02 20:40:36 +05:30
18 changed files with 3048 additions and 0 deletions

View File

@ -0,0 +1,367 @@
---
name: creating-sf-skill
description: "AI-assisted skill authoring pipeline for the afv-library. Use when the user wants to create a new skill, update an existing skill, generate a skill spec, scaffold skill files, or add a new capability to the skill catalog. TRIGGER when: user says create skill, new skill, skill creator, author skill, scaffold skill, generate skill, update skill, add a skill, skill pipeline. DO NOT TRIGGER when: user is directly editing an existing SKILL.md without asking for guided authoring.Invoke this skill whenever someone needs to add a new capability to the afv-library skill catalog, or wants to update/improve an existing skill. This includes creating skills for Apex, metadata, LWC, Flow, Agentforce, or any Salesforce domain."
license: LICENSE.txt has complete terms
metadata:
version: "1.0"
stage: Pilot
---
# Instructions
You are a contributor onboarding tool for the [afv-library](https://github.com/forcedotcom/afv-library).
Your job is to take minimal input and generate a complete, validated skill as fast as possible.
Ask **one thing at a time**. Generate first, refine after. Do not explain the pipeline.
The full contribution lifecycle has 5 phases:
| Phase | What | Status |
|-------|------|--------|
| 1 | Gather authoritative input | **This skill handles** |
| 2 | Generate v1 Draft | **This skill handles** |
| 3 | Generate test stubs | **This skill handles** |
| 4 | Run eval, move to Under Review | Next step for contributor |
| 5 | Push PR to afv-library, CI publishes | Final step for contributor |
---
## On Start
If the user passed `$ARGUMENTS` (a skill name), check if `skills/$0/SKILL.md` already exists.
If yes, use `AskUserQuestion`: options `Update existing skill` | `Create new skill with different name`.
Otherwise proceed to Step 1.
---
## Step 1 — Infer or confirm skill type
**Try to infer the skill type from the user's message before asking.**
| If the user mentions… | Infer type |
|----------------------|------------|
| metadata, object, field, validation rule, permission set, flow, layout, component, prompt template | Metadata generation |
| Apex class, trigger, batch, LWC, JavaScript, test class | Code generation |
| deploy, debug, setup, migrate, process, pipeline | Workflow / process |
- If confident: state the inferred type inline. Suggest 2-3 gerund skill names, **pick one as your recommendation, and briefly explain why** (e.g. most specific, matches naming convention, clearest intent). Move to Step 2.
- If genuinely uncertain: use `AskUserQuestion`:
```
question: "What kind of skill do you want to create?"
header: "Skill type"
options:
- label: "Metadata generation"
description: "Creates Salesforce metadata — custom objects, fields, validation rules, etc."
- label: "Code generation"
description: "Generates Apex classes, triggers, LWC components, or other code artifacts."
- label: "Workflow / process"
description: "Guides the user through a multi-step process — deployments, debugging, org setup, etc."
- label: "Other"
description: "Something else — I'll describe it."
```
After confirming type, **suggest 2-3 skill names inline** using the gerund naming convention
(e.g., `generating-validation-rule`, `building-data-cloud-connector`). Present as text, not a question.
---
## Step 2 — Describe the skill + gather reference material
**Do not expose step numbers to the user.** Never say "Step 2" or label sections in user-facing messages.
Use `AskUserQuestion` to understand the use case:
```
header: "Use case"
question: "What's the use case for this skill? Tell me what problem it solves, who would use it, and what it should produce.
If you have any examples, schemas, or docs handy, feel free to share them too — a file path, URL, or paste works."
options:
- label: "I'll describe it here"
description: "Type your use case and I'll generate from that."
- label: "I have reference material"
description: "I'll share a schema, example file, or doc."
```
Read every resource the user provides. Then summarize what you extracted — keep it tight, 2-3 bullets max:
> **Extracted context:**
> • …
> • …
**Now generate the description** using both the user's description and the extracted context.
Expand it to include trigger phrases, `TRIGGER when` / `DO NOT TRIGGER when` clauses, and the word "use".
**Print the description as plain text in your response message first**, then call `AskUserQuestion`. The user cannot see the description inside the tool's question or option fields — it must appear in the message body before the tool call.
Example message format:
```
Here's the skill description I've drafted:
> **Skill name:** `generating-<name>`
>
> **Description:** <full description text here>
Does this capture it?
```
Then use `AskUserQuestion`:
```
question: "Does this description capture what you want the skill to do?"
header: "Description"
options:
- label: "Looks good (Recommended)"
description: "Use this and proceed to generation."
- label: "I want to edit it"
description: "Tell me what to change."
```
If they edit, incorporate and re-confirm.
**Silent background work before generating:**
- Scan `skills/` for existing skills with overlapping scope (duplicate check).
- Identify dependency candidates from existing skill names.
- Infer gotchas from domain knowledge and the provided reference material.
---
## Step 3 — Generate
Tell the user: **"Generating Skill..."**
Then do the work. Do not ask any more questions.
1. Read `./references/progressive_disclosure.md` — use it to decide what
goes in SKILL.md vs `assets/` vs `references/` vs `examples/`.
2. Read `./templates/skill_template.md`.
3. Read `./templates/frontmatter_reference.yaml`.
4. Read `./references/best_practices.md`.
5. Generate all files following the rules below.
### What goes WHERE — hard rules
SKILL.md is **only** for the workflow, rules, and gotchas. Everything else goes into subdirectories.
| Content type | Where it goes |
|-------------|---------------|
| Code templates (`.cls`, `.xml`, `.js`, `.html`) | `assets/` |
| Code examples / sample output | `examples/` |
| API specs, schemas, XML definitions | `references/` or `assets/` |
| Detailed reference tables (> 20 rows) | `references/` |
| Step-by-step guides for sub-procedures | `references/` |
| Input/output example pairs | `examples/` |
| Configuration files, manifests | `assets/` |
**What stays in SKILL.md**: frontmatter, overview (1-3 sentences), scope, required inputs,
workflow steps, constraint table, gotchas table (short), output expectations (file list only),
cross-skill integration table, and Reference File Index.
### How to reference files from SKILL.md
Every file in `assets/`, `references/`, or `examples/` **must** have a specific load instruction
in SKILL.md. Use this pattern:
```markdown
1. **Read the service template** — load `assets/service.cls` before generating.
2. **For REST endpoints**, read `references/rest_api_patterns.md` for status codes.
3. **See example output** in `examples/basic_service.cls` for the expected structure.
```
Never write "see references/ for details." Always name the specific file and scenario.
### Directory structure to create
```
skills/<skill-name>/
├── SKILL.md # Workflow + rules + gotchas ONLY (target < 300 lines)
├── assets/ # Code templates, XML schemas, config files
├── references/ # Prose docs, detailed tables, sub-guides
├── examples/ # Input/output pairs, sample generated files
└── tests/
└── evals/
├── <skill-name>-<scenario-1>/
│ ├── prompt.md # Exact trigger prompt (no heading — just the prompt text)
│ └── gold/ # Expected output artifact
├── <skill-name>-<scenario-2>/
│ ├── prompt.md
│ └── gold/
└── <skill-name>-<scenario-3>/
├── prompt.md
└── gold/
```
Read `./templates/tests_structure.md` before scaffolding `tests/`.
Create `assets/`, `references/`, and `examples/` only if there is content to put in them.
Do NOT create `tests/unit/` — only create `tests/evals/` with 2-3 datasets.
### Generating SKILL.md content
**Frontmatter — always set `stage: Draft`:**
```yaml
---
name: <skill-name>
description: "<from Step 2>"
license: LICENSE.txt has complete terms
metadata:
version: "1.0"
stage: Draft
---
```
**Body rules:**
- Add what the agent lacks, omit what it knows.
- Task-oriented: every section = an instruction, not a description.
- Target < 300 lines for SKILL.md body.
**Required sections:**
| Section | What to write |
|---------|---------------|
| Title + overview | 1-3 sentences — what and why |
| Scope | In-scope / out-of-scope boundary |
| Required Inputs | What context to gather before acting |
| Workflow | Numbered steps with `read` instructions |
| Rules | Hard constraints table |
| Gotchas | Short table of pitfalls — max 10 rows |
| Output Expectations | List of files produced (not their content) |
| Cross-Skill Integration | When to delegate (if applicable) |
| Reference File Index | Maps every subdirectory file to when it's read |
### Write all files
1. Write `skills/<skill-name>/SKILL.md`.
2. Write each `assets/` file — code templates, schemas.
3. Write each `references/` file — detailed guides, tables.
4. Write each `examples/` file — sample inputs/outputs.
5. Write `tests/evals/` datasets — see Step 4 below for eval content rules.
### Validate
Run the validator:
```bash
npx tsx scripts/validate-skills.ts 2>&1
```
For the full list of rules, see `./references/validation_rules.md`.
**If validation fails**: fix the issues and re-run. Only surface to the user if you cannot
resolve an issue yourself.
---
## Step 4 — Review
Tell the user:
> **"Your skill is generated and in draft state — please review and let me know if you'd like any changes."**
Then print the file tree and key decisions:
```
skills/<name>/
├── SKILL.md
├── assets/<files>
├── references/<files>
├── examples/<files>
└── tests/
└── evals/<dataset-1>/, <dataset-2>/, <dataset-3>/
Key decisions:
<e.g. "Formula reference table moved to references/ 25 rows, too large for SKILL.md">
<e.g. "assets/template.xml built from the schema provided in context">
<e.g. "5 gotchas inferred from domain knowledge">
```
Use `AskUserQuestion`:
```
question: "Would you like to make any changes?"
header: "Review"
options:
- label: "Looks good"
description: "Proceed to generating eval datasets."
- label: "I want changes"
description: "Tell me what to modify and I'll update the skill."
```
If changes requested: collect feedback, edit files, re-validate, re-print, return here.
---
## Step 5 — Generate eval datasets
Read `./templates/tests_structure.md` before writing eval files.
For each of 2-3 positive trigger scenarios (derived from the skill description):
**`tests/evals/<skill-name>-<scenario>/prompt.md`** — write the exact prompt a user would type.
No heading, no title, no markdown framing — just the raw prompt text:
```
<The exact user message that should trigger this skill>
```
**`tests/evals/<skill-name>-<scenario>/gold/<artifact>`** — expected output file with the
correct extension for the artifact type (e.g. `ContactEmail.validationRule-meta.xml`, `AccountService.cls`).
Infer extension from skill type. Mark with a stub comment at the top:
```
# STUB — review and update expected output before running eval
```
Do **not** create `seed-data/` folders — those are filled in by the contributor during eval.
Use `AskUserQuestion`:
```
question: "Eval stubs generated. How do they look?"
header: "Evals"
options:
- label: "Looks good"
description: "Proceed to next steps."
- label: "Add or change some"
description: "Tell me what to adjust."
```
---
## Step 6 — Done
Print the final summary:
```
Skill: <name>
Location: skills/<name>/
Stage: Draft
What this skill does:
<1-line summary of primary capability>
<key scope boundary or constraint>
<notable pattern, delegation, or integration>
Next steps:
1. Run the eval prompts in tests/evals/ and review the outputs against gold/.
When the outputs look right, update the frontmatter: stage: Under Review
2. Once you're happy with the skill, open a PR to forcedotcom/afv-library.
CI will validate the skill and publish it to the catalog.
```
---
## Gotchas (for you, the agent)
| Issue | What to do |
|-------|------------|
| Description missing "use" | Validator requires it. Always include "Use when..." in generated descriptions. |
| YAML parse error | Descriptions contain `: ` — always wrap in double quotes. |
| Gerund naming | First word must end in `-ing`. Suggest names that follow this. |
| Body over 500 lines | Split into `references/`. See `./references/progressive_disclosure.md`. |
| User gives vague input | Don't ask repeatedly — make your best guess, generate, and confirm. |
| Validation fails | Fix it yourself. Only ask the user if you genuinely can't resolve it. |
| No reference material provided | Generate representative stubs; mark everything `# STUB`. |
| Skill type is clear from user message | Do not ask — infer it, state it, move on. |

View File

@ -0,0 +1,87 @@
---
name: generating-validation-rule
description: "Create and update Salesforce validation rules for any standard or custom object. Use when the user asks to add a validation rule, enforce field requirements, prevent invalid data entry, or implement record-level business logic constraints. TRIGGER when: user says validation rule, field validation, required field check, data constraint, record validation, prevent invalid data. DO NOT TRIGGER when: user asks about Flow validation or Apex validation logic — delegate to generating-flow or generating-apex."
metadata:
version: "1.0"
stage: Draft
license: LICENSE.txt has complete terms
---
# Generating Validation Rules
Create validation rules that enforce business logic at the record level.
## Scope
- **In scope**: Creating new validation rules, updating existing rules, formula syntax,
error message placement, activation/deactivation.
- **Out of scope**: Flow-based validation (use `generating-flow`), Apex triggers for
complex validation (use `generating-apex`).
## Required Inputs
- Target object (standard or custom)
- Business rule to enforce (in plain language)
- Error message text
- Error location (top of page or specific field)
Defaults:
- Active: true
- Description: auto-generated from the business rule
## Workflow
1. **Identify the object** — confirm the API name of the target object.
2. **Understand the rule** — translate the business requirement into a boolean formula.
3. **Check existing rules** — look for overlapping validation rules on the same object.
4. **Read the metadata template** — load `./assets/validation_rule_template.xml` before generating.
5. **Write the formula** — the formula must evaluate to `TRUE` when the data is **invalid**.
For common formula patterns, read `./references/formula_patterns.md`.
6. **Set error message** — clear, user-friendly message explaining what's wrong and how to fix it.
7. **Create metadata file** — generate the `.validationRule-meta.xml` using the template structure.
Compare against `./examples/require_email_on_contact.xml` for expected output format.
8. **Syntax check** — verify the formula compiles (field API names, function syntax).
9. **Edge case review** — test with null values, blank strings, boundary conditions.
## Rules
| Constraint | Rationale |
|-----------|-----------|
| Formula evaluates to TRUE on invalid data | Salesforce convention — TRUE = error |
| Use API names, not labels | Labels change; API names are stable |
| Handle null fields with `ISBLANK()` | Prevent null pointer errors |
| Use `PRIORVALUE()` only in update context | Not available on insert |
## Gotchas
| Issue | Resolution |
|-------|------------|
| Rule fires on record types it shouldn't | Add `RecordType.DeveloperName` check to formula |
| Rule blocks data migration/bulk loads | Add bypass via Custom Permission or Custom Metadata |
| `ISCHANGED()` always false on insert | Guard with `NOT(ISNEW())` before using `ISCHANGED()` |
| Formula too long (> 5000 chars) | Split into multiple rules or use a helper formula field |
| Error on wrong field | Verify field API name in `errorConditionFormula` attribute |
## Output Expectations
Deliverables per validation rule:
- `<ObjectName>.validationRule-meta.xml`
File structure follows the template in `./assets/validation_rule_template.xml`.
## Cross-Skill Integration
| Need | Delegate to |
|------|-------------|
| Complex multi-object validation | `generating-apex` (trigger-based) |
| Flow-based validation with user prompts | `generating-flow` |
| Custom object creation | `generating-custom-object` |
| Field creation for formula references | `generating-custom-field` |
## Reference File Index
| File | When to read |
|------|-------------|
| `./assets/validation_rule_template.xml` | Before generating any validation rule metadata file |
| `./references/formula_patterns.md` | When writing the formula — common patterns and functions |
| `./examples/require_email_on_contact.xml` | To verify generated output matches expected format |

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<ValidationRule xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>{ObjectApiName}.{RuleDeveloperName}</fullName>
<active>true</active>
<description>{Plain language description of what this rule enforces}</description>
<errorConditionFormula>{Formula that evaluates TRUE when data is INVALID}</errorConditionFormula>
<errorDisplayField>{FieldApiName — omit for page-level errors}</errorDisplayField>
<errorMessage>{User-friendly message: what's wrong and how to fix it}</errorMessage>
</ValidationRule>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<ValidationRule xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Contact.Require_Email</fullName>
<active>true</active>
<description>Requires an email address on all Contact records</description>
<errorConditionFormula>ISBLANK(Email)</errorConditionFormula>
<errorDisplayField>Email</errorDisplayField>
<errorMessage>Email is required on Contact records. Please provide a valid email address.</errorMessage>
</ValidationRule>

View File

@ -0,0 +1,85 @@
# Common Validation Rule Formula Patterns
## Required field (non-blank)
```
ISBLANK(Field__c)
```
Error: "Field is required."
## Required field only on specific record type
```
AND(
RecordType.DeveloperName = "Enterprise",
ISBLANK(Field__c)
)
```
## Numeric range check
```
OR(
Amount__c <= 0,
Amount__c > 1000000
)
```
Error: "Amount must be between 1 and 1,000,000."
## Date must be in the future
```
Close_Date__c <= TODAY()
```
Error: "Close date must be after today."
## Date comparison (end after start)
```
End_Date__c <= Start_Date__c
```
Error: "End date must be after start date."
## Field changed validation (update only)
```
AND(
NOT(ISNEW()),
ISCHANGED(Stage__c),
ISPICKVAL(PRIORVALUE(Stage__c), "Closed Won")
)
```
Error: "Cannot change stage after Closed Won."
## Bypass with Custom Permission
Wrap any formula to allow admin bypass:
```
AND(
NOT($Permission.Bypass_Validation_Rules),
{your formula here}
)
```
## Regex pattern match (email format)
```
NOT(REGEX(Email__c, "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"))
```
## Cross-field dependency
```
AND(
ISPICKVAL(Type__c, "Partner"),
ISBLANK(Partner_Account__c)
)
```
Error: "Partner Account is required when Type is Partner."

View File

@ -0,0 +1,149 @@
# Skill Authoring Best Practices
Principles for writing high-quality skills. Based on patterns observed across the
afv-library and the Agent Skills best practices guide.
## Core Principles
### 1. Add What the Agent Lacks, Omit What It Knows
The most common mistake is including general knowledge the LLM already has.
Focus exclusively on:
- **Project-specific conventions** — naming patterns, directory structure, coding standards
unique to this project.
- **Domain-specific procedures** — workflows, API quirks, deployment steps that are
not publicly documented or are team-specific.
- **Gotchas** — failure modes, edge cases, known bugs that the agent would not anticipate.
**Omit**: general programming concepts, language syntax, well-known design patterns.
### 2. Extract from a Hands-On Task
The best skills are written after doing the task manually:
1. Perform the task yourself (or watch someone do it).
2. Note every decision point, tool invocation, and gotcha encountered.
3. Write the skill as the instructions you wish you had before starting.
4. Feed real execution traces back into the skill.
### 3. Refine with Real Execution
> "The first draft of a skill usually needs refinement. Run the skill against real
> tasks, then feed the results — all of them, not just failures — back into the
> creation process."
After generating a skill:
- Run it against 3-5 real prompts.
- Read the full execution trace, not just the final output.
- Identify: false positives, false negatives, wasted tokens, vague instructions.
### 4. Task-Oriented, Not Concept-Oriented
Structure skills around what the agent should **do**, not what **exists**.
**Bad**: "Apex supports various class types including Service, Selector, Domain..."
**Good**: "1. Identify the class type needed. 2. Read the matching template from assets/.
3. Generate the class following the template pattern."
### 5. Concise Over Comprehensive
Every token in a SKILL.md costs compute and risks confusing the agent with
irrelevant instructions. Cut ruthlessly:
- Remove sections the agent never follows.
- Remove examples that duplicate other examples.
- Remove caveats about edge cases that never happen.
## Description Writing
The description is the most important field — it determines when the skill triggers.
### Do
- Front-load the primary use case.
- Include specific keywords users say: "Apex", ".cls", "trigger", "batch job".
- Add `TRIGGER when:` and `DO NOT TRIGGER when:` clauses.
- **Be pushy**: list implicit triggers where the user may not use the domain term directly.
- Keep it factual and specific.
### Don't
- Use vague language: "helps with Salesforce development".
- Include implementation details: "uses the AccountService pattern".
- Make it too narrow: only one exact phrase triggers it.
- Make it too broad: triggers on everything.
### Be Pushy About Triggers
Err on the side of listing more implicit triggers, not fewer. A skill that never fires is useless;
a skill that fires on a near-miss is recoverable. Explicitly call out contexts where the skill
applies **even if the user doesn't name the domain directly**:
> "TRIGGER when: user asks to add a rule, restrict a field, or enforce a policy — even if they
> don't explicitly say 'validation rule' or 'Apex'."
This is especially important for domain keywords the user may not know (e.g., they say "make this
field required" not "add a required validation rule").
### Template
```
<Primary purpose statement>. ALWAYS ACTIVATE when <high-confidence triggers>.
Use this skill for <broader use cases>. TRIGGER when: <specific list include implicit
contexts where the user may not use the exact domain term>.
DO NOT TRIGGER when: <exclusions with delegation targets>.
```
## Workflow Structure
### Phase-Based
For multi-step skills, use numbered phases:
```markdown
### Phase 1 — Discover
1. **Read project conventions** — check for existing patterns.
2. **Identify inputs** — what context is needed.
### Phase 2 — Generate
3. **Read template** — load the matching template.
4. **Author code** — generate following the template.
### Phase 3 — Validate
5. **Run checks** — execute validation tools.
6. **Report** — present results.
```
### Decision Matrices
For complex decisions, use tables instead of nested conditionals:
```markdown
| Scenario | Pattern | Template |
|----------|---------|----------|
| Standard async work | Queueable | assets/queueable.cls |
| Large datasets | Batch Apex | assets/batch.cls |
| Recurring schedule | Schedulable | assets/schedulable.cls |
```
## Anti-Patterns
| Anti-Pattern | Why It's Bad | Fix |
|-------------|-------------|-----|
| Wall of text with no structure | Agent can't find relevant instructions | Use headers, tables, numbered steps |
| Generic advice | Wastes tokens on things the agent knows | Add only project-specific knowledge |
| Deeply nested conditionals | Agent loses track of which branch it's in | Flatten into decision tables |
| Too many optional steps | Agent follows all of them, wasting time | Make steps required or remove them |
| Missing gotchas section | Agent hits known pitfalls | Add gotchas from real execution failures |
| Referencing files that don't exist | Agent hallucinates content | Verify all referenced paths exist |
| Duplicating content from other skills | Wastes tokens and risks staleness | Reference the other skill instead |
## Cross-Skill Integration Patterns
When your skill needs another skill's capability:
1. **Delegate explicitly**: "For Apex tests, delegate to `generating-apex-test` skill."
2. **Define boundaries clearly**: "This skill handles X. For Y, use Z skill."
3. **Don't duplicate**: If another skill covers a topic, reference it, don't copy.

View File

@ -0,0 +1,85 @@
# Progressive Disclosure — What Goes Where
SKILL.md is the workflow controller. It tells the agent what to do and when to read other files.
It does **not** contain the detailed content itself.
## Hard Rules
These are not guidelines. Always follow them.
| Content type | Goes in | Example filename |
|-------------|---------|-----------------|
| Code templates | `assets/` | `service.cls`, `batch.cls`, `rest_resource.cls` |
| XML schemas / metadata templates | `assets/` | `meta_template.xml`, `object_schema.xml` |
| Config samples, manifests | `assets/` | `sfdx-project.json`, `package.xml` |
| API reference docs | `references/` | `api_patterns.md`, `rest_endpoints.md` |
| Detailed decision tables (> 20 rows) | `references/` | `error_codes.md`, `field_mappings.md` |
| Step-by-step sub-procedures | `references/` | `deployment_steps.md`, `migration_guide.md` |
| Edge case handling guides | `references/` | `edge_cases.md`, `null_handling.md` |
| Full input/output examples | `examples/` | `basic_usage.md`, `AccountService.cls` |
| Sample generated files | `examples/` | `example_output.xml`, `sample_trigger.trigger` |
## What Stays in SKILL.md
Only these belong in the main file:
- **Frontmatter** (name, description, metadata)
- **Overview** (1-3 sentences)
- **Scope** (in/out boundary)
- **Required Inputs** (bullet list of what to gather)
- **Workflow** (numbered steps with read instructions to subdirectory files)
- **Rules/Constraints** (short table — max 15 rows)
- **Gotchas** (short table — max 10 rows)
- **Output Expectations** (file list only, not content)
- **Cross-Skill Integration** (delegation table)
- **Reference File Index** (maps every subdirectory file to its trigger)
Target: **< 300 lines** for SKILL.md body.
## How to Reference from SKILL.md
Every file in a subdirectory needs a specific load instruction in the workflow section.
**Good — embedded in a workflow step:**
```markdown
1. **Read the service template** — load `assets/service.cls` before generating.
2. **Check error handling patterns** — if the class needs custom exceptions, read `references/error_handling.md`.
3. **Compare against example** — verify output matches `examples/AccountService.cls`.
```
**Good — in the Reference File Index:**
```markdown
| File | When to read |
|------|-------------|
| `assets/service.cls` | Before generating any service class |
| `references/error_handling.md` | When implementing custom exception handling |
| `examples/AccountService.cls` | To verify generated output matches expected format |
```
**Bad — vague references the agent will ignore:**
```markdown
See the `references/` directory for more details.
Check `assets/` for templates.
```
## Common Mistakes
| Mistake | Why it's wrong | Fix |
|---------|---------------|-----|
| Inlining a 50-line code template in SKILL.md | Bloats the file, wastes tokens on every invocation | Put in `assets/`, add a read instruction |
| Pasting an API spec into the workflow | Spec content is static reference, not workflow | Put in `references/`, read only when needed |
| Adding example output inline | Examples are for validation, not every-run context | Put in `examples/`, reference from output section |
| Creating subdirectory but not referencing it | Agent never reads unreferenced files | Add entry to Reference File Index |
| Using "see references/" without naming a file | Agent doesn't know which file to read | Always name the specific file |
## Token Budget
| Component | Target | Maximum |
|-----------|--------|---------|
| SKILL.md body | < 300 lines | 500 lines |
| Single reference file | < 200 lines | 300 lines |
| Single asset file | No line limit | Keep focused on one template |
| Single example file | < 100 lines | 200 lines |

View File

@ -0,0 +1,129 @@
# Validation Rules Reference
Complete reference for all validation checks enforced by `scripts/validate-skills.ts`.
The validator runs structure checks first (before reading SKILL.md), then content checks.
## Structure Checks
These run on every entry in `skills/` before SKILL.md is read.
A fatal error aborts all content checks for that entry.
### 1. Entry Must Be a Directory
- **Severity**: Error (fatal)
- **Rule**: No loose files in `skills/` — only directories.
- **Fix**: Move the file into a skill directory or remove it.
### 2. Must Contain SKILL.md
- **Severity**: Error (fatal)
- **Rule**: Every skill directory must have a `SKILL.md` file.
- **Fix**: Create `SKILL.md` with proper frontmatter and body.
### 3. Name Must Be Kebab-Case
- **Severity**: Error
- **Pattern**: `^[a-z0-9]+(-[a-z0-9]+)*$`
- **Allowed**: lowercase letters, digits, hyphens
- **Not allowed**: uppercase, underscores, dots, spaces
- **Examples**:
- `generating-apex` (valid)
- `Generating-Apex` (invalid — uppercase)
- `generating_apex` (invalid — underscore)
### 4. Name <= 64 Characters
- **Severity**: Error
- **Rule**: Directory name must not exceed 64 characters.
### 5. Gerund First Word
- **Severity**: Warning (not blocking)
- **Rule**: First word of the skill name should end in `-ing`.
- **Rationale**: Convention for consistency (e.g., `generating-`, `building-`, `deploying-`).
- **Examples**:
- `generating-apex` (pass)
- `agentforce-development` (warning — "agentforce" doesn't end in -ing)
### 6. No Nested Skills
- **Severity**: Error
- **Rule**: No subdirectory of a skill may contain its own `SKILL.md`.
- **Fix**: Flatten nested skills to be siblings under `skills/`.
## Content Checks
These run after SKILL.md is read successfully.
### 7. Valid YAML Frontmatter Block
- **Severity**: Error (fatal)
- **Rule**: SKILL.md must start with `---\n...\n---` YAML frontmatter.
- **Fix**: Add frontmatter between `---` delimiters at the top of the file.
### 8. JSON-Compatible YAML
- **Severity**: Error
- **Rule**: Frontmatter must parse with `js-yaml` JSON_SCHEMA.
- **Common cause**: Unquoted values containing `: ` (colon + space).
- **Fix**: Wrap values in single or double quotes.
```yaml
# BAD
description: Primary skill: generates Apex classes
# GOOD
description: "Primary skill: generates Apex classes"
```
### 9. `name` Matches Directory
- **Severity**: Error
- **Rule**: The `name` field in frontmatter must exactly match the directory name.
- **Fix**: Ensure `name: generating-apex` if the directory is `skills/generating-apex/`.
### 10. `description` Present and Non-Empty
- **Severity**: Error
- **Rule**: Frontmatter must include a `description` field with content.
### 11. Body Non-Empty
- **Severity**: Error
- **Rule**: There must be content after the frontmatter block.
### 12. Description >= 20 Words
- **Severity**: Error
- **Rule**: Description must contain at least 20 words.
- **Rationale**: Short descriptions don't provide enough trigger context.
### 13. Description <= 1024 Characters
- **Severity**: Error
- **Rule**: Description must not exceed 1024 characters.
### 14. Description Contains "use"
- **Severity**: Error
- **Rule**: Description must include the word "use" (case-insensitive).
- **Rationale**: Enforces trigger/activation language.
- **Fix**: Include phrases like "Use when...", "Use this skill for...", "Used to...".
### 15. Body <= 500 Lines
- **Severity**: Warning (not blocking)
- **Rule**: Skill body should be under 500 lines for context efficiency.
- **Fix**: Extract detailed content into `references/` with conditional load instructions.
## Running Validation
```bash
# Validate all skills
npm run validate:skills
# Validate only changed skills (CI mode)
npm run validate:skills -- --changed --base=origin/main
# Validate a specific skill (filter output)
npx tsx scripts/validate-skills.ts 2>&1 | grep -A5 "<skill-name>"
```

View File

@ -0,0 +1,92 @@
# AFV-Library Skill Frontmatter — Complete Field Reference
#
# All skills in skills/<name>/SKILL.md must include YAML frontmatter
# between --- delimiters. This file documents every supported field.
#
# Validation: scripts/validate-skills.ts
# ============================================================
# REQUIRED FIELDS
# ============================================================
name:
type: string
required: true
description: "Unique skill identifier. Must match the directory name exactly."
validation:
pattern: "^[a-z0-9]+(-[a-z0-9]+)*$"
max_length: 64
example: "generating-apex"
description:
type: string
required: true
description: >
What the skill does and when it should activate. This is the primary
signal used for skill routing — it must be rich and specific.
validation:
min_words: 20
max_chars: 1024
must_contain: "use"
tips:
- "Include TRIGGER when and DO NOT TRIGGER when clauses"
- "Front-load the most important use case"
- "Include specific keywords users would say"
- "Must be wrapped in double quotes if it contains ': '"
example: >
"Primary Apex authoring skill for class generation, refactoring, and review.
ALWAYS ACTIVATE when the user mentions Apex, .cls, triggers, or asks to
create/refactor a class. Use this skill for requests involving SObject CRUD,
mapping collections, or code review of existing Apex."
# ============================================================
# OPTIONAL FIELDS
# ============================================================
license:
type: string
required: false
description: "License for the skill. Top-level frontmatter field — NOT nested under metadata."
default: "LICENSE.txt has complete terms"
example: "LICENSE.txt"
metadata:
type: object
required: false
fields:
version:
type: string
description: "Version. New skills use \"1.0\"."
example: "1.0"
stage:
type: enum
values: ["Draft", "Under Review", "Published"]
description: "Current lifecycle stage."
argument-hint:
type: string
description: "CLI usage hint shown in autocomplete."
example: "<class-name> [--type service|selector|batch]"
# ============================================================
# COMMON MISTAKES
# ============================================================
#
# 1. Unquoted description with ": " — causes YAML parse error
# BAD: description: Primary skill: generates Apex
# GOOD: description: "Primary skill: generates Apex"
#
# 2. Name doesn't match directory
# Directory: skills/generating-apex/
# Frontmatter name must be: generating-apex
#
# 3. Description too short
# Must be at least 20 words. Include trigger context.
#
# 4. Missing "use" in description
# Validator checks for the word "use" (case-insensitive)
# Include: "Use when...", "Use this skill for...", etc.
#
# 5. license nested under metadata
# BAD: metadata:
# license: ...
# GOOD: license: Apache-2.0 # top-level field

View File

@ -0,0 +1,109 @@
---
name: <SKILL_NAME>
description: "<DESCRIPTION 20+ words, <= 1024 chars, must contain 'use'. Include TRIGGER when and DO NOT TRIGGER when clauses.>"
license: LICENSE.txt has complete terms
metadata:
version: "1.0"
stage: Draft
---
# <Skill Title>
<1-3 sentence overview: what this skill does and why it exists.>
## Scope
- **In scope**: <what this skill handles>
- **Out of scope**: <what this skill does NOT handle delegate to other skills>
---
## Required Inputs
Gather or infer before proceeding:
- <Input 1>: <description and how to obtain>
- <Input 2>: <description and how to obtain>
- <Input 3>: <description and how to obtain>
Defaults unless specified:
- <Default 1>
- <Default 2>
If the user provides a clear, complete request, generate immediately without unnecessary back-and-forth.
---
## Workflow
All steps are sequential. Do not skip or reorder. If blocked, stop and ask for missing context.
1. **<Step name>**
- <Action to take>
- <Expected outcome>
2. **Read the template** — load `assets/<template-file>` before generating.
3. **<Step name>**
- <Action to take>
- For <specific sub-topic>, read `references/<topic>.md`.
4. **<Step name>**
- <Action to take>
- Compare output against `examples/<example-file>`.
---
## Rules / Constraints
<!-- Short table. If > 15 rows, move extras to references/. -->
| Constraint | Rationale |
|-----------|-----------|
| <Rule 1> | <Why this matters> |
| <Rule 2> | <Why this matters> |
---
## Gotchas
<!-- Short table — max 10 rows. Common pitfalls and fixes. -->
| Issue | Resolution |
|-------|------------|
| <Problem 1> | <How to fix it> |
| <Problem 2> | <How to fix it> |
---
## Output Expectations
Deliverables:
- <File 1>: `<path pattern>`
- <File 2>: `<path pattern>`
File structure follows the template in `assets/<template-file>`.
---
## Cross-Skill Integration
<!-- When to delegate to other skills. Remove section if not applicable. -->
| Need | Delegate to |
|------|-------------|
| <Need 1> | `<other-skill>` skill |
| <Need 2> | `<other-skill>` skill |
---
## Reference File Index
<!-- MANDATORY if any assets/, references/, or examples/ files exist. -->
<!-- Map every subdirectory file to the specific scenario that needs it. -->
| File | When to read |
|------|-------------|
| `assets/<template-file>` | Before generating — use as the starting structure |
| `references/<topic>.md` | When handling <specific scenario> |
| `examples/<example-file>` | To verify generated output matches expected format |

View File

@ -0,0 +1,98 @@
# Tests Directory Structure
Every skill in afv-library must include a `tests/` directory. This is the canonical layout.
## Full Structure
```
skills/<skill-name>/
└── tests/
└── evals/
├── <skill-name>-<scenario-1>/
│ ├── prompt.md # Exact trigger prompt for this scenario
│ ├── gold/ # Expected output artifact(s)
│ └── seed-data/ # Input dependencies
├── <skill-name>-<scenario-2>/
│ ├── prompt.md
│ ├── gold/
│ └── seed-data/
└── <skill-name>-<scenario-3>/
├── prompt.md
├── gold/
└── seed-data/
```
---
## evals/
### Dataset naming
`<skill-name>-<short-scenario>` — kebab-case, descriptive, specific to the test case.
Examples:
- `generating-validation-rule-required-field`
- `generating-apex-service-class`
- `deploying-permission-set-with-field-access`
Generate **2-3 datasets** covering the main positive trigger scenarios. Scenarios should be
distinct — different objects, different rule types, or different complexity levels.
### prompt.md
The exact prompt a user would type to trigger the skill for this scenario. No heading, no title,
no markdown framing — just the raw prompt text.
**Format:**
```
<The exact user message that should trigger this skill>
```
**Example:**
```
Create a validation rule that requires the Email field on the Contact object.
```
### gold/
Expected output artifact(s) the skill should produce. Use the correct file extension for the
artifact type — do not use `expected.md`.
| Skill type | Extension examples |
|------------|-------------------|
| Validation rule | `ContactEmailRequired.validationRule-meta.xml` |
| Apex class | `AccountService.cls` |
| LWC component | `myComponent.html`, `myComponent.js` |
| Custom object | `My_Object__c.object-meta.xml` |
| Permission set | `My_Permission_Set.permissionset-meta.xml` |
Mark the top of every gold file as a stub:
```
<!-- STUB — review and update expected output before running eval -->
```
or for non-XML:
```
# STUB — review and update expected output before running eval
```
### seed-data/
Input dependencies the eval needs to run. This is the context the skill would normally gather
from the org or the user:
- Object field lists (JSON)
- Existing metadata samples (XML)
- Schema fragments
- Any other input fixture
Derive from reference material provided during authoring. If none was provided, generate
representative seed data and mark it as a stub.
---
## Lifecycle
| Stage | Who | Action |
|-------|-----|--------|
| Draft | Agent (this skill) | Scaffold all stubs, mark everything `# STUB` |
| Under Review | Contributor | Fill in gold files, validate fixtures, run evals manually |
| Published | CI | Evals run automatically on PRs |

View File

@ -0,0 +1,319 @@
---
name: developing-datacloud-code-extension
description: "Develop and deploy Data Cloud Code Extensions using SF CLI plugin. Use this skill when creating custom Python transformations for Data Cloud, deploying code extensions, or testing data transformations. Supports init, run, scan, and deploy operations."
---
# developing-datacloud-code-extension Skill
## Overview
This skill provides a complete workflow for developing, testing, and deploying custom Python code extensions to Salesforce Data Cloud. Code extensions allow you to write Python transformations that read from and write to Data Lake Objects (DLOs) and Data Model Objects (DMOs).
## When to Use
- User wants to create a new code extension project
- User needs to test a code extension locally
- User wants to scan code for required permissions
- User needs to deploy a code extension to Data Cloud
- User is working with Data Cloud transformations
- User wants to read/write DLO or DMO data programmatically
## Prerequisites Check
Before executing any code extension commands, verify prerequisites:
1. **SF CLI with plugin installed**
```bash
sf plugins --core | grep data-code-extension
```
If not installed:
```bash
sf plugins install @salesforce/plugin-data-codeextension
```
2. **Python 3.11**
```bash
python --version # Should show 3.11.x
```
3. **Data Cloud Custom Code SDK**
```bash
pip list | grep salesforce-data-customcode
```
If not installed:
```bash
pip install salesforce-data-customcode
```
4. **Docker running** (for deploy only)
```bash
docker ps
```
5. **Authenticated org**
```bash
sf org display --target-org <org_alias> --json
```
## Skill Workflow
### Phase 1: Initialize Project
Create a new code extension project with scaffolding.
**Commands:**
For **script-based** code extensions (batch transformations):
```bash
sf data-code-extension script init --package-dir <directory>
```
For **function-based** code extensions (real-time):
```bash
sf data-code-extension function init --package-dir <directory>
```
**Required Option:**
- `--package-dir, -p` - Directory path where the package will be created
**What it creates:**
```
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
└── README.md
```
## Directory Context During Workflow
**IMPORTANT:** Understanding the directory structure is critical for successful deployment.
**Commands and their directory requirements:**
| Command | Run From | Path/File Argument |
|---------|----------|-------------------|
| `init` | Parent directory | `<project-name>` or `.` |
| `scan` | Project root | `./payload/entrypoint.py` |
| `run` | Project root | `./payload/entrypoint.py` |
| `deploy` | Project root | `--package-dir ./payload` (**REQUIRED**) |
**CRITICAL: The `--package-dir` argument in deploy command MUST point to the `payload` directory, not the project root.**
### Phase 2: Develop Transformation
Edit `payload/entrypoint.py` with transformation logic.
**Script Example (Batch):**
```python
from datacustomcode import Client
client = Client()
# Read from DLO
df = client.read_dlo('Employee__dll')
# Transform data (uppercase position field)
df['position_upper'] = df['position'].str.upper()
# Write to output DLO
client.write_to_dlo('Employee_Upper__dll', df, 'overwrite')
```
**Function Example (Real-time):**
```python
from datacustomcode import FunctionClient
def transform(event, context):
client = FunctionClient(context)
input_data = event['data']
output = {
'name': input_data['name'].upper(),
'status': 'processed'
}
return output
```
**Common Operations:**
- `client.read_dlo('DLO_Name__dll')` - Read from DLO
- `client.read_dmo('DMO_Name')` - Read from DMO
- `client.write_to_dlo('DLO_Name__dll', df, 'overwrite')` - Write to DLO
- `client.write_to_dmo('DMO_Name', df, 'upsert')` - Write to DMO
### Phase 3: Scan for Permissions
Scan the entrypoint file to detect required permissions and generate config.json.
**Command:**
```bash
sf data-code-extension script scan --entrypoint ./payload/entrypoint.py
```
**What it detects:**
- Read permissions for DLOs/DMOs
- Write permissions for DLOs/DMOs
- Python package dependencies
- Updates `config.json` and `requirements.txt`
### Phase 4: Validate DLO Schema (Pre-Test Check)
**CRITICAL: Before running tests locally, validate that all DLOs used in your code exist and have the expected fields.**
#### Step 4a: Extract DLOs from config.json
After scanning, review the generated `config.json` to identify all DLOs:
```bash
cat payload/config.json
```
#### Step 4b: Validate Each DLO Schema
**Use the `getting-datacloud-schema` skill to verify DLOs exist and check field names.**
For each DLO referenced in your code:
1. **Verify DLO exists:**
```bash
python3 scripts/get_dlo_schema.py <org_alias> <dlo_name>
```
2. **Verify field names match** — compare fields used in your `entrypoint.py` against the DLO schema.
3. **Check all DLOs:**
- Validate all DLOs in `read` permissions
- Validate all DLOs in `write` permissions
- Check field names match exactly (case-sensitive)
- Verify data types are compatible with operations
#### Step 4c: Validation Checklist
Before proceeding to run, ensure:
- [ ] All DLOs in config.json exist in target org
- [ ] All field names used in code exist in DLO schemas
- [ ] Field data types match your transformation logic
- [ ] Primary key fields are correctly identified
- [ ] Write target DLOs are created and accessible
### Phase 5: Test Locally
After validating DLO schemas, run the code extension locally against your Data Cloud org.
**Command:**
```bash
sf data-code-extension script run --entrypoint <entrypoint_file> --target-org <org_alias> [options]
```
**Options:**
- `--target-org, -o` - SF CLI org alias (required)
- `--config-file, -c` - Custom config file path
**If you get errors:**
- Re-validate DLO schemas
- Check field names are exact matches
- Verify data types are compatible
- Review error messages for field/DLO issues
### Phase 6: Deploy to Data Cloud
Deploy the code extension to Data Cloud for scheduled or on-demand execution.
**CRITICAL: You MUST specify `--package-dir ./payload` to point to the payload directory created by init.**
**Command:**
```bash
sf data-code-extension script deploy --target-org <org_alias> --name <name> --package-dir ./payload --package-version <version> --description <description> [options]
```
**Required Options:**
- `--target-org, -o` - SF CLI org alias
- `--name, -n` - Name for code extension deployment
- `--package-dir` - Path to payload directory (**REQUIRED** - must be `./payload` when running from project root)
- `--package-version` - Version string (default: 0.0.1)
- `--description` - Description of code extension
**Optional Options:**
- `--cpu-size` - CPU size: CPU_L, CPU_XL, CPU_2XL (default), CPU_4XL
- `--function-invoke-opt` - Function invoke options (for function type)
- `--network` - Docker network (default: default)
**After deployment:**
- Navigate to Data Cloud in Salesforce UI
- Go to Data Transforms section
- Find your deployment by name
- Click "Run Now" to execute
- Schedule for recurring execution
## Error Handling
### Common Issues and Solutions
| Error | Solution |
|-------|----------|
| `command data-code-extension not found` | `sf plugins install @salesforce/plugin-data-codeextension` |
| `datacustomcode CLI not found` | `pip install salesforce-data-customcode` |
| `Python version mismatch` | Use pyenv: `pyenv install 3.11.0 && pyenv local 3.11.0` |
| `Cannot connect to Docker daemon` | Start Docker Desktop |
| `No org found for alias` | `sf org login web --alias <org_alias>` |
| `config.json not found` | `sf data-code-extension script scan --entrypoint ./payload/entrypoint.py` |
| `DLO not found` | Verify DLO exists (use getting-datacloud-schema skill), check spelling and `__dll` suffix |
| `Permission denied writing` | Re-run scan, verify target DLO exists and is writable |
| `Deploy fails - wrong directory` | Ensure `--package-dir` points to `payload/` directory, not project root |
## Best Practices
### Development
1. Always scan before testing — run scan after code changes
2. Test locally first — use `run` command before deploying
3. Use version control — git commit after each successful test
4. Version your deployments — use semantic versioning (1.0.0, 1.1.0, etc.)
5. Deploy from project root with `--package-dir ./payload`
### Performance
- **CPU_L**: Small datasets (< 1M records)
- **CPU_2XL**: Medium datasets (1M-10M records)
- **CPU_4XL**: Large datasets (> 10M records)
### Security
1. No hardcoded credentials — use SF CLI authentication only
2. Validate input data — check for nulls and data types
3. Limit write permissions — only grant necessary DLO/DMO access
## Integration with Other Skills
**Use with getting-datacloud-schema skill (CRITICAL for validation):**
The `getting-datacloud-schema` skill is **required** for validating DLOs before testing code extensions.
**Use with Datakit Workflow:**
1. Create DLO via code extension
2. Map DLO to DMO using datakit workflow
3. Use DMO in segments and activations
## Command Reference
| Command | Purpose | Required Args |
|---------|---------|---------------|
| `script init` | Create new script project | --package-dir |
| `function init` | Create new function project | --package-dir |
| `script scan` | Generate config | entrypoint file |
| `script run` | Test locally | entrypoint file, --target-org |
| `script deploy` | Deploy to Data Cloud | --target-org, --name, --package-dir, --package-version, --description |
## Resources
- SF CLI Plugin: https://github.com/salesforcecli/plugin-data-code-extension
- Python SDK: https://github.com/forcedotcom/datacloud-customcode-python-sdk
- Data Cloud Docs: https://help.salesforce.com/s/articleView?id=sf.c360_a_intro.htm
- Python SDK PyPI: https://pypi.org/project/salesforce-data-customcode/
## Notes
- Code extensions run in isolated Python 3.11 environment
- Docker is required only for deployment, not for local testing
- Use SF CLI authentication only (no separate credential files)
- Scan command auto-detects permissions from code
- Local run uses actual Data Cloud data (not mocked)
- Deployments are versioned and can be rolled back in UI

View File

@ -0,0 +1,193 @@
# developing-datacloud-code-extension Skill
## Overview
A skill that provides a complete workflow for developing, testing, and deploying custom Python code extensions to Salesforce Data Cloud using the SF CLI plugin.
## What It Does
This skill helps you create Data Cloud Code Extensions through a complete workflow:
1. **Init** - Create new code extension project with scaffolding
2. **Develop** - Write Python transformation logic
3. **Scan** - Auto-detect permissions and generate config
4. **Run** - Test locally against Data Cloud org
5. **Deploy** - Package and deploy to Data Cloud
## Usage
**Initialize a project:**
```
"Create a new Data Cloud code extension project called employee-transform"
"Initialize a code extension to transform employee data"
```
**Test locally:**
```
"Run the code extension in my-transform directory against afvibe org"
"Test the entrypoint.py file locally"
```
**Scan for permissions:**
```
"Scan the entrypoint.py to generate config"
"Update permissions in config.json"
```
**Deploy:**
```
"Deploy Employee_Upper code extension to afvibe"
"Deploy this transform with package-version 1.0.0"
```
### Direct Command Usage
```bash
# Initialize project
sf data-code-extension script init --package-dir <directory>
# Scan for permissions
sf data-code-extension script scan --entrypoint ./payload/entrypoint.py
# Test locally
sf data-code-extension script run --entrypoint ./payload/entrypoint.py --target-org <org_alias>
# Deploy
sf data-code-extension script deploy --target-org <org_alias> --name <name> --package-version <version> --description <description> --package-dir ./payload
```
## Prerequisites
1. **SF CLI with Plugin**
```bash
sf plugins install @salesforce/plugin-data-codeextension
```
2. **Python 3.11**
```bash
python --version # Must be 3.11.x
```
3. **Data Cloud Custom Code SDK**
```bash
pip install salesforce-data-customcode
```
4. **Docker** (for deploy only)
- Docker Desktop or equivalent
5. **Authenticated Org**
```bash
sf org login web --alias <org_alias>
```
## Quick Start
### Complete End-to-End Example
```bash
# 1. Create project
mkdir employee-transform && cd employee-transform
sf data-code-extension script init --package-dir .
# 2. Edit payload/entrypoint.py with your transformation
# 3. Scan for permissions
sf data-code-extension script scan --entrypoint ./payload/entrypoint.py
# 4. Test locally
sf data-code-extension script run --entrypoint ./payload/entrypoint.py --target-org afvibe
# 5. Deploy (MUST include --package-dir ./payload)
sf data-code-extension script deploy \
--target-org afvibe \
--name Employee_Upper \
--package-version 1.0.0 \
--description "Uppercase employee positions" \
--package-dir ./payload
```
## Example Transformation
**Read from DLO, transform, write to DLO:**
```python
from datacustomcode import Client
client = Client()
# Read employee data from DLO
employees = client.read_dlo('Employee__dll')
# Transform - uppercase position field
employees['position_upper'] = employees['position'].str.upper()
# Select output columns
output = employees[['id', 'name', 'position_upper']]
# Write to output DLO
client.write_to_dlo('Employee_Upper__dll', output, 'overwrite')
print(f"Processed {len(output)} employee records")
```
## Project Structure
After `init`, you'll have:
```
my-transform/
├── payload/
│ ├── entrypoint.py # Your transformation code
│ ├── config.json # Permissions and configuration
│ └── requirements.txt # Python dependencies
└── README.md
```
## Common Operations
### Read/Write DLOs
```python
# Read
df = client.read_dlo('Employee__dll')
# Write (modes: 'overwrite', 'append')
client.write_to_dlo('Employee_Upper__dll', df, 'overwrite')
```
### Read/Write DMOs
```python
# Read
df = client.read_dmo('EmployeeDMO')
# Write (modes: 'upsert', 'insert')
client.write_to_dmo('EmployeeDMO', df, 'upsert')
```
## Troubleshooting
| Error | Quick Fix |
|-------|-----------|
| Plugin not found | `sf plugins install @salesforce/plugin-data-codeextension` |
| Python SDK missing | `pip install salesforce-data-customcode` |
| Wrong Python version | Use pyenv to install 3.11.0 |
| Org not connected | `sf org login web --alias <alias>` |
| Config missing | Run scan command |
| DLO not found | Check DLO name, use getting-datacloud-schema skill |
| Docker error | Start Docker Desktop |
## CPU Size Selection
| CPU Size | Use Case | Data Volume |
|----------|----------|-------------|
| CPU_L | Small datasets | < 1M records |
| CPU_XL | Medium datasets | 1M-5M records |
| CPU_2XL | Large datasets (default) | 5M-10M records |
| CPU_4XL | Very large datasets | > 10M records |
## Resources
- **SF CLI Plugin**: https://github.com/salesforcecli/plugin-data-code-extension
- **Python SDK**: https://github.com/forcedotcom/datacloud-customcode-python-sdk
- **Data Cloud Docs**: https://help.salesforce.com/s/articleView?id=sf.c360_a_intro.htm
- **SDK on PyPI**: https://pypi.org/project/salesforce-data-customcode/

View File

@ -0,0 +1,269 @@
# Data Cloud Code Extension - Quick Reference
## Command Cheat Sheet
### Initialize Project
```bash
# Create script project
sf data-code-extension script init --package-dir <directory>
# Create function project
sf data-code-extension function init --package-dir <directory>
# Examples
sf data-code-extension script init --package-dir .
sf data-code-extension script init --package-dir my-transform
```
### Scan for Permissions
```bash
# Basic scan
sf data-code-extension script scan --entrypoint ./payload/entrypoint.py
# Preview without saving
sf data-code-extension script scan --entrypoint ./payload/entrypoint.py --dry-run
# Custom config location
sf data-code-extension script scan --entrypoint ./payload/entrypoint.py --config ./custom-config.json
# Skip requirements.txt
sf data-code-extension script scan --entrypoint ./payload/entrypoint.py --no-requirements
```
### Run Locally
```bash
# Basic run
sf data-code-extension script run --entrypoint ./payload/entrypoint.py --target-org <org_alias>
# With custom config
sf data-code-extension script run --entrypoint ./payload/entrypoint.py -o <org_alias> -c custom-config.json
# Examples
sf data-code-extension script run --entrypoint ./payload/entrypoint.py --target-org afvibe
sf data-code-extension script run --entrypoint ./payload/entrypoint.py -o afvibe
```
### Deploy
```bash
# Minimal deployment (MUST include --package-dir ./payload)
sf data-code-extension script deploy \
--target-org <org_alias> \
--name <name> \
--package-version <version> \
--description "<description>" \
--package-dir ./payload
# Full options
sf data-code-extension script deploy \
--target-org <org_alias> \
--name <name> \
--package-version <version> \
--description "<description>" \
--cpu-size <CPU_L|CPU_XL|CPU_2XL|CPU_4XL> \
--package-dir ./payload
# Examples (CRITICAL: Always include --package-dir ./payload)
sf data-code-extension script deploy \
--target-org afvibe \
--name Employee_Upper \
--package-version 1.0.0 \
--description "Uppercase employee positions" \
--package-dir ./payload
```
## Common Workflows
### New Project from Scratch
```bash
# 1. Create directory
mkdir my-transform && cd my-transform
# 2. Initialize
sf data-code-extension script init --package-dir .
# 3. Edit payload/entrypoint.py with your transformation
# 4. Scan
sf data-code-extension script scan --entrypoint ./payload/entrypoint.py
# 5. Test
sf data-code-extension script run --entrypoint ./payload/entrypoint.py --target-org afvibe
# 6. Deploy (MUST include --package-dir ./payload)
sf data-code-extension script deploy \
--target-org afvibe \
--name MyTransform \
--package-version 1.0.0 \
--description "My transformation" \
--package-dir ./payload
```
### Update Existing Code Extension
```bash
# 1. Edit payload/entrypoint.py
# 2. Re-scan
sf data-code-extension script scan --entrypoint ./payload/entrypoint.py
# 3. Test
sf data-code-extension script run --entrypoint ./payload/entrypoint.py -o afvibe
# 4. Deploy with new version (include --package-dir ./payload)
sf data-code-extension script deploy \
-o afvibe \
-n MyTransform \
--package-version 1.1.0 \
--description "Updated transformation" \
--package-dir ./payload
```
## Python Code Patterns
### Read/Write DLO
```python
from datacustomcode import Client
client = Client()
# Read
df = client.read_dlo('Employee__dll')
# Transform
df['new_field'] = df['old_field'].str.upper()
# Write (modes: 'overwrite', 'append')
client.write_to_dlo('Output__dll', df, 'overwrite')
```
### Read/Write DMO
```python
# Read
df = client.read_dmo('EmployeeDMO')
# Write (modes: 'upsert', 'insert')
client.write_to_dmo('EmployeeDMO', df, 'upsert')
```
### Multiple DLO Operations
```python
# Read multiple
employees = client.read_dlo('Employee__dll')
departments = client.read_dlo('Department__dll')
# Join
merged = employees.merge(departments, on='dept_id')
# Write multiple
client.write_to_dlo('Enriched__dll', merged, 'overwrite')
client.write_to_dmo('EmployeeDMO', merged, 'upsert')
```
### Data Transformations
```python
import pandas as pd
# Filter
active = df[df['status'] == 'Active']
# Computed column
df['full_name'] = df['first'] + ' ' + df['last']
# Aggregate
summary = df.groupby('dept')['salary'].mean()
# Conditional
df['grade'] = df['position'].apply(
lambda x: 'Senior' if 'VP' in x else 'Junior'
)
```
## Option Reference
### --cpu-size
- `CPU_L` - Small datasets (< 1M records)
- `CPU_XL` - Medium datasets (1M-5M)
- `CPU_2XL` - Large datasets (5M-10M) **[default]**
- `CPU_4XL` - Very large (> 10M records)
### Write Modes
- `overwrite` - Replace all data
- `append` - Add to existing data
- `upsert` - Update or insert (DMO only)
- `insert` - Insert only (DMO only)
## Troubleshooting Quick Fixes
```bash
# Plugin not found
sf plugins install @salesforce/plugin-data-codeextension
# Python SDK missing
pip install salesforce-data-customcode
# Verify Python version (must be 3.11.x)
python --version
# Org not connected
sf org login web --alias <org_alias>
# Config missing
sf data-code-extension script scan --entrypoint ./payload/entrypoint.py
# Docker not running (for deploy)
# Start Docker Desktop
```
## File Structure
```
my-project/
├── payload/
│ ├── entrypoint.py # Main code
│ ├── config.json # Auto-generated permissions
│ └── requirements.txt # Auto-generated dependencies
└── README.md
```
## config.json Format
```json
{
"version": "1.0",
"permissions": {
"read": ["Employee__dll", "Department__dll"],
"write": ["Enriched__dll"]
},
"resources": {
"cpu_size": "CPU_2XL"
}
}
```
## Common Errors
| Error | Quick Fix |
|-------|-----------|
| Plugin not found | `sf plugins install @salesforce/plugin-data-codeextension` |
| Python SDK missing | `pip install salesforce-data-customcode` |
| Wrong Python version | Use pyenv to install 3.11.0 |
| Org not connected | `sf org login web --alias <alias>` |
| Config missing | Run scan command |
| DLO not found | Check DLO name, use getting-datacloud-schema skill |
| Docker error | Start Docker Desktop |
## Deployment Checklist
- [ ] Code written in entrypoint.py
- [ ] Scanned for permissions
- [ ] Tested locally
- [ ] Version number decided
- [ ] Description added
- [ ] CPU size chosen
- [ ] Docker running
- [ ] Org authenticated
## Resources
- Plugin: https://github.com/salesforcecli/plugin-data-code-extension
- Python SDK: https://github.com/forcedotcom/datacloud-customcode-python-sdk
- Data Cloud Docs: https://help.salesforce.com/s/articleView?id=sf.c360_a_intro.htm

View File

@ -0,0 +1,380 @@
---
name: getting-datacloud-schema
description: "Retrieve Data Lake Object (DLO) and Data Model Object (DMO) schema information from Salesforce Data Cloud using REST APIs. Use this skill when you need to inspect DLO or DMO field definitions, data types, or metadata. Takes org alias and optional DLO/DMO name as parameters."
---
# getting-datacloud-schema Skill
## Overview
This skill retrieves Data Lake Object (DLO) and Data Model Object (DMO) schema information from Salesforce Data Cloud using the SSOT REST API. It can list all DLOs or DMOs in an org, or retrieve detailed schema for a specific DLO or DMO.
## When to Use
- User wants to see all DLOs or DMOs in a Data Cloud org
- User needs field schema for a specific DLO or DMO
- User is exploring Data Cloud data structures
- User needs to understand DLO or DMO field types and metadata
## Prerequisites
- SF CLI installed and authenticated to target org
- Org has Data Cloud enabled
- User has appropriate Data Cloud permissions
## Skill Execution
### Parameters
1. **org_alias** (required): The SF CLI org alias (e.g., 'afvibe', 'myorg')
2. **dlo_name** (optional): Specific DLO developer name (e.g., 'Employee__dll')
3. **dmo_name** (optional): Specific DMO developer name (e.g., 'Individual__dlm')
### Step 1: Discover Connected Org
First, run `sf org list` to find out which org is connected and extract the alias to use for all subsequent calls:
```bash
sf org list
```
Example output:
```
┌────┬───────┬──────────────────────────┬────────────────────┬───────────┐
│ │ Alias │ Username │ Org Id │ Status │
├────┼───────┼──────────────────────────┼────────────────────┼───────────┤
│ 🍁 │ myorg │ chandresh@afvidedemo.org │ 00DKZ00000b80NT2AY │ Connected │
└────┴───────┴──────────────────────────┴────────────────────┴───────────┘
```
Extract the **Alias** value (e.g., `myorg`) from the output and use it as the `<org_alias>` for all subsequent calls. Use `--all` to see expired and deleted scratch orgs as well.
### Step 2: Validate SF CLI Authentication
Before making API calls, verify the org is connected:
```bash
sf org display --target-org <org_alias> --json
```
If not connected, inform user to run:
```bash
sf org login web --alias <org_alias>
```
### Step 3a: Execute DLO Schema Script
Use the Python script to retrieve DLO information:
**To list all DLOs:**
```bash
python3 ./scripts/get_dlo_schema.py <org_alias>
```
**To get specific DLO schema:**
```bash
python3 ./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>
```
**To get specific DMO schema:**
```bash
python3 ./scripts/get_dmo_schema.py <org_alias> <dmo_name>
```
### Step 4: Present Results
Parse and present the results in a user-friendly format:
**For DLO List:**
- Show DLO name, label, category, and ID
- Indicate total count
- Highlight DLOs with data (totalRecords > 0)
**For DLO Schema:**
- Show basic info (name, label, category, status)
- List all fields with:
- Field name
- Data type
- Primary key indicator
- Nullable status
- Highlight custom fields (exclude system fields like DataSource__c, cdp_sys_*)
- Show record count if available
**For DMO List:**
- Show DMO name, label, category, and ID
- Indicate total count
**For DMO Schema:**
- Show basic info (name, label, category, description)
- List all fields with:
- Field name
- Data type
- Primary key indicator
- Nullable status
- Show dataspace information if available
### Step 5: Offer Next Steps
After displaying results, suggest relevant follow-up actions:
- Query data from the DLO
- Create calculated insights
- Build segments
- Set up data streams
- Create DMO mappings
## API Endpoints Used
### List All DLOs
```
GET /services/data/v64.0/ssot/data-lake-objects
```
Response structure:
```json
{
"dataLakeObjects": [
{
"name": "Employee__dll",
"label": "Employee",
"category": "Profile",
"id": "1dlXXXXXXXXXXXXXXX",
"status": "ACTIVE",
"totalRecords": 12,
"fields": [...]
}
],
"totalSize": 5
}
```
### Get DLO Schema
```
GET /services/data/v64.0/ssot/data-lake-objects/{dlo_name}
```
Response structure (same as individual object in list response, but wrapped in paginated format).
### List All DMOs
```
GET /services/data/v64.0/ssot/data-model-objects
```
Response structure:
```json
{
"dataModelObjects": [
{
"name": "Individual__dlm",
"label": "Individual",
"category": "Profile",
"id": "0dmXXXXXXXXXXXXXXX",
"fields": [...]
}
],
"totalSize": 10
}
```
### Get DMO Schema
```
GET /services/data/v64.0/ssot/data-model-objects/{dmo_name}
```
Response structure (same as individual object in list response, but wrapped in paginated format).
## Error Handling
**Common Issues:**
1. **Org not connected**
- Message: "Org not connected"
- Solution: Ask user to authenticate via SF CLI
2. **DLO not found**
- Message: "DLO 'XYZ__dll' not found"
- Solution: List all DLOs first to verify name
5. **DMO not found**
- Message: "DMO 'XYZ__dlm' not found"
- Solution: List all DMOs first to verify name
3. **Permission issues**
- Message: HTTP 403 errors
- Solution: Verify user has Data Cloud permissions
4. **API version mismatch**
- Current: v64.0
- Solution: Script can be updated for newer API versions
## Example Usage
**Example 1: List all DLOs**
```
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
4. Display formatted list of DLOs
```
**Example 2: Get specific DLO schema**
```
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
4. Display field schema with types and metadata
```
**Example 3: Explore DLOs then get schema**
```
User: "What DLOs exist in myorg and show me the schema for the Employee one"
Response:
1. Run sf org list to discover connected org alias
2. List all DLOs in myorg
3. Identify Employee__dll
4. Get detailed schema for Employee__dll
5. Present both results
```
**Example 4: List all DMOs**
```
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
4. Display formatted list of DMOs
```
**Example 5: Get specific DMO schema**
```
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
4. Display field schema with types and metadata
```
**Example 6: Explore DMOs then get schema**
```
User: "What DMOs exist in myorg and show me the schema for the Individual one"
Response:
1. Run sf org list to discover connected org alias
2. List all DMOs in myorg
3. Identify Individual__dlm
4. Get detailed schema for Individual__dlm
5. Present both results
```
## Output Format
### DLO List Output
```
Found 5 DLOs in org 'afvibe':
1. DataCustomCodeLogs__dll
Label: DataCustomCodeLogs
Category: Engagement
Records: 233
2. Employee__dll
Label: Employee
Category: Profile
Records: 12
[...]
```
### DLO Schema Output
```
DLO: Employee__dll
Label: Employee
Category: Profile
Status: ACTIVE
Records: 12
Custom Fields:
• id__c (Text) - Primary Key
• name__c (Text)
• position__c (Text)
• manager_id__c (Number)
System Fields:
• DataSource__c (Text)
• InternalOrganization__c (Text)
• cdp_sys_SourceVersion__c (Text)
Next steps:
- Query data: SELECT * FROM Employee__dll LIMIT 10
- Create segment based on position field
- Set up data stream for real-time updates
```
### DMO List Output
```
Found 10 DMOs in org 'afvibe':
1. Individual__dlm
Label: Individual
Category: Profile
2. ContactPointEmail__dlm
Label: Contact Point Email
Category: Profile
[...]
```
### DMO Schema Output
```
DMO: Individual__dlm
Label: Individual
Category: Profile
Description: Represents an individual person
Fields:
• Id__c (Text) - Primary Key
• FirstName__c (Text)
• LastName__c (Text)
• BirthDate__c (DateTime)
Next steps:
- Query data: SELECT * FROM Individual__dlm LIMIT 10
- View DLO mappings to this DMO
- Create calculated insights
```
## Notes
- DLO names always end with `__dll` suffix
- DMO names always end with `__dlm` suffix
- Field names always end with `__c` suffix
- System fields (DataSource__c, KQ_*, cdp_sys_*) are automatically added
- Primary key fields are required for DLO and DMO queries
- API supports pagination (limit/offset) for large result sets
## Related Skills
- **datakit_workflow**: For DMO mapping operations
- **datakit_validation**: For validating datakit configurations
- Use this skill before creating DMO mappings to understand source DLO structure

View File

@ -0,0 +1,191 @@
# getting-datacloud-schema Skill
## Overview
A skill that retrieves Data Lake Object (DLO) and Data Model Object (DMO) schema information from Salesforce Data Cloud using REST APIs.
## Usage
**List all DLOs:**
```
"Show me all DLOs in afvibe org"
"List Data Lake Objects in myorg"
```
**Get specific DLO schema:**
```
"Get the schema for Employee__dll in afvibe"
"What fields does the Employee__dll DLO have in myorg?"
```
**List all DMOs:**
```
"Show me all DMOs in afvibe org"
"List Data Model Objects in myorg"
```
**Get specific DMO schema:**
```
"Get the schema for Individual__dlm in afvibe"
"What fields does the Individual__dlm DMO have in myorg?"
```
### Direct Script Usage
You can also run the scripts directly:
```bash
# List all DLOs
python3 scripts/get_dlo_schema.py <org_alias>
# Get specific DLO schema
python3 scripts/get_dlo_schema.py <org_alias> <dlo_name>
# List all DMOs
python3 scripts/get_dmo_schema.py <org_alias>
# Get specific DMO schema
python3 scripts/get_dmo_schema.py <org_alias> <dmo_name>
```
**Examples:**
```bash
# List all DLOs in afvibe org
python3 scripts/get_dlo_schema.py afvibe
# Get Employee__dll schema from afvibe
python3 scripts/get_dlo_schema.py afvibe Employee__dll
# List all DMOs in afvibe org
python3 scripts/get_dmo_schema.py afvibe
# Get Individual__dlm schema from afvibe
python3 scripts/get_dmo_schema.py afvibe Individual__dlm
```
## Prerequisites
1. **SF CLI Installed**
```bash
sf --version
```
2. **Authenticated to Target Org**
```bash
sf org login web --alias <org_alias>
```
3. **Python 3 and Dependencies**
```bash
pip install requests pyyaml
```
4. **Data Cloud Enabled**
- Org must have Data Cloud provisioned
- User must have Data Cloud permissions
## What It Does
### List All DLOs
- Calls: `GET /services/data/v64.0/ssot/data-lake-objects`
- Returns: All DLOs with name, label, category, ID, record count
- Shows paginated results
### Get DLO Schema
- Calls: `GET /services/data/v64.0/ssot/data-lake-objects/{dlo_name}`
- Returns: Detailed field schema including field names, data types, primary key indicators, nullable status
### List All DMOs
- Calls: `GET /services/data/v64.0/ssot/data-model-objects`
- Returns: All DMOs with name, label, category, ID
- Shows paginated results
### Get DMO Schema
- Calls: `GET /services/data/v64.0/ssot/data-model-objects/{dmo_name}`
- Returns: Detailed field schema including field names, data types, primary key indicators, nullable status
## API Endpoints
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/services/data/v64.0/ssot/data-lake-objects` | GET | List all DLOs |
| `/services/data/v64.0/ssot/data-lake-objects/{name}` | GET | Get DLO schema |
| `/services/data/v64.0/ssot/data-model-objects` | GET | List all DMOs |
| `/services/data/v64.0/ssot/data-model-objects/{name}` | GET | Get DMO schema |
## Output Format
### DLO List
```
Found 5 DLOs in org 'afvibe':
1. DataCustomCodeLogs__dll
Label: DataCustomCodeLogs
Category: Engagement
Records: 233
2. Employee__dll
Label: Employee
Category: Profile
Records: 12
```
### DLO Schema
```
DLO: Employee__dll
Label: Employee
Category: Profile
Status: ACTIVE
Records: 12
Fields (9 total):
- id__c (Text) - Primary Key
- name__c (Text)
- position__c (Text)
- manager_id__c (Number)
- DataSource__c (Text)
[...]
```
### DMO List
```
Found 10 DMOs in org 'afvibe':
1. Individual__dlm
Label: Individual
Category: Profile
2. ContactPointEmail__dlm
Label: Contact Point Email
Category: Profile
```
### DMO Schema
```
DMO: Individual__dlm
Label: Individual
Category: Profile
Fields (8 total):
- Id__c (Text) - Primary Key
- FirstName__c (Text)
- LastName__c (Text)
- BirthDate__c (DateTime)
[...]
```
## Troubleshooting
| Issue | Fix |
|-------|-----|
| Org not connected | `sf org login web --alias <org_alias>` |
| Module not found: requests | `pip install requests pyyaml` |
| DLO not found | Verify name ends with `__dll`, list all DLOs first |
| DMO not found | Verify name ends with `__dlm`, list all DMOs first |
| Permission denied | Verify user has Data Cloud permissions |
## Related Skills
- **datakit workflow**: For DMO mapping operations
- **datakit validation**: For validating datakit configurations
- Use this skill before creating DMO mappings to understand source DLO structure

View File

@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""
List all Data Lake Objects and retrieve schema for one DLO using REST API.
Uses SF CLI for authentication.
"""
import subprocess
import json
import sys
import requests
def authenticate_to_org(org_alias):
"""
Authenticate to Salesforce org using SF CLI.
Args:
org_alias: SF CLI org alias (e.g., 'afvibe')
Returns:
Tuple of (instance_url, access_token, username)
"""
print(f"🔐 Authenticating to Salesforce org '{org_alias}'...")
try:
result = subprocess.run(
['sf', 'org', 'display', '--target-org', org_alias, '--json'],
capture_output=True,
text=True,
check=True
)
org_data = json.loads(result.stdout)
if org_data.get('status') != 0:
raise Exception(f"SF CLI returned error: {org_data}")
org_info = org_data['result']
if org_info.get('connectedStatus') != 'Connected':
raise Exception(f"Org '{org_alias}' is not connected. Run: sf org login web --alias {org_alias}")
instance_url = org_info['instanceUrl']
access_token = org_info['accessToken']
username = org_info.get('username', 'Unknown')
print(f"✅ Authenticated as: {username}")
print(f"📍 Instance: {instance_url}\n")
return instance_url, access_token, username
except subprocess.CalledProcessError as e:
raise Exception(f"SF CLI command failed: {e.stderr}")
except (json.JSONDecodeError, KeyError) as e:
raise Exception(f"Failed to parse SF CLI output: {e}")
def list_all_dlos(instance_url, access_token, api_version='v64.0'):
"""
List all Data Lake Objects using SSOT REST API.
Args:
instance_url: Salesforce instance URL
access_token: OAuth access token
api_version: API version (default: v64.0)
Returns:
List of DLO dictionaries
"""
url = f"{instance_url}/services/data/{api_version}/ssot/data-lake-objects"
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
print("📋 Fetching all Data Lake Objects...")
response = requests.get(url, headers=headers)
if response.status_code != 200:
raise Exception(f"API Error: HTTP {response.status_code}\n{response.text[:500]}")
response_data = response.json()
# Extract DLO list from paginated response
if isinstance(response_data, dict) and 'dataLakeObjects' in response_data:
dlos = response_data['dataLakeObjects']
total_size = response_data.get('totalSize', len(dlos))
print(f"✅ Found {len(dlos)} DLOs (Total: {total_size})\n")
else:
# Fallback if response format is different
dlos = response_data if isinstance(response_data, list) else []
print(f"✅ Found {len(dlos)} DLOs\n")
return dlos
def get_dlo_schema(instance_url, access_token, dlo_name, api_version='v64.0'):
"""
Get detailed schema for a specific DLO.
Args:
instance_url: Salesforce instance URL
access_token: OAuth access token
dlo_name: DLO developer name (e.g., 'Employee__dll')
api_version: API version (default: v64.0)
Returns:
DLO detail dictionary with full schema
"""
url = f"{instance_url}/services/data/{api_version}/ssot/data-lake-objects/{dlo_name}"
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
print(f"🔍 Fetching schema for DLO: {dlo_name}...")
response = requests.get(url, headers=headers)
if response.status_code != 200:
raise Exception(f"API Error: HTTP {response.status_code}\n{response.text[:500]}")
response_data = response.json()
# Extract DLO from paginated response
if isinstance(response_data, dict) and 'dataLakeObjects' in response_data:
dlos = response_data['dataLakeObjects']
if dlos:
return dlos[0] # Return first (should be only) DLO
else:
raise Exception(f"DLO '{dlo_name}' not found")
else:
# Fallback if response format is different
return response_data
def display_dlo_list(dlos):
"""Display summary of all DLOs."""
print("=" * 80)
print("📊 DATA LAKE OBJECTS")
print("=" * 80)
for idx, dlo in enumerate(dlos, 1):
print(f"\n{idx}. {dlo.get('name', 'Unknown')}")
print(f" Label: {dlo.get('label', 'N/A')}")
print(f" Category: {dlo.get('category', 'N/A')}")
if 'id' in dlo:
print(f" ID: {dlo['id']}")
def display_dlo_schema(dlo_detail):
"""Display detailed schema information for a DLO."""
print("\n" + "=" * 80)
print(f"🔍 SCHEMA DETAILS FOR: {dlo_detail.get('name')}")
print("=" * 80)
print(f"\n📝 Basic Information:")
print(f" Name: {dlo_detail.get('name')}")
print(f" Label: {dlo_detail.get('label')}")
print(f" Category: {dlo_detail.get('category')}")
print(f" Description: {dlo_detail.get('description', 'N/A')}")
if 'dataspaceInfo' in dlo_detail:
dataspaces = dlo_detail['dataspaceInfo']
dataspace_names = [ds.get('name', 'Unknown') for ds in dataspaces]
print(f" Dataspaces: {', '.join(dataspace_names)}")
# Display field schema
fields = dlo_detail.get('fields', [])
if fields:
print(f"\n🔧 Fields ({len(fields)} total):")
print("-" * 80)
# Show all fields with detailed info
for field in fields:
print(f"\n{field.get('name')}")
print(f" Label: {field.get('label', 'N/A')}")
print(f" Data Type: {field.get('dataType', 'Unknown')}")
print(f" Primary Key: {field.get('isPrimaryKey', False)}")
print(f" Nullable: {field.get('isNullable', True)}")
if 'length' in field:
print(f" Length: {field['length']}")
if 'precision' in field:
print(f" Precision: {field['precision']}")
if 'scale' in field:
print(f" Scale: {field['scale']}")
else:
print("\n ⚠️ No fields found in schema")
# Show full JSON (optional, can be commented out)
print("\n" + "=" * 80)
print("📄 FULL SCHEMA (JSON):")
print("=" * 80)
print(json.dumps(dlo_detail, indent=2))
def main():
"""Main execution function."""
if len(sys.argv) < 2:
print("Usage: python list_dlos_and_schema.py <org_alias> [dlo_name]")
print("\nExamples:")
print(" python list_dlos_and_schema.py afvibe")
print(" python list_dlos_and_schema.py afvibe Employee__dll")
sys.exit(1)
org_alias = sys.argv[1]
specific_dlo = sys.argv[2] if len(sys.argv) > 2 else None
try:
# Step 1: Authenticate
instance_url, access_token, username = authenticate_to_org(org_alias)
# Step 2: List all DLOs
dlos = list_all_dlos(instance_url, access_token)
display_dlo_list(dlos)
# Step 3: Get schema for a specific DLO
if specific_dlo:
# User specified a DLO name
dlo_detail = get_dlo_schema(instance_url, access_token, specific_dlo)
display_dlo_schema(dlo_detail)
elif dlos:
# Get schema for the first DLO
first_dlo = dlos[0]
dlo_name = first_dlo.get('name')
dlo_detail = get_dlo_schema(instance_url, access_token, dlo_name)
display_dlo_schema(dlo_detail)
else:
print("\n⚠️ No DLOs found in this org")
print("\n✅ Done!")
except Exception as e:
print(f"\n❌ Error: {e}")
sys.exit(1)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,233 @@
#!/usr/bin/env python3
"""
List all Data Model Objects and retrieve schema for one DMO using REST API.
Uses SF CLI for authentication.
"""
import subprocess
import json
import sys
import requests
def authenticate_to_org(org_alias):
"""
Authenticate to Salesforce org using SF CLI.
Args:
org_alias: SF CLI org alias (e.g., 'afvibe')
Returns:
Tuple of (instance_url, access_token, username)
"""
print(f"🔐 Authenticating to Salesforce org '{org_alias}'...")
try:
result = subprocess.run(
['sf', 'org', 'display', '--target-org', org_alias, '--json'],
capture_output=True,
text=True,
check=True
)
org_data = json.loads(result.stdout)
if org_data.get('status') != 0:
raise Exception(f"SF CLI returned error: {org_data}")
org_info = org_data['result']
if org_info.get('connectedStatus') != 'Connected':
raise Exception(f"Org '{org_alias}' is not connected. Run: sf org login web --alias {org_alias}")
instance_url = org_info['instanceUrl']
access_token = org_info['accessToken']
username = org_info.get('username', 'Unknown')
print(f"✅ Authenticated as: {username}")
print(f"📍 Instance: {instance_url}\n")
return instance_url, access_token, username
except subprocess.CalledProcessError as e:
raise Exception(f"SF CLI command failed: {e.stderr}")
except (json.JSONDecodeError, KeyError) as e:
raise Exception(f"Failed to parse SF CLI output: {e}")
def list_all_dmos(instance_url, access_token, api_version='v64.0'):
"""
List all Data Model Objects using SSOT REST API.
Args:
instance_url: Salesforce instance URL
access_token: OAuth access token
api_version: API version (default: v64.0)
Returns:
List of DMO dictionaries
"""
url = f"{instance_url}/services/data/{api_version}/ssot/data-model-objects"
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
print("📋 Fetching all Data Model Objects...")
response = requests.get(url, headers=headers)
if response.status_code != 200:
raise Exception(f"API Error: HTTP {response.status_code}\n{response.text[:500]}")
response_data = response.json()
# Extract DMO list from paginated response
if isinstance(response_data, dict) and 'dataModelObject' in response_data:
dmos = response_data['dataModelObject']
total_size = response_data.get('totalSize', len(dmos))
print(f"✅ Found {len(dmos)} DMOs (Total: {total_size})\n")
else:
# Fallback if response format is different
dmos = response_data if isinstance(response_data, list) else []
print(f"✅ Found {len(dmos)} DMOs\n")
return dmos
def get_dmo_schema(instance_url, access_token, dmo_name, api_version='v64.0'):
"""
Get detailed schema for a specific DMO.
Args:
instance_url: Salesforce instance URL
access_token: OAuth access token
dmo_name: DMO developer name (e.g., 'Individual__dlm')
api_version: API version (default: v64.0)
Returns:
DMO detail dictionary with full schema
"""
url = f"{instance_url}/services/data/{api_version}/ssot/data-model-objects/{dmo_name}"
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
print(f"🔍 Fetching schema for DMO: {dmo_name}...")
response = requests.get(url, headers=headers)
if response.status_code != 200:
raise Exception(f"API Error: HTTP {response.status_code}\n{response.text[:500]}")
response_data = response.json()
# Single DMO endpoint returns the object directly (not wrapped in an array)
return response_data
def display_dmo_list(dmos):
"""Display summary of all DMOs."""
print("=" * 80)
print("📊 DATA MODEL OBJECTS")
print("=" * 80)
for idx, dmo in enumerate(dmos, 1):
print(f"\n{idx}. {dmo.get('name', 'Unknown')}")
print(f" Label: {dmo.get('label', 'N/A')}")
print(f" Category: {dmo.get('category', 'N/A')}")
print(f" Creation Type: {dmo.get('creationType', 'N/A')}")
print(f" Data Space: {dmo.get('dataSpaceName', 'N/A')}")
def display_dmo_schema(dmo_detail):
"""Display detailed schema information for a DMO."""
print("\n" + "=" * 80)
print(f"🔍 SCHEMA DETAILS FOR: {dmo_detail.get('name')}")
print("=" * 80)
print(f"\n📝 Basic Information:")
print(f" Name: {dmo_detail.get('name')}")
print(f" Label: {dmo_detail.get('label')}")
print(f" Category: {dmo_detail.get('category')}")
print(f" Creation Type: {dmo_detail.get('creationType', 'N/A')}")
print(f" Data Space: {dmo_detail.get('dataSpaceName', 'N/A')}")
# Display field schema
fields = dmo_detail.get('fields', [])
if fields:
print(f"\n🔧 Fields ({len(fields)} total):")
print("-" * 80)
# Show all fields with detailed info
for field in fields:
print(f"\n{field.get('name')}")
print(f" Label: {field.get('label', 'N/A')}")
print(f" Data Type: {field.get('type', 'Unknown')}")
print(f" Primary Key: {field.get('isPrimaryKey', False)}")
print(f" Creation Type: {field.get('creationType', 'N/A')}")
print(f" Usage Tag: {field.get('usageTag', 'N/A')}")
if 'length' in field:
print(f" Length: {field['length']}")
if 'precision' in field:
print(f" Precision: {field['precision']}")
if 'scale' in field:
print(f" Scale: {field['scale']}")
else:
print("\n ⚠️ No fields found in schema")
# Show full JSON
print("\n" + "=" * 80)
print("📄 FULL SCHEMA (JSON):")
print("=" * 80)
print(json.dumps(dmo_detail, indent=2))
def main():
"""Main execution function."""
if len(sys.argv) < 2:
print("Usage: python get_dmo_schema.py <org_alias> [dmo_name]")
print("\nExamples:")
print(" python get_dmo_schema.py afvibe")
print(" python get_dmo_schema.py afvibe Individual__dlm")
sys.exit(1)
org_alias = sys.argv[1]
specific_dmo = sys.argv[2] if len(sys.argv) > 2 else None
try:
# Step 1: Authenticate
instance_url, access_token, username = authenticate_to_org(org_alias)
# Step 2: List all DMOs
dmos = list_all_dmos(instance_url, access_token)
display_dmo_list(dmos)
# Step 3: Get schema for a specific DMO
if specific_dmo:
# User specified a DMO name
dmo_detail = get_dmo_schema(instance_url, access_token, specific_dmo)
display_dmo_schema(dmo_detail)
elif dmos:
# Get schema for the first DMO
first_dmo = dmos[0]
dmo_name = first_dmo.get('name')
dmo_detail = get_dmo_schema(instance_url, access_token, dmo_name)
display_dmo_schema(dmo_detail)
else:
print("\n⚠️ No DMOs found in this org")
print("\n✅ Done!")
except Exception as e:
print(f"\n❌ Error: {e}")
sys.exit(1)
if __name__ == '__main__':
main()