Onboards an external, consuming repo onto the @idhub identity resolution stack (identity-core, identity-adapter-nextjs, identity-adapter-angular, identity-provider-segment, identity-provider-launchdarkly, identity-provider-fullstory) without ever touching this monorepo's own packages. Detects the target's framework from real config-file evidence (next.config.*, angular.json, or neither → framework-agnostic core-only path), asks — never infers — which vendor providers to wire via one multiSelect AskUserQuestion call with no preselection and an explicit none-yet option, installs with the target's OWN package manager (pnpm/npm/yarn matched to its existing lockfile, never introducing a second one), and asks the cookie domain scoping question (host-only default vs. an explicit shared-subdomain value) rather than guessing either narrow (silently fails to share identity across subdomains) or broad (leaks the anonymous id to sibling subdomains). Scaffolds framework wiring write-if-absent from this skill's own self-contained references (never a repo-relative path back into this monorepo, per skill-channel-rollout's reference-normalization invariant), writes only vendor env var PLACEHOLDER names to .env.example-style files (never a real key, never touching a live .env/.env.local), and surfaces — but never auto-edits — the target's actual login/logout call sites where identify()/reset() must be placed by a human, since auth flow is high-blast-radius and a multi-step login can have more than one plausible 'now logged in' point.
identity-onboarding
Overview
Onboards another repo (a consuming app, not this monorepo) onto the
@idhub identity resolution stack: one canonical anonymous id generated
upstream of every vendor SDK, and a single identify() call that fans out to
every registered provider. This skill is the rollout side — it never changes
anything in the @idhub packages themselves, only in the target repo.
target repo path
│
▼
1. framework detection — next.config.*(js/ts/mjs)? angular.json? neither?
│ ambiguous or unrecognized → ask, never guess
▼
2. AskUserQuestion — which vendor providers (segment / launchdarkly /
fullstory / none yet), multiSelect, no preselection
▼
3. AskUserQuestion — cookie domain scoping (host-only default, or a shared
subdomain value via "Other")
▼
4. install with the TARGET's own package manager (pnpm-lock.yaml → pnpm add,
package-lock.json → npm install, yarn.lock → yarn add) — never introduce a
second lockfile
▼
5. scaffold wiring, write-if-absent, from this skill's own references/
(never a repo-relative path back into this monorepo)
▼
6. write vendor env var PLACEHOLDERS only (.env.example or equivalent) —
never a real key
▼
7. report: files written/skipped, env vars the human must fill in, and the
exact login/logout call sites found — do NOT auto-edit those
▼
STOP
The invariants
Create one todo per invariant. Each is independently verifiable.
-
Framework detection is explicit file evidence, never an inferred guess. Check for
next.config.js/.ts/.mjs(→ Next.js adapter,references/nextjs-wiring.md) orangular.json(→ Angular adapter,references/angular-wiring.md). Neither present → framework-agnostic path,references/vanilla-wiring.md(rawidentity-coreonly, no adapter package). Both present (a monorepo with more than one app) → stop and ask which app directory is the actual target rather than picking one. Verify: a plain Express/vanilla-JS repo getsidentity-coreonly, never an adapter package it can't use. -
Provider selection is asked, never inferred. Which vendors (Segment, LaunchDarkly, FullStory, or none yet) a team has actually licensed cannot be derived from the target repo's code — a fresh app may want zero, one, two, or all three. One
AskUserQuestioncall,multiSelect: true, no option preselected, an explicit "none yet" choice included. Never install all three providers "just in case" or skip the ask because the repo already has one vendor's raw SDK installed (that SDK may predate this rollout and be slated for removal, not reuse). Verify: running this skill against a repo with zero identity-related dependencies still produces exactly the providers the user selected — not more, not fewer. -
Installation uses the target repo's own package manager — this skill never introduces a second lockfile. Detect from
pnpm-lock.yaml/package-lock.json/yarn.lock(or apackageManagerfield) and install with the matching command (pnpm add/npm install/yarn add). A target with no lockfile at all is a stop-and-ask case (same posture asskill-channel-rolloutinvariant 1) — never silently pick one on the target's behalf. Verify: onboarding into an npm-managed repo never creates apnpm-lock.yamlalongside the existingpackage-lock.json. -
identify()/reset()call sites are surfaced, never auto-wired. Grep the target for likely login/logout handlers (signIn,login,signOut,logout, auth callback routes). Show the exact file:line and the snippet to add from the chosen wiring reference — but a human places it. Auth flow is high-blast-radius: a multi-step login (OAuth redirect, MFA step-up, impersonation) can have more than one plausible "the user is now logged in" point, and firingidentify()at the wrong one either double-fires or fires before the realuserIdis known. The same applies toreset()at logout — firing it before session teardown completes can race the provider SDKs' own cleanup. Verify: after a run, the target's actual login/logout handler files are byte-identical to before — only wiring/config files from step 5 changed. -
Vendor credentials are never invented, guessed, or hardcoded — only placeholder env var names are written. Each provider's real config shape is already fixed by its package API: Segment needs a
writeKey(+ a caller-suppliedanalytics/analytics-nodeclient instance, which this skill does not create), LaunchDarkly needs aclientSideId, FullStory needs anorgId. Write the placeholder names (seereferences/provider-env-vars.md) into.env.example(or the target's equivalent) with an empty or obviously-fake value — never a value that looks like it could be real, and never write to a real.envfile that might already hold live secrets. Verify:git grepfor the literal placeholder strings inreferences/provider-env-vars.mdfinds only.env.example-style files this skill wrote, never a.env/.env.localfile. -
Cookie domain scoping is asked, never guessed. Host-only (the
identity-coredefault — unsetdomain) is correct for a single-domain app; a shared value (.example.com-style) is required only when the app spans subdomains that must share the same anonymous id. Guessing wrong in either direction is a real defect, not a style choice: too narrow silently fails to share identity across subdomains that need it; too broad leaks the anonymous id to sibling subdomains that should never see it. Ask viaAskUserQuestionwith an explicit "single domain, no sharing needed" option plus an "Other" free-text path for the shared-domain value. Verify: the scaffolded client/module config'scookie.domainis either absent or exactly the value the user typed — never a placeholder guess like.example.comleft in verbatim. -
Scaffolding is write-if-absent — an existing wiring file is reported as a conflict, never overwritten.
middleware.ts,app/providers.tsx,app.module.ts(or equivalent) may already exist with unrelated logic. Check existence before every write from step 5; if present, print the snippet that needs to be merged in by hand and move on rather than clobbering the file. Matches the same posture asskill-channel-rolloutinvariant 4. Verify: running this skill twice against the same target produces identical output on the second run — no file is silently duplicated or rewritten.
Red flags
- Assuming a framework from
package.jsondependencies (e.g.nextlisted as a devDependency for tooling only) instead of the real config-file evidence → invariant 1. - Installing all three vendor providers by default, or because "the repo will probably want them eventually" → invariant 2; ask, don't guess.
- Running
pnpm addin a repo whose own lockfile ispackage-lock.json→ invariant 3. - Editing a
signIn/onLoginhandler directly to insert theidentify()call → invariant 4; surface it, let a human place it. - Writing anything into
.envor.env.local(as opposed to.env.example) → invariant 5, and a real risk of committing a path that later leaks a live key. - Defaulting
cookie.domainto.example.comor leaving it unset without asking → invariant 6. - Overwriting an existing
middleware.ts/app.module.ts/providers.tsxfound in the target repo → invariant 7.
Related, not covered
Publishing new @idhub package versions, or changes inside this
monorepo's own packages/* is out of scope — this skill only touches the
target (consuming) repo. See this repo's own README.md and each package's
README.md for the packages' own API and release flow.
Choosing which vendor SDKs a team should adopt (Segment vs. a competitor, LaunchDarkly vs. a competitor) is a product/business decision outside this skill's scope — this skill only wires up the providers already decided on in step 2.
Angular Universal's companion Express middleware for SSR apps is covered
in references/angular-wiring.md step 4 (mirroring the adapter's own
quick-start caveat) — it is a real, separate wiring step for SSR Angular
targets, not an edge case to skip.
References
references/nextjs-wiring.md— App Router middleware + provider componentuseIdentity()snippet.
references/angular-wiring.md—IdentityModule.forRoot()+ injectedIdentityServicesnippet, plus the Angular Universal SSR middleware caveat.references/vanilla-wiring.md— directidentity-coreusage with no adapter package, for targets that are neither Next.js nor Angular.references/provider-env-vars.md— the exact placeholder env var name per vendor provider, and which one requires a caller-supplied SDK client instance rather than just a key.