Skip to main content
Freesona’s cog-based architecture makes it straightforward to add new commands. All logic lives in focused modules under cogs/, shared utilities live in utils/, and a single registry file (utils/modules.py) controls what’s available at runtime. Add a file, register it, and enable it from Discord — no restart required.

How Modules Work

  • Each feature lives in cogs/<category>/<name>.py as a discord.py Cog class.
  • Core modules (help, ping, status, admin) always load on startup and cannot be toggled.
  • Optional modules are registered in OPTIONAL_MODULES in utils/modules.py and can be toggled via /module enable and /module disable at runtime.
  • Enabled/disabled state persists in config.json under enabled_modules, so your configuration survives restarts.
  • Slash commands are automatically re-synced when you enable or disable a module.

Creating a New Module

1

Create the cog file

Create cogs/<category>/<name>.py. At minimum, define a Cog subclass and a setup coroutine:
cogs/tools/mycommand.py
import discord
from discord.ext import commands

class MyCommand(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.command()
    async def mycommand(self, ctx, *, text: str):
        await ctx.send(f"You said: {text}")

async def setup(bot):
    await bot.add_cog(MyCommand(bot))
Place it under an existing category (ai, media, moderation, system, tools, fun) or create a new subfolder.
2

Register it in utils/modules.py

Add an entry to OPTIONAL_MODULES using a short lowercase key and the full dotted module path:
utils/modules.py
OPTIONAL_MODULES = {
    # ... existing entries ...
    "mycommand": "cogs.tools.mycommand",
}
Once registered, the key appears in /module list and supports autocomplete.
3

Enable it from Discord

Run the enable command in your server:
/module enable mycommand
The bot loads the cog immediately and re-syncs slash commands.
4

Verify with /module list

Run /module list — your new module should appear as enabled with its load status confirmed.

Accessing Bot Utilities

Import from utils/ for functionality shared across cogs:
ModuleWhat it provides
utils/generation.pysafe_generate — the main AI generation call with security checks, persona injection, and memory injection
utils/memory.pyget_user_facts_prompt, extract_and_store_fact — read and write per-user long-term facts
utils/persona.pyassemble_persona — build the active system instruction from the current persona fields
utils/config.pyload_config, save_config — read and write config.json at runtime
utils/security.pydetect_injection, sanitize_prompt, is_public_http_url — input validation and URL guards
utils/rss.pyload_rss_feeds, parse_feed, save_rss_feed — RSS/Atom feed management
Keep cogs focused — each file should handle one feature area. Cogs should not import directly from each other. If one cog needs functionality from another (like mvsep.py uses ytdlp.py), access it via bot.get_cog("CogClassName") at call time rather than a top-level import.

Adding Slash Command Support

discord.py supports exposing a single command as both a prefix and a slash command via @commands.hybrid_command. Use this by default — one decorator, one callback:
import discord
from discord.ext import commands

class MyCommand(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.hybrid_command(name="mycommand", description="Does a thing")
    async def mycommand(self, ctx: commands.Context, *, text: str):
        await ctx.send(f"You said: {text}")
Only fall back to defining separate @app_commands.command and @commands.command callbacks when the slash version needs interaction-only features that @commands.command can’t provide — for example, modals (interaction.response.send_modal(...)), which require a raw discord.Interaction. After adding new slash commands, run /sync (Bot Owner) to register them globally with Discord. The /module enable and /module disable commands also trigger an automatic re-sync.
Run python scripts/check_project.py before deploying to verify syntax and config. On Windows PowerShell you can use .\\scripts\\check.ps1 instead. Both run the same checks and exit non-zero on any failure.