add-league
A specialized tool for adding new OSRS league season data to the project codebase.
Install
mkdir -p .claude/skills/add-league && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/10803" && unzip -o skill.zip -d .claude/skills/add-league && rm skill.zipInstalls to .claude/skills/add-league
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Add a new OSRS league season to the Discord bot. Use when: adding a league, creating a new league, new league season, new league type. Creates model, migrations, updates League type, config, and commands.Key capabilities
- →Scaffold database models
- →Create migrations
- →Update league mappings
- →Register commands
How it works
Scaffolds database models, migrations, and configuration mappings for new OSRS league seasons.
Inputs & outputs
When to use add-league
- →Add a new league season
- →Create league database model
- →Update league commands
About this skill
Add New OSRS League
Add a new league season to the bot. This involves creating a database model, migrations, and updating all league mappings across the codebase.
When to Use
- A new OSRS Leagues season is announced and needs to be added
- User asks to "add a new league" or "create a league"
Prerequisites
Before starting, ask the user for:
- League key — snake_case identifier (e.g.,
demonic_pacts) - Display name — Human-readable name (e.g.,
Demonic Pacts) - PascalCase name — For model/table (e.g.,
DemonicPactsLeague) - Point thresholds — Rank point thresholds for bronze through dragon, or whether to reuse an existing league's thresholds
- Set as current league? — Whether to update
CURRENT_LEAGUE - Command type —
leagueNameBronze(league not yet started, sets everyone to bronze) orleagueNameLocal(league active, looks up hiscores)
Procedure
All steps reference the project root. Use the existing raging_echoes / RagingEchoesLeague as the pattern to follow.
Step 1: Create the League Model
Create src/database/models/League/{PascalName}League.ts:
import { CreationOptional, DataTypes, Sequelize } from 'sequelize';
import { InitializableModel } from '../types';
class {PascalName}League extends InitializableModel<{PascalName}League> {
declare name: string;
declare points: number;
declare readonly createdAt: CreationOptional<Date>;
declare readonly updatedAt: CreationOptional<Date>;
static initialize = (sequelize: Sequelize) => {
{PascalName}League.init(
{
name: {
type: DataTypes.STRING,
allowNull: false,
primaryKey: true,
unique: true,
},
points: {
type: DataTypes.INTEGER,
allowNull: false,
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
},
{
tableName: '{PascalName}League',
sequelize,
},
);
};
// eslint-disable-next-line @typescript-eslint/no-empty-function
static initializeAssociations() {}
}
export default {PascalName}League;
Step 2: Create Migrations
Migration filenames use {timestamp}-{description}.ts format. Use the current epoch in milliseconds.
2a. Create league table — src/database/migrations/{timestamp}-create-{kebab-name}-league.ts:
import { DataTypes, QueryInterface } from 'sequelize';
import { {PascalName}League } from '../models';
module.exports = {
async up(queryInterface: QueryInterface) {
await queryInterface.createTable<{PascalName}League>('{PascalName}League', {
name: {
type: DataTypes.STRING,
allowNull: false,
primaryKey: true,
unique: true,
},
points: {
type: DataTypes.INTEGER,
allowNull: false,
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
});
},
async down(queryInterface: QueryInterface) {
await queryInterface.dropTable('{PascalName}League');
},
};
2b. Add DiscordUser column — src/database/migrations/{timestamp+1}-add-discord-user-{kebab-name}.ts:
import { DataTypes, QueryInterface } from 'sequelize';
module.exports = {
async up(queryInterface: QueryInterface) {
await queryInterface.addColumn('DiscordUser', '{snake_name}_name', {
type: DataTypes.STRING,
allowNull: true,
});
},
async down(queryInterface: QueryInterface) {
await queryInterface.removeColumn('DiscordUser', '{snake_name}_name');
},
};
Step 3: Register Model in Index
In src/database/models/index.ts:
- Add import:
import {PascalName}League from './League/{PascalName}League'; - Add to the
modelsarray - Add to both the named
export { }block
Step 4: Update DiscordUser Model
In src/database/models/DiscordUser.ts:
- Add property:
declare {snake_name}_name?: CreationOptional<string>; - Add column in the
init()schema:{snake_name}_name: { type: DataTypes.STRING, allowNull: true, },
Step 5: Update League Definitions
In src/leagues.ts:
- Add
{PascalName}Leagueto the import from'./database/models' - Add
'{snake_name}'to theLeagueunion type - Add entry to
LeagueRankingswith point thresholds - Add entry to
LeagueNames:{snake_name}: '{Display Name}' - Add entry to
LeagueDiscordColumn:{snake_name}: '{snake_name}_name' - Add
case '{snake_name}':togetLeagueAttributes()switch →{PascalName}League.findByPk(username) - Add
case '{snake_name}':toinsertLeagueName()switch →{PascalName}League.upsert(...) - If setting as current: change
CURRENT_LEAGUEto'{snake_name}'
Step 6: Update Config
In src/config.ts, add to config.ranks:
{snake_name}: {
bronze: process.env.{UPPER_SNAKE}_BRONZE,
iron: process.env.{UPPER_SNAKE}_IRON,
steel: process.env.{UPPER_SNAKE}_STEEL,
mithril: process.env.{UPPER_SNAKE}_MITHRIL,
adamant: process.env.{UPPER_SNAKE}_ADAMANT,
rune: process.env.{UPPER_SNAKE}_RUNE,
dragon: process.env.{UPPER_SNAKE}_DRAGON,
},
Step 7: Register Command
In src/discord/interactions/commands/index.ts:
- If league has NOT started yet: use
leagueNameBronze('{snake_name}')and importleagueNameBronzefrom'./leagueNameBronze' - If league IS active: use
leagueNameLocal('{snake_name}') - Add the call to the
commandDataarray
Step 8: Update Remove League Roles Command
In src/discord/interactions/commands/remove_league_roles.ts:
- Add
{snake_name}_name: nullto theresult.update()call
Verification
After all changes:
- Run
npx tsc --noEmit— confirms no type errors (theLeagueunion type enforces exhaustiveness on all mapping objects) - Run
npx eslinton all changed files - Run
yarn testif possible (pretest recreates SQLite DB with new migrations)
Files Changed Summary
| File | Action |
|---|---|
src/database/models/League/{PascalName}League.ts | NEW |
src/database/migrations/*-create-{kebab}-league.ts | NEW |
src/database/migrations/*-add-discord-user-{kebab}.ts | NEW |
src/database/models/DiscordUser.ts | Add property + schema column |
src/database/models/index.ts | Import + export model |
src/leagues.ts | Type, rankings, names, column map, switch cases |
src/config.ts | Rank role env vars |
src/discord/interactions/commands/index.ts | Register command |
src/discord/interactions/commands/remove_league_roles.ts | Add null reset |
Notes
- No changes are needed to tasks, actions, messages, or scheduled jobs — they all work dynamically off
CURRENT_LEAGUEand theLeaguetype - The
Leagueunion type provides compile-time exhaustiveness checking: if any mapping object (LeagueRankings,LeagueNames,LeagueDiscordColumn) is missing the new league key, TypeScript will report an error - Environment variables for Discord role IDs (
{UPPER_SNAKE}_BRONZEetc.) must be configured in.envand deployment environments separately
When not to use it
- →Modifying existing league seasons
Prerequisites
Limitations
- →Requires manual environment variable configuration
How it compares
Automates the multi-file update process required for adding a new league.
Compared to similar skills
add-league side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| add-league (this skill) | 0 | 4mo | Review | Intermediate |
| convex-functions | 4 | 6mo | No flags | Intermediate |
| nanoclaw-backend-ts | 0 | 3mo | No flags | Advanced |
| dev-supabase | 0 | 2mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
convex-functions
waynesutton
Writing queries, mutations, actions, and HTTP actions with proper argument validation, error handling, internal functions, and runtime considerations
nanoclaw-backend-ts
binidx
Use when editing NanoClaw backend TypeScript under src. Covers Express routes, conversation flow, database persistence, runtime state, providers, scheduler, and agent execution.
dev-supabase
aibot88
Backend development with Supabase. Trigger when the user wants to configure auth, the database, or Supabase storage.
bullmq-specialist
davila7
BullMQ expert for Redis-backed job queues, background processing, and reliable async execution in Node.js/TypeScript applications. Use when: bullmq, bull queue, redis queue, background job, job queue.
prisma-expert
davila7
Prisma ORM expert for schema design, migrations, query optimization, relations modeling, and database operations. Use PROACTIVELY for Prisma schema issues, migration problems, query performance, relation design, or database connection issues.
agent-dev-backend-api
ruvnet
Agent skill for dev-backend-api - invoke with $agent-dev-backend-api