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

# Environment Setup

> Install and configure the CLI and editor tools for working with Oso Cloud.

export const Languages = {
  JAVASCRIPT: "js",
  PYTHON: "py",
  RUBY: "rb",
  JAVA: "java",
  DOTNET: "dotnet",
  GO: "go",
  SHELL: "shell",
  MACOS: "macos",
  WINDOWS: "windows",
  BASH: "bash",
  CMD: "cmd",
  TYPESCRIPT: "ts",
  HTML: "html",
  CSS: "css",
  JSON: "json",
  YAML: "yaml",
  XML: "xml"
};

export const CodeTab = ({title, language, children, disabled = false}) => {
  const getLanguageDisplayName = language => {
    const displayNames = {
      [Languages.JAVASCRIPT]: "JavaScript",
      [Languages.PYTHON]: "Python",
      [Languages.RUBY]: "Ruby",
      [Languages.JAVA]: "Java",
      [Languages.DOTNET]: ".NET",
      [Languages.GO]: "Go",
      [Languages.SHELL]: "Shell",
      [Languages.MACOS]: "MacOS / Linux",
      [Languages.WINDOWS]: "Windows",
      [Languages.CMD]: "Command Prompt",
      [Languages.TYPESCRIPT]: "TypeScript",
      [Languages.HTML]: "HTML",
      [Languages.CSS]: "CSS",
      [Languages.JSON]: "JSON",
      [Languages.YAML]: "YAML",
      [Languages.XML]: "XML"
    };
    return displayNames[language] || language || "Code";
  };
  const displayTitle = title || getLanguageDisplayName(language);
  if (!displayTitle) {
    console.warn('CodeTab component requires either a title prop or language prop');
    return null;
  }
  if (language && typeof language !== 'string') {
    console.warn('CodeTab language prop must be a string');
  }
  if (title && typeof title !== 'string') {
    console.warn('CodeTab title prop must be a string');
  }
  if (disabled && typeof disabled !== 'boolean') {
    console.warn('CodeTab disabled prop must be a boolean');
  }
  return React.createElement(React.Fragment, null, children);
};

export const CodeTabs = ({children, defaultTab = 0, className = '', syncLanguages = true, onTabChange, 'aria-label': ariaLabel = 'Code examples'}) => {
  const {useState, useMemo, useEffect, useCallback, useId, Children, isValidElement} = React;
  const baseId = useId();
  const getLanguageDisplayName = useCallback(language => {
    const displayNames = {
      [Languages.JAVASCRIPT]: "JavaScript",
      [Languages.PYTHON]: "Python",
      [Languages.RUBY]: "Ruby",
      [Languages.JAVA]: "Java",
      [Languages.DOTNET]: ".NET",
      [Languages.GO]: "Go",
      [Languages.SHELL]: "Shell",
      [Languages.MACOS]: "MacOS / Linux",
      [Languages.WINDOWS]: "Windows",
      [Languages.CMD]: "Command Prompt",
      [Languages.TYPESCRIPT]: "TypeScript",
      [Languages.HTML]: "HTML",
      [Languages.CSS]: "CSS",
      [Languages.JSON]: "JSON",
      [Languages.YAML]: "YAML",
      [Languages.XML]: "XML"
    };
    return displayNames[language] || language || "Code";
  }, []);
  const LANGUAGE_PREF_KEY = useMemo(() => 'mintlify-tabs-language-preference', []);
  const LANGUAGE_CHANGE_EVENT = useMemo(() => 'mintlify-tabs-language-change', []);
  const findMatchingLanguageTab = useCallback((tabs, targetLanguage) => {
    if (!targetLanguage?.trim() || !Array.isArray(tabs) || tabs.length === 0) return -1;
    const normalizedTarget = targetLanguage.toLowerCase().trim();
    return tabs.findIndex(tab => tab?.language?.toLowerCase().trim() === normalizedTarget);
  }, []);
  const getLanguagePreference = useCallback(() => {
    if (typeof window === 'undefined' || !window.localStorage) return null;
    try {
      const stored = localStorage.getItem(LANGUAGE_PREF_KEY);
      if (!stored) return null;
      const parsed = JSON.parse(stored);
      if (typeof parsed === 'object' && parsed.language && typeof parsed.language === 'string') {
        return parsed;
      }
      return null;
    } catch (error) {
      console.warn('Failed to parse language preference:', error);
      return null;
    }
  }, [LANGUAGE_PREF_KEY]);
  const setLanguagePreference = useCallback(language => {
    if (typeof window === 'undefined' || !window.localStorage || !language?.trim()) return;
    try {
      const preference = {
        language: language.trim(),
        timestamp: Date.now()
      };
      localStorage.setItem(LANGUAGE_PREF_KEY, JSON.stringify(preference));
      if (window.CustomEvent && window.dispatchEvent) {
        const event = new CustomEvent(LANGUAGE_CHANGE_EVENT, {
          detail: preference,
          bubbles: false,
          cancelable: false
        });
        window.dispatchEvent(event);
      }
    } catch (error) {
      console.warn('Failed to save language preference:', error);
    }
  }, [LANGUAGE_PREF_KEY, LANGUAGE_CHANGE_EVENT]);
  const generateTabId = useCallback((title, index) => {
    const cleanTitle = title?.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '') || 'tab';
    return `${baseId}-${cleanTitle}-${index}`;
  }, [baseId]);
  const [announcement, setAnnouncement] = useState('');
  const announceToScreenReader = useCallback((message, duration = 1000) => {
    setAnnouncement(message);
    const timeoutId = setTimeout(() => setAnnouncement(''), duration);
    return () => clearTimeout(timeoutId);
  }, []);
  const processedTabs = useMemo(() => {
    if (!children) return [];
    const tabData = [];
    Children.forEach(children, (child, index) => {
      if (!isValidElement(child) || child.type !== CodeTab) {
        console.warn(`Invalid child at index ${index}. Expected CodeTab component.`);
        return;
      }
      const {title, language, disabled = false} = child.props || ({});
      const displayTitle = title || getLanguageDisplayName(language);
      if (!displayTitle || typeof displayTitle !== 'string') {
        console.warn(`CodeTab at index ${index} missing title and language props`);
        return;
      }
      tabData.push({
        title: displayTitle.trim(),
        language: language?.trim() || null,
        disabled: Boolean(disabled),
        content: child.props.children,
        id: generateTabId(displayTitle, index),
        index
      });
    });
    return tabData;
  }, [children, generateTabId]);
  const [activeIndex, setActiveIndex] = useState(0);
  useEffect(() => {
    if (processedTabs.length === 0) return;
    if (typeof defaultTab === 'number') {
      const clampedIndex = Math.max(0, Math.min(defaultTab, processedTabs.length - 1));
      setActiveIndex(clampedIndex);
      return;
    }
    if (typeof defaultTab === 'string' && defaultTab.trim()) {
      const matchIndex = processedTabs.findIndex(tab => tab.title?.toLowerCase() === defaultTab.toLowerCase().trim());
      if (matchIndex >= 0 && !processedTabs[matchIndex].disabled) {
        setActiveIndex(matchIndex);
        return;
      }
    }
    const firstEnabledIndex = processedTabs.findIndex(tab => !tab.disabled);
    setActiveIndex(firstEnabledIndex >= 0 ? firstEnabledIndex : 0);
  }, [processedTabs, defaultTab]);
  useEffect(() => {
    if (!syncLanguages || processedTabs.length === 0) return;
    const preference = getLanguagePreference();
    if (preference) {
      const matchIndex = findMatchingLanguageTab(processedTabs, preference.language);
      if (matchIndex >= 0) {
        setActiveIndex(matchIndex);
      }
    }
  }, [syncLanguages, processedTabs, getLanguagePreference, findMatchingLanguageTab]);
  const handleTabClick = useCallback(index => {
    const targetTab = processedTabs[index];
    if (!targetTab || targetTab.disabled || index === activeIndex) return;
    const previousIndex = activeIndex;
    setActiveIndex(index);
    const announcement = `${targetTab.title} tab selected. Tab ${index + 1} of ${processedTabs.length}.`;
    const cleanup = announceToScreenReader(announcement);
    if (syncLanguages && targetTab.language) {
      setLanguagePreference(targetTab.language);
    }
    onTabChange?.(index, targetTab, previousIndex);
    return cleanup;
  }, [processedTabs, activeIndex, syncLanguages, setLanguagePreference, onTabChange, announceToScreenReader]);
  useEffect(() => {
    if (!syncLanguages || typeof window === 'undefined') return;
    const handleLanguageChange = event => {
      try {
        const {language} = event.detail || ({});
        if (!language || typeof language !== 'string') return;
        const matchIndex = findMatchingLanguageTab(processedTabs, language);
        if (matchIndex >= 0 && matchIndex !== activeIndex && !processedTabs[matchIndex]?.disabled) {
          setActiveIndex(matchIndex);
          const currentTab = processedTabs[matchIndex];
          const announcement = `Synchronized to ${currentTab.title} tab. Tab ${matchIndex + 1} of ${processedTabs.length}.`;
          announceToScreenReader(announcement, 600);
          onTabChange?.(matchIndex, currentTab, activeIndex);
        }
      } catch (error) {
        console.warn('Error handling language change event:', error);
      }
    };
    window.addEventListener(LANGUAGE_CHANGE_EVENT, handleLanguageChange);
    return () => {
      window.removeEventListener(LANGUAGE_CHANGE_EVENT, handleLanguageChange);
    };
  }, [syncLanguages, processedTabs, activeIndex, findMatchingLanguageTab, LANGUAGE_CHANGE_EVENT, onTabChange, announceToScreenReader]);
  const handleKeyDown = useCallback((event, index) => {
    let targetIndex = -1;
    switch (event.key) {
      case 'ArrowLeft':
        event.preventDefault();
        targetIndex = index > 0 ? index - 1 : processedTabs.length - 1;
        break;
      case 'ArrowRight':
        event.preventDefault();
        targetIndex = index < processedTabs.length - 1 ? index + 1 : 0;
        break;
      case 'Enter':
      case ' ':
        event.preventDefault();
        handleTabClick(index);
        return;
      case 'Home':
        event.preventDefault();
        targetIndex = 0;
        break;
      case 'End':
        event.preventDefault();
        targetIndex = processedTabs.length - 1;
        break;
      default:
        return;
    }
    if (targetIndex !== -1) {
      setActiveIndex(targetIndex);
    }
  }, [processedTabs.length, handleTabClick]);
  if (processedTabs.length === 0) {
    return <div className="p-4 text-gray-500 italic text-center border border-gray-200 rounded-lg">
        No valid tabs found. Please check your CodeTab components.
      </div>;
  }
  const safeActiveIndex = Math.max(0, Math.min(activeIndex, processedTabs.length - 1));
  const activeTab = processedTabs[safeActiveIndex];
  return <div className={`tabs tab-container ${className}`} id={activeTab?.language || 'code-tabs'}>
      {}
      <div aria-live="polite" aria-atomic="true" className="sr-only">
        {announcement}
      </div>

      {}
      <ul role="tablist" aria-label={ariaLabel} className="not-prose mb-6 pb-[1px] flex-none min-w-full overflow-auto border-b border-gray-200 gap-x-6 flex dark:border-gray-200/10" data-component-part="tabs-list">
        {processedTabs.map((tab, index) => {
    const isActive = index === safeActiveIndex;
    const isDisabled = tab.disabled;
    return <li key={tab.id} id={tab.id} className="cursor-pointer">
              <button role="tab" aria-selected={isActive} aria-controls={`${tab.id}-panel`} aria-disabled={isDisabled} aria-setsize={processedTabs.length} aria-posinset={index + 1} tabIndex={isActive ? 0 : -1} disabled={isDisabled} onClick={() => handleTabClick(index)} onKeyDown={e => handleKeyDown(e, index)} className={isDisabled ? 'flex text-sm items-center gap-1.5 leading-6 font-semibold whitespace-nowrap pt-3 pb-2.5 -mb-px max-w-max border-b text-gray-400 border-transparent cursor-not-allowed opacity-50' : isActive ? 'flex text-sm items-center gap-1.5 leading-6 font-semibold whitespace-nowrap pt-3 pb-2.5 -mb-px max-w-max border-b text-primary dark:text-primary-light border-current' : 'flex text-sm items-center gap-1.5 leading-6 font-semibold whitespace-nowrap pt-3 pb-2.5 -mb-px max-w-max border-b text-gray-900 border-transparent hover:border-gray-300 dark:text-gray-200 dark:hover:border-gray-700'} data-component-part="tab-button" data-active={isActive}>
                {tab.title}
              </button>
            </li>;
  })}
      </ul>

      {}
      <div role="tabpanel" id={`${activeTab?.id}-panel`} aria-labelledby={activeTab?.id} aria-label={`${activeTab?.title} content, tab panel ${safeActiveIndex + 1} of ${processedTabs.length}`} tabIndex={0} className="prose dark:prose-dark overflow-x-auto" data-component-part="tab-content">
        {activeTab?.content}
      </div>
    </div>;
};

## Core development tools

* **Oso Cloud CLI**: validate, test, and deploy Polar policies from the command line.
* **Editor extensions**: syntax highlighting, error detection, and in-editor testing.
* **Local testing**: use the [Oso Dev Server](/develop/local-dev/oso-dev-server) for fast iteration without network calls.

## Install the Oso Cloud CLI

<CodeTabs>
  <CodeTab language={Languages.MACOS}>
    ```bash theme={null}
    # Download and install the latest version
    curl -L https://cloud.osohq.com/install.sh | bash

    # Verify
    which oso-cloud

    # Configure credentials
    export OSO_AUTH=<your_oso_api_key>
    ```
  </CodeTab>

  <CodeTab language={Languages.WINDOWS}>
    The Windows CLI is distributed as an executable. To install it, run the following command from a Windows bash prompt:

    ```bash theme={null}
    Invoke-WebRequest "https://d3i4cc4dqewpo9.cloudfront.net/latest/oso_cli.exe" -OutFile C:\<your_oso_cli_folder>\oso_cli.exe
    ```

    Replace `<your_oso_cli_folder>` with the location of the folder where you want to store the CLI.

    Verify the installation succeeded using:

    ```bash theme={null}
    C:\<your_oso_cli_folder>\oso_cli --help
    ```

    Permanently add the `<your_oso_cli_folder>` directory to your PATH by appending it to the PATH environment variable.

    **Configure the CLI**

    When you use the CLI, it connects to one of your account's environments.

    To configure the CLI to use a specific environment:

    1. Create or find an API key for the environment you want to use.

    2. Store the API key as `OSO_AUTH` for the current bash session:

    ```bash theme={null}
    Set-Item -Path env:OSO_AUTH -Value "<your_oso_api_key>"
    ```
  </CodeTab>
</CodeTabs>

### Key CLI commands

Once installed, use these commands in your development workflow:

<CodeTabs>
  <CodeTab language={Languages.MACOS}>
    ```bash theme={null}
    # Validate policy syntax
    oso-cloud validate

    # Run policy tests
    oso-cloud test

    # Deploy policies to Oso Cloud or Dev Server
    oso-cloud policy "<policy-name>"

    # Generate TypeScript types from policies
    oso-cloud generate-types
    ```
  </CodeTab>

  <CodeTab language={Languages.WINDOWS}>
    ```bash theme={null}
    # Validate policy syntax
    oso_cloud validate

    # Run policy tests
    oso_cloud test

    # Deploy policies to Oso Cloud or Dev Server
    oso_cloud policy "<policy-name>"

    # Generate TypeScript types from policies
    oso_cloud generate-types
    ```
  </CodeTab>
</CodeTabs>

## Editor integrations

### VS Code

Install the official VS Code extension to enable:

* **Syntax highlighting** for `.polar` files
* **Real-time error detection**
* **Inline test runner**
* **Auto-complete** for built-in Polar functions

**To install:**

1. Open VS Code Extensions panel (`Ctrl+Shift+X`)
2. Search for "Oso"
3. Install the official extension

**Alternative:** Download from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=osohq.oso)

### Other editors

Oso supports the Language Server Protocol (LSP) for syntax validation, auto-completion, and testing. Use any LSP-compatible editor with the CLI as the language server.

<AccordionGroup>
  <Accordion title="Neovim">
    **Install LSP**:

    1. Install the [oso-cloud CLI](/reference/sdks/install)
    2. Create an LSP config directory: `mkdir ~/.config/nvim/after/lsp/`
    3. Place the following in `~/.config/nvim/after/lsp/polar.lua`:
       ```lua theme={null}
       return {
           cmd = { "oso-cloud", "lsp" },
           filetypes = { "polar" }
       }
       ```
    4. Re-launch nvim
    5. Enable the config: `:lua vim.lsp.enable("polar")`

    To troubleshoot issues, run `:checkhealth vim.lsp`.
  </Accordion>

  <Accordion title="Console Editors (Vim, Emacs)">
    Start the LSP server using the CLI:

    ```bash theme={null}
    oso-cloud lsp
    ```

    Configure your editor to use this LSP for `.polar` files.
  </Accordion>

  <Accordion title="JetBrains IDEs (WebStorm, IntelliJ)">
    **Install syntax highlighting:**

    1. Clone [oso-vscode-extension](https://github.com/osohq/oso-vscode-extension)
    2. Run `git submodule init` and then `git submodule update` in your local `oso-vscode-extension` repository
    3. In JetBrains settings: `Editor` → `TextMate bundles` → Add bundle
    4. Select the cloned `oso-vscode-extension` folder

    **Install LSP:**

    1. Install the LSP4IJ plugin
    2. Go to `Language & Frameworks` → `Language Servers`
    3. Add new server:
       * **Command:** `oso-cloud lsp`
       * **File patterns:** `*.polar`
       * **Language ID:** `polar`
  </Accordion>

  <Accordion title="Zed">
    **To install:**

    1. Open the Zed extensions panel (`Ctrl+Shift+X`)
    2. Search for "Oso"
    3. Install the extension (published by Oso Security, Inc.)
  </Accordion>

  <Accordion title="Web-based development">
    Use the built-in policy editor at [ui.osohq.com](https://ui.osohq.com). No setup required. Supports LSP features out of the box.
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="CLI Issues">
    **CLI not found:**

    * Check if the binary is in your system PATH
    * Restart your terminal
    * Verify installation by running: `oso-cloud --version`

    **Permission denied:**

    * Make the binary executable: `chmod +x oso-cloud`
    * Use `sudo` for system-wide installation
  </Accordion>

  <Accordion title="Editor Issues">
    **VS Code extension not working:**

    * Restart VS Code
    * Check the Extensions panel for errors
    * Verify `.polar` files have syntax highlighting

    **LSP not connecting:**

    * Ensure CLI is installed and on PATH
    * Check LSP configuration in your editor
    * Restart the editor
  </Accordion>
</AccordionGroup>

## Next steps

Your development tools are ready. Here's how to start building:

1. **[Set up local testing](/develop/local-dev/oso-dev-server)** with the Oso Dev Server
2. **[Write your first policy](/develop/policies/overview)** in Polar
3. **[Model your authorization data](/develop/facts/overview)** as facts
4. **[Implement authorization checks](/develop/enforce/authorize-requests)** in your application
