> ## Documentation Index
> Fetch the complete documentation index at: https://docs.endorlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Endor Labs Agent Kit

> Ready-to-use Endor Labs security agents for AI coding assistants.

export const AgentKitInstaller = () => {
  const COPY_LABEL = 'Copy';
  const COPIED_LABEL = 'Copied';
  const COPY_FAILED_LABEL = 'Copy failed';
  const COPY_ERROR_SUFFIX = ':error';
  const COPY_FEEDBACK_TIMEOUT_MS = 2000;
  const COPY_KEY_INSTALL = 'install';
  const COPY_KEY_SETUP = 'setup';
  const COPY_KEY_RUN = 'run';
  const HEADLINE_COUNT = 4;
  const OTHER_CHOICE_ID = 'other';
  const OTHER_CHOICE_LABEL = 'Other agents';
  const AGENT_SELECT_ID = 'aki-agent-select';
  const AGENT_SELECT_LABEL = 'Select an agent';
  const SETUP_PROMPT = 'Use the endor-agent-kit-setup skill to check Endor Labs Agent Kit readiness.';
  const PLACEHOLDER_HINT = 'Replace placeholders such as <org> and <namespace> with your own values before running.';
  const RADIO_SELECTOR = 'button[role="radio"]';
  const BORDER_SUBTLE = 'rgba(38, 208, 124, 0.3)';
  const BORDER_STRONG = 'rgba(38, 208, 124, 0.7)';
  const HOSTS = [{
    id: 'claude-code',
    label: 'Claude Code',
    install: '/plugin marketplace add endorlabs/ai-plugins\n/plugin install endor-labs-agent-kit@endorlabs\n/reload-plugins\n/agents',
    invoke: agent => `@agent-${agent.id} ${agent.task}`
  }, {
    id: 'codex',
    label: 'OpenAI Codex',
    install: 'codex plugin marketplace add endorlabs/ai-plugins --sparse .agents/plugins --sparse plugins/codex/endor-labs-agent-kit',
    invoke: agent => `Use the ${agent.id} skill to ${agent.task}.`
  }, {
    id: 'gemini',
    label: 'Gemini CLI',
    install: 'git clone https://github.com/endorlabs/ai-plugins\ngemini extensions install ./ai-plugins/plugins/gemini/endor-labs-agent-kit\ngemini extensions list',
    invoke: agent => `@${agent.id} ${agent.task}`
  }, {
    id: 'antigravity',
    label: 'Antigravity CLI',
    install: 'git clone https://github.com/endorlabs/ai-plugins\ncd ai-plugins\nagy plugin validate ./plugins/antigravity/endor-labs-agent-kit\nagy plugin install ./plugins/antigravity/endor-labs-agent-kit\nagy plugin list',
    invoke: agent => `@${agent.id} ${agent.task}`
  }];
  const AGENTS = [{
    id: 'sca-remediation',
    label: 'SCA Remediation',
    task: 'check this repository for P0 SCA findings I can start remediating'
  }, {
    id: 'ai-sast-remediation',
    label: 'AI SAST Remediation',
    task: 'triage AI SAST findings for this repository'
  }, {
    id: 'configuration-automation',
    label: 'Configuration Automation',
    task: 'probe GitHub org <org> for Endor Labs monitored-branch onboarding gaps'
  }, {
    id: 'troubleshooting',
    label: 'Troubleshooting',
    task: 'diagnose this Endor Labs scan failure from redacted error text and read-only tenant evidence'
  }, {
    id: 'cicd-posture',
    label: 'CI/CD And Supply Chain Posture',
    task: 'assess CI/CD and supply chain posture for namespace <namespace>'
  }, {
    id: 'dependency-reviewer',
    label: 'Dependency Reviewer',
    task: 'assess whether to use npm lodash version 4.17.20'
  }, {
    id: 'oss-upgrade-investigator',
    label: 'OSS Upgrade Investigator',
    task: 'show the safest upgrade path for repository <owner>/<repo> package lodash'
  }, {
    id: 'vulnerability-explainer',
    label: 'Vulnerability Explainer',
    task: 'explain CVE-2021-44228 using verified Endor Labs evidence'
  }, {
    id: 'findings-browser',
    label: 'Findings Browser',
    task: 'show the critical and high reachable findings for namespace <namespace>'
  }, {
    id: 'malware-responder',
    label: 'Malware Responder',
    task: 'assess tenant exposure to the latest supply-chain malware intelligence'
  }, {
    id: 'remediation-planning',
    label: 'Remediation Planning',
    task: 'preview remediation options for this repository'
  }];
  const [selectedHostId, setSelectedHostId] = useState(HOSTS[0].id);
  const [agentChoice, setAgentChoice] = useState(AGENTS[0].id);
  const [otherAgentId, setOtherAgentId] = useState(() => AGENTS[HEADLINE_COUNT].id);
  const [copiedKey, setCopiedKey] = useState('');
  const [isDark, setIsDark] = useState(false);
  const hostGroupRef = useRef(null);
  const agentGroupRef = useRef(null);
  useEffect(() => {
    const checkTheme = () => {
      const root = document.documentElement;
      setIsDark(root.dataset.theme === 'dark' || root.classList.contains('dark'));
    };
    checkTheme();
    const observer = new MutationObserver(checkTheme);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['data-theme', 'class']
    });
    return () => observer.disconnect();
  }, []);
  useEffect(() => {
    if (!copiedKey || copiedKey.endsWith(COPY_ERROR_SUFFIX)) {
      return;
    }
    const timer = setTimeout(() => setCopiedKey(''), COPY_FEEDBACK_TIMEOUT_MS);
    return () => clearTimeout(timer);
  }, [copiedKey]);
  const errorKey = key => key + COPY_ERROR_SUFFIX;
  const markCopyError = key => setCopiedKey(errorKey(key));
  const handleCopy = (text, key) => {
    try {
      navigator.clipboard.writeText(text).then(() => setCopiedKey(key)).catch(() => markCopyError(key));
    } catch {
      markCopyError(key);
    }
  };
  const getCopyLabel = key => {
    if (copiedKey === key) {
      return COPIED_LABEL;
    }
    if (copiedKey === errorKey(key)) {
      return COPY_FAILED_LABEL;
    }
    return COPY_LABEL;
  };
  const getNextRadioIndex = (key, currentIndex, count) => {
    if (key === 'Home') {
      return 0;
    }
    if (key === 'End') {
      return count - 1;
    }
    if (key === 'ArrowRight' || key === 'ArrowDown') {
      return (currentIndex + 1) % count;
    }
    if (key === 'ArrowLeft' || key === 'ArrowUp') {
      return (currentIndex - 1 + count) % count;
    }
    return -1;
  };
  const focusRadio = (groupRef, index) => {
    const buttons = groupRef.current?.querySelectorAll(RADIO_SELECTOR);
    if (buttons && buttons[index]) {
      buttons[index].focus();
    }
  };
  const handleRadioKeydown = (event, items, selectedId, onSelect, groupRef) => {
    const currentIndex = items.findIndex(item => item.id === selectedId);
    const nextIndex = getNextRadioIndex(event.key, currentIndex, items.length);
    if (nextIndex < 0) {
      return;
    }
    event.preventDefault();
    onSelect(items[nextIndex].id);
    focusRadio(groupRef, nextIndex);
  };
  const backgroundColor = isDark ? '#0d1117' : '#ffffff';
  const backgroundSecondary = isDark ? '#161b22' : '#f6f8fa';
  const textColor = isDark ? '#e6edf3' : '#1f2937';
  const styles = {
    card: {
      background: backgroundColor,
      border: '1px solid ' + BORDER_SUBTLE,
      borderRadius: '16px',
      padding: '1.25rem',
      color: textColor
    },
    legend: {
      fontWeight: 700,
      fontSize: '0.9rem',
      margin: '0 0 0.5rem'
    },
    field: {
      border: '1px solid ' + BORDER_SUBTLE,
      borderRadius: '12px',
      padding: '0.75rem',
      margin: '0 0 1rem'
    },
    grid: {
      display: 'grid',
      gridTemplateColumns: 'repeat(4, 1fr)',
      gap: '0.5rem'
    },
    marker: {
      display: 'inline-block',
      width: '1.1em'
    },
    dropdownWrap: {
      display: 'flex',
      flexDirection: 'column',
      gap: '0.35rem',
      marginTop: '0.75rem'
    },
    dropdownLabel: {
      fontSize: '0.8rem',
      fontWeight: 600
    },
    select: {
      padding: '0.45rem 0.6rem',
      border: '1px solid ' + BORDER_STRONG,
      borderRadius: '8px',
      background: backgroundColor,
      color: textColor,
      fontSize: '0.8rem',
      fontWeight: 600
    },
    block: {
      background: backgroundSecondary,
      border: '1px solid ' + BORDER_SUBTLE,
      borderRadius: '12px',
      padding: '0.75rem',
      margin: '0 0 0.75rem'
    },
    blockHead: {
      display: 'flex',
      justifyContent: 'space-between',
      alignItems: 'center',
      marginBottom: '0.4rem',
      gap: '0.5rem'
    },
    blockLabel: {
      fontWeight: 700,
      fontSize: '0.85rem'
    },
    pre: {
      margin: 0,
      whiteSpace: 'pre-wrap',
      wordBreak: 'break-word',
      fontFamily: "Monaco, Menlo, 'Ubuntu Mono', monospace",
      fontSize: '0.8rem',
      lineHeight: 1.5
    },
    copyButton: {
      padding: '0.25rem 0.6rem',
      border: '1px solid ' + BORDER_STRONG,
      borderRadius: '8px',
      background: 'transparent',
      color: textColor,
      cursor: 'pointer',
      fontSize: '0.75rem',
      fontWeight: 600,
      flexShrink: 0
    },
    hint: {
      fontSize: '0.78rem',
      opacity: 0.8,
      margin: '0.25rem 0 0'
    }
  };
  const getChoiceStyle = selected => ({
    padding: '0.5rem 0.6rem',
    border: '1px solid ' + (selected ? BORDER_STRONG : BORDER_SUBTLE),
    borderRadius: '10px',
    background: selected ? backgroundSecondary : 'transparent',
    color: textColor,
    cursor: 'pointer',
    fontSize: '0.8rem',
    fontWeight: 600,
    textAlign: 'left'
  });
  const renderRadioGroup = (groupKey, groupLabel, items, selectedId, onSelect, groupRef, extra) => {
    const legendId = 'aki-' + groupKey + '-label';
    return <div style={styles.field}>
        <div id={legendId} style={styles.legend}>{groupLabel}</div>
        <div role="radiogroup" aria-labelledby={legendId} ref={groupRef} className="aki-grid" style={styles.grid} onKeyDown={event => handleRadioKeydown(event, items, selectedId, onSelect, groupRef)}>
          {items.map(item => {
      const selected = item.id === selectedId;
      return <button key={item.id} type="button" role="radio" aria-checked={selected} tabIndex={selected ? 0 : -1} className="aki-choice" onClick={() => onSelect(item.id)} style={getChoiceStyle(selected)}>
                <span aria-hidden="true" style={styles.marker}>{selected ? '✓' : ''}</span>{item.label}
              </button>;
    })}
        </div>
        {extra}
      </div>;
  };
  const renderAgentDropdown = otherAgents => <div style={styles.dropdownWrap}>
      <label htmlFor={AGENT_SELECT_ID} style={styles.dropdownLabel}>{AGENT_SELECT_LABEL}</label>
      <select id={AGENT_SELECT_ID} value={otherAgentId} style={styles.select} onChange={event => setOtherAgentId(event.target.value)}>
        {otherAgents.map(agent => <option key={agent.id} value={agent.id}>{agent.label}</option>)}
      </select>
    </div>;
  const renderCopyButton = (text, key, copyAriaLabel) => <button type="button" className="aki-copy" aria-label={copyAriaLabel} onClick={() => handleCopy(text, key)} style={styles.copyButton}>
      {getCopyLabel(key)}
    </button>;
  const renderCodeBlock = (label, code, key, copyAriaLabel) => <div style={styles.block}>
      <div style={styles.blockHead}>
        <span style={styles.blockLabel}>{label}</span>
        {renderCopyButton(code, key, copyAriaLabel)}
      </div>
      <pre style={styles.pre}><code>{code}</code></pre>
    </div>;
  const headlineAgents = AGENTS.slice(0, HEADLINE_COUNT);
  const otherAgents = AGENTS.slice(HEADLINE_COUNT);
  const agentChoiceItems = [...headlineAgents, {
    id: OTHER_CHOICE_ID,
    label: OTHER_CHOICE_LABEL
  }];
  const isOther = agentChoice === OTHER_CHOICE_ID;
  const selectedAgentId = isOther ? otherAgentId : agentChoice;
  const selectedHost = HOSTS.find(host => host.id === selectedHostId) || HOSTS[0];
  const selectedAgent = AGENTS.find(agent => agent.id === selectedAgentId) || AGENTS[0];
  const invokePrompt = selectedHost.invoke(selectedAgent);
  const installLabel = 'Install in ' + selectedHost.label;
  const runLabel = 'Run ' + selectedAgent.label;
  const agentExtra = isOther ? renderAgentDropdown(otherAgents) : null;
  return <div className="not-prose aki" style={{
    margin: '1rem 0',
    fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif'
  }}>
      <div className="aki-card" style={styles.card}>
        {renderRadioGroup('host', '1. Choose your host', HOSTS, selectedHostId, setSelectedHostId, hostGroupRef)}
        {renderRadioGroup('agent', '2. Choose a starting agent', agentChoiceItems, agentChoice, setAgentChoice, agentGroupRef, agentExtra)}
        {renderCodeBlock(installLabel, selectedHost.install, COPY_KEY_INSTALL, 'Copy the install command')}
        {renderCodeBlock('Set up your machine', SETUP_PROMPT, COPY_KEY_SETUP, 'Copy the setup prompt')}
        {renderCodeBlock(runLabel, invokePrompt, COPY_KEY_RUN, 'Copy the first-workflow prompt')}
        <p style={styles.hint}>{PLACEHOLDER_HINT}</p>
      </div>
    </div>;
};

export const YamlTable = ({children, data: propData, content}) => {
  const KV_RE = /^([A-Za-z][A-Za-z0-9_()/#\s-]+?):\s*(.+)$/;
  const INLINE_MD_RE = /(\[([^\]]+)\]\(([^)]+)\))|(`([^`]+)`)|(\*\*([^*]+)\*\*)|(\*([^*]+)\*)/g;
  const YES_RE = /^-yes-$/i;
  const NO_RE = /^-no-$/i;
  const LIMITED_RE = /^-(limited|partial)-$/i;
  const NA_RE = /^-(na|none)-$/i;
  const NA2_RE = /^-na2-$/i;
  const SIMPLE_TAG_RE = /(<br\s*\/?>)|(<p\s*\/?>)|(-note-)|(-warning-)/gi;
  const renderSuccessIcon = () => <svg viewBox="0 0 24 24" width="100%" height="100%" aria-hidden="true">
      <path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM10 17L5 12L6.41 10.59L10 14.17L17.59 6.58L19 8L10 17Z" fill="currentColor" />
    </svg>;
  const renderFailureIcon = () => <svg viewBox="0 0 24 24" width="100%" height="100%" aria-hidden="true">
      <path fillRule="evenodd" clipRule="evenodd" d="M11.9902 1.98633C12.5208 1.9864 13.0426 2.12743 13.501 2.39453C13.9581 2.66107 14.3372 3.04388 14.5986 3.50391L22.5967 17.5L22.6895 17.6738C22.8921 18.0851 22.9979 18.5387 22.9981 18.999C22.9981 19.5253 22.8605 20.0431 22.5977 20.499C22.3348 20.9549 21.9555 21.3332 21.5 21.5967C21.0445 21.8601 20.5272 21.9994 20.001 22H4.00001C3.47453 22.0031 2.95699 21.868 2.50001 21.6084C2.04032 21.3471 1.65712 20.9684 1.39063 20.5117C1.12431 20.055 0.983643 19.5355 0.982429 19.0068C0.981281 18.4793 1.11967 17.9611 1.38282 17.5039L9.38184 3.50391C9.64344 3.04359 10.023 2.66111 10.4805 2.39453C10.9388 2.12755 11.4598 1.98636 11.9902 1.98633ZM12 16.9004C11.3925 16.9004 10.9004 17.3925 10.9004 18C10.9004 18.6075 11.3925 19.0996 12 19.0996H12.0098C12.6173 19.0996 13.1104 18.6075 13.1104 18C13.1104 17.3925 12.6173 16.9004 12.0098 16.9004H12ZM12 5.90039C11.3925 5.9004 10.9004 6.39249 10.9004 7V13C10.9004 13.6075 11.3925 14.0996 12 14.0996C12.6075 14.0996 13.0996 13.6075 13.0996 13V7C13.0996 6.39249 12.6075 5.90039 12 5.90039Z" fill="currentColor" />
    </svg>;
  const renderPartialSuccessIcon = () => <svg viewBox="0 0 24 24" width="100%" height="100%" aria-hidden="true">
      <path d="M12 1C18.0751 1 23 5.92487 23 12C23 18.0751 18.0751 23 12 23C5.92487 23 1 18.0751 1 12C1 5.92487 5.92487 1 12 1ZM12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3ZM12 5C13.8565 5 15.6374 5.73705 16.9502 7.0498C18.263 8.36256 19 10.1435 19 12C19 13.8565 18.263 15.6374 16.9502 16.9502C15.6374 18.263 13.8565 19 12 19C11.4477 19 11 18.5523 11 18V6C11 5.73478 11.1054 5.4805 11.293 5.29297C11.4805 5.10543 11.7348 5 12 5Z" fill="currentColor" />
    </svg>;
  const renderPendingIcon = () => <svg viewBox="0 0 24 24" width="100%" height="100%" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
      <path d="M10.1 2.182a10 10 0 0 1 3.8 0" />
      <path d="M13.9 21.818a10 10 0 0 1-3.8 0" />
      <path d="M17.609 3.721a10 10 0 0 1 2.69 2.7" />
      <path d="M2.182 13.9a10 10 0 0 1 0-3.8" />
      <path d="M20.279 17.609a10 10 0 0 1-2.7 2.69" />
      <path d="M21.818 10.1a10 10 0 0 1 0 3.8" />
      <path d="M3.721 6.391a10 10 0 0 1 2.7-2.69" />
      <path d="M6.391 20.279a10 10 0 0 1-2.69-2.7" />
    </svg>;
  const renderRunningIcon = () => <svg className="yt-status-running-svg" viewBox="22 22 44 44" width="100%" height="100%" aria-hidden="true">
      <circle className="yt-status-running-circle" cx="44" cy="44" r="20.2" fill="none" stroke="currentColor" strokeWidth="3.6" />
    </svg>;
  const renderSkippedIcon = () => <svg viewBox="0 0 24 24" width="100%" height="100%" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <circle cx="12" cy="12" r="10" />
      <path d="M8 12h8" />
    </svg>;
  const STATUS_ICON_DEFS = [{
    re: /^-status-success-$/i,
    colorClass: 'yt-status-color-success',
    label: 'Success',
    render: renderSuccessIcon
  }, {
    re: /^-status-failure-$/i,
    colorClass: 'yt-status-color-failure',
    label: 'Failure',
    render: renderFailureIcon
  }, {
    re: /^-status-partial-success-$/i,
    colorClass: 'yt-status-color-partial',
    label: 'Partial success',
    render: renderPartialSuccessIcon
  }, {
    re: /^-status-pending-$/i,
    colorClass: 'yt-status-color-neutral',
    label: 'Pending',
    render: renderPendingIcon
  }, {
    re: /^-status-running-$/i,
    colorClass: 'yt-status-color-neutral',
    label: 'Running',
    render: renderRunningIcon
  }, {
    re: /^-status-skipped-$/i,
    colorClass: 'yt-status-color-neutral',
    label: 'Skipped',
    render: renderSkippedIcon
  }];
  const renderStatusBadge = def => <span className={['yt-status-icon', def.colorClass].join(' ')} role="img" aria-label={def.label} title={def.label}>
      {def.render()}
    </span>;
  const tryParseKV = trimmed => {
    const m = KV_RE.exec(trimmed);
    return m ? {
      key: m[1],
      value: m[2].trim()
    } : null;
  };
  const registerKey = (key, seenKeys, orderedKeys) => {
    if (!seenKeys.has(key)) {
      orderedKeys.push(key);
      seenKeys.add(key);
    }
  };
  const flushEntry = (currentEntry, entries) => {
    if (Object.keys(currentEntry).length > 0) entries.push(currentEntry);
  };
  const parseDashPrefixed = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed.startsWith('- ')) {
        if (inEntry) entries.push(currentEntry);
        currentEntry = {};
        inEntry = true;
        const kv = tryParseKV(trimmed.substring(2).trim());
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      } else if (inEntry && trimmed !== '') {
        const kv = tryParseKV(trimmed);
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      }
    }
    flushEntry(currentEntry, entries);
  };
  const parseBlankSeparated = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed === '') {
        if (inEntry) {
          flushEntry(currentEntry, entries);
          currentEntry = {};
          inEntry = false;
        }
        continue;
      }
      const kv = tryParseKV(trimmed);
      if (!kv) continue;
      const isNewEntry = !line.startsWith(' ') && !line.startsWith('\t');
      if (isNewEntry && inEntry && Object.keys(currentEntry).length > 0) {
        entries.push(currentEntry);
        currentEntry = {};
      }
      registerKey(kv.key, seenKeys, orderedKeys);
      currentEntry[kv.key] = kv.value;
      inEntry = true;
    }
    flushEntry(currentEntry, entries);
  };
  const normalizeEntries = (entries, orderedKeys) => entries.map(entry => {
    const filled = {};
    for (const key of orderedKeys) filled[key] = entry[key] || '';
    return filled;
  });
  const parseYamlTableContent = contentStr => {
    if (!contentStr) return [];
    const entries = [];
    const orderedKeys = [];
    const seenKeys = new Set();
    const lines = contentStr.split('\n');
    if (lines.some(line => line.trim().startsWith('- '))) {
      parseDashPrefixed(lines, entries, orderedKeys, seenKeys);
    } else {
      parseBlankSeparated(lines, entries, orderedKeys, seenKeys);
    }
    return normalizeEntries(entries, orderedKeys);
  };
  const processText = text => {
    if (!text) return text;
    const parts = [];
    let keyIndex = 0;
    let lastIndex = 0;
    let match;
    while ((match = INLINE_MD_RE.exec(text)) !== null) {
      if (match.index > lastIndex) parts.push(text.slice(lastIndex, match.index));
      if (match[1]) {
        parts.push(<a key={keyIndex++} href={match[3]}>{match[2]}</a>);
      } else if (match[4]) {
        parts.push(<code key={keyIndex++}>{match[5]}</code>);
      } else if (match[6]) {
        parts.push(<strong key={keyIndex++}>{match[7]}</strong>);
      } else if (match[8]) {
        parts.push(<em key={keyIndex++}>{match[9]}</em>);
      }
      lastIndex = match.index + match[0].length;
    }
    if (lastIndex < text.length) parts.push(text.slice(lastIndex));
    if (parts.length === 0) return text;
    const keyRef = {
      current: keyIndex
    };
    return expandHtmlTags(parts, keyRef);
  };
  const processBadges = text => {
    if (!text || typeof text !== 'string') return text;
    if (YES_RE.test(text)) return <span className="yt-badge-yes" role="img" aria-label="Supported" title="Supported">✓</span>;
    if (NO_RE.test(text)) return <span className="yt-badge-no" role="img" aria-label="Not supported" title="Not supported">✗</span>;
    if (LIMITED_RE.test(text)) return <span className="yt-badge-limited" role="img" aria-label="Partially supported" title="Partially supported">◐</span>;
    if (NA_RE.test(text) || NA2_RE.test(text)) return <span className="yt-sr-only" title="Not applicable">Not applicable</span>;
    const statusBadge = STATUS_ICON_DEFS.find(def => def.re.test(text));
    if (statusBadge) return renderStatusBadge(statusBadge);
    return processText(text);
  };
  const cellClassName = text => {
    if (!text || typeof text !== 'string') return undefined;
    if (NA_RE.test(text)) return 'yt-cell-na';
    if (NA2_RE.test(text)) return 'yt-cell-na2';
    return undefined;
  };
  const expandSimpleTags = (str, keyRef) => {
    const result = [];
    let last = 0;
    SIMPLE_TAG_RE.lastIndex = 0;
    let m;
    while ((m = SIMPLE_TAG_RE.exec(str)) !== null) {
      if (m.index > last) result.push(str.slice(last, m.index));
      if (m[1]) {
        result.push(<br key={keyRef.current++} />);
      } else if (m[2]) {
        result.push(<br key={keyRef.current++} />, <br key={keyRef.current++} />);
      } else if (m[3]) {
        result.push(<span key={keyRef.current++} className="yt-badge-note" style={{
          fontWeight: 600
        }}>Note: </span>);
      } else if (m[4]) {
        result.push(<span key={keyRef.current++} className="yt-badge-warning" style={{
          fontWeight: 600
        }}>Warning: </span>);
      }
      last = m.index + m[0].length;
    }
    if (last < str.length) result.push(str.slice(last));
    return result;
  };
  const expandHtmlTags = (chunks, keyRef) => {
    const out = [];
    for (const chunk of chunks) {
      if (typeof chunk === 'string') {
        out.push(...expandSimpleTags(chunk, keyRef));
      } else {
        out.push(chunk);
      }
    }
    return out;
  };
  const extractText = node => {
    if (node === null || node === undefined) return '';
    if (typeof node === 'string') return node;
    if (typeof node === 'number') return String(node);
    if (typeof node === 'boolean') return '';
    if (Array.isArray(node)) return node.map(extractText).join('');
    if (node && typeof node === 'object' && node.type) {
      const props = node.props || ({});
      if (typeof props.children === 'string') return props.children;
      if (props.children) return extractText(props.children);
      return '';
    }
    return String(node || '');
  };
  const [mounted, setMounted] = useState(false);
  const scrollWrapRef = useRef(null);
  const [isScrollable, setIsScrollable] = useState(false);
  useEffect(() => {
    setMounted(true);
  }, []);
  const data = useMemo(() => {
    if (propData) return propData;
    if (content && typeof content === 'string') return parseYamlTableContent(content);
    if (!children) return [];
    if (typeof children === 'string') return parseYamlTableContent(children);
    const childrenArray = Array.isArray(children) ? children : [children];
    return parseYamlTableContent(childrenArray.map(extractText).join('').trim());
  }, [children, propData, content]);
  const columns = useMemo(() => {
    if (!data || data.length === 0) return [];
    const firstRow = data[0];
    if (!firstRow || typeof firstRow !== 'object') return [];
    return Object.keys(firstRow);
  }, [data]);
  useEffect(() => {
    const wrap = scrollWrapRef.current;
    if (!wrap) return undefined;
    const updateScrollable = () => {
      setIsScrollable(wrap.scrollWidth > wrap.clientWidth);
    };
    updateScrollable();
    const observer = new ResizeObserver(updateScrollable);
    observer.observe(wrap);
    return () => observer.disconnect();
  }, [mounted, data]);
  if (!mounted) return null;
  if (!data || data.length === 0) return null;
  const rowKey = row => columns.map(c => row[c] || '').join('|');
  return <div ref={scrollWrapRef} style={{
    overflowX: 'auto',
    margin: '1.75rem 0'
  }} role={isScrollable ? 'region' : undefined} aria-label={isScrollable ? 'Scrollable table' : undefined} tabIndex={isScrollable ? 0 : undefined}>
      <table style={{
    display: 'table',
    width: '100%',
    minWidth: '100%',
    margin: 0
  }}>
        <thead>
          <tr>
            {columns.map(col => <th key={col}>{col.replaceAll('_', ' ')}</th>)}
          </tr>
        </thead>
        <tbody>
          {data.map(row => <tr key={rowKey(row)}>
              {columns.map(col => <td key={col} className={cellClassName(row[col])}>{processBadges(row[col])}</td>)}
            </tr>)}
        </tbody>
      </table>
    </div>;
};

The Endor Labs Agent Kit is a catalog of ready-to-use security agents that run inside your AI coding assistant. Each agent packages a specific Endor Labs workflow, such as triaging findings, remediating vulnerable dependencies, or diagnosing scan failures, with a built-in safety contract, approval gates, and evidence requirements. Install the kit as a plugin or extension in your host of choice, then ask the assistant to run a workflow in plain language.

The Agent Kit replaces the earlier Endor Labs Skills offering, which could only install and configure `endorctl`. The kit ships a full set of workflow agents across every supported host.

## How it is distributed

The Agent Kit lives in two open-source repositories:

* **[endorlabs/ai-plugins](https://github.com/endorlabs/ai-plugins)**: The distribution and marketplace repository. This is what you install from. It is already published and branded in the Claude Code and Cursor marketplaces.
* **[endorlabs/endor-labs-agent-kit](https://github.com/endorlabs/endor-labs-agent-kit)**: The source and builder repository. Use it to contribute or propose new agents, file issues, or grab runtime-neutral [portable agents](/secure-ai-coding/agent-kit/portable) for your own agent runtime.

<Note>
  **Upgrading from Endor Labs Skills**

  If you previously installed the Endor Labs plugin as `ai-plugins@endorlabs`, it keeps working and existing installs are unaffected. New users should install the `endor-labs-agent-kit@endorlabs` plugin instead. Do not enable both plugin IDs in the same Claude Code profile, because they expose the same agents and setup skill.
</Note>

## Supported hosts

Install the kit in any of the following hosts.

<YamlTable>
  {`
    - Host: Claude Code
    How_to_install: Plugin from the Claude Code plugin marketplace
    - Host: OpenAI Codex
    How_to_install: Plugin from the Codex plugin marketplace
    - Host: Gemini CLI
    How_to_install: Gemini extension
    - Host: Antigravity CLI
    How_to_install: Antigravity plugin
    - Host: Cursor
    How_to_install: Cursor plugin for the IDE
    - Host: Cursor SDK
    How_to_install: Python SDK for automation, CI, and cloud runs
    - Host: Portable
    How_to_install: Runtime-neutral bundles for your own agent runtime
    `}
</YamlTable>

## Agent catalog

The kit ships 11 agents. Most are read-only. Two agents, AI SAST Remediation and SCA Remediation, can change state, and they keep every mutating action behind a separate approval gate. See the [safety model](/secure-ai-coding/agent-kit/safety-model) for details.

<Tip>
  In a hurry? Jump to [Quick start](#quick-start) and pick your host and agent to copy the exact install command and first-workflow prompt.
</Tip>

If you are new to the Agent Kit, start with the four most popular agents: **SCA Remediation**, **AI SAST Remediation**, **Configuration Automation**, and **Troubleshooting**.

The **License** column shows the Endor Labs license each agent requires. Agents marked **Any** work with any Endor Labs license because they operate on findings and package intelligence you already have. For what each license includes, see [Licenses](/introduction/licenses).

<YamlTable>
  {`
    - Agent: SCA Remediation
    Workflow: \`sca-remediation\`
    What_it_does: Plan and apply approval-gated dependency fixes using Endor Labs SCA findings, upgrade-risk evidence, and local validation.
    Safety: mutating, approval-gated
    License: Endor Open Source Pro
    - Agent: AI SAST Remediation
    Workflow: \`ai-sast-remediation\`
    What_it_does: Triage and remediate Endor Labs AI SAST findings using exploit-reproduction evidence, data-flow context, and approval-gated fixes.
    Safety: mutating, approval-gated
    License: Endor Code Pro
    - Agent: Configuration Automation
    Workflow: \`configuration-automation\`
    What_it_does: Find GitHub-to-Endor Labs onboarding and monitored-branch coverage gaps, and prescribe the setup to close them, without changing anything.
    Safety: read-only
    License: Any (GitHub only)
    - Agent: Troubleshooting
    Workflow: \`troubleshooting\`
    What_it_does: Diagnose Endor Labs setup and workflow problems, such as scan failures and missing integrations, from focused read-only evidence.
    Safety: read-only
    License: Any
    - Agent: CI/CD And Supply Chain Posture
    Workflow: \`cicd-posture\`
    What_it_does: Score CI/CD and supply chain posture from read-only Endor Labs findings and repository configuration evidence.
    Safety: read-only
    License: Endor Open Source Pro
    - Agent: Dependency Reviewer
    Workflow: \`dependency-reviewer\`
    What_it_does: Review an exact package version, a package's risk profile, or a repository's declared dependencies through one bounded workflow.
    Safety: read-only
    License: Any
    - Agent: OSS Upgrade Investigator
    Workflow: \`oss-upgrade-investigator\`
    What_it_does: Compare Endor Labs upgrade candidates, risk, breaking changes, and code impact to identify the safest upgrade path.
    Safety: read-only
    License: Endor Open Source Pro
    - Agent: Vulnerability Explainer
    Workflow: \`vulnerability-explainer\`
    What_it_does: Explain a vulnerability's severity, exploitability, affected versions, and recommended remediation.
    Safety: read-only
    License: Any
    - Agent: Findings Browser
    Workflow: \`findings-browser\`
    What_it_does: Browse and filter existing Endor Labs findings with clear scope, pagination limits, and evidence gaps.
    Safety: read-only
    License: Any
    - Agent: Malware Responder
    Workflow: \`malware-responder\`
    What_it_does: Correlate current supply chain malware intelligence with your Endor Labs inventory to assess tenant exposure.
    Safety: read-only
    License: Any
    - Agent: Remediation Planning
    Workflow: \`remediation-planning\`
    What_it_does: Compare read-only remediation options and recommend the safest evidence-backed next step without opening pull requests.
    Safety: read-only
    License: Endor Open Source Pro
    `}
</YamlTable>

Each host exposes these workflows with its own invocation syntax. See the install page for your host for exact commands and example prompts.

## Quick start

Choose your host and a starting agent to generate the matching install command, setup prompt, and first-workflow prompt. The four most popular agents appear as buttons. Select **Other agents** to pick any of the remaining agents from the dropdown.

<AgentKitInstaller />

Using Cursor or running agents programmatically? See [Cursor](/secure-ai-coding/agent-kit/cursor) and the [Cursor SDK](/secure-ai-coding/agent-kit/cursor-sdk).

## Run setup first

After you install the kit in any host, run the setup skill before anything else.

```text theme={null}
Use the endor-agent-kit-setup skill to check Endor Labs Agent Kit readiness.
```

Setup is readiness guidance only. It checks for `endorctl`, authentication, namespace selection, `gh`, and toolchain prerequisites, and reports what is missing. It never runs scans, runs `endorctl host-check`, edits shell profiles, installs runtimes, or writes credentials.

## Choose your host to get started

<CardGroup cols={3}>
  <Card title="Claude Code" icon="robot" href="/secure-ai-coding/agent-kit/claude-code">
    Install the Endor Labs Agent Kit plugin in Claude Code.
  </Card>

  <Card title="Cursor" icon="laptop-code" href="/secure-ai-coding/agent-kit/cursor">
    Install the Endor Labs Agent Kit plugin in Cursor.
  </Card>

  <Card title="OpenAI Codex" icon="brain" href="/secure-ai-coding/agent-kit/codex">
    Install the Endor Labs Agent Kit plugin in OpenAI Codex.
  </Card>

  <Card title="Gemini CLI" icon="gem" href="/secure-ai-coding/agent-kit/gemini-cli">
    Install the Endor Labs Agent Kit extension in Gemini CLI.
  </Card>

  <Card title="Antigravity CLI" icon="rocket" href="/secure-ai-coding/agent-kit/antigravity-cli">
    Install the Endor Labs Agent Kit plugin in Antigravity CLI.
  </Card>

  <Card title="Cursor SDK" icon="python" href="/secure-ai-coding/agent-kit/cursor-sdk">
    Run Agent Kit workflows programmatically with the Cursor Python SDK.
  </Card>

  <Card title="Portable agents" icon="boxes-stacked" href="/secure-ai-coding/agent-kit/portable">
    Run runtime-neutral agents in your own agent runtime.
  </Card>

  <Card title="Safety model" icon="shield-halved" href="/secure-ai-coding/agent-kit/safety-model">
    Understand the safety contract and output guarantees.
  </Card>
</CardGroup>

To scan your code for vulnerabilities, secrets, and SAST issues directly in your IDE, see the [Endor Labs MCP server](/setup-deployment/mcp).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.