mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-08 08:13:49 +08:00
Compare commits
3 Commits
415df9b8a0
...
da8e3ffacc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da8e3ffacc | ||
|
|
f5e63fd06a | ||
|
|
8f70982668 |
367
skills/creating-sf-skill/SKILL.md
Normal file
367
skills/creating-sf-skill/SKILL.md
Normal 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. |
|
||||
@ -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 |
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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."
|
||||
149
skills/creating-sf-skill/references/best_practices.md
Normal file
149
skills/creating-sf-skill/references/best_practices.md
Normal 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.
|
||||
@ -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 |
|
||||
129
skills/creating-sf-skill/references/validation_rules.md
Normal file
129
skills/creating-sf-skill/references/validation_rules.md
Normal 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>"
|
||||
```
|
||||
@ -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
|
||||
109
skills/creating-sf-skill/templates/skill_template.md
Normal file
109
skills/creating-sf-skill/templates/skill_template.md
Normal 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 |
|
||||
98
skills/creating-sf-skill/templates/tests_structure.md
Normal file
98
skills/creating-sf-skill/templates/tests_structure.md
Normal 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 |
|
||||
319
skills/developing-datacloud-code-extension/SKILL.md
Normal file
319
skills/developing-datacloud-code-extension/SKILL.md
Normal 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
|
||||
193
skills/developing-datacloud-code-extension/references/README.md
Normal file
193
skills/developing-datacloud-code-extension/references/README.md
Normal 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/
|
||||
@ -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
|
||||
380
skills/getting-datacloud-schema/SKILL.md
Normal file
380
skills/getting-datacloud-schema/SKILL.md
Normal 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
|
||||
191
skills/getting-datacloud-schema/references/README.md
Normal file
191
skills/getting-datacloud-schema/references/README.md
Normal 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
|
||||
244
skills/getting-datacloud-schema/scripts/get_dlo_schema.py
Executable file
244
skills/getting-datacloud-schema/scripts/get_dlo_schema.py
Executable 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()
|
||||
233
skills/getting-datacloud-schema/scripts/get_dmo_schema.py
Executable file
233
skills/getting-datacloud-schema/scripts/get_dmo_schema.py
Executable 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()
|
||||
Loading…
Reference in New Issue
Block a user