@bernierllc/slash-commands-core
v1.0.2
Published
Core foundation for slash command packages - provides interfaces, types, and utilities for building slash commands
Readme
@bernierllc/slash-commands-core
Core foundation package for building slash command packages. Provides types, utilities, and base classes following the validator pattern for MECE slash command architecture.
Installation
npm install @bernierllc/slash-commands-coreFeatures
- Type Safety - Complete TypeScript definitions for slash commands
- Base Classes - Abstract base class for consistent command packages
- Validation - Built-in argument parsing and validation with Zod
- Multi-platform - Support for in-app, Slack, Discord, Teams platforms
- Rate Limiting - Built-in rate limiting utilities
- Permission System - Role-based command permissions
- NeverHub Integration - Service discovery and event system support
Quick Start
Creating a Slash Command Package
import { SlashCommandPackage, SlashCommandSpec, SlashCommandContext, SlashCommandResponse, z } from '@bernierllc/slash-commands-core';
export class MyCommandsPackage extends SlashCommandPackage {
readonly packageName = '@my-org/my-commands';
readonly version = '1.0.0';
readonly description = 'My custom slash commands';
readonly commands: Record<string, SlashCommandSpec> = {
'hello': {
name: 'hello',
description: 'Say hello to someone',
usage: '<name>',
examples: ['/hello John', '/hello @user'],
arguments: [{
name: 'name',
description: 'Name to greet',
required: true,
type: 'string',
schema: z.string().min(1).max(50)
}],
requiresAuth: false,
platforms: ['in-app', 'slack', 'discord']
}
};
async initialize(): Promise<void> {
// Register command handlers
this.handlers.set('hello', this.handleHello.bind(this));
}
private async handleHello(
context: SlashCommandContext,
args: Record<string, any>
): Promise<SlashCommandResponse> {
const name = args.name as string;
return {
success: true,
message: `Hello, ${name}! 👋`,
formatting: {
markdown: `**Hello, ${name}!** 👋`,
plainText: `Hello, ${name}!`
}
};
}
}Using with NeverHub Service Discovery
import { NeverHubAdapter } from '@bernierllc/neverhub-adapter';
export class NeverHubCommandPackage extends SlashCommandPackage {
private neverhub?: NeverHubAdapter;
async initialize(): Promise<void> {
// Initialize command handlers first
await super.initialize();
// Setup NeverHub integration
if (await NeverHubAdapter.detect()) {
this.neverhub = new NeverHubAdapter();
await this.neverhub.register({
type: 'slash-commands-tasks',
name: this.packageName,
version: this.version,
capabilities: [{
type: 'slash-commands',
name: 'task-management',
commands: Object.keys(this.commands),
version: this.version
}]
});
// Listen for command execution events
await this.neverhub.subscribe('slash-command.execute', async (event) => {
if (this.commands[event.data.command]) {
const response = await this.execute(event.data);
await this.neverhub!.publishEvent({
type: 'slash-command.response',
data: {
commandId: event.data.commandId,
response,
userId: event.data.userId
}
});
}
});
}
}
}Core Types
SlashCommandContext
interface SlashCommandContext {
commandId: string; // Unique execution ID
command: string; // Command name (without /)
args: string[]; // Raw arguments
userId: string; // User who executed
platform: string; // Platform (in-app, slack, etc.)
conversationId?: string; // Channel/conversation ID
metadata?: Record<string, any>; // Platform-specific data
timestamp: Date; // Execution timestamp
}SlashCommandResponse
interface SlashCommandResponse {
success: boolean; // Execution success
message?: string; // User-facing message
data?: any; // Structured response data
error?: string; // Error message if failed
formatting?: { // Platform-specific formatting
markdown?: string;
plainText?: string;
platform?: Record<string, any>;
};
ephemeral?: boolean; // Private response flag
}SlashCommandSpec
interface SlashCommandSpec {
name: string; // Command name
description: string; // Human-readable description
usage?: string; // Usage syntax
examples?: string[]; // Usage examples
aliases?: string[]; // Alternative names
arguments?: SlashCommandArgument[]; // Expected arguments
requiresAuth?: boolean; // Authentication required
minRole?: string; // Minimum role required
adminOnly?: boolean; // Admin-only command
platforms?: string[]; // Supported platforms
category?: string; // Help category
enabled?: boolean; // Default enabled state
}Utilities
Command Parsing
import { parseCommandString, createCommandContext } from '@bernierllc/slash-commands-core';
const { command, args } = parseCommandString('/hello world');
// command: "hello", args: ["world"]
const context = createCommandContext('/hello world', 'user123', 'in-app');Response Helpers
import { createSuccessResponse, createErrorResponse } from '@bernierllc/slash-commands-core';
// Success response
const success = createSuccessResponse('Task created successfully', { id: '123' });
// Error response
const error = createErrorResponse('Task not found', true);Validation
import { validateCommandArgs, isValidCommandName, z } from '@bernierllc/slash-commands-core';
// Validate command name
const valid = isValidCommandName('my-command'); // true
const invalid = isValidCommandName('My Command'); // false
// Validate arguments with Zod
const schema = z.object({
title: z.string().min(1),
priority: z.enum(['low', 'medium', 'high'])
});Rate Limiting
import { CommandRateLimiter } from '@bernierllc/slash-commands-core';
const limiter = new CommandRateLimiter(10, 60000); // 10 requests per minute
if (!limiter.isAllowed(userId)) {
const remaining = limiter.getRemainingRequests(userId);
const resetTime = limiter.getTimeUntilReset(userId);
// Handle rate limit exceeded
}Platform Formatting
import { formatResponseForPlatform, escapeForPlatform } from '@bernierllc/slash-commands-core';
const response = {
success: true,
message: 'Default message',
formatting: {
markdown: '**Bold** message',
plainText: 'Bold message'
}
};
const slackFormatted = formatResponseForPlatform(response, 'slack');
const escaped = escapeForPlatform('Special *chars*', 'discord');Architecture Pattern
The slash-commands-core follows the validator pattern used throughout BernierLLC packages:
- Core Package (
@bernierllc/slash-commands-core) - Provides foundation types and utilities - Specific Packages (
@bernierllc/slash-commands-tasks, etc.) - Implement specific commands - Suite Integration - In-app-chat suite orchestrates command routing via NeverHub
MECE Principles
- Mutually Exclusive - Each command package handles distinct functionality
- Collectively Exhaustive - All command needs covered by the ecosystem
- Service Discovery - Commands auto-register when packages come online
- Graceful Degradation - Works without optional command packages
Integration with In-App Chat
Commands automatically integrate with @bernierllc/in-app-chat when both packages are available:
- Command packages register with NeverHub
- In-app-chat discovers available commands
- Users can execute commands across platforms (in-app, Slack, Discord, Teams)
- Responses are formatted appropriately for each platform
Development Workflow
1. Create Command Package
mkdir my-commands-package
cd my-commands-package
npm init
npm install @bernierllc/slash-commands-core2. Implement Commands
Extend SlashCommandPackage and implement your commands following the examples above.
3. Test Locally
const package = new MyCommandsPackage();
await package.initialize();
const context = createCommandContext('/hello world', 'user123', 'in-app');
const response = await package.execute(context);4. Publish and Register
Once published, the package will automatically be discovered by in-app-chat suite when both are running.
See Also
- @bernierllc/in-app-chat - Main chat suite that orchestrates commands
- @bernierllc/neverhub-adapter - Service discovery and events
- @bernierllc/slash-commands-tasks - Example task management commands
License
Copyright (c) 2025 Bernier LLC. All rights reserved.
