Skip to content
Content
Hook

Hooks

What skills runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
skills
2645 skills2 commands1 hook

Claude Code

> /plugin marketplace add Mikefluff/skills
> /plugin install mikefluff-skills@mikefluff

Where it lives

  • hooks/skills-update-banner.jsGitHub
    Read the script
    #!/usr/bin/env node
    // skills-update-banner.js — Claude Code status-line hook that ambient-checks
    // whether a newer Mikefluff/skills release is available.
    //
    // Behaviour:
    //   • Reads ~/.claude/skills/.skills-collection.json to find the local version.
    //   • Fetches the latest GitHub release with a 24-hour cache (tag + topline
    //     extracted from the release body).
    //   • If a newer version exists, appends a quiet single-line banner to the
    //     status-line input: " · skills v0.2.0→0.3.0 +3 skills (topline)".
    //   • Never prompts, never modifies files, never installs anything. The user
    //     follows up by invoking the `/skills-update` skill on demand.
    //
    // Installation:
    //   bash scripts/install-hook.sh    # idempotent
    //   — OR hand-edit ~/.claude/settings.json:
    //   {
    //     "statusLine": {
    //       "type": "command",
    //       "command": "node /absolute/path/to/hooks/skills-update-banner.js"
    //     }
    //   }
    //
    // The hook fails open: any error (network, parse, missing file) is silently
    // swallowed and the original status line passes through unchanged.
    
    const fs = require('fs');
    const os = require('os');
    const path = require('path');
    const https = require('https');
    
    const REPO = 'Mikefluff/skills';
    const PREFIX = path.join(os.homedir(), '.claude', 'skills');
    const MARKER_PATH = path.join(PREFIX, '.skills-collection.json');
    const CACHE_PATH = path.join(os.tmpdir(), 'skills-update-banner-cache.json');
    const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24h
    const HTTP_TIMEOUT_MS = 1500;
    const MAX_BANNER_LEN = 80;
    
    const SILENT_PASSTHROUGH = (line) => {
      process.stdout.write(line || '');
      process.exit(0);
    };
    
    function readStdin() {
      return new Promise((resolve) => {
        let data = '';
        if (process.stdin.isTTY) return resolve('');
        process.stdin.setEncoding('utf8');
        process.stdin.on('data', (chunk) => (data += chunk));
        process.stdin.on('end', () => resolve(data));
        setTimeout(() => resolve(data), 200).unref();
      });
    }
    
    function safeRead(p) {
      try { return fs.readFileSync(p, 'utf8'); } catch { return null; }
    }
    
    function readLocalMarker() {
      const raw = safeRead(MARKER_PATH);
      if (!raw) return null;
      try {
        const obj = JSON.parse(raw);
        return {
          version: typeof obj.version === 'string' ? obj.version : null,
          skills: Array.isArray(obj.skills) ? obj.skills : [],
        };
      } catch {
        return null;
      }
    }
    
    function readCache() {
      const raw = safeRead(CACHE_PATH);
      if (!raw) return null;
      try {
        const obj = JSON.parse(raw);
        if (typeof obj.cachedAt !== 'number' || typeof obj.version !== 'string') return null;
        if (Date.now() - obj.cachedAt > CACHE_TTL_MS) return null;
        return obj;
      } catch {
        return null;
      }
    }
    
    function writeCache(payload) {
      try {
        fs.writeFileSync(
          CACHE_PATH,
          JSON.stringify({ cachedAt: Date.now(), ...payload }, null, 2),
        );
      } catch {
        // best-effort cache
      }
    }
    
    function ghRequest(reqPath) {
      return new Promise((resolve) => {
        const req = https.request(
          {
            host: 'api.github.com',
            path: reqPath,
            method: 'GET',
            headers: {
              'User-Agent': 'skills-update-banner/2.0',
              'Accept': 'application/vnd.github+json',
            },
            timeout: HTTP_TIMEOUT_MS,
          },
          (res) => {
            let body = '';
            res.on('data', (c) => (body += c));
            res.on('end', () => {
              try { resolve(JSON.parse(body)); } catch { resolve(null); }
            });
          },
        );
        req.on('error', () => resolve(null));
        req.on('timeout', () => { req.destroy(); resolve(null); });
        req.end();
      });
    }
    
    async function fetchLatestRelease() {
      const obj = await ghRequest(`/repos/${REPO}/releases/latest`);
      if (!obj || typeof obj.tag_name !== 'string') return null;
      return {
        version: obj.tag_name.replace(/^v/, ''),
        body: typeof obj.body === 'string' ? obj.body : '',
      };
    }
    
    async function fetchRemoteSkillsList(version) {
      // Pull skills.json from the tag's tree to count delta.
      const tag = `v${version}`;
      const obj = await ghRequest(`/repos/${REPO}/contents/skills.json?ref=${encodeURIComponent(tag)}`);
      if (!obj || typeof obj.content !== 'string') return null;
      try {
        const decoded = Buffer.from(obj.content, 'base64').toString('utf8');
        const parsed = JSON.parse(decoded);
        return Array.isArray(parsed.skills) ? parsed.skills.map((s) => s.name) : null;
      } catch {
        return null;
      }
    }
    
    function semverGreater(a, b) {
      const pa = a.split('.').map((n) => parseInt(n, 10) || 0);
      const pb = b.split('.').map((n) => parseInt(n, 10) || 0);
      for (let i = 0; i < 3; i++) {
        if ((pa[i] || 0) > (pb[i] || 0)) return true;
        if ((pa[i] || 0) < (pb[i] || 0)) return false;
      }
      return false;
    }
    
    function extractTopline(body) {
      if (!body) return '';
      // First bulleted line. Strip markdown emphasis + leading "**`name`**." prefix.
      const lines = body.split('\n').map((l) => l.trim()).filter(Boolean);
      for (const line of lines) {
        if (!line.startsWith('-') && !line.startsWith('*')) continue;
        let stripped = line.replace(/^[*-]\s+/, '');
        stripped = stripped.replace(/\*\*([^*]+)\*\*/g, '$1');
        stripped = stripped.replace(/`([^`]+)`/g, '$1');
        stripped = stripped.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
        if (stripped.length > 0) return stripped;
      }
      return '';
    }
    
    function clip(s, n) {
      if (s.length <= n) return s;
      return s.slice(0, n - 1) + '…';
    }
    
    (async () => {
      const upstream = await readStdin();
    
      const marker = readLocalMarker();
      if (!marker || !marker.version) return SILENT_PASSTHROUGH(upstream);
    
      let cached = readCache();
      let remoteVersion = cached ? cached.version : null;
      let remoteBody = cached ? cached.body : '';
      let remoteSkills = cached && Array.isArray(cached.skills) ? cached.skills : null;
    
      if (!remoteVersion) {
        const rel = await fetchLatestRelease();
        if (!rel) return SILENT_PASSTHROUGH(upstream);
        remoteVersion = rel.version;
        remoteBody = rel.body;
        remoteSkills = awa

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.

@mikefluff
Ships withskills

45 skills for Claude Code that make content — and refuse to let it read like a machine made it. Prose editing that strips the tells. Prompt engineering for 40+ image, video and music models, with optional one-command execution against the real APIs.

Stars on GitHub

Momentum

stars in total
26
Star history of Mikefluff/skills on GitHub, a chart by star-history.com

More plugins that ship hooks.

watch

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.

1 skill1 hook21k
@bradautomates@bradautomates
Install Plugin
meigen-ai-design-mcp

meigen-ai-design-mcp

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.

3 skills+41.8k
@jau123@jau123
Install Plugin
capcut-cli

capcut-cli

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.

1 skill1 hook1.1k
@renezander030@renezander030
Install Plugin
wind-comic

wind-comic

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.

7 skills11 hooks714
@chrischen667788@chrischen667788
Install Plugin
bitwize-music

bitwize-music

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.

53 skills+2648
@bitwize-music-studio@bitwize-music-studio
Install Plugin
master-skill

master-skill

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.

21 skills1 hook495
@xr843@xr843
Install Plugin
claude-skills-journalism

claude-skills-journali…

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.

72 skills+3451
@jamditis@jamditis
Install Plugin
showtime

showtime

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.

1 skill+2310
@faviovazquez@faviovazquez
Install Plugin

Questions about the hooks in skills.

Which coding agents can use the hooks in skills?
The hooks in skills install in Claude Code and Cursor: each runs Claude Code hooks from .claude/settings.json, so copy the hooks there.