> ## 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.

# toolchains

> Use the toolchains command to detect tools in your repository, create a scan profile, or generate a Dockerfile.

export const Draft = ({children}) => {
  const SHOW_PARAM = 'show';
  const SHOW_VALUE = 'hidden';
  const BORDER_RGB = '220, 38, 38';
  const BORDER_COLOR = '#dc2626';
  const BG_LIGHT = 'rgba(254, 226, 226, 0.65)';
  const BG_DARK = 'rgba(127, 29, 29, 0.35)';
  const HEADER_TEXT_LIGHT = '#111111';
  const HEADER_TEXT_DARK = '#f5f5f5';
  const CARD_MARGIN = '1rem 0';
  const CARD_BORDER_RADIUS = '8px';
  const HEADER_PADDING = '0.75rem 1rem';
  const HEADER_FONT_SIZE = '1.25rem';
  const BODY_PADDING = '0 1rem 0.75rem';
  const ref = useRef(null);
  const [isDark, setIsDark] = useState(false);
  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 (!ref.current) return;
    const params = new URLSearchParams(globalThis.location.search);
    if (params.get(SHOW_PARAM) === SHOW_VALUE) {
      ref.current.style.display = 'block';
    }
  }, []);
  return <div ref={ref} className="not-prose" style={{
    display: 'none'
  }} role="note" aria-label="Draft internal content">
      <div style={{
    margin: CARD_MARGIN,
    border: `2px solid ${BORDER_COLOR}`,
    borderRadius: CARD_BORDER_RADIUS,
    backgroundColor: isDark ? BG_DARK : BG_LIGHT,
    boxShadow: `0 0 0 1px rgba(${BORDER_RGB}, 0.15) inset`
  }}>
        <div className="not-prose" style={{
    padding: HEADER_PADDING,
    fontWeight: 700,
    fontSize: HEADER_FONT_SIZE,
    color: isDark ? HEADER_TEXT_DARK : HEADER_TEXT_LIGHT,
    textAlign: 'center'
  }}>
          Do not use! Draft content. Development in progress.
        </div>
        <div style={{
    padding: BODY_PADDING
  }}>{children}</div>
      </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>;
};

Use the `endorctl toolchains` command to detect the current tools used in your repository, create a scan profile, or generate a Dockerfile.

<Note>
  Toolchain commands are not supported on Windows.
</Note>

## Usage

* Use the `help` argument to see the options associated with toolchains command.

  ```bash theme={null}
  endorctl toolchains --help
  ```

### Detect tools in your repository

Use `endorctl detect` to identify the tools currently used in your repository.
The following arguments can help you refine your scan:

* Use the `-p` argument to define the local filesystem path to the repository you want to scan.

  ```bash theme={null}
  endorctl toolchains detect -p <path_to_repository>
  ```

* Use the `--exclude-path` argument to exclude specific file paths or directories.

  ```bash theme={null}
  endorctl toolchains detect -p <path_to_repository> --exclude-path 'python/**'
  ```

* Use the `--include-path` argument to limit the scan to a specific file path or directory.

  ```bash theme={null}
  endorctl toolchains detect -p <path_to_repository> --include-path 'development/**'
  ```

### Create a scan profile

Use `endorctl generate` to create a scan profile. See [Configure build tools](/scan/scan-profiles/build-tools) for more details.

* Use the `profile-name` argument to assign a name to your profile. This command creates a `.endorctl/scanprofile.yaml` file with the tools in the repository.

  ```bash theme={null}
  endorctl toolchains generate -p <path_to_repository>  --profile-name <profile-name>
  ```

* Use the `output-type` argument to specify the format of the output file.

  ```bash theme={null}
  endorctl toolchains generate -p <path_to_repository> --profile-name <profile-name> --output-type <output-type>
  ```

* Use the `--output-path` argument to set the output file location.

  ```bash theme={null}
  endorctl toolchains generate -p <path_to_repository> --profile-name <profile-name> --output-type json --output-path <output-path>
  ```

* Use the `--create-profile` argument to create and save the scan profile using the specified options.

  ```bash theme={null}
  endorctl toolchains generate -p <path_to_repository> --profile-name <profile-name> --create-profile
  ```

<Draft>
  **Detect toolchains in your repository and generate a Dockerfile**

  Use `endorctl toolchains docker` to auto detect toolchains and build tools in your repository and generate a Dockerfile pre-configured with endorctl and those tools for scanning your project with Endor Labs. This gives you a working scan environment quickly without manually identifying and installing language toolchains.

  * Navigate to the root of your repository and run the following command to generate a Dockerfile in the current directory.

    ```bash theme={null}
    endorctl toolchains docker
    ```

  * Use the `-p` argument to specify the repository path to scan, and the `-o` argument to set where the Dockerfile is written.

    ```bash theme={null}
    endorctl toolchains docker -p <path_to_repository> -o <output_path>
    ```

  * Use the `-l` or `--languages` argument to limit auto-detection to specific languages.

    ```bash theme={null}
    endorctl toolchains docker -p <path_to_repository> -l <list_of_languages> -o <output_path>
    ```

  * Use the `--include-path` argument to limit toolchain auto detection to specific file paths or directories. The following example limits detection to the `development/` directory and its contents.

    ```bash theme={null}
    endorctl toolchains docker -p <path_to_repository> --include-path 'development/**' -o <output_path>
    ```

  * Use the `--exclude-path` argument to exclude specific paths from toolchain auto detection. The following example excludes the `python/` directory from auto detection.

    ```bash theme={null}
    endorctl toolchains docker -p <path_to_repository> --exclude-path 'python/**' -o <output_path>
    ```
</Draft>

## Options

The `endorctl toolchains` command uses the following flags and environment variables.

<YamlTable>
  {`


    - Flag: \`p\`, \`path\`
    Environment_Variable: \`ENDOR_TOOLCHAINS_PATH\`
    Type: string
    Description: Set the path on the local filesystem to the repository to scan. Default is \`.\`.

    - Flag: \`arch\`
    Environment_Variable: \`ENDOR_TOOLCHAINS_ARCH\`
    Type: string
    Description: Set the toolchain architecture (\`amd64\` or \`arm64\`).

    - Flag: \`os\`
    Environment_Variable: \`ENDOR_TOOLCHAINS_OS\`
    Type: string
    Description: Set the toolchain operating system (\`linux\` or \`darwin\`).

    - Flag: \`exclude-path\`
    Environment_Variable: \`ENDOR_TOOLCHAINS_EXCLUDE_PATH\`
    Type: string
    Description: Specify one or more file paths or directories to exclude from the scan using Glob style expressions. For example, \`src/java/**\` excludes all files under src/java. Paths must be relative to the root of the repository. Use quotes to ensure that your shell does not expand wild cards.

    - Flag: \`include-path\`
    Environment_Variable: \`ENDOR_TOOLCHAINS_INCLUDE_PATH\`
    Type: string
    Description: Limit the scan to the specified file paths or directories using Glob style expressions. For example, \`src/java/**\` includes all files under src/java. Paths must be relative to the root of the repository. Use quotes to ensure that your shell does not expand wild cards.

    - Flag: \`o\`, \`output-path\`
    Environment_Variable: \`ENDOR_TOOLCHAINS_OUTPUT_PATH\`
    Type: string
    Description: Set the output path for the generated scan profile. Used with \`endorctl toolchains generate\`.


    `}
</YamlTable>

<Draft>
  * Flag: `o`, `output-path`
    Environment\_Variable: `ENDOR_TOOLCHAINS_DOCKER_OUTPUT_PATH`
    Type: string
    Description: Set the output path for the generated Dockerfile. Default is `.`. Used with `endorctl toolchains docker`.

  * Flag: `l`, `languages`
    Environment\_Variable: `ENDOR_TOOLCHAINS_LANGUAGES`
    Type: string
    Description: Limit toolchain auto detection to specific languages. Use with `endorctl toolchains docker`.
</Draft>


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