Skip to content

Scaffolder & Kits

WhiskerPlatform splits “the tooling a developer touches” into four packages with deliberately different lifecycles:

Package Binary Lives in the consumer’s project as Purpose
@nike/whisker-cli thundercats Global install Front-of-platform CLI — scaffolding, AI context, audits, updates
@nike/create-whisker create-whisker Nothing (one-time generator); also imported by @nike/whisker-cli Writes the project skeleton
@nike/whisker-module-kit whisker devDependency Per-module CLI for front-end modules
@nike/whisker-service-kit whisker-service dependency (runtime) + devDependency (CLI) Runtime helpers + CLI for backend services

Keeping them separate matters: the front-of-platform CLI can iterate on agent context without churning the kits, the scaffolder can change prompts without forcing every consumer to upgrade, and the kits can iterate without dragging scaffold-time concerns along for the ride.

@nike/whisker-cli — front-of-platform CLI (Thundercats)

Section titled “@nike/whisker-cli — front-of-platform CLI (Thundercats)”

The published package is @nike/whisker-cli; the binary is thundercats (there is no tc alias yet — see Planned).

Command What it does
thundercats create module|service [target] Wraps @nike/create-whisker’s scaffolder for that kind.
thundercats create monorepo [target] Same, but for a pnpm-workspace.yaml root with one or more packages/* — module/service/lambda team-identity, compliance, and QMA prompts are collected once at the root and inherited by every package added in the same interactive session (only genuinely per-package fields, like a service’s AWS account numbers, prompt again).
thundercats add module|service|lambda [target] Adds a new package to an existing monorepo (delegates to create-whisker’s runAddPackage).
thundercats add iframe-consumer Wraps @nike/create-whisker’s iframe-conversion flow on an existing app. This one still chains thundercats init afterward — it’s a retrofit onto a pre-existing repo, not a fresh scaffold.
thundercats init First-time .cursor/ scaffold for a repo create-whisker didn’t create (e.g. add iframe-consumer’s retrofit target, or a repo bootstrapped some other way) — writes the always-on core rule and the sidecar manifest tracking managed-file hashes. The @thundercats orchestrator skill and its specialist references install separately, once per machine, via thundercats install-skill.
thundercats audit Read-only diagnostic. Exit 0 clean, 1 drift, 2 blocker.
thundercats update Re-renders managed .cursor/ files (--rules / --agents surgical surfaces; --dry-run previews). For module-kind projects, also delegates to the project’s own locally installed @nike/whisker-module-kit (pnpm exec whisker upgrade, --write unless --dry-run) — see Module Kit → whisker upgrade below. Does not self-update the CLI itself first (see Planned).
thundercats list-agent-skills Enumerates installed skills.
thundercats upgrade Installs a newer global @nike/whisker-cli + @nike/create-whisker from Artifactory — the CLI’s own self-update path (unrelated to, and not to be confused with, the per-project whisker upgrade shipped by the module kit).

thundercats add-skill and thundercats diff exist as command surfaces but exit with a deferral notice — see Planned.

A team scaffolding a new module today runs one command and ends up with a working project and a populated .cursor/:

Terminal window
thundercats create module my-module
# scaffold runs, writing AGENTS.md + .cursor/rules/ itself → done
cd my-module
# open in any IDE → @thundercats in chat → the orchestrator routes to specialists

@nike/create-whisker writes the agent-context files (AGENTS.md, .cursor/rules/thundercats-core.mdc, the sidecar) directly as the last step of every scaffold — module, service, lambda, and each package in a monorepo — rather than thundercats create chaining a separate init call afterward. That means a team scaffolding with bare create-whisker (no @nike/whisker-cli installed at all) still gets agent context for free; thundercats init is reserved for onboarding a repo create-whisker didn’t create.

The old pnpm create whisker entry point still works for back-compat; it shells into the same library code.

Both a CLI (pnpm create whisker / npx @nike/create-whisker) and a library — runScaffolder() and runAddIframeConsumer() are public exports consumed by @nike/whisker-cli. Interactive, single-shot; re-running against an existing target directory refuses to overwrite.

What it writes (module flow):

Step File(s) Purpose
1 templates/module/** Base TypeScript + Vite + React app
2 src/moduleManifest.ts + module-federation.config.ts The module’s declared platform surface and federation contract
3 package.json (rewritten) Pinned kit + WCL versions
4 config/whisker.config.json Dev orchestration: ports, BFF URLs, host contract pin, auto-clone toggle
5 .husky/pre-commit + post-commit Wire git hooks to whisker pre-commit / post-commit
6 Jenkinsfile + config/quality-config.yaml Branch-aware CI pipeline + Nike QMA thresholds

config/module-manifest.json is not generated at scaffold time — it’s regenerated by whisker dev and CI from src/moduleManifest.ts, so it’s never stale and never a hand-edit target.

add-iframe-consumer converts an existing app in place: detects the package manager from the lockfile, writes src/moduleManifest.(ts|js) and config/whisker.config.json (moduleType: "iframe-consumer"), patches package.json#scripts.dev to shell into whisker dev, and scans for *_ORIGINS arrays to patch idempotently. Prints a manual-step checklist for anything it can’t safely auto-fix.

@nike/whisker-module-kit — the front-end CLI

Section titled “@nike/whisker-module-kit — the front-end CLI”

A per-module devDependency exposing the whisker CLI plus the canonical ModuleManifestSchema (Zod) and mergeManifestForUpsert. The Lambda publisher imports the same schema and merge function — the kit and the runtime ingest path can never disagree.

Command Pipeline
whisker dev generate manifest → validate → whisker:prep on host → build module (VITE_IS_LOCAL=true) → start preview → rebuild host loader → build + start host → vite --watch + browser-sync proxying the host
whisker dev:prod Same, against the prod BFF and prod registry
whisker generate-manifest Executes src/moduleManifest.ts with Node 24’s native TS runtime and writes config/module-manifest.json
whisker validate-manifest Runs ModuleManifestSchema.parse(); fails CI on schema violation
whisker doctor Pre-flight checks — see Automation, devkit & E2E
whisker pre-commit / post-commit Husky hooks — regenerate + validate on commit, update provenance after
whisker update-check Fetches the host’s published contract and reports drift (read-only)
whisker upgrade The kit’s own maintenance command — run after bumping @nike/whisker-module-kit. Dry-run by default; --write applies mechanical migrations (deprecated whisker.config.json field cleanup, hand-listed federation exposes entries collapsed into a resolveFederationExposes() spread) and bumps moduleKitVersion as a receipt. thundercats update shells out to this exact command for module projects.

For iframe consumers, whisker dev runs a simpler pipeline: regenerate manifest → whisker:prep on host → start the child app → start whisker-host on 3001 with VITE_LOCAL_IFRAME_APPS pointing at the local child. No browser-sync, no remote build.

ModuleManifestSchema is namespaced (identity / integration / access / deployment / ui / capabilities) — see Manifest schema reference for the full shape.

@nike/whisker-service-kit — the back-end runtime

Section titled “@nike/whisker-service-kit — the back-end runtime”

A peer of the module kit for Node services. The runtime is feature-complete:

Surface What it removes from the consumer
startService(opts?) Express bootstrap, file-based route discovery, helmet, CORS, error handling, request logging
createAuthMiddleware Okta issuer allow-list, CORPORATE vs STORE token routing, typed req.principal
fetchFromService<T>(url, opts) OSCAR S2S calling with scope validation, retry, timeout, trace propagation
Guards requireGroup, requireMinStoreRole, requireUserType, requireAny, requireAll
getLogger() ECS-shaped winston with active trace/span IDs
whisker-service CLI setup, dev, doctor, build

Not published to Artifactory yet — deliberately excluded from the first pilot release (.changeset/config.json#ignore) because the pilot’s dependency graph doesn’t reach it. thundercats create service scaffolds a working template today, but until the package publishes, a freshly scaffolded service can’t pnpm install outside this workspace.

  • tc short binary alias — decided but not shipped; package.json#bin only exposes thundercats today.
  • Self-update by default on thundercats update — the original design had update self-update the CLI before doing anything else. Today only the separate thundercats upgrade command self-updates; update doesn’t call it first.
  • thundercats add-skill / thundercats diff — Phase 2D. External-skill fetching (Tier 1/2/3), the centralized ~/.thundercats/cache/, audience + quality gates, and promoted-rule drift all land together. See AI-first tooling.
  • Multi-repo scope (--all / --repos / --root) — flags exist and fall back to single-repo with a notice; fan-out ships with Phase 2D.
  • @nike/whisker-service-kit Artifactory publish — ships in the immediate follow-up release once the pilot publish path is validated.
flowchart LR
  tc["@nike/whisker-cli<br/>(thundercats)"] -->|imports lib| cw["@nike/create-whisker"]
  tc -->|init/update .cursor/, retrofit + maintenance| repo["consumer repo"]
  tc -->|"'update' command delegates"| upgrade["whisker upgrade<br/>(module-kit codemods)"]
  cw -->|writes skeleton + .cursor/ in one pass| repo
  repo -->|devDependency| mk["@nike/whisker-module-kit"]
  repo -.dependency.-> sk["@nike/whisker-service-kit"]
  mk -->|ships| upgrade
  mk -->|exports| schema["ModuleManifestSchema (Zod)"]
  mk -->|exports| merge["mergeManifestForUpsert()"]
  schema -->|imported by| lambda["whisker-manifest-publisher Lambda"]
  merge -->|imported by| lambda

The schema being shipped from the kit and imported by the Lambda is the single most important coupling in the platform — a manifest that passes whisker validate-manifest locally is guaranteed to pass Lambda validation, because there is exactly one definition.