
Pairing
- 14 installs
- 610 repo stars
- Updated June 26, 2026
- alsk1992/cloddsbot
Pairing (in cloddsbot) is a skill that pairs and authenticates bot users, assigns trust levels, and controls channel access.
About
This skill handles user pairing, authentication, and trust management for a bot across channels like Telegram. A developer uses it to issue and approve pairing codes, assign owner or paired trust levels, and gate access so only paired users can trade. It includes code expiry, rate limiting, auto-approve rules for localhost and Tailscale, and an audit trail.
- Pairs new users, manages trust levels, and controls access across channels
- Time-limited pairing codes with rate limiting and an audit trail
- Three trust levels: owner, paired, stranger
Pairing by the numbers
- 14 all-time installs (skills.sh)
- Ranked #1,619 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
pairing capabilities & compatibility
- Pricing
- Free
What pairing says it does
User pairing, authentication, and trust management
Pair new users to Clodds, manage trust levels, and control access across channels.
npx skills add https://github.com/alsk1992/cloddsbot --skill pairingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 14 |
|---|---|
| repo stars | ★ 610 |
| Last updated | June 26, 2026 |
| Repository | alsk1992/cloddsbot ↗ |
What it does
Pair and authenticate bot users, then gate access by trust level.
When should I use this skill?
You need to authenticate new bot users and gate access by trust level.
What you get
Authenticated users with owner/paired/stranger trust levels and an audit trail.
By the numbers
- 3 trust levels
- 8-character pairing codes
- 1-hour code expiry
Files
Pairing - Complete API Reference
Pair new users to Clodds, manage trust levels, and control access across channels.
---
Chat Commands
Pairing (New Users)
/pair Request pairing (generates code)
/pair-code ABC123 Enter pairing code
/unpair Remove your pairingAdmin Commands
/pairing list List pending requests
/pairing approve <code> Approve pairing request
/pairing reject <code> Reject pairing request
/pairing users List paired users
/pairing remove <user> Remove user pairingTrust Management
/trust <user> owner Grant owner trust
/trust <user> paired Standard trust
/trust list List trust levels---
TypeScript API Reference
Create Pairing Service
import { createPairingService } from 'clodds/pairing';
const pairing = createPairingService({
// Code settings
codeLength: 8,
codeExpiryMinutes: 60,
maxPendingPerChannel: 3,
// Auto-approve settings
autoApproveLocal: true, // Auto-approve localhost
autoApproveTailscale: true, // Auto-approve Tailscale IPs
autoApproveOwners: true, // Owners auto-approve their requests
// Storage
storage: 'sqlite',
dbPath: './pairing.db',
});Create Pairing Request
// User requests pairing
const request = await pairing.createPairingRequest({
channelId: 'telegram-123',
userId: 'telegram-user-456',
username: 'johndoe',
displayName: 'John Doe',
});
console.log(`Pairing code: ${request.code}`);
console.log(`Expires: ${request.expiresAt}`);
console.log(`Share this code with an admin to get approved`);Validate Code
// Check if code is valid
const valid = await pairing.validateCode({
code: 'ABC123XY',
});
if (valid) {
console.log(`Valid code for user: ${valid.username}`);
console.log(`Channel: ${valid.channelId}`);
}Approve Request
// Admin approves pairing
await pairing.approveRequest({
code: 'ABC123XY',
approvedBy: 'admin-user-id',
trustLevel: 'paired',
});Reject Request
// Admin rejects pairing
await pairing.rejectRequest({
code: 'ABC123XY',
rejectedBy: 'admin-user-id',
reason: 'Unknown user',
});Check Pairing Status
// Check if user is paired
const isPaired = await pairing.isPaired({
channelId: 'telegram-123',
userId: 'telegram-user-456',
});
if (isPaired) {
console.log('User is paired and can use Clodds');
}Get Trust Level
const trust = await pairing.getTrustLevel({
channelId: 'telegram-123',
userId: 'telegram-user-456',
});
console.log(`Trust level: ${trust}`);
// 'owner' | 'paired' | 'stranger'
// Check specific permission
if (trust === 'owner') {
console.log('Full admin access');
} else if (trust === 'paired') {
console.log('Standard trading access');
} else {
console.log('No access - must pair first');
}List Pending Requests
const pending = await pairing.listPendingRequests({
channelId: 'telegram-123', // Optional: filter by channel
});
for (const req of pending) {
console.log(`Code: ${req.code}`);
console.log(`User: ${req.username} (${req.displayName})`);
console.log(`Requested: ${req.createdAt}`);
console.log(`Expires: ${req.expiresAt}`);
}List Paired Users
const users = await pairing.listPairedUsers({
channelId: 'telegram-123', // Optional: filter by channel
});
for (const user of users) {
console.log(`${user.username}: ${user.trustLevel}`);
console.log(` Paired: ${user.pairedAt}`);
console.log(` Approved by: ${user.approvedBy}`);
}Check Owner Status
const isOwner = await pairing.isOwner({
channelId: 'telegram-123',
userId: 'telegram-user-456',
});
if (isOwner) {
console.log('User has owner privileges');
}Remove Pairing
// Remove user's pairing
await pairing.removePairing({
channelId: 'telegram-123',
userId: 'telegram-user-456',
});---
Trust Levels
| Level | Access |
|---|---|
| owner | Full admin: approve users, manage settings, trading |
| paired | Standard: trading, portfolio, queries |
| stranger | None: must pair first |
---
Pairing Code Format
- Length: 8 characters
- Characters: Uppercase letters + numbers
- Excludes: 0, O, 1, I, L (avoid confusion)
- Example:
ABC234XY
---
Auto-Approve Rules
| Condition | Behavior |
|---|---|
| Localhost | Auto-approve with owner trust |
| Tailscale IP | Auto-approve with owner trust |
| Owner request | Auto-approve their other channels |
---
Security Features
| Feature | Description |
|---|---|
| Code expiry | Codes expire after 1 hour |
| Rate limiting | Max 3 pending per channel |
| Unambiguous codes | No confusable characters |
| Audit trail | Who approved/rejected when |
---
CLI Admin Commands
# List pending pairing requests
clodds pairing list telegram
# Approve a request
clodds pairing approve ABC234XY
# List paired users
clodds pairing users telegram
# Add user directly (bypass code)
clodds pairing add telegram user-123
# Remove user
clodds pairing remove telegram user-123---
Best Practices
1. Share codes securely — Don't post in public channels 2. Set expiry appropriately — Shorter for sensitive systems 3. Review pending regularly — Don't let requests pile up 4. Use owner sparingly — Most users only need 'paired' 5. Audit periodically — Review who has access
/**
* Pairing CLI Skill
*
* Commands:
* /pair - Request pairing (generates code)
* /pair-code <code> - Enter pairing code
* /unpair - Remove your pairing
* /pairing list - List pending requests
* /pairing approve <code> - Approve pairing request
* /pairing reject <code> - Reject pairing request
* /pairing users - List paired users
* /pairing remove <user> - Remove user pairing
* /trust <user> owner - Grant owner trust
* /trust <user> paired - Standard trust
* /trust list - List trust levels
*/
import {
createPairingService,
PairingService,
TrustLevel,
} from '../../../pairing/index';
import { logger } from '../../../utils/logger';
let service: PairingService | null = null;
async function getService(): Promise<PairingService | null> {
if (!service) {
try {
const { createDatabase } = await import('../../../db/index');
const db = createDatabase();
service = createPairingService(db);
} catch { /* leave null if dependencies missing */ }
}
return service;
}
async function handlePair(channel: string, userId: string, username?: string): Promise<string> {
const svc = await getService();
if (!svc) return 'Pairing service not initialized. Database required.';
const code = await svc.createPairingRequest(channel, userId, username);
if (!code) {
if (svc.isPaired(channel, userId)) {
return 'You are already paired.';
}
return 'Could not create pairing request. Maximum pending requests may have been reached.';
}
return `**Pairing Request Created**\n\n` +
`Your pairing code: \`${code}\`\n\n` +
`Share this code with an admin to get approved.\n` +
`Code expires in 1 hour.`;
}
async function handlePairCode(code: string): Promise<string> {
const svc = await getService();
if (!svc) return 'Pairing service not initialized.';
if (!code) return 'Usage: /pair-code <code>';
const request = await svc.validateCode(code);
if (!request) {
return 'Invalid or expired pairing code.';
}
return `Pairing successful! User ${request.username || request.userId} has been paired on channel ${request.channel}.`;
}
async function handleUnpair(channel: string, userId: string): Promise<string> {
const svc = await getService();
if (!svc) return 'Pairing service not initialized.';
if (!svc.isPaired(channel, userId)) {
return 'You are not currently paired.';
}
svc.removePairedUser(channel, userId);
return 'Your pairing has been removed.';
}
async function handleList(channel: string): Promise<string> {
const svc = await getService();
if (!svc) return 'Pairing service not initialized.';
const pending = svc.listPendingRequests(channel);
if (pending.length === 0) {
return 'No pending pairing requests.';
}
let output = `**Pending Pairing Requests** (${pending.length})\n\n`;
for (const req of pending) {
output += `Code: \`${req.code}\`\n`;
output += ` User: ${req.username || req.userId}\n`;
output += ` Requested: ${req.createdAt.toLocaleString()}\n`;
output += ` Expires: ${req.expiresAt.toLocaleString()}\n\n`;
}
return output;
}
async function handleApprove(channel: string, code: string): Promise<string> {
const svc = await getService();
if (!svc) return 'Pairing service not initialized.';
if (!code) return 'Usage: /pairing approve <code>';
const success = await svc.approveRequest(channel, code);
if (!success) {
return `Could not approve code "${code}". It may be invalid, expired, or for a different channel.`;
}
return `Pairing request \`${code}\` approved.`;
}
async function handleReject(channel: string, code: string): Promise<string> {
const svc = await getService();
if (!svc) return 'Pairing service not initialized.';
if (!code) return 'Usage: /pairing reject <code>';
const success = await svc.rejectRequest(channel, code);
if (!success) {
return `Could not reject code "${code}". It may be invalid or expired.`;
}
return `Pairing request \`${code}\` rejected.`;
}
async function handleUsers(channel: string): Promise<string> {
const svc = await getService();
if (!svc) return 'Pairing service not initialized.';
const users = svc.listPairedUsers(channel);
if (users.length === 0) {
return 'No paired users on this channel.';
}
let output = `**Paired Users** (${users.length})\n\n`;
for (const user of users) {
const trust = user.isOwner ? 'owner' : 'paired';
output += `**${user.username || user.userId}**\n`;
output += ` Trust: ${trust}\n`;
output += ` Paired: ${user.pairedAt.toLocaleString()}\n`;
output += ` Method: ${user.pairedBy}\n\n`;
}
return output;
}
async function handleRemove(channel: string, userId: string): Promise<string> {
const svc = await getService();
if (!svc) return 'Pairing service not initialized.';
if (!userId) return 'Usage: /pairing remove <user>';
svc.removePairedUser(channel, userId);
return `User ${userId} has been unpaired.`;
}
async function handleTrust(channel: string, userId: string, level: string): Promise<string> {
const svc = await getService();
if (!svc) return 'Pairing service not initialized.';
if (level === 'owner') {
svc.setOwner(channel, userId);
return `User ${userId} granted owner trust.`;
} else if (level === 'paired') {
svc.removeOwner(channel, userId);
return `User ${userId} set to standard (paired) trust.`;
}
return 'Usage: /trust <user> owner|paired';
}
async function handleTrustList(channel: string): Promise<string> {
const svc = await getService();
if (!svc) return 'Pairing service not initialized.';
const owners = svc.listOwners(channel);
const users = svc.listPairedUsers(channel);
let output = '**Trust Levels**\n\n';
output += `**Owners** (${owners.length}):\n`;
for (const owner of owners) {
output += ` - ${owner.username || owner.userId}\n`;
}
output += `\n**Paired** (${users.filter(u => !u.isOwner).length}):\n`;
for (const user of users.filter(u => !u.isOwner)) {
output += ` - ${user.username || user.userId}\n`;
}
return output;
}
export async function execute(args: string): Promise<string> {
const parts = args.trim().split(/\s+/);
const command = parts[0]?.toLowerCase() || 'help';
const rest = parts.slice(1);
// Default channel/userId for CLI context
const channel = 'cli';
const userId = 'cli-user';
switch (command) {
case 'pair':
return handlePair(channel, userId);
case 'pair-code':
return handlePairCode(rest[0]);
case 'unpair':
return handleUnpair(channel, userId);
case 'list':
return handleList(channel);
case 'approve':
return handleApprove(channel, rest[0]);
case 'reject':
return handleReject(channel, rest[0]);
case 'users':
return handleUsers(channel);
case 'remove':
return handleRemove(channel, rest[0]);
case 'trust':
if (rest[0] === 'list') return handleTrustList(channel);
if (rest.length < 2) return 'Usage: /trust <user> owner|paired';
return handleTrust(channel, rest[0], rest[1]);
case 'cleanup': {
const cleanupSvc = await getService();
if (cleanupSvc) {
cleanupSvc.cleanupExpired();
return 'Expired pairing requests cleaned up.';
}
return 'Pairing service not initialized.';
}
case 'help':
default:
return `**Pairing Commands**
**User Pairing:**
/pairing pair - Request pairing (generates code)
/pairing pair-code <code> - Enter pairing code
/pairing unpair - Remove your pairing
**Admin Commands:**
/pairing list - List pending requests
/pairing approve <code> - Approve pairing request
/pairing reject <code> - Reject pairing request
/pairing users - List paired users
/pairing remove <user> - Remove user pairing
/pairing cleanup - Clean up expired requests
**Trust Management:**
/pairing trust <user> owner - Grant owner trust
/pairing trust <user> paired - Standard trust
/pairing trust list - List trust levels`;
}
}
export default {
name: 'pairing',
description: 'User pairing, authentication, and trust management',
commands: ['/pairing', '/pair', '/unpair', '/trust'],
handle: execute,
};
Related skills
FAQ
What trust levels exist?
owner (full admin), paired (standard trading), and stranger (no access, must pair first).
How long are pairing codes valid?
Codes expire after 1 hour and are limited to 3 pending per channel.