> ## 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-triage',
    label: 'AI SAST Triage',
    task: 'triage AI SAST findings for this repository'
  }, {
    id: 'probe-droid',
    label: 'Probe Droid',
    task: 'probe GitHub org <org> for Endor Labs monitored-branch onboarding gaps'
  }, {
    id: 'endor-troubleshooter',
    label: 'Endor Troubleshooter',
    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-decision-helper',
    label: 'Dependency Decision Helper',
    task: 'assess whether to use npm lodash version 4.17.20'
  }, {
    id: 'package-risk-summary',
    label: 'Package Risk Summary',
    task: 'summarize npm lodash version 4.17.20 with verified Endor Labs evidence'
  }, {
    id: 'repository-dependency-reviewer',
    label: 'Repository Dependency Reviewer',
    task: "review this repository's dependency manifests with read-only evidence only"
  }, {
    id: 'upgrade-impact-analysis',
    label: 'Upgrade Impact Analysis',
    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-response',
    label: 'Malware Response',
    task: 'use the malware-response workflow within its safety contract'
  }, {
    id: 'remediation-planner',
    label: 'Remediation Planner',
    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 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>;
    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);
  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]);
  if (!mounted) return null;
  if (!data || data.length === 0) return null;
  const rowKey = row => columns.map(c => row[c] || '').join('|');
  return <table>
      <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>;
};

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 13 agents. Most are read-only. Two agents, AI SAST Triage 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 Triage**, **Probe Droid**, and **Endor Troubleshooter**.

<YamlTable>
  {`
    - Agent: SCA Remediation
    Workflow: \`sca-remediation\`
    What_it_does: Remediate dependency vulnerabilities with Endor Labs SCA findings, upgrade-impact evidence, low-risk PR lanes, deterministic risk decisions, and approved PR or MR creation.
    Safety: mutating, approval-gated
    - Agent: AI SAST Triage
    Workflow: \`ai-sast-triage\`
    What_it_does: Triage Endor Labs AI SAST findings, apply exploit and remediation context, and open requested change requests.
    Safety: mutating, approval-gated
    - Agent: Probe Droid
    Workflow: \`probe-droid\`
    What_it_does: Probe GitHub onboarding gaps and prescribe Endor Labs scan profiles, toolchains, package integrations, and reachability setup.
    Safety: read-only
    - Agent: Endor Troubleshooter
    Workflow: \`endor-troubleshooter\`
    What_it_does: Diagnose Endor Labs errors, scan failures, slow scans, missing integrations, SSO, container, policy, and reachability issues.
    Safety: read-only
    - Agent: CI/CD and Supply Chain Posture
    Workflow: \`cicd-posture\`
    What_it_does: Assess CI/CD and supply chain posture from existing Endor Labs findings and read-only GitHub configuration evidence.
    Safety: read-only
    - Agent: Dependency Decision Helper
    Workflow: \`dependency-decision-helper\`
    What_it_does: Decide whether to add, upgrade to, or keep a specific package version.
    Safety: read-only
    - Agent: Package Risk Summary
    Workflow: \`package-risk-summary\`
    What_it_does: Summarize the risk profile of a specific package version.
    Safety: read-only
    - Agent: Repository Dependency Reviewer
    Workflow: \`repository-dependency-reviewer\`
    What_it_does: Review local dependency manifests with read-only file inspection and Endor Labs evidence.
    Safety: read-only
    - Agent: Upgrade Impact Analysis
    Workflow: \`upgrade-impact-analysis\`
    What_it_does: Analyze upgrade impact with version-upgrade, code-impact, findings, and manifest context.
    Safety: read-only
    - Agent: Vulnerability Explainer
    Workflow: \`vulnerability-explainer\`
    What_it_does: Explain a specific CVE, GHSA, or Endor Labs vulnerability and what to do next.
    Safety: read-only
    - Agent: Findings Browser
    Workflow: \`findings-browser\`
    What_it_does: Browse, filter, and summarize existing Endor Labs findings with read-only namespace-scoped queries.
    Safety: read-only
    - Agent: Malware Response
    Workflow: \`malware-response\`
    What_it_does: Correlate supply-chain malware intelligence against your tenant package inventory.
    Safety: read-only
    - Agent: Remediation Planner
    Workflow: \`remediation-planner\`
    What_it_does: Preview safe dependency remediation options without opening PRs.
    Safety: read-only
    `}
</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).
