Skip to content
Cloud & Infrastructure
Hook

Hooks

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

From plugin
vercel
24750 skills3 agents4 commands2 hooks
+1
Install
> /plugin marketplace add vercel-labs/vercel-plugin
> /plugin install vercel-plugin@vercel

Ships with vercel. Installing the plugin gets these hooks.

What fires, and when

SessionStart

Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.

  • Matchesstartup|resume|clear|compactnode "${CLAUDE_PLUGIN_ROOT}/hooks/session-start-seen-skills.mjs"node "${CLAUDE_PLUGIN_ROOT}/hooks/session-start-profiler.mjs"node "${CLAUDE_PLUGIN_ROOT}/hooks/inject-claude-md.mjs"

SessionEnd

  • node "${CLAUDE_PLUGIN_ROOT}/hooks/session-end-cleanup.mjs"
Read hooks/hooks.json

Where it lives

  • hooks/compat.mjsGitHub
    Read the script
    // hooks/src/compat.mts
    import { appendFileSync } from "fs";
    var cursorSessionEnv = /* @__PURE__ */ new Map();
    var currentHookEventName;
    function isRecord(value) {
      return typeof value === "object" && value !== null && !Array.isArray(value);
    }
    function readString(value) {
      return typeof value === "string" ? value : void 0;
    }
    function readRecord(value) {
      return isRecord(value) ? value : void 0;
    }
    function readWorkspaceRoot(raw) {
      if (!Array.isArray(raw.workspace_roots)) return void 0;
      const firstRoot = raw.workspace_roots[0];
      return typeof firstRoot === "string" ? firstRoot : void 0;
    }
    function normalizeToolOutputValue(value) {
      if (typeof value === "undefined") return void 0;
      if (typeof value === "string") return value;
      try {
        return JSON.stringify(value);
      } catch {
        return String(value);
      }
    }
    function escapeShellEnvValue(value) {
      return value.replace(/(["\\$`])/g, "\\$1");
    }
    function drainCursorSessionEnv() {
      if (cursorSessionEnv.size === 0) return void 0;
      const env = Object.fromEntries(cursorSessionEnv);
      cursorSessionEnv.clear();
      return env;
    }
    function detectPlatform(raw) {
      if ("conversation_id" in raw || "workspace_roots" in raw || "cursor_version" in raw) {
        return "cursor";
      }
      return "claude-code";
    }
    function normalizeInput(raw) {
      const platform = detectPlatform(raw);
      const sessionId = readString(raw.session_id ?? raw.conversation_id) ?? "";
      const cwd = readString(raw.cwd) ?? readWorkspaceRoot(raw) ?? process.env.CURSOR_PROJECT_DIR ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
      const hookEvent = readString(raw.hook_event_name) ?? "";
      const toolOutput = normalizeToolOutputValue(raw.tool_output ?? raw.tool_response);
      currentHookEventName = hookEvent || void 0;
      return {
        platform,
        sessionId,
        cwd,
        hookEvent,
        toolName: readString(raw.tool_name),
        toolInput: readRecord(raw.tool_input),
        toolOutput,
        prompt: readString(raw.prompt),
        raw
      };
    }
    function formatOutput(platform, internal) {
      if (platform === "cursor") {
        const env = {
          ...drainCursorSessionEnv() ?? {},
          ...internal.env ?? {}
        };
        const output = {};
        if (typeof internal.additionalContext !== "undefined") {
          output.additional_context = internal.additionalContext;
        }
        if (typeof internal.permission !== "undefined") {
          output.permission = internal.permission;
        }
        if (Object.keys(env).length > 0) {
          output.env = env;
        }
        if (typeof internal.userMessage !== "undefined") {
          output.user_message = internal.userMessage;
        }
        return output;
      }
      const hookSpecificOutput = {};
      if (typeof internal.additionalContext !== "undefined") {
        if (currentHookEventName) {
          hookSpecificOutput.hookEventName = currentHookEventName;
        }
        hookSpecificOutput.additionalContext = internal.additionalContext;
      }
      if (typeof internal.permission !== "undefined") {
        if (currentHookEventName) {
          hookSpecificOutput.hookEventName = currentHookEventName;
        }
        hookSpecificOutput.permissionDecision = internal.permission;
      }
      if (Object.keys(hookSpecificOutput).length === 0) {
        return {};
      }
      return { hookSpecificOutput };
    }
    function getEnvFilePath() {
      return process.env.CLAUDE_ENV_FILE || null;
    }
    function setSessionEnv(platform, key, value) {
      if (platform === "cursor") {
        cursorSessionEnv.set(key, value);
        return;
      }
      const envFile = getEnvFilePath();
      if (!envFile) return;
      appendFileSync(envFile, `export ${key}="${escapeShellEnvValue(value)}"
    `);
    }
    function getProjectRoot() {
      return process.env.CLAUDE_PROJECT_ROOT ?? process.env.CURSOR_PROJECT_DIR ?? process.cwd();
    }
    export {
      detectPlatform,
      formatOutput,
      getEnvFilePath,
      getProjectRoot,
      normalizeInput,
      setSessionEnv
    };
    
  • hooks/compat.test.tsGitHub
    Read the script
    import { afterEach, describe, expect, it } from "bun:test"
    import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
    import { tmpdir } from "node:os"
    import { join } from "node:path"
    import {
      detectPlatform,
      formatOutput,
      getEnvFilePath,
      getProjectRoot,
      normalizeInput,
      setSessionEnv,
    } from "./src/compat.mts"
    
    const originalClaudeEnvFile = process.env.CLAUDE_ENV_FILE
    const originalClaudeProjectDir = process.env.CLAUDE_PROJECT_DIR
    const originalClaudeProjectRoot = process.env.CLAUDE_PROJECT_ROOT
    const originalCursorProjectDir = process.env.CURSOR_PROJECT_DIR
    
    const tempDirs: string[] = []
    
    function restoreEnv(key: "CLAUDE_ENV_FILE" | "CLAUDE_PROJECT_DIR" | "CLAUDE_PROJECT_ROOT" | "CURSOR_PROJECT_DIR", value: string | undefined): void {
      if (typeof value === "string") {
        process.env[key] = value
        return
      }
    
      delete process.env[key]
    }
    
    afterEach(() => {
      restoreEnv("CLAUDE_ENV_FILE", originalClaudeEnvFile)
      restoreEnv("CLAUDE_PROJECT_DIR", originalClaudeProjectDir)
      restoreEnv("CLAUDE_PROJECT_ROOT", originalClaudeProjectRoot)
      restoreEnv("CURSOR_PROJECT_DIR", originalCursorProjectDir)
      normalizeInput({})
      formatOutput("cursor", {})
    
      while (tempDirs.length > 0) {
        rmSync(tempDirs.pop() as string, { recursive: true, force: true })
      }
    })
    
    describe("compat", () => {
      it("test_detectPlatform_returns_cursor_when_cursor_fields_are_present", () => {
        expect(detectPlatform({ conversation_id: "cursor-conversation" })).toBe("cursor")
        expect(detectPlatform({ workspace_roots: ["/tmp/project"] })).toBe("cursor")
        expect(detectPlatform({ cursor_version: "1.0.0" })).toBe("cursor")
        expect(detectPlatform({ session_id: "claude-session" })).toBe("claude-code")
      })
    
      it("test_normalizeInput_maps_cursor_and_claude_payloads_with_platform_fallbacks", () => {
        process.env.CURSOR_PROJECT_DIR = "/tmp/cursor-project"
        process.env.CLAUDE_PROJECT_DIR = "/tmp/claude-project"
    
        const cursorInput = normalizeInput({
          conversation_id: "cursor-conversation",
          hook_event_name: "PostToolUse",
          tool_name: "Edit",
          tool_input: { file_path: "app/page.tsx" },
          tool_output: "{\"ok\":true}",
        })
    
        expect(cursorInput).toEqual({
          platform: "cursor",
          sessionId: "cursor-conversation",
          cwd: "/tmp/cursor-project",
          hookEvent: "PostToolUse",
          toolName: "Edit",
          toolInput: { file_path: "app/page.tsx" },
          toolOutput: "{\"ok\":true}",
          prompt: undefined,
          raw: {
            conversation_id: "cursor-conversation",
            hook_event_name: "PostToolUse",
            tool_name: "Edit",
            tool_input: { file_path: "app/page.tsx" },
            tool_output: "{\"ok\":true}",
          },
        })
    
        const claudeInput = normalizeInput({
          session_id: "claude-session",
          hook_event_name: "PostToolUse",
          tool_response: { status: "done" },
          prompt: "Summarize the diff",
        })
    
        expect(claudeInput.platform).toBe("claude-code")
        expect(claudeInput.sessionId).toBe("claude-session")
        expect(claudeInput.cwd).toBe("/tmp/cursor-project")
        expect(claudeInput.toolOutput).toBe("{\"status\":\"done\"}")
        expect(claudeInput.prompt).toBe("Summarize the diff")
      })
    
      it("test_formatOutput_emits_claude_shape_and_appends_env_exports_when_requested", () => {
        const tempDir = mkdtempSync(join(tmpdir(), "compat-claude-"))
        const envFile = join(tempDir, "claude.env")
        tempDirs.push(tempDir)
        writeFileSync(envFile, "", "utf-8")
    
        process.env.CLAUDE_ENV_FILE = envFile
        process.env.CLAUDE_PROJECT_ROOT = "/tmp/claude-root"
    
        normalizeInput({
          session_id: "claude-session",
          hook_event_name: "PreToolUse",
        })
    
        setSessionEnv("claude-code", "VERCEL_PLUGIN_TEST", '$value"quoted`')
    
        expect(getEnvFilePath()).toBe(envFile)
        expect(getProjectRoot()).toBe("/tmp/claude-root")
    
        const content = readFileSync(envFile, "utf-8")
        expect(content).toContain('export VERCEL_PLUGIN_TEST="\\$value\\"quoted\\`"\n')
        expect(formatOutput("claude-code", {})).toEqual({})
    
        expect(
          formatOutput("claude-code", {
            additionalContext: "Use the repo root",
            permission: "deny",
            userMessage: "ignored by Claude",
          }),
        ).toEqual({
          hookSpecificOutput: {
            hookEventName: "PreToolUse",
            additionalContext: "Use the repo root",
            permissionDecision: "deny",
          },
        })
      })
    
      it("test_formatOutput_emits_cursor_shape_and_drains_session_env_cache", () => {
        process.env.CURSOR_PROJECT_DIR = "/tmp/cursor-root"
    
        normalizeInput({
          conversation_id: "cursor-conversation",
          hook_event_name: "PostToolUse",
        })
    
        setSessionEnv("cursor", "CURSOR_ONLY", "1")
        expect(getProjectRoot()).toBe("/tmp/cursor-root")
    
        expect(
          formatOutput("cursor", {
            additionalContext: "Run targeted tests",
            permission: "allow",
            env: { INLINE_ENV: "2" },
            userMessage: "Proceed",
          }),
        ).toEqual({
          additional_context: "Run targeted tests",
          permission: "allow",
          env: {
            CURSOR_ONLY: "1",
            INLINE_ENV: "2",
          },
          user_message: "Proceed",
        })
    
        expect(formatOutput("cursor", {})).toEqual({})
      })
    })
    
  • hooks/hook-env.mjsGitHub
  • hooks/inject-claude-md.mjsRunsGitHub
    Read the script
    #!/usr/bin/env node
    
    // hooks/src/inject-claude-md.mts
    import { existsSync, readFileSync } from "fs";
    import { join, resolve } from "path";
    import { fileURLToPath } from "url";
    import { formatOutput } from "./compat.mjs";
    import { pluginRoot, safeReadFile } from "./hook-env.mjs";
    import { hasSessionStartActivationMarkers, isGreenfieldDirectory } from "./session-start-activation.mjs";
    var GREENFIELD_CONTEXT = `<!-- vercel-plugin:greenfield-execution -->
    ## Greenfield execution mode
    
    This directory is empty.
    Do not stop in planning mode or spin up a read-only planning subagent.
    Choose sensible defaults immediately.
    Start executing with real tool calls.
    Use non-interactive scaffolding commands (--yes) where available.
    Only ask follow-up questions when blocked by missing credentials or irreversible decisions.`;
    function parseInjectClaudeMdInput(raw) {
      try {
        if (!raw.trim()) return null;
        return JSON.parse(raw);
      } catch {
        return null;
      }
    }
    function detectInjectClaudeMdPlatform(input, _env = process.env) {
      if (input && ("conversation_id" in input || "cursor_version" in input)) {
        return "cursor";
      }
      return "claude-code";
    }
    function buildInjectClaudeMdParts(content, env = process.env, knowledgeUpdate = null, greenfield = env.VERCEL_PLUGIN_GREENFIELD === "true") {
      const parts = [];
      if (content !== null) {
        parts.push(content);
      }
      if (knowledgeUpdate !== null) {
        parts.push(knowledgeUpdate);
      }
      if (greenfield) {
        parts.push(GREENFIELD_CONTEXT);
      }
      return parts;
    }
    function formatInjectClaudeMdOutput(platform, content) {
      if (platform === "cursor") {
        return JSON.stringify(formatOutput(platform, { additionalContext: content }));
      }
      return content;
    }
    function resolveInjectClaudeMdProjectRoot(env = process.env) {
      return env.CLAUDE_PROJECT_ROOT ?? env.CURSOR_PROJECT_DIR ?? process.cwd();
    }
    function stripFrontmatter(content) {
      const match = content.match(/^---\n[\s\S]*?\n---\n?([\s\S]*)$/);
      return match ? match[1].trim() : content.trim();
    }
    function main() {
      const input = parseInjectClaudeMdInput(readFileSync(0, "utf8"));
      const platform = detectInjectClaudeMdPlatform(input);
      const projectRoot = resolveInjectClaudeMdProjectRoot();
      const isGreenfield = isGreenfieldDirectory(projectRoot);
      const greenfieldOverride = process.env.VERCEL_PLUGIN_GREENFIELD === "true";
      const shouldActivate = isGreenfield || greenfieldOverride || !existsSync(projectRoot) || hasSessionStartActivationMarkers(projectRoot);
      if (!shouldActivate) {
        if (platform === "cursor") {
          process.stdout.write(JSON.stringify(formatOutput(platform, {})));
        }
        return;
      }
      const thinSessionContext = safeReadFile(join(pluginRoot(), "vercel-session.md"));
      const knowledgeUpdateRaw = safeReadFile(join(pluginRoot(), "skills", "knowledge-update", "SKILL.md"));
      const knowledgeUpdate = knowledgeUpdateRaw !== null ? stripFrontmatter(knowledgeUpdateRaw) : null;
      const parts = buildInjectClaudeMdParts(
        thinSessionContext,
        process.env,
        knowledgeUpdate,
        isGreenfield || greenfieldOverride
      );
      if (parts.length === 0) {
        return;
      }
      process.stdout.write(formatInjectClaudeMdOutput(platform, parts.join("\n\n")));
    }
    var INJECT_CLAUDE_MD_ENTRYPOINT = fileURLToPath(import.meta.url);
    var isInjectClaudeMdEntrypoint = process.argv[1] ? resolve(process.argv[1]) === INJECT_CLAUDE_MD_ENTRYPOINT : false;
    if (isInjectClaudeMdEntrypoint) {
      main();
    }
    export {
      buildInjectClaudeMdParts,
      detectInjectClaudeMdPlatform,
      formatInjectClaudeMdOutput,
      parseInjectClaudeMdInput
    };
    
  • hooks/lexical-index.mjsGitHub
  • hooks/lexical-index.test.tsGitHub
  • hooks/logger.mjsGitHub
  • hooks/patterns.mjsGitHub
  • hooks/platform-hook-compat.test.tsGitHub
  • hooks/pretooluse-skill-inject.mjsGitHub
  • hooks/prompt-analysis.mjsGitHub
  • hooks/prompt-patterns.mjsGitHub
  • hooks/session-end-cleanup.mjsRunsGitHub
    Read the script
    #!/usr/bin/env node
    
    // hooks/src/session-end-cleanup.mts
    import { createHash } from "crypto";
    import { readdirSync, readFileSync, rmSync, unlinkSync } from "fs";
    import { tmpdir } from "os";
    import { join, resolve } from "path";
    import { fileURLToPath } from "url";
    var SAFE_SESSION_ID_RE = /^[a-zA-Z0-9_-]+$/;
    function tempSessionIdSegment(sessionId) {
      if (SAFE_SESSION_ID_RE.test(sessionId)) {
        return sessionId;
      }
      return createHash("sha256").update(sessionId).digest("hex");
    }
    function removeFileIfPresent(path) {
      try {
        unlinkSync(path);
      } catch {
      }
    }
    function removeDirIfPresent(path) {
      try {
        rmSync(path, { recursive: true, force: true });
      } catch {
      }
    }
    function parseSessionEndHookInput(raw) {
      try {
        if (!raw.trim()) return null;
        return JSON.parse(raw);
      } catch {
        return null;
      }
    }
    function normalizeSessionEndSessionId(input) {
      if (!input) return null;
      const sessionId = input.session_id ?? input.conversation_id ?? "";
      return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : null;
    }
    function parseSessionIdFromStdin() {
      return normalizeSessionEndSessionId(parseSessionEndHookInput(readFileSync(0, "utf8")));
    }
    function main() {
      const sessionId = parseSessionIdFromStdin();
      if (sessionId === null) {
        process.exit(0);
      }
      const tempRoot = tmpdir();
      const prefix = `vercel-plugin-${tempSessionIdSegment(sessionId)}-`;
      let entries = [];
      try {
        entries = readdirSync(tempRoot).filter((name) => name.startsWith(prefix));
      } catch {
      }
      for (const entry of entries) {
        const fullPath = join(tempRoot, entry);
        if (entry.endsWith(".d")) {
          removeDirIfPresent(fullPath);
        } else {
          removeFileIfPresent(fullPath);
        }
      }
      process.exit(0);
    }
    var SESSION_END_CLEANUP_ENTRYPOINT = fileURLToPath(import.meta.url);
    var isSessionEndCleanupEntrypoint = process.argv[1] ? resolve(process.argv[1]) === SESSION_END_CLEANUP_ENTRYPOINT : false;
    if (isSessionEndCleanupEntrypoint) {
      main();
    }
    export {
      normalizeSessionEndSessionId,
      parseSessionEndHookInput
    };
    
  • hooks/session-hooks-platform-compat.test.tsGitHub
  • hooks/session-start-activation.mjsGitHub
  • hooks/session-start-profiler-platform.test.tsGitHub
  • hooks/session-start-profiler.mjsRunsGitHub
    Read the script
    var __create = Object.create;
    var __defProp = Object.defineProperty;
    var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
    var __getOwnPropNames = Object.getOwnPropertyNames;
    var __getProtoOf = Object.getPrototypeOf;
    var __hasOwnProp = Object.prototype.hasOwnProperty;
    var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
      get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
    }) : x)(function(x) {
      if (typeof require !== "undefined") return require.apply(this, arguments);
      throw Error('Dynamic require of "' + x + '" is not supported');
    });
    var __commonJS = (cb, mod) => function __require2() {
      return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
    };
    var __copyProps = (to, from, except, desc) => {
      if (from && typeof from === "object" || typeof from === "function") {
        for (let key of __getOwnPropNames(from))
          if (!__hasOwnProp.call(to, key) && key !== except)
            __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
      }
      return to;
    };
    var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
      // If the importer is in node compatibility mode or this is not an ESM
      // file that has been converted to a CommonJS file using a Babel-
      // compatible transform (i.e. "__esModule" has not been set), then set
      // "default" to the CommonJS "module.exports" for node compatibility.
      isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
      mod
    ));
    
    // node_modules/detect-agent/dist/index.js
    var require_dist = __commonJS({
      "node_modules/detect-agent/dist/index.js"(exports, module) {
        "use strict";
        var __defProp2 = Object.defineProperty;
        var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
        var __getOwnPropNames2 = Object.getOwnPropertyNames;
        var __hasOwnProp2 = Object.prototype.hasOwnProperty;
        var __export = (target, all) => {
          for (var name in all)
            __defProp2(target, name, { get: all[name], enumerable: true });
        };
        var __copyProps2 = (to, from, except, desc) => {
          if (from && typeof from === "object" || typeof from === "function") {
            for (let key of __getOwnPropNames2(from))
              if (!__hasOwnProp2.call(to, key) && key !== except)
                __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
          }
          return to;
        };
        var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
        var index_exports = {};
        __export(index_exports, {
          KNOWN_AGENTS: () => KNOWN_AGENTS,
          determineAgent: () => determineAgent
        });
        module.exports = __toCommonJS(index_exports);
        var agents_default = {
          $schema: "./agents.schema.json",
          version: 1,
          description: "Language-agnostic specification for detecting AI agents and automated development environments. Agents are evaluated in array order; the first agent whose `match` condition is satisfied wins. Every agent's `match` is a combinator (`anyOf`/`allOf`) whose `conditions` are evaluated as a tree: combinators nest, and `env_set`/`env_value`/`file_exists` are leaf checks. See agents.schema.json for the full structure.",
          aiAgentVar: "AI_AGENT",
          agents: [
            {
              key: "CURSOR",
              name: "cursor",
              match: {
                type: "anyOf",
                conditions: [{ type: "env_set", name: "CURSOR_TRACE_ID" }]
              }
            },
            {
              key: "CURSOR_CLI",
              name: "cursor-cli",
              match: {
                type: "anyOf",
                conditions: [
                  { type: "env_set", name: "CURSOR_AGENT" },
                  {
                    type: "env_value",
                    name: "CURSOR_EXTENSION_HOST_ROLE",
                    value: "agent-exec"
                  }
                ]
              }
            },
            {
              key: "KIMI",
              name: "kimi",
              description: "Kimi Code plugin hooks. KIMI_CODE_HOME may be configured outside an active Kimi session, so detection uses the plugin-scoped KIMI_PLUGIN_ROOT marker.",
              match: {
                type: "anyOf",
                conditions: [{ type: "env_set", name: "KIMI_PLUGIN_ROOT" }]
              }
            },
            {
              key: "GROK",
              name: "grok",
              description: "Grok Build plugin hooks. Evaluated before Claude Code because Grok supports Claude Code plugins and may expose compatibility markers.",
              match: {
                type: "anyOf",
                conditions: [
                  { type: "env_set", name: "GROK_PLUGIN_ROOT" },
                  { type: "env_set", name: "GROK_PLUGIN_DATA" }
                ]
              }
            },
            {
              key: "GEMINI",
              name: "gemini_cli",
              match: {
                type: "anyOf",
                conditions: [{ type: "env_set", name: "GEMINI_CLI" }]
              }
            },
            {
              key: "CLINE",
              name: "cline",
              match: {
                type: "anyOf",
                conditions: [{ type: "env_set", name: "CLINE_ACTIVE" }]
              }
            },
            {
              key: "CODEX",
              name: "codex_cli",
              match: {
                type: "anyOf",
                conditions: [
                  { type: "env_set", name: "CODEX_SANDBOX" },
                  { type: "env_set", name: "CODEX_CI" },
                  { type: "env_set", name: "CODEX_THREAD_ID" },
                  { type: "env_set", name: "CODEX_SANDBOX_NETWORK_DISABLED" }
                ]
              }
            },
            {
              key: "ANTIGRAVITY",
              name: "antigravity",
              match: {
                type: "anyOf",
                conditions: [
                  { type: "env_set", name: "ANTIGRAVITY_AGENT" },
                  { type: "env_set", name: "ANTIGRAVITY_CLI_ALIAS" }
                ]
              }
          
  • hooks/session-start-seen-skills.mjsRunsGitHub
    Read the script
    #!/usr/bin/env node
    
    // hooks/src/session-start-seen-skills.mts
    import { readFileSync } from "fs";
    import { resolve } from "path";
    import { fileURLToPath } from "url";
    import {
      formatOutput
    } from "./compat.mjs";
    import {
      removeAllSessionDedupArtifacts
    } from "./hook-env.mjs";
    import { createLogger } from "./logger.mjs";
    var CONTEXT_CLEARING_EVENTS = /* @__PURE__ */ new Set(["clear", "compact"]);
    function parseSessionStartSeenSkillsInput(raw) {
      try {
        if (!raw.trim()) return null;
        return JSON.parse(raw);
      } catch {
        return null;
      }
    }
    function detectSessionStartSeenSkillsPlatform(input, _env = process.env) {
      if (input && ("conversation_id" in input || "cursor_version" in input)) {
        return "cursor";
      }
      return "claude-code";
    }
    function formatSessionStartSeenSkillsCursorOutput() {
      return JSON.stringify(formatOutput("cursor", {
        env: {
          VERCEL_PLUGIN_SEEN_SKILLS: ""
        }
      }));
    }
    function resetDedupStateForSession(sessionId) {
      return removeAllSessionDedupArtifacts(sessionId);
    }
    function main() {
      const log = createLogger();
      const input = parseSessionStartSeenSkillsInput(readFileSync(0, "utf8"));
      const platform = detectSessionStartSeenSkillsPlatform(input);
      if (platform === "cursor") {
        process.stdout.write(formatSessionStartSeenSkillsCursorOutput());
        return;
      }
      const hookEvent = input?.hook_event_name ?? "";
      const sessionId = input?.session_id ?? "";
      const resetTriggered = CONTEXT_CLEARING_EVENTS.has(hookEvent) && !!sessionId;
      let removedFiles = 0;
      let removedDirs = 0;
      if (resetTriggered) {
        const result = resetDedupStateForSession(sessionId);
        removedFiles = result.removedFiles;
        removedDirs = result.removedDirs;
      }
      log.debug("session-start-seen-skills:decision", {
        event: hookEvent || "unknown",
        sessionId: sessionId || "none",
        resetTriggered,
        removedFiles,
        removedDirs
      });
    }
    var SESSION_START_SEEN_SKILLS_ENTRYPOINT = fileURLToPath(import.meta.url);
    var isSessionStartSeenSkillsEntrypoint = process.argv[1] ? resolve(process.argv[1]) === SESSION_START_SEEN_SKILLS_ENTRYPOINT : false;
    if (isSessionStartSeenSkillsEntrypoint) {
      main();
    }
    export {
      detectSessionStartSeenSkillsPlatform,
      formatSessionStartSeenSkillsCursorOutput,
      parseSessionStartSeenSkillsInput,
      resetDedupStateForSession
    };
    
  • hooks/setup-telemetry.mjsGitHub
  • hooks/shared-contractions.mjsGitHub
  • hooks/skill-map-frontmatter.mjsGitHub
  • hooks/skill-map-frontmatter.test.tsGitHub
  • hooks/stemmer.mjsGitHub
  • hooks/telemetry.mjsGitHub
  • hooks/tsup.config.tsGitHub
  • hooks/unified-ranker.mjsGitHub
  • hooks/user-prompt-submit-skill-inject.mjsGitHub
  • hooks/user-prompt-submit-skill-inject.test.tsGitHub
  • hooks/vercel-config.mjsGitHub
  • hooks/vercel-context.mjsGitHub

All 30 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.

Ships withvercel

Comprehensive Vercel ecosystem plugin — relational knowledge graph, skills for every major product, specialized agents, and Vercel conventions. Turns any AI agent into a Vercel expert.

Get the whole plugin