watch
Lets Claude watch a video from a URL or local file by pulling timestamped frames and a transcript. Works in Claude Code, and the watch skill downloads with yt-dlp and extracts frames with ffmpeg.
What unslop runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Claude Code
> /plugin marketplace add MohamedAbdallah-14/unslop> /plugin install unslop@unslop-marketplace
Cursor
Cursor also runs Claude Code hooks from .claude/settings.json, so copy these hooks there to use them in Cursor.
Where it lives
# Install unslop hooks into Claude Code's user settings on Windows.
#
# Copies hook scripts into $CLAUDE_CONFIG_DIR/hooks/ (or ~/.claude/hooks/)
# and registers SessionStart + UserPromptSubmit hooks in settings.json.
# Also wires the statusline so the [unslop] badge shows when active.
#
# Supports: -Force to overwrite existing hooks.
# Requires: PowerShell 5.1+, Node.js
param([switch]$Force)
$ErrorActionPreference = 'Stop'
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ClaudeDir = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME '.claude' }
$HooksDir = Join-Path $ClaudeDir 'hooks'
$Settings = Join-Path $ClaudeDir 'settings.json'
if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
Write-Error "Error: 'node' not found on PATH. Install Node.js from https://nodejs.org"
exit 1
}
$HookFiles = @('package.json', 'unslop-config.js', 'unslop-activate.js', 'unslop-mode-tracker.js', 'unslop-statusline.ps1')
# Check if already installed (unless -Force)
if (-not $Force) {
$allPresent = $true
foreach ($hook in $HookFiles) {
if (-not (Test-Path (Join-Path $HooksDir $hook))) {
$allPresent = $false
break
}
}
if ($allPresent -and (Test-Path $Settings)) {
Write-Host "Unslop hooks already installed in $HooksDir"
Write-Host " Re-run with -Force to overwrite."
exit 0
}
}
if ($Force -and (Test-Path (Join-Path $HooksDir 'unslop-activate.js'))) {
Write-Host "Reinstalling unslop hooks (-Force)..."
} else {
Write-Host "Installing unslop hooks..."
}
New-Item -ItemType Directory -Force -Path $HooksDir | Out-Null
foreach ($hook in $HookFiles) {
$src = Join-Path $ScriptDir $hook
$dst = Join-Path $HooksDir $hook
if (Test-Path $src) {
Copy-Item -Force $src $dst
} else {
$url = "https://raw.githubusercontent.com/MohamedAbdallah-14/unslop/main/hooks/$hook"
Invoke-WebRequest -Uri $url -OutFile $dst
}
Write-Host " Installed: $dst"
}
if (-not (Test-Path $Settings)) { '{}' | Out-File -Encoding utf8 $Settings }
# Back up settings before modifying
Copy-Item $Settings "$Settings.bak"
$env:UNSLOP_SETTINGS = $Settings
$env:UNSLOP_HOOKS_DIR = $HooksDir
node -e @"
const fs = require('fs');
const settingsPath = process.env.UNSLOP_SETTINGS;
const hooksDir = process.env.UNSLOP_HOOKS_DIR;
const managedStatusLinePath = hooksDir + '/unslop-statusline.ps1';
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
if (!settings.hooks) settings.hooks = {};
if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
const hasStart = settings.hooks.SessionStart.some(e =>
e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop'))
);
if (!hasStart) {
settings.hooks.SessionStart.push({
hooks: [{
type: 'command',
command: 'node \"' + hooksDir + '/unslop-activate.js\"',
timeout: 5,
statusMessage: 'Loading unslop mode...'
}]
});
}
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
const hasPrompt = settings.hooks.UserPromptSubmit.some(e =>
e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop'))
);
if (!hasPrompt) {
settings.hooks.UserPromptSubmit.push({
hooks: [{
type: 'command',
command: 'node \"' + hooksDir + '/unslop-mode-tracker.js\"',
timeout: 5,
statusMessage: 'Tracking unslop mode...'
}]
});
}
if (!settings.statusLine) {
settings.statusLine = {
type: 'command',
command: 'powershell -ExecutionPolicy Bypass -File \"' + managedStatusLinePath + '\"'
};
console.log(' Statusline badge configured.');
} else {
const cmd = typeof settings.statusLine === 'string'
? settings.statusLine
: (settings.statusLine.command || '');
if (cmd.includes(managedStatusLinePath)) {
console.log(' Statusline badge already configured.');
} else {
console.log(' NOTE: Existing statusline detected - unslop badge NOT added.');
}
}
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
console.log(' Hooks wired in settings.json');
"@
Write-Host ""
Write-Host "Done! Restart Claude Code to activate."
Write-Host ""
Write-Host "What's installed:"
Write-Host " - SessionStart hook: auto-loads unslop rules every session"
Write-Host " - Mode tracker hook: updates statusline badge when you switch modes"
Write-Host " - Statusline badge: shows [unslop] or [unslop:FULL] etc."
#!/bin/bash
# unslop — one-command hook installer for Claude Code
# Installs: SessionStart hook (auto-load rules) + UserPromptSubmit hook (mode tracking)
# Usage: bash hooks/install.sh
# or: bash hooks/install.sh --force (re-install over existing hooks)
set -e
FORCE=0
for arg in "$@"; do
case "$arg" in
--force|-f) FORCE=1 ;;
esac
done
case "$OSTYPE" in
msys*|cygwin*|mingw*)
echo "WARNING: Running on Windows ($OSTYPE)."
echo " This script works in Git Bash/MSYS but symlinks may require"
echo " Developer Mode or admin privileges."
echo " If you installed via 'claude plugin install', you don't need this script."
echo ""
;;
esac
if ! command -v node >/dev/null 2>&1; then
echo "ERROR: 'node' is required to install the unslop hooks (used to merge"
echo " the hook config into ~/.claude/settings.json safely)."
echo " Install Node.js from https://nodejs.org and re-run this script."
exit 1
fi
CLAUDE_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
HOOKS_DIR="$CLAUDE_DIR/hooks"
SETTINGS="$CLAUDE_DIR/settings.json"
REPO_URL="https://raw.githubusercontent.com/MohamedAbdallah-14/unslop/main/hooks"
HOOK_FILES=("package.json" "unslop-config.js" "unslop-activate.js" "unslop-mode-tracker.js" "unslop-statusline.sh")
SCRIPT_DIR=""
if [ -n "${BASH_SOURCE[0]:-}" ] && [ -f "${BASH_SOURCE[0]}" ]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
fi
# Check if already installed (unless --force)
ALREADY_INSTALLED=0
if [ "$FORCE" -eq 0 ]; then
ALL_FILES_PRESENT=1
for hook in "${HOOK_FILES[@]}"; do
if [ ! -f "$HOOKS_DIR/$hook" ]; then
ALL_FILES_PRESENT=0
break
fi
done
HOOKS_WIRED=0
HAS_STATUSLINE=0
if [ "$ALL_FILES_PRESENT" -eq 1 ] && [ -f "$SETTINGS" ]; then
if UNSLOP_SETTINGS="$SETTINGS" node -e "
const fs = require('fs');
const settings = JSON.parse(fs.readFileSync(process.env.UNSLOP_SETTINGS, 'utf8'));
const hasUnslopHook = (event) =>
Array.isArray(settings.hooks?.[event]) &&
settings.hooks[event].some(e =>
e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop'))
);
process.exit(
hasUnslopHook('SessionStart') &&
hasUnslopHook('UserPromptSubmit') &&
!!settings.statusLine
? 0
: 1
);
" >/dev/null 2>&1; then
HOOKS_WIRED=1
HAS_STATUSLINE=1
fi
fi
if [ "$ALL_FILES_PRESENT" -eq 1 ] && [ "$HOOKS_WIRED" -eq 1 ] && [ "$HAS_STATUSLINE" -eq 1 ]; then
ALREADY_INSTALLED=1
echo "Unslop hooks already installed in $HOOKS_DIR"
echo " Re-run with --force to overwrite: bash hooks/install.sh --force"
echo ""
fi
fi
if [ "$ALREADY_INSTALLED" -eq 1 ] && [ "$FORCE" -eq 0 ]; then
echo "Nothing to do. Hooks are already in place."
exit 0
fi
if [ "$FORCE" -eq 1 ] && [ -f "$HOOKS_DIR/unslop-activate.js" ]; then
echo "Reinstalling unslop hooks (--force)..."
else
echo "Installing unslop hooks..."
fi
mkdir -p "$HOOKS_DIR"
for hook in "${HOOK_FILES[@]}"; do
if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/$hook" ]; then
cp "$SCRIPT_DIR/$hook" "$HOOKS_DIR/$hook"
else
curl -fsSL "$REPO_URL/$hook" -o "$HOOKS_DIR/$hook"
fi
echo " Installed: $HOOKS_DIR/$hook"
done
chmod +x "$HOOKS_DIR/unslop-statusline.sh"
if [ ! -f "$SETTINGS" ]; then
echo '{}' > "$SETTINGS"
fi
# Back up existing settings.json before touching it
cp "$SETTINGS" "$SETTINGS.bak"
UNSLOP_SETTINGS="$SETTINGS" UNSLOP_HOOKS_DIR="$HOOKS_DIR" node -e "
const fs = require('fs');
const settingsPath = process.env.UNSLOP_SETTINGS;
const hooksDir = process.env.UNSLOP_HOOKS_DIR;
const managedStatusLinePath = hooksDir + '/unslop-statusline.sh';
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
if (!settings.hooks) settings.hooks = {};
if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
const hasStart = settings.hooks.SessionStart.some(e =>
e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop'))
);
if (!hasStart) {
settings.hooks.SessionStart.push({
hooks: [{
type: 'command',
command: 'node \"' + hooksDir + '/unslop-activate.js\"',
timeout: 5,
statusMessage: 'Loading unslop mode...'
}]
});
}
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
const hasPrompt = settings.hooks.UserPromptSubmit.some(e =>
e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop'))
);
if (!hasPrompt) {
settings.hooks.UserPromptSubmit.push({
hooks: [{
type: 'command',
command: 'node \"' + hooksDir + '/unslop-mode-tracker.js\"',
timeout: 5,
statusMessage: 'Tracking unslop mode...'
}]
});
}
if (!settings.statusLine) {
settings.statusLine = {
type: 'command',
command: 'bash \"' + managedStatusLinePath + '\"'
};
console.log(' Statusline badge configured.');
} else {
const cmd = typeof settings.statusLine === 'string'
? settings.statusLine
: (settings.statusLine.command || '');
if (cmd.includes(managedStatusLinePath)) {
console.log(' Statusline badge already configured.');
} else {
console.log(' NOTE: Existing statusline detected — unslop badge NOT added.');
console.log(' See hooks/README.md to add the badge to your existing statusline.');
}
}
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
console.log(' Hooks wired in settings.json');
"
echo ""
echo "Done! Restart Claude Code to activate."
echo ""
echo "What's installed:"
echo " - SessionStart hook: auto-loads unslop rules every session"
echo " - Mode tracker hook: updates statusline badge when you switch modes"
echo " (/unslop subtle, /unslop full, /unslop-commit, etc.)"
echo " - Statusline badge: shows [unslop] or [unslop:FULL] etc."
# unslop — uninstall hook files and remove settings.json entries
# Usage: pwsh hooks/uninstall.ps1
$ErrorActionPreference = 'Stop'
$ClaudeDir = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME '.claude' }
$HooksDir = Join-Path $ClaudeDir 'hooks'
$Settings = Join-Path $ClaudeDir 'settings.json'
$Flag = Join-Path $ClaudeDir '.unslop-active'
$HookFiles = @('package.json', 'unslop-config.js', 'unslop-activate.js', 'unslop-mode-tracker.js', 'unslop-statusline.ps1')
Write-Host "Uninstalling unslop hooks..."
foreach ($hook in $HookFiles) {
$target = Join-Path $HooksDir $hook
if (Test-Path $target) {
Remove-Item $target -Force
Write-Host " Removed: $target"
}
}
if (Test-Path $Flag) {
Remove-Item $Flag -Force
Write-Host " Removed flag file: $Flag"
}
if ((Test-Path $Settings) -and (Get-Command node -ErrorAction SilentlyContinue)) {
Copy-Item $Settings "$Settings.bak"
$env:UNSLOP_SETTINGS = $Settings
node -e @"
const fs = require('fs');
const settingsPath = process.env.UNSLOP_SETTINGS;
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
if (settings.hooks) {
for (const event of ['SessionStart', 'UserPromptSubmit']) {
if (Array.isArray(settings.hooks[event])) {
settings.hooks[event] = settings.hooks[event].filter(e =>
!(e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop')))
);
if (settings.hooks[event].length === 0) delete settings.hooks[event];
}
}
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
}
if (settings.statusLine) {
const cmd = typeof settings.statusLine === 'string'
? settings.statusLine
: (settings.statusLine.command || '');
if (cmd.includes('unslop-statusline')) {
delete settings.statusLine;
console.log(' Removed statusline config.');
}
}
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
console.log(' Cleaned settings.json');
"@
} else {
Write-Host " Skipped settings.json cleanup (node not found or settings.json missing)."
}
Write-Host ""
Write-Host "Done. Restart Claude Code to complete removal."
#!/bin/bash
# unslop — uninstall hook files and remove settings.json entries
# Usage: bash hooks/uninstall.sh
set -e
CLAUDE_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
HOOKS_DIR="$CLAUDE_DIR/hooks"
SETTINGS="$CLAUDE_DIR/settings.json"
FLAG="$CLAUDE_DIR/.unslop-active"
HOOK_FILES=("package.json" "unslop-config.js" "unslop-activate.js" "unslop-mode-tracker.js" "unslop-statusline.sh")
echo "Uninstalling unslop hooks..."
for hook in "${HOOK_FILES[@]}"; do
target="$HOOKS_DIR/$hook"
if [ -f "$target" ]; then
rm "$target"
echo " Removed: $target"
fi
done
if [ -f "$FLAG" ]; then
rm "$FLAG"
echo " Removed flag file: $FLAG"
fi
if [ -f "$SETTINGS" ] && command -v node >/dev/null 2>&1; then
cp "$SETTINGS" "$SETTINGS.bak"
UNSLOP_SETTINGS="$SETTINGS" node -e "
const fs = require('fs');
const settingsPath = process.env.UNSLOP_SETTINGS;
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
if (settings.hooks) {
for (const event of ['SessionStart', 'UserPromptSubmit']) {
if (Array.isArray(settings.hooks[event])) {
settings.hooks[event] = settings.hooks[event].filter(e =>
!(e.hooks && e.hooks.some(h => h.command && h.command.includes('unslop')))
);
if (settings.hooks[event].length === 0) delete settings.hooks[event];
}
}
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
}
if (settings.statusLine) {
const cmd = typeof settings.statusLine === 'string'
? settings.statusLine
: (settings.statusLine.command || '');
if (cmd.includes('unslop-statusline')) {
delete settings.statusLine;
console.log(' Removed statusline config.');
}
}
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
console.log(' Cleaned settings.json');
"
else
echo " Skipped settings.json cleanup (node not found or settings.json missing)."
fi
echo ""
echo "Done. Restart Claude Code to complete removal."
#!/usr/bin/env node
// unslop — Claude Code SessionStart activation hook
//
// Runs on every session start:
// 1. Writes flag file at $CLAUDE_CONFIG_DIR/.unslop-active (statusline reads this)
// 2. Emits unslop ruleset as hidden SessionStart context
// 3. Detects missing statusline config and emits setup nudge
const fs = require('fs');
const path = require('path');
const os = require('os');
const {
getDefaultMode, safeWriteFlag, getFlagPath,
getTurnCounterPath, resetTurnCount,
} = require('./unslop-config');
const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
const flagPath = getFlagPath();
const counterPath = getTurnCounterPath();
const settingsPath = path.join(claudeDir, 'settings.json');
const mode = getDefaultMode();
// Persona-drift reset: a new session always starts at turn 0. RMTBench /
// HorizonBench report that long contexts accumulate drift; the counter is
// only meaningful within a single session, so we zero it here.
resetTurnCount(counterPath);
if (mode === 'off') {
try { fs.unlinkSync(flagPath); } catch (e) {}
process.stdout.write('OK');
process.exit(0);
}
safeWriteFlag(flagPath, mode);
// Independent modes have their own skill files — don't emit the full ruleset.
const INDEPENDENT_MODES = new Set(['commit', 'review']);
if (INDEPENDENT_MODES.has(mode)) {
process.stdout.write('UNSLOP MODE ACTIVE — level: ' + mode + '. Behavior defined by /unslop-' + mode + ' skill.');
process.exit(0);
}
// Read SKILL.md — the single source of truth for unslop behavior.
// Plugin installs: __dirname = <plugin_root>/hooks/, SKILL.md at <plugin_root>/skills/unslop/SKILL.md
// Standalone installs: __dirname = $CLAUDE_CONFIG_DIR/hooks/, SKILL.md won't exist — falls back to activation rule then hardcoded rules.
let skillContent = '';
try {
skillContent = fs.readFileSync(
path.join(__dirname, '..', 'skills', 'unslop', 'SKILL.md'), 'utf8'
);
} catch (e) { /* try activation rule next */ }
// Fallback: try the activation rule file (lighter weight than full SKILL.md)
let activationRule = '';
if (!skillContent) {
try {
activationRule = fs.readFileSync(
path.join(__dirname, '..', 'rules', 'unslop-activate.md'), 'utf8'
).trim();
} catch (e) { /* will use hardcoded fallback */ }
}
let output;
if (skillContent) {
const body = skillContent.replace(/^---[\s\S]*?---\s*/, '');
// Filter intensity table and examples to the active level
const filtered = body.split('\n').reduce((acc, line) => {
const tableRowMatch = line.match(/^\|\s*\*\*(\S+?)\*\*\s*\|/);
if (tableRowMatch) {
if (tableRowMatch[1] === mode) {
acc.push(line);
}
return acc;
}
const exampleMatch = line.match(/^- (\S+?):\s/);
if (exampleMatch) {
if (exampleMatch[1] === mode) {
acc.push(line);
}
return acc;
}
acc.push(line);
return acc;
}, []);
output = 'UNSLOP MODE ACTIVE — level: ' + mode + '\n\n' + filtered.join('\n');
} else if (activationRule) {
output = 'UNSLOP MODE ACTIVE — level: ' + mode + '\n\n' + activationRule;
} else {
output =
'UNSLOP MODE ACTIVE — level: ' + mode + '\n\n' +
'Write like a careful human. All technical substance stays exact. Only AI-slop dies.\n\n' +
'## Persistence\n\n' +
'ACTIVE EVERY RESPONSE. No revert after many turns. No drift back into AI-template English.\n' +
'Off only: "stop unslop" / "normal mode".\n\n' +
'Current level: **' + mode + '**. Switch: `/unslop subtle|balanced|full|voice-match|anti-detector`.\n\n' +
'## Rules\n\n' +
'Drop: sycophancy ("great question", "I\'d be happy to"), stock vocab (delve/tapestry/testament/seamless/holistic/leverage-as-filler), ' +
'hedging stacks ("it\'s important to note that"), tricolon padding, em-dash pileups, performative balance, tidy five-paragraph shapes.\n\n' +
'Keep: technical terms exact, code unchanged, real uncertainty when honest.\n' +
'Engineer burstiness: mix short and long sentences deliberately.\n\n' +
'Pattern: [concrete observation]. [why]. [what to do next].\n\n' +
'## Auto-Clarity\n\n' +
'Drop unslop style for: security warnings, irreversible actions, legal/medical/financial precision, user confused. Resume after.\n\n' +
'## Boundaries\n\n' +
'Code/commits/PRs: write normal. "stop unslop" or "normal mode": revert. Level persists until changed or session ends.';
}
// Detect missing statusline config — nudge Claude to help set it up
try {
let hasStatusline = false;
if (fs.existsSync(settingsPath)) {
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
if (settings.statusLine) {
hasStatusline = true;
}
}
if (!hasStatusline) {
const isWindows = process.platform === 'win32';
const scriptName = isWindows ? 'unslop-statusline.ps1' : 'unslop-statusline.sh';
const scriptPath = path.join(__dirname, scriptName);
const command = isWindows
? `powershell -ExecutionPolicy Bypass -File "${scriptPath}"`
: `bash "${scriptPath}"`;
const statusLineSnippet =
'"statusLine": { "type": "command", "command": ' + JSON.stringify(command) + ' }';
output += "\n\n" +
"STATUSLINE SETUP NEEDED: The unslop plugin includes a statusline badge showing active mode " +
"(e.g. [unslop], [unslop:full]). It is not configured yet. " +
"To enable, add this to " + path.join(claudeDir, 'settings.json') + ": " +
statusLineSnippet + " " +
"Proactively offer to set this up for the user on first interaction.";
}
} catch (e) {
// Silent fail — don't block session start over statusline detection
}
process.stdout.write(output);
#!/usr/bin/env node
// unslop — shared configuration resolver
//
// Resolution order for default mode:
// 1. UNSLOP_DEFAULT_MODE environment variable
// 2. Config file defaultMode field:
// - $XDG_CONFIG_HOME/unslop/config.json (any platform, if set)
// - ~/.config/unslop/config.json (macOS / Linux fallback)
// - %APPDATA%\unslop\config.json (Windows fallback)
// 3. 'balanced'
const fs = require('fs');
const path = require('path');
const os = require('os');
const VALID_MODES = [
'off', 'subtle', 'balanced', 'full',
'voice-match', 'anti-detector',
'commit', 'review'
];
function getConfigDir() {
if (process.env.XDG_CONFIG_HOME) {
return path.join(process.env.XDG_CONFIG_HOME, 'unslop');
}
if (process.platform === 'win32') {
return path.join(
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
'unslop'
);
}
return path.join(os.homedir(), '.config', 'unslop');
}
function getConfigPath() {
return path.join(getConfigDir(), 'config.json');
}
function getDefaultMode() {
const envMode = process.env.UNSLOP_DEFAULT_MODE;
if (envMode && VALID_MODES.includes(envMode.toLowerCase())) {
return envMode.toLowerCase();
}
try {
const configPath = getConfigPath();
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
if (config.defaultMode && VALID_MODES.includes(config.defaultMode.toLowerCase())) {
return config.defaultMode.toLowerCase();
}
} catch (e) {
// Config file doesn't exist or is invalid
}
return 'balanced';
}
// Symlink-safe flag file write.
// Refuses symlinks at the target file and at the immediate parent directory,
// uses O_NOFOLLOW where available, writes atomically via temp + rename with
// 0600 permissions. Protects against local attackers replacing the predictable
// flag path with a symlink to clobber other files.
function safeWriteFlag(flagPath, content) {
try {
const flagDir = path.dirname(flagPath);
fs.mkdirSync(flagDir, { recursive: true });
try {
if (fs.lstatSync(flagDir).isSymbolicLink()) return;
} catch (e) {
return;
}
try {
if (fs.lstatSync(flagPath).isSymbolicLink()) return;
} catch (e) {
if (e.code !== 'ENOENT') return;
}
const tempPath = path.join(flagDir, `.unslop-active.${process.pid}.${Date.now()}`);
const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | O_NOFOLLOW;
let fd;
try {
fd = fs.openSync(tempPath, flags, 0o600);
fs.writeSync(fd, String(content));
try { fs.fchmodSync(fd, 0o600); } catch (e) { /* best-effort on Windows */ }
} finally {
if (fd !== undefined) fs.closeSync(fd);
}
fs.renameSync(tempPath, flagPath);
} catch (e) {
// Silent fail — flag is best-effort
}
}
// Symlink-safe, size-capped, whitelist-validated flag file read.
// Returns null on any anomaly — never inject untrusted bytes into model context.
const MAX_FLAG_BYTES = 64;
function readFlag(flagPath) {
try {
let st;
try {
st = fs.lstatSync(flagPath);
} catch (e) {
return null;
}
if (st.isSymbolicLink() || !st.isFile()) return null;
if (st.size > MAX_FLAG_BYTES) return null;
const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
const flags = fs.constants.O_RDONLY | O_NOFOLLOW;
let fd;
let out;
try {
fd = fs.openSync(flagPath, flags);
const buf = Buffer.alloc(MAX_FLAG_BYTES);
const n = fs.readSync(fd, buf, 0, MAX_FLAG_BYTES, 0);
out = buf.slice(0, n).toString('utf8');
} finally {
if (fd !== undefined) fs.closeSync(fd);
}
const raw = out.trim().toLowerCase();
if (!VALID_MODES.includes(raw)) return null;
return raw;
} catch (e) {
return null;
}
}
function getFlagPath() {
const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
return path.join(claudeDir, '.unslop-active');
}
// Persona-drift reinforcement counter. Tracks how many user turns have
// passed in this session while unslop has been active. RMTBench measures
// >30% persona degradation after 8–12 turns; HorizonBench (arXiv
// 2604.17283, Apr 2026) benchmarks preference evolution over time. We use
// the counter to re-emit a shorter reinforcement banner at predetermined
// drift-risk checkpoints rather than every turn (which would get tuned out).
function getTurnCounterPath() {
const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
return path.join(claudeDir, '.unslop-turn-count');
}
// Read the counter. Same symlink-safe / size-capped discipline as readFlag.
function readTurnCount(counterPath) {
try {
let st;
try {
st = fs.lstatSync(counterPath);
} catch (e) {
return 0;
}
if (st.isSymbolicLink() || !st.isFile()) return 0;
if (st.size > 32) return 0;
const O_NOFOLLOW = typeof fs.constants.O_NOFOLLOW === 'number' ? fs.constants.O_NOFOLLOW : 0;
const flags = fs.constants.O_RDONLY | O_NOFOLLOW;
let fd, raw;
try {
fd = fs.openSync(counterPath, flags);
const buf = Buffer.alloc(32);
const n = fs.readSync(fd, buf, 0, 32, 0);
raw = buf.slice(0, n).toString('utf8').trim();
} finally {
if (fd !== undefined) fs.closeSync(fd);
}
const n = parseInt(raw, 10);
if (!Number.isFinite(n) || n < 0 || n > 1_000_000) return 0;
return n;
} catch (e) {
return 0;
}
}
// Symlink-safe atomic-rename write of the counter. Uses the same pattern as
// safeWriteFlag to resist local-attacker symlink games.
function writeTurnCount(counterPath, n) {
try {
const dir = path.dirname(counterPath);
fs.mkdirSync(dir, { recursive: true });
try {
if (fs.lstatSync(dir).isSymbolicLink()) return;
} catch (e) { return; }
try {
if (fs.lstatSyAll 9 scripts are listed above. The source is inlined for 6 of them, starting with whatever hooks.json actually runs. See all of them in the repo.
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Make AI output sound human. Strips AI-isms (sycophancy, stock vocab, hedging stacks, em-dash pileups), preserves code/URLs/headings. Plugin for Claude Code, Cursor, Windsurf, Codex, Cline, Copilot, Gemini.
Lets Claude watch a video from a URL or local file by pulling timestamped frames and a transcript. Works in Claude Code, and the watch skill downloads with yt-dlp and extracts frames with ffmpeg.
Gives Claude Code users access to MeiGen for discovering inspiration and creating images and videos with AI models. It also offers skills for product photoshoot planning, social video thumbnails, and visual creative direction.
Edits CapCut and JianYing video projects from the terminal or an LLM agent, covering subtitles, timing, speed, volume, templates, animations, and cutting long videos into shorts. The result opens in CapCut with every track still editable.
Turns a single line of story text into a finished short drama, covering the script, storyboards, character-consistent video and voiceover. Works with the AI model providers you already use, so you skip assembling each step by hand.
A Claude Code plugin for planning and producing AI-generated music albums, with skills for album concepts, tracklists, progress dashboards, idea tracking, and album artwork prompts.
A skill that builds AI teaching personas of specific Buddhist masters, grounded in Buddhist scripture texts and tested for fidelity to their teachings. It includes ethics gates for boundaries on what the persona will say.
Gives journalists and researchers ready-made guidance for fact-checking, source verification, interview prep, story pitches and data journalism, plus accessibility audits for news sites. Works in Claude Code and Codex.
A local video studio for your coding agent. Describe a video in one sentence: your agent directs, your machine renders. Claude Code, Codex, Cursor, Devin and more. No API keys, no uploads.