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

# Installation Guide

Save your API key once in a standard local config file, then let your own script or agent fetch the latest snapshot manifest from `/api/llm/v1/snapshots/latest`.

<Steps>
  <Step title="Save Key Locally">
    <Tabs>
      <Tab title="macOS/Linux">
        ```bash theme={null}
        CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/marketscanner/llm-bundle.env"
        mkdir -p "$(dirname "$CONFIG_FILE")"
        umask 077
        cat > "$CONFIG_FILE" <<'EOF'
        MARKETSCANNER_API_KEY=PASTE_API_KEY_HERE
        EOF
        echo "Saved key to $CONFIG_FILE"
        ```
      </Tab>

      <Tab title="Windows">
        ```powershell theme={null}
        $configFile = Join-Path $env:APPDATA 'MarketScanner\llm-bundle.env'
        New-Item -ItemType Directory -Force -Path (Split-Path -Parent $configFile) | Out-Null
        @"
        MARKETSCANNER_API_KEY=PASTE_API_KEY_HERE
        "@ | Set-Content $configFile
        Write-Host "Saved key to $configFile"
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="LLM Directions">
    Copy and paste this into your LLM:

    ```text theme={null}
    Follow these directions:
    https://gist.github.com/tlk3/31e0b091f0d2d8c1790a6067edca5fd3

    My API key is already stored in the correct default location.

    If I say /msr refresh, download the latest MarketScanner bundle and then reload it from disk before answering.

    After refresh, work from the local bundle files unless I explicitly ask for another API call.

    Do not print or expose my API key.
    ```
  </Step>

  <Step title="Optional Downloader Install">
    Optional for users who want a saved local downloader script instead of relying on `/msr refresh`.

    <Tabs>
      <Tab title="macOS/Linux - download.sh">
        ```bash theme={null}
        #!/usr/bin/env bash
        set -euo pipefail
        CONFIG_FILE="${MARKETSCANNER_CONFIG_FILE:-${XDG_CONFIG_HOME:-$HOME/.config}/marketscanner/llm-bundle.env}"
        if [ -f "$CONFIG_FILE" ]; then
          set -a
          . "$CONFIG_FILE"
          set +a
        fi
        : "${MARKETSCANNER_API_KEY:?Set MARKETSCANNER_API_KEY or create $CONFIG_FILE first}"
        BASE_URL="${MARKETSCANNER_BASE_URL:-https://app.stratalerts.com}"
        OUTPUT_DIR="${1:-$HOME/marketscanner-data/latest}"
        mkdir -p "$OUTPUT_DIR"
        MANIFEST_URL="$BASE_URL/api/llm/v1/snapshots/latest"
        curl --fail --silent --show-error \
          -H "Authorization: Bearer $MARKETSCANNER_API_KEY" \
          "$MANIFEST_URL" -o "$OUTPUT_DIR/manifest.json"

        extract_chunk_refs() {
          grep -o '\{[^}]*\}' "$OUTPUT_DIR/manifest.json" | while IFS= read -r obj; do
            ref="$(printf '%s\n' "$obj" | sed -n 's/.*"relative_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
            if [ -z "$ref" ]; then
              ref="$(printf '%s\n' "$obj" | sed -n 's/.*"path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
            fi
            if [ -z "$ref" ]; then
              ref="$(printf '%s\n' "$obj" | sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
            fi
            if [ -n "$ref" ] && printf '%s' "$obj" | grep -q '"chunk_order"\|"chunk_name"\|"symbol_count"'; then
              printf '%s\n' "$ref"
            fi
          done
        }

        snapshot_version="$(sed -n 's/.*"snapshot_version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$OUTPUT_DIR/manifest.json")"
        chunk_refs="$(extract_chunk_refs)"
        if [ -z "$chunk_refs" ]; then
          echo "No chunk references found in manifest" >&2
          exit 1
        fi

        while IFS= read -r ref; do
          [ -n "$ref" ] || continue
          chunk_name="${ref##*/}"
          case "$ref" in
            http://*|https://*) chunk_url="$ref" ;;
            /api/*) chunk_url="${BASE_URL%/}$ref" ;;
            *)
              if [ -n "$snapshot_version" ]; then
                chunk_url="${BASE_URL%/}/api/llm/v1/snapshots/$snapshot_version/$chunk_name"
              else
                chunk_url="${BASE_URL%/}/${ref#./}"
              fi
              ;;
          esac
          mkdir -p "$OUTPUT_DIR/$(dirname "$ref")"
          curl --fail --silent --show-error \
            -H "Authorization: Bearer $MARKETSCANNER_API_KEY" \
            "$chunk_url" -o "$OUTPUT_DIR/$ref"
        done <<< "$chunk_refs"

        missing=0
        while IFS= read -r ref; do
          [ -n "$ref" ] || continue
          if [ ! -f "$OUTPUT_DIR/$ref" ]; then
            if [ "$missing" -eq 0 ]; then
              echo "Bundle incomplete. Missing chunks:" >&2
            fi
            echo "- $ref" >&2
            missing=1
          fi
        done <<< "$chunk_refs"

        if [ "$missing" -ne 0 ]; then
          exit 1
        fi

        chunk_count="$(printf '%s\n' "$chunk_refs" | grep -c '.')"
        echo "Bundle ready: $OUTPUT_DIR"
        echo "snapshot_version=${snapshot_version:-unknown}"
        echo "chunk_count=$chunk_count"
        ```
      </Tab>

      <Tab title="Windows - download.ps1">
        ```powershell theme={null}
        $ErrorActionPreference = 'Stop'
        $configFile = if ($env:MARKETSCANNER_CONFIG_FILE) { $env:MARKETSCANNER_CONFIG_FILE } elseif ($env:APPDATA) { Join-Path $env:APPDATA 'MarketScanner\llm-bundle.env' } else { Join-Path $HOME 'AppData\Roaming\MarketScanner\llm-bundle.env' }
        if ((-not $env:MARKETSCANNER_API_KEY) -and (Test-Path $configFile)) {
          Get-Content $configFile | ForEach-Object {
            if ($_ -match '^\s*MARKETSCANNER_API_KEY=(.+)$') { $env:MARKETSCANNER_API_KEY = $matches[1].Trim() }
          }
        }
        if (-not $env:MARKETSCANNER_API_KEY) { throw "Set MARKETSCANNER_API_KEY or create $configFile first." }
        $baseUrl = if ($env:MARKETSCANNER_BASE_URL) { $env:MARKETSCANNER_BASE_URL } else { 'https://app.stratalerts.com' }
        $outputDir = if ($args.Length -gt 0) { $args[0] } else { Join-Path $HOME 'marketscanner-data/latest' }
        New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
        $manifestUrl = "$baseUrl/api/llm/v1/snapshots/latest"
        $headers = @{ Authorization = "Bearer $($env:MARKETSCANNER_API_KEY)" }
        $manifest = Invoke-RestMethod -Headers $headers -Uri $manifestUrl
        $manifest | ConvertTo-Json -Depth 10 | Set-Content (Join-Path $outputDir 'manifest.json')
        $snapshotVersion = if ($manifest.snapshot_version) { "$($manifest.snapshot_version)" } else { '' }
        $chunkRefs = @()
        foreach ($chunk in @($manifest.chunks)) {
          $ref = ''
          if ($chunk.PSObject.Properties.Name -contains 'relative_path' -and $chunk.relative_path) { $ref = "$($chunk.relative_path)".Trim() }
          elseif ($chunk.PSObject.Properties.Name -contains 'path' -and $chunk.path) { $ref = "$($chunk.path)".Trim() }
          elseif ($chunk.PSObject.Properties.Name -contains 'name' -and $chunk.name) { $ref = "$($chunk.name)".Trim() }
          if ($ref) { $chunkRefs += $ref }
        }
        if (-not $chunkRefs.Count) { throw 'No chunk references found in manifest' }
        foreach ($ref in $chunkRefs) {
          $chunkName = Split-Path -Leaf $ref
          if ($ref -match '^https?://') { $chunkUrl = $ref }
          elseif ($ref.StartsWith('/api/')) { $chunkUrl = "$baseUrl$ref" }
          elseif ($snapshotVersion) { $chunkUrl = "$baseUrl/api/llm/v1/snapshots/$snapshotVersion/$chunkName" }
          else { $chunkUrl = "$baseUrl/$($ref.TrimStart('/'))" }
          $targetPath = Join-Path $outputDir ($ref -replace '/', [IO.Path]::DirectorySeparatorChar)
          $targetDir = Split-Path -Parent $targetPath
          if ($targetDir) { New-Item -ItemType Directory -Force -Path $targetDir | Out-Null }
          Invoke-WebRequest -Headers $headers -Uri $chunkUrl -OutFile $targetPath
        }
        $missing = @()
        foreach ($ref in $chunkRefs) {
          $targetPath = Join-Path $outputDir ($ref -replace '/', [IO.Path]::DirectorySeparatorChar)
          if (-not (Test-Path $targetPath)) { $missing += $ref }
        }
        if ($missing.Count -gt 0) {
          Write-Error "Bundle incomplete. Missing chunks:`n- $($missing -join "`n- ")"
          exit 1
        }
        Write-Host "Bundle ready: $outputDir"
        Write-Host "snapshot_version=$([string]::IsNullOrWhiteSpace($snapshotVersion) ? unknown : $snapshotVersion)"
        Write-Host "chunk_count=$($chunkRefs.Count)"
        ```
      </Tab>
    </Tabs>
  </Step>
</Steps>
