ddb-import — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ddb-import (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Turns a player's D&D Beyond character (a URL, an id, or pasted JSON) into a fully-built Foundry PC — the same real type:character + advancement build that [[pc-builder]] produces, just sourced from a DDB sheet instead of a typed concept. It adds NO new mechanics: the parse-ddb-character tool does the deterministic transcription, this skill does the judgment, and the [[pc-builder]] back half (create-pc → gear → finishing pass) does the build.
The seam (design.md §7 — "skills decide / tools do"). parse-ddb-character is a pure transcriber: it emits a normalized plan with raw DDB names + flags and does zero compendium lookup. This skill makes every fuzzy/policy decision the tool deliberately won't: mapping a DDB name to the exact premium-2024 entry, deciding "trivial spelling delta vs genuine miss", and stopping to ask rather than substituting or inventing.
Follow the shared project authoring policy: read _shared/authoring-policy.md — 2024 by default · compendium-FIRST (premium PHB/DMG/MM; copy, don't author) · never the SRD · custom = copy→modify→rename · no 2024 match → STOP and ASK. The importer inherits this hard: create-pc resolves class/species/ background by exact name in the premium books and ERRORS on a miss (it never falls back to the SRD), so you must canonicalize DDB names before building, and ask on anything that won't resolve.
Access — we never touch the player's D&D Beyond account. A public character is fetched by id; a private one (the common case) is handled by asking the player to set it Public or paste/save its JSON. We do not accept or store a D&D Beyond session ("cobalt") cookie — it is account-password-equivalent. parse-ddb-character enforces this: it only fetches public characters.Call `parse-ddb-character` with exactly one of:
167582904) or `url` (a dndbeyond.com link)— fetches the character if it is Public.
{success, data, …} envelope or the inner data). Usethis whenever the player gives you JSON directly.
If the fetch returns PRIVATE (403), tell the player: "That character is private — on D&D Beyond set its sharing to Public (Manage → Character Privacy → Public), or paste/save the character JSON and I'll import that." Do not ask for their cookie. A 404 means the id is wrong or the character was deleted.
The tool returns { success, plan, message }. The `plan` is the structured import (see ddb/parse.ts for the shape): name, edition (2024/2014/mixed), abilities (final scores), classes[] (primary first, each with subclass), species, background, proficiencies (skills/expertise/saves/languages/ tools/armor/weapons), options (fightingStyle + other named picks), spells (cantrips + prepared/ known + per-spell all), inventory[], feats[], currency, hp, art.avatarUrl, and crucially `unresolved[]` (every homebrew / 2014-legacy / custom entry) plus abilityNotes / warnings.
Read the message digest aloud to the user (who the character is, level, edition) so they can confirm it's the right one before you build.
For every named entity the build needs — class, each subclass, species, background, each feat, each spell, each inventory item — map the raw DDB name to the exact premium-book name. DDB names won't always match ("Variant Human" vs "Human", "Longbow, +1" vs the magic-variant entry, curly apostrophes, casing, renamed subclasses). Use the forgiving front door, then carry the exact name forward:
search-compendium (substring, premium-first) to find the real entry by name.search-compendium-spells / search-compendium-items (faceted, return a usable uuid) for spells/gear.Lineage-split species. Some 2024 species are split by sub-lineage in the premium books while DDB stores a single species name plus a legacy/lineage option. Combine them to get the exact entry: the plan's species.fullName + the matching options.other legacy → the real premium name. E.g. DDB species "Tiefling" + options.other: ["Infernal Legacy"] → premium "Tiefling, Infernal" (the books have Tiefling, Abyssal/Chthonic/Infernal — there is no bare "Tiefling"). search-compendium the base name to see the variants, then pass the exact one to create-pc. Watch for this on Tiefling (and any species whose search-compendium returns several "<Name>, <lineage>" entries).
Edition rule (your house rule): confirm the obvious, ask the ambiguous. When a 2014/legacy DDB entity has a clear 2024 premium equivalent, canonicalize to it and tell the user what you mapped (e.g. "DDB had the 2014 Variant Human; using the 2024 Human"). When the match is ambiguous or absent, do not guess — take it to Step 4.
This is the import policy (the GM has authorized it for DDB imports — the §2.3 "explicit permission" path). The use case is parity: a GM reproducing a player's sheet for play, so nothing is silently lost. After Step 3, handle anything still unresolved (a plan.unresolved[] entry, or a name that wouldn't canonicalize) compendium-first, author-as-fallback, never drop:
search-compendium for variant names,lineage splits, and "(Varies)" focuses (e.g. DDB "Staff" → premium Arcane Focus (Varies); DDB "Robe" → premium Robe) before concluding there's no match.
text the parser captured (plan.feats[].description / plan.inventory[].description):
add-feature mode feature, featureType: "passive", with the description;import-item the closest base then rename, else add-item with the description.(homebrew / 3rd-party); verify mechanics." If you can't infer the mechanics from the text, author it as a passive* feature carrying the text + flag, and the GM finishes it.
review.
The one thing still worth a quick ASK (not authoring): a genuinely ambiguous mapping fork between real premium options (e.g. which of two plausible 2024 subclasses a renamed legacy subclass maps to). Authoring is for content with no premium home; asking is for a real choice between premium entries.
create-pc takes the player picks in a choices map keyed level → advancement-id → data, and the ids are pack-specific. The plan gives you the picks (names); you bind them to the ids:
create-pc with partial choices and readthe `needsChoices[]` dry-run — it persists nothing) to learn each advancement's id, type, and legal options.
proficiencies.skills / expertise / languages / tools / weapons (chosen) → Trait{ chosen: [keys] } (skill keys like skills:ath; the plan already gives 3-letter skill codes).
options.fightingStyle / draconic ancestry → ItemChoice { selected: [uuid] } (match theoption label to the right uuid).
subclass (at its level 3) → Subclass { uuid } (the canonicalized premiumsubclass).
plan.abilities — pass them straight to create-pc.abilities;do NOT re-add background/ASI increases (the engine skips that advancement on purpose).
Granted (non-chosen) proficiencies come from advancement automatically — you only key the chosen ones. Use the needsChoices[] dry-run to confirm the map is complete before building.
create-pc)Call `create-pc` with name, className (the primary = plan.classes[0]), level (its level), multiclass: [{ className, levels }] for every other class, species, background, abilities (= plan.abilities), the choices map, and spells: { cantrips, prepared } (= plan.spells.cantrips / .prepared, canonicalized names). On success, @scale resolves natively. If needsChoices[] comes back, fill the gaps and re-call — nothing was created.
create-pc adds no gear/feats. After the PC exists (reuse [[pc-builder]] Step 6, defer item judgment to [[physical-item-builder]]):
plan.inventory[]line, search-compendium-items → `import-item` (actorIdentifier = the new PC, equipped and quantity from the line). Normalize "<item>, +N" to the magic-variant entry and DDB's "Item, Sub" naming to the premium entry (e.g. "Traveler's Clothes" → Clothes, Traveler's; "Bullseye Lantern" → Lantern, Bullseye). Any item with no premium match goes through Step 4 (author from plan.inventory[].description + flag) — never dropped.
update-actor with plan.currency.plan.feats[], `add-feature` modecompendium-features with the canonicalized name. Skip the noise DDB lists as "feats": the background's origin feat (e.g. Magic Initiate (Cleric) from Acolyte) is auto-granted by create-pc's background advancement — don't re-add it; a "… Ability Score Improvements" entry is the background/ASI increase, already baked into the final abilities — don't add it either. A named feat that isn't in the premium books goes through the Step-4 STOP-and-ASK gate, never invented.
plan.art.avatarUrl, upload-asset it into theworld (e.g. worlds/<world>/assets/portraits/<name>.jpeg), then set-actor-art at that Data-relative path — it sets portrait + token. Only when the character has no DDB avatar, fall back to the deterministic class-pregen default ([[pc-builder]] Step 7, primary class).
update-actor biography from the DDB free-text the parser captured. Renderplan.bio.entries as labeled blocks — each a **<label>:** <text> line (HTML <p><strong>label:</strong> text</p>) — so the player's Organizations / Backstory / Ideals / Bonds / Flaws / Faith / Alignment / Physical land in the Foundry bio verbatim (e.g. Organizations: A member of the league of shadows.). DDB fields don't all map 1:1 to Foundry fields — this is where the rest go.
set-actor-ownership — assign the player as owner (or leave GM if the GM owns thetable's PCs).
move-documentsif the table organizes differently).
get-actor to confirm HP/AC/abilities/skills/saves, class/species/background, spell slots, and inventory; confirm @scale resolved (a real die, not @scale.…). Then report the build AND its known lossy points — don't fake fidelity:
create-pc imports all spells as prepared from the PHB only.An exact known/prepared split, prepared-count limits, or a non-PHB spell won't round-trip (out-of-book names surface in create-pc warnings — fix or ask, don't invent).
DDB sheet's current HP / spent hit dice / death saves / inspiration.
trust plan for the transcription, and spend your judgment on names, the ask-gate, and the build.
the JSON — never ask for or accept a D&D Beyond session token.
parse-ddb-character is already wiredinto the registry but won't be callable until the server reloads.
create-pc reports unresolvedScale, surface it; don't patch itthe way the NPC builder does.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.