From xyo-skills
Bootstrap a new XL1 application — a single React dApp, a single xl1-service backend, a plain Node.js service/CLI, or a full-stack pnpm monorepo with React + xl1-service + a shared TypeScript library. Activates when the user wants to create, start, bootstrap, initialize, or scaffold a new XL1 project. Do NOT activate for work in an existing project.
How this skill is triggered — by the user, by Claude, or both
Slash command
/xyo-skills:xl1-scaffoldThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill **only** when the user is starting a **new** XL1 project. For work in an existing repo — adding features, fixing bugs, or answering questions about XL1 concepts — use the appropriate lower-layer skill instead ([xl1-knowledge](../xl1-knowledge/SKILL.md) for the chain, [xl1-patterns](../xl1-patterns/SKILL.md) for design patterns).
scripts/scaffold/README.mdscripts/scaffold/presets/base.jsscripts/scaffold/presets/index.jsscripts/scaffold/presets/node.jsscripts/scaffold/presets/react.jsscripts/scaffold/presets/xl1-monorepo.jsscripts/scaffold/presets/xl1-service.jsscripts/scaffold/presets/xl1-shared.jsscripts/scaffold/registry.jsscripts/scaffold/scaffold-xl1-dapp.jsscripts/scaffold/templates/node/_env.examplescripts/scaffold/templates/node/eslint.config.mjsscripts/scaffold/templates/node/src/index.tsscripts/scaffold/templates/node/vitest.config.tsscripts/scaffold/templates/react/_env.examplescripts/scaffold/templates/react/eslint.config.mjsscripts/scaffold/templates/react/index.htmlscripts/scaffold/templates/react/src/App.tsxscripts/scaffold/templates/react/src/main.tsxscripts/scaffold/templates/react/src/vite-env.d.tsUse this skill only when the user is starting a new XL1 project. For work in an existing repo — adding features, fixing bugs, or answering questions about XL1 concepts — use the appropriate lower-layer skill instead (xl1-knowledge for the chain, xl1-patterns for design patterns).
Skill identity. This skill's version is exposed in this file's frontmatter under metadata.version. When reporting which skills informed your work, format as <skill-name> v<version> (e.g. xl1-scaffold v1.1.19). When multiple skills from this plugin are active, each may be listed.
Activate when the user says anything like:
Do not activate when:
package.json depending on @xyo-network/*If ambiguous, ask one question: "Are you starting a new project, or adding to an existing one?"
If the user wants a new project but the request is vague or exploratory — they haven't named the archetype, the patterns, or the multi-party shape — defer to xl1-build first. That skill runs a short planning wizard and hands a refined prompt back here. Concrete prompts (the archetype is named, the patterns are implied, the network is specified) skip the wizard and come straight to this skill.
Before invoking the scaffold, scan the prompt and the working directory for conventions that change how you build and verify the dApp. These cues are easy to miss because they look like incidental context, but each one prescribes specific behavior.
.env file with a seed phrase already in the empty repo. The user expects the dApp to be headless-verifiable end-to-end by an agent or CI, using the same identity a browser user would have on that seed. Build the dApp normally — browser wallet wiring intact, React providers, the works — but also write a Node verification script that exercises the happy path through GatewayBuilder.build(signer) and run it before reporting completion. The seed phrase is for verification only; it does not change the UX surface of the dApp. Read Headless dApp Verification before planning — it documents the prompt shape, signer derivation, and anti-patterns. The DoD's Headless Verification section becomes mandatory in this case.DefaultNetworks entry the scaffold's gateway points at and which INDEXER_FLOOR_BLOCK is captured.Treat any of these cues as load-bearing. Surface them in your acknowledgement before scaffolding so the user can correct any misread.
If the prompt itself is missing the synthesized spec from xl1-build — for example, a fresh conversation where the wizard ran in a prior session — check the cwd for a PRD.md file before asking the user to repeat themselves. The PRD is written by xl1-build at the end of its Phase 4 and contains the same fields as the inline synthesized prompt (Shape, Network, Patterns, Multi-party, History/browse, Backend, Headless verification, Notes) under structured markdown headings, plus an ## Acceptance criteria section that carries Layer 3 of the completion gate. Read it the same way you would read the inline prompt.
If both the chat prompt and PRD.md are absent, fall back to asking the user — or, if the request is exploratory enough, defer to xl1-build to generate a PRD first.
When the user gives a concrete spec inline (skipping the wizard) and no PRD.md exists at the cwd, write one before scaffolding. This closes the gap so every project has a Layer 3 completion gate regardless of how it was kicked off.
The shape mirrors what xl1-build Phase 4 produces — same sections, same headings. The data sources differ:
xl1-patterns/* for the matched patterns, dapp-checklist.md for anti-patterns to convert into negative criteria).xl1-scaffold's metadata.version from this file's frontmatter (since the wizard didn't run, attribute to the scaffold).Write the file at the cwd as PRD.md. If the user's prompt is too vague to derive acceptance criteria — for example, they said "build me something on XL1" — do not guess; defer to xl1-build to run the wizard properly. Generating weak criteria from a vague prompt is worse than no criteria at all because it makes the loop appear to terminate when the spec was never concrete.
Surface the written PRD in your acknowledgement so the user can correct any misread before scaffolding starts:
Wrote
PRD.mdcapturing the spec and acceptance criteria. Review it briefly — anything off, tell me before I scaffold.
The scaffold runtime ships with this plugin under scripts/scaffold/ (synced from packages/xl1-scaffold/ at build time). Invoke it directly with Node — ${CLAUDE_SKILL_DIR} resolves to this skill's directory at runtime.
This skill scaffolds either a single project or a full-stack monorepo, based on whether the dApp needs ongoing chain tracking.
| Template | When | Used standalone? |
|---|---|---|
react (default) | User-facing dApp that reads current chain state and submits transactions on user action. Most browser-facing apps. | ✓ also a monorepo member at packages/app |
xl1-service | Node + Express HTTP service backed by @xyo-network/xl1-sdk. The backend for any dApp needing chain-watching, indexing, or scheduled jobs. | ✓ also a monorepo member at packages/service |
node | Plain Node CLI/script — rare from this skill; usually a backend that needs HTTP routes prefers xl1-service. | ✓ standalone only |
xl1-monorepo | pnpm workspace root for a full-stack dApp. No source of its own — just pnpm-workspace.yaml, root scripts, and a README. | monorepo flow only (step 1 below) |
xl1-shared | TypeScript library for environment-neutral code (types, Zod schemas, constants) shared between app and service. Compiles to dist/ so workspace consumers import compiled JS via workspace:*. | monorepo flow only (member at packages/shared) |
Default to the monorepo flow (xl1-monorepo + xl1-shared + react + xl1-service). Drop to a single template only when the prompt is clearly read-only / browser-only (e.g. "wallet balance dashboard", "show recent transactions"). When in doubt, scaffold the monorepo — it's strictly a superset; an unused service is cheap to delete later, but retrofitting a service into a single-template scaffold is annoying.
Scaffold a monorepo when the dApp needs logic that runs independently of any user's browser session. Triggers:
The monorepo gives the React app and the xl1-service a place to share code via the xl1-shared library — typically API request/response types, Zod schemas, and constants used on both sides.
Examples mapped to decisions:
| Prompt fragment | Decision |
|---|---|
| "two-player rock-paper-scissors with commit-reveal", "browse past games" | monorepo — service watches reveals, indexes history; shared holds Move/Game types |
| "prediction market with settlement deadline" | monorepo — settlement runs independently; shared holds Market/Outcome types |
| "wallet balance dashboard" | react only |
| "one-click contract interaction" | react only |
| "show recent transactions for an address" (read-on-view) | react only |
| "real-time leaderboard updated as blocks arrive" | monorepo |
The xl1-patterns skill catalogues patterns that imply a backend — commit-reveal, chain data indexing, prediction markets all do.
Default to the monorepo flow. Don't ask the user — just scaffold it. The monorepo is a superset of the single-template options; unused parts (e.g. service if the app turns out to be read-only) are easy to delete later. Retrofitting a backend into a standalone React scaffold is much harder.
Only drop to a single template (react) when the prompt is unambiguously read-only — wallet balance dashboards, one-shot contract interactions, simple display dApps. If you find yourself even slightly unsure, choose the monorepo.
The clarifying question pattern ("does any logic need to run independently of a browser session?") is discouraged — it adds round-trip latency and the answer is almost always "yes, eventually." Skip it; default to monorepo and let the user prune if needed.
./rps-game"), use that as the root../rps-game from "build me a rock-paper-scissors game").For a monorepo, scaffold sub-packages under packages/:
<root>/
├── package.json, pnpm-workspace.yaml, README.md, .gitignore ← from xl1-monorepo
└── packages/
├── app/ ← react template
├── service/ ← xl1-service template
└── shared/ ← xl1-shared template (types/schemas used by app + service)
The monorepo templates wire app ↔ service the same way every time: app on :3000, service on :3001, all service routes under /api/*, Vite dev-proxies /api/* to the service so the browser stays same-origin, and the workspace root's pnpm dev runs both concurrently. Do not add CORS middleware to the service — there is nothing to CORS for in this topology, and adding it "just in case" is the anti-pattern that the prescription exists to prevent. The full pattern (including the prod reverse-proxy story and the cross-origin escape hatch) is documented in Browser ↔ Service Wiring. When implementing the dApp, fetch with relative paths (fetch('/api/foo')) — no VITE_API_URL, no hardcoded host.
For a single template:
node "${CLAUDE_SKILL_DIR}/scripts/scaffold/scaffold-xl1-dapp.js" <root> --template=<react|node|xl1-service>
For a monorepo, four scaffold invocations + a single root install:
SKILL="${CLAUDE_SKILL_DIR}/scripts/scaffold/scaffold-xl1-dapp.js"
# 1. Workspace root (no install — verification skipped, root has no installable code yet).
# The root's package.json `name` field is the dir's basename (e.g. `rps-game`); sub-
# package scopes auto-derive from this (sub-packages become `@rps-game/<basename>`).
node "$SKILL" <root> --template=xl1-monorepo --no-install
# 2. Sub-packages — each runs in workspace-member mode (skips its own install + verification;
# drops its own packageManager + .gitignore; package name becomes @<scope>/<basename>).
# Order matters: scaffold shared FIRST. When app and service are scaffolded after,
# they auto-detect the sibling shared package and wire a "@<scope>/shared": "workspace:*"
# dep into their package.json automatically.
node "$SKILL" <root>/packages/shared --template=xl1-shared --workspace-member
node "$SKILL" <root>/packages/app --template=react --workspace-member
node "$SKILL" <root>/packages/service --template=xl1-service --workspace-member
# 3. Single install at the workspace root — links all members + installs deps for all.
cd <root> && pnpm install
# 4. Verify across the workspace.
pnpm -r run typecheck && pnpm -r run lint && pnpm -r run build
# For xl1-service specifically, also smoke-test the server boot:
pnpm --filter @<scope>/service run smoke
The scaffold (per single-template invocation, when NOT in workspace-member mode):
package.json, tsconfig.json, eslint.config.mjs, vite.config.ts (React), source files, and .gitignore.pnpm install, then pnpm typecheck, pnpm lint, pnpm build, and — for the Node and xl1-service templates — a smoke test that executes the compiled output.In --workspace-member mode, steps 1–2 still run (with the workspace-aware tweaks); step 3 is skipped (the root install handles all linking + verification).
If any step fails, the scaffold exits non-zero. Relay the failing output to the user verbatim before attempting a fix.
xl1-shared is intentionally a thin library — its sample src/index.ts is just export {} plus a comment block. Fill it in as you discover what app and service need to agree on.
Belongs in shared/:
interface SubmitCommitRequest, interface GameStateResponse)enum Move { Rock, Paper, Scissors })type GameId = Brand<string, 'GameId'>)Does NOT belong in shared/:
packages/app/src/fs, child_process) → packages/service/src/@xyo-network/xl1-react-client-sdk — browser-only, lives in app@xyo-network/xl1-sdk — typically lives in service (or both if app needs read-only chain queries; either way, don't re-export it through shared)Adding cross-package imports:
When you scaffold app and service AFTER shared (the recommended order), the scaffold auto-wires "@<scope>/shared": "workspace:*" into their package.json dependencies. No manual edit needed for the initial scaffold.
If you scaffold a workspace member out of order or add one later, edit its package.json manually to add:
{
"dependencies": {
"@<scope>/shared": "workspace:*"
}
}
Then pnpm install at the workspace root re-links. Imports work via the package's name field:
import type { SubmitCommitRequest, GameStateResponse } from '@<scope>/shared'
When you add new exports to shared/, run pnpm --filter @<scope>/shared run build (or pnpm -r run build from root) so consumers see the updated dist/.
After the scaffold reports success:
Briefly summarize what was created (template(s), target dir(s), one-line "ready to develop"). For monorepos, mention the workspace root + each sub-package and remind the user that pnpm install at the root links everything.
Capture the indexer floor block — see the next section. Part of dApp creation, not deferred to first deploy.
Proactively continue implementing whatever the user originally described. Do not stop and wait. For monorepos, the natural ordering is:
packages/shared/src/ based on what the feature needs and export them for consumers.packages/service/src/ (chain-watching, indexing, deadline handling).packages/app/src/ consuming the service routes via fetch.
Consult xl1-patterns for the canonical recipe (commit-reveal, indexing, etc.).Only pause if you need a clarifying decision that can't be inferred from the original request (e.g. "should players be able to rematch without re-committing stakes?").
Before reporting the work complete, walk the full three-layer completion gate (see xy-development/workflow.md § Applying the Definition of Done):
PRD.md acceptance criteria (in the cwd, if present): project-specific positive and negative assertionsThis is mandatory, not optional, and it is an agent-facing completion gate — it gates whether you stop and report done, independent of any later deploy or release. If any item across any layer fails, keep iterating until it passes — do not stop and report partial completion. Fix the failing item, re-walk the relevant layer, and only stop when every applicable layer is fully green. The Layer 2 checklist enumerates exactly the rules and anti-patterns the rest of the skill stack documents, and an unwalked DoD is the most common cause of completed dApps that violate them. In your completion summary, call out each relevant section across all three layers with explicit pass/fail and a one-line note on why. Sections marked "(if applicable)" can be skipped only when truly out of scope (e.g. no commit-reveal in a read-only dashboard); state that they were skipped and why. If the prompt included a seed-phrase .env (see "Interpreting the prompt"), the Headless Verification section is mandatory — you must have run the verification script and report its outcome, not just check the boxes. The completion summary must distinguish "chain interactions verified" from "service-derived state verified" as separate facts. If the dApp exposes derived state through a service (indexer REST API, GraphQL, etc.), the verify run must have round-tripped through that service surface — never via direct viewer.block.payloadsByHash synthesis — and gated on both viewer.finalization.headNumber() and the indexer's lastIndexedBlock watermark. Reporting "verified" when only the chain edge was exercised, or rationalizing an empty indexer as "Sequence is slow" without checking the watermark, is the failure mode the service round-trip exists to prevent.
If the dApp introduces its own payload schemas (the default — any custom dApp under a fresh com.<your-org>.<app>.* namespace; see Schema Naming), capture a sensible floor block as part of dApp creation and record it in .env as INDEXER_FLOOR_BLOCK. The agent does this; the user never sees the step. This is part of the dApp Definition of Done — see dApp Checklist.
The reason: the chain accepts arbitrary bytes for any schema, including before the dApp existed. An indexer that walks from block 0 will (a) waste hours on blocks that provably contain none of the dApp's data and (b) honor pre-deployment matches that cannot be the dApp's data. Capturing a floor makes correctness and performance the default. See Chain Data Indexing — Floor Block for the full framing.
Anchor the capture to development time, not first publish. Publish steps are routinely deferred, automated, or skipped, and an "after first publish" rule frequently misses. Capturing during dApp creation is unambiguous and runs every time. Precision isn't the goal — performance optimization is. A few blocks of slack on either side don't matter.
The procedure:
Number(await viewer.finalization.headNumber())..env (root for single-template scaffolds; the relevant sub-package's .env for monorepos):
INDEXER_FLOOR_BLOCK=<n>
VITE_INDEXER_FLOOR_BLOCK=<n> # only if there's a Vite-built browser package
INDEXER_FLOOR_BLOCK from the indexer (process.env) and from the browser dApp (import.meta.env.VITE_INDEXER_FLOOR_BLOCK) so all readers share the same floor.INDEXER_FLOOR_BLOCK is per chain. A dApp scaffolded for mainnet, sequence, and a local devnet has three different .env files with three different captured values. Don't reuse a floor across environments.
For an unbounded dApp — one whose purpose is to index pre-existing schemas (an XL1 transfer indexer, an inscription-substrate indexer, an XRC-20 ledger for an existing token) — set INDEXER_FLOOR_BLOCK=0 explicitly. The env var is required either way; there is no silent default. State the unbounded choice in the hand-off summary so it's auditable.
Mixed dApps (some bounded schemas, some pre-existing) still set INDEXER_FLOOR_BLOCK to the captured head; per-schema floors live in the indexer code per Chain Data Indexing — Mixed indexers. Mixing is the last resort — splitting into separate bounded and unbounded indexer processes is strictly faster.
| Flag | Values | Default | Notes |
|---|---|---|---|
--template, -t | react, node, xl1-service, xl1-monorepo, xl1-shared | react | xl1-service extends node with an Express HTTP server bound to process.env.PORT || 3000 and a Hello world route. xl1-monorepo and xl1-shared are used together with the other templates in the monorepo flow — see "Choosing template(s)" above. |
--target | path | positional[0] or src | Can also pass as first positional arg |
--force | — | off | Overwrite files in a non-empty target dir |
--no-install | — | off | Write files only; skip pnpm install and verification |
--workspace-member | — | off | Sub-package mode: rename to @<scope>/<basename>, drop packageManager + .gitignore, skip the install/verification chain. The workspace root's pnpm install handles all members. |
--workspace-scope | string | auto-derived | Override the scope used in --workspace-member mode. Default: walk up to find pnpm-workspace.yaml, read the root package.json name, use it as the scope (rps-game → @rps-game; @org/foo → @org). |
Fetch errors during version resolution (ENOTFOUND, ETIMEDOUT) — transient npm registry issues. Retry once. If persistent, check the user's network.
pnpm ERR_PNPM_MISSING_TIME — the scaffold pins pnpm to the latest 10.x via packageManager + corepack specifically to avoid this; it should not appear in normal runs.
"Target dir is not empty" — pass --force to overwrite in place, or pick a different target.
npx claudepluginhub xyoraclenetwork/xyo-claude-plugin --plugin xyo-skillsCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.