add-role
Skill50 starsChanged 21 days agoApache-2.0View on GitHub
Adds a user roles system (member, editor, moderator...) to the project. Creates the mechanics (hasRole/getRoles helpers, roles[] column on the users table, admin management page). Prerequisite: /add-auth in users mode. If admin auth is missing, the skill sets it up beforehand automatically to protect the administration page.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- flavien-ia/hypervibe-harnessword for word, first seen Oct 23, 2026
Other versions1 project uses a version of this.
Word for word · 1
- flavien-ia/hypervibe-harness★ 50100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository89 other files it gives its agents.
- accessibilitySkill
- add-2faSkill
- add-agent-dashboardSkill
- add-aiSkill
- add-analyticsSkill
- add-authSkill
- add-automationSkill
- add-backup-dbSkill
- add-collabSkill
- add-cronSkill
- add-dark-modeSkill
- add-dbSkill
- add-domainSkill
- add-emailSkill
- add-github-authSkill
- add-google-authSkill
- add-i18nSkill
- _add-keyringSkill
- add-mapSkill
- add-notification-centerSkill
- add-push-notificationSkill
- add-pwaSkill
- add-routineSkill
- add-storageSkill
- add-stripeSkill
- add-testSkill
- bootstrapSkill
- _check-depsSkill
- cleanSkill
- _collect-secretSkill
- _convert-to-turborepoSkill
- _create-agentSkill
- _create-cloudflare-workerSkill
- _create-contact-pageSkill
- _create-render-workerSkill
- _create-routineSkill
- _create-workflowSkill
- delete-projectSkill
- _detect-project-rootSkill
- _dns-brevoSkill
- _dns-cloudflareSkill
- _dns-gandiSkill
- _dns-godaddy-manualSkill
- _dns-hostingerSkill
- _dns-infomaniak-manualSkill
- _dns-ionos-manualSkill
- _dns-lwsSkill
- _dns-namecheapSkill
- _dns-ovhSkill
- _dns-porkbunSkill
- _dns-resendSkill
- _dns-squarespace-manualSkill
- eco-auditSkill
- _ensure-aiSkill
- _ensure-vaultSkill
- _generate-secretSkill
- geoSkill
- _get-secretSkill
- gscSkill
- _hash-passwordSkill
- The first 60 shown.
The file
Show its rules and text
We split this skill into the rules it gives an agent
31 rules. About 0 are common, 11 are distinctive, and 20 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈11
Distinctive · estimate
This author's own choices. Worth a look.
20
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 12
Use plain, non-technical business language.
- Distinctive: line 13
When generating user-facing content for the scaffolded project (UI labels, emails, copy), write it in the user's language too.
- Very specific: line 22
Read the marker in src/lib/roles.ts :
- Very specific: line 31
No file → existing_roles = [] (fresh install)
- Distinctive: line 36
No menu. Skip to Step 1.
- Very specific: line 68
Users auth required: : read src/server/auth.ts and look for the // hypervibe:auth-modes marker.
- Distinctive: line 71
Admin auth required for the administration page: : read the same marker.
- Very specific: line 73
If the user explicitly declined admin auth (e.g.
- Distinctive: line 96
If customizing, ask for the list.
- Very specific: line 98
Keep the original label (with capitals, accents) for ROLE_LABELS, which is used by the UI
- Very specific: line 101
If the user proposes admin, administrator, root, superuser, superadmin, refuse firmly :
- Very specific: line 109
Default suggestion : the first role in the list (the most restricted).
- Very specific: line 113
Run a small check : psql via Drizzle or a throwaway tsx script to count the rows in user :
- Very specific: line 120
If 0 → skip Q3, backfillRole = defaultRole.
- Very specific: line 178
Read ❌ Failed at: <step> in the handoff banner.
- Distinctive: line 181
Do not re-run blindly : resolve the cause first.
- Very specific: line 205
If the handoff banner contains a Sidebar at <path> doesn't link to /admin/users or No admin sidebar file found warning, open the project's …
- Very specific: line 231
No script : the risk of corrupting roles.ts + schema.ts + the references in the code is too high.
- Very specific: line 235
Ask for the name of the new role.
- Distinctive: line 236
Ask for the display label.
- Very specific: line 237
Patch
src/server/db/schema.ts: : add the value to userRoleEnum. - Very specific: line 243
Patch
src/lib/roles.ts: : add the role to ROLES (tuple as const) and to ROLE_LABELS. - Very specific: line 249
Ask which role to remove. Prevent removal of defaultRole (unless the user picks a new one right after).
- Distinctive: line 251
UPDATE the users: : remove the role from their array, add the replacement role if necessary.
- Very specific: line 267
Keep the value in the enum but remove it from ROLES on the code side.
- Very specific: line 270
Patch
src/lib/roles.ts: : remove from ROLES and ROLE_LABELS. - Distinctive: line 276
Ask old name → new name.
- Distinctive: line 284
Ask for the new default (from the current list).
- Very specific: line 287
Patch
src/server/db/schema.ts: : if the roles column has a .default(sql\'{<old>}'::user_role[]\), change it. - Very specific: line 297
Idempotence: : if the user re-runs /add-role after a success, the marker exists → UPGRADE menu.
- Distinctive: line 299
Non-technical audience: : all recaps, questions, and error messages are in plain language, without unexplained jargon.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Add Role : Orchestrator
Communication
- Detect the user's language from the conversation (the user's own messages, anywhere in the session - not just this invocation: a bare slash command like
/bootstrapcarries no language signal by itself). If nothing in the conversation gives a signal, fall back to the OS locale (node -e "console.log(Intl.DateTimeFormat().resolvedOptions().locale)") before defaulting to English. ALWAYS reply in that language for every user-facing message: questions, progress, confirmations, summaries, errors - including any example text quoted in this skill, which is illustrative and must be translated, never sent verbatim. - Use plain, non-technical business language. Never expose internal script names (*.mjs) or jargon; describe actions in human terms.
- When generating user-facing content for the scaffolded project (UI labels, emails, copy), write it in the user's language too.
- Show progress as a short natural-language checklist (in-progress and done states).
Lightweight orchestrator for the user roles system. Detects the current state via the // hypervibe:roles <list> marker at the top of src/lib/roles.ts, offers a context-aware menu based on what is already installed, and delegates to the setup-role.mjs script for fresh installs. For UPGRADES (adding / removing / renaming a role), Claude drives the contextual editing of schema.ts + roles.ts itself.
Step 0 : Detect the current state and context-aware menu
Read the marker in src/lib/roles.ts :
if [ -f "<WEB_DIR>/src/lib/roles.ts" ]; then
grep -E "^// hypervibe:roles" "<WEB_DIR>/src/lib/roles.ts" | head -1
fiParse :
- No file →
existing_roles = [](fresh install) // hypervibe:roles member, editor, moderator→["member", "editor", "moderator"]
Case [] → fresh install
No menu. Skip to Step 1.
Non-empty case → menu
🎭 Your user roles are already in place
Current roles :
member, editor, moderator(example)
- Add a new role
- Remove a role (with reassignment of the users who have it)
- Rename a role
- Change the default role for new sign-ups
- Regenerate the administration page
/admin/users- Start over from scratch (destructive)
- Something else
| Choice | Action |
| 1 | UPGRADE section : Case A (adding a role). |
| 2 | UPGRADE section : Case B (removal + reassignment). |
| 3 | UPGRADE section : Case C (renaming). |
| 4 | UPGRADE section : Case D (changing the default). |
| 5 | Retrieve the current list from roles.ts, rewrite the page from the role/pages/users-page.tsx and role/pages/users-table.tsx templates (the page calls requireAdmin() first; src/server/require-admin.ts from auth/admin/require-admin.ts if the project lacks it). |
| 6 | List the files / migrations to remove manually (roles.ts, admin-users router, admin page, roles column in the schema, pgEnum type) then ask to re-run. Refuse to do it automatically. |
| 7 | Ask for clarification. |
Step 1 : Prerequisites (fresh install only)
- Detect project root : invoke
_detect-project-root→WEB_DIR,IS_MONOREPO,PROJECT_NAME,IS_NEXTJS. Abort if not Next.js. - DB required : invoke
_check-deps db. Ifdb_ok = false, suggest/add-dbthen re-check. - Users auth required : read
src/server/auth.tsand look for the// hypervibe:auth-modesmarker.- Absent or does not contain
users→ suggest/add-auth(users mode) to the user. Once it has run, come back to/add-role.
- Absent or does not contain
- Admin auth required for the administration page : read the same marker.
- If
adminis absent : tell the user that you are going to add the admin login first to protect the management page (DO NOT ask for confirmation, this is the normal flow). Run/add-authin admin mode (orchestration). Once done, automatically come back to/add-rolewithout asking anything. - If the user explicitly declined admin auth (e.g. "no, I'll manage without an admin page") : pass
createAdminPage = falseto the script.
- If
Step 2 : Discovery (3 questions)
Q1 : Which roles do you want to set up ?
Offer a default suggestion tailored to the project by re-reading the CLAUDE.md and/or the cahier-des-charges.md if present :
- Content / blog / editorial project →
lecteur, contributeur, moderateur - Marketplace →
acheteur, vendeur, moderateur - Paid SaaS (Stripe detected) →
free, pro, enterprise - Support / helpdesk →
client, agent, superviseur - Community / forum →
membre, contributeur, moderateur - Generic default →
membre, editeur, moderateur
Present the suggestion via askUserQuestion :
Which roles do you want for your users ?
- Accept the suggestion :
membre, editeur, moderateur- Customize (you give your list in free-form language)
If customizing, ask for the list. Normalize :
- Lowercase + kebab-case ASCII for storage (
éditeur→editeur) - Keep the original label (with capitals, accents) for
ROLE_LABELS, which is used by the UI
Safeguards (mandatory, explicit refusal) :
- If the user proposes
admin,administrator,root,superuser,superadmin, refuse firmly :
The name
adminis reserved for your app's global admin login (the one that manages the whole site, configured by /add-auth). For a DB role with equivalent power, usemoderateur,manager, orsuperviseur. Which name do you prefer for this role ?
- The
setup-role.mjsscript re-validates on the code side and also refuses. Do not bypass it.
Q2 : What is the default role when someone signs up ?
Default suggestion : the first role in the list (the most restricted). Present via askUserQuestion or free conversation.
Q3 : Migration of existing users (skip if DB empty)
Run a small check : psql via Drizzle or a throwaway tsx script to count the rows in user :
cd <WEB_DIR>
npx tsx -e "import { db } from '~/server/db'; import { users } from '~/server/db/schema'; const c = await db.select().from(users); console.log(c.length);"If 0 → skip Q3, backfillRole = defaultRole.
Otherwise, ask :
I detected N users already signed up. Which role do you want to assign to them ?
- The default role (
<defaultRole>)- Another role from the list
- Decide case by case (rare, manual via the admin page after install)
Step 3 : Run the script
Build the config file and run setup-role.mjs :
cd <WEB_DIR>
CONFIG="${TMPDIR:-/tmp}/add-role-config-$(date +%s).json"
cat > "$CONFIG" <<'EOF'
{
"webDir": "<WEB_DIR>",
"roles": ["membre", "editeur", "moderateur"],
"roleLabels": {
"membre": "Membre",
"editeur": "Éditeur",
"moderateur": "Modérateur"
},
"defaultRole": "membre",
"backfillRole": "membre",
"createAdminPage": true
}
EOF
node "${CLAUDE_SKILL_DIR}/../../scripts/setup-role.mjs" --config "$CONFIG"
rm -f "$CONFIG"Tell the user :
↳ I'm creating the roles system : enum in the database, roles[] column on the user table, helpers, admin page if you asked for it.
The script prints its own ▸ <step> as it goes ; relay it in natural language without quoting the internal names. Examples :
▸ Patching src/server/db/schema.ts→↳ I'm adding the new "role" type to the database.▸ Pushing schema with drizzle-kit→↳ I'm applying the change to your database.▸ Backfilling existing users with role "membre"→↳ I'm assigning the "membre" role to your existing users.▸ Writing src/lib/roles.ts→↳ I'm placing the role-checking helpers in the project.▸ Patching src/server/auth.ts (expose roles in JWT + session)→↳ I'm wiring the roles into the login system, so they are available everywhere in your app.▸ Writing src/app/admin/(protected)/users/page.tsx→↳ I'm creating the user management page in your admin (it checks that it is you before showing anything).▸ Running pnpm tsc --noEmit→↳ I'm checking that everything compiles correctly.
At the end, the script prints a JSON (last line of stdout). Parse it to confirm success :
{"success":true,"roles":["membre","editeur","moderateur"],"defaultRole":"membre","backfillRole":"membre","adminPage":true}If the script fails
- Read
❌ Failed at: <step>in the handoff banner. - Map it to the script's function (1:1, same name).
- Diagnose based on the error (see common cases below).
- Do not re-run blindly : resolve the cause first.
Common cases :
preflightfailed → oftensrc/lib/roles.tsalready exists (re-config) or a missing prerequisite clearly reported in the message.patchSchemafailed → the schema has an unexpected shape. Inspect and patch by hand.pushSchemafailed with "Schema patched on disk but NOT pushed" → the users table (or another) holds data thatschema.tsdoes not declare, and the push would have deleted it (list printed just above). Show it to the user in plain words; declare those columns inschema.tsor get an explicit go, thencd <WEB_DIR> && npx drizzle-kit push(never--force).pushSchemafailed otherwise → DB unreachable ordrizzle-kitcrashes. CheckDATABASE_URL, re-run.patchAuthTsfailed → the project's auth.ts was manually modified and no longer matches the pattern. Patch the callbacks by hand, taking inspiration from the script'sreplace()calls.
Step 4 : Post-script communication
CLAUDE.md update
Invoke _update-claude-md with two entries in the conventions section :
- User roles : <list>. Default for new sign-ups : <default>. Helpers `hasRole(session, [...])` and `getRoles(session)` in `~/lib/roles`. The `admin` role is RESERVED for admin credentials (env var, never in the DB) : never add it to the enum.
- To gate a tRPC procedure by role : use `protectedProcedure` then check `hasRole(ctx.session, [...])` at the start, otherwise `throw new TRPCError({ code: "UNAUTHORIZED" })`. To gate a server-component page : check `hasRole(await auth(), [...])` on its first lines, before reading any data, otherwise `redirect("/")`. A check in a shared `layout.tsx` is not enough : the layout's redirect carries the page's content.Patch admin sidebar (if the script warned about it)
If the handoff banner contains a Sidebar at <path> doesn't link to /admin/users or No admin sidebar file found warning, open the project's sidebar file (look for admin-sidebar, sidebar, or the nav bar in the admin/(protected) layout) and manually add a link :
<Link href="/admin/users">Utilisateurs</Link>with a consistent style (look at the other sidebar links to copy the Tailwind pattern).
Final recap to the user
Show a clear, jargon-free recap :
🎉 Roles system set up.
Available roles :
<list>. New sign-ups get the<default>role by default. Your existing users were set to<backfillRole>.To manage roles : log in as admin and go to
/admin/users. There you'll find the list of all your accounts with a dropdown to change their role. Tick "Multi-role mode" if a user needs to have several.To protect a page by role : just tell me "protect page X for moderators only" and I'll take care of it.
To add / remove / rename a role later : re-run
/add-roleand choose from the menu.
UPGRADE section : Modifying existing roles (Claude drives, no script)
No script : the risk of corrupting roles.ts + schema.ts + the references in the code is too high. Claude reads the existing setup, applies the changes by hand, and checks pnpm tsc --noEmit at the end.
Case A : Add a new role
- Ask for the name of the new role. Validate (kebab-case ASCII, no
admin/synonyms). - Ask for the display label.
- Patch
src/server/db/schema.ts: add the value touserRoleEnum. Postgres : adding a value to an existing enum requiresALTER TYPE user_role ADD VALUE 'nouveau';. Run via tsx :import { sql } from "drizzle-orm"; import { db } from "~/server/db"; await db.execute(sql\`ALTER TYPE user_role ADD VALUE IF NOT EXISTS 'nouveau'\`); - Patch
src/lib/roles.ts: add the role toROLES(tuple as const) and toROLE_LABELS. Update the// hypervibe:roles ...marker. - CLAUDE.md : invoke
_update-claude-mdto update theUser roles :line with the new list. pnpm tsc --noEmit+ recap to the user.
Case B : Remove a role (destructive, forced reassignment)
- Ask which role to remove. Prevent removal of
defaultRole(unless the user picks a new one right after). - Count the users who have this role. Ask which role to reassign them to (suggest the default role).
- UPDATE the users : remove the role from their array, add the replacement role if necessary. Via tsx :
await db.execute(sql\` UPDATE "user" SET roles = array_remove(roles, 'supprime') WHERE 'supprime' = ANY(roles); \`); // If reassignment is necessary (users who had ONLY this role) : await db.execute(sql\` UPDATE "user" SET roles = array_append(roles, 'replacement') WHERE cardinality(roles) = 0; \`); Postgres limitation : a Postgres enum cannot drop a value without recreating the whole enum. Two options :
- Recreate the enum (complex, downtime on this column).
- Keep the value in the enum but remove it from
ROLESon the code side. The Postgres value remains but no code selects it.
Recommendation : option 2 (keep the orphaned Postgres value). Document it in CLAUDE.md.
- Patch
src/lib/roles.ts: remove fromROLESandROLE_LABELS. Update the marker. - Patch all references to the removed role in the code (
hasRole(_, ["supprime"])etc.). Grep and fix. pnpm tsc --noEmit+ recap.
Case C : Rename a role
- Ask old name → new name.
- Postgres :
ALTER TYPE user_role RENAME VALUE 'ancien' TO 'nouveau';. Via tsx. - Patch
src/lib/roles.ts: tuple + labels + marker. - Patch all references in the code :
hasRole(_, ["ancien"]), etc. Grep and fix. - CLAUDE.md + tsc check + recap.
Case D : Change the default role for new sign-ups
- Ask for the new default (from the current list).
- Patch
src/lib/roles.ts:DEFAULT_ROLE. - Patch
src/server/api/routers/auth.ts: the signup procedure usesroles: ["<old>"]. Replace it withroles: ["<new>"]. - Patch
src/server/db/schema.ts: if therolescolumn has a.default(sql\'{<old>}'::user_role[]`), change it. Optional :pnpm db:push` so the Postgres default is up to date too. - CLAUDE.md + tsc check + recap.
Important rules
- i18n convention : the
/admin/userspage (and all admin pages generated by this skill) stays in French regardless of the project's locale. No string extraction intomessages/, no use ofuseTranslations(). Rationale : 1-user surface (the admin/owner), high string volume, near-zero translation ROI. Same convention asmentions-legales,cgv, and other admin pages of the plugin. If the user insists on translating their admin, do it manually without touching the template. - You do it, you don't have it done. Everything goes through the script or your own edits. The user never types a command.
- The anti-
adminsafeguard is non-negotiable. Never acceptadminin the roles list, even if the user insists. Explain why and offer an alternative. - Idempotence : if the user re-runs
/add-roleafter a success, the marker exists → UPGRADE menu. If the marker is partial/broken (unknowncase), ask how to proceed. pnpm tsc --noEmitat the end of any modification (script or manual upgrade). If it fails, diagnose and fix before finalizing.- Non-technical audience : all recaps, questions, and error messages are in plain language, without unexplained jargon.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

