Full Code of caioricciuti/ch-ui for AI

main f21fb4c40d52 cached
274 files
2.1 MB
577.0k tokens
1864 symbols
1 requests
Download .txt
Showing preview only (2,324K chars total). Download the full file or copy to clipboard to get everything.
Repository: caioricciuti/ch-ui
Branch: main
Commit: f21fb4c40d52
Files: 274
Total size: 2.1 MB

Directory structure:
gitextract_j38tsz8m/

├── .dockerignore
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   └── feature.yml
│   └── workflows/
│       └── release.yml
├── .gitignore
├── .gitpod.yml
├── Dockerfile
├── LICENSE.md
├── Makefile
├── README.md
├── VERSION
├── ch-ui.conf
├── cmd/
│   ├── connect.go
│   ├── connect_detach_unix.go
│   ├── connect_detach_windows.go
│   ├── connect_process_unix.go
│   ├── connect_process_windows.go
│   ├── root.go
│   ├── server.go
│   ├── service.go
│   ├── tunnel.go
│   ├── uninstall.go
│   ├── update.go
│   └── version.go
├── connector/
│   ├── clickhouse.go
│   ├── config/
│   │   └── config.go
│   ├── connector.go
│   ├── hostinfo.go
│   ├── hostinfo_unix.go
│   ├── hostinfo_windows.go
│   ├── protocol.go
│   ├── service/
│   │   ├── launchd.go
│   │   ├── service.go
│   │   └── systemd.go
│   └── ui/
│       └── ui.go
├── docs/
│   ├── brain/
│   │   └── SKILLS.md
│   ├── cant-login.md
│   ├── legal/
│   │   ├── privacy-policy.md
│   │   └── terms-of-service.md
│   ├── license.md
│   └── production-runbook.md
├── frontend.go
├── go.mod
├── go.sum
├── internal/
│   ├── alerts/
│   │   └── dispatcher.go
│   ├── brain/
│   │   ├── provider.go
│   │   └── provider_test.go
│   ├── config/
│   │   ├── config.go
│   │   ├── secret.go
│   │   └── secret_test.go
│   ├── crypto/
│   │   └── aes.go
│   ├── database/
│   │   ├── alert_digests.go
│   │   ├── alerts.go
│   │   ├── audit_logs.go
│   │   ├── audit_logs_test.go
│   │   ├── brain.go
│   │   ├── cleanup.go
│   │   ├── connections.go
│   │   ├── dashboards.go
│   │   ├── database.go
│   │   ├── migrations.go
│   │   ├── migrations_guardrails_test.go
│   │   ├── models.go
│   │   ├── pipelines.go
│   │   ├── rate_limits.go
│   │   ├── saved_queries.go
│   │   ├── schedules.go
│   │   ├── sessions.go
│   │   ├── settings.go
│   │   └── user_roles.go
│   ├── embedded/
│   │   └── embedded.go
│   ├── governance/
│   │   ├── guardrails.go
│   │   ├── guardrails_test.go
│   │   ├── harvester_access.go
│   │   ├── harvester_metadata.go
│   │   ├── harvester_querylog.go
│   │   ├── incidents.go
│   │   ├── lineage.go
│   │   ├── policy_engine.go
│   │   ├── store.go
│   │   ├── syncer.go
│   │   └── types.go
│   ├── langfuse/
│   │   └── langfuse.go
│   ├── license/
│   │   ├── license.go
│   │   ├── pubkey.go
│   │   ├── public.pem
│   │   └── tokens.go
│   ├── models/
│   │   ├── dag.go
│   │   ├── ref.go
│   │   ├── runner.go
│   │   └── scheduler.go
│   ├── pipelines/
│   │   ├── clickhouse_sink.go
│   │   ├── database_source.go
│   │   ├── helpers.go
│   │   ├── kafka.go
│   │   ├── kafka_scram.go
│   │   ├── registry.go
│   │   ├── runner.go
│   │   ├── s3_source.go
│   │   ├── types.go
│   │   └── webhook.go
│   ├── queryproc/
│   │   ├── variables.go
│   │   └── variables_test.go
│   ├── scheduler/
│   │   ├── cron.go
│   │   └── runner.go
│   ├── server/
│   │   ├── handlers/
│   │   │   ├── admin.go
│   │   │   ├── admin_brain.go
│   │   │   ├── admin_governance.go
│   │   │   ├── admin_langfuse.go
│   │   │   ├── auth.go
│   │   │   ├── auth_helpers_test.go
│   │   │   ├── brain.go
│   │   │   ├── connections.go
│   │   │   ├── dashboards.go
│   │   │   ├── governance.go
│   │   │   ├── governance_alerts.go
│   │   │   ├── governance_auditlog.go
│   │   │   ├── governance_querylog.go
│   │   │   ├── health.go
│   │   │   ├── license.go
│   │   │   ├── models.go
│   │   │   ├── pipelines.go
│   │   │   ├── query.go
│   │   │   ├── query_guardrails_test.go
│   │   │   ├── query_upload.go
│   │   │   ├── saved_queries.go
│   │   │   ├── schedules.go
│   │   │   └── view_graph.go
│   │   ├── middleware/
│   │   │   ├── context.go
│   │   │   ├── cors.go
│   │   │   ├── license.go
│   │   │   ├── logging.go
│   │   │   ├── ratelimit.go
│   │   │   ├── ratelimit_test.go
│   │   │   ├── security.go
│   │   │   └── session.go
│   │   └── server.go
│   ├── tunnel/
│   │   ├── api.go
│   │   ├── gateway.go
│   │   └── protocol.go
│   └── version/
│       └── version.go
├── license/
│   └── public.pem
├── main.go
└── ui/
    ├── .gitignore
    ├── README.md
    ├── index.html
    ├── package.json
    ├── src/
    │   ├── App.svelte
    │   ├── app.css
    │   ├── lib/
    │   │   ├── api/
    │   │   │   ├── alerts.ts
    │   │   │   ├── auth.ts
    │   │   │   ├── brain.ts
    │   │   │   ├── client.ts
    │   │   │   ├── governance.ts
    │   │   │   ├── models.ts
    │   │   │   ├── pipelines.ts
    │   │   │   ├── query.ts
    │   │   │   └── stream.ts
    │   │   ├── basePath.ts
    │   │   ├── components/
    │   │   │   ├── brain/
    │   │   │   │   ├── BrainArtifactCard.svelte
    │   │   │   │   ├── BrainEmptyState.svelte
    │   │   │   │   ├── BrainHeader.svelte
    │   │   │   │   ├── BrainInput.svelte
    │   │   │   │   ├── BrainMentionDropdown.svelte
    │   │   │   │   ├── BrainMessage.svelte
    │   │   │   │   ├── BrainSidebar.svelte
    │   │   │   │   ├── BrainSqlBlock.svelte
    │   │   │   │   └── brain-markdown.ts
    │   │   │   ├── common/
    │   │   │   │   ├── Button.svelte
    │   │   │   │   ├── Combobox.svelte
    │   │   │   │   ├── ConfirmDialog.svelte
    │   │   │   │   ├── ContextMenu.svelte
    │   │   │   │   ├── HelpTip.svelte
    │   │   │   │   ├── InputDialog.svelte
    │   │   │   │   ├── MiniTrendChart.svelte
    │   │   │   │   ├── Modal.svelte
    │   │   │   │   ├── ProRequired.svelte
    │   │   │   │   ├── Sheet.svelte
    │   │   │   │   ├── Spinner.svelte
    │   │   │   │   └── Toast.svelte
    │   │   │   ├── dashboard/
    │   │   │   │   ├── ChartPanel.svelte
    │   │   │   │   ├── DashboardGrid.svelte
    │   │   │   │   ├── PanelEditor.svelte
    │   │   │   │   ├── TimeRangeSelector.svelte
    │   │   │   │   └── time-picker/
    │   │   │   │       ├── CalendarMonth.svelte
    │   │   │   │       ├── DualCalendar.svelte
    │   │   │   │       ├── PresetList.svelte
    │   │   │   │       ├── TimeInput.svelte
    │   │   │   │       └── TimezoneSelect.svelte
    │   │   │   ├── editor/
    │   │   │   │   ├── InsightsPanel.svelte
    │   │   │   │   ├── ResultFooter.svelte
    │   │   │   │   ├── ResultPanel.svelte
    │   │   │   │   ├── SchemaPanel.svelte
    │   │   │   │   ├── SqlEditor.svelte
    │   │   │   │   ├── StatsPanel.svelte
    │   │   │   │   └── Toolbar.svelte
    │   │   │   ├── explorer/
    │   │   │   │   ├── DataPreview.svelte
    │   │   │   │   └── DatabaseTree.svelte
    │   │   │   ├── governance/
    │   │   │   │   ├── LineageGraph.svelte
    │   │   │   │   └── LineageTableNode.svelte
    │   │   │   ├── layout/
    │   │   │   │   ├── CommandPalette.svelte
    │   │   │   │   ├── Shell.svelte
    │   │   │   │   ├── Sidebar.svelte
    │   │   │   │   ├── TabBar.svelte
    │   │   │   │   ├── TabContent.svelte
    │   │   │   │   ├── TabGroup.svelte
    │   │   │   │   └── content/
    │   │   │   │       ├── DatabaseContent.svelte
    │   │   │   │       ├── ModelContent.svelte
    │   │   │   │       ├── QueryContent.svelte
    │   │   │   │       └── TableContent.svelte
    │   │   │   ├── models/
    │   │   │   │   └── ModelNode.svelte
    │   │   │   ├── pipelines/
    │   │   │   │   ├── NodeConfigPanel.svelte
    │   │   │   │   ├── PipelineCanvas.svelte
    │   │   │   │   ├── PipelineEditor.svelte
    │   │   │   │   ├── PipelineList.svelte
    │   │   │   │   ├── PipelineStatusBar.svelte
    │   │   │   │   ├── PipelineToolbar.svelte
    │   │   │   │   └── nodes/
    │   │   │   │       ├── SinkNode.svelte
    │   │   │   │       └── SourceNode.svelte
    │   │   │   └── table/
    │   │   │       ├── Pagination.svelte
    │   │   │       ├── TableCell.svelte
    │   │   │       ├── TableHeader.svelte
    │   │   │       └── VirtualTable.svelte
    │   │   ├── editor/
    │   │   │   └── completions.ts
    │   │   ├── stores/
    │   │   │   ├── command-palette.svelte.ts
    │   │   │   ├── license.svelte.ts
    │   │   │   ├── number-format.svelte.ts
    │   │   │   ├── query-limit.svelte.ts
    │   │   │   ├── router.svelte.ts
    │   │   │   ├── schema.svelte.ts
    │   │   │   ├── session.svelte.ts
    │   │   │   ├── tabs.svelte.ts
    │   │   │   ├── theme.svelte.ts
    │   │   │   └── toast.svelte.ts
    │   │   ├── types/
    │   │   │   ├── alerts.ts
    │   │   │   ├── api.ts
    │   │   │   ├── brain.ts
    │   │   │   ├── governance.ts
    │   │   │   ├── models.ts
    │   │   │   ├── pipelines.ts
    │   │   │   ├── query.ts
    │   │   │   └── schema.ts
    │   │   └── utils/
    │   │       ├── calendar.ts
    │   │       ├── ch-types.ts
    │   │       ├── chart-transform.ts
    │   │       ├── dashboard-time.test.ts
    │   │       ├── dashboard-time.ts
    │   │       ├── export.ts
    │   │       ├── format.ts
    │   │       ├── grid-layout.ts
    │   │       ├── lineage-layout.ts
    │   │       ├── safe-json.ts
    │   │       ├── sql.ts
    │   │       ├── stats.ts
    │   │       └── uuid.ts
    │   ├── main.ts
    │   └── pages/
    │       ├── Admin.svelte
    │       ├── Brain.svelte
    │       ├── Dashboards.svelte
    │       ├── Governance.svelte
    │       ├── Home.svelte
    │       ├── Login.svelte
    │       ├── Models.svelte
    │       ├── Pipelines.svelte
    │       ├── SavedQueries.svelte
    │       ├── Schedules.svelte
    │       └── Settings.svelte
    ├── svelte.config.js
    ├── tsconfig.app.json
    ├── tsconfig.json
    ├── tsconfig.node.json
    ├── vite.config.d.ts
    ├── vite.config.ts
    └── vitest.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
.git
.github
.claude
.DS_Store
ch-ui
ch-ui-server.pid
data
dist
tmp
node_modules
ui/node_modules
ui/.svelte-kit
ui/dist
ui/.DS_Store


================================================
FILE: .github/ISSUE_TEMPLATE/bug.yml
================================================
name: Bug Report
description: Found something that doesn't work as expected?
body:
  - type: dropdown
    id: os
    attributes:
      label: Operating System
      description: What OS are you running CH-UI on?
      options:
        - Linux
        - macOS
        - Windows
        - Other
    validations:
      required: true
  - type: dropdown
    id: arch
    attributes:
      label: Architecture
      description: What architecture?
      options:
        - x86_64 (amd64)
        - ARM64 (aarch64 / Apple Silicon)
        - Other
    validations:
      required: true
  - type: textarea
    id: repro
    attributes:
      label: How did you encounter the bug?
      description: How can this bug be reproduced? Please provide steps to reproduce.
      placeholder: |-
        1. Start CH-UI with...
        2. Go to...
        3. Click on...
    validations:
      required: true
  - type: textarea
    id: expected
    attributes:
      label: What did you expect?
      description: What was supposed to happen?
    validations:
      required: true
  - type: textarea
    id: actual
    attributes:
      label: Actual Result
      description: What actually happened?
    validations:
      required: true
  - type: textarea
    id: version
    attributes:
      label: Version
      description: What version of CH-UI are you using?
      placeholder: e.g. 2.0.0
    validations:
      required: true
  - type: textarea
    id: logs
    attributes:
      label: Logs / Error Output
      description: Any relevant logs or error messages from the terminal?
      render: shell
    validations:
      required: false
  - type: markdown
    attributes:
      value: |-
        ### All done, now, just submit the issue and I will do my best to take care of it!
    validations:
      required: false


================================================
FILE: .github/ISSUE_TEMPLATE/feature.yml
================================================
name: Feature Request
description: Tell us about something ch-UI doesn't do yet, but should!
body:
  - type: textarea
    id: idea
    attributes:
      label: Idea Statement
      description: Which is the feature you would like to see implemented?
      placeholder: |-
        I want to be able to do anything I want, whenever I want. Because my ideas are the best.
    validations:
      required: true
  - type: textarea
    id: expected
    attributes:
      label: Feature implementation brainstorm
      description: All your ideas are welcome, let's brainstorm together.
      placeholder: |-
        Create the next big feature that will all our problems.
    validations:
      required: false
  - type: markdown
    attributes:
      value: |-
        ## Thanks 🙏
    validations:
      required: false


================================================
FILE: .github/workflows/release.yml
================================================
name: Release

on:
  push:
    tags:
      - 'v*'

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

permissions:
  contents: write
  packages: write

concurrency:
  group: release-${{ github.ref }}
  cancel-in-progress: false

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.24'

      - name: Setup Bun
        uses: oven-sh/setup-bun@v2

      - name: Build frontend
        run: make build-frontend

      - name: Extract version
        id: version
        run: echo "version=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"

      - name: Cross-compile binaries
        env:
          VERSION: ${{ steps.version.outputs.version }}
          COMMIT: ${{ github.sha }}
          DATE: ${{ github.event.head_commit.timestamp }}
        run: |
          LDFLAGS="-s -w -X main.Version=${VERSION} -X main.Commit=${COMMIT} -X main.BuildDate=${DATE}"

          CGO_ENABLED=0 GOOS=linux  GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o dist/ch-ui-linux-amd64 .
          CGO_ENABLED=0 GOOS=linux  GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o dist/ch-ui-linux-arm64 .
          CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o dist/ch-ui-darwin-amd64 .
          CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o dist/ch-ui-darwin-arm64 .

      - name: Alpine smoke test (linux-amd64)
        run: |
          chmod +x dist/ch-ui-linux-amd64
          docker run --rm -v "$PWD/dist:/dist:ro" alpine:3.20 /dist/ch-ui-linux-amd64 version

      - name: Generate checksums
        working-directory: dist
        run: |
          sha256sum ch-ui-* > checksums.txt
          cat checksums.txt

      - name: Create or update GitHub Release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          set -euo pipefail

          TAG="${VERSION}"
          TITLE="${TAG}"
          REPO="${{ github.repository }}"
          NOTES_FILE="$(mktemp)"

          cat > "${NOTES_FILE}" <<EOF
          ## Install

          Download the binary for your platform and run it:

          \`\`\`bash
          # Linux (amd64)
          curl -L -o ch-ui https://github.com/${REPO}/releases/download/${TAG}/ch-ui-linux-amd64
          chmod +x ch-ui
          sudo install -m 755 ch-ui /usr/local/bin/ch-ui
          ch-ui
          \`\`\`

          If you don't want a global install, run it as \`./ch-ui\`.

          ## Verify checksum

          \`\`\`bash
          sha256sum -c checksums.txt
          \`\`\`
          EOF

          if gh release view "${TAG}" >/dev/null 2>&1; then
            gh release edit "${TAG}" --title "${TITLE}" --notes-file "${NOTES_FILE}"
          else
            gh release create "${TAG}" --title "${TITLE}" --notes-file "${NOTES_FILE}"
          fi

          gh release upload "${TAG}" \
            dist/ch-ui-linux-amd64 \
            dist/ch-ui-linux-arm64 \
            dist/ch-ui-darwin-amd64 \
            dist/ch-ui-darwin-arm64 \
            dist/checksums.txt \
            --clobber

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Verify GHCR credentials
        env:
          GHCR_PAT: ${{ secrets.GHCR_PAT }}
        run: |
          if [ -z "${GHCR_PAT}" ]; then
            echo "GHCR_PAT secret is required to publish ghcr.io/${{ github.repository }} images."
            exit 1
          fi

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GHCR_PAT }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./Dockerfile
          push: true
          platforms: linux/amd64,linux/arm64
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          build-args: |
            VERSION=${{ steps.version.outputs.version }}
            COMMIT=${{ github.sha }}
            BUILD_DATE=${{ github.event.head_commit.timestamp }}


================================================
FILE: .gitignore
================================================
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# Dependencies
node_modules

# Build output
dist/
dist-ssr
*.local
.claude

# Frontend build output (embedded into Go binary)
ui/dist/
!ui/dist/.gitkeep

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Environment
.env
.env.local
!.env.example

# Docker local data
.clickhouse_local_data

# SQLite database
/data
*.db
*.db-shm
*.db-wal


# Go binary (built by Makefile)
ch-ui

.claude


.gocache/

# License tool secrets
license/private.pem
license/*.json
license/*.log
license/go.*
license/licensetool
license/README.md
license/main.go

posts.md
ch-ui-server.pid
CLAUDE.md

================================================
FILE: .gitpod.yml
================================================
image: gitpod/workspace-full

tasks:
  - name: ClickHouse
    init: docker pull clickhouse/clickhouse-server:latest
    command: |
      docker run -d --rm \
        --name clickhouse \
        -p 8123:8123 \
        -p 9000:9000 \
        clickhouse/clickhouse-server:latest
      echo "ClickHouse running on port 8123"

  - name: CH-UI
    init: |
      curl -L -o ch-ui https://github.com/caioricciuti/ch-ui/releases/latest/download/ch-ui-linux-amd64
      chmod +x ch-ui
    command: |
      # Wait for ClickHouse to be ready
      echo "Waiting for ClickHouse..."
      while ! curl -s http://localhost:8123/ping > /dev/null 2>&1; do sleep 1; done
      echo "ClickHouse is up. Starting CH-UI..."
      CLICKHOUSE_URL=http://localhost:8123 ./ch-ui

ports:
  - port: 3488
    onOpen: open-browser
    visibility: public
  - port: 8123
    onOpen: ignore
  - port: 9000
    onOpen: ignore

================================================
FILE: Dockerfile
================================================
# syntax=docker/dockerfile:1.7

FROM oven/bun:1.2.23 AS ui-builder
WORKDIR /src/ui

COPY ui/package.json ui/bun.lock ./
RUN bun install --frozen-lockfile

COPY ui/ ./
ENV CHUI_VITE_MINIFY=true \
    CHUI_VITE_REPORT_COMPRESSED=false
RUN bun run build

FROM golang:1.25-alpine AS go-builder
WORKDIR /src

ARG VERSION=dev
ARG COMMIT=none
ARG BUILD_DATE=unknown
ARG TARGETOS=linux
ARG TARGETARCH=amd64

COPY go.mod go.sum ./
RUN go mod download

COPY . .
COPY --from=ui-builder /src/ui/dist ./ui/dist

RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
    go build -trimpath -ldflags "-s -w -X main.Version=${VERSION} -X main.Commit=${COMMIT} -X main.BuildDate=${BUILD_DATE}" -o /out/ch-ui .

FROM alpine:3.20 AS runtime
RUN addgroup -S chui && adduser -S -G chui chui \
    && apk add --no-cache ca-certificates tzdata \
    && mkdir -p /app/data \
    && chown -R chui:chui /app

WORKDIR /app
COPY --from=go-builder /out/ch-ui /usr/local/bin/ch-ui

ENV DATABASE_PATH=/app/data/ch-ui.db

EXPOSE 3488
VOLUME ["/app/data"]

USER chui
ENTRYPOINT ["ch-ui", "server"]


================================================
FILE: LICENSE.md
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to the Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by the Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding any notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   Copyright 2024-2026 Caio Ricciuti

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: Makefile
================================================
# CH-UI Makefile
# Single binary: server + agent + embedded frontend

VERSION ?= $(shell cat VERSION 2>/dev/null || git describe --tags --always --dirty 2>/dev/null || echo "dev")
COMMIT  ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "none")
DATE    ?= $(shell date -u '+%Y-%m-%dT%H:%M:%SZ')
LDFLAGS = -s -w \
	-X main.Version=$(VERSION) \
	-X main.Commit=$(COMMIT) \
	-X main.BuildDate=$(DATE)

BINARY = ch-ui

.PHONY: app build rebuild from-scratch build-frontend build-go dev test clean tidy vet help

## app: Build frontend + Go binary (production-ready)
app: build-frontend build-go

## build: Build everything (frontend + Go binary)
build: app

## rebuild: Clean artifacts, then build everything
rebuild:
	$(MAKE) clean
	$(MAKE) build

## from-scratch: Alias for rebuild
from-scratch: rebuild

## build-frontend: Build the React frontend
build-frontend:
	cd ui && bun install
	@cd ui && (CHUI_VITE_MINIFY=true CHUI_VITE_REPORT_COMPRESSED=false bun run build || \
		(echo "Frontend build was killed; retrying with low-memory profile (no minify)..." && \
		CHUI_VITE_MINIFY=false CHUI_VITE_REPORT_COMPRESSED=false bun run build))

## build-go: Build just the Go binary (skip frontend rebuild)
build-go:
	CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -o $(BINARY) .

## dev: Start the server in dev mode (expects Vite running on :5173)
dev:
	go run -ldflags "$(LDFLAGS)" . server --dev

## test: Run all Go tests
test:
	go test ./... -v -count=1

## clean: Remove build artifacts
clean:
	rm -f $(BINARY)
	rm -rf ui/dist/

## tidy: Clean up Go modules
tidy:
	go mod tidy

## vet: Run go vet
vet:
	go vet ./...

## help: Show this help message
help:
	@echo "Available targets:"
	@grep -E '^## ' Makefile | sed 's/## /  /'


================================================
FILE: README.md
================================================
<p align="center">
  <img src="ui/src/assets/logo.png" alt="CH-UI Logo" width="88" />
</p>

<h1 align="center">CH-UI</h1>

<p align="center">
  <strong>The open-source ClickHouse management platform.</strong><br/>
  SQL editor, dashboards, AI copilot, data pipelines, models, and admin — all in one binary. Free.
</p>

<p align="center">
  <a href="https://github.com/caioricciuti/ch-ui/releases"><img src="https://img.shields.io/github/v/release/caioricciuti/ch-ui?label=version" alt="Version" /></a>
  <a href="https://github.com/caioricciuti/ch-ui/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue" alt="License" /></a>
  <a href="https://github.com/caioricciuti/ch-ui/stargazers"><img src="https://img.shields.io/github/stars/caioricciuti/ch-ui" alt="Stars" /></a>
  <a href="https://github.com/caioricciuti/ch-ui/pkgs/container/ch-ui"><img src="https://img.shields.io/badge/docker-ghcr.io-blue" alt="Docker" /></a>
</p>

---

## Why CH-UI?

Most ClickHouse tools give you a query box and call it a day. CH-UI gives you a full workspace — and almost everything is **free and open source**.

Download one binary. Run it. Get:

- A multi-tab **SQL editor** with formatting, profiling, and streaming results
- **Dashboards** with a drag-and-drop panel builder and multiple chart types
- **Brain** — an AI assistant that understands your schema (OpenAI, Ollama, or any compatible provider)
- **Data pipelines** — visual builder for Webhook, S3, Kafka, and DB sources into ClickHouse
- **Models** — dbt-style SQL transformations with dependency graphs and scheduling
- **Admin panel** — user management, connection management, provider configuration
- **Saved queries**, **schema explorer**, **connection management**, and more

No Docker requirement. No external dependencies. No signup.

---

## Table of Contents

- [Features (Free)](#features-free)
- [Community vs Pro](#community-vs-pro)
- [Quick Start](#quick-start)
- [Quick Start (Docker)](#quick-start-docker)
- [Architecture](#architecture)
- [Remote ClickHouse (Tunnel)](#remote-clickhouse-tunnel)
- [CLI Reference](#cli-reference)
- [Configuration](#configuration)
- [Production Checklist](#production-checklist)
- [Troubleshooting](#troubleshooting)
- [Development](#development)
- [Upgrade](#upgrade)
- [Legal](#legal)
- [Contributing](#contributing)

---

## Features (Free)

Everything below is included in the free Community edition under Apache 2.0.

### SQL Editor

- Multi-tab interface with persistent state
- CodeMirror 6 with SQL syntax highlighting and autocomplete
- Query formatting and beautification
- Streaming results via SSE — no timeout on long queries
- **Query cost estimation** — see estimated rows and parts to scan before running (like BigQuery's dry run)
- Query profiling (pulls from `system.query_log`) with estimate vs actual accuracy comparison
- Query plan analysis (EXPLAIN with parsed tree view)
- Configurable max result rows and query timeout
- Guardrails enforcement (query validation before execution)

### Schema Explorer

- Full database/table/column tree browser
- Table data preview with pagination
- Column type introspection
- Search across databases and tables

### Dashboards

- Create unlimited dashboards
- Drag-and-drop panel builder
- Multiple chart types (line, bar, scatter, area, and more via uplot)
- Time range selector with presets (1h, 24h, 7d, 30d, custom)
- Timezone support
- Auto-refresh control
- Each panel runs its own SQL query against your ClickHouse

### Brain (AI Assistant)

- Chat with your data using natural language
- Multi-chat support with full history persistence
- **Provider support:** OpenAI, OpenAI-compatible APIs (Groq, Together, etc.), Ollama (local LLMs)
- Admin-controlled model and provider activation
- Schema-aware context (attach up to 10 tables as context per chat)
- SQL artifact generation — run generated queries directly from chat
- Brain skills (configurable system prompts/instructions)
- Token usage tracking
- Langfuse integration for LLM observability

### Data Pipelines

- Visual pipeline canvas (drag-and-drop with XyFlow)
- **Source connectors:** Webhook (inbound HTTP), Database (SQL query), S3, Kafka (with SCRAM auth)
- **Sink:** ClickHouse (native insert with configurable batch size)
- Pipeline start/stop controls
- Run history, metrics, and error tracking
- Real-time monitoring (rows ingested, bytes, batches, errors)

### Models (SQL Transformations)

- dbt-style SQL models with `table`, `view`, and `incremental` materialization
- Model dependency graph (DAG visualization)
- Execution with dependency ordering
- Run history and results tracking
- Table engine configuration per model
- Can be scheduled via the scheduler (Pro) or run manually

### Saved Queries

- Save queries with titles and descriptions
- Sort by date, name, or query length
- Filter, search, copy, and organize
- Quick access from the sidebar

### Admin Panel

- User management (create, delete, assign roles)
- ClickHouse user management (create users, update passwords, delete)
- Connection management with multi-connection support
- Brain provider and model configuration
- Brain skill management
- Langfuse integration settings
- System statistics dashboard

### Connections & Tunnel

- Multi-connection support (manage multiple ClickHouse instances)
- Secure WebSocket tunnel for remote ClickHouse access
- Token-based agent authentication
- Connection health monitoring
- Install connector as OS service (`ch-ui service install`)

### Other

- Dark mode
- Session-based authentication with rate limiting
- Security headers (CSP, X-Frame-Options, etc.)
- Health check endpoint (`/health`)
- Self-update (`ch-ui update`)
- Shell completion generation

---

## Community vs Pro

Almost everything is free. Pro adds enterprise governance and scheduling.

| Capability | Community (Free) | Pro |
|---|:---:|:---:|
| SQL editor + explorer + formatting + profiling | **Yes** | Yes |
| Saved queries | **Yes** | Yes |
| Dashboards + panel builder | **Yes** | Yes |
| Brain (AI assistant, multi-provider) | **Yes** | Yes |
| Data pipelines (Webhook, S3, Kafka, DB) | **Yes** | Yes |
| Models (SQL transformations, DAG) | **Yes** | Yes |
| Admin panel + user management | **Yes** | Yes |
| Multi-connection management | **Yes** | Yes |
| Tunnel (remote ClickHouse) | **Yes** | Yes |
| Scheduled query jobs + cron + history | - | **Yes** |
| Governance (metadata, visual lineage graph, column-level lineage, access matrix) | - | **Yes** |
| Policies + incidents + violations | - | **Yes** |
| Alerting (SMTP, Resend, Brevo) | - | **Yes** |

See: [`docs/license.md`](docs/license.md)

---

## Quick Start

### 1) Download

Linux (amd64):
```bash
curl -L -o ch-ui https://github.com/caioricciuti/ch-ui/releases/latest/download/ch-ui-linux-amd64
chmod +x ch-ui
```

Linux (arm64):
```bash
curl -L -o ch-ui https://github.com/caioricciuti/ch-ui/releases/latest/download/ch-ui-linux-arm64
chmod +x ch-ui
```

macOS (Apple Silicon):
```bash
curl -L -o ch-ui https://github.com/caioricciuti/ch-ui/releases/latest/download/ch-ui-darwin-arm64
chmod +x ch-ui
```

macOS (Intel):
```bash
curl -L -o ch-ui https://github.com/caioricciuti/ch-ui/releases/latest/download/ch-ui-darwin-amd64
chmod +x ch-ui
```

Optional — verify checksum:
```bash
curl -L -o checksums.txt https://github.com/caioricciuti/ch-ui/releases/latest/download/checksums.txt
sha256sum -c checksums.txt --ignore-missing
```

### 2) Run

```bash
sudo install -m 755 ch-ui /usr/local/bin/ch-ui
ch-ui
```

Or just `./ch-ui` from the download folder.

Open `http://localhost:3488` and log in with your ClickHouse credentials.

---

## Quick Start (Docker)

```bash
docker run --rm \
  -p 3488:3488 \
  -v ch-ui-data:/app/data \
  -e CLICKHOUSE_URL=http://host.docker.internal:8123 \
  ghcr.io/caioricciuti/ch-ui:latest
```

- On Linux, replace `host.docker.internal` with a host/IP reachable from the container.
- Persisted state is stored in `/app/data/ch-ui.db` (volume: `ch-ui-data`).

---

## Architecture

CH-UI ships as a single binary with two operating modes:
- **`server`** — web app + API + WebSocket tunnel gateway (default)
- **`connect`** — lightweight agent that exposes local ClickHouse over secure WebSocket

```mermaid
flowchart LR
    U["Browser"] --> S["CH-UI Server\n(UI + API + Gateway)"]
    S <--> DB["SQLite\n(state, settings, chats, dashboards)"]
    A["ch-ui connect\n(Agent)"] <--> S
    A --> CH["ClickHouse"]
```

For local use, the server starts an embedded connector automatically against `localhost:8123`.

**Tech stack:** Go backend (chi v5, SQLite WAL mode), Svelte 5 frontend (TypeScript, Vite, TailwindCSS), embedded at build time.

---

## Remote ClickHouse (Tunnel)

Connect to ClickHouse instances running on other machines using the secure WebSocket tunnel.

**Server (VM2):**
```bash
ch-ui server --port 3488
```

**Agent (VM1, where ClickHouse runs):**
```bash
ch-ui connect --url wss://your-ch-ui-domain/connect --key cht_your_tunnel_token
```

### Tunnel key management

Run these on the server host:

```bash
ch-ui tunnel create --name "vm1-clickhouse"   # Create connection + key
ch-ui tunnel list                              # List all connections
ch-ui tunnel show <connection-id>              # Show token + setup commands
ch-ui tunnel rotate <connection-id>            # Rotate token (old one invalidated)
ch-ui tunnel delete <connection-id>            # Delete connection
```

- Token can also be generated from the Admin UI.
- Agent only needs outbound access to the server's `/connect` endpoint.
- Add `--takeover` to replace a stale agent session.
- Install as OS service: `ch-ui service install --key cht_xxx --url wss://host/connect`

For full hardening guide: [`docs/production-runbook.md`](docs/production-runbook.md)

---

## CLI Reference

### Quick start commands

```bash
ch-ui                     # Start server (local ClickHouse)
ch-ui server start --detach  # Start in background
ch-ui server status          # Check if running
ch-ui server stop            # Stop server
```

### Full command map

| Command | Description |
|---|---|
| `ch-ui` / `ch-ui server` | Start web app + API + gateway |
| `ch-ui connect` | Start tunnel agent next to ClickHouse |
| `ch-ui tunnel create/list/show/rotate/delete` | Manage tunnel keys (server host) |
| `ch-ui service install/start/stop/status/logs/uninstall` | Manage connector as OS service |
| `ch-ui update` | Update to latest release |
| `ch-ui version` | Print version |
| `ch-ui completion bash/zsh/fish` | Generate shell completions |
| `ch-ui uninstall` | Remove CH-UI from system |

### Server flags

| Flag | Default | Description |
|---|---|---|
| `--port, -p` | `3488` | HTTP port |
| `--clickhouse-url` | `http://localhost:8123` | Local ClickHouse URL |
| `--connection-name` | `Local ClickHouse` | Display name for local connection |
| `--config, -c` | - | Path to `server.yaml` |
| `--detach` | - | Run in background |
| `--dev` | - | Development mode (proxy to Vite) |

### Connect flags

| Flag | Default | Description |
|---|---|---|
| `--url` | - | WebSocket tunnel URL (`wss://`) |
| `--key` | - | Tunnel token (`cht_...`) |
| `--clickhouse-url` | `http://localhost:8123` | Local ClickHouse |
| `--config, -c` | - | Path to `config.yaml` |
| `--detach` | - | Run in background |
| `--takeover` | - | Replace stale agent session |

---

## Configuration

CH-UI works without config files. You only need them for production defaults or service-managed startup.

### Config file locations

| File | macOS | Linux |
|---|---|---|
| `server.yaml` | `~/.config/ch-ui/server.yaml` | `/etc/ch-ui/server.yaml` |
| `config.yaml` | `~/.config/ch-ui/config.yaml` | `/etc/ch-ui/config.yaml` |

**Priority:** CLI flags > environment variables > config file > built-in defaults

### Server config

```yaml
port: 3488
app_url: https://ch-ui.yourcompany.com
database_path: /var/lib/ch-ui/ch-ui.db
clickhouse_url: http://localhost:8123
connection_name: Local ClickHouse
app_secret_key: "change-this-in-production"
allowed_origins:
  - https://ch-ui.yourcompany.com
```

| Key | Env var | Default | Description |
|---|---|---|---|
| `port` | `PORT` | `3488` | HTTP port |
| `app_url` | `APP_URL` | `http://localhost:<port>` | Public URL for links and tunnel inference |
| `database_path` | `DATABASE_PATH` | `./data/ch-ui.db` | SQLite database location |
| `clickhouse_url` | `CLICKHOUSE_URL` | `http://localhost:8123` | Embedded local connection target |
| `connection_name` | `CONNECTION_NAME` | `Local ClickHouse` | Display name for local connection |
| `app_secret_key` | `APP_SECRET_KEY` | auto-generated | Session encryption key |
| `allowed_origins` | `ALLOWED_ORIGINS` | empty | CORS allowlist (comma-separated in env) |
| `tunnel_url` | `TUNNEL_URL` | derived from port | Tunnel endpoint advertised to agents |

### Connector config

```yaml
tunnel_token: "cht_your_token"
clickhouse_url: "http://127.0.0.1:8123"
tunnel_url: "wss://your-ch-ui-domain/connect"
```

| Key | Env var | Default | Description |
|---|---|---|---|
| `tunnel_token` | `TUNNEL_TOKEN` | required | Auth key from `ch-ui tunnel create` |
| `clickhouse_url` | `CLICKHOUSE_URL` | `http://localhost:8123` | Local ClickHouse |
| `tunnel_url` | `TUNNEL_URL` | `ws://127.0.0.1:3488/connect` | Server gateway endpoint |

### Changing the local ClickHouse URL

```bash
# CLI flag
ch-ui server --clickhouse-url http://127.0.0.1:8123

# Environment variable
CLICKHOUSE_URL=http://127.0.0.1:8123 ch-ui server

# With custom connection name
ch-ui server --clickhouse-url http://127.0.0.1:8123 --connection-name "My ClickHouse"
```

The login page also has a **Can't login?** button that shows setup guidance.

---

## Production Checklist

- [ ] Set a strong `APP_SECRET_KEY`
- [ ] Set `APP_URL` to your public HTTPS URL
- [ ] Configure `ALLOWED_ORIGINS`
- [ ] Put CH-UI behind a TLS reverse proxy (Nginx example: [`ch-ui.conf`](ch-ui.conf))
- [ ] Ensure WebSocket upgrade support for `/connect`
- [ ] Back up SQLite database regularly
- [ ] Run connector as OS service on remote hosts

### Backup and restore

```bash
# Backup
cp /var/lib/ch-ui/ch-ui.db /var/backups/ch-ui-$(date +%F).db

# Restore — stop server first, then replace the DB file
```

---

## Troubleshooting

### Port already in use

```bash
ch-ui server status   # Check if already running
ch-ui server stop     # Stop the old process
```

### Can't log in

- **Authentication failed** — wrong ClickHouse credentials
- **Connection unavailable** — wrong URL or connector offline
- **Too many attempts** — wait for retry window; fix URL first if needed

Click **Can't login?** on the login page for guided recovery, or restart with:
```bash
ch-ui server --clickhouse-url 'http://127.0.0.1:8123'
```

Full guide: [`docs/cant-login.md`](docs/cant-login.md)

### Connector auth fails (`invalid token`)

- Verify you copied the latest `cht_...` token
- Check with `ch-ui tunnel list`
- Rotate with `ch-ui tunnel rotate <connection-id>`

### WebSocket fails behind proxy

Your proxy must forward upgrades on `/connect`:
- `Upgrade` and `Connection: upgrade` headers
- Long read/send timeouts
- Buffering disabled for tunnel path

### Health check

```bash
curl http://localhost:3488/health
```

---

## Development

Requirements: Go 1.25+, Bun

```bash
git clone https://github.com/caioricciuti/ch-ui.git
cd ch-ui
make build    # Full production build (frontend + Go binary)
./ch-ui
```

Dev mode (two terminals):
```bash
make dev                              # Terminal 1: Go server
cd ui && bun install && bun run dev   # Terminal 2: Vite dev server
```

Useful targets: `make build` | `make test` | `make vet` | `make clean` | `make rebuild`

---

## Upgrade

```bash
ch-ui update
```

Downloads the latest release for your OS/arch, verifies checksum, and replaces the binary.

---

## Legal

- Core license: [`LICENSE`](LICENSE) (Apache 2.0)
- Licensing details: [`docs/license.md`](docs/license.md)
- Terms: [`docs/legal/terms-of-service.md`](docs/legal/terms-of-service.md)
- Privacy: [`docs/legal/privacy-policy.md`](docs/legal/privacy-policy.md)

---

## Contributing

Issues and PRs are welcome.

When contributing, please include:
- Reproduction steps (for bugs)
- Expected behavior
- Migration notes (if schema/API changed)
- Screenshots (for UI changes)


# Gitpod One-Click Demo

## Try it now

[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/caioricciuti/ch-ui)

> Launches a full CH-UI + ClickHouse environment in your browser. No install required.
> Free tier: 50 hours/month, no credit card - Via Gitpod (https://www.gitpod.io/)

================================================
FILE: VERSION
================================================
v2.0.23

================================================
FILE: ch-ui.conf
================================================
upstream ch-ui {
    server 127.0.0.1:3488;
    keepalive 64;
}

# ─── HTTP → HTTPS redirect ──────────────────────────────────────────────────
server {
    listen 80;
    listen [::]:80;
    server_name ch-ui.example.com;

    # Let certbot handle ACME challenges
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

# ─── HTTPS ───────────────────────────────────────────────────────────────────
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name ch-ui.example.com;

    # SSL certificates — managed by certbot
    ssl_certificate /etc/letsencrypt/live/ch-ui.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ch-ui.example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Gzip (static assets are already embedded in the Go binary)
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml;

    # Agent tunnel WebSocket — keep alive indefinitely
    location = /connect {
        proxy_pass http://ch-ui;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400;
        proxy_send_timeout 86400;
        proxy_buffering off;
    }

    # Health check — no access log noise
    location = /health {
        proxy_pass http://ch-ui;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        access_log off;
    }

    # Agent binary downloads — large files
    location /download/ {
        proxy_pass http://ch-ui;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_read_timeout 300;
    }

    # Everything else — API, frontend, install script
    location / {
        proxy_pass http://ch-ui;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 300;
        proxy_send_timeout 300;
    }
}


================================================
FILE: cmd/connect.go
================================================
package cmd

import (
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"

	"github.com/caioricciuti/ch-ui/connector"
	"github.com/caioricciuti/ch-ui/connector/config"
	"github.com/caioricciuti/ch-ui/connector/service"
	"github.com/caioricciuti/ch-ui/connector/ui"
	"github.com/spf13/cobra"
)

var (
	connectURL        string
	connectKey        string
	connectCHURL      string
	connectDetach     bool
	connectTakeover   bool
	connectConfigPath string
)

var connectCmd = &cobra.Command{
	Use:   "connect",
	Short: "Connect to a CH-UI server as a tunnel",
	Long: `Connect this machine's ClickHouse instance to a remote CH-UI server
via a secure WebSocket tunnel. Queries executed in the CH-UI dashboard
will be forwarded through this tunnel to your local ClickHouse.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		u := ui.New(false, false, false, false)
		u.Logo("")

		// Build CLI config from flags
		cliCfg := &config.Config{}
		if cmd.Flags().Changed("key") {
			cliCfg.Token = connectKey
		}
		if cmd.Flags().Changed("url") {
			cliCfg.TunnelURL = connectURL
		}
		if cmd.Flags().Changed("clickhouse-url") {
			cliCfg.ClickHouseURL = connectCHURL
		}
		cliCfg.Takeover = connectTakeover

		cfg, err := config.Load(connectConfigPath, cliCfg)
		if err != nil {
			u.Error("Configuration error: %v", err)
			if strings.Contains(strings.ToLower(err.Error()), "tunnel token is required") {
				u.Info("Create a tunnel token on your CH-UI server host with:")
				u.Info("  ch-ui tunnel create --name <connection-name>")
				u.Info("Then retry connect with --key <token> (or set TUNNEL_TOKEN).")
			}
			return err
		}

		if connectDetach {
			pid, logPath, err := startDetached()
			if err != nil {
				return fmt.Errorf("failed to start in background: %w", err)
			}
			u.Success("Started in background (PID %d)", pid)
			if logPath != "" {
				u.Info("Logs: %s", logPath)
			}
			return nil
		}

		if !connectTakeover {
			if running, err := service.New().IsRunning(); err == nil && running {
				u.Info("CH-UI service is already running on this machine")
				u.Info("Use 'ch-ui service status' to inspect it")
				u.Info("Use 'ch-ui service stop' to stop it before running connect")
				return nil
			}
		}

		releasePID, err := acquirePIDLock()
		if err != nil {
			u.DiagnosticError(ui.ErrorTypeConfig, "Local host",
				err.Error(),
				[]string{
					"Check current state with: ch-ui service status",
					"If this is stale, remove it and retry: rm -f " + pidFilePath(),
				},
			)
			return err
		}
		defer releasePID()

		conn := connector.New(cfg, u)

		sigCh := make(chan os.Signal, 1)
		signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
		go func() {
			<-sigCh
			u.Info("Shutting down...")
			conn.Shutdown()
		}()

		if err := conn.Run(); err != nil {
			u.Error("Connection error: %v", err)
			return err
		}
		return nil
	},
}

func init() {
	connectCmd.Flags().StringVar(&connectURL, "url", "", "CH-UI server WebSocket URL (ws:// or wss://)")
	connectCmd.Flags().StringVar(&connectKey, "key", "", "Tunnel token (cht_..., create on server with: ch-ui tunnel create --name <name>)")
	connectCmd.Flags().StringVar(&connectCHURL, "clickhouse-url", "", "ClickHouse HTTP URL (default: http://localhost:8123)")
	connectCmd.Flags().BoolVar(&connectDetach, "detach", false, "Run in background")
	connectCmd.Flags().BoolVar(&connectTakeover, "takeover", false, "Replace an existing active session")
	connectCmd.Flags().StringVarP(&connectConfigPath, "config", "c", "", "Path to config file")
	rootCmd.AddCommand(connectCmd)
}

// ── Detach ──────────────────────────────────────────────────────────────────

func startDetached() (int, string, error) {
	exe, err := os.Executable()
	if err != nil {
		return 0, "", err
	}
	exe, err = filepath.EvalSymlinks(exe)
	if err != nil {
		return 0, "", err
	}

	args := sanitizeDetachedArgs(os.Args[1:])
	if len(args) == 0 || args[0] != "connect" {
		return 0, "", fmt.Errorf("detach must be started from 'connect' command")
	}

	logDir := service.GetConfigDir()
	if err := os.MkdirAll(logDir, 0755); err != nil {
		return 0, "", err
	}
	logPath := filepath.Join(logDir, "ch-ui-connect.log")

	logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		return 0, "", err
	}

	cmd := exec.Command(exe, args...)
	cmd.Env = append(os.Environ(), "CHUI_DETACHED=1")
	cmd.Stdout = logFile
	cmd.Stderr = logFile
	setProcessDetachedAttr(cmd)

	if err := cmd.Start(); err != nil {
		logFile.Close()
		return 0, "", err
	}
	_ = logFile.Close()
	return cmd.Process.Pid, logPath, nil
}

func sanitizeDetachedArgs(in []string) []string {
	args := make([]string, 0, len(in))
	for _, a := range in {
		if a == "--detach" || strings.HasPrefix(a, "--detach=") {
			continue
		}
		args = append(args, a)
	}
	return args
}

// ── PID guard ───────────────────────────────────────────────────────────────

func pidFilePath() string {
	return filepath.Join(service.GetConfigDir(), "ch-ui.pid")
}

func acquirePIDLock() (func(), error) {
	pidPath := pidFilePath()
	if err := os.MkdirAll(filepath.Dir(pidPath), 0755); err != nil {
		return nil, fmt.Errorf("failed to create state dir: %w", err)
	}

	for attempts := 0; attempts < 2; attempts++ {
		f, err := os.OpenFile(pidPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
		if err == nil {
			pid := os.Getpid()
			_, writeErr := f.WriteString(strconv.Itoa(pid))
			closeErr := f.Close()
			if writeErr != nil {
				_ = os.Remove(pidPath)
				return nil, fmt.Errorf("failed to write pid file: %w", writeErr)
			}
			if closeErr != nil {
				_ = os.Remove(pidPath)
				return nil, fmt.Errorf("failed to finalize pid file: %w", closeErr)
			}
			return func() {
				currentPID, readErr := readPIDFile(pidPath)
				if readErr == nil && currentPID != os.Getpid() {
					return
				}
				_ = os.Remove(pidPath)
			}, nil
		}

		if !errors.Is(err, os.ErrExist) {
			return nil, fmt.Errorf("failed to create pid file: %w", err)
		}

		existingPID, readErr := readPIDFile(pidPath)
		if readErr != nil {
			_ = os.Remove(pidPath)
			continue
		}
		if isProcessRunning(existingPID) {
			return nil, fmt.Errorf("another ch-ui connect process is already running (PID %d)", existingPID)
		}

		_ = os.Remove(pidPath)
	}

	return nil, fmt.Errorf("failed to acquire lock at %s", pidPath)
}

func readPIDFile(path string) (int, error) {
	raw, err := os.ReadFile(path)
	if err != nil {
		return 0, err
	}
	pid, err := strconv.Atoi(strings.TrimSpace(string(raw)))
	if err != nil || pid <= 0 {
		return 0, fmt.Errorf("invalid pid file")
	}
	return pid, nil
}

// ── Helpers ─────────────────────────────────────────────────────────────────

func copyFile(src, dst string) error {
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close()
	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()
	if _, err := io.Copy(out, in); err != nil {
		return err
	}
	return out.Close()
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}


================================================
FILE: cmd/connect_detach_unix.go
================================================
//go:build darwin || linux

package cmd

import (
	"os/exec"
	"syscall"
)

func setProcessDetachedAttr(cmd *exec.Cmd) {
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setsid: true,
	}
}


================================================
FILE: cmd/connect_detach_windows.go
================================================
//go:build windows

package cmd

import "os/exec"

func setProcessDetachedAttr(cmd *exec.Cmd) {
	// No-op on windows.
}


================================================
FILE: cmd/connect_process_unix.go
================================================
//go:build darwin || linux

package cmd

import (
	"os"
	"syscall"
)

func isProcessRunning(pid int) bool {
	if pid <= 0 {
		return false
	}
	p, err := os.FindProcess(pid)
	if err != nil {
		return false
	}
	return p.Signal(syscall.Signal(0)) == nil
}


================================================
FILE: cmd/connect_process_windows.go
================================================
//go:build windows

package cmd

func isProcessRunning(pid int) bool {
	return pid > 0
}


================================================
FILE: cmd/root.go
================================================
package cmd

import (
	"bufio"
	"fmt"
	"os"
	"strings"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	Use:   "ch-ui",
	Short: "CH-UI - ClickHouse UI and management platform",
	Long:  "CH-UI is a single binary that serves a ClickHouse management platform for local and remote deployments.",
}

func init() {
	loadEnvFile(".env")
}

func Execute() {
	if len(os.Args) == 1 {
		rootCmd.SetArgs([]string{"server"})
	}

	if err := rootCmd.Execute(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

// loadEnvFile reads a .env file and sets environment variables.
// Existing env vars are NOT overwritten (real env takes precedence).
// Silently does nothing if the file doesn't exist.
func loadEnvFile(path string) {
	f, err := os.Open(path)
	if err != nil {
		return
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		key, val, ok := strings.Cut(line, "=")
		if !ok {
			continue
		}
		key = strings.TrimSpace(key)
		val = strings.TrimSpace(val)
		// Strip surrounding quotes
		if len(val) >= 2 && ((val[0] == '"' && val[len(val)-1] == '"') || (val[0] == '\'' && val[len(val)-1] == '\'')) {
			val = val[1 : len(val)-1]
		}
		// Don't overwrite existing env vars
		if os.Getenv(key) == "" {
			os.Setenv(key, val)
		}
	}
}


================================================
FILE: cmd/server.go
================================================
package cmd

import (
	"context"
	"errors"
	"fmt"
	"io/fs"
	"log/slog"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/caioricciuti/ch-ui/internal/config"
	"github.com/caioricciuti/ch-ui/internal/database"
	"github.com/caioricciuti/ch-ui/internal/embedded"
	"github.com/caioricciuti/ch-ui/internal/server"
	"github.com/caioricciuti/ch-ui/internal/version"
	"github.com/spf13/cobra"
)

// FrontendFS holds the embedded frontend filesystem, set by main before Execute().
var FrontendFS fs.FS

var (
	serverPort           int
	devMode              bool
	serverClickHouse     string
	serverConnectionName string
	serverDetach         bool
	serverConfig         string
	serverPIDFile        string
	serverStopTimeout    time.Duration
	restartDetach        bool
)

var serverCmd = &cobra.Command{
	Use:   "server",
	Short: "Start the CH-UI server",
	Long:  "Start the CH-UI HTTP server that serves the API, frontend, and tunnel gateway.",
	PersistentPreRun: func(cmd *cobra.Command, args []string) {
		serverPIDFile = resolvePIDFile(serverPIDFile)
	},
	RunE: func(cmd *cobra.Command, args []string) error {
		return runServer(cmd)
	},
}

var serverStartCmd = &cobra.Command{
	Use:   "start",
	Short: "Start the CH-UI server",
	RunE: func(cmd *cobra.Command, args []string) error {
		return runServer(cmd)
	},
}

var serverStopCmd = &cobra.Command{
	Use:   "stop",
	Short: "Stop the CH-UI server",
	RunE: func(cmd *cobra.Command, args []string) error {
		stopped, err := stopServer(serverPIDFile, serverStopTimeout)
		if err != nil {
			return err
		}
		if stopped {
			fmt.Println("CH-UI server stopped")
		}
		return nil
	},
}

var serverStatusCmd = &cobra.Command{
	Use:   "status",
	Short: "Show CH-UI server status",
	RunE: func(cmd *cobra.Command, args []string) error {
		pid, running, err := getRunningServerPID(serverPIDFile)
		if err != nil {
			return err
		}
		if running {
			fmt.Printf("CH-UI server is running (PID %d)\n", pid)
			fmt.Printf("PID file: %s\n", serverPIDFile)
			return nil
		}

		addr := fmt.Sprintf("127.0.0.1:%d", serverPort)
		if isTCPPortOpen(addr) {
			fmt.Printf("CH-UI server PID file not found, but port %d is in use.\n", serverPort)
			fmt.Printf("Another process may be listening on %s.\n", addr)
			return nil
		}

		fmt.Println("CH-UI server is not running")
		return nil
	},
}

var serverRestartCmd = &cobra.Command{
	Use:   "restart",
	Short: "Restart the CH-UI server",
	RunE: func(cmd *cobra.Command, args []string) error {
		_, err := stopServer(serverPIDFile, serverStopTimeout)
		if err != nil {
			return err
		}

		if restartDetach {
			startArgs := buildServerStartArgs(cmd)
			pid, logPath, err := startDetachedServer(startArgs)
			if err != nil {
				return fmt.Errorf("failed to restart in background: %w", err)
			}
			fmt.Printf("CH-UI server restarted in background (PID %d)\n", pid)
			if logPath != "" {
				fmt.Printf("Logs: %s\n", logPath)
			}
			return nil
		}

		serverDetach = false
		return runServer(cmd)
	},
}

func init() {
	pf := serverCmd.PersistentFlags()
	pf.IntVarP(&serverPort, "port", "p", 3488, "Port to listen on")
	pf.BoolVar(&devMode, "dev", false, "Enable development mode (proxy to Vite)")
	pf.StringVar(&serverClickHouse, "clickhouse-url", "", "Local ClickHouse HTTP URL for the embedded connection")
	pf.StringVar(&serverConnectionName, "connection-name", "", "Display name for the embedded local connection")
	pf.StringVarP(&serverConfig, "config", "c", "", "Path to config file")
	pf.StringVar(&serverPIDFile, "pid-file", "ch-ui-server.pid", "Path to server PID file")
	pf.DurationVar(&serverStopTimeout, "stop-timeout", 10*time.Second, "Graceful stop timeout")

	serverCmd.Flags().BoolVar(&serverDetach, "detach", false, "Run server in background")
	serverStartCmd.Flags().BoolVar(&serverDetach, "detach", false, "Run server in background")

	serverRestartCmd.Flags().BoolVar(&restartDetach, "detach", true, "Run restarted server in background")

	serverCmd.AddCommand(serverStartCmd, serverStopCmd, serverStatusCmd, serverRestartCmd)
	rootCmd.AddCommand(serverCmd)
}

func runServer(cmd *cobra.Command) error {
	if serverDetach {
		startArgs := buildServerStartArgs(cmd)
		pid, logPath, err := startDetachedServer(startArgs)
		if err != nil {
			return fmt.Errorf("failed to start in background: %w", err)
		}
		fmt.Printf("CH-UI server started in background (PID %d)\n", pid)
		if logPath != "" {
			fmt.Printf("Logs: %s\n", logPath)
		}
		return nil
	}

	if err := preparePIDFileForStart(serverPIDFile); err != nil {
		return err
	}
	if err := writeServerPIDFile(serverPIDFile, os.Getpid()); err != nil {
		return fmt.Errorf("failed to write PID file %q: %w", serverPIDFile, err)
	}
	defer cleanupServerPIDFile(serverPIDFile, os.Getpid())

	// Load configuration
	cfg := config.Load(serverConfig)

	// Override with flags if provided
	if cmd.Flags().Changed("port") {
		cfg.Port = serverPort
	}
	if cmd.Flags().Changed("clickhouse-url") {
		cfg.ClickHouseURL = strings.TrimSpace(serverClickHouse)
	}
	if cmd.Flags().Changed("connection-name") {
		cfg.ConnectionName = strings.TrimSpace(serverConnectionName)
	}
	// --dev flag is the authority for dev mode in the server command.
	// Without it, always serve the embedded frontend (production mode).
	cfg.DevMode = devMode

	// Setup structured logging
	logLevel := slog.LevelInfo
	if cfg.DevMode {
		logLevel = slog.LevelDebug
	}
	logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel}))
	slog.SetDefault(logger)

	slog.Info("Starting CH-UI server",
		"version", version.Version,
		"port", cfg.Port,
		"dev", cfg.DevMode,
	)

	secretSource, err := config.EnsureAppSecretKey(cfg)
	if err != nil {
		return fmt.Errorf("failed to initialize app secret key: %w", err)
	}
	if secretSource == config.SecretKeySourceGenerated {
		slog.Warn("APP_SECRET_KEY was not configured; generated a persisted secret key",
			"path", config.AppSecretKeyPath(cfg.DatabasePath))
	} else if secretSource == config.SecretKeySourceFile {
		slog.Info("Loaded persisted app secret key",
			"path", config.AppSecretKeyPath(cfg.DatabasePath))
	}

	// Initialize database
	db, err := database.Open(cfg.DatabasePath)
	if err != nil {
		return fmt.Errorf("failed to open database: %w", err)
	}
	defer db.Close()

	slog.Info("Database initialized", "path", cfg.DatabasePath)

	// Load stored license from database
	if stored, err := db.GetSetting("license_json"); err == nil && stored != "" {
		cfg.LicenseJSON = stored
		slog.Info("License loaded from database")
	}

	// Create and start server
	srv := server.New(cfg, db, FrontendFS)

	// Start embedded agent (connects to local ClickHouse if configured)
	ea, err := embedded.Start(db, cfg.Port, cfg.ClickHouseURL, cfg.ConnectionName)
	if err != nil {
		slog.Warn("Failed to start embedded agent", "error", err)
	}

	// Graceful shutdown
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	errCh := make(chan error, 1)
	go func() {
		errCh <- srv.Start()
	}()

	select {
	case err := <-errCh:
		if ea != nil {
			ea.Stop()
		}
		return err
	case <-ctx.Done():
		slog.Info("Shutting down server...")
		if ea != nil {
			ea.Stop()
		}
		return srv.Shutdown(context.Background())
	}
}

func buildServerStartArgs(cmd *cobra.Command) []string {
	args := []string{"server"}
	if cmd.Flags().Changed("port") {
		args = append(args, fmt.Sprintf("--port=%d", serverPort))
	}
	if cmd.Flags().Changed("dev") && devMode {
		args = append(args, "--dev")
	}
	if cmd.Flags().Changed("config") && strings.TrimSpace(serverConfig) != "" {
		args = append(args, "--config", serverConfig)
	}
	if cmd.Flags().Changed("clickhouse-url") && strings.TrimSpace(serverClickHouse) != "" {
		args = append(args, "--clickhouse-url", serverClickHouse)
	}
	if cmd.Flags().Changed("connection-name") && strings.TrimSpace(serverConnectionName) != "" {
		args = append(args, "--connection-name", serverConnectionName)
	}
	// Always include absolute PID file path so the child process and
	// future update/restart commands can reliably locate the PID file
	// regardless of the caller's working directory.
	args = append(args, "--pid-file", serverPIDFile)
	if cmd.Flags().Changed("stop-timeout") {
		args = append(args, fmt.Sprintf("--stop-timeout=%s", serverStopTimeout.String()))
	}
	return args
}

func startDetachedServer(args []string) (int, string, error) {
	exe, err := os.Executable()
	if err != nil {
		return 0, "", err
	}
	exe, err = filepath.EvalSymlinks(exe)
	if err != nil {
		return 0, "", err
	}

	if err := preparePIDFileForStart(serverPIDFile); err != nil {
		return 0, "", err
	}

	logPath := filepath.Join(".", "ch-ui-server.log")
	logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		return 0, "", err
	}

	child := exec.Command(exe, args...)
	child.Env = append(os.Environ(), "CHUI_DETACHED=1")
	child.Stdout = logFile
	child.Stderr = logFile
	setProcessDetachedAttr(child)

	if err := child.Start(); err != nil {
		_ = logFile.Close()
		return 0, "", err
	}
	_ = logFile.Close()

	absLog, _ := filepath.Abs(logPath)
	return child.Process.Pid, absLog, nil
}

func stopServer(pidFile string, timeout time.Duration) (bool, error) {
	pid, running, err := getRunningServerPID(pidFile)
	if err != nil {
		return false, err
	}
	if !running {
		addr := fmt.Sprintf("127.0.0.1:%d", serverPort)
		if isTCPPortOpen(addr) {
			fmt.Printf("CH-UI server PID file not found, but port %d is in use.\n", serverPort)
			fmt.Printf("This can happen after upgrading from an older build without PID management.\n")
			fmt.Printf("Stop that process once manually, then start with this build.\n")
			fmt.Printf("Expected PID file: %s\n", pidFile)
			return false, nil
		}
		fmt.Println("CH-UI server is not running")
		return false, nil
	}

	proc, err := os.FindProcess(pid)
	if err != nil {
		return false, fmt.Errorf("failed to locate process %d: %w", pid, err)
	}
	if err := proc.Signal(syscall.SIGTERM); err != nil {
		if !processExists(pid) {
			_ = os.Remove(pidFile)
			return false, nil
		}
		return false, fmt.Errorf("failed to stop PID %d: %w", pid, err)
	}

	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		if !processExists(pid) {
			_ = os.Remove(pidFile)
			return true, nil
		}
		time.Sleep(200 * time.Millisecond)
	}

	return false, fmt.Errorf("timeout waiting for PID %d to stop (waited %s)", pid, timeout.String())
}

func getRunningServerPID(pidFile string) (int, bool, error) {
	pid, err := readServerPIDFile(pidFile)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return 0, false, nil
		}
		return 0, false, err
	}

	if processExists(pid) {
		return pid, true, nil
	}

	_ = os.Remove(pidFile)
	return 0, false, nil
}

func preparePIDFileForStart(pidFile string) error {
	pid, running, err := getRunningServerPID(pidFile)
	if err != nil {
		return err
	}
	if running {
		return fmt.Errorf("server already running (PID %d); stop it first with `ch-ui server stop`", pid)
	}
	return nil
}

func writeServerPIDFile(pidFile string, pid int) error {
	if strings.TrimSpace(pidFile) == "" {
		return fmt.Errorf("pid file path is empty")
	}
	dir := filepath.Dir(pidFile)
	if dir != "." && dir != "" {
		if err := os.MkdirAll(dir, 0o755); err != nil {
			return err
		}
	}
	return os.WriteFile(pidFile, []byte(fmt.Sprintf("%d\n", pid)), 0o644)
}

func readServerPIDFile(pidFile string) (int, error) {
	data, err := os.ReadFile(pidFile)
	if err != nil {
		return 0, err
	}
	raw := strings.TrimSpace(string(data))
	if raw == "" {
		return 0, fmt.Errorf("pid file %q is empty", pidFile)
	}
	pid, err := strconv.Atoi(raw)
	if err != nil || pid <= 0 {
		return 0, fmt.Errorf("invalid PID in %q", pidFile)
	}
	return pid, nil
}

func cleanupServerPIDFile(pidFile string, expectedPID int) {
	pid, err := readServerPIDFile(pidFile)
	if err != nil {
		return
	}
	if pid == expectedPID {
		_ = os.Remove(pidFile)
	}
}

func processExists(pid int) bool {
	if pid <= 0 {
		return false
	}
	proc, err := os.FindProcess(pid)
	if err != nil {
		return false
	}
	err = proc.Signal(syscall.Signal(0))
	if err == nil {
		return true
	}
	if errors.Is(err, syscall.EPERM) {
		return true
	}
	var sysErr *os.SyscallError
	if errors.As(err, &sysErr) && errors.Is(sysErr.Err, syscall.EPERM) {
		return true
	}
	return false
}

func isTCPPortOpen(addr string) bool {
	conn, err := net.DialTimeout("tcp", addr, 400*time.Millisecond)
	if err != nil {
		return false
	}
	_ = conn.Close()
	return true
}

// resolvePIDFile converts a relative PID file path to absolute so that
// server detection works regardless of the caller's working directory.
func resolvePIDFile(pidFile string) string {
	if filepath.IsAbs(pidFile) {
		return pidFile
	}
	abs, err := filepath.Abs(pidFile)
	if err != nil {
		return pidFile
	}
	return abs
}


================================================
FILE: cmd/service.go
================================================
package cmd

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/caioricciuti/ch-ui/connector/config"
	"github.com/caioricciuti/ch-ui/connector/service"
	"github.com/spf13/cobra"
)

// ── service (parent) ────────────────────────────────────────────────────────

var serviceCmd = &cobra.Command{
	Use:   "service",
	Short: "Manage CH-UI as a system service",
}

// ── service install ─────────────────────────────────────────────────────────

var (
	svcInstallKey string
	svcInstallURL string
	svcInstallCH  string
)

var serviceInstallCmd = &cobra.Command{
	Use:   "install",
	Short: "Install CH-UI connect as a system service",
	Long: `Install CH-UI as a system service (launchd on macOS, systemd on Linux)
so it automatically connects to the server on boot.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		svc := service.New()

		if svc.IsInstalled() {
			fmt.Println("Service is already installed")
			fmt.Println("Use 'ch-ui service restart' to restart, or 'ch-ui service uninstall' first")
			return nil
		}

		// Resolve current binary
		currentBin, err := os.Executable()
		if err != nil {
			return fmt.Errorf("failed to determine current binary path: %w", err)
		}
		currentBin, err = filepath.EvalSymlinks(currentBin)
		if err != nil {
			return fmt.Errorf("failed to resolve binary path: %w", err)
		}

		// Copy binary to service location if needed
		if currentBin != service.BinaryPath {
			fmt.Printf("Copying binary to %s...\n", service.BinaryPath)
			if err := copyFile(currentBin, service.BinaryPath); err != nil {
				return fmt.Errorf("failed to copy binary: %w (try: sudo cp %s %s)", err, currentBin, service.BinaryPath)
			}
			if err := os.Chmod(service.BinaryPath, 0755); err != nil {
				return fmt.Errorf("failed to set binary permissions: %w", err)
			}
			fmt.Printf("Binary installed at %s\n", service.BinaryPath)
		}

		// Create config file
		configPath := service.GetConfigPath()
		if svcInstallKey != "" {
			configDir := service.GetConfigDir()
			if err := os.MkdirAll(configDir, 0755); err != nil {
				return fmt.Errorf("failed to create config directory: %w", err)
			}

			chURL := svcInstallCH
			if chURL == "" {
				chURL = config.Defaults.ClickHouseURL
			}
			tURL := svcInstallURL
			if tURL == "" {
				tURL = config.Defaults.TunnelURL
			}

			configContent := fmt.Sprintf(`# CH-UI Configuration
tunnel_token: "%s"
clickhouse_url: "%s"
tunnel_url: "%s"
`, svcInstallKey, chURL, tURL)

			if err := os.WriteFile(configPath, []byte(configContent), 0600); err != nil {
				return fmt.Errorf("failed to write config file: %w", err)
			}
			fmt.Printf("Configuration saved to %s\n", configPath)
		} else if !fileExists(configPath) {
			return fmt.Errorf("no config file found at %s and no --key provided\n\nUsage:\n  ch-ui service install --key <token> --url <server-url>", configPath)
		}

		// Install the service
		fmt.Println("Installing service...")
		if err := svc.Install(configPath); err != nil {
			return fmt.Errorf("failed to install service: %w", err)
		}

		fmt.Println("Service installed and started")
		fmt.Println("  Check status: ch-ui service status")
		fmt.Println("  View logs:    ch-ui service logs -f")
		return nil
	},
}

// ── service uninstall ───────────────────────────────────────────────────────

var (
	svcUninstallPurge bool
	svcUninstallForce bool
)

var serviceUninstallCmd = &cobra.Command{
	Use:   "uninstall",
	Short: "Uninstall the CH-UI service",
	Long:  "Stop and remove the system service. Use --purge to also remove the binary and config files.",
	RunE: func(cmd *cobra.Command, args []string) error {
		svc := service.New()

		if !svc.IsInstalled() && !svcUninstallForce {
			fmt.Println("Service is not installed")
			return nil
		}

		fmt.Println("Stopping service...")
		_ = svc.Stop()

		fmt.Println("Removing service configuration...")
		if err := svc.Uninstall(); err != nil {
			if !svcUninstallForce {
				return fmt.Errorf("failed to uninstall service: %w", err)
			}
			fmt.Printf("Warning: failed to uninstall service: %v (continuing with --force)\n", err)
		}

		fmt.Println("Service uninstalled")

		if svcUninstallPurge {
			if fileExists(service.BinaryPath) {
				fmt.Printf("Removing binary %s...\n", service.BinaryPath)
				if err := os.Remove(service.BinaryPath); err != nil {
					fmt.Printf("Warning: failed to remove binary: %v\n", err)
				} else {
					fmt.Println("Binary removed")
				}
			}
			configDir := service.GetConfigDir()
			if fileExists(configDir) {
				fmt.Printf("Removing config directory %s...\n", configDir)
				if err := os.RemoveAll(configDir); err != nil {
					fmt.Printf("Warning: failed to remove config directory: %v\n", err)
				} else {
					fmt.Println("Configuration removed")
				}
			}
		}
		return nil
	},
}

// ── service start/stop/restart/status/logs ──────────────────────────────────

var serviceStartCmd = &cobra.Command{
	Use: "start", Short: "Start the service",
	RunE: func(cmd *cobra.Command, args []string) error {
		if err := service.New().Start(); err != nil {
			return err
		}
		fmt.Println("Service started")
		return nil
	},
}

var serviceStopCmd = &cobra.Command{
	Use: "stop", Short: "Stop the service",
	RunE: func(cmd *cobra.Command, args []string) error {
		if err := service.New().Stop(); err != nil {
			return err
		}
		fmt.Println("Service stopped")
		return nil
	},
}

var serviceRestartCmd = &cobra.Command{
	Use: "restart", Short: "Restart the service",
	RunE: func(cmd *cobra.Command, args []string) error {
		if err := service.New().Restart(); err != nil {
			return err
		}
		fmt.Println("Service restarted")
		return nil
	},
}

var serviceStatusCmd = &cobra.Command{
	Use:   "status",
	Short: "Show service status",
	RunE: func(cmd *cobra.Command, args []string) error {
		svc := service.New()
		if !svc.IsInstalled() {
			fmt.Println("Service is not installed")
			fmt.Println("Install with: ch-ui service install --key <token> --url <server-url>")
			return nil
		}
		status, err := svc.Status()
		if err != nil {
			return fmt.Errorf("failed to get service status: %w", err)
		}
		running, _ := svc.IsRunning()
		fmt.Println()
		fmt.Printf("  Service:    %s\n", service.ServiceName)
		fmt.Printf("  Status:     %s\n", status)
		fmt.Printf("  Running:    %v\n", running)
		fmt.Printf("  Config:     %s\n", service.GetConfigPath())
		if logPath := svc.GetLogPath(); logPath != "" {
			fmt.Printf("  Logs:       %s\n", logPath)
		}
		fmt.Printf("  Platform:   %s\n", svc.Platform())
		fmt.Println()
		return nil
	},
}

var (
	svcLogsFollow bool
	svcLogsLines  int
)

var serviceLogsCmd = &cobra.Command{
	Use: "logs", Short: "View service logs",
	RunE: func(cmd *cobra.Command, args []string) error {
		return service.New().Logs(svcLogsFollow, svcLogsLines)
	},
}

// ── init ────────────────────────────────────────────────────────────────────

func init() {
	// Install flags
	serviceInstallCmd.Flags().StringVar(&svcInstallKey, "key", "", "Tunnel token (cht_...)")
	serviceInstallCmd.Flags().StringVar(&svcInstallURL, "url", "", "CH-UI server WebSocket URL")
	serviceInstallCmd.Flags().StringVar(&svcInstallCH, "clickhouse-url", "", "ClickHouse HTTP URL")

	// Uninstall flags
	serviceUninstallCmd.Flags().BoolVar(&svcUninstallPurge, "purge", false, "Also remove binary and config files")
	serviceUninstallCmd.Flags().BoolVar(&svcUninstallForce, "force", false, "Force uninstall even if errors occur")

	// Logs flags
	serviceLogsCmd.Flags().BoolVarP(&svcLogsFollow, "follow", "f", false, "Follow log output")
	serviceLogsCmd.Flags().IntVarP(&svcLogsLines, "lines", "n", 50, "Number of log lines to show")

	// Wire up
	serviceCmd.AddCommand(serviceInstallCmd, serviceUninstallCmd, serviceStartCmd, serviceStopCmd, serviceRestartCmd, serviceStatusCmd, serviceLogsCmd)
	rootCmd.AddCommand(serviceCmd)
}


================================================
FILE: cmd/tunnel.go
================================================
package cmd

import (
	"errors"
	"fmt"
	"net/url"
	"os"
	"strings"

	serverconfig "github.com/caioricciuti/ch-ui/internal/config"
	"github.com/caioricciuti/ch-ui/internal/database"
	"github.com/caioricciuti/ch-ui/internal/license"
	"github.com/spf13/cobra"
)

var (
	tunnelConfigPath  string
	tunnelDBPath      string
	tunnelURLOverride string

	tunnelCreateName string
	tunnelShowToken  bool

	tunnelDeleteForce bool
)

var tunnelCmd = &cobra.Command{
	Use:   "tunnel",
	Short: "Manage tunnel keys for remote ClickHouse agents",
	Long: `Create and manage tunnel connection keys in this CH-UI server database.
Run these commands on the server host (VM where CH-UI server stores its SQLite DB)
to bootstrap remote agents from other machines (VM2, VM3, ...).`,
}

var tunnelCreateCmd = &cobra.Command{
	Use:   "create",
	Short: "Create a new tunnel connection and token",
	RunE: func(cmd *cobra.Command, args []string) error {
		name := strings.TrimSpace(tunnelCreateName)
		if name == "" {
			return errors.New("connection name is required (use --name)")
		}

		db, cfg, err := openTunnelDB()
		if err != nil {
			return err
		}
		defer db.Close()

		token := license.GenerateTunnelToken()
		id, err := db.CreateConnection(name, token, false)
		if err != nil {
			return fmt.Errorf("create connection: %w", err)
		}

		conn, err := db.GetConnectionByID(id)
		if err != nil {
			return fmt.Errorf("connection created but failed to load: %w", err)
		}
		if conn == nil {
			return errors.New("connection created but failed to load: not found")
		}

		printTunnelConnectionInfo(cfg, *conn)
		return nil
	},
}

var tunnelListCmd = &cobra.Command{
	Use:   "list",
	Short: "List tunnel connections",
	RunE: func(cmd *cobra.Command, args []string) error {
		db, _, err := openTunnelDB()
		if err != nil {
			return err
		}
		defer db.Close()

		conns, err := db.GetConnections()
		if err != nil {
			return fmt.Errorf("list connections: %w", err)
		}

		if len(conns) == 0 {
			fmt.Println("No tunnel connections found.")
			fmt.Println("Create one with: ch-ui tunnel create --name <connection-name>")
			return nil
		}

		fmt.Printf("%-36s  %-22s  %-12s  %-8s  %-35s\n", "ID", "NAME", "STATUS", "EMBEDDED", "TOKEN")
		for _, c := range conns {
			token := maskToken(c.TunnelToken)
			if tunnelShowToken {
				token = c.TunnelToken
			}
			embedded := "no"
			if c.IsEmbedded {
				embedded = "yes"
			}
			fmt.Printf("%-36s  %-22s  %-12s  %-8s  %-35s\n",
				c.ID,
				truncate(c.Name, 22),
				c.Status,
				embedded,
				token,
			)
		}
		return nil
	},
}

var tunnelShowCmd = &cobra.Command{
	Use:   "show <connection-id>",
	Short: "Show token and setup instructions for a connection",
	Args:  cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		db, cfg, err := openTunnelDB()
		if err != nil {
			return err
		}
		defer db.Close()

		connID := strings.TrimSpace(args[0])
		conn, err := db.GetConnectionByID(connID)
		if err != nil {
			return fmt.Errorf("load connection: %w", err)
		}
		if conn == nil {
			return fmt.Errorf("connection %q not found", connID)
		}

		printTunnelConnectionInfo(cfg, *conn)
		return nil
	},
}

var tunnelRotateCmd = &cobra.Command{
	Use:   "rotate <connection-id>",
	Short: "Rotate (regenerate) tunnel token for a connection",
	Args:  cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		db, cfg, err := openTunnelDB()
		if err != nil {
			return err
		}
		defer db.Close()

		connID := strings.TrimSpace(args[0])
		conn, err := db.GetConnectionByID(connID)
		if err != nil {
			return fmt.Errorf("load connection: %w", err)
		}
		if conn == nil {
			return fmt.Errorf("connection %q not found", connID)
		}

		newToken := license.GenerateTunnelToken()
		if err := db.UpdateConnectionToken(connID, newToken); err != nil {
			return fmt.Errorf("rotate token: %w", err)
		}

		updated, err := db.GetConnectionByID(connID)
		if err != nil {
			return fmt.Errorf("token rotated but failed to reload connection: %w", err)
		}
		if updated == nil {
			return errors.New("token rotated but failed to reload connection: not found")
		}

		fmt.Println("Token rotated successfully. Previous token is now invalid.")
		printTunnelConnectionInfo(cfg, *updated)
		return nil
	},
}

var tunnelDeleteCmd = &cobra.Command{
	Use:   "delete <connection-id>",
	Short: "Delete a tunnel connection",
	Args:  cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		db, _, err := openTunnelDB()
		if err != nil {
			return err
		}
		defer db.Close()

		connID := strings.TrimSpace(args[0])
		conn, err := db.GetConnectionByID(connID)
		if err != nil {
			return fmt.Errorf("load connection: %w", err)
		}
		if conn == nil {
			return fmt.Errorf("connection %q not found", connID)
		}
		if conn.IsEmbedded && !tunnelDeleteForce {
			return errors.New("refusing to delete embedded connection without --force")
		}

		if err := db.DeleteConnection(connID); err != nil {
			return fmt.Errorf("delete connection: %w", err)
		}

		fmt.Printf("Deleted connection %q (%s)\n", conn.Name, conn.ID)
		return nil
	},
}

func init() {
	tunnelCmd.PersistentFlags().StringVarP(&tunnelConfigPath, "config", "c", "", "Path to server config file")
	tunnelCmd.PersistentFlags().StringVar(&tunnelDBPath, "db", "", "Override SQLite database path")
	tunnelCmd.PersistentFlags().StringVar(&tunnelURLOverride, "url", "", "Public tunnel URL (ws:// or wss://) for setup output")

	tunnelCreateCmd.Flags().StringVar(&tunnelCreateName, "name", "", "Connection name (e.g. VM2 ClickHouse)")
	_ = tunnelCreateCmd.MarkFlagRequired("name")

	tunnelListCmd.Flags().BoolVar(&tunnelShowToken, "show-token", false, "Show full tunnel tokens")

	tunnelDeleteCmd.Flags().BoolVar(&tunnelDeleteForce, "force", false, "Force delete embedded connection")

	tunnelCmd.AddCommand(tunnelCreateCmd, tunnelListCmd, tunnelShowCmd, tunnelRotateCmd, tunnelDeleteCmd)
	rootCmd.AddCommand(tunnelCmd)
}

func openTunnelDB() (*database.DB, *serverconfig.Config, error) {
	cfg := serverconfig.Load(tunnelConfigPath)
	if strings.TrimSpace(tunnelDBPath) != "" {
		cfg.DatabasePath = strings.TrimSpace(tunnelDBPath)
	}

	db, err := database.Open(cfg.DatabasePath)
	if err != nil {
		return nil, nil, fmt.Errorf("open database %q: %w", cfg.DatabasePath, err)
	}
	return db, cfg, nil
}

func printTunnelConnectionInfo(cfg *serverconfig.Config, conn database.Connection) {
	tunnelURL := inferPublicTunnelURL(cfg)
	token := conn.TunnelToken

	connectCmd := fmt.Sprintf("ch-ui connect --url %s --key %s --clickhouse-url http://localhost:8123", tunnelURL, token)
	serviceCmd := fmt.Sprintf("ch-ui service install --url %s --key %s --clickhouse-url http://localhost:8123", tunnelURL, token)

	fmt.Println()
	fmt.Printf("Connection:         %s\n", conn.Name)
	fmt.Printf("Connection ID:      %s\n", conn.ID)
	fmt.Printf("Tunnel Token:       %s\n", token)
	fmt.Println()
	fmt.Println("Use on the ClickHouse host:")
	fmt.Printf("  %s\n", connectCmd)
	fmt.Println()
	fmt.Println("Run as service on the ClickHouse host:")
	fmt.Printf("  %s\n", serviceCmd)
	fmt.Println()

	if isLoopbackTunnelURL(tunnelURL) {
		fmt.Fprintf(os.Stderr, "Warning: tunnel URL %q is loopback/local. Set --url or APP_URL/TUNNEL_URL in server config for remote VM setup.\n", tunnelURL)
	}
}

func inferPublicTunnelURL(cfg *serverconfig.Config) string {
	if strings.TrimSpace(tunnelURLOverride) != "" {
		return websocketConnectURL(strings.TrimSpace(tunnelURLOverride))
	}

	configTunnelURL := strings.TrimSpace(cfg.TunnelURL)
	if configTunnelURL != "" && !isLoopbackTunnelURL(configTunnelURL) {
		return websocketConnectURL(configTunnelURL)
	}

	if appURL := strings.TrimSpace(cfg.AppURL); appURL != "" {
		return websocketConnectURL(appURL)
	}

	if configTunnelURL != "" {
		return websocketConnectURL(configTunnelURL)
	}

	return "ws://127.0.0.1:3488/connect"
}

func websocketConnectURL(raw string) string {
	u, err := url.Parse(raw)
	if err != nil {
		return raw
	}
	switch strings.ToLower(u.Scheme) {
	case "http":
		u.Scheme = "ws"
	case "https":
		u.Scheme = "wss"
	case "ws", "wss":
		// already websocket scheme
	default:
		// keep as-is (can still be validated by caller command later)
	}

	path := strings.TrimRight(u.Path, "/")
	if path == "" {
		u.Path = "/connect"
	} else if !strings.HasSuffix(path, "/connect") {
		u.Path = path + "/connect"
	}

	u.RawQuery = ""
	u.Fragment = ""
	return u.String()
}

func isLoopbackTunnelURL(raw string) bool {
	u, err := url.Parse(raw)
	if err != nil {
		s := strings.ToLower(raw)
		return strings.Contains(s, "127.0.0.1") || strings.Contains(s, "localhost")
	}
	host := strings.ToLower(u.Hostname())
	return host == "127.0.0.1" || host == "localhost" || host == "::1"
}

func maskToken(token string) string {
	if len(token) <= 12 {
		return token
	}
	return token[:8] + "..." + token[len(token)-4:]
}

func truncate(s string, max int) string {
	if max < 4 || len(s) <= max {
		return s
	}
	return s[:max-3] + "..."
}


================================================
FILE: cmd/uninstall.go
================================================
package cmd

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/caioricciuti/ch-ui/connector/service"
	serverconfig "github.com/caioricciuti/ch-ui/internal/config"
	"github.com/spf13/cobra"
)

var (
	uninstallConfigPath string
	uninstallDBPath     string
	uninstallForce      bool
	uninstallPrintOnly  bool
	uninstallPIDFiles   []string
)

type uninstallPlan struct {
	serverConfigPath string
	databasePath     string
	serverPIDFiles   []string
	connectorPIDFile string
	cleanupPaths     []string
}

var uninstallCmd = &cobra.Command{
	Use:   "uninstall",
	Short: "Uninstall CH-UI from this machine",
	Long: `Best-effort local uninstall for CH-UI.
This command stops services/processes, removes local CH-UI files, and prints manual
cleanup commands for anything that still requires privileged shell access.`,
	RunE: runUninstall,
}

func init() {
	uninstallCmd.Flags().StringVarP(&uninstallConfigPath, "config", "c", "", "Path to server config file (used to locate database)")
	uninstallCmd.Flags().StringVar(&uninstallDBPath, "db", "", "Override server SQLite database path")
	uninstallCmd.Flags().BoolVar(&uninstallForce, "force", false, "Continue uninstall even if some steps fail")
	uninstallCmd.Flags().BoolVar(&uninstallPrintOnly, "print-only", false, "Only print cleanup commands without executing uninstall")
	uninstallCmd.Flags().StringSliceVar(&uninstallPIDFiles, "pid-file", nil, "Additional server PID file path to stop/remove (repeatable)")

	rootCmd.AddCommand(uninstallCmd)
}

func runUninstall(cmd *cobra.Command, args []string) error {
	plan := buildUninstallPlan()

	fmt.Println("CH-UI uninstall (best effort)")
	fmt.Printf("Server config: %s\n", plan.serverConfigPath)
	fmt.Printf("Database:      %s\n", plan.databasePath)

	if uninstallPrintOnly {
		printManualUninstallCommands(plan)
		return nil
	}

	var failures []string

	if err := stopDetachedConnectProcess(plan.connectorPIDFile); err != nil {
		failures = append(failures, err.Error())
	}

	for _, pidFile := range plan.serverPIDFiles {
		if err := stopServerByPIDFile(pidFile); err != nil {
			failures = append(failures, err.Error())
		}
	}

	if err := uninstallConnectorService(); err != nil {
		failures = append(failures, err.Error())
	}

	if err := uninstallServerSystemService(); err != nil {
		failures = append(failures, err.Error())
	}

	for _, p := range plan.cleanupPaths {
		removed, err := removePathIfExists(p)
		if err != nil {
			failures = append(failures, fmt.Sprintf("remove %s: %v", p, err))
			continue
		}
		if removed {
			fmt.Printf("Removed %s\n", p)
		}
	}

	printManualUninstallCommands(plan)

	if len(failures) == 0 {
		fmt.Println("Uninstall completed.")
		return nil
	}

	fmt.Println("Uninstall completed with warnings:")
	for _, failure := range failures {
		fmt.Printf("  - %s\n", failure)
	}

	if uninstallForce {
		return nil
	}

	return errors.New("one or more uninstall steps failed (rerun with --force to continue)" +
		"\nUse the manual cleanup commands shown above")
}

func buildUninstallPlan() uninstallPlan {
	cfg := serverconfig.Load(uninstallConfigPath)

	if strings.TrimSpace(uninstallDBPath) != "" {
		cfg.DatabasePath = strings.TrimSpace(uninstallDBPath)
	}

	serverConfigPath := strings.TrimSpace(uninstallConfigPath)
	if serverConfigPath == "" {
		serverConfigPath = serverconfig.DefaultServerConfigPath()
	}

	pidFiles := append([]string{"ch-ui-server.pid", "/var/lib/ch-ui/run/ch-ui-server.pid"}, uninstallPIDFiles...)
	pidFiles = uniqueNonEmpty(pidFiles)

	cleanupPaths := []string{
		service.BinaryPath,
		service.GetConfigDir(),
		serverConfigPath,
		cfg.DatabasePath,
		"ch-ui-server.log",
	}
	cleanupPaths = append(cleanupPaths, pidFiles...)

	if runtime.GOOS == "darwin" {
		home, _ := os.UserHomeDir()
		cleanupPaths = append(cleanupPaths,
			filepath.Join(home, "Library", "LaunchAgents", service.ServiceLabel+".plist"),
			filepath.Join(home, "Library", "Logs", "ch-ui"),
		)
	}

	if runtime.GOOS == "linux" {
		cleanupPaths = append(cleanupPaths,
			"/etc/systemd/system/ch-ui.service",
			"/etc/systemd/system/ch-ui-server.service",
		)
	}

	cleanupPaths = uniqueNonEmpty(cleanupPaths)

	return uninstallPlan{
		serverConfigPath: serverConfigPath,
		databasePath:     cfg.DatabasePath,
		serverPIDFiles:   pidFiles,
		connectorPIDFile: filepath.Join(service.GetConfigDir(), "ch-ui.pid"),
		cleanupPaths:     cleanupPaths,
	}
}

func stopDetachedConnectProcess(pidFile string) error {
	pid, err := readPIDFile(pidFile)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return fmt.Errorf("read connect pid file %s: %w", pidFile, err)
	}

	if !processExists(pid) {
		_ = os.Remove(pidFile)
		return nil
	}

	proc, err := os.FindProcess(pid)
	if err != nil {
		return fmt.Errorf("locate connect process %d: %w", pid, err)
	}
	if err := proc.Signal(syscall.SIGTERM); err != nil {
		return fmt.Errorf("stop connect process %d: %w", pid, err)
	}

	deadline := time.Now().Add(10 * time.Second)
	for time.Now().Before(deadline) {
		if !processExists(pid) {
			_ = os.Remove(pidFile)
			fmt.Printf("Stopped connect process (PID %d)\n", pid)
			return nil
		}
		time.Sleep(200 * time.Millisecond)
	}

	return fmt.Errorf("timeout waiting for connect process %d to stop", pid)
}

func stopServerByPIDFile(pidFile string) error {
	pid, running, err := getRunningServerPID(pidFile)
	if err != nil {
		return fmt.Errorf("inspect server pid file %s: %w", pidFile, err)
	}
	if !running {
		return nil
	}

	stopped, err := stopServer(pidFile, 10*time.Second)
	if err != nil {
		return fmt.Errorf("stop server process %d from %s: %w", pid, pidFile, err)
	}
	if stopped {
		fmt.Printf("Stopped server process (PID %d) from %s\n", pid, pidFile)
	}
	return nil
}

func uninstallConnectorService() error {
	svc := service.New()
	if !svc.IsInstalled() {
		fmt.Println("Connector service is not installed")
		return nil
	}

	fmt.Println("Stopping connector service...")
	_ = svc.Stop()

	fmt.Println("Uninstalling connector service...")
	if err := svc.Uninstall(); err != nil {
		return fmt.Errorf("uninstall connector service: %w", err)
	}

	fmt.Println("Connector service uninstalled")
	return nil
}

func uninstallServerSystemService() error {
	if runtime.GOOS != "linux" {
		return nil
	}

	var warnings []string
	steps := [][]string{
		{"systemctl", "stop", "ch-ui-server"},
		{"systemctl", "disable", "ch-ui-server"},
		{"systemctl", "daemon-reload"},
	}

	for _, step := range steps {
		if err := runPrivileged(step[0], step[1:]...); err != nil {
			warnings = append(warnings, fmt.Sprintf("%s: %v", strings.Join(step, " "), err))
		}
	}

	if len(warnings) == 0 {
		return nil
	}

	return errors.New(strings.Join(warnings, "; "))
}

func runPrivileged(name string, args ...string) error {
	cmdName := name
	cmdArgs := args
	if runtime.GOOS == "linux" && os.Geteuid() != 0 {
		cmdArgs = append([]string{name}, args...)
		cmdName = "sudo"
	}

	cmd := exec.Command(cmdName, cmdArgs...)
	out, err := cmd.CombinedOutput()
	if err == nil {
		return nil
	}

	msg := strings.TrimSpace(string(out))
	if msg == "" {
		return err
	}
	return fmt.Errorf("%w: %s", err, msg)
}

func removePathIfExists(path string) (bool, error) {
	path = strings.TrimSpace(path)
	if path == "" {
		return false, nil
	}
	if path == "/" {
		return false, fmt.Errorf("refusing to remove root path")
	}

	info, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}

	if info.IsDir() {
		if err := os.RemoveAll(path); err != nil {
			return false, err
		}
		return true, nil
	}

	if err := os.Remove(path); err != nil {
		return false, err
	}
	return true, nil
}

func printManualUninstallCommands(plan uninstallPlan) {
	fmt.Println()
	fmt.Println("Manual cleanup commands (run if anything remains):")

	for _, cmd := range manualUninstallCommands(plan) {
		fmt.Printf("  %s\n", cmd)
	}

	fmt.Println()
	fmt.Println("Optional verification:")
	fmt.Println("  ch-ui version")
	fmt.Println("  ch-ui service status")
	fmt.Println("  ch-ui server status")
}

func manualUninstallCommands(plan uninstallPlan) []string {
	commands := []string{}

	quotedConfig := shellQuote(plan.serverConfigPath)
	quotedDB := shellQuote(plan.databasePath)

	switch runtime.GOOS {
	case "darwin":
		home, _ := os.UserHomeDir()
		launchAgent := filepath.Join(home, "Library", "LaunchAgents", service.ServiceLabel+".plist")
		logDir := filepath.Join(home, "Library", "Logs", "ch-ui")

		commands = append(commands,
			"launchctl unload "+shellQuote(launchAgent)+" 2>/dev/null || true",
			"rm -f "+shellQuote(launchAgent),
			"rm -rf "+shellQuote(service.GetConfigDir()),
			"rm -rf "+shellQuote(logDir),
			"rm -f "+shellQuote(service.BinaryPath),
			"rm -f "+quotedConfig,
			"rm -f "+quotedDB,
		)
	default:
		commands = append(commands,
			"sudo systemctl stop ch-ui 2>/dev/null || true",
			"sudo systemctl disable ch-ui 2>/dev/null || true",
			"sudo rm -f /etc/systemd/system/ch-ui.service",
			"sudo systemctl stop ch-ui-server 2>/dev/null || true",
			"sudo systemctl disable ch-ui-server 2>/dev/null || true",
			"sudo rm -f /etc/systemd/system/ch-ui-server.service",
			"sudo systemctl daemon-reload",
			"sudo rm -rf "+shellQuote(service.GetConfigDir()),
			"sudo rm -f "+shellQuote(service.BinaryPath),
			"sudo rm -f "+quotedConfig,
			"sudo rm -f "+quotedDB,
		)
	}

	if len(plan.serverPIDFiles) > 0 {
		var quoted []string
		for _, p := range plan.serverPIDFiles {
			quoted = append(quoted, shellQuote(p))
		}
		commands = append(commands, "rm -f "+strings.Join(quoted, " "))
	}
	commands = append(commands,
		"rm -f "+shellQuote("ch-ui-server.log"),
	)

	return commands
}

func shellQuote(s string) string {
	return "'" + strings.ReplaceAll(s, "'", "'\\''") + "'"
}

func uniqueNonEmpty(in []string) []string {
	seen := make(map[string]struct{}, len(in))
	out := make([]string, 0, len(in))
	for _, raw := range in {
		p := strings.TrimSpace(raw)
		if p == "" {
			continue
		}
		if _, ok := seen[p]; ok {
			continue
		}
		seen[p] = struct{}{}
		out = append(out, p)
	}
	return out
}


================================================
FILE: cmd/update.go
================================================
package cmd

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/caioricciuti/ch-ui/internal/version"
	"github.com/spf13/cobra"
)

const (
	releasesURL = "https://api.github.com/repos/caioricciuti/ch-ui/releases/latest"
)

type ghRelease struct {
	TagName string    `json:"tag_name"`
	Assets  []ghAsset `json:"assets"`
}

type ghAsset struct {
	Name               string `json:"name"`
	BrowserDownloadURL string `json:"browser_download_url"`
}

var updateCmd = &cobra.Command{
	Use:   "update",
	Short: "Update CH-UI to the latest version",
	Long:  "Download the latest CH-UI release from GitHub and replace the current binary.",
	RunE:  runUpdate,
}

var (
	updateRestartServer bool
	updatePIDFile       string
	updateStopTimeout   time.Duration
)

func init() {
	updateCmd.Flags().BoolVar(&updateRestartServer, "restart-server", true, "Automatically restart a running CH-UI server after update")
	updateCmd.Flags().StringVar(&updatePIDFile, "pid-file", "ch-ui-server.pid", "Server PID file path used to detect/restart a running server")
	updateCmd.Flags().DurationVar(&updateStopTimeout, "stop-timeout", 10*time.Second, "Graceful stop timeout used when restarting after update")
	rootCmd.AddCommand(updateCmd)
}

func runUpdate(cmd *cobra.Command, args []string) error {
	// Resolve PID file to absolute path so we can detect the running
	// server regardless of the caller's working directory.
	updatePIDFile = resolvePIDFile(updatePIDFile)

	// Resolve current binary path
	currentBin, err := os.Executable()
	if err != nil {
		return fmt.Errorf("failed to determine current binary path: %w", err)
	}
	currentBin, err = filepath.EvalSymlinks(currentBin)
	if err != nil {
		return fmt.Errorf("failed to resolve binary path: %w", err)
	}

	var runningPID int
	var running bool
	restartArgs := []string{"server", "--pid-file", updatePIDFile}
	if updateRestartServer {
		runningPID, running, err = getRunningServerPID(updatePIDFile)
		if err != nil {
			return fmt.Errorf("failed to inspect server status via PID file %q: %w", updatePIDFile, err)
		}
		if running {
			restartArgs = detectServerRestartArgs(runningPID, updatePIDFile)
			fmt.Printf("Detected running CH-UI server (PID %d); it will be restarted after update.\n", runningPID)
		}
	}

	// Check write permissions
	dir := filepath.Dir(currentBin)
	if err := checkWritable(dir); err != nil {
		return fmt.Errorf("cannot write to %s: %w (try running with sudo)", dir, err)
	}

	fmt.Printf("Current version: %s\n", version.Version)
	fmt.Println("Checking for updates...")

	// Fetch latest release info
	release, err := fetchLatestRelease()
	if err != nil {
		return fmt.Errorf("failed to check for updates: %w", err)
	}

	latestTag := release.TagName
	if latestTag == version.Version {
		fmt.Printf("Already up to date (%s)\n", version.Version)
		return nil
	}

	fmt.Printf("New version available: %s → %s\n", version.Version, latestTag)

	// Find the right asset for this platform
	assetName := fmt.Sprintf("ch-ui-%s-%s", runtime.GOOS, runtime.GOARCH)
	var assetURL string
	var checksumsURL string
	for _, a := range release.Assets {
		if a.Name == assetName {
			assetURL = a.BrowserDownloadURL
		}
		if a.Name == "checksums.txt" {
			checksumsURL = a.BrowserDownloadURL
		}
	}
	if assetURL == "" {
		return fmt.Errorf("no release asset found for %s/%s (expected %s)", runtime.GOOS, runtime.GOARCH, assetName)
	}

	// Download checksums
	var expectedHash string
	if checksumsURL != "" {
		expectedHash, err = fetchExpectedChecksum(checksumsURL, assetName)
		if err != nil {
			fmt.Printf("Warning: could not verify checksum: %v\n", err)
		}
	}

	// Download binary to temp file in the same directory (for atomic rename)
	tmpPath := currentBin + ".update-tmp"
	fmt.Printf("Downloading %s...\n", assetName)
	if err := downloadFile(assetURL, tmpPath); err != nil {
		os.Remove(tmpPath)
		return fmt.Errorf("failed to download update: %w", err)
	}

	// Verify checksum
	if expectedHash != "" {
		actualHash, err := fileSHA256(tmpPath)
		if err != nil {
			os.Remove(tmpPath)
			return fmt.Errorf("failed to compute checksum: %w", err)
		}
		if actualHash != expectedHash {
			os.Remove(tmpPath)
			return fmt.Errorf("checksum mismatch: expected %s, got %s", expectedHash, actualHash)
		}
		fmt.Println("Checksum verified ✓")
	}

	// Make executable
	if err := os.Chmod(tmpPath, 0755); err != nil {
		os.Remove(tmpPath)
		return fmt.Errorf("failed to set permissions: %w", err)
	}

	// Atomic replace
	if err := os.Rename(tmpPath, currentBin); err != nil {
		os.Remove(tmpPath)
		return fmt.Errorf("failed to replace binary: %w", err)
	}

	fmt.Printf("Updated successfully: %s → %s\n", version.Version, latestTag)

	if !updateRestartServer || !running {
		fmt.Println("Restart CH-UI to use the new version.")
		return nil
	}

	fmt.Printf("Restarting CH-UI server (PID %d)...\n", runningPID)
	stopped, err := stopServer(updatePIDFile, updateStopTimeout)
	if err != nil {
		return fmt.Errorf("binary updated to %s but failed to stop running server: %w", latestTag, err)
	}
	if !stopped {
		return fmt.Errorf("binary updated to %s but could not confirm server stop; run `ch-ui server restart --detach --pid-file %s`", latestTag, updatePIDFile)
	}

	prevPIDFile := serverPIDFile
	serverPIDFile = updatePIDFile
	pid, logPath, err := startDetachedServer(restartArgs)
	serverPIDFile = prevPIDFile
	if err != nil {
		return fmt.Errorf("binary updated to %s and server stopped, but failed to start it again: %w", latestTag, err)
	}

	fmt.Printf("CH-UI server restarted in background (PID %d)\n", pid)
	if logPath != "" {
		fmt.Printf("Logs: %s\n", logPath)
	}
	fmt.Println("Update complete and running the new version.")
	return nil
}

func detectServerRestartArgs(pid int, pidFile string) []string {
	args, err := readProcessArgs(pid)
	if err != nil {
		return []string{"server", "--pid-file", pidFile}
	}
	sanitized := sanitizeServerStartArgs(args, pidFile)
	if len(sanitized) == 0 {
		return []string{"server", "--pid-file", pidFile}
	}
	return sanitized
}

func readProcessArgs(pid int) ([]string, error) {
	if runtime.GOOS != "linux" {
		return nil, fmt.Errorf("unsupported OS for process args inspection: %s", runtime.GOOS)
	}
	data, err := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
	if err != nil {
		return nil, err
	}
	parts := strings.Split(string(data), "\x00")
	if len(parts) == 0 {
		return nil, fmt.Errorf("empty cmdline for PID %d", pid)
	}
	out := make([]string, 0, len(parts))
	for i, part := range parts {
		if i == 0 {
			continue // executable path
		}
		if strings.TrimSpace(part) == "" {
			continue
		}
		out = append(out, part)
	}
	return out, nil
}

func sanitizeServerStartArgs(args []string, pidFile string) []string {
	// Safe fallback that keeps behavior predictable.
	out := []string{"server"}

	// Expect args from the running server process to start with "server"
	// (or "server start" in older/manual invocations).
	i := 0
	if len(args) > 0 && args[0] == "server" {
		i = 1
		if i < len(args) && args[i] == "start" {
			i++
		}
	}

	for i < len(args) {
		a := args[i]

		switch {
		case a == "server" || a == "start" || a == "stop" || a == "status" || a == "restart":
			i++
		case a == "--detach" || a == "-h" || a == "--help":
			i++
		case a == "--dev":
			out = append(out, a)
			i++
		case a == "--port" || a == "-p" ||
			a == "--config" || a == "-c" ||
			a == "--clickhouse-url" ||
			a == "--connection-name" ||
			a == "--stop-timeout":
			if i+1 < len(args) {
				out = append(out, a, args[i+1])
				i += 2
				continue
			}
			i++
		case a == "--pid-file":
			if i+1 < len(args) {
				out = append(out, a, resolvePIDFile(args[i+1]))
				i += 2
				continue
			}
			i++
		case strings.HasPrefix(a, "--port=") ||
			strings.HasPrefix(a, "--config=") ||
			strings.HasPrefix(a, "--clickhouse-url=") ||
			strings.HasPrefix(a, "--connection-name=") ||
			strings.HasPrefix(a, "--stop-timeout="):
			out = append(out, a)
			i++
		case strings.HasPrefix(a, "--pid-file="):
			val := strings.TrimPrefix(a, "--pid-file=")
			out = append(out, "--pid-file="+resolvePIDFile(val))
			i++
		default:
			i++
		}
	}

	if !hasFlag(out, "--pid-file") {
		out = append(out, "--pid-file", pidFile)
	}
	return out
}

func hasFlag(args []string, longName string) bool {
	for _, a := range args {
		if a == longName || strings.HasPrefix(a, longName+"=") {
			return true
		}
	}
	return false
}

func fetchLatestRelease() (*ghRelease, error) {
	resp, err := http.Get(releasesURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("GitHub API returned %s", resp.Status)
	}

	var release ghRelease
	if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
		return nil, fmt.Errorf("failed to parse release info: %w", err)
	}
	return &release, nil
}

func fetchExpectedChecksum(url, assetName string) (string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	for _, line := range strings.Split(string(body), "\n") {
		parts := strings.Fields(line)
		if len(parts) == 2 && parts[1] == assetName {
			return parts[0], nil
		}
	}
	return "", fmt.Errorf("checksum not found for %s", assetName)
}

func downloadFile(url, dest string) error {
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("download returned %s", resp.Status)
	}

	f, err := os.Create(dest)
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = io.Copy(f, resp.Body)
	return err
}

func fileSHA256(path string) (string, error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()

	h := sha256.New()
	if _, err := io.Copy(h, f); err != nil {
		return "", err
	}
	return hex.EncodeToString(h.Sum(nil)), nil
}

func checkWritable(dir string) error {
	tmp := filepath.Join(dir, ".ch-ui-update-check")
	f, err := os.Create(tmp)
	if err != nil {
		return err
	}
	f.Close()
	return os.Remove(tmp)
}


================================================
FILE: cmd/version.go
================================================
package cmd

import (
	"fmt"

	"github.com/caioricciuti/ch-ui/internal/version"
	"github.com/spf13/cobra"
)

var versionCmd = &cobra.Command{
	Use:   "version",
	Short: "Print version information",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Printf("ch-ui %s (commit: %s, built: %s)\n", version.Version, version.Commit, version.BuildDate)
	},
}

func init() {
	rootCmd.AddCommand(versionCmd)
}


================================================
FILE: connector/clickhouse.go
================================================
package connector

import (
	"bufio"
	"context"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// CHClient handles ClickHouse query execution
type CHClient struct {
	baseURL   string
	transport *http.Transport
	httpClient *http.Client
}

// NewCHClient creates a new ClickHouse HTTP client
func NewCHClient(baseURL string, insecureSkipVerify bool) *CHClient {
	transport := &http.Transport{
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		TLSClientConfig:       &tls.Config{InsecureSkipVerify: insecureSkipVerify},
		MaxIdleConns:          100,
		MaxIdleConnsPerHost:   10,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
		ResponseHeaderTimeout: 5 * time.Minute,
		DisableKeepAlives:     false,
		ForceAttemptHTTP2:     true,
	}

	return &CHClient{
		baseURL:   strings.TrimSuffix(baseURL, "/"),
		transport: transport,
		httpClient: &http.Client{
			Transport: transport,
			Timeout:   5 * time.Minute,
		},
	}
}

// QueryResult holds the result of a query execution
type QueryResult struct {
	Data       []map[string]interface{} `json:"data"`
	Meta       []ColumnMeta             `json:"meta"`
	Rows       int                      `json:"rows"`
	Statistics struct {
		Elapsed   float64 `json:"elapsed"`
		RowsRead  uint64  `json:"rows_read"`
		BytesRead uint64  `json:"bytes_read"`
	} `json:"statistics"`
}

// ColumnMeta describes a column in the result
type ColumnMeta struct {
	Name string `json:"name"`
	Type string `json:"type"`
}

// isTransientError checks if an error is a transient connection error that
// should be retried (e.g. server closed an idle keep-alive connection).
func isTransientError(err error) bool {
	if err == nil {
		return false
	}
	s := err.Error()
	if strings.Contains(s, "unexpected EOF") ||
		strings.Contains(s, "connection reset by peer") ||
		strings.Contains(s, "transport connection broken") ||
		strings.Contains(s, "use of closed network connection") {
		return true
	}
	var netErr net.Error
	if errors.As(err, &netErr) && netErr.Timeout() {
		return false // real timeouts should not be retried
	}
	return false
}

// doWithRetry executes an HTTP request, retrying once on transient connection errors.
func (c *CHClient) doWithRetry(req *http.Request, client *http.Client) (*http.Response, error) {
	resp, err := client.Do(req)
	if err != nil && isTransientError(err) {
		// Close any idle connections that may be stale, then retry once.
		c.transport.CloseIdleConnections()

		// Clone the request for retry (the body must be re-readable).
		retryReq := req.Clone(req.Context())
		if req.GetBody != nil {
			body, bodyErr := req.GetBody()
			if bodyErr != nil {
				return nil, err // return original error
			}
			retryReq.Body = body
		}
		return client.Do(retryReq)
	}
	return resp, err
}

// Execute runs a query against ClickHouse
func (c *CHClient) Execute(ctx context.Context, query, user, password string) (*QueryResult, error) {
	// Determine if this is a read or write query
	isWrite := isWriteQuery(query)
	hasFormat := hasFormatClause(query)

	// Build URL with parameters
	params := url.Values{}
	params.Set("default_format", "JSON")

	// For read queries without explicit FORMAT, add FORMAT JSON
	finalQuery := query
	if !isWrite && !hasFormat {
		finalQuery = strings.TrimRight(query, "; \n\t") + " FORMAT JSON"
	}

	fullURL := c.baseURL + "/?" + params.Encode()

	// Create request
	req, err := http.NewRequestWithContext(ctx, "POST", fullURL, strings.NewReader(finalQuery))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	// Set auth if provided
	if user != "" {
		req.SetBasicAuth(user, password)
	}

	req.Header.Set("Content-Type", "text/plain")

	// GetBody allows doWithRetry to re-create the body on retry
	bodyStr := finalQuery
	req.GetBody = func() (io.ReadCloser, error) {
		return io.NopCloser(strings.NewReader(bodyStr)), nil
	}

	// Execute with retry on transient connection errors
	resp, err := c.doWithRetry(req, c.httpClient)
	if err != nil {
		return nil, fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	// Check for errors
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("ClickHouse error: %s", string(body))
	}

	// For write queries or queries with explicit format, we may get empty body
	if len(body) == 0 || (isWrite && !hasFormat) {
		return &QueryResult{
			Data: []map[string]interface{}{},
			Meta: []ColumnMeta{},
			Rows: 0,
		}, nil
	}

	// Parse JSON response
	var result QueryResult
	if err := json.Unmarshal(body, &result); err != nil {
		// If JSON parse fails but status was OK, treat as DDL success
		if isWrite {
			return &QueryResult{
				Data: []map[string]interface{}{},
				Meta: []ColumnMeta{},
				Rows: 0,
			}, nil
		}
		return nil, fmt.Errorf("failed to parse response: %w (body: %s)", err, truncate(string(body), 200))
	}

	return &result, nil
}

// ExecuteRaw runs a query and returns the raw ClickHouse response bytes without intermediate parsing.
// The format parameter controls the FORMAT clause appended to read queries (e.g. "JSONCompact").
func (c *CHClient) ExecuteRaw(ctx context.Context, query, user, password, format string) (json.RawMessage, error) {
	isWrite := isWriteQuery(query)
	hasFormat := hasFormatClause(query)

	finalQuery := query
	if !isWrite && !hasFormat {
		if format == "" {
			format = "JSON"
		}
		finalQuery = strings.TrimRight(query, "; \n\t") + " FORMAT " + format
	}

	params := url.Values{}
	params.Set("default_format", "JSON")
	fullURL := c.baseURL + "/?" + params.Encode()

	req, err := http.NewRequestWithContext(ctx, "POST", fullURL, strings.NewReader(finalQuery))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}
	if user != "" {
		req.SetBasicAuth(user, password)
	}
	req.Header.Set("Content-Type", "text/plain")

	// GetBody allows doWithRetry to re-create the body on retry
	bodyStr := finalQuery
	req.GetBody = func() (io.ReadCloser, error) {
		return io.NopCloser(strings.NewReader(bodyStr)), nil
	}

	resp, err := c.doWithRetry(req, c.httpClient)
	if err != nil {
		return nil, fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("ClickHouse error: %s", string(body))
	}

	if len(body) == 0 || (isWrite && !hasFormat) {
		return json.RawMessage(`{"data":[],"meta":[],"rows":0}`), nil
	}

	return json.RawMessage(body), nil
}

// StreamChunk holds a batch of rows for streaming execution.
type StreamChunk struct {
	Seq  int               `json:"seq"`
	Data json.RawMessage   `json:"data"` // JSON array of arrays: [[v1,v2],[v3,v4],...]
}

// ExecuteStreaming runs a query using JSONCompactEachRow format, reading the response
// line-by-line without buffering the entire result. It calls onMeta with column metadata,
// then onChunk for each batch of chunkSize rows, and returns final statistics.
func (c *CHClient) ExecuteStreaming(
	ctx context.Context,
	query, user, password string,
	chunkSize int,
	settings map[string]string,
	onMeta func(meta json.RawMessage) error,
	onChunk func(seq int, data json.RawMessage) error,
) (*json.RawMessage, int64, error) {
	isWrite := isWriteQuery(query)
	hasFormat := hasFormatClause(query)

	if chunkSize <= 0 {
		chunkSize = 5000
	}

	// Get column metadata via a LIMIT 0 query with JSONCompact, or send empty meta for writes
	if !isWrite && !hasFormat {
		trimmed := strings.TrimRight(query, "; \n\t")
		var metaQuery string
		if limitRe := regexp.MustCompile(`(?i)\bLIMIT\s+\d+(\s*,\s*\d+)?(\s+OFFSET\s+\d+)?`); limitRe.MatchString(trimmed) {
			metaQuery = limitRe.ReplaceAllString(trimmed, "LIMIT 0")
		} else {
			// Use a newline so that trailing -- line comments don't swallow the injected clause
			metaQuery = trimmed + "\nLIMIT 0"
		}
		metaResult, err := c.ExecuteRaw(ctx, metaQuery, user, password, "JSONCompact")
		if err != nil {
			return nil, 0, fmt.Errorf("metadata query failed: %w", err)
		}
		var compact struct {
			Meta json.RawMessage `json:"meta"`
		}
		if err := json.Unmarshal(metaResult, &compact); err == nil && len(compact.Meta) > 0 {
			if err := onMeta(compact.Meta); err != nil {
				return nil, 0, err
			}
		}
	} else {
		// Write queries: send empty meta so consumers always get exactly one meta message
		if err := onMeta(json.RawMessage("[]")); err != nil {
			return nil, 0, err
		}
	}

	// Now execute the actual query with JSONCompactEachRow for streaming
	finalQuery := query
	if !isWrite && !hasFormat {
		// Use a newline so trailing -- line comments don't swallow the FORMAT clause
		finalQuery = strings.TrimRight(query, "; \n\t") + "\nFORMAT JSONCompactEachRow"
	}

	// Extract max_result_rows for precise client-side enforcement in the scanner loop.
	var maxRows int64
	if v, ok := settings["max_result_rows"]; ok {
		if n, err := strconv.ParseInt(v, 10, 64); err == nil && n > 0 {
			maxRows = n
		}
	}

	params := url.Values{}
	params.Set("default_format", "JSON")
	params.Set("send_progress_in_http_headers", "0")
	// Pass settings as ClickHouse HTTP URL params for coarse server-side abort.
	// max_result_rows + result_overflow_mode=break causes ClickHouse to stop at block
	// granularity (~65k rows), preventing the server from doing unbounded work.
	// The scanner loop below enforces the exact row count on top of this.
	for k, v := range settings {
		params.Set(k, v)
	}
	fullURL := c.baseURL + "/?" + params.Encode()

	req, err := http.NewRequestWithContext(ctx, "POST", fullURL, strings.NewReader(finalQuery))
	if err != nil {
		return nil, 0, fmt.Errorf("failed to create request: %w", err)
	}
	if user != "" {
		req.SetBasicAuth(user, password)
	}
	req.Header.Set("Content-Type", "text/plain")

	// Use a client without timeout for streaming (context controls cancellation)
	// but share the configured transport for proper TLS and connection management.
	streamClient := &http.Client{Transport: c.transport}
	resp, err := streamClient.Do(req)
	if err != nil {
		return nil, 0, fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		return nil, 0, fmt.Errorf("ClickHouse error: %s", string(body))
	}

	// Read line by line, accumulate chunks
	scanner := bufio.NewScanner(resp.Body)
	scanner.Buffer(make([]byte, 0, 1024*1024), 10*1024*1024) // 10MB max line

	var batch []json.RawMessage
	seq := 0
	var totalRows int64

	for scanner.Scan() {
		select {
		case <-ctx.Done():
			return nil, totalRows, ctx.Err()
		default:
		}

		line := scanner.Bytes()
		if len(line) == 0 {
			continue
		}

		// Each line is a JSON array: [v1, v2, v3]
		row := make(json.RawMessage, len(line))
		copy(row, line)
		batch = append(batch, row)
		totalRows++

		// Enforce max_result_rows limit: break early (closes body, aborts ClickHouse query)
		if maxRows > 0 && totalRows >= maxRows {
			break
		}

		if len(batch) >= chunkSize {
			chunkData, _ := json.Marshal(batch)
			if err := onChunk(seq, chunkData); err != nil {
				return nil, totalRows, err
			}
			batch = batch[:0]
			seq++
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, totalRows, fmt.Errorf("stream read error: %w", err)
	}

	// Flush remaining rows
	if len(batch) > 0 {
		chunkData, _ := json.Marshal(batch)
		if err := onChunk(seq, chunkData); err != nil {
			return nil, totalRows, err
		}
	}

	// We don't get statistics from JSONCompactEachRow format directly.
	// Return nil stats — the server can compute elapsed time itself.
	return nil, totalRows, nil
}

// TestConnection verifies connectivity and returns the ClickHouse version
func (c *CHClient) TestConnection(ctx context.Context, user, password string) (string, error) {
	query := "SELECT version() as version FORMAT JSON"

	result, err := c.Execute(ctx, query, user, password)
	if err != nil {
		return "", err
	}

	if len(result.Data) > 0 {
		if v, ok := result.Data[0]["version"]; ok {
			return fmt.Sprintf("%v", v), nil
		}
	}

	return "unknown", nil
}

// Query patterns
var (
	writeQueryPattern = regexp.MustCompile(`(?i)^\s*(INSERT|CREATE|DROP|ALTER|TRUNCATE|RENAME|ATTACH|DETACH|OPTIMIZE|GRANT|REVOKE|KILL|SYSTEM|SET|USE)`)
	formatPattern     = regexp.MustCompile(`(?i)\bFORMAT\s+\w+\s*$`)
	commentPattern    = regexp.MustCompile(`(?m)^\s*--.*$`)
)

func isWriteQuery(query string) bool {
	// Strip leading comments
	stripped := commentPattern.ReplaceAllString(query, "")
	stripped = strings.TrimSpace(stripped)
	return writeQueryPattern.MatchString(stripped)
}

func hasFormatClause(query string) bool {
	return formatPattern.MatchString(strings.TrimSpace(query))
}

func truncate(s string, maxLen int) string {
	if len(s) <= maxLen {
		return s
	}
	return s[:maxLen] + "..."
}


================================================
FILE: connector/config/config.go
================================================
package config

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"gopkg.in/yaml.v3"
)

// Config holds all agent configuration
type Config struct {
	// Required
	Token string `yaml:"tunnel_token"`

	// URLs
	ClickHouseURL string `yaml:"clickhouse_url"`
	TunnelURL     string `yaml:"tunnel_url"`

	// Timing
	ReconnectDelay     time.Duration `yaml:"reconnect_delay"`
	MaxReconnectDelay  time.Duration `yaml:"max_reconnect_delay"`
	HeartbeatInterval  time.Duration `yaml:"heartbeat_interval"`
	InsecureSkipVerify bool          `yaml:"insecure_skip_verify"`

	// Output control
	Verbose bool `yaml:"-"`
	Quiet   bool `yaml:"-"`
	NoColor bool `yaml:"-"`
	JSON    bool `yaml:"-"`
	// Connect behavior
	Takeover bool `yaml:"-"`
}

// Default configuration values
var Defaults = Config{
	ClickHouseURL:      "http://localhost:8123",
	TunnelURL:          "ws://127.0.0.1:3488/connect",
	ReconnectDelay:     1 * time.Second,
	MaxReconnectDelay:  30 * time.Second,
	HeartbeatInterval:  30 * time.Second,
	InsecureSkipVerify: false,
}

// configFile is the YAML structure for config file
type configFile struct {
	TunnelToken        string `yaml:"tunnel_token"`
	ClickHouseURL      string `yaml:"clickhouse_url"`
	TunnelURL          string `yaml:"tunnel_url"`
	InsecureSkipVerify bool   `yaml:"insecure_skip_verify"`
}

// DefaultConfigPath returns the platform-specific default config path
func DefaultConfigPath() string {
	switch runtime.GOOS {
	case "darwin":
		home, _ := os.UserHomeDir()
		return filepath.Join(home, ".config", "ch-ui", "config.yaml")
	default: // linux and others
		return "/etc/ch-ui/config.yaml"
	}
}

// Load creates a Config by merging: CLI flags -> config file -> environment variables
// Priority: CLI flags override config file, config file overrides env vars
func Load(configPath string, cliConfig *Config) (*Config, error) {
	cfg := Defaults

	// 1. Load from config file (lowest priority after defaults)
	if configPath != "" {
		if err := loadFromFile(configPath, &cfg); err != nil {
			// Only error if file was explicitly specified and doesn't exist
			if !os.IsNotExist(err) {
				return nil, fmt.Errorf("failed to load config file: %w", err)
			}
		}
	} else {
		// Try default path, ignore if not exists
		_ = loadFromFile(DefaultConfigPath(), &cfg)
	}

	// 2. Override with environment variables
	loadFromEnv(&cfg)

	// 3. Override with CLI flags (highest priority)
	if cliConfig != nil {
		mergeConfig(&cfg, cliConfig)
	}

	// Validate
	if err := cfg.Validate(); err != nil {
		return nil, err
	}

	return &cfg, nil
}

func loadFromFile(path string, cfg *Config) error {
	data, err := os.ReadFile(path)
	if err != nil {
		return err
	}

	var fc configFile
	if err := yaml.Unmarshal(data, &fc); err != nil {
		return fmt.Errorf("invalid YAML: %w", err)
	}

	if fc.TunnelToken != "" {
		cfg.Token = fc.TunnelToken
	}
	if fc.ClickHouseURL != "" {
		cfg.ClickHouseURL = fc.ClickHouseURL
	}
	if fc.TunnelURL != "" {
		cfg.TunnelURL = fc.TunnelURL
	}
	cfg.InsecureSkipVerify = fc.InsecureSkipVerify

	return nil
}

func loadFromEnv(cfg *Config) {
	if v := os.Getenv("TUNNEL_TOKEN"); v != "" {
		cfg.Token = v
	}
	if v := os.Getenv("CLICKHOUSE_URL"); v != "" {
		cfg.ClickHouseURL = v
	}
	if v := os.Getenv("TUNNEL_URL"); v != "" {
		cfg.TunnelURL = v
	}
	if v := os.Getenv("TUNNEL_INSECURE_SKIP_VERIFY"); v == "1" || strings.EqualFold(v, "true") || strings.EqualFold(v, "yes") {
		cfg.InsecureSkipVerify = true
	}
}

func mergeConfig(dst, src *Config) {
	if src.Token != "" {
		dst.Token = src.Token
	}
	if src.ClickHouseURL != "" && src.ClickHouseURL != Defaults.ClickHouseURL {
		dst.ClickHouseURL = src.ClickHouseURL
	}
	if src.TunnelURL != "" && src.TunnelURL != Defaults.TunnelURL {
		dst.TunnelURL = src.TunnelURL
	}
	if src.ReconnectDelay != 0 && src.ReconnectDelay != Defaults.ReconnectDelay {
		dst.ReconnectDelay = src.ReconnectDelay
	}
	if src.MaxReconnectDelay != 0 && src.MaxReconnectDelay != Defaults.MaxReconnectDelay {
		dst.MaxReconnectDelay = src.MaxReconnectDelay
	}
	if src.HeartbeatInterval != 0 && src.HeartbeatInterval != Defaults.HeartbeatInterval {
		dst.HeartbeatInterval = src.HeartbeatInterval
	}
	dst.Verbose = src.Verbose
	dst.Quiet = src.Quiet
	dst.NoColor = src.NoColor
	dst.JSON = src.JSON
	dst.Takeover = src.Takeover
	if src.InsecureSkipVerify {
		dst.InsecureSkipVerify = true
	}
}

// Validate checks if the configuration is valid
func (c *Config) Validate() error {
	if c.Token == "" {
		return fmt.Errorf("tunnel token is required (use --key, TUNNEL_TOKEN env, or config file)")
	}

	if !strings.HasPrefix(c.Token, "cht_") {
		return fmt.Errorf("invalid tunnel token format (should start with 'cht_')")
	}

	if !strings.HasPrefix(c.TunnelURL, "ws://") && !strings.HasPrefix(c.TunnelURL, "wss://") {
		return fmt.Errorf("tunnel URL must start with ws:// or wss://")
	}

	if !strings.HasPrefix(c.ClickHouseURL, "http://") && !strings.HasPrefix(c.ClickHouseURL, "https://") {
		return fmt.Errorf("ClickHouse URL must start with http:// or https://")
	}

	return nil
}

// GenerateTemplate returns a YAML config template
func GenerateTemplate() string {
	return `# CH-UI Agent Configuration
#
# This file can be placed at:
#   - Linux: /etc/ch-ui/config.yaml
#   - macOS: ~/.config/ch-ui/config.yaml
#
# All settings can also be specified via environment variables or CLI flags.
# Priority: CLI flags > Environment variables > Config file

# Required: Your tunnel token from CH-UI server (ch-ui tunnel create --name <name>)
tunnel_token: "cht_your_token_here"

# ClickHouse HTTP API URL (default: http://localhost:8123)
clickhouse_url: "http://localhost:8123"

# CH-UI tunnel URL (default: ws://127.0.0.1:3488/connect)
tunnel_url: "ws://127.0.0.1:3488/connect"

# Skip TLS certificate validation for tunnel connection (unsafe, dev only)
# insecure_skip_verify: false
`
}

// Redacted returns a copy of the config with sensitive fields redacted
func (c *Config) Redacted() Config {
	redacted := *c
	if redacted.Token != "" {
		if len(redacted.Token) > 8 {
			redacted.Token = redacted.Token[:8] + "..."
		} else {
			redacted.Token = "***"
		}
	}
	return redacted
}


================================================
FILE: connector/connector.go
================================================
package connector

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/caioricciuti/ch-ui/connector/config"
	"github.com/caioricciuti/ch-ui/connector/ui"
	"github.com/gorilla/websocket"
)

// Connector manages the tunnel connection to a CH-UI server
type Connector struct {
	cfg      *config.Config
	ui       *ui.UI
	chClient *CHClient

	conn          *websocket.Conn
	connMu        sync.Mutex
	authenticated bool
	startTime     time.Time

	// Stats
	queriesExecuted atomic.Int64
	lastQueryTime   atomic.Int64

	// Control
	ctx    context.Context
	cancel context.CancelFunc
	done   chan struct{}

	// Reconnection
	reconnectDelay time.Duration
}

// New creates a new Connector instance
func New(cfg *config.Config, u *ui.UI) *Connector {
	ctx, cancel := context.WithCancel(context.Background())

	return &Connector{
		cfg:            cfg,
		ui:             u,
		chClient:       NewCHClient(cfg.ClickHouseURL, cfg.InsecureSkipVerify),
		reconnectDelay: cfg.ReconnectDelay,
		ctx:            ctx,
		cancel:         cancel,
		done:           make(chan struct{}),
	}
}

// Run starts the connector and blocks until shutdown
func (c *Connector) Run() error {
	c.startTime = time.Now()

	// Initial connection
	if err := c.connect(); err != nil {
		if ce, ok := err.(*ConnectError); ok && ce.Type == "auth" {
			c.ui.Error("Authentication failed — not retrying (token may be invalid or revoked)")
			return err
		}
		return err
	}

	// Start message handler
	go c.messageLoop()

	// Start heartbeat
	go c.heartbeatLoop()

	// Start host info reporting
	go c.hostInfoLoop()

	// Wait for shutdown
	<-c.done
	return nil
}

// Shutdown gracefully stops the connector
func (c *Connector) Shutdown() {
	c.cancel()
	c.connMu.Lock()
	if c.conn != nil {
		c.conn.WriteMessage(websocket.CloseMessage,
			websocket.FormatCloseMessage(websocket.CloseNormalClosure, "shutdown"))
		c.conn.Close()
	}
	c.connMu.Unlock()
	close(c.done)
}

// Stats returns current connector statistics
func (c *Connector) Stats() (queriesExecuted int64, uptime time.Duration, lastQuery time.Time) {
	queriesExecuted = c.queriesExecuted.Load()
	uptime = time.Since(c.startTime)
	if ts := c.lastQueryTime.Load(); ts > 0 {
		lastQuery = time.Unix(0, ts)
	}
	return
}

// ConnectError represents a classified connection error
type ConnectError struct {
	Type    string // "network", "auth", "server", "protocol"
	Message string
	Err     error
}

func (e *ConnectError) Error() string {
	return e.Message
}

func (c *Connector) connect() error {
	c.ui.Info("Connecting to %s...", extractHost(c.cfg.TunnelURL))

	dialer := websocket.Dialer{
		HandshakeTimeout: 10 * time.Second,
		TLSClientConfig:  &tls.Config{InsecureSkipVerify: c.cfg.InsecureSkipVerify},
	}

	if c.cfg.InsecureSkipVerify {
		c.ui.Warn("TLS certificate verification is disabled (insecure_skip_verify=true)")
	}

	headers := http.Header{}
	headers.Set("User-Agent", "ch-ui-agent/1.0")

	conn, dialResp, err := dialer.DialContext(c.ctx, c.cfg.TunnelURL, headers)
	if err != nil {
		dialErr := err
		if dialResp != nil {
			body, _ := io.ReadAll(io.LimitReader(dialResp.Body, 2048))
			dialResp.Body.Close()
			if len(body) > 0 {
				dialErr = fmt.Errorf("%w (status=%d body=%q)", err, dialResp.StatusCode, strings.TrimSpace(string(body)))
			} else {
				dialErr = fmt.Errorf("%w (status=%d)", err, dialResp.StatusCode)
			}
		}

		c.ui.ConnectionError(dialErr, c.cfg.TunnelURL)
		return &ConnectError{Type: "network", Message: "Failed to connect to CH-UI server", Err: dialErr}
	}

	c.connMu.Lock()
	c.conn = conn
	c.connMu.Unlock()

	// Send auth message
	authMsg := AgentMessage{
		Type:     MsgTypeAuth,
		Token:    c.cfg.Token,
		Takeover: c.cfg.Takeover,
	}

	if err := c.send(authMsg); err != nil {
		conn.Close()
		c.ui.ConnectionError(err, c.cfg.TunnelURL)
		return &ConnectError{Type: "network", Message: "Failed to send authentication", Err: err}
	}

	c.ui.Debug("Auth message sent, waiting for response...")

	// Wait for auth response
	conn.SetReadDeadline(time.Now().Add(10 * time.Second))
	_, message, err := conn.ReadMessage()
	if err != nil {
		conn.Close()
		c.ui.ConnectionError(err, c.cfg.TunnelURL)
		return &ConnectError{Type: "network", Message: "Failed to receive auth response", Err: err}
	}
	conn.SetReadDeadline(time.Time{}) // Clear deadline

	var authResp GatewayMessage
	if err := json.Unmarshal(message, &authResp); err != nil {
		conn.Close()
		c.ui.DiagnosticError(ui.ErrorTypeServer, "CH-UI Server",
			"Received invalid response from server",
			[]string{
				"The server may be running an incompatible version",
				"Try updating the agent to the latest version",
				"Contact support if the issue persists",
			})
		return &ConnectError{Type: "protocol", Message: "Invalid server response", Err: err}
	}

	switch authResp.Type {
	case MsgTypeAuthOK:
		c.authenticated = true
		c.reconnectDelay = c.cfg.ReconnectDelay // Reset on successful connection
		c.ui.Success("Authenticated successfully")
		c.ui.Success("Tunnel established")
		c.ui.Status(c.cfg.TunnelURL, c.cfg.ClickHouseURL, time.Since(c.startTime))
		return nil

	case MsgTypeAuthError:
		conn.Close()
		// Server may send error in either "error" or "message" field
		errMsg := authResp.Error
		if errMsg == "" {
			errMsg = authResp.Message
		}
		if errMsg == "" {
			errMsg = "Authentication failed (no details provided)"
		}
		if isPermanentAuthError(errMsg) {
			c.ui.AuthError(errMsg)
			return &ConnectError{Type: "auth", Message: errMsg}
		}

		c.ui.Warn("Server temporarily rejected authentication: %s", errMsg)
		return &ConnectError{Type: "server", Message: errMsg}

	default:
		conn.Close()
		c.ui.DiagnosticError(ui.ErrorTypeServer, "CH-UI Server",
			fmt.Sprintf("Unexpected response type: %s", authResp.Type),
			[]string{
				"The server may be running an incompatible version",
				"Try updating the agent to the latest version",
			})
		return &ConnectError{Type: "protocol", Message: fmt.Sprintf("Unexpected response: %s", authResp.Type)}
	}
}

func isPermanentAuthError(msg string) bool {
	lower := strings.ToLower(strings.TrimSpace(msg))
	if lower == "" {
		return false
	}

	return strings.Contains(lower, "invalid tunnel token") ||
		strings.Contains(lower, "invalid token") ||
		strings.Contains(lower, "revoked")
}

func (c *Connector) messageLoop() {
	for {
		select {
		case <-c.ctx.Done():
			return
		default:
		}

		c.connMu.Lock()
		conn := c.conn
		c.connMu.Unlock()

		if conn == nil {
			time.Sleep(100 * time.Millisecond)
			continue
		}

		_, message, err := conn.ReadMessage()
		if err != nil {
			if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
				c.ui.Disconnected("server closed connection")
			} else {
				c.ui.Disconnected(err.Error())
			}

			c.connMu.Lock()
			c.conn = nil
			c.authenticated = false
			c.connMu.Unlock()

			// Attempt reconnection
			c.reconnect()
			continue
		}

		var msg GatewayMessage
		if err := json.Unmarshal(message, &msg); err != nil {
			c.ui.Debug("Invalid message: %v", err)
			continue
		}

		c.handleMessage(msg)
	}
}

func (c *Connector) handleMessage(msg GatewayMessage) {
	switch msg.Type {
	case MsgTypePing:
		c.send(AgentMessage{Type: MsgTypePong})

	case MsgTypeQuery:
		go c.executeQuery(msg)

	case MsgTypeQueryStream:
		go c.executeStreamQuery(msg)

	case MsgTypeTestConnection:
		go c.testConnection(msg)

	case MsgTypeCancelQuery:
		c.ui.Debug("Cancel query requested for %s (not implemented)", msg.QueryID)

	default:
		c.ui.Debug("Unknown message type: %s", msg.Type)
	}
}

func (c *Connector) executeQuery(msg GatewayMessage) {
	start := time.Now()
	queryID := msg.QueryID
	sql := msg.Query

	format := msg.Format // "" or "JSON" = legacy, "JSONCompact" = tier 1

	// If a compact format is requested, use ExecuteRaw to avoid intermediate parsing
	if format != "" && format != "JSON" {
		raw, err := c.chClient.ExecuteRaw(c.ctx, sql, msg.User, msg.Password, format)
		elapsed := time.Since(start)

		if err != nil {
			c.ui.QueryError(queryID, err)
			c.send(AgentMessage{
				Type:    MsgTypeQueryError,
				QueryID: queryID,
				Error:   err.Error(),
			})
			return
		}

		c.queriesExecuted.Add(1)
		c.lastQueryTime.Store(time.Now().UnixNano())
		c.ui.QueryLog(queryID, elapsed, 0)

		// Send raw bytes directly — no intermediate parse/reserialize
		c.send(AgentMessage{
			Type:    MsgTypeQueryResult,
			QueryID: queryID,
			Data:    raw,
		})
		return
	}

	// Legacy JSON path — parse into structured result
	result, err := c.chClient.Execute(c.ctx, sql, msg.User, msg.Password)
	elapsed := time.Since(start)

	if err != nil {
		c.ui.QueryError(queryID, err)
		c.send(AgentMessage{
			Type:    MsgTypeQueryError,
			QueryID: queryID,
			Error:   err.Error(),
		})
		return
	}

	c.queriesExecuted.Add(1)
	c.lastQueryTime.Store(time.Now().UnixNano())

	rows := len(result.Data)
	c.ui.QueryLog(queryID, elapsed, rows)

	c.send(AgentMessage{
		Type:    MsgTypeQueryResult,
		QueryID: queryID,
		Data:    result.Data,
		Meta:    result.Meta,
		Stats: &QueryStats{
			Elapsed:   result.Statistics.Elapsed,
			RowsRead:  result.Statistics.RowsRead,
			BytesRead: result.Statistics.BytesRead,
		},
	})
}

func (c *Connector) executeStreamQuery(msg GatewayMessage) {
	start := time.Now()
	queryID := msg.QueryID
	sql := msg.Query

	c.ui.Debug("Stream query %s: %s", queryID, truncateStr(sql, 80))

	// Send chunks as they arrive
	onMeta := func(meta json.RawMessage) error {
		return c.send(AgentMessage{
			Type:    MsgTypeQueryStreamStart,
			QueryID: queryID,
			Meta:    meta,
		})
	}

	onChunk := func(seq int, data json.RawMessage) error {
		return c.send(AgentMessage{
			Type:    MsgTypeQueryStreamChunk,
			QueryID: queryID,
			Data:    data,
			Seq:     seq,
		})
	}

	_, totalRows, err := c.chClient.ExecuteStreaming(c.ctx, sql, msg.User, msg.Password, 5000, msg.Settings, onMeta, onChunk)
	elapsed := time.Since(start)

	if err != nil {
		c.ui.QueryError(queryID, err)
		c.send(AgentMessage{
			Type:    MsgTypeQueryStreamError,
			QueryID: queryID,
			Error:   err.Error(),
		})
		return
	}

	c.queriesExecuted.Add(1)
	c.lastQueryTime.Store(time.Now().UnixNano())
	c.ui.QueryLog(queryID, elapsed, int(totalRows))

	c.send(AgentMessage{
		Type:      MsgTypeQueryStreamEnd,
		QueryID:   queryID,
		TotalRows: totalRows,
		Stats: &QueryStats{
			Elapsed: elapsed.Seconds(),
		},
	})
}

func truncateStr(s string, maxLen int) string {
	if len(s) <= maxLen {
		return s
	}
	return s[:maxLen] + "..."
}

func (c *Connector) testConnection(msg GatewayMessage) {
	version, err := c.chClient.TestConnection(c.ctx, msg.User, msg.Password)

	if err != nil {
		c.ui.Debug("Connection test failed: %v", err)
		c.send(AgentMessage{
			Type:    MsgTypeTestResult,
			QueryID: msg.QueryID,
			Online:  false,
			Error:   err.Error(),
		})
		return
	}

	c.ui.Debug("Connection test successful, version: %s", version)
	c.send(AgentMessage{
		Type:    MsgTypeTestResult,
		QueryID: msg.QueryID,
		Online:  true,
		Version: version,
	})
}

func (c *Connector) heartbeatLoop() {
	ticker := time.NewTicker(c.cfg.HeartbeatInterval)
	defer ticker.Stop()

	for {
		select {
		case <-c.ctx.Done():
			return
		case <-ticker.C:
			c.connMu.Lock()
			conn := c.conn
			authenticated := c.authenticated
			if conn != nil && authenticated {
				if err := conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(5*time.Second)); err != nil {
					c.ui.Debug("Heartbeat failed: %v", err)
				}
			}
			c.connMu.Unlock()
		}
	}
}

// sendHostInfo collects and sends host machine metrics to the server
func (c *Connector) sendHostInfo() {
	hostInfo := CollectHostInfo(c.startTime)

	if err := c.send(AgentMessage{
		Type:     MsgTypeHostInfo,
		HostInfo: hostInfo,
	}); err != nil {
		c.ui.Debug("Failed to send host info: %v", err)
	} else {
		c.ui.Debug("Host info sent (CPU: %d cores, Mem: %d MB, Disk: %d GB)",
			hostInfo.CPUCores,
			hostInfo.MemoryTotal/(1024*1024),
			hostInfo.DiskTotal/(1024*1024*1024))
	}
}

// hostInfoLoop sends host info periodically (every 60 seconds)
func (c *Connector) hostInfoLoop() {
	// Send initial host info after a short delay to allow auth to complete
	time.Sleep(2 * time.Second)
	c.sendHostInfo()

	ticker := time.NewTicker(60 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-c.ctx.Done():
			return
		case <-ticker.C:
			c.connMu.Lock()
			authenticated := c.authenticated
			c.connMu.Unlock()

			if authenticated {
				c.sendHostInfo()
			}
		}
	}
}

func (c *Connector) reconnect() {
	for {
		select {
		case <-c.ctx.Done():
			return
		default:
		}

		c.ui.Reconnecting(c.reconnectDelay)
		time.Sleep(c.reconnectDelay)

		// Exponential backoff
		c.reconnectDelay *= 2
		if c.reconnectDelay > c.cfg.MaxReconnectDelay {
			c.reconnectDelay = c.cfg.MaxReconnectDelay
		}

		if err := c.connect(); err != nil {
			if ce, ok := err.(*ConnectError); ok && ce.Type == "auth" {
				c.ui.Error("Authentication failed — stopping reconnection (token is invalid or revoked)")
				close(c.done)
				return
			}
			c.ui.Error("Reconnection failed: %v", err)
			continue
		}

		return
	}
}

func (c *Connector) send(msg AgentMessage) error {
	c.connMu.Lock()
	defer c.connMu.Unlock()

	if c.conn == nil {
		return fmt.Errorf("not connected")
	}

	data, err := json.Marshal(msg)
	if err != nil {
		return err
	}

	return c.conn.WriteMessage(websocket.TextMessage, data)
}

func extractHost(urlStr string) string {
	parsed, err := url.Parse(urlStr)
	if err == nil && parsed.Host != "" {
		if hostname := parsed.Hostname(); hostname != "" {
			return hostname
		}
		return parsed.Host
	}

	trimmed := strings.TrimPrefix(strings.TrimPrefix(urlStr, "wss://"), "ws://")
	for i, c := range trimmed {
		if c == '/' || c == ':' {
			return trimmed[:i]
		}
	}
	return trimmed
}


================================================
FILE: connector/hostinfo.go
================================================
package connector

import (
	"os"
	"runtime"
	"time"
)

// HostInfo contains system metrics from the host machine
type HostInfo struct {
	Hostname    string `json:"hostname"`
	OS          string `json:"os"`
	Arch        string `json:"arch"`
	CPUCores    int    `json:"cpu_cores"`
	MemoryTotal uint64 `json:"memory_total"` // bytes
	MemoryFree  uint64 `json:"memory_free"`  // bytes
	DiskTotal   uint64 `json:"disk_total"`   // bytes
	DiskFree    uint64 `json:"disk_free"`    // bytes
	GoVersion   string `json:"go_version"`
	AgentUptime int64  `json:"agent_uptime"` // seconds
	CollectedAt string `json:"collected_at"` // ISO 8601
}

// CollectHostInfo gathers system metrics from the host machine
func CollectHostInfo(agentStartTime time.Time) *HostInfo {
	info := &HostInfo{
		OS:          runtime.GOOS,
		Arch:        runtime.GOARCH,
		CPUCores:    runtime.NumCPU(),
		GoVersion:   runtime.Version(),
		AgentUptime: int64(time.Since(agentStartTime).Seconds()),
		CollectedAt: time.Now().UTC().Format(time.RFC3339),
	}

	// Hostname
	if hostname, err := os.Hostname(); err == nil {
		info.Hostname = hostname
	} else {
		info.Hostname = "unknown"
	}

	// Memory stats (use Go runtime as cross-platform source)
	info.MemoryTotal, info.MemoryFree = getMemoryInfo()

	// Disk stats for root filesystem (platform-specific)
	info.DiskTotal, info.DiskFree = getDiskInfo()

	return info
}

// getMemoryInfo returns total and free memory in bytes
// Uses runtime.MemStats as a cross-platform approach
func getMemoryInfo() (total, free uint64) {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)

	// Use runtime stats as cross-platform source
	// Sys is total memory obtained from OS
	// Alloc is memory currently in use
	total = m.Sys
	free = m.Sys - m.Alloc
	return
}


================================================
FILE: connector/hostinfo_unix.go
================================================
//go:build !windows

package connector

import "syscall"

// getDiskInfo returns total and free disk space for the root filesystem
func getDiskInfo() (total, free uint64) {
	var stat syscall.Statfs_t
	if err := syscall.Statfs("/", &stat); err != nil {
		return 0, 0
	}

	total = stat.Blocks * uint64(stat.Bsize)
	free = stat.Bfree * uint64(stat.Bsize)
	return
}


================================================
FILE: connector/hostinfo_windows.go
================================================
//go:build windows

package connector

import (
	"syscall"
	"unsafe"
)

// getDiskInfo returns total and free disk space for the C: drive
func getDiskInfo() (total, free uint64) {
	kernel32 := syscall.MustLoadDLL("kernel32.dll")
	getDiskFreeSpaceEx := kernel32.MustFindProc("GetDiskFreeSpaceExW")

	var freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes uint64

	path, _ := syscall.UTF16PtrFromString("C:\\")
	r, _, _ := getDiskFreeSpaceEx.Call(
		uintptr(unsafe.Pointer(path)),
		uintptr(unsafe.Pointer(&freeBytesAvailable)),
		uintptr(unsafe.Pointer(&totalNumberOfBytes)),
		uintptr(unsafe.Pointer(&totalNumberOfFreeBytes)),
	)

	if r == 0 {
		return 0, 0
	}

	return totalNumberOfBytes, totalNumberOfFreeBytes
}


================================================
FILE: connector/protocol.go
================================================
package connector

// GatewayMessage represents messages received from the CH-UI tunnel server.
type GatewayMessage struct {
	Type     string `json:"type"`               // Message type: auth_ok, auth_error, query, query_stream, ping, cancel_query, test_connection
	QueryID  string `json:"query_id,omitempty"` // Query identifier
	Query    string `json:"query,omitempty"`    // SQL query to execute
	User     string `json:"user,omitempty"`     // ClickHouse username for this query
	Password string `json:"password,omitempty"` // ClickHouse password for this query
	Format   string            `json:"format,omitempty"`   // ClickHouse output format (JSONCompact, stream, etc.)
	Error    string            `json:"error,omitempty"`    // Error message (for auth_error)
	Message  string            `json:"message,omitempty"`  // Additional message info
	Settings map[string]string `json:"settings,omitempty"` // ClickHouse query settings (URL params)
}

// AgentMessage represents messages sent to the CH-UI tunnel server.
type AgentMessage struct {
	Type      string      `json:"type"`               // Message type: auth, pong, query_result, query_error, test_result, host_info, query_stream_*
	QueryID   string      `json:"query_id,omitempty"` // Query identifier (for query responses)
	Token     string      `json:"token,omitempty"`    // Tunnel token (for auth message)
	Takeover  bool        `json:"takeover,omitempty"` // Request takeover of an existing session for this token
	Data      interface{} `json:"data,omitempty"`     // Query result data
	Meta      interface{} `json:"meta,omitempty"`     // Query result metadata
	Stats     *QueryStats `json:"statistics,omitempty"`
	Error     string      `json:"error,omitempty"`      // Error message
	Version   string      `json:"version,omitempty"`    // ClickHouse version (for test_result)
	Online    bool        `json:"online,omitempty"`     // Connection status (for test_result)
	HostInfo  *HostInfo   `json:"host_info,omitempty"`  // Host machine metrics
	Seq       int         `json:"seq,omitempty"`        // Chunk sequence number (for streaming)
	TotalRows int64       `json:"total_rows,omitempty"` // Total row count (for streaming)
}

// QueryStats contains query execution statistics
type QueryStats struct {
	Elapsed   float64 `json:"elapsed"`
	RowsRead  uint64  `json:"rows_read"`
	BytesRead uint64  `json:"bytes_read"`
}

// Message types from gateway
const (
	MsgTypeAuthOK         = "auth_ok"
	MsgTypeAuthError      = "auth_error"
	MsgTypeQuery          = "query"
	MsgTypeQueryStream    = "query_stream"
	MsgTypePing           = "ping"
	MsgTypeCancelQuery    = "cancel_query"
	MsgTypeTestConnection = "test_connection"
)

// Message types to gateway
const (
	MsgTypeAuth             = "auth"
	MsgTypePong             = "pong"
	MsgTypeQueryResult      = "query_result"
	MsgTypeQueryError       = "query_error"
	MsgTypeTestResult       = "test_result"
	MsgTypeHostInfo         = "host_info"
	MsgTypeQueryStreamStart = "query_stream_start"
	MsgTypeQueryStreamChunk = "query_stream_chunk"
	MsgTypeQueryStreamEnd   = "query_stream_end"
	MsgTypeQueryStreamError = "query_stream_error"
)


================================================
FILE: connector/service/launchd.go
================================================
package service

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
)

const launchdPlistTemplate = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>%s</string>
    <key>ProgramArguments</key>
    <array>
        <string>%s</string>
        <string>connect</string>
        <string>--config</string>
        <string>%s</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <dict>
        <key>SuccessfulExit</key>
        <false/>
    </dict>
    <key>ThrottleInterval</key>
    <integer>5</integer>
    <key>StandardOutPath</key>
    <string>%s</string>
    <key>StandardErrorPath</key>
    <string>%s</string>
    <key>WorkingDirectory</key>
    <string>/tmp</string>
</dict>
</plist>
`

func (m *Manager) launchdPlistPath() string {
	home, _ := os.UserHomeDir()
	return filepath.Join(home, "Library", "LaunchAgents", ServiceLabel+".plist")
}

func (m *Manager) launchdLogDir() string {
	home, _ := os.UserHomeDir()
	return filepath.Join(home, "Library", "Logs", "ch-ui")
}

func (m *Manager) launchdLogPath() string {
	return filepath.Join(m.launchdLogDir(), "agent.log")
}

func (m *Manager) launchdIsInstalled() bool {
	return fileExists(m.launchdPlistPath())
}

func (m *Manager) launchdIsRunning() (bool, error) {
	output, err := runCommand("launchctl", "list")
	if err != nil {
		return false, err
	}
	return strings.Contains(output, ServiceLabel), nil
}

func (m *Manager) launchdInstall(configPath string) error {
	// Create log directory
	logDir := m.launchdLogDir()
	if err := os.MkdirAll(logDir, 0755); err != nil {
		return fmt.Errorf("failed to create log directory: %w", err)
	}

	// Create LaunchAgents directory if it doesn't exist
	agentsDir := filepath.Dir(m.launchdPlistPath())
	if err := os.MkdirAll(agentsDir, 0755); err != nil {
		return fmt.Errorf("failed to create LaunchAgents directory: %w", err)
	}

	// Generate plist content
	logPath := m.launchdLogPath()
	plistContent := fmt.Sprintf(launchdPlistTemplate,
		ServiceLabel,
		BinaryPath,
		configPath,
		logPath,
		logPath,
	)

	// Write plist file
	if err := os.WriteFile(m.launchdPlistPath(), []byte(plistContent), 0644); err != nil {
		return fmt.Errorf("failed to write plist file: %w", err)
	}

	// Load the service
	_, err := runCommand("launchctl", "load", m.launchdPlistPath())
	if err != nil {
		return fmt.Errorf("failed to load service: %w", err)
	}

	return nil
}

func (m *Manager) launchdUninstall() error {
	// Stop the service first (ignore errors if not running)
	_ = m.launchdStop()

	// Unload the service
	if m.launchdIsInstalled() {
		runCommand("launchctl", "unload", m.launchdPlistPath())
	}

	// Remove plist file
	plistPath := m.launchdPlistPath()
	if fileExists(plistPath) {
		if err := os.Remove(plistPath); err != nil {
			return fmt.Errorf("failed to remove plist file: %w", err)
		}
	}

	return nil
}

func (m *Manager) launchdStart() error {
	if !m.launchdIsInstalled() {
		return fmt.Errorf("service not installed. Run 'ch-ui service install' first")
	}

	// Check if already running
	running, _ := m.launchdIsRunning()
	if running {
		return fmt.Errorf("service is already running")
	}

	// Start the service
	_, err := runCommand("launchctl", "start", ServiceLabel)
	if err != nil {
		return fmt.Errorf("failed to start service: %w", err)
	}

	return nil
}

func (m *Manager) launchdStop() error {
	running, _ := m.launchdIsRunning()
	if !running {
		return fmt.Errorf("service is not running")
	}

	_, err := runCommand("launchctl", "stop", ServiceLabel)
	if err != nil {
		return fmt.Errorf("failed to stop service: %w", err)
	}

	return nil
}

func (m *Manager) launchdRestart() error {
	if !m.launchdIsInstalled() {
		return fmt.Errorf("service not installed. Run 'ch-ui service install' first")
	}

	// Stop if running
	running, _ := m.launchdIsRunning()
	if running {
		runCommand("launchctl", "stop", ServiceLabel)
	}

	// Start the service
	_, err := runCommand("launchctl", "start", ServiceLabel)
	if err != nil {
		return fmt.Errorf("failed to restart service: %w", err)
	}

	return nil
}

func (m *Manager) launchdStatus() (string, error) {
	if !m.launchdIsInstalled() {
		return "not installed", nil
	}

	output, _ := runCommand("launchctl", "list")
	lines := strings.Split(output, "\n")
	for _, line := range lines {
		if strings.Contains(line, ServiceLabel) {
			parts := strings.Fields(line)
			if len(parts) >= 2 {
				pid := parts[0]
				status := parts[1]
				if pid != "-" {
					return fmt.Sprintf("running (PID: %s)", pid), nil
				}
				if status != "0" {
					return fmt.Sprintf("stopped (last exit: %s)", status), nil
				}
			}
			return "stopped", nil
		}
	}

	return "not running", nil
}

func (m *Manager) launchdLogs(follow bool, lines int) error {
	logPath := m.launchdLogPath()

	if !fileExists(logPath) {
		fmt.Println("No logs found yet. Service may not have started.")
		return nil
	}

	if follow {
		cmd := exec.Command("tail", "-f", "-n", strconv.Itoa(lines), logPath)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		return cmd.Run()
	}

	cmd := exec.Command("tail", "-n", strconv.Itoa(lines), logPath)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}


================================================
FILE: connector/service/service.go
================================================
package service

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
)

const (
	ServiceName      = "ch-ui"
	ServiceLabel     = "com.ch-ui"
	BinaryPath       = "/usr/local/bin/ch-ui"
	SystemConfigDir  = "/etc/ch-ui"
	SystemConfigPath = "/etc/ch-ui/config.yaml"
)

// Manager provides cross-platform service management
type Manager struct {
	platform string
}

// New creates a new service manager for the current platform
func New() *Manager {
	return &Manager{platform: runtime.GOOS}
}

// IsInstalled checks if the service is installed
func (m *Manager) IsInstalled() bool {
	switch m.platform {
	case "darwin":
		return m.launchdIsInstalled()
	case "linux":
		return m.systemdIsInstalled()
	default:
		return false
	}
}

// IsRunning checks if the service is currently running
func (m *Manager) IsRunning() (bool, error) {
	switch m.platform {
	case "darwin":
		return m.launchdIsRunning()
	case "linux":
		return m.systemdIsRunning()
	default:
		return false, fmt.Errorf("unsupported platform: %s", m.platform)
	}
}

// Install installs the service
func (m *Manager) Install(configPath string) error {
	switch m.platform {
	case "darwin":
		return m.launchdInstall(configPath)
	case "linux":
		return m.systemdInstall(configPath)
	default:
		return fmt.Errorf("unsupported platform: %s", m.platform)
	}
}

// Uninstall removes the service
func (m *Manager) Uninstall() error {
	switch m.platform {
	case "darwin":
		return m.launchdUninstall()
	case "linux":
		return m.systemdUninstall()
	default:
		return fmt.Errorf("unsupported platform: %s", m.platform)
	}
}

// Start starts the service
func (m *Manager) Start() error {
	switch m.platform {
	case "darwin":
		return m.launchdStart()
	case "linux":
		return m.systemdStart()
	default:
		return fmt.Errorf("unsupported platform: %s", m.platform)
	}
}

// Stop stops the service
func (m *Manager) Stop() error {
	switch m.platform {
	case "darwin":
		return m.launchdStop()
	case "linux":
		return m.systemdStop()
	default:
		return fmt.Errorf("unsupported platform: %s", m.platform)
	}
}

// Restart restarts the service
func (m *Manager) Restart() error {
	switch m.platform {
	case "darwin":
		return m.launchdRestart()
	case "linux":
		return m.systemdRestart()
	default:
		return fmt.Errorf("unsupported platform: %s", m.platform)
	}
}

// Status returns the service status as a string
func (m *Manager) Status() (string, error) {
	switch m.platform {
	case "darwin":
		return m.launchdStatus()
	case "linux":
		return m.systemdStatus()
	default:
		return "", fmt.Errorf("unsupported platform: %s", m.platform)
	}
}

// Logs returns recent service logs
func (m *Manager) Logs(follow bool, lines int) error {
	switch m.platform {
	case "darwin":
		return m.launchdLogs(follow, lines)
	case "linux":
		return m.systemdLogs(follow, lines)
	default:
		return fmt.Errorf("unsupported platform: %s", m.platform)
	}
}

// GetLogPath returns the path to the log file (for macOS)
func (m *Manager) GetLogPath() string {
	switch m.platform {
	case "darwin":
		home, _ := os.UserHomeDir()
		return filepath.Join(home, "Library", "Logs", "ch-ui", "agent.log")
	case "linux":
		return "" // Uses journald
	default:
		return ""
	}
}

// Platform returns the current platform
func (m *Manager) Platform() string {
	return m.platform
}

// NeedsSudo returns true if sudo is needed for service operations
func (m *Manager) NeedsSudo() bool {
	// macOS launchd user agents don't need sudo
	// Linux systemd system services need sudo
	return m.platform == "linux"
}

// runCommand runs a command and returns combined output
func runCommand(name string, args ...string) (string, error) {
	cmd := exec.Command(name, args...)
	output, err := cmd.CombinedOutput()
	return strings.TrimSpace(string(output)), err
}

// runCommandWithSudo runs a command with sudo if needed
func runCommandWithSudo(needsSudo bool, name string, args ...string) (string, error) {
	if needsSudo && os.Geteuid() != 0 {
		args = append([]string{name}, args...)
		name = "sudo"
	}
	return runCommand(name, args...)
}

// fileExists checks if a file exists
func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

// GetConfigPath returns the appropriate config path based on platform
func GetConfigPath() string {
	switch runtime.GOOS {
	case "darwin":
		home, _ := os.UserHomeDir()
		return filepath.Join(home, ".config", "ch-ui", "config.yaml")
	default:
		return SystemConfigPath
	}
}

// GetConfigDir returns the appropriate config directory based on platform
func GetConfigDir() string {
	switch runtime.GOOS {
	case "darwin":
		home, _ := os.UserHomeDir()
		return filepath.Join(home, ".config", "ch-ui")
	default:
		return SystemConfigDir
	}
}


================================================
FILE: connector/service/systemd.go
================================================
package service

import (
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

const systemdServiceTemplate = `[Unit]
Description=CH-UI Tunnel
Documentation=https://ch-ui.com/docs
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=%s connect --config %s
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=%s

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=true
ReadWritePaths=%s

[Install]
WantedBy=multi-user.target
`

const systemdServicePath = "/etc/systemd/system/ch-ui.service"

func (m *Manager) systemdIsInstalled() bool {
	return fileExists(systemdServicePath)
}

func (m *Manager) systemdIsRunning() (bool, error) {
	output, err := runCommand("systemctl", "is-active", ServiceName)
	if err != nil {
		return false, nil // Not running or not installed
	}
	return strings.TrimSpace(output) == "active", nil
}

func (m *Manager) systemdInstall(configPath string) error {
	// Create config directory with proper permissions
	configDir := SystemConfigDir
	if err := os.MkdirAll(configDir, 0755); err != nil {
		// Try with sudo
		_, err = runCommandWithSudo(true, "mkdir", "-p", configDir)
		if err != nil {
			return fmt.Errorf("failed to create config directory: %w", err)
		}
	}

	// Generate service content
	serviceContent := fmt.Sprintf(systemdServiceTemplate,
		BinaryPath,
		configPath,
		ServiceName,
		configDir,
	)

	// Write service file (needs sudo)
	tmpFile := "/tmp/ch-ui-agent.service"
	if err := os.WriteFile(tmpFile, []byte(serviceContent), 0644); err != nil {
		return fmt.Errorf("failed to write service file: %w", err)
	}
	defer os.Remove(tmpFile)

	// Move to systemd directory
	_, err := runCommandWithSudo(true, "mv", tmpFile, systemdServicePath)
	if err != nil {
		return fmt.Errorf("failed to install service file: %w", err)
	}

	// Set permissions
	runCommandWithSudo(true, "chmod", "644", systemdServicePath)

	// Reload systemd
	_, err = runCommandWithSudo(true, "systemctl", "daemon-reload")
	if err != nil {
		return fmt.Errorf("failed to reload systemd: %w", err)
	}

	// Enable the service
	_, err = runCommandWithSudo(true, "systemctl", "enable", ServiceName)
	if err != nil {
		return fmt.Errorf("failed to enable service: %w", err)
	}

	// Start the service
	_, err = runCommandWithSudo(true, "systemctl", "start", ServiceName)
	if err != nil {
		return fmt.Errorf("failed to start service: %w", err)
	}

	return nil
}

func (m *Manager) systemdUninstall() error {
	// Stop the service
	runCommandWithSudo(true, "systemctl", "stop", ServiceName)

	// Disable the service
	runCommandWithSudo(true, "systemctl", "disable", ServiceName)

	// Remove service file
	if fileExists(systemdServicePath) {
		_, err := runCommandWithSudo(true, "rm", systemdServicePath)
		if err != nil {
			return fmt.Errorf("failed to remove service file: %w", err)
		}
	}

	// Reload systemd
	runCommandWithSudo(true, "systemctl", "daemon-reload")

	return nil
}

func (m *Manager) systemdStart() error {
	if !m.systemdIsInstalled() {
		return fmt.Errorf("service not installed. Run 'ch-ui service install' first")
	}

	running, _ := m.systemdIsRunning()
	if running {
		return fmt.Errorf("service is already running")
	}

	_, err := runCommandWithSudo(true, "systemctl", "start", ServiceName)
	if err != nil {
		return fmt.Errorf("failed to start service: %w", err)
	}

	return nil
}

func (m *Manager) systemdStop() error {
	running, _ := m.systemdIsRunning()
	if !running {
		return fmt.Errorf("service is not running")
	}

	_, err := runCommandWithSudo(true, "systemctl", "stop", ServiceName)
	if err != nil {
		return fmt.Errorf("failed to stop service: %w", err)
	}

	return nil
}

func (m *Manager) systemdRestart() error {
	if !m.systemdIsInstalled() {
		return fmt.Errorf("service not installed. Run 'ch-ui service install' first")
	}

	_, err := runCommandWithSudo(true, "systemctl", "restart", ServiceName)
	if err != nil {
		return fmt.Errorf("failed to restart service: %w", err)
	}

	return nil
}

func (m *Manager) systemdStatus() (string, error) {
	if !m.systemdIsInstalled() {
		return "not installed", nil
	}

	output, _ := runCommand("systemctl", "is-active", ServiceName)
	status := strings.TrimSpace(output)

	switch status {
	case "active":
		// Get more details
		detailOutput, _ := runCommand("systemctl", "show", ServiceName, "--property=MainPID,ActiveEnterTimestamp")
		var pid, since string
		for _, line := range strings.Split(detailOutput, "\n") {
			if strings.HasPrefix(line, "MainPID=") {
				pid = strings.TrimPrefix(line, "MainPID=")
			}
			if strings.HasPrefix(line, "ActiveEnterTimestamp=") {
				since = strings.TrimPrefix(line, "ActiveEnterTimestamp=")
			}
		}
		if pid != "" && pid != "0" {
			if since != "" {
				return fmt.Sprintf("running (PID: %s, since: %s)", pid, since), nil
			}
			return fmt.Sprintf("running (PID: %s)", pid), nil
		}
		return "running", nil
	case "inactive":
		return "stopped", nil
	case "failed":
		return "failed (check logs with: ch-ui service logs)", nil
	default:
		return status, nil
	}
}

func (m *Manager) systemdLogs(follow bool, lines int) error {
	args := []string{"-u", ServiceName, "-n", strconv.Itoa(lines)}
	if follow {
		args = append(args, "-f")
	}

	cmd := exec.Command("journalctl", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}


================================================
FILE: connector/ui/ui.go
================================================
package ui

import (
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/fatih/color"
)

// UI handles formatted terminal output
type UI struct {
	out      io.Writer
	noColor  bool
	quiet    bool
	verbose  bool
	jsonMode bool

	green   *color.Color
	red     *color.Color
	yellow  *color.Color
	cyan    *color.Color
	blue    *color.Color
	magenta *color.Color
	bold    *color.Color
	dim     *color.Color
}

// New creates a new UI instance
func New(noColor, quiet, verbose, jsonMode bool) *UI {
	if noColor {
		color.NoColor = true
	}

	return &UI{
		out:      os.Stdout,
		noColor:  noColor,
		quiet:    quiet,
		verbose:  verbose,
		jsonMode: jsonMode,
		green:    color.New(color.FgGreen),
		red:      color.New(color.FgRed),
		yellow:   color.New(color.FgYellow),
		cyan:     color.New(color.FgCyan),
		blue:     color.New(color.FgBlue),
		magenta:  color.New(color.FgMagenta),
		bold:     color.New(color.Bold),
		dim:      color.New(color.Faint),
	}
}

// Logo prints the CH-UI ASCII art logo
func (u *UI) Logo(version string) {
	if u.quiet || u.jsonMode {
		return
	}

	logo := `
  ██████╗██╗  ██╗      ██╗   ██╗██╗
 ██╔════╝██║  ██║      ██║   ██║██║
 ██║     ███████║█████╗██║   ██║██║
 ██║     ██╔══██║╚════╝██║   ██║██║
 ╚██████╗██║  ██║      ╚██████╔╝██║
  ╚═════╝╚═╝  ╚═╝       ╚═════╝ ╚═╝`

	u.cyan.Println(logo)
	u.dim.Printf("  Tunnel %s\n\n", version)
}

// Info prints an info message
func (u *UI) Info(format string, args ...interface{}) {
	if u.quiet || u.jsonMode {
		return
	}
	u.cyan.Print("→ ")
	fmt.Fprintf(u.out, format+"\n", args...)
}

// Success prints a success message
func (u *UI) Success(format string, args ...interface{}) {
	if u.quiet || u.jsonMode {
		return
	}
	u.green.Print("✓ ")
	fmt.Fprintf(u.out, format+"\n", args...)
}

// Error prints an error message
func (u *UI) Error(format string, args ...interface{}) {
	if u.jsonMode {
		return
	}
	u.red.Print("✗ ")
	fmt.Fprintf(os.Stderr, format+"\n", args...)
}

// ErrorType represents the category of error
type ErrorType string

const (
	ErrorTypeNetwork ErrorType = "NETWORK"
	ErrorTypeAuth    ErrorType = "AUTH"
	ErrorTypeServer  ErrorType = "SERVER"
	ErrorTypeConfig  ErrorType = "CONFIG"
	ErrorTypeUnknown ErrorType = "UNKNOWN"
)

// DiagnosticError prints a detailed error with source, type, and suggestions
func (u *UI) DiagnosticError(errType ErrorType, source, message string, suggestions []string) {
	if u.jsonMode {
		return
	}

	fmt.Fprintln(os.Stderr)

	// Error header with type badge
	u.red.Fprint(os.Stderr, "┌─ ERROR ")
	u.dim.Fprintf(os.Stderr, "[%s]\n", errType)

	// Source
	u.red.Fprint(os.Stderr, "│\n")
	u.red.Fprint(os.Stderr, "│  ")
	u.bold.Fprint(os.Stderr, "Source: ")
	fmt.Fprintln(os.Stderr, source)

	// Message
	u.red.Fprint(os.Stderr, "│  ")
	u.bold.Fprint(os.Stderr, "Error:  ")
	fmt.Fprintln(os.Stderr, message)

	// Suggestions
	if len(suggestions) > 0 {
		u.red.Fprint(os.Stderr, "│\n")
		u.red.Fprint(os.Stderr, "│  ")
		u.yellow.Fprintln(os.Stderr, "Possible causes:")
		for _, s := range suggestions {
			u.red.Fprint(os.Stderr, "│    ")
			u.dim.Fprint(os.Stderr, "• ")
			fmt.Fprintln(os.Stderr, s)
		}
	}

	u.red.Fprint(os.Stderr, "│\n")
	u.red.Fprintln(os.Stderr, "└─")
	fmt.Fprintln(os.Stderr)
}

// AuthError prints an authentication-specific error with helpful context
func (u *UI) AuthError(serverMessage string) {
	source := "CH-UI Server"
	var suggestions []string

	// Classify the error and provide specific suggestions
	switch {
	case strings.Contains(strings.ToLower(serverMessage), "invalid") && strings.Contains(strings.ToLower(serverMessage), "token"):
		suggestions = []string{
			"The tunnel token is invalid or has been revoked",
			"Check that you copied the complete token (starts with 'cht_')",
			"Generate a new token on the server with: ch-ui tunnel create --name <connection-name>",
			"Verify the token belongs to the target server instance",
		}
	case strings.Contains(strings.ToLower(serverMessage), "license"):
		suggestions = []string{
			"The server license may have expired",
			"Contact your administrator to renew the license",
			"Check server logs for license validation details",
		}
	case strings.Contains(strings.ToLower(serverMessage), "already connected"):
		suggestions = []string{
			"Another agent process is already connected with this token",
			"Stop the existing process or service before starting a new one",
			"Use 'ch-ui service status' to check service mode",
			"Reconnect with '--takeover' to replace the active session",
		}
	case strings.Contains(strings.ToLower(serverMessage), "not found"):
		suggestions = []string{
			"The organization associated with this token may have been deleted",
			"The tunnel connection may have been removed",
			"Contact your administrator",
		}
	default:
		suggestions = []string{
			"Check that your token is valid and not expired",
			"Verify the tunnel URL is correct",
			"Check CH-UI server logs for tunnel auth errors",
		}
	}

	u.DiagnosticError(ErrorTypeAuth, source, serverMessage, suggestions)
}

// ConnectionError prints a connection-specific error
func (u *UI) ConnectionError(err error, tunnelURL string) {
	source := fmt.Sprintf("Connection to %s", tunnelURL)
	message := err.Error()
	var suggestions []string

	switch {
	case strings.Contains(message, "connection refused"):
		suggestions = []string{
			"The CH-UI server may be down or unreachable",
			"Check if the tunnel URL is correct: " + tunnelURL,
			"Verify your network/firewall allows outbound WebSocket connections",
			"If using a custom server, ensure it's running",
		}
	case strings.Contains(message, "no such host") || strings.Contains(message, "lookup"):
		suggestions = []string{
			"Cannot resolve the tunnel server hostname",
			"Check your DNS settings",
			"Verify the tunnel URL is correct: " + tunnelURL,
		}
	case strings.Contains(message, "timeout") || strings.Contains(message, "deadline"):
		suggestions = []string{
			"Connection timed out - server may be overloaded or unreachable",
			"Check your network connection",
			"Try again in a few moments",
		}
	case strings.Contains(message, "certificate") || strings.Contains(message, "tls"):
		suggestions = []string{
			"SSL/TLS certificate error",
			"If using a self-signed certificate, this is expected in dev mode",
			"Verify the tunnel URL protocol (ws:// vs wss://)",
		}
	default:
		suggestions = []string{
			"Check your network connection",
			"Verify the tunnel URL: " + tunnelURL,
			"Try running with --verbose for more details",
		}
	}

	u.DiagnosticError(ErrorTypeNetwork, source, message, suggestions)
}

// Warn prints a warning message
func (u *UI) Warn(format string, args ...interface{}) {
	if u.quiet || u.jsonMode {
		return
	}
	u.yellow.Print("! ")
	fmt.Fprintf(u.out, format+"\n", args...)
}

// Debug prints a debug message (only in verbose mode)
func (u *UI) Debug(format string, args ...interface{}) {
	if !u.verbose || u.jsonMode {
		return
	}
	u.dim.Printf("[debug] "+format+"\n", args...)
}

// Status prints the connection status block
func (u *UI) Status(tunnelURL, clickhouseURL string, uptime time.Duration) {
	if u.quiet || u.jsonMode {
		return
	}

	fmt.Println()
	u.bold.Println("  Status:     ", u.green.Sprint("Connected"))
	fmt.Printf("  Tunnel:     %s\n", tunnelURL)
	fmt.Printf("  ClickHouse: %s\n", clickhouseURL)
	fmt.Printf("  Uptime:     %s\n", formatDuration(uptime))
	fmt.Println()
	u.dim.Println("Press Ctrl+C to disconnect")
	fmt.Println()
}

// QueryLog prints a query execution log line
func (u *UI) QueryLog(queryID string, elapsed time.Duration, rows int) {
	if u.quiet || u.jsonMode {
		return
	}

	timestamp := time.Now().Format("2006-01-02 15:04:05")
	u.dim.Printf("[%s] ", timestamp)
	fmt.Printf("Query %s executed ", u.cyan.Sprint(queryID[:8]))
	u.dim.Printf("(%s, %s rows)\n", elapsed.Round(time.Millisecond), formatNumber(rows))
}

// QueryError prints a query error log line
func (u *UI) QueryError(queryID string, err error) {
	if u.jsonMode {
		return
	}

	timestamp := time.Now().Format("2006-01-02 15:04:05")
	u.dim.Printf("[%s] ", timestamp)
	u.red.Printf("Query %s failed: %v\n", queryID[:8], err)
}

// Disconnected prints a disconnection message
func (u *UI) Disconnected(reason string) {
	if u.jsonMode {
		return
	}
	u.yellow.Print("! ")
	fmt.Printf("Disconnected: %s\n", reason)
}

// Reconnecting prints a reconnection message
func (u *UI) Reconnecting(delay time.Duration) {
	if u.quiet || u.jsonMode {
		return
	}
	u.cyan.Print("→ ")
	fmt.Printf("Reconnecting in %s...\n", delay.Round(time.Millisecond))
}

// Box prints a boxed message
func (u *UI) Box(title string, lines map[string]string, order []string) {
	if u.quiet || u.jsonMode {
		return
	}

	fmt.Println()
	u.bold.Println(title)
	fmt.Println(strings.Repeat("─", len(title)+2))

	for _, key := range order {
		if val, ok := lines[key]; ok {
			fmt.Printf("  %-12s %s\n", key+":", val)
		}
	}
	fmt.Println()
}

// Helpers

func formatDuration(d time.Duration) string {
	if d < time.Minute {
		return fmt.Sprintf("%ds", int(d.Seconds()))
	}
	if d < time.Hour {
		return fmt.Sprintf("%dm %ds", int(d.Minutes()), int(d.Seconds())%60)
	}
	return fmt.Sprintf("%dh %dm", int(d.Hours()), int(d.Minutes())%60)
}

func formatNumber(n int) string {
	if n < 1000 {
		return fmt.Sprintf("%d", n)
	}
	if n < 1000000 {
		return fmt.Sprintf("%.1fK", float64(n)/1000)
	}
	return fmt.Sprintf("%.1fM", float64(n)/1000000)
}

// FormatBytes formats bytes to human readable format
func FormatBytes(b uint64) string {
	const unit = 1024
	if b < unit {
		return fmt.Sprintf("%d B", b)
	}
	div, exp := uint64(unit), 0
	for n := b / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp])
}


================================================
FILE: docs/brain/SKILLS.md
================================================
# Brain Skills

This file defines the default instruction set used by Brain across all chats.
Admins can copy this content into the Brain Skills editor and create variants.

## Role

You are **Brain**, a senior ClickHouse analytics copilot.

## Main goals

- Produce correct, executable ClickHouse SQL.
- Help users move from question -> query -> insight quickly.
- Stay concise and explicit about assumptions.

## SQL rules

- Prefer read-only exploration first.
- Use `LIMIT 100` by default for exploratory selects.
- Avoid `SELECT *` on large tables unless explicitly requested.
- Always qualify tables with backticks when needed (for example: `` `db.table` ``).
- If the request is ambiguous, ask one targeted clarification question.

## Safety rules

- Do not suggest destructive SQL (DROP/TRUNCATE/DELETE/ALTER) unless the user asks directly.
- If the user asks for destructive SQL, include a short warning and confirmation step.
- For expensive queries, provide a preview query first (sample, top-N, or date-bounded window).

## Artifact contract

When query output or derived assets exist, create or reference artifacts with stable titles:

- `SQL Draft: <topic>`
- `Query Result: <topic>`
- `Insight Summary: <topic>`
- `Chart Spec: <topic>`

Each artifact should include:

- Purpose (1 line)
- Inputs used (query/message references)
- Output payload (JSON/text/SQL)

## Query tool contract

When running SQL tools:

1. Start with read-only SQL.
2. Keep runtime bounded (small scans first).
3. Persist output as an artifact.
4. Summarize findings in 3-5 bullets.

## Response format

Default assistant response structure:

1. One-line intent confirmation.
2. SQL block when applicable.
3. Short explanation.
4. Optional next-step variants.

## Example pattern

````text
Got it. You want daily active users by region for the last 30 days.

```sql
SELECT
  toDate(event_time) AS day,
  region,
  uniq(user_id) AS dau
FROM `analytics.events`
WHERE event_time >= now() - INTERVAL 30 DAY
GROUP BY day, region
ORDER BY day DESC, dau DESC
LIMIT 100
```

This computes DAU by region and keeps the result bounded for quick validation.
If you want, I can also return a stacked timeseries version.
````


================================================
FILE: docs/cant-login.md
================================================
# Can't Login?

Use this guide when CH-UI loads but sign-in fails, local connection is wrong, or you are blocked by retry windows.

## Quick Diagnosis

| What you see | Most likely cause | What to do |
|---|---|---|
| `Authentication failed` | Wrong ClickHouse username/password | Retry with correct credentials for the selected connection |
| `Connection unavailable` / `Unreachable` | Local ClickHouse URL is wrong or connector is offline | Update local URL/name, restart CH-UI, then retry |
| `Too many login attempts` | Repeated failed attempts triggered temporary lock | Wait retry window; if URL/connection was wrong, fix setup and restart before retrying |
| No connections configured | Embedded local connection was not created/updated correctly | Run setup command below and restart CH-UI |

## Local Recovery (Recommended)

1. Open **Can't login?** in CH-UI login.
2. Set:
   - `ClickHouse URL`
   - `Connection Name`
3. Restart CH-UI with one of these commands.

Global install:

```bash
ch-ui server --clickhouse-url 'http://127.0.0.1:8123' --connection-name 'My Connection 1'
```

Local binary:

```bash
./ch-ui server --clickhouse-url 'http://127.0.0.1:8123' --connection-name 'My Connection 1'
```

Then open `http://localhost:3488` and sign in again.

## Docker Recovery

```bash
docker run --rm \
  -p 3488:3488 \
  -v ch-ui-data:/app/data \
  -e CLICKHOUSE_URL='http://127.0.0.1:8123' \
  -e CONNECTION_NAME='My Connection 1' \
  ghcr.io/caioricciuti/ch-ui:latest
```

## Env And Config Alternatives

Environment variables:

```bash
CLICKHOUSE_URL='http://127.0.0.1:8123' CONNECTION_NAME='My Connection 1' ch-ui server
```

Config file (`server.yaml`):

```yaml
clickhouse_url: http://127.0.0.1:8123
connection_name: My Connection 1
```

## Notes

- Local URL setup does **not** require Admin access.
- Admin and multi-connection management are Pro-only features.
- Setup commands intentionally exclude passwords; credentials stay in the Sign in form.
- Connection name precedence: `--connection-name` > `CONNECTION_NAME` > `server.yaml` > `Local ClickHouse`.


================================================
FILE: docs/legal/privacy-policy.md
================================================
# Privacy Policy

**Effective date:** February 12, 2026
**Last updated:** February 12, 2026

CH-UI ("we", "our", "us") is developed by Caio Ricciuti. This privacy policy explains how we handle data when you use CH-UI software.

---

## What CH-UI does NOT collect

CH-UI is a self-hosted application. When you run CH-UI on your own infrastructure:

- **No telemetry** is sent to us or any third party
- **No usage data** leaves your server
- **No analytics** are collected
- **No cookies** are set by us (only session cookies for your own login)
- **Your queries, data, and database contents never leave your infrastructure**

## Data stored locally

CH-UI stores the following data in a local SQLite database on your server:

- **User sessions** — login tokens for authenticated access
- **Saved queries** — queries you choose to save
- **Dashboard configurations** — layout and panel settings (Pro)
- **Scheduled jobs** — query schedules you create (Pro)
- **Connection settings** — ClickHouse connection details (encrypted)
- **License information** — your license key if you activate Pro
- **Application settings** — preferences and configuration

All data is stored in the SQLite file specified by `database_path` in your config (default: `./data/ch-ui.db`). You have full control over this data.

## Pro license activation

When you activate a Pro license, the license file is stored locally in your database. No information is sent to external servers during activation — the license is validated offline using cryptographic signatures.

## Managed hosting

If you use a managed CH-UI hosting offering:

- We may store your account information (email, name) for authentication
- We may store your ClickHouse connection metadata (not your database contents)
- We do not access, read, or store your ClickHouse data
- Tunnel connections are end-to-end between your agent and your browser session

## Third-party services

The self-hosted CH-UI binary does not communicate with any third-party services except:

- **Your ClickHouse server** — as configured by you
- **OpenAI API** — only if you configure the Brain AI feature (Pro) with your own API key

## Data deletion

Since all data is stored locally:

- Delete the SQLite database file to remove all application data
- Uninstall the binary to fully remove CH-UI

## Contact

For privacy questions: **c.ricciuti@ch-ui.com**

## Changes

We may update this policy. Changes will be posted in this file and noted in release changelogs.


================================================
FILE: docs/legal/terms-of-service.md
================================================
# Terms of Service

**Effective date:** February 12, 2026
**Last updated:** February 12, 2026

These terms govern your use of CH-UI software developed by Caio Ricciuti.

---

## 1. Software license

CH-UI is distributed under a dual-license model:

- **CH-UI Core** (Community Edition) is licensed under the [Apache License 2.0](../../LICENSE). You may use, modify, and distribute it freely under those terms.
- **CH-UI Pro** modules require a separate commercial license. Pro features are clearly marked in the application and documentation.

## 2. Self-hosted usage

When you run CH-UI on your own infrastructure:

- You are responsible for your own data, backups, and security
- You are responsible for compliance with applicable laws in your jurisdiction
- We provide the software "as is" without warranty (see Section 6)

## 3. Pro license

If you purchase a CH-UI Pro license:

- The license grants you access to Pro features for the duration specified
- Licenses are non-transferable unless agreed in writing
- License terms are specified in the license file provided to you
- Tampering with or circumventing license validation is prohibited

## 4. Acceptable use

You agree not to:

- Reverse-engineer the license validation mechanism
- Redistribute Pro modules without authorization
- Use CH-UI to violate applicable laws or regulations
- Misrepresent CH-UI as your own product

## 5. Intellectual property

- CH-UI, the CH-UI logo, and related marks are the property of Caio Ricciuti
- Open source components are governed by their respective licenses
- Your data remains yours — we claim no ownership over data processed by CH-UI

## 6. Disclaimer of warranty

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

## 7. Limitation of liability

To the maximum extent permitted by law, Caio Ricciuti shall not be liable for any indirect, incidental, special, consequential, or punitive damages, or any loss of profits or revenues, whether incurred directly or indirectly.

## 8. Changes

We may update these terms. Continued use of the software after changes constitutes acceptance.

## 9. Contact

For questions about these terms: **c.ricciuti@ch-ui.com**


================================================
FILE: docs/license.md
================================================
# CH-UI Licensing

CH-UI uses a dual-license model: open source core + commercial Pro modules.

---

## CH-UI Core (Community Edition)

**License:** [Apache License 2.0](../LICENSE)

The core of CH-UI is free and open source. This includes:

- SQL Editor (multi-tab, formatting, profiling, streaming results, query plan analysis)
- Schema Explorer (database/table/column browser, data preview)
- Saved Queries
- Dashboards (panel builder, multiple chart types, time ranges)
- Brain AI Assistant (OpenAI, OpenAI-compatible, Ollama — multi-chat, artifacts, skills)
- Data Pipelines (Webhook, S3, Kafka, Database sources into ClickHouse)
- Models (dbt-style SQL transformations with DAG and materialization)
- Admin Panel (user management, connection management, provider configuration)
- Multi-connection management
- Tunnel connector (`ch-ui connect`) for remote ClickHouse access
- Embedded web frontend
- All CLI commands

You can use, modify, and distribute CH-UI Core freely under the Apache 2.0 license.

## CH-UI Pro

**License:** Commercial (proprietary)

Pro modules extend CH-UI with enterprise features:

- Scheduled query jobs (cron-based scheduling, execution history, timezone support)
- Governance (metadata sync, query log analytics, data lineage, access matrix, tagging)
- Policies and incident management (violation detection, incident workflow, severity tracking)
- Alerting (SMTP, Resend, Brevo — rules by event type/severity, escalation)

Pro features require a valid license file. Licenses are per-deployment and include a customer name, expiration date, and feature set.

### How to activate

1. Open CH-UI in your browser
2. Go to **Settings > License**
3. Paste or upload your license file
4. Pro features unlock immediately

### How to get a license

Visit [ch-ui.com/pricing](https://ch-ui.com/pricing) or contact **c.ricciuti@ch-ui.com**.

## License boundary

The licensing boundary is enforced server-side via HTTP 402 middleware on Pro-only routes:

- **Free routes:** queries, saved queries, dashboards, pipelines, models, brain, admin, connections
- **Pro routes:** `/api/schedules/*`, `/api/governance/*` (including alerts)

The Pro license check is enforced both server-side (HTTP 402 middleware) and client-side (UI gate).

## FAQ

**Can I use CH-UI Core in production?**
Yes, freely. Apache 2.0 allows commercial use.

**Can I modify CH-UI Core?**
Yes. You must retain the copyright notice and license.

**Do I need Pro for dashboards, Brain, or pipelines?**
No. Dashboards, Brain AI, data pipelines, models, and admin are all free.

**What features require Pro?**
Only scheduled query jobs, governance (lineage, policies, incidents, access matrix), and alerting.

**What happens when a Pro license expires?**
Pro features become locked. Core features continue working. Your data is never lost.


================================================
FILE: docs/production-runbook.md
================================================
# CH-UI Production Runbook (VM2 Server + VM1 Connector)

This runbook covers a production topology where:

- **VM2** runs `ch-ui server` (UI, API, tunnel gateway)
- **VM1** runs `ch-ui connect` (agent next to ClickHouse)

## 1. VM2 Server Hardening

1. Create server config at `/etc/ch-ui/server.yaml`:

```yaml
port: 3488
app_url: https://ch-ui.example.com
app_secret_key: "replace-with-long-random-secret"
allowed_origins:
  - https://ch-ui.example.com
database_path: /var/lib/ch-ui/ch-ui.db
```

2. Keep runtime state in writable directories:

```bash
sudo mkdir -p /var/lib/ch-ui/run
sudo mkdir -p /var/lib/ch-ui
sudo chown -R chui:chui /var/lib/ch-ui
```

3. Use lifecycle commands with explicit PID file:

```bash
ch-ui server start -c /etc/ch-ui/server.yaml --detach --pid-file /var/lib/ch-ui/run/ch-ui-server.pid
ch-ui server status -c /etc/ch-ui/server.yaml --pid-file /var/lib/ch-ui/run/ch-ui-server.pid
ch-ui server stop -c /etc/ch-ui/server.yaml --pid-file /var/lib/ch-ui/run/ch-ui-server.pid
```

## 2. VM2 systemd Service (recommended)

Create `/etc/systemd/system/ch-ui-server.service`:

```ini
[Unit]
Description=CH-UI Server
After=network.target

[Service]
Type=simple
User=chui
Group=chui
WorkingDirectory=/var/lib/ch-ui
ExecStart=/usr/local/bin/ch-ui server start -c /etc/ch-ui/server.yaml --pid-file /var/lib/ch-ui/run/ch-ui-server.pid
ExecStop=/usr/local/bin/ch-ui server stop -c /etc/ch-ui/server.yaml --pid-file /var/lib/ch-ui/run/ch-ui-server.pid
Restart=always
RestartSec=5
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
```

Then:

```bash
sudo systemctl daemon-reload
sudo systemctl enable ch-ui-server
sudo systemctl start ch-ui-server
sudo systemctl status ch-ui-server
```

## 3. VM2 Reverse Proxy (TLS + WebSocket)

Your proxy must:

- route app traffic to `127.0.0.1:3488`
- support WebSocket upgrades on `/connect`
- keep long-enough timeouts for tunnel traffic

Use the repo example: `ch-ui.conf`.

## 4. VM1 Connector Setup

1. On VM2, create a tunnel key for VM1:

```bash
ch-ui tunnel create --name "vm1-clickhouse" -c /etc/ch-ui/server.yaml --url wss://ch-ui.example.com/connect
```

Copy the generated `cht_...` token.

2. Install connector service on VM1:

```bash
sudo /usr/local/bin/ch-ui service install \
  --url wss://ch-ui.example.com/connect \
  --key cht_your_tunnel_token \
  --clickhouse-url http://127.0.0.1:8123
```

3. Verify:

```bash
ch-ui service status
ch-ui service logs -f
```

4. (Optional) Rotate compromised/old token from VM2:

```bash
ch-ui tunnel list -c /etc/ch-ui/server.yaml
ch-ui tunnel rotate <connection-id> -c /etc/ch-ui/server.yaml --url wss://ch-ui.example.com/connect
```

## 5. Network Policy

- VM2 inbound: `443` (or your TLS port)
- VM2 inbound: `3488` only from localhost/reverse-proxy path
- VM1 outbound: allow to `wss://ch-ui.example.com/connect`
- VM1 ClickHouse can stay local-only (`127.0.0.1:8123`)

## 6. Monitoring and Backups

1. Health endpoint:

```bash
curl -fsS http://127.0.0.1:3488/health
```

2. Back up SQLite:

- file: `/var/lib/ch-ui/ch-ui.db`
- schedule daily snapshot + retention policy
- verify restore procedure quarterly

3. Log collection:

- VM2: `journalctl -u ch-ui-server`
- VM1: `ch-ui service logs` or platform service logs

## 7. Upgrade Procedure

1. Replace binaries on VM2 and VM1.
2. Restart services:

```bash
sudo systemctl restart ch-ui-server
ch-ui service restart
```

3. Validate:

```bash
ch-ui version
ch-ui server status -c /etc/ch-ui/server.yaml --pid-file /var/lib/ch-ui/run/ch-ui-server.pid
ch-ui service status
```

## 8. Notes on Older Binaries

Older builds did not support server lifecycle subcommands (`status/stop/restart`).
If `ch-ui server status` starts the server, replace the binary with a newer build and retry.


================================================
FILE: frontend.go
================================================
package main

import (
	"embed"
	"io/fs"
	"log/slog"
)

//go:embed all:ui/dist
var uiDistFS embed.FS

func frontendFS() fs.FS {
	sub, err := fs.Sub(uiDistFS, "ui/dist")
	if err != nil {
		slog.Warn("Failed to open embedded frontend directory", "error", err)
		return nil
	}
	return sub
}


================================================
FILE: go.mod
================================================
module github.com/caioricciuti/ch-ui

go 1.25.0

require (
	github.com/IBM/sarama v1.47.0
	github.com/fatih/color v1.18.0
	github.com/go-chi/chi/v5 v5.2.5
	github.com/go-sql-driver/mysql v1.9.3
	github.com/google/uuid v1.6.0
	github.com/gorilla/websocket v1.5.3
	github.com/lib/pq v1.11.2
	github.com/minio/minio-go/v7 v7.0.98
	github.com/spf13/cobra v1.10.2
	github.com/xdg-go/scram v1.2.0
	github.com/xitongsys/parquet-go v1.6.2
	github.com/xitongsys/parquet-go-source v0.0.0-20241021075129-b732d2ac9c9b
	golang.org/x/crypto v0.48.0
	gopkg.in/yaml.v3 v3.0.1
	modernc.org/sqlite v1.44.3
)

require (
	filippo.io/edwards25519 v1.1.0 // indirect
	github.com/apache/arrow/go/arrow v0.0.0-20200730104253-651201b0f516 // indirect
	github.com/apache/thrift v0.14.2 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/dustin/go-humanize v1.0.1 // indirect
	github.com/eapache/go-resiliency v1.7.0 // indirect
	github.com/eapache/queue v1.1.0 // indirect
	github.com/go-ini/ini v1.67.0 // indirect
	github.com/golang/snappy v0.0.3 // indirect
	github.com/hashicorp/go-uuid v1.0.3 // indirect
	github.com/inconshreveable/mousetrap v1.1.0 // indirect
	github.com/jcmturner/aescts/v2 v2.0.0 // indirect
	github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
	github.com/jcmturner/gofork v1.7.6 // indirect
	github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
	github.com/jcmturner/rpc/v2 v2.0.3 // indirect
	github.com/klauspost/compress v1.18.4 // indirect
	github.com/klauspost/cpuid/v2 v2.2.11 // indirect
	github.com/klauspost/crc32 v1.3.0 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/minio/crc64nvme v1.1.1 // indirect
	github.com/minio/md5-simd v1.1.2 // indirect
	github.com/ncruces/go-strftime v1.0.0 // indirect
	github.com/philhofer/fwd v1.2.0 // indirect
	github.com/pierrec/lz4/v4 v4.1.25 // indirect
	github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
	github.com/rogpeppe/go-internal v1.14.1 // indirect
	github.com/rs/xid v1.6.0 // indirect
	github.com/spf13/pflag v1.0.9 // indirect
	github.com/tinylib/msgp v1.6.1 // indirect
	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
	github.com/xdg-go/stringprep v1.0.4 // indirect
	go.yaml.in/yaml/v3 v3.0.4 // indirect
	golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
	golang.org/x/net v0.51.0 // indirect
	golang.org/x/sys v0.41.0 // indirect
	golang.org/x/text v0.34.0 // indirect
	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
	modernc.org/libc v1.67.6 // indirect
	modernc.org/mathutil v1.7.1 // indirect
	modernc.org/memory v1.11.0 // indirect
)


================================================
FILE: go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.82.0/go.mod h1:vlKccHJGuFBFufnAnuB08dfEH9Y3H7dzDzRECFdC2TA=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw=
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI=
cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=
cloud.google.com/go/monitoring v1.1.0/go.mod h1:L81pzz7HKn14QCMaCs6NTQkdBnE87TElyanS95vIcl4=
cloud.google.com/go/monitoring v1.4.0/go.mod h1:y6xnxfwI3hTFWOdkOaD7nfJVlwuC3/mS/5kvtT131p4=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/pubsub v1.19.0/go.mod h1:/O9kmSe9bb9KRnIAWkzmqhPjHo6LtzGOBYd/kr06XSs=
cloud.google.com/go/secretmanager v1.3.0/go.mod h1:+oLTkouyiYiabAQNugCeTS3PAArGiMJuBqvJnJsyH+U=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho=
cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA=
cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A=
cloud.google.com/go/trace v1.2.0/go.mod h1:Wc8y/uYyOhPy12KEnXG9XGrvfMz5F5SrYecQlbW1rwM=
contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8=
contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/azure-amqp-common-go/v3 v3.2.1/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
github.com/Azure/azure-amqp-common-go/v3 v3.2.2/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.0.0/go.mod h1:ceIuwmxDWptoW3eCqSXlnPsZFKh4X+R38dWPv7GS9Vs=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4=
github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU=
github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
github.com/Azure/go-amqp v0.16.4/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest v0.11.22/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/adal v0.9.17/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.9/go.mod h1:hg3/1yw0Bq87O3KvvnJoAh34/0zbP7SFizX/qN5JvjU=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOw
Download .txt
gitextract_j38tsz8m/

├── .dockerignore
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   └── feature.yml
│   └── workflows/
│       └── release.yml
├── .gitignore
├── .gitpod.yml
├── Dockerfile
├── LICENSE.md
├── Makefile
├── README.md
├── VERSION
├── ch-ui.conf
├── cmd/
│   ├── connect.go
│   ├── connect_detach_unix.go
│   ├── connect_detach_windows.go
│   ├── connect_process_unix.go
│   ├── connect_process_windows.go
│   ├── root.go
│   ├── server.go
│   ├── service.go
│   ├── tunnel.go
│   ├── uninstall.go
│   ├── update.go
│   └── version.go
├── connector/
│   ├── clickhouse.go
│   ├── config/
│   │   └── config.go
│   ├── connector.go
│   ├── hostinfo.go
│   ├── hostinfo_unix.go
│   ├── hostinfo_windows.go
│   ├── protocol.go
│   ├── service/
│   │   ├── launchd.go
│   │   ├── service.go
│   │   └── systemd.go
│   └── ui/
│       └── ui.go
├── docs/
│   ├── brain/
│   │   └── SKILLS.md
│   ├── cant-login.md
│   ├── legal/
│   │   ├── privacy-policy.md
│   │   └── terms-of-service.md
│   ├── license.md
│   └── production-runbook.md
├── frontend.go
├── go.mod
├── go.sum
├── internal/
│   ├── alerts/
│   │   └── dispatcher.go
│   ├── brain/
│   │   ├── provider.go
│   │   └── provider_test.go
│   ├── config/
│   │   ├── config.go
│   │   ├── secret.go
│   │   └── secret_test.go
│   ├── crypto/
│   │   └── aes.go
│   ├── database/
│   │   ├── alert_digests.go
│   │   ├── alerts.go
│   │   ├── audit_logs.go
│   │   ├── audit_logs_test.go
│   │   ├── brain.go
│   │   ├── cleanup.go
│   │   ├── connections.go
│   │   ├── dashboards.go
│   │   ├── database.go
│   │   ├── migrations.go
│   │   ├── migrations_guardrails_test.go
│   │   ├── models.go
│   │   ├── pipelines.go
│   │   ├── rate_limits.go
│   │   ├── saved_queries.go
│   │   ├── schedules.go
│   │   ├── sessions.go
│   │   ├── settings.go
│   │   └── user_roles.go
│   ├── embedded/
│   │   └── embedded.go
│   ├── governance/
│   │   ├── guardrails.go
│   │   ├── guardrails_test.go
│   │   ├── harvester_access.go
│   │   ├── harvester_metadata.go
│   │   ├── harvester_querylog.go
│   │   ├── incidents.go
│   │   ├── lineage.go
│   │   ├── policy_engine.go
│   │   ├── store.go
│   │   ├── syncer.go
│   │   └── types.go
│   ├── langfuse/
│   │   └── langfuse.go
│   ├── license/
│   │   ├── license.go
│   │   ├── pubkey.go
│   │   ├── public.pem
│   │   └── tokens.go
│   ├── models/
│   │   ├── dag.go
│   │   ├── ref.go
│   │   ├── runner.go
│   │   └── scheduler.go
│   ├── pipelines/
│   │   ├── clickhouse_sink.go
│   │   ├── database_source.go
│   │   ├── helpers.go
│   │   ├── kafka.go
│   │   ├── kafka_scram.go
│   │   ├── registry.go
│   │   ├── runner.go
│   │   ├── s3_source.go
│   │   ├── types.go
│   │   └── webhook.go
│   ├── queryproc/
│   │   ├── variables.go
│   │   └── variables_test.go
│   ├── scheduler/
│   │   ├── cron.go
│   │   └── runner.go
│   ├── server/
│   │   ├── handlers/
│   │   │   ├── admin.go
│   │   │   ├── admin_brain.go
│   │   │   ├── admin_governance.go
│   │   │   ├── admin_langfuse.go
│   │   │   ├── auth.go
│   │   │   ├── auth_helpers_test.go
│   │   │   ├── brain.go
│   │   │   ├── connections.go
│   │   │   ├── dashboards.go
│   │   │   ├── governance.go
│   │   │   ├── governance_alerts.go
│   │   │   ├── governance_auditlog.go
│   │   │   ├── governance_querylog.go
│   │   │   ├── health.go
│   │   │   ├── license.go
│   │   │   ├── models.go
│   │   │   ├── pipelines.go
│   │   │   ├── query.go
│   │   │   ├── query_guardrails_test.go
│   │   │   ├── query_upload.go
│   │   │   ├── saved_queries.go
│   │   │   ├── schedules.go
│   │   │   └── view_graph.go
│   │   ├── middleware/
│   │   │   ├── context.go
│   │   │   ├── cors.go
│   │   │   ├── license.go
│   │   │   ├── logging.go
│   │   │   ├── ratelimit.go
│   │   │   ├── ratelimit_test.go
│   │   │   ├── security.go
│   │   │   └── session.go
│   │   └── server.go
│   ├── tunnel/
│   │   ├── api.go
│   │   ├── gateway.go
│   │   └── protocol.go
│   └── version/
│       └── version.go
├── license/
│   └── public.pem
├── main.go
└── ui/
    ├── .gitignore
    ├── README.md
    ├── index.html
    ├── package.json
    ├── src/
    │   ├── App.svelte
    │   ├── app.css
    │   ├── lib/
    │   │   ├── api/
    │   │   │   ├── alerts.ts
    │   │   │   ├── auth.ts
    │   │   │   ├── brain.ts
    │   │   │   ├── client.ts
    │   │   │   ├── governance.ts
    │   │   │   ├── models.ts
    │   │   │   ├── pipelines.ts
    │   │   │   ├── query.ts
    │   │   │   └── stream.ts
    │   │   ├── basePath.ts
    │   │   ├── components/
    │   │   │   ├── brain/
    │   │   │   │   ├── BrainArtifactCard.svelte
    │   │   │   │   ├── BrainEmptyState.svelte
    │   │   │   │   ├── BrainHeader.svelte
    │   │   │   │   ├── BrainInput.svelte
    │   │   │   │   ├── BrainMentionDropdown.svelte
    │   │   │   │   ├── BrainMessage.svelte
    │   │   │   │   ├── BrainSidebar.svelte
    │   │   │   │   ├── BrainSqlBlock.svelte
    │   │   │   │   └── brain-markdown.ts
    │   │   │   ├── common/
    │   │   │   │   ├── Button.svelte
    │   │   │   │   ├── Combobox.svelte
    │   │   │   │   ├── ConfirmDialog.svelte
    │   │   │   │   ├── ContextMenu.svelte
    │   │   │   │   ├── HelpTip.svelte
    │   │   │   │   ├── InputDialog.svelte
    │   │   │   │   ├── MiniTrendChart.svelte
    │   │   │   │   ├── Modal.svelte
    │   │   │   │   ├── ProRequired.svelte
    │   │   │   │   ├── Sheet.svelte
    │   │   │   │   ├── Spinner.svelte
    │   │   │   │   └── Toast.svelte
    │   │   │   ├── dashboard/
    │   │   │   │   ├── ChartPanel.svelte
    │   │   │   │   ├── DashboardGrid.svelte
    │   │   │   │   ├── PanelEditor.svelte
    │   │   │   │   ├── TimeRangeSelector.svelte
    │   │   │   │   └── time-picker/
    │   │   │   │       ├── CalendarMonth.svelte
    │   │   │   │       ├── DualCalendar.svelte
    │   │   │   │       ├── PresetList.svelte
    │   │   │   │       ├── TimeInput.svelte
    │   │   │   │       └── TimezoneSelect.svelte
    │   │   │   ├── editor/
    │   │   │   │   ├── InsightsPanel.svelte
    │   │   │   │   ├── ResultFooter.svelte
    │   │   │   │   ├── ResultPanel.svelte
    │   │   │   │   ├── SchemaPanel.svelte
    │   │   │   │   ├── SqlEditor.svelte
    │   │   │   │   ├── StatsPanel.svelte
    │   │   │   │   └── Toolbar.svelte
    │   │   │   ├── explorer/
    │   │   │   │   ├── DataPreview.svelte
    │   │   │   │   └── DatabaseTree.svelte
    │   │   │   ├── governance/
    │   │   │   │   ├── LineageGraph.svelte
    │   │   │   │   └── LineageTableNode.svelte
    │   │   │   ├── layout/
    │   │   │   │   ├── CommandPalette.svelte
    │   │   │   │   ├── Shell.svelte
    │   │   │   │   ├── Sidebar.svelte
    │   │   │   │   ├── TabBar.svelte
    │   │   │   │   ├── TabContent.svelte
    │   │   │   │   ├── TabGroup.svelte
    │   │   │   │   └── content/
    │   │   │   │       ├── DatabaseContent.svelte
    │   │   │   │       ├── ModelContent.svelte
    │   │   │   │       ├── QueryContent.svelte
    │   │   │   │       └── TableContent.svelte
    │   │   │   ├── models/
    │   │   │   │   └── ModelNode.svelte
    │   │   │   ├── pipelines/
    │   │   │   │   ├── NodeConfigPanel.svelte
    │   │   │   │   ├── PipelineCanvas.svelte
    │   │   │   │   ├── PipelineEditor.svelte
    │   │   │   │   ├── PipelineList.svelte
    │   │   │   │   ├── PipelineStatusBar.svelte
    │   │   │   │   ├── PipelineToolbar.svelte
    │   │   │   │   └── nodes/
    │   │   │   │       ├── SinkNode.svelte
    │   │   │   │       └── SourceNode.svelte
    │   │   │   └── table/
    │   │   │       ├── Pagination.svelte
    │   │   │       ├── TableCell.svelte
    │   │   │       ├── TableHeader.svelte
    │   │   │       └── VirtualTable.svelte
    │   │   ├── editor/
    │   │   │   └── completions.ts
    │   │   ├── stores/
    │   │   │   ├── command-palette.svelte.ts
    │   │   │   ├── license.svelte.ts
    │   │   │   ├── number-format.svelte.ts
    │   │   │   ├── query-limit.svelte.ts
    │   │   │   ├── router.svelte.ts
    │   │   │   ├── schema.svelte.ts
    │   │   │   ├── session.svelte.ts
    │   │   │   ├── tabs.svelte.ts
    │   │   │   ├── theme.svelte.ts
    │   │   │   └── toast.svelte.ts
    │   │   ├── types/
    │   │   │   ├── alerts.ts
    │   │   │   ├── api.ts
    │   │   │   ├── brain.ts
    │   │   │   ├── governance.ts
    │   │   │   ├── models.ts
    │   │   │   ├── pipelines.ts
    │   │   │   ├── query.ts
    │   │   │   └── schema.ts
    │   │   └── utils/
    │   │       ├── calendar.ts
    │   │       ├── ch-types.ts
    │   │       ├── chart-transform.ts
    │   │       ├── dashboard-time.test.ts
    │   │       ├── dashboard-time.ts
    │   │       ├── export.ts
    │   │       ├── format.ts
    │   │       ├── grid-layout.ts
    │   │       ├── lineage-layout.ts
    │   │       ├── safe-json.ts
    │   │       ├── sql.ts
    │   │       ├── stats.ts
    │   │       └── uuid.ts
    │   ├── main.ts
    │   └── pages/
    │       ├── Admin.svelte
    │       ├── Brain.svelte
    │       ├── Dashboards.svelte
    │       ├── Governance.svelte
    │       ├── Home.svelte
    │       ├── Login.svelte
    │       ├── Models.svelte
    │       ├── Pipelines.svelte
    │       ├── SavedQueries.svelte
    │       ├── Schedules.svelte
    │       └── Settings.svelte
    ├── svelte.config.js
    ├── tsconfig.app.json
    ├── tsconfig.json
    ├── tsconfig.node.json
    ├── vite.config.d.ts
    ├── vite.config.ts
    └── vitest.config.ts
Download .txt
SYMBOL INDEX (1864 symbols across 163 files)

FILE: cmd/connect.go
  function init (line 117) | func init() {
  function startDetached (line 129) | func startDetached() (int, string, error) {
  function sanitizeDetachedArgs (line 169) | func sanitizeDetachedArgs(in []string) []string {
  function pidFilePath (line 182) | func pidFilePath() string {
  function acquirePIDLock (line 186) | func acquirePIDLock() (func(), error) {
  function readPIDFile (line 234) | func readPIDFile(path string) (int, error) {
  function copyFile (line 248) | func copyFile(src, dst string) error {
  function fileExists (line 265) | func fileExists(path string) bool {

FILE: cmd/connect_detach_unix.go
  function setProcessDetachedAttr (line 10) | func setProcessDetachedAttr(cmd *exec.Cmd) {

FILE: cmd/connect_detach_windows.go
  function setProcessDetachedAttr (line 7) | func setProcessDetachedAttr(cmd *exec.Cmd) {

FILE: cmd/connect_process_unix.go
  function isProcessRunning (line 10) | func isProcessRunning(pid int) bool {

FILE: cmd/connect_process_windows.go
  function isProcessRunning (line 5) | func isProcessRunning(pid int) bool {

FILE: cmd/root.go
  function init (line 18) | func init() {
  function Execute (line 22) | func Execute() {
  function loadEnvFile (line 36) | func loadEnvFile(path string) {

FILE: cmd/server.go
  function init (line 130) | func init() {
  function runServer (line 149) | func runServer(cmd *cobra.Command) error {
  function buildServerStartArgs (line 262) | func buildServerStartArgs(cmd *cobra.Command) []string {
  function startDetachedServer (line 289) | func startDetachedServer(args []string) (int, string, error) {
  function stopServer (line 325) | func stopServer(pidFile string, timeout time.Duration) (bool, error) {
  function getRunningServerPID (line 367) | func getRunningServerPID(pidFile string) (int, bool, error) {
  function preparePIDFileForStart (line 384) | func preparePIDFileForStart(pidFile string) error {
  function writeServerPIDFile (line 395) | func writeServerPIDFile(pidFile string, pid int) error {
  function readServerPIDFile (line 408) | func readServerPIDFile(pidFile string) (int, error) {
  function cleanupServerPIDFile (line 424) | func cleanupServerPIDFile(pidFile string, expectedPID int) {
  function processExists (line 434) | func processExists(pid int) bool {
  function isTCPPortOpen (line 456) | func isTCPPortOpen(addr string) bool {
  function resolvePIDFile (line 467) | func resolvePIDFile(pidFile string) string {

FILE: cmd/service.go
  function init (line 241) | func init() {

FILE: cmd/tunnel.go
  function init (line 208) | func init() {
  function openTunnelDB (line 224) | func openTunnelDB() (*database.DB, *serverconfig.Config, error) {
  function printTunnelConnectionInfo (line 237) | func printTunnelConnectionInfo(cfg *serverconfig.Config, conn database.C...
  function inferPublicTunnelURL (line 261) | func inferPublicTunnelURL(cfg *serverconfig.Config) string {
  function websocketConnectURL (line 282) | func websocketConnectURL(raw string) string {
  function isLoopbackTunnelURL (line 310) | func isLoopbackTunnelURL(raw string) bool {
  function maskToken (line 320) | func maskToken(token string) string {
  function truncate (line 327) | func truncate(s string, max int) string {

FILE: cmd/uninstall.go
  type uninstallPlan (line 27) | type uninstallPlan struct
  function init (line 44) | func init() {
  function runUninstall (line 54) | func runUninstall(cmd *cobra.Command, args []string) error {
  function buildUninstallPlan (line 117) | func buildUninstallPlan() uninstallPlan {
  function stopDetachedConnectProcess (line 167) | func stopDetachedConnectProcess(pidFile string) error {
  function stopServerByPIDFile (line 202) | func stopServerByPIDFile(pidFile string) error {
  function uninstallConnectorService (line 221) | func uninstallConnectorService() error {
  function uninstallServerSystemService (line 240) | func uninstallServerSystemService() error {
  function runPrivileged (line 265) | func runPrivileged(name string, args ...string) error {
  function removePathIfExists (line 286) | func removePathIfExists(path string) (bool, error) {
  function printManualUninstallCommands (line 316) | func printManualUninstallCommands(plan uninstallPlan) {
  function manualUninstallCommands (line 331) | func manualUninstallCommands(plan uninstallPlan) []string {
  function shellQuote (line 382) | func shellQuote(s string) string {
  function uniqueNonEmpty (line 386) | func uniqueNonEmpty(in []string) []string {

FILE: cmd/update.go
  constant releasesURL (line 21) | releasesURL = "https://api.github.com/repos/caioricciuti/ch-ui/releases/...
  type ghRelease (line 24) | type ghRelease struct
  type ghAsset (line 29) | type ghAsset struct
  function init (line 47) | func init() {
  function runUpdate (line 54) | func runUpdate(cmd *cobra.Command, args []string) error {
  function detectServerRestartArgs (line 197) | func detectServerRestartArgs(pid int, pidFile string) []string {
  function readProcessArgs (line 209) | func readProcessArgs(pid int) ([]string, error) {
  function sanitizeServerStartArgs (line 234) | func sanitizeServerStartArgs(args []string, pidFile string) []string {
  function hasFlag (line 299) | func hasFlag(args []string, longName string) bool {
  function fetchLatestRelease (line 308) | func fetchLatestRelease() (*ghRelease, error) {
  function fetchExpectedChecksum (line 326) | func fetchExpectedChecksum(url, assetName string) (string, error) {
  function downloadFile (line 347) | func downloadFile(url, dest string) error {
  function fileSHA256 (line 368) | func fileSHA256(path string) (string, error) {
  function checkWritable (line 382) | func checkWritable(dir string) error {

FILE: cmd/version.go
  function init (line 18) | func init() {

FILE: connector/clickhouse.go
  type CHClient (line 21) | type CHClient struct
    method doWithRetry (line 94) | func (c *CHClient) doWithRetry(req *http.Request, client *http.Client)...
    method Execute (line 115) | func (c *CHClient) Execute(ctx context.Context, query, user, password ...
    method ExecuteRaw (line 196) | func (c *CHClient) ExecuteRaw(ctx context.Context, query, user, passwo...
    method ExecuteStreaming (line 258) | func (c *CHClient) ExecuteStreaming(
    method TestConnection (line 411) | func (c *CHClient) TestConnection(ctx context.Context, user, password ...
  function NewCHClient (line 28) | func NewCHClient(baseURL string, insecureSkipVerify bool) *CHClient {
  type QueryResult (line 56) | type QueryResult struct
  type ColumnMeta (line 68) | type ColumnMeta struct
  function isTransientError (line 75) | func isTransientError(err error) bool {
  type StreamChunk (line 250) | type StreamChunk struct
  function isWriteQuery (line 435) | func isWriteQuery(query string) bool {
  function hasFormatClause (line 442) | func hasFormatClause(query string) bool {
  function truncate (line 446) | func truncate(s string, maxLen int) string {

FILE: connector/config/config.go
  type Config (line 15) | type Config struct
    method Validate (line 171) | func (c *Config) Validate() error {
    method Redacted (line 217) | func (c *Config) Redacted() Config {
  type configFile (line 49) | type configFile struct
  function DefaultConfigPath (line 57) | func DefaultConfigPath() string {
  function Load (line 69) | func Load(configPath string, cliConfig *Config) (*Config, error) {
  function loadFromFile (line 101) | func loadFromFile(path string, cfg *Config) error {
  function loadFromEnv (line 126) | func loadFromEnv(cfg *Config) {
  function mergeConfig (line 141) | func mergeConfig(dst, src *Config) {
  function GenerateTemplate (line 192) | func GenerateTemplate() string {

FILE: connector/connector.go
  type Connector (line 22) | type Connector struct
    method Run (line 61) | func (c *Connector) Run() error {
    method Shutdown (line 88) | func (c *Connector) Shutdown() {
    method Stats (line 101) | func (c *Connector) Stats() (queriesExecuted int64, uptime time.Durati...
    method connect (line 121) | func (c *Connector) connect() error {
    method messageLoop (line 245) | func (c *Connector) messageLoop() {
    method handleMessage (line 290) | func (c *Connector) handleMessage(msg GatewayMessage) {
    method executeQuery (line 312) | func (c *Connector) executeQuery(msg GatewayMessage) {
    method executeStreamQuery (line 380) | func (c *Connector) executeStreamQuery(msg GatewayMessage) {
    method testConnection (line 439) | func (c *Connector) testConnection(msg GatewayMessage) {
    method heartbeatLoop (line 462) | func (c *Connector) heartbeatLoop() {
    method sendHostInfo (line 485) | func (c *Connector) sendHostInfo() {
    method hostInfoLoop (line 502) | func (c *Connector) hostInfoLoop() {
    method reconnect (line 526) | func (c *Connector) reconnect() {
    method send (line 557) | func (c *Connector) send(msg AgentMessage) error {
  function New (line 46) | func New(cfg *config.Config, u *ui.UI) *Connector {
  type ConnectError (line 111) | type ConnectError struct
    method Error (line 117) | func (e *ConnectError) Error() string {
  function isPermanentAuthError (line 234) | func isPermanentAuthError(msg string) bool {
  function truncateStr (line 432) | func truncateStr(s string, maxLen int) string {
  function extractHost (line 573) | func extractHost(urlStr string) string {

FILE: connector/hostinfo.go
  type HostInfo (line 10) | type HostInfo struct
  function CollectHostInfo (line 25) | func CollectHostInfo(agentStartTime time.Time) *HostInfo {
  function getMemoryInfo (line 53) | func getMemoryInfo() (total, free uint64) {

FILE: connector/hostinfo_unix.go
  function getDiskInfo (line 8) | func getDiskInfo() (total, free uint64) {

FILE: connector/hostinfo_windows.go
  function getDiskInfo (line 11) | func getDiskInfo() (total, free uint64) {

FILE: connector/protocol.go
  type GatewayMessage (line 4) | type GatewayMessage struct
  type AgentMessage (line 17) | type AgentMessage struct
  type QueryStats (line 34) | type QueryStats struct
  constant MsgTypeAuthOK (line 42) | MsgTypeAuthOK         = "auth_ok"
  constant MsgTypeAuthError (line 43) | MsgTypeAuthError      = "auth_error"
  constant MsgTypeQuery (line 44) | MsgTypeQuery          = "query"
  constant MsgTypeQueryStream (line 45) | MsgTypeQueryStream    = "query_stream"
  constant MsgTypePing (line 46) | MsgTypePing           = "ping"
  constant MsgTypeCancelQuery (line 47) | MsgTypeCancelQuery    = "cancel_query"
  constant MsgTypeTestConnection (line 48) | MsgTypeTestConnection = "test_connection"
  constant MsgTypeAuth (line 53) | MsgTypeAuth             = "auth"
  constant MsgTypePong (line 54) | MsgTypePong             = "pong"
  constant MsgTypeQueryResult (line 55) | MsgTypeQueryResult      = "query_result"
  constant MsgTypeQueryError (line 56) | MsgTypeQueryError       = "query_error"
  constant MsgTypeTestResult (line 57) | MsgTypeTestResult       = "test_result"
  constant MsgTypeHostInfo (line 58) | MsgTypeHostInfo         = "host_info"
  constant MsgTypeQueryStreamStart (line 59) | MsgTypeQueryStreamStart = "query_stream_start"
  constant MsgTypeQueryStreamChunk (line 60) | MsgTypeQueryStreamChunk = "query_stream_chunk"
  constant MsgTypeQueryStreamEnd (line 61) | MsgTypeQueryStreamEnd   = "query_stream_end"
  constant MsgTypeQueryStreamError (line 62) | MsgTypeQueryStreamError = "query_stream_error"

FILE: connector/service/launchd.go
  constant launchdPlistTemplate (line 12) | launchdPlistTemplate = `<?xml version="1.0" encoding="UTF-8"?>
  method launchdPlistPath (line 44) | func (m *Manager) launchdPlistPath() string {
  method launchdLogDir (line 49) | func (m *Manager) launchdLogDir() string {
  method launchdLogPath (line 54) | func (m *Manager) launchdLogPath() string {
  method launchdIsInstalled (line 58) | func (m *Manager) launchdIsInstalled() bool {
  method launchdIsRunning (line 62) | func (m *Manager) launchdIsRunning() (bool, error) {
  method launchdInstall (line 70) | func (m *Manager) launchdInstall(configPath string) error {
  method launchdUninstall (line 107) | func (m *Manager) launchdUninstall() error {
  method launchdStart (line 127) | func (m *Manager) launchdStart() error {
  method launchdStop (line 147) | func (m *Manager) launchdStop() error {
  method launchdRestart (line 161) | func (m *Manager) launchdRestart() error {
  method launchdStatus (line 181) | func (m *Manager) launchdStatus() (string, error) {
  method launchdLogs (line 208) | func (m *Manager) launchdLogs(follow bool, lines int) error {

FILE: connector/service/service.go
  constant ServiceName (line 13) | ServiceName      = "ch-ui"
  constant ServiceLabel (line 14) | ServiceLabel     = "com.ch-ui"
  constant BinaryPath (line 15) | BinaryPath       = "/usr/local/bin/ch-ui"
  constant SystemConfigDir (line 16) | SystemConfigDir  = "/etc/ch-ui"
  constant SystemConfigPath (line 17) | SystemConfigPath = "/etc/ch-ui/config.yaml"
  type Manager (line 21) | type Manager struct
    method IsInstalled (line 31) | func (m *Manager) IsInstalled() bool {
    method IsRunning (line 43) | func (m *Manager) IsRunning() (bool, error) {
    method Install (line 55) | func (m *Manager) Install(configPath string) error {
    method Uninstall (line 67) | func (m *Manager) Uninstall() error {
    method Start (line 79) | func (m *Manager) Start() error {
    method Stop (line 91) | func (m *Manager) Stop() error {
    method Restart (line 103) | func (m *Manager) Restart() error {
    method Status (line 115) | func (m *Manager) Status() (string, error) {
    method Logs (line 127) | func (m *Manager) Logs(follow bool, lines int) error {
    method GetLogPath (line 139) | func (m *Manager) GetLogPath() string {
    method Platform (line 152) | func (m *Manager) Platform() string {
    method NeedsSudo (line 157) | func (m *Manager) NeedsSudo() bool {
  function New (line 26) | func New() *Manager {
  function runCommand (line 164) | func runCommand(name string, args ...string) (string, error) {
  function runCommandWithSudo (line 171) | func runCommandWithSudo(needsSudo bool, name string, args ...string) (st...
  function fileExists (line 180) | func fileExists(path string) bool {
  function GetConfigPath (line 186) | func GetConfigPath() string {
  function GetConfigDir (line 197) | func GetConfigDir() string {

FILE: connector/service/systemd.go
  constant systemdServiceTemplate (line 11) | systemdServiceTemplate = `[Unit]
  constant systemdServicePath (line 37) | systemdServicePath = "/etc/systemd/system/ch-ui.service"
  method systemdIsInstalled (line 39) | func (m *Manager) systemdIsInstalled() bool {
  method systemdIsRunning (line 43) | func (m *Manager) systemdIsRunning() (bool, error) {
  method systemdInstall (line 51) | func (m *Manager) systemdInstall(configPath string) error {
  method systemdUninstall (line 107) | func (m *Manager) systemdUninstall() error {
  method systemdStart (line 128) | func (m *Manager) systemdStart() error {
  method systemdStop (line 146) | func (m *Manager) systemdStop() error {
  method systemdRestart (line 160) | func (m *Manager) systemdRestart() error {
  method systemdStatus (line 173) | func (m *Manager) systemdStatus() (string, error) {
  method systemdLogs (line 210) | func (m *Manager) systemdLogs(follow bool, lines int) error {

FILE: connector/ui/ui.go
  type UI (line 14) | type UI struct
    method Logo (line 55) | func (u *UI) Logo(version string) {
    method Info (line 73) | func (u *UI) Info(format string, args ...interface{}) {
    method Success (line 82) | func (u *UI) Success(format string, args ...interface{}) {
    method Error (line 91) | func (u *UI) Error(format string, args ...interface{}) {
    method DiagnosticError (line 111) | func (u *UI) DiagnosticError(errType ErrorType, source, message string...
    method AuthError (line 151) | func (u *UI) AuthError(serverMessage string) {
    method ConnectionError (line 195) | func (u *UI) ConnectionError(err error, tunnelURL string) {
    method Warn (line 238) | func (u *UI) Warn(format string, args ...interface{}) {
    method Debug (line 247) | func (u *UI) Debug(format string, args ...interface{}) {
    method Status (line 255) | func (u *UI) Status(tunnelURL, clickhouseURL string, uptime time.Durat...
    method QueryLog (line 271) | func (u *UI) QueryLog(queryID string, elapsed time.Duration, rows int) {
    method QueryError (line 283) | func (u *UI) QueryError(queryID string, err error) {
    method Disconnected (line 294) | func (u *UI) Disconnected(reason string) {
    method Reconnecting (line 303) | func (u *UI) Reconnecting(delay time.Duration) {
    method Box (line 312) | func (u *UI) Box(title string, lines map[string]string, order []string) {
  function New (line 32) | func New(noColor, quiet, verbose, jsonMode bool) *UI {
  type ErrorType (line 100) | type ErrorType
  constant ErrorTypeNetwork (line 103) | ErrorTypeNetwork ErrorType = "NETWORK"
  constant ErrorTypeAuth (line 104) | ErrorTypeAuth    ErrorType = "AUTH"
  constant ErrorTypeServer (line 105) | ErrorTypeServer  ErrorType = "SERVER"
  constant ErrorTypeConfig (line 106) | ErrorTypeConfig  ErrorType = "CONFIG"
  constant ErrorTypeUnknown (line 107) | ErrorTypeUnknown ErrorType = "UNKNOWN"
  function formatDuration (line 331) | func formatDuration(d time.Duration) string {
  function formatNumber (line 341) | func formatNumber(n int) string {
  function FormatBytes (line 352) | func FormatBytes(b uint64) string {

FILE: frontend.go
  function frontendFS (line 12) | func frontendFS() fs.FS {

FILE: internal/alerts/dispatcher.go
  constant ChannelTypeSMTP (line 24) | ChannelTypeSMTP   = "smtp"
  constant ChannelTypeResend (line 25) | ChannelTypeResend = "resend"
  constant ChannelTypeBrevo (line 26) | ChannelTypeBrevo  = "brevo"
  constant EventTypePolicyViolation (line 30) | EventTypePolicyViolation = "policy.violation"
  constant EventTypeScheduleFailed (line 31) | EventTypeScheduleFailed  = "schedule.failed"
  constant EventTypeScheduleSlow (line 32) | EventTypeScheduleSlow    = "schedule.slow"
  constant SeverityInfo (line 36) | SeverityInfo     = "info"
  constant SeverityWarn (line 37) | SeverityWarn     = "warn"
  constant SeverityError (line 38) | SeverityError    = "error"
  constant SeverityCritical (line 39) | SeverityCritical = "critical"
  constant dispatchTickInterval (line 43) | dispatchTickInterval = 8 * time.Second
  constant maxNewEventsPerTick (line 44) | maxNewEventsPerTick  = 100
  constant maxJobsPerTick (line 45) | maxJobsPerTick       = 30
  type Dispatcher (line 48) | type Dispatcher struct
    method Start (line 66) | func (d *Dispatcher) Start() {
    method Stop (line 84) | func (d *Dispatcher) Stop() {
    method tick (line 88) | func (d *Dispatcher) tick() {
    method materializeEventJobs (line 97) | func (d *Dispatcher) materializeEventJobs() {
    method processDueJobs (line 161) | func (d *Dispatcher) processDueJobs() {
    method processDueDigests (line 224) | func (d *Dispatcher) processDueDigests() {
    method sendByChannelType (line 290) | func (d *Dispatcher) sendByChannelType(ctx context.Context, channelTyp...
    method tryEscalationForDispatchJob (line 411) | func (d *Dispatcher) tryEscalationForDispatchJob(job database.AlertDis...
    method tryEscalationForDigest (line 444) | func (d *Dispatcher) tryEscalationForDigest(digest database.AlertRoute...
    method sendSMTP (line 553) | func (d *Dispatcher) sendSMTP(ctx context.Context, cfg map[string]inte...
    method sendResend (line 683) | func (d *Dispatcher) sendResend(ctx context.Context, cfg map[string]in...
    method sendBrevo (line 732) | func (d *Dispatcher) sendBrevo(ctx context.Context, cfg map[string]int...
  function NewDispatcher (line 55) | func NewDispatcher(db *database.DB, cfg *config.Config) *Dispatcher {
  function SendDirect (line 283) | func SendDirect(ctx context.Context, channelType string, channelConfig m...
  function ruleMatchesEvent (line 303) | func ruleMatchesEvent(rule database.AlertRule, event database.AlertEvent...
  function severityRank (line 312) | func severityRank(s string) int {
  function parseRecipients (line 327) | func parseRecipients(raw string) []string {
  function parseStringList (line 345) | func parseStringList(raw string) []string {
  function defaultBody (line 363) | func defaultBody(job database.AlertDispatchJobWithDetails) string {
  function renderTemplate (line 378) | func renderTemplate(tpl string, job database.AlertDispatchJobWithDetails...
  function retryBackoff (line 398) | func retryBackoff(attempt int) time.Duration {
  function renderDigestBody (line 477) | func renderDigestBody(digest database.AlertRouteDigestWithDetails) string {
  function coalesce (line 502) | func coalesce(v *string, fallback string) string {
  function stringCfg (line 509) | func stringCfg(cfg map[string]interface{}, key string) string {
  function boolCfg (line 517) | func boolCfg(cfg map[string]interface{}, key string, defaultVal bool) bo...
  function intCfg (line 535) | func intCfg(cfg map[string]interface{}, key string, defaultVal int) int {

FILE: internal/brain/provider.go
  type Message (line 17) | type Message struct
  type ProviderConfig (line 23) | type ProviderConfig struct
  type ChatResult (line 30) | type ChatResult struct
  type Provider (line 37) | type Provider interface
  function NewProvider (line 42) | func NewProvider(kind string) (Provider, error) {
  type openAIProvider (line 55) | type openAIProvider struct
    method baseURL (line 117) | func (p *openAIProvider) baseURL(cfg ProviderConfig) string {
    method StreamChat (line 182) | func (p *openAIProvider) StreamChat(ctx context.Context, cfg ProviderC...
    method streamChatAttempt (line 216) | func (p *openAIProvider) streamChatAttempt(ctx context.Context, cfg Pr...
    method ListModels (line 309) | func (p *openAIProvider) ListModels(ctx context.Context, cfg ProviderC...
  type openAIRequest (line 59) | type openAIRequest struct
  type openAIStreamOptions (line 67) | type openAIStreamOptions struct
  type openAIChunk (line 71) | type openAIChunk struct
  constant openAIDefaultTemperature (line 83) | openAIDefaultTemperature = 0.1
  function ensureOpenAIV1Base (line 85) | func ensureOpenAIV1Base(rawBase string) string {
  function shouldRetryOpenAIV1 (line 109) | func shouldRetryOpenAIV1(status int, body []byte) bool {
  function DefaultModelParameters (line 138) | func DefaultModelParameters(kind, model string) map[string]interface{} {
  function openAIRequestTemperature (line 147) | func openAIRequestTemperature(model string) *float64 {
  function openAIModelRequiresDefaultTemperature (line 155) | func openAIModelRequiresDefaultTemperature(model string) bool {
  function openAIModelParameters (line 163) | func openAIModelParameters(temperature *float64) map[string]interface{} {
  function isUnsupportedOpenAITemperature (line 171) | func isUnsupportedOpenAITemperature(status int, body []byte) bool {
  type ollamaProvider (line 376) | type ollamaProvider struct
    method baseURL (line 380) | func (p *ollamaProvider) baseURL(cfg ProviderConfig) string {
    method StreamChat (line 387) | func (p *ollamaProvider) StreamChat(ctx context.Context, cfg ProviderC...
    method ListModels (line 457) | func (p *ollamaProvider) ListModels(ctx context.Context, cfg ProviderC...

FILE: internal/brain/provider_test.go
  function TestOpenAIProviderStreamChatOmitsTemperatureForReasoningModels (line 14) | func TestOpenAIProviderStreamChatOmitsTemperatureForReasoningModels(t *t...
  function TestOpenAIProviderStreamChatRetriesWithoutTemperatureOnUnsupportedValue (line 68) | func TestOpenAIProviderStreamChatRetriesWithoutTemperatureOnUnsupportedV...
  function writeOpenAIStreamResponse (line 139) | func writeOpenAIStreamResponse(w http.ResponseWriter, content string, in...

FILE: internal/config/config.go
  type Config (line 15) | type Config struct
    method IsProduction (line 218) | func (c *Config) IsProduction() bool {
    method IsPro (line 222) | func (c *Config) IsPro() bool {
  type serverConfigFile (line 41) | type serverConfigFile struct
  function DefaultServerConfigPath (line 53) | func DefaultServerConfigPath() string {
  function Load (line 65) | func Load(configPath string) *Config {
  function loadServerConfigFile (line 144) | func loadServerConfigFile(path string, cfg *Config) error {
  function GenerateServerTemplate (line 184) | func GenerateServerTemplate() string {
  function trimQuotes (line 227) | func trimQuotes(s string) string {

FILE: internal/config/secret.go
  constant DefaultAppSecretKey (line 15) | DefaultAppSecretKey = "ch-ui-default-secret-key-change-in-production"
  type SecretKeySource (line 18) | type SecretKeySource
  constant SecretKeySourceConfigured (line 21) | SecretKeySourceConfigured SecretKeySource = "configured"
  constant SecretKeySourceFile (line 22) | SecretKeySourceFile       SecretKeySource = "file"
  constant SecretKeySourceGenerated (line 23) | SecretKeySourceGenerated  SecretKeySource = "generated"
  function AppSecretKeyPath (line 27) | func AppSecretKeyPath(databasePath string) string {
  function EnsureAppSecretKey (line 38) | func EnsureAppSecretKey(cfg *Config) (SecretKeySource, error) {
  function generateRandomSecret (line 76) | func generateRandomSecret(size int) (string, error) {

FILE: internal/config/secret_test.go
  function TestEnsureAppSecretKeyConfigured (line 8) | func TestEnsureAppSecretKeyConfigured(t *testing.T) {
  function TestEnsureAppSecretKeyGenerateAndReload (line 26) | func TestEnsureAppSecretKeyGenerateAndReload(t *testing.T) {

FILE: internal/crypto/aes.go
  function deriveKey (line 20) | func deriveKey(secret string) ([]byte, error) {
  function Encrypt (line 26) | func Encrypt(plaintext, secret string) (string, error) {
  function Decrypt (line 64) | func Decrypt(encryptedStr, secret string) (string, error) {
  function IsEncrypted (line 112) | func IsEncrypted(value string) bool {

FILE: internal/database/alert_digests.go
  type AlertRouteDigestWithDetails (line 13) | type AlertRouteDigestWithDetails struct
  method UpsertAlertRouteDigest (line 45) | func (db *DB) UpsertAlertRouteDigest(rule AlertRule, route AlertRuleRout...
  method ListDueAlertRouteDigests (line 138) | func (db *DB) ListDueAlertRouteDigests(limit int) ([]AlertRouteDigestWit...
  method MarkAlertRouteDigestSending (line 199) | func (db *DB) MarkAlertRouteDigestSending(id string) error {
  method MarkAlertRouteDigestSent (line 214) | func (db *DB) MarkAlertRouteDigestSent(id string) error {
  method MarkAlertRouteDigestRetry (line 229) | func (db *DB) MarkAlertRouteDigestRetry(id string, nextAttemptAt time.Ti...
  method MarkAlertRouteDigestFailed (line 248) | func (db *DB) MarkAlertRouteDigestFailed(id string, lastError string) er...
  function parseDigestStringArray (line 265) | func parseDigestStringArray(raw string) []string {

FILE: internal/database/alerts.go
  type AlertChannel (line 13) | type AlertChannel struct
  type AlertRule (line 24) | type AlertRule struct
  type AlertRuleRoute (line 39) | type AlertRuleRoute struct
  type AlertRuleRouteView (line 56) | type AlertRuleRouteView struct
  type AlertEvent (line 64) | type AlertEvent struct
  type AlertDispatchJob (line 79) | type AlertDispatchJob struct
  type AlertDispatchJobWithDetails (line 96) | type AlertDispatchJobWithDetails struct
  method CreateAlertChannel (line 122) | func (db *DB) CreateAlertChannel(name, channelType, encryptedConfig stri...
  method UpdateAlertChannel (line 136) | func (db *DB) UpdateAlertChannel(id, name, channelType string, encrypted...
  method DeleteAlertChannel (line 161) | func (db *DB) DeleteAlertChannel(id string) error {
  method GetAlertChannelByID (line 168) | func (db *DB) GetAlertChannelByID(id string) (*AlertChannel, error) {
  method ListAlertChannels (line 177) | func (db *DB) ListAlertChannels() ([]AlertChannel, error) {
  method CreateAlertRule (line 202) | func (db *DB) CreateAlertRule(name, eventType, severityMin string, enabl...
  method UpdateAlertRule (line 223) | func (db *DB) UpdateAlertRule(id, name, eventType, severityMin string, e...
  method DeleteAlertRule (line 243) | func (db *DB) DeleteAlertRule(id string) error {
  method GetAlertRuleByID (line 250) | func (db *DB) GetAlertRuleByID(id string) (*AlertRule, error) {
  method ListAlertRules (line 259) | func (db *DB) ListAlertRules() ([]AlertRule, error) {
  method ListEnabledAlertRules (line 284) | func (db *DB) ListEnabledAlertRules() ([]AlertRule, error) {
  method ReplaceAlertRuleRoutes (line 310) | func (db *DB) ReplaceAlertRuleRoutes(ruleID string, routes []AlertRuleRo...
  method ListAlertRuleRoutes (line 386) | func (db *DB) ListAlertRuleRoutes(ruleID string) ([]AlertRuleRouteView, ...
  method ListActiveAlertRuleRoutes (line 439) | func (db *DB) ListActiveAlertRuleRoutes(ruleID string) ([]AlertRuleRoute...
  method CreateAlertEvent (line 492) | func (db *DB) CreateAlertEvent(connectionID *string, eventType, severity...
  method ListAlertEvents (line 521) | func (db *DB) ListAlertEvents(limit int, eventType, status string) ([]Al...
  method ListNewAlertEvents (line 578) | func (db *DB) ListNewAlertEvents(limit int) ([]AlertEvent, error) {
  method MarkAlertEventProcessed (line 619) | func (db *DB) MarkAlertEventProcessed(id string) error {
  method HasRecentAlertDispatch (line 630) | func (db *DB) HasRecentAlertDispatch(routeID, fingerprint string, since ...
  method CreateAlertDispatchJob (line 650) | func (db *DB) CreateAlertDispatchJob(eventID, ruleID, routeID, channelID...
  method ListDueAlertDispatchJobs (line 666) | func (db *DB) ListDueAlertDispatchJobs(limit int) ([]AlertDispatchJobWit...
  method MarkAlertDispatchJobSending (line 740) | func (db *DB) MarkAlertDispatchJobSending(id string) error {
  method MarkAlertDispatchJobSent (line 755) | func (db *DB) MarkAlertDispatchJobSent(id, providerMessageID string) err...
  method MarkAlertDispatchJobRetry (line 771) | func (db *DB) MarkAlertDispatchJobRetry(id string, nextAttemptAt time.Ti...
  method MarkAlertDispatchJobFailed (line 787) | func (db *DB) MarkAlertDispatchJobFailed(id, lastError string) error {
  function scanAlertChannel (line 802) | func scanAlertChannel(scanner interface {
  function scanAlertChannelRow (line 819) | func scanAlertChannelRow(row *sql.Row) (*AlertChannel, error) {
  function scanAlertRule (line 838) | func scanAlertRule(scanner interface {
  function scanAlertRuleRow (line 857) | func scanAlertRuleRow(row *sql.Row) (*AlertRule, error) {
  function parseRecipientsJSON (line 878) | func parseRecipientsJSON(raw string) []string {

FILE: internal/database/audit_logs.go
  type AuditLogParams (line 12) | type AuditLogParams struct
  type AuditLog (line 21) | type AuditLog struct
  method CreateAuditLog (line 32) | func (db *DB) CreateAuditLog(params AuditLogParams) error {
  method GetAuditLogs (line 46) | func (db *DB) GetAuditLogs(limit int) ([]AuditLog, error) {
  method GetAuditLogsFiltered (line 51) | func (db *DB) GetAuditLogsFiltered(limit int, timeRange, action, usernam...

FILE: internal/database/audit_logs_test.go
  function openTestDB (line 8) | func openTestDB(t *testing.T) *DB {
  function insertAuditLogAt (line 21) | func insertAuditLogAt(t *testing.T, db *DB, action, username, details, i...
  function TestGetAuditLogsFiltered_TimeRangeActionUsernameSearch (line 33) | func TestGetAuditLogsFiltered_TimeRangeActionUsernameSearch(t *testing.T) {
  function TestGetAuditLogsFiltered_SearchMatchesMultipleFieldsCaseInsensitive (line 55) | func TestGetAuditLogsFiltered_SearchMatchesMultipleFieldsCaseInsensitive...

FILE: internal/database/brain.go
  type BrainProvider (line 13) | type BrainProvider struct
  type BrainProviderSecret (line 27) | type BrainProviderSecret struct
  type BrainModel (line 33) | type BrainModel struct
  type BrainModelRuntime (line 45) | type BrainModelRuntime struct
  type BrainChat (line 58) | type BrainChat struct
  type BrainMessage (line 75) | type BrainMessage struct
  type BrainArtifact (line 87) | type BrainArtifact struct
  type BrainToolCall (line 99) | type BrainToolCall struct
  type BrainSkill (line 112) | type BrainSkill struct
  function boolToInt (line 123) | func boolToInt(v bool) int {
  function intToBool (line 130) | func intToBool(v int) bool {
  function nullableString (line 134) | func nullableString(value string) interface{} {
  method GetBrainProviders (line 142) | func (db *DB) GetBrainProviders() ([]BrainProvider, error) {
  method GetBrainProviderByID (line 171) | func (db *DB) GetBrainProviderByID(id string) (*BrainProviderSecret, err...
  method CreateBrainProvider (line 192) | func (db *DB) CreateBrainProvider(name, kind, baseURL string, encryptedA...
  method UpdateBrainProvider (line 233) | func (db *DB) UpdateBrainProvider(id, name, kind, baseURL string, encryp...
  method DeleteBrainProvider (line 288) | func (db *DB) DeleteBrainProvider(id string) error {
  method GetBrainModels (line 296) | func (db *DB) GetBrainModels(providerID string) ([]BrainModel, error) {
  method GetBrainModelByID (line 331) | func (db *DB) GetBrainModelByID(id string) (*BrainModel, error) {
  method EnsureBrainModel (line 349) | func (db *DB) EnsureBrainModel(providerID, name, displayName string) (st...
  method UpdateBrainModel (line 378) | func (db *DB) UpdateBrainModel(id string, displayName string, isActive, ...
  method SetBrainModelActive (line 415) | func (db *DB) SetBrainModelActive(id string, isActive bool) error {
  method ClearDefaultBrainModelsByProvider (line 427) | func (db *DB) ClearDefaultBrainModelsByProvider(providerID string) error {
  method ClearDefaultBrainModelByProviderExcept (line 439) | func (db *DB) ClearDefaultBrainModelByProviderExcept(providerID, keepMod...
  method GetDefaultBrainModelRuntime (line 451) | func (db *DB) GetDefaultBrainModelRuntime() (*BrainModelRuntime, error) {
  method GetBrainModelRuntimeByID (line 491) | func (db *DB) GetBrainModelRuntimeByID(modelID string) (*BrainModelRunti...
  method GetBrainSkills (line 530) | func (db *DB) GetBrainSkills() ([]BrainSkill, error) {
  method GetActiveBrainSkill (line 557) | func (db *DB) GetActiveBrainSkill() (*BrainSkill, error) {
  method GetBrainSkillByID (line 575) | func (db *DB) GetBrainSkillByID(id string) (*BrainSkill, error) {
  method UpsertDefaultBrainSkill (line 593) | func (db *DB) UpsertDefaultBrainSkill(name, content, createdBy string) (...
  method UpdateBrainSkill (line 615) | func (db *DB) UpdateBrainSkill(id, name, content string, isActive, isDef...
  method CreateBrainSkill (line 645) | func (db *DB) CreateBrainSkill(name, content, createdBy string, isActive...
  method GetBrainChatsByUser (line 676) | func (db *DB) GetBrainChatsByUser(username, connectionID string, include...
  function scanBrainChat (line 704) | func scanBrainChat(scanner interface {
  method GetBrainChatByIDForUser (line 724) | func (db *DB) GetBrainChatByIDForUser(chatID, username string) (*BrainCh...
  method CreateBrainChat (line 746) | func (db *DB) CreateBrainChat(username, connectionID, title, providerID,...
  method UpdateBrainChat (line 764) | func (db *DB) UpdateBrainChat(chatID, title, providerID, modelID string,...
  method TouchBrainChat (line 774) | func (db *DB) TouchBrainChat(chatID string) error {
  method DeleteBrainChat (line 783) | func (db *DB) DeleteBrainChat(chatID string) error {
  method GetBrainMessages (line 791) | func (db *DB) GetBrainMessages(chatID string) ([]BrainMessage, error) {
  method CreateBrainMessage (line 815) | func (db *DB) CreateBrainMessage(chatID, role, content, status, errorTex...
  method UpdateBrainMessage (line 828) | func (db *DB) UpdateBrainMessage(id, content, status, errorText string) ...
  method CreateBrainArtifact (line 837) | func (db *DB) CreateBrainArtifact(chatID, messageID, artifactType, title...
  method GetBrainArtifacts (line 847) | func (db *DB) GetBrainArtifacts(chatID string) ([]BrainArtifact, error) {
  method CreateBrainToolCall (line 872) | func (db *DB) CreateBrainToolCall(chatID, messageID, toolName, inputJSON...
  method GetBrainModelsWithProvider (line 882) | func (db *DB) GetBrainModelsWithProvider(activeOnly bool) ([]map[string]...

FILE: internal/database/cleanup.go
  method StartCleanupJobs (line 10) | func (db *DB) StartCleanupJobs() {
  method cleanupExpiredSessions (line 35) | func (db *DB) cleanupExpiredSessions() {
  method cleanupRateLimits (line 48) | func (db *DB) cleanupRateLimits() {

FILE: internal/database/connections.go
  type Connection (line 14) | type Connection struct
  type HostInfo (line 26) | type HostInfo struct
  method GetConnections (line 41) | func (db *DB) GetConnections() ([]Connection, error) {
  method GetConnectionByToken (line 70) | func (db *DB) GetConnectionByToken(token string) (*Connection, error) {
  method GetConnectionByTokenCtx (line 99) | func (db *DB) GetConnectionByTokenCtx(ctx context.Context, token string)...
  method GetConnectionByID (line 127) | func (db *DB) GetConnectionByID(id string) (*Connection, error) {
  method GetConnectionCount (line 151) | func (db *DB) GetConnectionCount() (int, error) {
  method CreateConnection (line 161) | func (db *DB) CreateConnection(name, token string, isEmbedded bool) (str...
  method UpdateConnectionStatus (line 178) | func (db *DB) UpdateConnectionStatus(id, status string) error {
  method DeleteConnection (line 191) | func (db *DB) DeleteConnection(id string) error {
  method UpdateConnectionToken (line 200) | func (db *DB) UpdateConnectionToken(id, newToken string) error {
  method UpdateConnectionName (line 209) | func (db *DB) UpdateConnectionName(id, newName string) error {
  method UpdateConnectionHostInfo (line 218) | func (db *DB) UpdateConnectionHostInfo(connId string, info HostInfo) err...
  method GetConnectionHostInfo (line 234) | func (db *DB) GetConnectionHostInfo(connId string) (*HostInfo, error) {
  method GetEmbeddedConnection (line 258) | func (db *DB) GetEmbeddedConnection() (*Connection, error) {

FILE: internal/database/dashboards.go
  constant systemDashboardName (line 12) | systemDashboardName        = "System Overview"
  constant systemDashboardDescription (line 13) | systemDashboardDescription = "Built-in operational dashboard for ClickHo...
  constant systemDashboardCreatedBy (line 14) | systemDashboardCreatedBy   = "system"
  type Dashboard (line 18) | type Dashboard struct
  type Panel (line 28) | type Panel struct
  method GetDashboards (line 45) | func (db *DB) GetDashboards() ([]Dashboard, error) {
  method GetDashboardByID (line 73) | func (db *DB) GetDashboardByID(id string) (*Dashboard, error) {
  method CreateDashboard (line 94) | func (db *DB) CreateDashboard(name, description, createdBy string) (stri...
  method UpdateDashboard (line 118) | func (db *DB) UpdateDashboard(id, name, description string) error {
  method DeleteDashboard (line 137) | func (db *DB) DeleteDashboard(id string) error {
  method GetPanelsByDashboard (line 146) | func (db *DB) GetPanelsByDashboard(dashboardID string) ([]Panel, error) {
  method GetPanelByID (line 174) | func (db *DB) GetPanelByID(id string) (*Panel, error) {
  method CreatePanel (line 194) | func (db *DB) CreatePanel(dashboardID, name, panelType, query, connectio...
  method UpdatePanel (line 218) | func (db *DB) UpdatePanel(id, name, panelType, query, connectionID, conf...
  method DeletePanel (line 237) | func (db *DB) DeletePanel(id string) error {
  type seededPanel (line 245) | type seededPanel struct
  method EnsureSystemOverviewDashboard (line 258) | func (db *DB) EnsureSystemOverviewDashboard() error {

FILE: internal/database/database.go
  function nullStringToPtr (line 15) | func nullStringToPtr(ns sql.NullString) *string {
  type DB (line 23) | type DB struct
    method Close (line 74) | func (db *DB) Close() error {
    method Conn (line 79) | func (db *DB) Conn() *sql.DB {
  function Open (line 29) | func Open(path string) (*DB, error) {

FILE: internal/database/migrations.go
  method runMigrations (line 11) | func (db *DB) runMigrations() error {
  method migrateModelSchedulesAnchor (line 944) | func (db *DB) migrateModelSchedulesAnchor() error {
  method ensureColumn (line 1030) | func (db *DB) ensureColumn(tableName, columnName, definition string) err...

FILE: internal/database/migrations_guardrails_test.go
  function TestGuardrailColumnsExistAfterMigrations (line 5) | func TestGuardrailColumnsExistAfterMigrations(t *testing.T) {
  function mustHaveColumn (line 13) | func mustHaveColumn(t *testing.T, db *DB, tableName, columnName string) {

FILE: internal/database/models.go
  type Model (line 12) | type Model struct
  type ModelRun (line 31) | type ModelRun struct
  type ModelRunResult (line 46) | type ModelRunResult struct
  method GetModelsByConnection (line 63) | func (db *DB) GetModelsByConnection(connectionID string) ([]Model, error) {
  method GetModelByID (line 90) | func (db *DB) GetModelByID(id string) (*Model, error) {
  method GetModelByName (line 108) | func (db *DB) GetModelByName(connectionID, name string) (*Model, error) {
  method CreateModel (line 126) | func (db *DB) CreateModel(connectionID, name, description, targetDB, mat...
  method UpdateModel (line 149) | func (db *DB) UpdateModel(id, name, description, targetDB, materializati...
  method DeleteModel (line 165) | func (db *DB) DeleteModel(id string) error {
  method UpdateModelStatus (line 174) | func (db *DB) UpdateModelStatus(id, status, lastError string) error {
  method CreateModelRun (line 195) | func (db *DB) CreateModelRun(connectionID string, totalModels int, trigg...
  method FinalizeModelRun (line 216) | func (db *DB) FinalizeModelRun(id, status string, succeeded, failed, ski...
  method GetModelRuns (line 230) | func (db *DB) GetModelRuns(connectionID string, limit, offset int) ([]Mo...
  method GetModelRunByID (line 258) | func (db *DB) GetModelRunByID(id string) (*ModelRun, error) {
  method CreateModelRunResult (line 283) | func (db *DB) CreateModelRunResult(runID, modelID, modelName string) (st...
  method UpdateModelRunResult (line 299) | func (db *DB) UpdateModelRunResult(runID, modelID, status, resolvedSQL s...
  method GetModelRunResults (line 323) | func (db *DB) GetModelRunResults(runID string) ([]ModelRunResult, error) {
  method HasRunningModelRun (line 352) | func (db *DB) HasRunningModelRun(connectionID string) (bool, error) {
  function scanModel (line 366) | func scanModel(rows *sql.Rows) (Model, error) {
  function scanModelRow (line 381) | func scanModelRow(row *sql.Row) (*Model, error) {
  type ModelSchedule (line 400) | type ModelSchedule struct
  method GetModelSchedulesByConnection (line 416) | func (db *DB) GetModelSchedulesByConnection(connectionID string) ([]Mode...
  method GetModelScheduleByAnchor (line 439) | func (db *DB) GetModelScheduleByAnchor(connectionID, anchorModelID strin...
  method UpsertModelSchedule (line 470) | func (db *DB) UpsertModelSchedule(connectionID, anchorModelID, cron, nex...
  method UpdateModelScheduleStatusByID (line 495) | func (db *DB) UpdateModelScheduleStatusByID(scheduleID, status, lastErro...
  method DeleteModelScheduleByAnchor (line 516) | func (db *DB) DeleteModelScheduleByAnchor(connectionID, anchorModelID st...
  method GetEnabledModelSchedules (line 528) | func (db *DB) GetEnabledModelSchedules() ([]ModelSchedule, error) {
  function scanModelSchedule (line 550) | func scanModelSchedule(rows *sql.Rows) (ModelSchedule, error) {

FILE: internal/database/pipelines.go
  type Pipeline (line 12) | type Pipeline struct
  type PipelineNode (line 28) | type PipelineNode struct
  type PipelineEdge (line 41) | type PipelineEdge struct
  type PipelineRun (line 52) | type PipelineRun struct
  type PipelineRunLog (line 67) | type PipelineRunLog struct
  method GetPipelines (line 78) | func (db *DB) GetPipelines() ([]Pipeline, error) {
  method GetPipelineByID (line 105) | func (db *DB) GetPipelineByID(id string) (*Pipeline, error) {
  method GetPipelinesByStatus (line 133) | func (db *DB) GetPipelinesByStatus(status string) ([]Pipeline, error) {
  method CreatePipeline (line 160) | func (db *DB) CreatePipeline(name, description, connectionID, createdBy ...
  method UpdatePipeline (line 184) | func (db *DB) UpdatePipeline(id, name, description string) error {
  method UpdatePipelineStatus (line 203) | func (db *DB) UpdatePipelineStatus(id, status, lastError string) error {
  method DeletePipeline (line 233) | func (db *DB) DeletePipeline(id string) error {
  method SavePipelineGraph (line 244) | func (db *DB) SavePipelineGraph(pipelineID string, nodes []PipelineNode,...
  method GetPipelineGraph (line 318) | func (db *DB) GetPipelineGraph(pipelineID string) ([]PipelineNode, []Pip...
  method CreatePipelineRun (line 375) | func (db *DB) CreatePipelineRun(pipelineID, status string) (string, erro...
  method UpdatePipelineRun (line 391) | func (db *DB) UpdatePipelineRun(id, status string, rowsIngested, bytesIn...
  method GetPipelineRuns (line 418) | func (db *DB) GetPipelineRuns(pipelineID string, limit, offset int) ([]P...
  method CreatePipelineRunLog (line 452) | func (db *DB) CreatePipelineRunLog(runID, level, message string) error {
  method GetPipelineRunLogs (line 467) | func (db *DB) GetPipelineRunLogs(runID string, limit int) ([]PipelineRun...
  function scanPipeline (line 499) | func scanPipeline(rows *sql.Rows) (Pipeline, error) {

FILE: internal/database/rate_limits.go
  type RateLimitEntry (line 10) | type RateLimitEntry struct
  method GetRateLimit (line 21) | func (db *DB) GetRateLimit(identifier string) (*RateLimitEntry, error) {
  method UpsertRateLimit (line 47) | func (db *DB) UpsertRateLimit(identifier, limitType string, attempts int...
  method DeleteRateLimit (line 73) | func (db *DB) DeleteRateLimit(identifier string) error {
  method CleanupExpiredRateLimits (line 83) | func (db *DB) CleanupExpiredRateLimits(windowMs int64) (int64, error) {

FILE: internal/database/saved_queries.go
  type SavedQuery (line 12) | type SavedQuery struct
  type CreateSavedQueryParams (line 24) | type CreateSavedQueryParams struct
  method GetSavedQueries (line 33) | func (db *DB) GetSavedQueries() ([]SavedQuery, error) {
  method GetSavedQueryByID (line 62) | func (db *DB) GetSavedQueryByID(id string) (*SavedQuery, error) {
  method CreateSavedQuery (line 84) | func (db *DB) CreateSavedQuery(params CreateSavedQueryParams) (string, e...
  method UpdateSavedQuery (line 111) | func (db *DB) UpdateSavedQuery(id, name, description, query, connectionI...
  method DeleteSavedQuery (line 133) | func (db *DB) DeleteSavedQuery(id string) error {

FILE: internal/database/schedules.go
  type Schedule (line 12) | type Schedule struct
  type ScheduleRun (line 31) | type ScheduleRun struct
  method GetSchedules (line 44) | func (db *DB) GetSchedules() ([]Schedule, error) {
  method GetEnabledSchedules (line 70) | func (db *DB) GetEnabledSchedules() ([]Schedule, error) {
  method GetScheduleByID (line 96) | func (db *DB) GetScheduleByID(id string) (*Schedule, error) {
  method CreateSchedule (line 127) | func (db *DB) CreateSchedule(name, savedQueryID, connectionID, cron, tim...
  method UpdateSchedule (line 157) | func (db *DB) UpdateSchedule(id, name, cron, timezone string, enabled bo...
  method UpdateScheduleStatus (line 175) | func (db *DB) UpdateScheduleStatus(id, status, lastError string, nextRun...
  method DeleteSchedule (line 197) | func (db *DB) DeleteSchedule(id string) error {
  method CreateScheduleRun (line 206) | func (db *DB) CreateScheduleRun(scheduleID, status string) (string, erro...
  method UpdateScheduleRun (line 222) | func (db *DB) UpdateScheduleRun(id, status string, rowsAffected, elapsed...
  method GetScheduleRuns (line 241) | func (db *DB) GetScheduleRuns(scheduleID string, limit, offset int) ([]S...
  function scanSchedule (line 276) | func scanSchedule(rows *sql.Rows) (Schedule, error) {

FILE: internal/database/sessions.go
  type Session (line 12) | type Session struct
  type CreateSessionParams (line 24) | type CreateSessionParams struct
  type SessionUser (line 34) | type SessionUser struct
  method GetSession (line 42) | func (db *DB) GetSession(token string) (*Session, error) {
  method CreateSession (line 84) | func (db *DB) CreateSession(params CreateSessionParams) (string, error) {
  method DeleteSession (line 106) | func (db *DB) DeleteSession(token string) error {
  method SetSessionsUserRole (line 115) | func (db *DB) SetSessionsUserRole(username, role string) error {
  method GetUsers (line 127) | func (db *DB) GetUsers() ([]SessionUser, error) {
  method GetActiveSessionsByConnection (line 165) | func (db *DB) GetActiveSessionsByConnection(connectionID string, limit i...

FILE: internal/database/settings.go
  constant SettingGovernanceSyncEnabled (line 12) | SettingGovernanceSyncEnabled     = "governance.sync_enabled"
  constant SettingGovernanceUpgradeBanner (line 13) | SettingGovernanceUpgradeBanner   = "governance.upgrade_banner_dismissed"
  constant SettingGovernanceSyncUpdatedBy (line 14) | SettingGovernanceSyncUpdatedBy   = "governance.sync_updated_by"
  constant SettingGovernanceSyncUpdatedAt (line 15) | SettingGovernanceSyncUpdatedAt   = "governance.sync_updated_at"
  method GovernanceSyncEnabled (line 20) | func (db *DB) GovernanceSyncEnabled() bool {
  method SetGovernanceSyncEnabled (line 26) | func (db *DB) SetGovernanceSyncEnabled(enabled bool, actor string) error {
  method GetSetting (line 41) | func (db *DB) GetSetting(key string) (string, error) {
  method SetSetting (line 54) | func (db *DB) SetSetting(key, value string) error {
  method GetAllSettings (line 68) | func (db *DB) GetAllSettings() (map[string]string, error) {
  method DeleteSetting (line 90) | func (db *DB) DeleteSetting(key string) error {

FILE: internal/database/user_roles.go
  type UserRole (line 9) | type UserRole struct
  method GetUserRole (line 17) | func (db *DB) GetUserRole(username string) (string, error) {
  method SetUserRole (line 30) | func (db *DB) SetUserRole(username, role string) error {
  method DeleteUserRole (line 43) | func (db *DB) DeleteUserRole(username string) error {
  method GetAllUserRoles (line 52) | func (db *DB) GetAllUserRoles() ([]UserRole, error) {
  method CountUsersWithRole (line 74) | func (db *DB) CountUsersWithRole(role string) (int, error) {
  method IsUserRole (line 84) | func (db *DB) IsUserRole(username, role string) (bool, error) {

FILE: internal/embedded/embedded.go
  type EmbeddedAgent (line 18) | type EmbeddedAgent struct
    method Stop (line 97) | func (ea *EmbeddedAgent) Stop() {
  function Start (line 26) | func Start(db *database.DB, port int, clickhouseURL, connectionName stri...

FILE: internal/governance/guardrails.go
  constant defaultGuardrailStaleAfter (line 15) | defaultGuardrailStaleAfter = 10 * time.Minute
  type guardrailStore (line 19) | type guardrailStore interface
  type alertEventWriter (line 27) | type alertEventWriter interface
  type GuardrailService (line 31) | type GuardrailService struct
    method EvaluateQuery (line 61) | func (s *GuardrailService) EvaluateQuery(connectionID, user, queryText...
    method EvaluateTable (line 66) | func (s *GuardrailService) EvaluateTable(connectionID, user, databaseN...
    method evaluate (line 77) | func (s *GuardrailService) evaluate(connectionID, user, queryText stri...
    method isAccessStateUncertain (line 191) | func (s *GuardrailService) isAccessStateUncertain(connectionID string)...
    method emitUncertainGuardrailEvent (line 216) | func (s *GuardrailService) emitUncertainGuardrailEvent(connectionID, u...
  type GuardrailDecision (line 38) | type GuardrailDecision struct
  type GuardrailBlock (line 43) | type GuardrailBlock struct
  function NewGuardrailService (line 52) | func NewGuardrailService(store *Store, db *database.DB) *GuardrailService {
  function pickBlockingPolicy (line 243) | func pickBlockingPolicy(policies []Policy) Policy {
  function guardrailSeverityPriority (line 264) | func guardrailSeverityPriority(v string) int {
  function normalizeGuardrailSeverity (line 279) | func normalizeGuardrailSeverity(v string) string {
  function extractPolicyTablesFromQuery (line 292) | func extractPolicyTablesFromQuery(queryText string) []string {

FILE: internal/governance/guardrails_test.go
  type guardrailTestContext (line 10) | type guardrailTestContext struct
    method setAccessSyncFresh (line 47) | func (c *guardrailTestContext) setAccessSyncFresh(t *testing.T) {
    method createPolicy (line 54) | func (c *guardrailTestContext) createPolicy(t *testing.T, name, severi...
  function newGuardrailTestContext (line 17) | func newGuardrailTestContext(t *testing.T) *guardrailTestContext {
  function TestGuardrailsWarnPolicyAllowsExecution (line 75) | func TestGuardrailsWarnPolicyAllowsExecution(t *testing.T) {
  function TestGuardrailsBlockPolicyBlocksAndPersistsViolation (line 89) | func TestGuardrailsBlockPolicyBlocksAndPersistsViolation(t *testing.T) {
  function TestGuardrailsPickDeterministicBlockingPolicy (line 123) | func TestGuardrailsPickDeterministicBlockingPolicy(t *testing.T) {
  function TestGuardrailsUncertainAccessStateAllowsAndEmitsAlert (line 142) | func TestGuardrailsUncertainAccessStateAllowsAndEmitsAlert(t *testing.T) {
  function TestExtractPolicyTablesFromQuery (line 167) | func TestExtractPolicyTablesFromQuery(t *testing.T) {

FILE: internal/governance/harvester_access.go
  constant overPermissionInactiveDays (line 13) | overPermissionInactiveDays = 30
  method syncAccess (line 17) | func (s *Syncer) syncAccess(ctx context.Context, creds CHCredentials) (*...
  function toBool (line 264) | func toBool(v interface{}) bool {

FILE: internal/governance/harvester_metadata.go
  method syncMetadata (line 16) | func (s *Syncer) syncMetadata(ctx context.Context, creds CHCredentials) ...
  function toInt64 (line 277) | func toInt64(v interface{}) int64 {
  function toStringPtr (line 302) | func toStringPtr(v interface{}) *string {

FILE: internal/governance/harvester_querylog.go
  constant queryLogBatchLimit (line 17) | queryLogBatchLimit = 5000
  constant defaultQueryLogWatermark (line 18) | defaultQueryLogWatermark = "2000-01-01 00:00:00"
  method syncQueryLog (line 22) | func (s *Syncer) syncQueryLog(ctx context.Context, creds CHCredentials) ...
  function classifyQuery (line 293) | func classifyQuery(query string) string {
  function normalizeQuery (line 326) | func normalizeQuery(query string) string {
  function hashNormalized (line 334) | func hashNormalized(normalized string) string {
  function extractTablesJSON (line 339) | func extractTablesJSON(v interface{}) string {
  function sanitizeQueryLogWatermark (line 373) | func sanitizeQueryLogWatermark(v string) string {

FILE: internal/governance/incidents.go
  method CreateObjectComment (line 14) | func (s *Store) CreateObjectComment(connectionID, objectType, dbName, ta...
  method ListObjectComments (line 40) | func (s *Store) ListObjectComments(connectionID, objectType, dbName, tab...
  method DeleteObjectComment (line 102) | func (s *Store) DeleteObjectComment(connectionID, id string) error {
  method ListIncidents (line 119) | func (s *Store) ListIncidents(connectionID, status, severity string, lim...
  method GetIncidentByID (line 167) | func (s *Store) GetIncidentByID(id string) (*Incident, error) {
  method CreateIncident (line 185) | func (s *Store) CreateIncident(connectionID, sourceType, sourceRef, dedu...
  method UpdateIncident (line 214) | func (s *Store) UpdateIncident(id, title, severity, status, assignee, de...
  method UpsertIncidentFromViolation (line 239) | func (s *Store) UpsertIncidentFromViolation(connectionID, sourceRef, pol...
  method GetViolationByID (line 296) | func (s *Store) GetViolationByID(id string) (*PolicyViolation, error) {
  method ListIncidentComments (line 319) | func (s *Store) ListIncidentComments(incidentID string, limit int) ([]In...
  method CreateIncidentComment (line 355) | func (s *Store) CreateIncidentComment(incidentID, commentText, createdBy...
  function scanIncident (line 380) | func scanIncident(scanner interface {
  function nullableValue (line 418) | func nullableValue(v string) interface{} {

FILE: internal/governance/lineage.go
  constant tableRefPattern (line 15) | tableRefPattern = `((?:` + "`" + `[^` + "`" + `]+` + "`" + `|[a-zA-Z_][a...
  type tableRefParsed (line 25) | type tableRefParsed struct
  function ExtractLineage (line 36) | func ExtractLineage(connectionID string, entry QueryLogEntry) []LineageE...
  type ColumnMapping (line 88) | type ColumnMapping struct
  type LineageResult (line 94) | type LineageResult struct
  function ExtractColumnLineage (line 109) | func ExtractColumnLineage(query string) []ColumnMapping {
  function ExtractLineageWithColumns (line 154) | func ExtractLineageWithColumns(connectionID string, entry QueryLogEntry)...
  function splitAndTrimColumns (line 173) | func splitAndTrimColumns(s string) []string {
  function splitSelectExpressions (line 188) | func splitSelectExpressions(s string) []string {
  function extractColumnName (line 218) | func extractColumnName(expr string) string {
  function extractTarget (line 245) | func extractTarget(query string) *tableRefParsed {
  function classifyEdgeType (line 260) | func classifyEdgeType(query string) EdgeType {
  function extractSourceTables (line 272) | func extractSourceTables(query string) []tableRefParsed {
  function parseTableRef (line 303) | func parseTableRef(groups []string) (database, table string) {
  function stripBackticks (line 318) | func stripBackticks(s string) string {
  function isSystemTable (line 327) | func isSystemTable(db, table string) bool {
  function normaliseWhitespace (line 345) | func normaliseWhitespace(s string) string {

FILE: internal/governance/policy_engine.go
  type PolicyStore (line 19) | type PolicyStore interface
  function EvaluatePolicies (line 28) | func EvaluatePolicies(connectionID string, entry QueryLogEntry, policies...
  function parseTablesUsed (line 91) | func parseTablesUsed(raw string) []string {
  function collectUserRoles (line 105) | func collectUserRoles(entries []AccessMatrixEntry) map[string]bool {
  function hasRole (line 117) | func hasRole(userRoles map[string]bool, requiredRole string) bool {
  function queryTouchesObject (line 123) | func queryTouchesObject(tablesUsed []string, queryText string, policy Po...
  function touchesDatabase (line 151) | func touchesDatabase(tablesUsed []string, database string) bool {
  function touchesTable (line 167) | func touchesTable(tablesUsed []string, database, table string) bool {
  function columnMentioned (line 195) | func columnMentioned(queryText, column string) bool {
  function isIdentChar (line 217) | func isIdentChar(c byte) bool {
  function describePolicyObject (line 226) | func describePolicyObject(p Policy) string {
  function deref (line 251) | func deref(s *string) string {

FILE: internal/governance/store.go
  function nullStringToPtr (line 14) | func nullStringToPtr(ns sql.NullString) *string {
  function nullIntToPtr (line 22) | func nullIntToPtr(ni sql.NullInt64) *int {
  function ptrToNullString (line 31) | func ptrToNullString(s *string) sql.NullString {
  type Store (line 39) | type Store struct
    method conn (line 52) | func (s *Store) conn() *sql.DB {
    method GetSyncStates (line 59) | func (s *Store) GetSyncStates(connectionID string) ([]SyncState, error) {
    method GetSyncState (line 88) | func (s *Store) GetSyncState(connectionID string, syncType string) (*S...
    method UpsertSyncState (line 110) | func (s *Store) UpsertSyncState(connectionID string, syncType string, ...
    method UpdateSyncWatermark (line 133) | func (s *Store) UpdateSyncWatermark(connectionID string, syncType stri...
    method GetDatabases (line 148) | func (s *Store) GetDatabases(connectionID string) ([]GovDatabase, erro...
    method UpsertDatabase (line 173) | func (s *Store) UpsertDatabase(d GovDatabase) error {
    method MarkDatabaseDeleted (line 195) | func (s *Store) MarkDatabaseDeleted(connectionID, name string) error {
    method GetTables (line 210) | func (s *Store) GetTables(connectionID string) ([]GovTable, error) {
    method GetTablesByDatabase (line 224) | func (s *Store) GetTablesByDatabase(connectionID, databaseName string)...
    method GetTableByName (line 239) | func (s *Store) GetTableByName(connectionID, dbName, tableName string)...
    method UpsertTable (line 258) | func (s *Store) UpsertTable(t GovTable) error {
    method MarkTableDeleted (line 285) | func (s *Store) MarkTableDeleted(connectionID, dbName, tableName strin...
    method GetColumns (line 316) | func (s *Store) GetColumns(connectionID, dbName, tableName string) ([]...
    method UpsertColumn (line 355) | func (s *Store) UpsertColumn(c GovColumn) error {
    method MarkColumnDeleted (line 383) | func (s *Store) MarkColumnDeleted(connectionID, dbName, tableName, col...
    method InsertSchemaChange (line 398) | func (s *Store) InsertSchemaChange(sc SchemaChange) error {
    method CreateSchemaChange (line 411) | func (s *Store) CreateSchemaChange(connectionID string, changeType Sch...
    method GetSchemaChanges (line 427) | func (s *Store) GetSchemaChanges(connectionID string, limit int) ([]Sc...
    method BatchInsertQueryLog (line 456) | func (s *Store) BatchInsertQueryLog(connectionID string, entries []Que...
    method InsertQueryLogBatch (line 498) | func (s *Store) InsertQueryLogBatch(entries []QueryLogEntry) (int, err...
    method GetQueryLog (line 554) | func (s *Store) GetQueryLog(connectionID string, limit, offset int, us...
    method GetTopQueries (line 606) | func (s *Store) GetTopQueries(connectionID string, limit int) ([]map[s...
    method InsertLineageEdge (line 654) | func (s *Store) InsertLineageEdge(edge LineageEdge) error {
    method UpsertLineageEdge (line 670) | func (s *Store) UpsertLineageEdge(edge LineageEdge) error {
    method GetLineageForTable (line 675) | func (s *Store) GetLineageForTable(connectionID, dbName, tableName str...
    method GetFullLineageGraph (line 710) | func (s *Store) GetFullLineageGraph(connectionID string) ([]LineageEdg...
    method InsertColumnLineageEdge (line 742) | func (s *Store) InsertColumnLineageEdge(edge ColumnLineageEdge) error {
    method GetColumnEdgesForEdgeIDs (line 756) | func (s *Store) GetColumnEdgesForEdgeIDs(edgeIDs []string) (map[string...
    method GetQueryByQueryID (line 792) | func (s *Store) GetQueryByQueryID(connectionID, queryID string) (*Quer...
    method GetTags (line 815) | func (s *Store) GetTags(connectionID string) ([]TagEntry, error) {
    method GetTagsForTable (line 829) | func (s *Store) GetTagsForTable(connectionID, dbName, tableName string...
    method GetTagsForColumn (line 844) | func (s *Store) GetTagsForColumn(connectionID, dbName, tableName, colN...
    method CreateTag (line 859) | func (s *Store) CreateTag(connectionID, objectType, dbName, tableName,...
    method DeleteTag (line 875) | func (s *Store) DeleteTag(id string) error {
    method GetTaggedTableCount (line 884) | func (s *Store) GetTaggedTableCount(connectionID string) (int, error) {
    method DeleteChUsersForConnection (line 912) | func (s *Store) DeleteChUsersForConnection(connectionID string) error {
    method DeleteChRolesForConnection (line 920) | func (s *Store) DeleteChRolesForConnection(connectionID string) error {
    method DeleteRoleGrantsForConnection (line 928) | func (s *Store) DeleteRoleGrantsForConnection(connectionID string) err...
    method DeleteGrantsForConnection (line 936) | func (s *Store) DeleteGrantsForConnection(connectionID string) error {
    method UpsertChUser (line 946) | func (s *Store) UpsertChUser(u ChUser) error {
    method GetChUsers (line 964) | func (s *Store) GetChUsers(connectionID string) ([]ChUser, error) {
    method UpsertChRole (line 995) | func (s *Store) UpsertChRole(r ChRole) error {
    method GetChRoles (line 1010) | func (s *Store) GetChRoles(connectionID string) ([]ChRole, error) {
    method UpsertRoleGrant (line 1037) | func (s *Store) UpsertRoleGrant(rg RoleGrant) error {
    method GetRoleGrants (line 1063) | func (s *Store) GetRoleGrants(connectionID string) ([]RoleGrant, error) {
    method UpsertGrant (line 1090) | func (s *Store) UpsertGrant(g Grant) error {
    method GetGrants (line 1114) | func (s *Store) GetGrants(connectionID string) ([]Grant, error) {
    method RebuildAccessMatrix (line 1148) | func (s *Store) RebuildAccessMatrix(connectionID string) (int, error) {
    method GetAccessMatrix (line 1281) | func (s *Store) GetAccessMatrix(connectionID string) ([]AccessMatrixEn...
    method GetAccessMatrixForUser (line 1295) | func (s *Store) GetAccessMatrixForUser(connectionID, userName string) ...
    method UserHasRole (line 1310) | func (s *Store) UserHasRole(connectionID, userName, roleName string) (...
    method GetOverPermissions (line 1324) | func (s *Store) GetOverPermissions(connectionID string) ([]OverPermiss...
    method GetOverPermissionsWithDays (line 1330) | func (s *Store) GetOverPermissionsWithDays(connectionID string, inacti...
    method GetPolicies (line 1399) | func (s *Store) GetPolicies(connectionID string) ([]Policy, error) {
    method GetEnabledPolicies (line 1407) | func (s *Store) GetEnabledPolicies(connectionID string) ([]Policy, err...
    method scanPolicies (line 1414) | func (s *Store) scanPolicies(query string, args ...interface{}) ([]Pol...
    method GetPolicyByID (line 1443) | func (s *Store) GetPolicyByID(id string) (*Policy, error) {
    method CreatePolicy (line 1468) | func (s *Store) CreatePolicy(connectionID, name, description, objectTy...
    method UpdatePolicy (line 1501) | func (s *Store) UpdatePolicy(id, name, description, requiredRole, seve...
    method DeletePolicy (line 1525) | func (s *Store) DeletePolicy(id string) error {
    method InsertPolicyViolation (line 1536) | func (s *Store) InsertPolicyViolation(v PolicyViolation) error {
    method CreateViolation (line 1549) | func (s *Store) CreateViolation(connectionID, policyID, queryLogID, us...
    method GetViolations (line 1566) | func (s *Store) GetViolations(connectionID string, limit int, policyID...
    method GetOverview (line 1633) | func (s *Store) GetOverview(connectionID string) (*GovernanceOverview,...
    method CleanupOldQueryLogs (line 1678) | func (s *Store) CleanupOldQueryLogs(connectionID string, before string...
    method CleanupOldViolations (line 1690) | func (s *Store) CleanupOldViolations(connectionID string, before strin...
  function NewStore (line 44) | func NewStore(db *database.DB) *Store {
  function scanTables (line 297) | func scanTables(rows *sql.Rows) ([]GovTable, error) {
  function scanLineageEdges (line 724) | func scanLineageEdges(rows *sql.Rows) ([]LineageEdge, error) {
  function scanTags (line 896) | func scanTags(rows *sql.Rows) ([]TagEntry, error) {
  function scanAccessMatrix (line 1376) | func scanAccessMatrix(rows *sql.Rows) ([]AccessMatrixEntry, error) {
  function normalizePolicyEnforcementMode (line 1610) | func normalizePolicyEnforcementMode(v string) string {
  function normalizeDetectionPhase (line 1620) | func normalizeDetectionPhase(v string) string {

FILE: internal/governance/syncer.go
  constant syncTickInterval (line 17) | syncTickInterval = 5 * time.Minute
  constant queryTimeout (line 18) | queryTimeout     = 60 * time.Second
  constant staleDuration (line 19) | staleDuration    = 10 * time.Minute
  constant retentionDays (line 20) | retentionDays    = 30
  type Syncer (line 26) | type Syncer struct
    method GetStore (line 49) | func (s *Syncer) GetStore() *Store {
    method StartBackground (line 56) | func (s *Syncer) StartBackground() {
    method Stop (line 91) | func (s *Syncer) Stop() {
    method IsRunning (line 102) | func (s *Syncer) IsRunning() bool {
    method SyncConnection (line 110) | func (s *Syncer) SyncConnection(ctx context.Context, creds CHCredentia...
    method SyncSingle (line 150) | func (s *Syncer) SyncSingle(ctx context.Context, creds CHCredentials, ...
    method backgroundTick (line 169) | func (s *Syncer) backgroundTick() {
    method pruneRetention (line 232) | func (s *Syncer) pruneRetention(connections []database.Connection) {
    method isSyncStale (line 253) | func (s *Syncer) isSyncStale(connectionID string) bool {
    method findCredentials (line 276) | func (s *Syncer) findCredentials(connectionID string) (CHCredentials, ...
    method auditCredentialBorrow (line 302) | func (s *Syncer) auditCredentialBorrow(connectionID string, sess datab...
    method executeQuery (line 336) | func (s *Syncer) executeQuery(creds CHCredentials, sql string) ([]map[...
  function NewSyncer (line 39) | func NewSyncer(store *Store, db *database.DB, gw *tunnel.Gateway, secret...

FILE: internal/governance/types.go
  type SensitivityTag (line 5) | type SensitivityTag
  constant TagPII (line 8) | TagPII       SensitivityTag = "PII"
  constant TagFinancial (line 9) | TagFinancial SensitivityTag = "FINANCIAL"
  constant TagInternal (line 10) | TagInternal  SensitivityTag = "INTERNAL"
  constant TagPublic (line 11) | TagPublic    SensitivityTag = "PUBLIC"
  constant TagCritical (line 12) | TagCritical  SensitivityTag = "CRITICAL"
  type SyncType (line 22) | type SyncType
  constant SyncMetadata (line 25) | SyncMetadata SyncType = "metadata"
  constant SyncQueryLog (line 26) | SyncQueryLog SyncType = "query_log"
  constant SyncAccess (line 27) | SyncAccess   SyncType = "access"
  type SchemaChangeType (line 32) | type SchemaChangeType
  constant ChangeDatabaseAdded (line 35) | ChangeDatabaseAdded     SchemaChangeType = "database_added"
  constant ChangeDatabaseRemoved (line 36) | ChangeDatabaseRemoved   SchemaChangeType = "database_removed"
  constant ChangeTableAdded (line 37) | ChangeTableAdded        SchemaChangeType = "table_added"
  constant ChangeTableRemoved (line 38) | ChangeTableRemoved      SchemaChangeType = "table_removed"
  constant ChangeColumnAdded (line 39) | ChangeColumnAdded       SchemaChangeType = "column_added"
  constant ChangeColumnRemoved (line 40) | ChangeColumnRemoved     SchemaChangeType = "column_removed"
  constant ChangeColumnTypeChanged (line 41) | ChangeColumnTypeChanged SchemaChangeType = "column_type_changed"
  type EdgeType (line 46) | type EdgeType
  constant EdgeSelectFrom (line 49) | EdgeSelectFrom     EdgeType = "select_from"
  constant EdgeInsertSelect (line 50) | EdgeInsertSelect   EdgeType = "insert_select"
  constant EdgeCreateAsSelect (line 51) | EdgeCreateAsSelect EdgeType = "create_as_select"
  type SyncState (line 56) | type SyncState struct
  type GovDatabase (line 69) | type GovDatabase struct
  type GovTable (line 79) | type GovTable struct
  type GovColumn (line 95) | type GovColumn struct
  type SchemaChange (line 112) | type SchemaChange struct
  type QueryLogEntry (line 125) | type QueryLogEntry struct
  type LineageEdge (line 147) | type LineageEdge struct
  type ColumnLineageEdge (line 161) | type ColumnLineageEdge struct
  type TagEntry (line 169) | type TagEntry struct
  type ChUser (line 181) | type ChUser struct
  type ChRole (line 192) | type ChRole struct
  type RoleGrant (line 200) | type RoleGrant struct
  type Grant (line 211) | type Grant struct
  type AccessMatrixEntry (line 226) | type AccessMatrixEntry struct
  type Policy (line 238) | type Policy struct
  type PolicyViolation (line 256) | type PolicyViolation struct
  type ObjectComment (line 272) | type ObjectComment struct
  type Incident (line 285) | type Incident struct
  type IncidentComment (line 306) | type IncidentComment struct
  type GovernanceOverview (line 316) | type GovernanceOverview struct
  type OverPermission (line 334) | type OverPermission struct
  type SyncResult (line 347) | type SyncResult struct
  type MetadataSyncResult (line 356) | type MetadataSyncResult struct
  type QueryLogSyncResult (line 363) | type QueryLogSyncResult struct
  type AccessSyncResult (line 370) | type AccessSyncResult struct
  type CHCredentials (line 380) | type CHCredentials struct
  type LineageNode (line 388) | type LineageNode struct
  type LineageGraph (line 396) | type LineageGraph struct
  function StrPtr (line 403) | func StrPtr(s string) *string {

FILE: internal/langfuse/langfuse.go
  type Config (line 16) | type Config struct
    method Enabled (line 23) | func (c Config) Enabled() bool {
    method NormalizeBaseURL (line 28) | func (c *Config) NormalizeBaseURL() {
  type Usage (line 36) | type Usage struct
  type TraceParams (line 43) | type TraceParams struct
  type GenerationParams (line 56) | type GenerationParams struct
  type ScoreParams (line 71) | type ScoreParams struct
  type EventParams (line 80) | type EventParams struct
  type event (line 87) | type event struct
  type Client (line 96) | type Client struct
    method Reconfigure (line 116) | func (c *Client) Reconfigure(cfg Config) {
    method IsEnabled (line 129) | func (c *Client) IsEnabled() bool {
    method getConfig (line 135) | func (c *Client) getConfig() Config {
    method Start (line 142) | func (c *Client) Start() {
    method Stop (line 148) | func (c *Client) Stop() {
    method loop (line 154) | func (c *Client) loop() {
    method enqueue (line 187) | func (c *Client) enqueue(e event) {
    method LogTrace (line 207) | func (c *Client) LogTrace(p TraceParams) {
    method LogGeneration (line 247) | func (c *Client) LogGeneration(p GenerationParams) {
    method LogScore (line 287) | func (c *Client) LogScore(p ScoreParams) {
    method LogEvent (line 311) | func (c *Client) LogEvent(p EventParams) {
    method flush (line 331) | func (c *Client) flush(batch []event) {
    method TestConnection (line 372) | func (c *Client) TestConnection(cfg Config) error {
  function New (line 107) | func New() *Client {
  function now (line 198) | func now() string {
  function newID (line 202) | func newID() string {
  type ConnectionError (line 393) | type ConnectionError struct
    method Error (line 397) | func (e *ConnectionError) Error() string {

FILE: internal/license/license.go
  type LicenseFile (line 17) | type LicenseFile struct
  type LicenseInfo (line 29) | type LicenseInfo struct
  function CommunityLicense (line 38) | func CommunityLicense() *LicenseInfo {
  function ValidateLicense (line 47) | func ValidateLicense(licenseJSON string) *LicenseInfo {
  function SignablePayload (line 112) | func SignablePayload(lf LicenseFile) []byte {
  function parsePublicKey (line 144) | func parsePublicKey(pemData []byte) (ed25519.PublicKey, error) {

FILE: internal/license/tokens.go
  function GenerateTunnelToken (line 13) | func GenerateTunnelToken() string {
  function GenerateSessionToken (line 20) | func GenerateSessionToken() string {
  function IsValidTunnelToken (line 29) | func IsValidTunnelToken(token string) bool {

FILE: internal/models/dag.go
  type DepGraph (line 6) | type DepGraph struct
    method ConnectedComponents (line 78) | func (g *DepGraph) ConnectedComponents() [][]string {
    method ComponentContaining (line 128) | func (g *DepGraph) ComponentContaining(modelID string) []string {
  function BuildDAG (line 19) | func BuildDAG(modelIDs []string, refsByID map[string][]string, nameToID ...
  function GetUpstreamDeps (line 140) | func GetUpstreamDeps(modelID string, deps map[string][]string) map[strin...

FILE: internal/models/ref.go
  function stripSQLComments (line 11) | func stripSQLComments(sql string) string {
  function ValidateModelName (line 29) | func ValidateModelName(name string) error {
  function ExtractRefs (line 41) | func ExtractRefs(sqlBody string) []string {
  function ResolveRefs (line 58) | func ResolveRefs(sqlBody string, modelTargets map[string]string) (string...

FILE: internal/models/runner.go
  type Runner (line 15) | type Runner struct
    method RunAll (line 34) | func (r *Runner) RunAll(connectionID, triggeredBy string) (string, err...
    method RunPipeline (line 66) | func (r *Runner) RunPipeline(connectionID, anchorModelID, triggeredBy ...
    method RunSingle (line 104) | func (r *Runner) RunSingle(connectionID, modelID, triggeredBy string) ...
    method Validate (line 145) | func (r *Runner) Validate(connectionID string) ([]ValidationError, err...
    method buildDAG (line 212) | func (r *Runner) buildDAG(allModels []database.Model) (*DepGraph, map[...
    method execute (line 235) | func (r *Runner) execute(connectionID, triggeredBy string, dag *DepGra...
    method acquireLock (line 343) | func (r *Runner) acquireLock(connectionID string) error {
    method releaseLock (line 353) | func (r *Runner) releaseLock(connectionID string) {
    method findCredentials (line 359) | func (r *Runner) findCredentials(connectionID string) (string, string,...
  function NewRunner (line 24) | func NewRunner(db *database.DB, gw *tunnel.Gateway, secret string) *Runn...
  type ValidationError (line 204) | type ValidationError struct
  function buildDDL (line 328) | func buildDDL(m database.Model, resolvedSQL string) []string {

FILE: internal/models/scheduler.go
  constant modelTickInterval (line 11) | modelTickInterval = 30 * time.Second
  type Scheduler (line 14) | type Scheduler struct
    method Start (line 30) | func (s *Scheduler) Start() {
    method Stop (line 49) | func (s *Scheduler) Stop() {
    method tick (line 53) | func (s *Scheduler) tick() {
  function NewScheduler (line 21) | func NewScheduler(db *database.DB, runner *Runner) *Scheduler {

FILE: internal/pipelines/clickhouse_sink.go
  type ClickHouseSink (line 19) | type ClickHouseSink struct
    method Type (line 37) | func (s *ClickHouseSink) Type() string { return "sink_clickhouse" }
    method Validate (line 40) | func (s *ClickHouseSink) Validate(cfg ConnectorConfig) error {
    method WriteBatch (line 53) | func (s *ClickHouseSink) WriteBatch(ctx context.Context, cfg Connector...
    method ensureTable (line 108) | func (s *ClickHouseSink) ensureTable(ctx context.Context, cfg Connecto...
    method findCredentials (line 184) | func (s *ClickHouseSink) findCredentials(connectionID string) (string,...
  function NewClickHouseSink (line 29) | func NewClickHouseSink(gw *tunnel.Gateway, db *database.DB, secretKey st...
  function inferClickHouseType (line 168) | func inferClickHouseType(v interface{}) string {

FILE: internal/pipelines/database_source.go
  type DatabaseSource (line 17) | type DatabaseSource struct
    method Type (line 19) | func (d *DatabaseSource) Type() string { return "source_database" }
    method Validate (line 22) | func (d *DatabaseSource) Validate(cfg ConnectorConfig) error {
    method Start (line 43) | func (d *DatabaseSource) Start(ctx context.Context, cfg ConnectorConfi...

FILE: internal/pipelines/helpers.go
  function intField (line 4) | func intField(fields map[string]interface{}, key string, def int) int {
  function stringField (line 22) | func stringField(fields map[string]interface{}, key, def string) string {
  function boolField (line 35) | func boolField(fields map[string]interface{}, key string, def bool) bool {

FILE: internal/pipelines/kafka.go
  type KafkaSource (line 16) | type KafkaSource struct
    method Type (line 18) | func (k *KafkaSource) Type() string { return "source_kafka" }
    method Validate (line 21) | func (k *KafkaSource) Validate(cfg ConnectorConfig) error {
    method Start (line 34) | func (k *KafkaSource) Start(ctx context.Context, cfg ConnectorConfig, ...
  type kafkaGroupHandler (line 99) | type kafkaGroupHandler struct
    method Setup (line 105) | func (h *kafkaGroupHandler) Setup(_ sarama.ConsumerGroupSession) error...
    method Cleanup (line 106) | func (h *kafkaGroupHandler) Cleanup(_ sarama.ConsumerGroupSession) err...
    method ConsumeClaim (line 108) | func (h *kafkaGroupHandler) ConsumeClaim(session sarama.ConsumerGroupS...

FILE: internal/pipelines/kafka_scram.go
  type scramClient (line 18) | type scramClient struct
    method Begin (line 23) | func (c *scramClient) Begin(userName, password, authzID string) (err e...
    method Step (line 32) | func (c *scramClient) Step(challenge string) (string, error) {
    method Done (line 36) | func (c *scramClient) Done() bool {

FILE: internal/pipelines/registry.go
  function NewSource (line 6) | func NewSource(nodeType string) (SourceConnector, error) {

FILE: internal/pipelines/runner.go
  type RunningPipeline (line 17) | type RunningPipeline struct
  type Runner (line 27) | type Runner struct
    method Start (line 49) | func (r *Runner) Start() {
    method Stop (line 69) | func (r *Runner) Stop() {
    method StartPipeline (line 93) | func (r *Runner) StartPipeline(pipelineID string) error {
    method StopPipeline (line 213) | func (r *Runner) StopPipeline(pipelineID string) error {
    method GetRunningMetrics (line 237) | func (r *Runner) GetRunningMetrics(pipelineID string) *Metrics {
    method runPipeline (line 248) | func (r *Runner) runPipeline(ctx context.Context, rp *RunningPipeline,...
  function NewRunner (line 38) | func NewRunner(db *database.DB, gw *tunnel.Gateway, cfg *config.Config) ...
  function isSourceType (line 317) | func isSourceType(nodeType string) bool {
  function parseNodeConfig (line 325) | func parseNodeConfig(node *database.PipelineNode) (ConnectorConfig, erro...

FILE: internal/pipelines/s3_source.go
  type S3Source (line 19) | type S3Source struct
    method Type (line 21) | func (s *S3Source) Type() string { return "source_s3" }
    method Validate (line 24) | func (s *S3Source) Validate(cfg ConnectorConfig) error {
    method Start (line 46) | func (s *S3Source) Start(ctx context.Context, cfg ConnectorConfig, out...
    method processFile (line 119) | func (s *S3Source) processFile(ctx context.Context, client *minio.Clie...
    method parseNDJSON (line 139) | func (s *S3Source) parseNDJSON(ctx context.Context, r io.Reader, batch...
    method parseCSV (line 185) | func (s *S3Source) parseCSV(ctx context.Context, r io.Reader, batchSiz...

FILE: internal/pipelines/types.go
  type Record (line 10) | type Record struct
  type Batch (line 16) | type Batch struct
  type ConnectorConfig (line 22) | type ConnectorConfig struct
  type SourceConnector (line 28) | type SourceConnector interface
  type SinkConnector (line 41) | type SinkConnector interface
  type Metrics (line 48) | type Metrics struct

FILE: internal/pipelines/webhook.go
  type WebhookSource (line 18) | type WebhookSource struct
    method Type (line 20) | func (w *WebhookSource) Type() string { return "source_webhook" }
    method Validate (line 23) | func (w *WebhookSource) Validate(cfg ConnectorConfig) error {
    method Start (line 29) | func (w *WebhookSource) Start(ctx context.Context, cfg ConnectorConfig...
  type webhookReceiver (line 90) | type webhookReceiver struct
  function HandleWebhook (line 97) | func HandleWebhook(w http.ResponseWriter, r *http.Request) {
  function parseWebhookBody (line 159) | func parseWebhookBody(body []byte, contentType string) ([]Record, error) {

FILE: internal/queryproc/variables.go
  type TimeRange (line 13) | type TimeRange struct
  type ProcessorOptions (line 20) | type ProcessorOptions struct
  type ProcessedResult (line 30) | type ProcessedResult struct
  function HasTimeVariables (line 54) | func HasTimeVariables(query string) bool {
  function ProcessQueryVariables (line 69) | func ProcessQueryVariables(opts ProcessorOptions) ProcessedResult {
  function InferTimeUnit (line 196) | func InferTimeUnit(columnName string) string {
  function getTimeBounds (line 216) | func getTimeBounds(tr *TimeRange) (from, to time.Time, ok bool) {
  function parseRelativeTime (line 249) | func parseRelativeTime(timeStr string, base time.Time) time.Time {
  function parseAbsoluteTime (line 306) | func parseAbsoluteTime(s string) time.Time {
  function generateTimeFilter (line 334) | func generateTimeFilter(from, to time.Time, columnName, timeUnit string)...
  function convertToEpoch (line 341) | func convertToEpoch(t time.Time, unit string) int64 {
  function calculateInterval (line 358) | func calculateInterval(from, to time.Time, maxDataPoints int) int {

FILE: internal/queryproc/variables_test.go
  function TestParseRelativeTime_NowMinusMinutes (line 9) | func TestParseRelativeTime_NowMinusMinutes(t *testing.T) {
  function TestGetTimeBounds_RelativeRangeWithCustomTo (line 22) | func TestGetTimeBounds_RelativeRangeWithCustomTo(t *testing.T) {
  function TestProcessQueryVariables_TimestampMacroWithRelativeExpression (line 36) | func TestProcessQueryVariables_TimestampMacroWithRelativeExpression(t *t...

FILE: internal/scheduler/cron.go
  function parseField (line 11) | func parseField(field string, min, max int) map[int]bool {
  function ComputeNextRun (line 64) | func ComputeNextRun(cron string, from time.Time) *time.Time {
  function ValidateCron (line 104) | func ValidateCron(cron string) bool {

FILE: internal/scheduler/runner.go
  constant tickInterval (line 17) | tickInterval  = 30 * time.Second
  constant maxConcurrent (line 18) | maxConcurrent = 3
  type Runner (line 22) | type Runner struct
    method Start (line 40) | func (r *Runner) Start() {
    method Stop (line 59) | func (r *Runner) Stop() {
    method tick (line 64) | func (r *Runner) tick() {
    method runSchedule (line 110) | func (r *Runner) runSchedule(schedule database.Schedule) {
    method findCredentials (line 282) | func (r *Runner) findCredentials(connectionID string) (string, string,...
  function NewRunner (line 30) | func NewRunner(db *database.DB, gw *tunnel.Gateway, secret string) *Runn...
  function nullableConnectionID (line 266) | func nullableConnectionID(connectionID string) *string {
  function maxInt (line 274) | func maxInt(a, b int) int {
  function countRows (line 300) | func countRows(result *tunnel.QueryResult) int {

FILE: internal/server/handlers/admin.go
  type AdminHandler (line 24) | type AdminHandler struct
    method Routes (line 33) | func (h *AdminHandler) Routes(r chi.Router) {
    method GetUsers (line 75) | func (h *AdminHandler) GetUsers(w http.ResponseWriter, r *http.Request) {
    method fetchCurrentClickHouseUsers (line 160) | func (h *AdminHandler) fetchCurrentClickHouseUsers(r *http.Request) (m...
    method GetUserRoles (line 201) | func (h *AdminHandler) GetUserRoles(w http.ResponseWriter, r *http.Req...
    method SetUserRole (line 218) | func (h *AdminHandler) SetUserRole(w http.ResponseWriter, r *http.Requ...
    method DeleteUserRole (line 292) | func (h *AdminHandler) DeleteUserRole(w http.ResponseWriter, r *http.R...
    method GetConnections (line 350) | func (h *AdminHandler) GetConnections(w http.ResponseWriter, r *http.R...
    method GetStats (line 384) | func (h *AdminHandler) GetStats(w http.ResponseWriter, r *http.Request) {
    method GetClickHouseUsers (line 432) | func (h *AdminHandler) GetClickHouseUsers(w http.ResponseWriter, r *ht...
    method CreateClickHouseUser (line 474) | func (h *AdminHandler) CreateClickHouseUser(w http.ResponseWriter, r *...
    method UpdateClickHouseUserPassword (line 599) | func (h *AdminHandler) UpdateClickHouseUserPassword(w http.ResponseWri...
    method DeleteClickHouseUser (line 725) | func (h *AdminHandler) DeleteClickHouseUser(w http.ResponseWriter, r *...
  function buildClickHouseCreateAuthClause (line 677) | func buildClickHouseCreateAuthClause(authType, password string) string {
  function buildClickHouseAlterAuthClause (line 688) | func buildClickHouseAlterAuthClause(authType, password string) string {
  function parseDefaultRolesInput (line 699) | func parseDefaultRolesInput(input []string) (all bool, roles []string, e...
  function escapeString (line 786) | func escapeString(s string) string {

FILE: internal/server/handlers/admin_brain.go
  function normalizeProviderKind (line 20) | func normalizeProviderKind(kind string) (string, bool) {
  function modelDisplayName (line 33) | func modelDisplayName(m database.BrainModel) string {
  function scoreRecommendedModel (line 40) | func scoreRecommendedModel(name string) int {
  function pickRecommendedModel (line 62) | func pickRecommendedModel(models []database.BrainModel) *database.BrainM...
  function applyModelBulkAction (line 81) | func applyModelBulkAction(db *database.DB, providerID, action string) (i...
  method ListBrainProviders (line 144) | func (h *AdminHandler) ListBrainProviders(w http.ResponseWriter, r *http...
  method CreateBrainProvider (line 156) | func (h *AdminHandler) CreateBrainProvider(w http.ResponseWriter, r *htt...
  method UpdateBrainProvider (line 222) | func (h *AdminHandler) UpdateBrainProvider(w http.ResponseWriter, r *htt...
  method DeleteBrainProvider (line 315) | func (h *AdminHandler) DeleteBrainProvider(w http.ResponseWriter, r *htt...
  method SyncBrainProviderModels (line 342) | func (h *AdminHandler) SyncBrainProviderModels(w http.ResponseWriter, r ...
  method ListBrainModels (line 443) | func (h *AdminHandler) ListBrainModels(w http.ResponseWriter, r *http.Re...
  method UpdateBrainModel (line 452) | func (h *AdminHandler) UpdateBrainModel(w http.ResponseWriter, r *http.R...
  method BulkUpdateBrainModels (line 521) | func (h *AdminHandler) BulkUpdateBrainModels(w http.ResponseWriter, r *h...
  method ListBrainSkills (line 567) | func (h *AdminHandler) ListBrainSkills(w http.ResponseWriter, r *http.Re...
  method CreateBrainSkill (line 579) | func (h *AdminHandler) CreateBrainSkill(w http.ResponseWriter, r *http.R...
  method UpdateBrainSkill (line 629) | func (h *AdminHandler) UpdateBrainSkill(w http.ResponseWriter, r *http.R...

FILE: internal/server/handlers/admin_governance.go
  type governanceSettingsResponse (line 15) | type governanceSettingsResponse struct
  method GetGovernanceSettings (line 24) | func (h *AdminHandler) GetGovernanceSettings(w http.ResponseWriter, r *h...
  method UpdateGovernanceSettings (line 31) | func (h *AdminHandler) UpdateGovernanceSettings(w http.ResponseWriter, r...
  method buildGovernanceSettingsResponse (line 81) | func (h *AdminHandler) buildGovernanceSettingsResponse() governanceSetti...

FILE: internal/server/handlers/admin_langfuse.go
  method GetLangfuseConfig (line 15) | func (h *AdminHandler) GetLangfuseConfig(w http.ResponseWriter, r *http....
  method UpdateLangfuseConfig (line 34) | func (h *AdminHandler) UpdateLangfuseConfig(w http.ResponseWriter, r *ht...
  method DeleteLangfuseConfig (line 110) | func (h *AdminHandler) DeleteLangfuseConfig(w http.ResponseWriter, r *ht...
  method TestLangfuseConnection (line 135) | func (h *AdminHandler) TestLangfuseConnection(w http.ResponseWriter, r *...
  function loadLangfuseConfig (line 188) | func loadLangfuseConfig(db *database.DB, appSecretKey string) (langfuse....

FILE: internal/server/handlers/auth.go
  constant SessionCookie (line 25) | SessionCookie      = "chui_session"
  constant SessionDuration (line 26) | SessionDuration    = 7 * 24 * time.Hour
  constant RateLimitWindow (line 27) | RateLimitWindow    = 15 * time.Minute
  constant MaxAttemptsPerIP (line 28) | MaxAttemptsPerIP   = 5
  constant MaxAttemptsPerUser (line 29) | MaxAttemptsPerUser = 3
  type AuthHandler (line 33) | type AuthHandler struct
    method Routes (line 41) | func (h *AuthHandler) Routes(r chi.Router) {
    method Login (line 140) | func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
    method Logout (line 339) | func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
    method Session (line 377) | func (h *AuthHandler) Session(w http.ResponseWriter, r *http.Request) {
    method Connections (line 449) | func (h *AuthHandler) Connections(w http.ResponseWriter, r *http.Reque...
    method SwitchConnection (line 476) | func (h *AuthHandler) SwitchConnection(w http.ResponseWriter, r *http....
    method detectClickHouseRole (line 633) | func (h *AuthHandler) detectClickHouseRole(connectionID, username, pas...
    method resolveUserRole (line 669) | func (h *AuthHandler) resolveUserRole(connectionID, username, password...
  type loginRequest (line 51) | type loginRequest struct
    method resolvedConnectionID (line 74) | func (r loginRequest) resolvedConnectionID() string {
  type switchConnectionRequest (line 58) | type switchConnectionRequest struct
    method resolvedConnectionID (line 81) | func (r switchConnectionRequest) resolvedConnectionID() string {
  type connectionInfo (line 65) | type connectionInfo struct
  function normalizeRateLimitUsername (line 88) | func normalizeRateLimitUsername(username string) string {
  function userRateLimitKey (line 92) | func userRateLimitKey(username, connectionID string) string {
  function sanitizeClickHouseAuthMessage (line 96) | func sanitizeClickHouseAuthMessage(raw string) string {
  function shouldUseSecureCookie (line 120) | func shouldUseSecureCookie(r *http.Request, cfg *config.Config) bool {
  function classifyGrants (line 712) | func classifyGrants(result *tunnel.QueryResult) string {
  function isPermissionError (line 757) | func isPermissionError(errStr string) bool {
  function escapeSingleQuotes (line 766) | func escapeSingleQuotes(s string) string {
  function getClientIP (line 775) | func getClientIP(r *http.Request) string {

FILE: internal/server/handlers/auth_helpers_test.go
  function TestUserRateLimitKeyScopedByConnection (line 5) | func TestUserRateLimitKeyScopedByConnection(t *testing.T) {
  function TestSanitizeClickHouseAuthMessage (line 18) | func TestSanitizeClickHouseAuthMessage(t *testing.T) {

FILE: internal/server/handlers/brain.go
  constant baseBrainPrompt (line 24) | baseBrainPrompt = `You are Brain, an expert ClickHouse assistant for ana...
  type BrainHandler (line 39) | type BrainHandler struct
    method Routes (line 46) | func (h *BrainHandler) Routes(r chi.Router) {
    method ListModels (line 103) | func (h *BrainHandler) ListModels(w http.ResponseWriter, r *http.Reque...
    method GetSkill (line 117) | func (h *BrainHandler) GetSkill(w http.ResponseWriter, r *http.Request) {
    method ListChats (line 131) | func (h *BrainHandler) ListChats(w http.ResponseWriter, r *http.Reques...
    method CreateChat (line 152) | func (h *BrainHandler) CreateChat(w http.ResponseWriter, r *http.Reque...
    method GetChat (line 201) | func (h *BrainHandler) GetChat(w http.ResponseWriter, r *http.Request) {
    method UpdateChat (line 222) | func (h *BrainHandler) UpdateChat(w http.ResponseWriter, r *http.Reque...
    method DeleteChat (line 329) | func (h *BrainHandler) DeleteChat(w http.ResponseWriter, r *http.Reque...
    method ListMessages (line 355) | func (h *BrainHandler) ListMessages(w http.ResponseWriter, r *http.Req...
    method ListArtifacts (line 382) | func (h *BrainHandler) ListArtifacts(w http.ResponseWriter, r *http.Re...
    method RunQueryArtifact (line 409) | func (h *BrainHandler) RunQueryArtifact(w http.ResponseWriter, r *http...
    method StreamMessage (line 506) | func (h *BrainHandler) StreamMessage(w http.ResponseWriter, r *http.Re...
    method LegacyChat (line 782) | func (h *BrainHandler) LegacyChat(w http.ResponseWriter, r *http.Reque...
    method resolveRuntimeModel (line 929) | func (h *BrainHandler) resolveRuntimeModel(chat *database.BrainChat, r...
    method buildSystemPrompt (line 958) | func (h *BrainHandler) buildSystemPrompt(contexts []schemaContext) str...
  type schemaColumn (line 63) | type schemaColumn struct
  type schemaContext (line 68) | type schemaContext struct
  type createChatRequest (line 75) | type createChatRequest struct
  type updateChatRequest (line 80) | type updateChatRequest struct
  type streamMessageRequest (line 89) | type streamMessageRequest struct
  type runQueryArtifactRequest (line 96) | type runQueryArtifactRequest struct
  function brainModelParameters (line 922) | func brainModelParameters(result *braincore.ChatResult, providerKind, mo...
  function buildMultiSchemaPrompt (line 975) | func buildMultiSchemaPrompt(contexts []schemaContext) string {
  function writeSSE (line 1001) | func writeSSE(w http.ResponseWriter, flusher http.Flusher, payload map[s...
  function autoTitle (line 1013) | func autoTitle(prompt string) string {
  function isBrainReadOnlyQuery (line 1024) | func isBrainReadOnlyQuery(query string) bool {
  function containsSQL (line 1031) | func containsSQL(text string) bool {

FILE: internal/server/handlers/connections.go
  type ConnectionsHandler (line 21) | type ConnectionsHandler struct
    method List (line 37) | func (h *ConnectionsHandler) List(w http.ResponseWriter, r *http.Reque...
    method Get (line 55) | func (h *ConnectionsHandler) Get(w http.ResponseWriter, r *http.Reques...
    method Create (line 78) | func (h *ConnectionsHandler) Create(w http.ResponseWriter, r *http.Req...
    method Delete (line 131) | func (h *ConnectionsHandler) Delete(w http.ResponseWriter, r *http.Req...
    method TestConnection (line 173) | func (h *ConnectionsHandler) TestConnection(w http.ResponseWriter, r *...
    method GetToken (line 229) | func (h *ConnectionsHandler) GetToken(w http.ResponseWriter, r *http.R...
    method RegenerateToken (line 255) | func (h *ConnectionsHandler) RegenerateToken(w http.ResponseWriter, r ...
    method buildConnectionResponse (line 302) | func (h *ConnectionsHandler) buildConnectionResponse(c database.Connec...
  type connectionResponse (line 28) | type connectionResponse struct
  function getSetupInstructions (line 324) | func getSetupInstructions(token string) map[string]string {
  function connJSON (line 332) | func connJSON(w http.ResponseWriter, status int, v interface{}) {

FILE: internal/server/handlers/dashboards.go
  type DashboardsHandler (line 21) | type DashboardsHandler struct
    method Routes (line 28) | func (h *DashboardsHandler) Routes() chi.Router {
    method ListDashboards (line 52) | func (h *DashboardsHandler) ListDashboards(w http.ResponseWriter, r *h...
    method GetDashboard (line 72) | func (h *DashboardsHandler) GetDashboard(w http.ResponseWriter, r *htt...
    method CreateDashboard (line 106) | func (h *DashboardsHandler) CreateDashboard(w http.ResponseWriter, r *...
    method UpdateDashboard (line 151) | func (h *DashboardsHandler) UpdateDashboard(w http.ResponseWriter, r *...
    method DeleteDashboard (line 232) | func (h *DashboardsHandler) DeleteDashboard(w http.ResponseWriter, r *...
    method CreatePanel (line 274) | func (h *DashboardsHandler) CreatePanel(w http.ResponseWriter, r *http...
    method UpdatePanel (line 366) | func (h *DashboardsHandler) UpdatePanel(w http.ResponseWriter, r *http...
    method DeletePanel (line 482) | func (h *DashboardsHandler) DeletePanel(w http.ResponseWriter, r *http...
    method ExecutePanelQuery (line 525) | func (h *DashboardsHandler) ExecutePanelQuery(w http.ResponseWriter, r...

FILE: internal/server/handlers/governance.go
  type GovernanceHandler (line 24) | type GovernanceHandler struct
    method Routes (line 33) | func (h *GovernanceHandler) Routes() chi.Router {
    method getCredentials (line 128) | func (h *GovernanceHandler) getCredentials(r *http.Request) (*governan...
    method executeClickHouseSQL (line 144) | func (h *GovernanceHandler) executeClickHouseSQL(creds *governance.CHC...
    method triggerSyncAsync (line 152) | func (h *GovernanceHandler) triggerSyncAsync(creds governance.CHCreden...
    method connectionID (line 165) | func (h *GovernanceHandler) connectionID(r *http.Request) string {
    method GetOverview (line 198) | func (h *GovernanceHandler) GetOverview(w http.ResponseWriter, r *http...
    method TriggerSync (line 215) | func (h *GovernanceHandler) TriggerSync(w http.ResponseWriter, r *http...
    method TriggerSingleSync (line 247) | func (h *GovernanceHandler) TriggerSingleSync(w http.ResponseWriter, r...
    method GetSyncStatus (line 276) | func (h *GovernanceHandler) GetSyncStatus(w http.ResponseWriter, r *ht...
    method ListDatabases (line 298) | func (h *GovernanceHandler) ListDatabases(w http.ResponseWriter, r *ht...
    method ListTables (line 318) | func (h *GovernanceHandler) ListTables(w http.ResponseWriter, r *http....
    method GetTableDetail (line 358) | func (h *GovernanceHandler) GetTableDetail(w http.ResponseWriter, r *h...
    method UpdateTableComment (line 422) | func (h *GovernanceHandler) UpdateTableComment(w http.ResponseWriter, ...
    method UpdateColumnComment (line 473) | func (h *GovernanceHandler) UpdateColumnComment(w http.ResponseWriter,...
    method ListTableNotes (line 526) | func (h *GovernanceHandler) ListTableNotes(w http.ResponseWriter, r *h...
    method ListColumnNotes (line 547) | func (h *GovernanceHandler) ListColumnNotes(w http.ResponseWriter, r *...
    method CreateTableNote (line 569) | func (h *GovernanceHandler) CreateTableNote(w http.ResponseWriter, r *...
    method CreateColumnNote (line 612) | func (h *GovernanceHandler) CreateColumnNote(w http.ResponseWriter, r ...
    method DeleteObjectNote (line 656) | func (h *GovernanceHandler) DeleteObjectNote(w http.ResponseWriter, r ...
    method ListSchemaChanges (line 685) | func (h *GovernanceHandler) ListSchemaChanges(w http.ResponseWriter, r...
    method ListQueryLog (line 708) | func (h *GovernanceHandler) ListQueryLog(w http.ResponseWriter, r *htt...
    method TopQueries (line 733) | func (h *GovernanceHandler) TopQueries(w http.ResponseWriter, r *http....
    method GetLineage (line 756) | func (h *GovernanceHandler) GetLineage(w http.ResponseWriter, r *http....
    method GetLineageGraph (line 825) | func (h *GovernanceHandler) GetLineageGraph(w http.ResponseWriter, r *...
    method GetQueryByQueryID (line 908) | func (h *GovernanceHandler) GetQueryByQueryID(w http.ResponseWriter, r...
    method ListTags (line 932) | func (h *GovernanceHandler) ListTags(w http.ResponseWriter, r *http.Re...
    method CreateTag (line 962) | func (h *GovernanceHandler) CreateTag(w http.ResponseWriter, r *http.R...
    method DeleteTag (line 1017) | func (h *GovernanceHandler) DeleteTag(w http.ResponseWriter, r *http.R...
    method ListChUsers (line 1048) | func (h *GovernanceHandler) ListChUsers(w http.ResponseWriter, r *http...
    method CreateChUser (line 1068) | func (h *GovernanceHandler) CreateChUser(w http.ResponseWriter, r *htt...
    method DeleteChUser (line 1175) | func (h *GovernanceHandler) DeleteChUser(w http.ResponseWriter, r *htt...
    method ListChRoles (line 1222) | func (h *GovernanceHandler) ListChRoles(w http.ResponseWriter, r *http...
    method GetAccessMatrix (line 1242) | func (h *GovernanceHandler) GetAccessMatrix(w http.ResponseWriter, r *...
    method GetOverPermissions (line 1270) | func (h *GovernanceHandler) GetOverPermissions(w http.ResponseWriter, ...
    method ListPolicies (line 1293) | func (h *GovernanceHandler) ListPolicies(w http.ResponseWriter, r *htt...
    method CreatePolicy (line 1313) | func (h *GovernanceHandler) CreatePolicy(w http.ResponseWriter, r *htt...
    method GetPolicy (line 1375) | func (h *GovernanceHandler) GetPolicy(w http.ResponseWriter, r *http.R...
    method UpdatePolicy (line 1390) | func (h *GovernanceHandler) UpdatePolicy(w http.ResponseWriter, r *htt...
    method DeletePolicy (line 1440) | func (h *GovernanceHandler) DeletePolicy(w http.ResponseWriter, r *htt...
    method ListViolations (line 1466) | func (h *GovernanceHandler) ListViolations(w http.ResponseWriter, r *h...
    method CreateIncidentFromViolation (line 1489) | func (h *GovernanceHandler) CreateIncidentFromViolation(w http.Respons...
    method ListIncidents (line 1537) | func (h *GovernanceHandler) ListIncidents(w http.ResponseWriter, r *ht...
    method GetIncident (line 1560) | func (h *GovernanceHandler) GetIncident(w http.ResponseWriter, r *http...
    method CreateIncident (line 1584) | func (h *GovernanceHandler) CreateIncident(w http.ResponseWriter, r *h...
    method UpdateIncident (line 1646) | func (h *GovernanceHandler) UpdateIncident(w http.ResponseWriter, r *h...
    method ListIncidentComments (line 1726) | func (h *GovernanceHandler) ListIncidentComments(w http.ResponseWriter...
    method CreateIncidentComment (line 1758) | func (h *GovernanceHandler) CreateIncidentComment(w http.ResponseWrite...
  function queryInt (line 173) | func queryInt(r *http.Request, key string, defaultVal int) int {
  function queryIntBounded (line 185) | func queryIntBounded(r *http.Request, key string, defaultVal, minVal, ma...
  function enrichLineageNodes (line 876) | func enrichLineageNodes(store *governance.Store, connID string, nodes []...
  function enrichLineageEdges (line 888) | func enrichLineageEdges(store *governance.Store, edges []governance.Line...
  function normalizeIncidentSeverity (line 1809) | func normalizeIncidentSeverity(v string) string {
  function normalizePolicyEnforcementMode (line 1818) | func normalizePolicyEnforcementMode(v string) (string, error) {
  function normalizeIncidentStatus (line 1830) | func normalizeIncidentStatus(v string) string {
  function derefString (line 1839) | func derefString(v *string) string {

FILE: internal/server/handlers/governance_alerts.go
  type alertRuleRoutePayload (line 22) | type alertRuleRoutePayload struct
  type alertRuleResponse (line 33) | type alertRuleResponse struct
  method ListAlertChannels (line 38) | func (h *GovernanceHandler) ListAlertChannels(w http.ResponseWriter, r *...
  method CreateAlertChannel (line 75) | func (h *GovernanceHandler) CreateAlertChannel(w http.ResponseWriter, r ...
  method UpdateAlertChannel (line 138) | func (h *GovernanceHandler) UpdateAlertChannel(w http.ResponseWriter, r ...
  method DeleteAlertChannel (line 222) | func (h *GovernanceHandler) DeleteAlertChannel(w http.ResponseWriter, r ...
  method TestAlertChannel (line 256) | func (h *GovernanceHandler) TestAlertChannel(w http.ResponseWriter, r *h...
  method ListAlertRules (line 312) | func (h *GovernanceHandler) ListAlertRules(w http.ResponseWriter, r *htt...
  method CreateAlertRule (line 336) | func (h *GovernanceHandler) CreateAlertRule(w http.ResponseWriter, r *ht...
  method UpdateAlertRule (line 418) | func (h *GovernanceHandler) UpdateAlertRule(w http.ResponseWriter, r *ht...
  method DeleteAlertRule (line 521) | func (h *GovernanceHandler) DeleteAlertRule(w http.ResponseWriter, r *ht...
  method ListAlertEvents (line 554) | func (h *GovernanceHandler) ListAlertEvents(w http.ResponseWriter, r *ht...
  method validateRuleRoutes (line 572) | func (h *GovernanceHandler) validateRuleRoutes(payload []alertRuleRouteP...
  function sanitizeChannelConfig (line 652) | func sanitizeChannelConfig(channelType string, cfg map[string]interface{...
  function validateChannelConfig (line 673) | func validateChannelConfig(channelType string, cfg map[string]interface{...
  function validateRecipients (line 709) | func validateRecipients(values []string) ([]string, error) {
  function isSupportedChannelType (line 730) | func isSupportedChannelType(v string) bool {
  function isSupportedEventType (line 739) | func isSupportedEventType(v string) bool {
  function isSupportedSeverity (line 748) | func isSupportedSeverity(v string) bool {
  function coalesceStringPtr (line 757) | func coalesceStringPtr(v *string, fallback *string) string {

FILE: internal/server/handlers/governance_auditlog.go
  method GetAuditLogs (line 15) | func (h *GovernanceHandler) GetAuditLogs(w http.ResponseWriter, r *http....

FILE: internal/server/handlers/governance_querylog.go
  method GetClickHouseQueryLog (line 23) | func (h *GovernanceHandler) GetClickHouseQueryLog(w http.ResponseWriter,...
  function shouldFallbackToQueryThreadLog (line 150) | func shouldFallbackToQueryThreadLog(err error) bool {

FILE: internal/server/handlers/health.go
  type HealthHandler (line 11) | type HealthHandler struct
    method Health (line 13) | func (h *HealthHandler) Health(w http.ResponseWriter, r *http.Request) {

FILE: internal/server/handlers/license.go
  type LicenseHandler (line 14) | type LicenseHandler struct
    method GetLicense (line 21) | func (h *LicenseHandler) GetLicense(w http.ResponseWriter, r *http.Req...
    method ActivateLicense (line 28) | func (h *LicenseHandler) ActivateLicense(w http.ResponseWriter, r *htt...
    method DeactivateLicense (line 63) | func (h *LicenseHandler) DeactivateLicense(w http.ResponseWriter, r *h...

FILE: internal/server/handlers/models.go
  type ModelsHandler (line 21) | type ModelsHandler struct
    method Routes (line 29) | func (h *ModelsHandler) Routes() chi.Router {
    method ListModels (line 57) | func (h *ModelsHandler) ListModels(w http.ResponseWriter, r *http.Requ...
    method CreateModel (line 77) | func (h *ModelsHandler) CreateModel(w http.ResponseWriter, r *http.Req...
    method GetModel (line 138) | func (h *ModelsHandler) GetModel(w http.ResponseWriter, r *http.Reques...
    method UpdateModel (line 154) | func (h *ModelsHandler) UpdateModel(w http.ResponseWriter, r *http.Req...
    method DeleteModel (line 218) | func (h *ModelsHandler) DeleteModel(w http.ResponseWriter, r *http.Req...
    method GetDAG (line 228) | func (h *ModelsHandler) GetDAG(w http.ResponseWriter, r *http.Request) {
    method ValidateAll (line 339) | func (h *ModelsHandler) ValidateAll(w http.ResponseWriter, r *http.Req...
    method RunAll (line 359) | func (h *ModelsHandler) RunAll(w http.ResponseWriter, r *http.Request) {
    method RunSingle (line 376) | func (h *ModelsHandler) RunSingle(w http.ResponseWriter, r *http.Reque...
    method ListRuns (line 394) | func (h *ModelsHandler) ListRuns(w http.ResponseWriter, r *http.Reques...
    method GetRun (line 420) | func (h *ModelsHandler) GetRun(w http.ResponseWriter, r *http.Request) {
    method ListPipelines (line 447) | func (h *ModelsHandler) ListPipelines(w http.ResponseWriter, r *http.R...
    method RunPipeline (line 530) | func (h *ModelsHandler) RunPipeline(w http.ResponseWriter, r *http.Req...
    method ListSchedules (line 550) | func (h *ModelsHandler) ListSchedules(w http.ResponseWriter, r *http.R...
    method GetSchedule (line 570) | func (h *ModelsHandler) GetSchedule(w http.ResponseWriter, r *http.Req...
    method UpsertSchedule (line 588) | func (h *ModelsHandler) UpsertSchedule(w http.ResponseWriter, r *http....
    method DeleteSchedule (line 631) | func (h *ModelsHandler) DeleteSchedule(w http.ResponseWriter, r *http....

FILE: internal/server/handlers/pipelines.go
  type PipelinesHandler (line 20) | type PipelinesHandler struct
    method Routes (line 28) | func (h *PipelinesHandler) Routes() chi.Router {
    method ListPipelines (line 56) | func (h *PipelinesHandler) ListPipelines(w http.ResponseWriter, r *htt...
    method GetPipeline (line 72) | func (h *PipelinesHandler) GetPipeline(w http.ResponseWriter, r *http....
    method CreatePipeline (line 113) | func (h *PipelinesHandler) CreatePipeline(w http.ResponseWriter, r *ht...
    method UpdatePipeline (line 160) | func (h *PipelinesHandler) UpdatePipeline(w http.ResponseWriter, r *ht...
    method DeletePipeline (line 195) | func (h *PipelinesHandler) DeletePipeline(w http.ResponseWriter, r *ht...
    method SaveGraph (line 236) | func (h *PipelinesHandler) SaveGraph(w http.ResponseWriter, r *http.Re...
    method StartPipeline (line 298) | func (h *PipelinesHandler) StartPipeline(w http.ResponseWriter, r *htt...
    method StopPipeline (line 334) | func (h *PipelinesHandler) StopPipeline(w http.ResponseWriter, r *http...
    method GetStatus (line 370) | func (h *PipelinesHandler) GetStatus(w http.ResponseWriter, r *http.Re...
    method ListRuns (line 397) | func (h *PipelinesHandler) ListRuns(w http.ResponseWriter, r *http.Req...
    method GetRunLogs (line 427) | func (h *PipelinesHandler) GetRunLogs(w http.ResponseWriter, r *http.R...
  type graphNode (line 452) | type graphNode struct
  type graphEdge (line 461) | type graphEdge struct
  type graphViewport (line 469) | type graphViewport struct

FILE: internal/server/handlers/query.go
  constant maxQueryTimeout (line 23) | maxQueryTimeout = 5 * time.Minute
  type QueryHandler (line 26) | type QueryHandler struct
    method Routes (line 34) | func (h *QueryHandler) Routes(r chi.Router) {
    method ExecuteQuery (line 151) | func (h *QueryHandler) ExecuteQuery(w http.ResponseWriter, r *http.Req...
    method FormatSQL (line 241) | func (h *QueryHandler) FormatSQL(w http.ResponseWriter, r *http.Reques...
    method ExplainQuery (line 261) | func (h *QueryHandler) ExplainQuery(w http.ResponseWriter, r *http.Req...
    method QueryPlan (line 313) | func (h *QueryHandler) QueryPlan(w http.ResponseWriter, r *http.Reques...
    method EstimateQuery (line 389) | func (h *QueryHandler) EstimateQuery(w http.ResponseWriter, r *http.Re...
    method SampleQuery (line 503) | func (h *QueryHandler) SampleQuery(w http.ResponseWriter, r *http.Requ...
    method QueryProfile (line 616) | func (h *QueryHandler) QueryProfile(w http.ResponseWriter, r *http.Req...
    method StreamQuery (line 700) | func (h *QueryHandler) StreamQuery(w http.ResponseWriter, r *http.Requ...
    method ExplorerData (line 832) | func (h *QueryHandler) ExplorerData(w http.ResponseWriter, r *http.Req...
    method ListDatabases (line 946) | func (h *QueryHandler) ListDatabases(w http.ResponseWriter, r *http.Re...
    method ListTables (line 982) | func (h *QueryHandler) ListTables(w http.ResponseWriter, r *http.Reque...
    method ListColumns (line 1043) | func (h *QueryHandler) ListColumns(w http.ResponseWriter, r *http.Requ...
    method ListDataTypes (line 1087) | func (h *QueryHandler) ListDataTypes(w http.ResponseWriter, r *http.Re...
    method ListClusters (line 1151) | func (h *QueryHandler) ListClusters(w http.ResponseWriter, r *http.Req...
    method CreateDatabase (line 1189) | func (h *QueryHandler) CreateDatabase(w http.ResponseWriter, r *http.R...
    method DropDatabase (line 1275) | func (h *QueryHandler) DropDatabase(w http.ResponseWriter, r *http.Req...
    method CreateTable (line 1352) | func (h *QueryHandler) CreateTable(w http.ResponseWriter, r *http.Requ...
    method DropTable (line 1541) | func (h *QueryHandler) DropTable(w http.ResponseWriter, r *http.Reques...
    method GetHostInfo (line 1627) | func (h *QueryHandler) GetHostInfo(w http.ResponseWriter, r *http.Requ...
    method ListCompletions (line 1654) | func (h *QueryHandler) ListCompletions(w http.ResponseWriter, r *http....
    method guardrailsEnabled (line 1723) | func (h *QueryHandler) guardrailsEnabled() bool {
    method enforceGuardrailsForQuery (line 1733) | func (h *QueryHandler) enforceGuardrailsForQuery(w http.ResponseWriter...
    method enforceGuardrailsForTable (line 1756) | func (h *QueryHandler) enforceGuardrailsForTable(w http.ResponseWriter...
    method writePolicyBlocked (line 1779) | func (h *QueryHandler) writePolicyBlocked(w http.ResponseWriter, block...
    method requireSchemaAdmin (line 1827) | func (h *QueryHandler) requireSchemaAdmin(w http.ResponseWriter, r *ht...
  type executeQueryRequest (line 62) | type executeQueryRequest struct
  type executeQueryResponse (line 68) | type executeQueryResponse struct
  type formatRequest (line 77) | type formatRequest struct
  type formatResponse (line 81) | type formatResponse struct
  type explainRequest (line 85) | type explainRequest struct
  type sampleRequest (line 89) | type sampleRequest struct
  type planNode (line 96) | type planNode struct
  type createDatabaseRequest (line 103) | type createDatabaseRequest struct
  type dropDatabaseRequest (line 110) | type dropDatabaseRequest struct
  type createTableColumn (line 117) | type createTableColumn struct
  type createTableRequest (line 124) | type createTableRequest struct
  type dropTableRequest (line 140) | type dropTableRequest struct
  function toInt64 (line 481) | func toInt64(v interface{}) int64 {
  function writeJSON (line 1707) | func writeJSON(w http.ResponseWriter, status int, v interface{}) {
  function writeError (line 1714) | func writeError(w http.ResponseWriter, status int, message string) {
  function escapeIdentifier (line 1802) | func escapeIdentifier(name string) string {
  function escapeLiteral (line 1809) | func escapeLiteral(value string) string {
  function stripTrailingSemicolon (line 1813) | func stripTrailingSemicolon(query string) string {
  function stripFormatClause (line 1817) | func stripFormatClause(query string) string {
  function isReadOnlyQuery (line 1822) | func isReadOnlyQuery(query string) bool {
  function validateSimpleObjectName (line 1846) | func validateSimpleObjectName(name string, label string) error {
  function isUnsafeSQLFragment (line 1859) | func isUnsafeSQLFragment(value string) bool {
  function isSystemDatabaseName (line 1872) | func isSystemDatabaseName(name string) bool {
  function shouldFallbackToGlobalSample (line 1881) | func shouldFallbackToGlobalSample(message string) bool {
  function strPtr (line 1889) | func strPtr(s string) *string {
  function decodeRows (line 1893) | func decodeRows(data json.RawMessage) []map[string]interface{} {
  function extractExplainLines (line 1915) | func extractExplainLines(data json.RawMessage) []string {
  function buildPlanTree (line 1950) | func buildPlanTree(lines []string) []planNode {
  function planLineLevel (line 1991) | func planLineLevel(line string) int {
  function cleanPlanLabel (line 2014) | func cleanPlanLabel(line string) string {
  function countRows (line 2025) | func countRows(data json.RawMessage) int {
  function extractNames (line 2038) | func extractNames(data json.RawMessage) []string {
  function formatSQL (line 2063) | func formatSQL(sql string) string {

FILE: internal/server/handlers/query_guardrails_test.go
  function TestQueryEndpointsBlockedByGuardrailPolicy (line 15) | func TestQueryEndpointsBlockedByGuardrailPolicy(t *testing.T) {
  function newBlockedQueryHandler (line 61) | func newBlockedQueryHandler(t *testing.T) (*QueryHandler, func()) {

FILE: internal/server/handlers/query_upload.go
  constant maxUploadBytes (line 29) | maxUploadBytes       = 25 * 1024 * 1024
  constant maxUploadPreviewRows (line 30) | maxUploadPreviewRows = 20
  type uploadDiscoveredColumn (line 33) | type uploadDiscoveredColumn struct
  type parsedUploadDataset (line 40) | type parsedUploadDataset struct
  type uploadInsertColumn (line 45) | type uploadInsertColumn struct
  method DiscoverUploadSchema (line 51) | func (h *QueryHandler) DiscoverUploadSchema(w http.ResponseWriter, r *ht...
  method IngestUpload (line 90) | func (h *QueryHandler) IngestUpload(w http.ResponseWriter, r *http.Reque...
  function readUploadFile (line 237) | func readUploadFile(w http.ResponseWriter, r *http.Request) (filename, f...
  function detectUploadFormat (line 277) | func detectUploadFormat(filename string, explicit string) (string, error) {
  function parseUploadDataset (line 301) | func parseUploadDataset(format string, payload []byte) (parsedUploadData...
  function parseCSVDataset (line 316) | func parseCSVDataset(payload []byte) (parsedUploadDataset, error) {
  function parseJSONDataset (line 362) | func parseJSONDataset(payload []byte) (parsedUploadDataset, error) {
  function parseJSONLinesDataset (line 395) | func parseJSONLinesDataset(payload []byte) (parsedUploadDataset, error) {
  function parseParquetDataset (line 423) | func parseParquetDataset(payload []byte) (parsedUploadDataset, error) {
  function normalizeCSVHeaders (line 485) | func normalizeCSVHeaders(header []string) []string {
  function normalizeRowFromAny (line 508) | func normalizeRowFromAny(raw interface{}) map[string]interface{} {
  function normalizeUploadValue (line 539) | func normalizeUploadValue(value interface{}) interface{} {
  function inferUploadColumns (line 613) | func inferUploadColumns(dataset parsedUploadDataset) []uploadDiscoveredC...
  function inferUploadColumnType (line 675) | func inferUploadColumnType(values []interface{}) (baseType string, nulla...
  function isDateTimeString (line 768) | func isDateTimeString(value string) bool {
  function parseUploadColumnsForm (line 785) | func parseUploadColumnsForm(raw string) ([]uploadDiscoveredColumn, error) {
  function parseMultipartBool (line 812) | func parseMultipartBool(raw string, defaultValue bool) bool {
  function boolPtr (line 827) | func boolPtr(value bool) *bool {
  function buildCreateTableSQL (line 831) | func buildCreateTableSQL(req createTableRequest) (string, error) {
  method resolveInsertColumns (line 965) | func (h *QueryHandler) resolveInsertColumns(
  method insertJSONEachRowBatches (line 1068) | func (h *QueryHandler) insertJSONEachRowBatches(
  function buildJSONEachRowInsertQuery (line 1114) | func buildJSONEachRowInsertQuery(
  function humanizeUploadInsertError (line 1194) | func humanizeUploadInsertError(message string) string {
  function truncateUploadCommand (line 1203) | func truncateUploadCommand(sql string, limit int) string {
  function coerceUploadValueForType (line 1210) | func coerceUploadValueForType(value interface{}, typeExpr string) (inter...
  function normalizeClickHouseType (line 1255) | func normalizeClickHouseType(typeExpr string) string {
  function parseBoolUploadValue (line 1274) | func parseBoolUploadValue(value interface{}) (bool, error) {
  function parseIntUploadValue (line 1301) | func parseIntUploadValue(value interface{}) (int64, error) {
  function parseFloatUploadValue (line 1340) | func parseFloatUploadValue(value interface{}) (float64, error) {
  function parseDateUploadValue (line 1375) | func parseDateUploadValue(value interface{}) (string, error) {
  function parseDateTimeUploadValue (line 1383) | func parseDateTimeUploadValue(value interface{}) (string, error) {
  function parseFlexibleTime (line 1391) | func parseFlexibleTime(value interface{}) (time.Time, error) {

FILE: internal/server/handlers/saved_queries.go
  type SavedQueriesHandler (line 16) | type SavedQueriesHandler struct
    method Routes (line 21) | func (h *SavedQueriesHandler) Routes(r chi.Router) {
    method List (line 31) | func (h *SavedQueriesHandler) List(w http.ResponseWriter, r *http.Requ...
    method Get (line 47) | func (h *SavedQueriesHandler) Get(w http.ResponseWriter, r *http.Reque...
    method Create (line 69) | func (h *SavedQueriesHandler) Create(w http.ResponseWriter, r *http.Re...
    method Update (line 132) | func (h *SavedQueriesHandler) Update(w http.ResponseWriter, r *http.Re...
    method Delete (line 233) | func (h *SavedQueriesHandler) Delete(w http.ResponseWriter, r *http.Re...
    method Duplicate (line 273) | func (h *SavedQueriesHandler) Duplicate(w http.ResponseWriter, r *http...

FILE: internal/server/handlers/schedules.go
  type SchedulesHandler (line 23) | type SchedulesHandler struct
    method Routes (line 30) | func (h *SchedulesHandler) Routes(r chi.Router) {
    method List (line 41) | func (h *SchedulesHandler) List(w http.ResponseWriter, r *http.Request) {
    method Get (line 57) | func (h *SchedulesHandler) Get(w http.ResponseWriter, r *http.Request) {
    method Create (line 79) | func (h *SchedulesHandler) Create(w http.ResponseWriter, r *http.Reque...
    method Update (line 180) | func (h *SchedulesHandler) Update(w http.ResponseWriter, r *http.Reque...
    method Delete (line 291) | func (h *SchedulesHandler) Delete(w http.ResponseWriter, r *http.Reque...
    method ListRuns (line 331) | func (h *SchedulesHandler) ListRuns(w http.ResponseWriter, r *http.Req...
    method ManualRun (line 386) | func (h *SchedulesHandler) ManualRun(w http.ResponseWriter, r *http.Re...

FILE: internal/server/handlers/view_graph.go
  type viewEntry (line 18) | type viewEntry struct
  constant vgTableRef (line 28) | vgTableRef = "(" +
  method GetViewGraph (line 41) | func (h *GovernanceHandler) GetViewGraph(w http.ResponseWriter, r *http....
  function buildViewGraph (line 100) | func buildViewGraph(rows []viewEntry) governance.LineageGraph {
  type parsedRef (line 202) | type parsedRef struct
    method key (line 207) | func (r parsedRef) key() string {
  function extractToTarget (line 215) | func extractToTarget(query string) *parsedRef {
  function extractViewSources (line 225) | func extractViewSources(query string) []parsedRef {
  function splitRef (line 254) | func splitRef(raw string) (string, string) {
  function stripBT (line 264) | func stripBT(s string) string {
  function normaliseWS (line 272) | func normaliseWS(s string) string {
  function isSystemDB (line 277) | func isSystemDB(db string) bool {

FILE: internal/server/middleware/context.go
  type contextKey (line 8) | type contextKey
  constant sessionKey (line 11) | sessionKey contextKey = "session"
  type SessionInfo (line 15) | type SessionInfo struct
  function SetSession (line 24) | func SetSession(ctx context.Context, session *SessionInfo) context.Conte...
  function GetSession (line 29) | func GetSession(r *http.Request) *SessionInfo {

FILE: internal/server/middleware/cors.go
  type CORSConfig (line 10) | type CORSConfig struct
  function CORS (line 17) | func CORS(cfg CORSConfig) func(http.Handler) http.Handler {

FILE: internal/server/middleware/license.go
  function RequirePro (line 11) | func RequirePro(cfg *config.Config) func(http.Handler) http.Handler {

FILE: internal/server/middleware/logging.go
  type responseWriter (line 12) | type responseWriter struct
    method WriteHeader (line 17) | func (rw *responseWriter) WriteHeader(code int) {
    method Hijack (line 23) | func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
    method Flush (line 32) | func (rw *responseWriter) Flush() {
    method Push (line 39) | func (rw *responseWriter) Push(target string, opts *http.PushOptions) ...
  function Logger (line 47) | func Logger(next http.Handler) http.Handler {

FILE: internal/server/middleware/ratelimit.go
  type RateLimiter (line 13) | type RateLimiter struct
    method CheckAuthRateLimit (line 38) | func (rl *RateLimiter) CheckAuthRateLimit(identifier, limitType string...
    method RecordAttempt (line 119) | func (rl *RateLimiter) RecordAttempt(identifier, limitType string) {
    method ResetLimit (line 143) | func (rl *RateLimiter) ResetLimit(identifier string) {
  function NewRateLimiter (line 18) | func NewRateLimiter(db *database.DB) *RateLimiter {
  type RateLimitResult (line 23) | type RateLimitResult struct
  function parseLimitTypeAndLockLevel (line 147) | func parseLimitTypeAndLockLevel(storedType, fallback string) (string, in...
  function formatLimitTypeWithLockLevel (line 169) | func formatLimitTypeWithLockLevel(base string, level int) string {
  function nextLockoutLevel (line 176) | func nextLockoutLevel(current int) int {
  function lockoutDurationForLevel (line 187) | func lockoutDurationForLevel(level int) time.Duration {

FILE: internal/server/middleware/ratelimit_test.go
  function TestProgressiveLockoutSchedule (line 11) | func TestProgressiveLockoutSchedule(t *testing.T) {
  function TestLegacyLongLockIsCappedToCurrentSchedule (line 101) | func TestLegacyLongLockIsCappedToCurrentSchedule(t *testing.T) {

FILE: internal/server/middleware/security.go
  function SecurityHeaders (line 6) | func SecurityHeaders(isProduction bool) func(http.Handler) http.Handler {

FILE: internal/server/middleware/session.go
  function writeJSON (line 13) | func writeJSON(w http.ResponseWriter, status int, v interface{}) {
  function Session (line 20) | func Session(db *database.DB, _ *tunnel.Gateway) func(http.Handler) http...
  function RequireAdmin (line 64) | func RequireAdmin(db *database.DB) func(http.Handler) http.Handler {

FILE: internal/server/server.go
  type Server (line 28) | type Server struct
    method setupRoutes (line 96) | func (s *Server) setupRoutes() {
    method Start (line 252) | func (s *Server) Start() error {
    method Shutdown (line 272) | func (s *Server) Shutdown(ctx context.Context) error {
  function New (line 46) | func New(cfg *config.Config, db *database.DB, frontendFS fs.FS) *Server {
  function loadLangfuseConfigFromDB (line 285) | func loadLangfuseConfigFromDB(db *database.DB, appSecretKey string) (lan...

FILE: internal/tunnel/api.go
  method IsTunnelOnline (line 13) | func (g *Gateway) IsTunnelOnline(connectionID string) bool {
  method GetTunnelStatus (line 19) | func (g *Gateway) GetTunnelStatus(connectionID string) (online bool, las...
  method GetConnectedCount (line 29) | func (g *Gateway) GetConnectedCount() int {
  method ExecuteQuery (line 39) | func (g *Gateway) ExecuteQuery(connectionID, sql, user, password string,...
  method ExecuteQueryWithFormat (line 99) | func (g *Gateway) ExecuteQueryWithFormat(connectionID, sql, user, passwo...
  method ExecuteStreamQuery (line 164) | func (g *Gateway) ExecuteStreamQuery(connectionID, sql, user, password s...
  method CleanupStream (line 205) | func (g *Gateway) CleanupStream(connectionID, requestID string) {
  method TestConnection (line 215) | func (g *Gateway) TestConnection(connectionID, user, password string, ti...

FILE: internal/tunnel/gateway.go
  function splitHostPort (line 54) | func splitHostPort(hostport string) (string, string) {
  function isLoopbackHost (line 69) | func isLoopbackHost(host string) bool {
  type PendingRequest (line 78) | type PendingRequest struct
  type PendingStreamRequest (line 84) | type PendingStreamRequest struct
  type ConnectedTunnel (line 92) | type ConnectedTunnel struct
  type Gateway (line 102) | type Gateway struct
    method Stop (line 120) | func (g *Gateway) Stop() {
    method HandleWebSocket (line 125) | func (g *Gateway) HandleWebSocket(w http.ResponseWriter, r *http.Reque...
    method readLoop (line 138) | func (g *Gateway) readLoop(conn *websocket.Conn) {
    method handleAuth (line 225) | func (g *Gateway) handleAuth(conn *websocket.Conn, token string, takeo...
    method handlePong (line 300) | func (g *Gateway) handlePong(connID string) {
    method touchTunnel (line 308) | func (g *Gateway) touchTunnel(connID string) {
    method handleQueryResult (line 318) | func (g *Gateway) handleQueryResult(connID string, msg *AgentMessage) {
    method handleQueryError (line 351) | func (g *Gateway) handleQueryError(connID string, msg *AgentMessage) {
    method handleTestResult (line 375) | func (g *Gateway) handleTestResult(connID string, msg *AgentMessage) {
    method handleHostInfo (line 412) | func (g *Gateway) handleHostInfo(connID string, msg *AgentMessage) {
    method handleStreamStart (line 427) | func (g *Gateway) handleStreamStart(connID string, msg *AgentMessage) {
    method handleStreamChunk (line 455) | func (g *Gateway) handleStreamChunk(connID string, msg *AgentMessage) {
    method handleStreamEnd (line 479) | func (g *Gateway) handleStreamEnd(connID string, msg *AgentMessage) {
    method handleStreamError (line 513) | func (g *Gateway) handleStreamError(connID string, msg *AgentMessage) {
    method handleDisconnect (line 541) | func (g *Gateway) handleDisconnect(connID string, ws *websocket.Conn) {
    method sendJSON (line 586) | func (g *Gateway) sendJSON(conn *websocket.Conn, msg GatewayMessage) {
    method heartbeatLoop (line 592) | func (g *Gateway) heartbeatLoop() {
    method pingAll (line 606) | func (g *Gateway) pingAll() {
  function NewGateway (line 109) | func NewGateway(db *database.DB) *Gateway {
  function strPtr (line 584) | func strPtr(s string) *string { return &s }

FILE: internal/tunnel/protocol.go
  type AgentMessage (line 6) | type AgentMessage struct
    method GetMessageID (line 26) | func (m *AgentMessage) GetMessageID() string {
    method GetStats (line 34) | func (m *AgentMessage) GetStats() json.RawMessage {
    method IsTestSuccess (line 42) | func (m *AgentMessage) IsTestSuccess() bool {
  type GatewayMessage (line 53) | type GatewayMessage struct
  type QueryResult (line 69) | type QueryResult struct
  type TestResult (line 76) | type TestResult struct
  type StreamDone (line 83) | type StreamDone struct
  type HostInfo (line 89) | type HostInfo struct

FILE: internal/version/version.go
  function Set (line 9) | func Set(v, c, d string) {

FILE: main.go
  function main (line 14) | func main() {

FILE: ui/src/lib/api/alerts.ts
  constant BASE (line 4) | const BASE = '/api/governance/alerts'
  type AlertRuleRoutePayload (line 6) | type AlertRuleRoutePayload = {
  function adminListAlertChannels (line 17) | async function adminListAlertChannels(): Promise<AlertChannel[]> {
  function adminCreateAlertChannel (line 22) | async function adminCreateAlertChannel(payload: {
  function adminUpdateAlertChannel (line 31) | async function adminUpdateAlertChannel(id: string, payload: {
  function adminDeleteAlertChannel (line 40) | async function adminDeleteAlertChannel(id: string): Promise<void> {
  function adminTestAlertChannel (line 44) | async function adminTestAlertChannel(id: string, payload: {
  function adminListAlertRules (line 52) | async function adminListAlertRules(): Promise<AlertRule[]> {
  function adminCreateAlertRule (line 57) | async function adminCreateAlertRule(payload: {
  function adminUpdateAlertRule (line 71) | async function adminUpdateAlertRule(id: string, payload: {
  function adminDeleteAlertRule (line 85) | async function adminDeleteAlertRule(id: string): Promise<void> {
  function adminListAlertEvents (line 89) | async function adminListAlertEvents(params: { limit?: number; eventType?...

FILE: ui/src/lib/api/auth.ts
  type LoginParams (line 4) | interface LoginParams {
  type LoginResponse (line 10) | interface LoginResponse {
  type ConnectionsResponse (line 15) | interface ConnectionsResponse {
  type SessionResponse (line 20) | interface SessionResponse {
  function login (line 26) | function login(params: LoginParams): Promise<LoginResponse> {
  function logout (line 31) | function logout(): Promise<void> {
  function checkSession (line 36) | async function checkSession(): Promise<Session | null> {
  function listConnections (line 47) | async function listConnections(): Promise<Connection[]> {

FILE: ui/src/lib/api/brain.ts
  function listBrainModels (line 12) | async function listBrainModels(): Promise<BrainModelOption[]> {
  function listBrainChats (line 17) | async function listBrainChats(includeArchived = false): Promise<BrainCha...
  function createBrainChat (line 22) | async function createBrainChat(payload: { title?: string; modelId?: stri...
  function updateBrainChat (line 27) | async function updateBrainChat(chatId: string, payload: { title?: string...
  function deleteBrainChat (line 31) | async function deleteBrainChat(chatId: string): Promise<void> {
  function listBrainMessages (line 35) | async function listBrainMessages(chatId: string): Promise<BrainMessage[]> {
  function listBrainArtifacts (line 40) | async function listBrainArtifacts(chatId: string): Promise<BrainArtifact...
  function runBrainQueryArtifact (line 45) | async function runBrainQueryArtifact(chatId: string, payload: { query: s...
  type StreamEvent (line 49) | interface StreamEvent {
  function streamBrainMessage (line 57) | async function streamBrainMessage(
  function adminListBrainProviders (line 104) | async function adminListBrainProviders(): Promise<BrainProviderAdmin[]> {
  function adminCreateBrainProvider (line 109) | async function adminCreateBrainProvider(payload: {
  function adminUpdateBrainProvider (line 120) | async function adminUpdateBrainProvider(id: string, payload: {
  function adminDeleteBrainProvider (line 131) | async function adminDeleteBrainProvider(id: string): Promise<void> {
  function adminSyncBrainProviderModels (line 135) | async function adminSyncBrainProviderModels(id: string): Promise<void> {
  function adminListBrainModels (line 139) | async function adminListBrainModels(): Promise<BrainModelOption[]> {
  function adminUpdateBrainModel (line 144) | async function adminUpdateBrainModel(id: string, payload: {
  function adminBulkUpdateBrainModels (line 152) | async function adminBulkUpdateBrainModels(payload: {
  function adminListBrainSkills (line 159) | async function adminListBrainSkills(): Promise<BrainSkill[]> {
  function adminCreateBrainSkill (line 164) | async function adminCreateBrainSkill(payload: {
  function adminUpdateBrainSkill (line 173) | async function adminUpdateBrainSkill(id: string, payload: {

FILE: ui/src/lib/api/client.ts
  function parseResponseBody (line 5) | async function parseResponseBody(res: Response): Promise<any> {
  function buildErrorMessage (line 21) | function buildErrorMessage(status: number, body: any): string {
  function request (line 35) | async function request<T = unknown>(
  function apiGet (line 71) | function apiGet<T = unknown>(url: string): Promise<T> {
  function apiPost (line 75) | function apiPost<T = unknown>(url: string, data?: unknown): Promise<T> {
  function apiPostForm (line 82) | function apiPostForm<T = unknown>(url: string, data: FormData): Promise<...
  function apiPut (line 89) | function apiPut<T = unknown>(url: string, data?: unknown): Promise<T> {
  function apiDel (line 96) | function apiDel<T = unknown>(url: string): Promise<T> {

FILE: ui/src/lib/api/governance.ts
  constant BASE (line 26) | const BASE = '/api/governance'
  function fetchOverview (line 30) | function fetchOverview() {
  function triggerSync (line 35) | function triggerSync() {
  function triggerSingleSync (line 39) | function triggerSingleSync(type: 'metadata' | 'query_log' | 'access') {
  function fetchSyncStatus (line 43) | function fetchSyncStatus() {
  function fetchGovernanceSettings (line 49) | function fetchGovernanceSettings() {
  function updateGovernanceSettings (line 53) | function updateGovernanceSettings(payload: {
  function fetchDatabases (line 62) | function fetchDatabases() {
  function fetchTables (line 66) | function fetchTables(params?: { database?: string; tag?: string; search?...
  function fetchTableDetail (line 75) | function fetchTableDetail(database: string, table: string) {
  function fetchTableNotes (line 91) | function fetchTableNotes(database: string, table: string) {
  function fetchColumnNotes (line 95) | function fetchColumnNotes(database: string, table: string, column: strin...
  function createTableNote (line 99) | function createTableNote(database: string, table: string, commentText: s...
  function createColumnNote (line 103) | function createColumnNote(database: string, table: string, column: strin...
  function deleteObjectNote (line 107) | function deleteObjectNote(id: string) {
  function fetchSchemaChanges (line 111) | function fetchSchemaChanges(limit = 50) {
  function fetchQueryLog (line 117) | function fetchQueryLog(params?: { user?: string; table?: string; limit?:...
  function fetchTopQueries (line 127) | function fetchTopQueries(limit = 20) {
  function fetchLineage (line 144) | function fetchLineage(database: string, table: string) {
  function fetchLineageGraph (line 149) | function fetchLineageGraph(includeColumns = false) {
  function fetchViewGraph (line 155) | function fetchViewGraph() {
  function fetchLineageWithColumns (line 160) | function fetchLineageWithColumns(database: string, table: string) {
  function fetchQueryByQueryID (line 166) | function fetchQueryByQueryID(queryId: string) {
  function fetchTags (line 172) | function fetchTags(params?: { database?: string; table?: string }) {
  function createTag (line 180) | function createTag(data: { object_type: string; database_name: string; t...
  function deleteTag (line 184) | function deleteTag(id: string) {
  function fetchAccessUsers (line 190) | function fetchAccessUsers() {
  function fetchAccessRoles (line 194) | function fetchAccessRoles() {
  function fetchAccessMatrix (line 198) | function fetchAccessMatrix(user?: string) {
  function fetchOverPermissions (line 203) | function fetchOverPermissions(days = 30) {
  function fetchPolicies (line 209) | function fetchPolicies() {
  function createPolicy (line 213) | function createPolicy(data: Partial<Policy>) {
  function getPolicy (line 217) | function getPolicy(id: string) {
  function updatePolicy (line 221) | function updatePolicy(id: string, data: Partial<Policy>) {
  function deletePolicy (line 225) | function deletePolicy(id: string) {
  function fetchViolations (line 231) | function fetchViolations(params?: { policy_id?: string; limit?: number }) {
  function promoteViolationToIncident (line 239) | function promoteViolationToIncident(id: string) {
  function fetchIncidents (line 243) | function fetchIncidents(params?: { status?: string; severity?: string; l...
  function getIncident (line 252) | function getIncident(id: string) {
  function createIncident (line 256) | function createIncident(data: {
  function updateIncident (line 268) | function updateIncident(id: string, data: {
  function fetchIncidentComments (line 279) | function fetchIncidentComments(id: string) {
  function createIncidentComment (line 283) | function createIncidentComment(id: string, commentText: string) {

FILE: ui/src/lib/api/models.ts
  constant BASE (line 4) | const BASE = '/api/models'
  function listModels (line 6) | function listModels() {
  function createModel (line 10) | function createModel(data: {
  function getModel (line 22) | function getModel(id: string) {
  function updateModel (line 26) | function updateModel(id: string, data: Partial<Omit<Model, 'id' | 'conne...
  function deleteModel (line 30) | function deleteModel(id: string) {
  function getDAG (line 34) | function getDAG() {
  function validateModels (line 38) | function validateModels() {
  function runAllModels (line 42) | function runAllModels() {
  function runSingleModel (line 46) | function runSingleModel(id: string) {
  function listModelRuns (line 50) | function listModelRuns(limit = 20, offset = 0) {
  function getModelRun (line 54) | function getModelRun(runId: string) {
  function listPipelines (line 58) | function listPipelines() {
  function runPipeline (line 62) | function runPipeline(anchorId: string) {
  function getPipelineSchedule (line 66) | function getPipelineSchedule(anchorId: string) {
  function upsertPipelineSchedule (line 70) | function upsertPipelineSchedule(anchorId: string, data: { cron: string; ...
  function deletePipelineSchedule (line 74) | function deletePipelineSchedule(anchorId: string) {

FILE: ui/src/lib/api/pipelines.ts
  constant BASE (line 4) | const BASE = '/api/pipelines'
  function listPipelines (line 6) | function listPipelines() {
  function createPipeline (line 10) | function createPipeline(data: { name: string; description?: string; conn...
  function getPipeline (line 14) | function getPipeline(id: string) {
  function updatePipeline (line 18) | function updatePipeline(id: string, data: { name: string; description?: ...
  function deletePipeline (line 22) | function deletePipeline(id: string) {
  function saveGraph (line 26) | function saveGraph(id: string, graph: {
  function startPipeline (line 34) | function startPipeline(id: string) {
  function stopPipeline (line 38) | function stopPipeline(id: string) {
  function getPipelineStatus (line 42) | function getPipelineStatus(id: string) {
  function listRuns (line 54) | function listRuns(id: string, limit = 20, offset = 0) {
  function getRunLogs (line 58) | function getRunLogs(id: string, runId: string, limit = 200) {

FILE: ui/src/lib/api/query.ts
  type RunQueryParams (line 12) | interface RunQueryParams {
  function escapeLiteral (line 17) | function escapeLiteral(value: string): string {
  function escapeIdentifier (line 23) | function escapeIdentifier(value: string): string {
  function runQuery (line 29) | function runQuery(params: RunQueryParams): Promise<LegacyQueryResult> {
  function formatSQL (line 34) | async function formatSQL(query: string): Promise<string> {
  function explainQuery (line 40) | function explainQuery(query: string): Promise<LegacyQueryResult> {
  function fetchQueryPlan (line 45) | function fetchQueryPlan(query: string): Promise<QueryPlanResult> {
  function fetchQueryProfile (line 50) | function fetchQueryProfile(query: string): Promise<QueryProfileResult> {
  function estimateQuery (line 55) | function estimateQuery(query: string): Promise<QueryEstimateResult> {
  function runSampleQuery (line 60) | function runSampleQuery(params: {
  function fetchExplorerData (line 75) | function fetchExplorerData(params: {
  function listDatabases (line 94) | async function listDatabases(): Promise<string[]> {
  function fetchCompletions (line 100) | async function fetchCompletions(): Promise<{ functions: string[]; keywor...
  function listTables (line 106) | async function listTables(database: string): Promise<string[]> {
  function listColumns (line 112) | async function listColumns(database: string, table: string): Promise<Col...
  function fetchTableInfo (line 120) | async function fetchTableInfo(database: string, table: string): Promise<...
  function fetchTableSchema (line 138) | async function fetchTableSchema(database: string, table: string): Promis...
  function fetchDatabaseInfo (line 143) | async function fetchDatabaseInfo(database: string): Promise<Record<strin...
  function fetchDatabaseTables (line 160) | async function fetchDatabaseTables(database: string): Promise<LegacyQuer...

FILE: ui/src/lib/api/stream.ts
  function executeStreamQuery (line 6) | async function executeStreamQuery(

FILE: ui/src/lib/basePath.ts
  type Window (line 10) | interface Window {
  function getBasePath (line 16) | function getBasePath(): string {
  function withBase (line 25) | function withBase(path: string): string {
  function stripBase (line 31) | function stripBase(path: string): string {

FILE: ui/src/lib/components/brain/brain-markdown.ts
  type MessageSegment (line 3) | interface MessageSegment {
  constant SQL_FENCE_RE (line 9) | const SQL_FENCE_RE = /```sql\n([\s\S]*?)```/g
  function renderMarkdown (line 17) | function renderMarkdown(content: string): string {
  function parseMessageSegments (line 26) | function parseMessageSegments(content: string): MessageSegment[] {
  function extractSqlBlocks (line 52) | function extractSqlBlocks(content: string): string[] {
  constant SQL_KEYWORDS (line 60) | const SQL_KEYWORDS = new Set([
  function highlightSQL (line 73) | function highlightSQL(sql: string): string {
  function escapeHtml (line 90) | function escapeHtml(s: string): string {

FILE: ui/src/lib/editor/completions.ts
  function ensureModelsLoaded (line 26) | async function ensureModelsLoaded(): Promise<void> {
  function refreshModelCache (line 48) | function refreshModelCache(): void {
  function detectRefContext (line 52) | function detectRefContext(doc: string, pos: number): { inside: boolean; ...
  function buildModelCompletions (line 59) | function buildModelCompletions(): Completion[] {
  type SqlContext (line 68) | type SqlContext = 'table' | 'column' | 'dot' | 'function' | 'default'
  type TableRef (line 70) | interface TableRef {
  function normalizeIdent (line 75) | function normalizeIdent(id: string): string {
  function parseTableRef (line 79) | function parseTableRef(ref: string): TableRef | null {
  function tableKey (line 96) | function tableKey(db: string, table: string): string {
  function ensureFunctionKeywordCache (line 100) | async function ensureFunctionKeywordCache(): Promise<void> {
  function ensureDatabasesLoaded (line 120) | async function ensureDatabasesLoaded(): Promise<void> {
  function ensureTablesCached (line 130) | async function ensureTablesCached(dbName: string): Promise<void> {
  function ensureColumnsCached (line 160) | async function ensureColumnsCached(dbName: string, tableName: string): P...
  function detectContext (line 192) | function detectContext(doc: string, pos: number): SqlContext {
  function buildAliasMap (line 208) | function buildAliasMap(doc: string): Map<string, string> {
  function buildReferencedTables (line 222) | function buildReferencedTables(doc: string): string[] {
  function knownDatabases (line 233) | function knownDatabases(): string[] {
  function findTablesForDatabase (line 238) | function findTablesForDatabase(dbName: string): string[] {
  function findColumns (line 244) | function findColumns(dbName: string, tableName: string): Column[] {
  function fuzzyScore (line 252) | function fuzzyScore(text: string, term: string): number {
  function rankCompletions (line 274) | function rankCompletions(items: Completion[], term: string): Completion[] {
  function dedupeCompletions (line 287) | function dedupeCompletions(items: Completion[]): Completion[] {
  function resolveUnqualifiedTableRefs (line 299) | async function resolveUnqualifiedTableRefs(tableName: string): Promise<T...
  function buildDotCompletions (line 310) | async function buildDotCompletions(doc: string, beforeCursor: string): P...
  function buildDatabaseCompletions (line 371) | function buildDatabaseCompletions(): Completion[] {
  function buildTableCompletions (line 379) | async function buildTableCompletions(term: string): Promise<Completion[]> {
  function buildReferencedColumnCompletions (line 421) | async function buildReferencedColumnCompletions(doc: string): Promise<Co...
  function buildFunctionCompletions (line 470) | function buildFunctionCompletions(): Completion[] {
  function buildKeywordCompletions (line 479) | function buildKeywordCompletions(): Completion[] {
  function buildSnippetCompletions (line 487) | function buildSnippetCompletions(): Completion[] {
  function clickhouseCompletionSource (line 542) | async function clickhouseCompletionSource(

FILE: ui/src/lib/stores/command-palette.svelte.ts
  function isCommandPaletteOpen (line 3) | function isCommandPaletteOpen(): boolean {
  function openCommandPalette (line 7) | function openCommandPalette(): void {
  function closeCommandPalette (line 11) | function closeCommandPalette(): void {
  function toggleCommandPalette (line 15) | function toggleCommandPalette(): void {

FILE: ui/src/lib/stores/license.svelte.ts
  function getLicense (line 8) | function getLicense(): LicenseInfo | null {
  function isLicenseLoading (line 12) | function isLicenseLoading(): boolean {
  function isProActive (line 16) | function isProActive(): boolean {
  function loadLicense (line 20) | async function loadLicense(force = false): Promise<void> {

FILE: ui/src/lib/stores/number-format.svelte.ts
  constant STORAGE_KEY (line 1) | const STORAGE_KEY = 'ch-ui-format-numbers'
  function getFormatNumbers (line 6) | function getFormatNumbers(): boolean {
  function toggleFormatNumbers (line 10) | function toggleFormatNumbers(): void {

FILE: ui/src/lib/stores/query-limit.svelte.ts
  constant STORAGE_KEY (line 1) | const STORAGE_KEY = 'ch-ui-max-result-rows'
  constant DEFAULT_LIMIT (line 2) | const DEFAULT_LIMIT = 1000
  function getMaxResultRows (line 7) | function getMaxResultRows(): number {
  function setMaxResultRows (line 11) | function setMaxResultRows(value: number): void {

FILE: ui/src/lib/stores/router.svelte.ts
  constant TAB_PATHS (line 7) | const TAB_PATHS: Record<string, string> = {
  constant PATH_TABS (line 21) | const PATH_TABS: Record<string, { type: SingletonTab['type']; label: str...
  function getCurrentPipelineId (line 41) | function getCurrentPipelineId(): string | undefined {
  function buildUrl (line 47) | function buildUrl(path: string, tabId?: string): string {
  function currentTabParam (line 53) | function currentTabParam(): string | null {
  function pushUrl (line 57) | function pushUrl(path: string, tabId?: string): void {
  function pushTabRoute (line 70) | function pushTabRoute(tabType: string): void {
  function pushTabRouteForTab (line 77) | function pushTabRouteForTab(tab: { id: string; type: string; dashboardId...
  function pushDashboardDetail (line 87) | function pushDashboardDetail(id: string): void {
  function pushDashboardList (line 93) | function pushDashboardList(): void {
  function pushPipelineDetail (line 99) | function pushPipelineDetail(id: string): void {
  function pushPipelineList (line 106) | function pushPipelineList(): void {
  function parseRoute (line 115) | function parseRoute(): { type: string; dashboardId?: string; pipelineId?...
  function tryRestoreFromTabParam (line 142) | function tryRestoreFromTabParam(): boolean {
  function updateSubRouteState (line 153) | function updateSubRouteState(): void {
  function syncRouteToTabs (line 160) | function syncRouteToTabs(): void {
  function initRouter (line 194) | function initRouter(): void {

FILE: ui/src/lib/stores/schema.svelte.ts
  function getDatabases (line 7) | function getDatabases(): Database[] {
  function isSchemaLoading (line 11) | function isSchemaLoading(): boolean {
  function loadDatabases (line 15) | async function loadDatabases(): Promise<void> {
  function loadTables (line 27) | async function loadTables(dbName: string): Promise<void> {
  function loadColumns (line 48) | async function loadColumns(dbName: string, tableName: string): Promise<v...
  function toggleDatabase (line 87) | function toggleDatabase(dbName: string): void {
  function toggleTable (line 97) | function toggleTable(dbName: string, tableName: string): void {

FILE: ui/src/lib/stores/session.svelte.ts
  function getSession (line 8) | function getSession(): Session | null {
  function isLoading (line 12) | function isLoading(): boolean {
  function getError (line 16) | function getError(): string | null {
  function isAuthenticated (line 20) | function isAuthenticated(): boolean {
  function initSession (line 25) | async function initSession(): Promise<void> {
  function login (line 38) | async function login(connectionId: string, username: string, password: s...
  function logout (line 53) | async function logout(): Promise<void> {

FILE: ui/src/lib/stores/tabs.svelte.ts
  type TabType (line 8) | type TabType = 'home' | 'query' | 'table' | 'database' | 'dashboard' | '...
  type TabBase (line 10) | interface TabBase {
  type QueryTab (line 16) | interface QueryTab extends TabBase {
  type TableTab (line 24) | interface TableTab extends TabBase {
  type DatabaseTab (line 30) | interface DatabaseTab extends TabBase {
  type DashboardTab (line 35) | interface DashboardTab extends TabBase {
  type ModelTab (line 40) | interface ModelTab extends TabBase {
  type HomeTab (line 50) | interface HomeTab extends TabBase {
  type SingletonTab (line 54) | interface SingletonTab extends TabBase {
  type Tab (line 58) | type Tab = HomeTab | QueryTab | TableTab | DatabaseTab | DashboardTab | ...
  type TabGroup (line 62) | interface TabGroup {
  type TabResult (line 70) | interface TabResult {
  constant STORAGE_KEY (line 81) | const STORAGE_KEY = 'ch-ui-tabs'
  constant HOME_TAB_ID (line 82) | const HOME_TAB_ID = 'home'
  constant HOME_TAB_NAME (line 83) | const HOME_TAB_NAME = 'Home'
  type StorageFormat (line 85) | interface StorageFormat {
  function saveTabs (line 91) | function saveTabs(): void {
  function loadTabs (line 103) | function loadTabs(): StorageFormat {
  type CreateQueryTabOptions (line 151) | interface CreateQueryTabOptions {
  function createQueryTab (line 157) | function createQueryTab(sql = '', options: CreateQueryTabOptions = {}): ...
  function createHomeTab (line 171) | function createHomeTab(): HomeTab {
  function normalizeTabsState (line 179) | function normalizeTabsState(state: StorageFormat): StorageFormat {
  function queueSave (line 268) | function queueSave(): void {
  function findGroupForTab (line 279) | function findGroupForTab(tabId: string): string | undefined {
  function isHomeTabId (line 283) | function isHomeTabId(tabId: string): boolean {
  function resolveTargetGroupId (line 288) | function resolveTargetGroupId(targetGroupId?: string): string {
  function getTabs (line 296) | function getTabs(): Tab[] {
  function getActiveTabId (line 301) | function getActiveTabId(): string {
  function getActiveTab (line 307) | function getActiveTab(): Tab | undefined {
  function getGroups (line 313) | function getGroups(): TabGroup[] {
  function getFocusedGroupId (line 317) | function getFocusedGroupId(): string {
  function isSplit (line 321) | function isSplit(): boolean {
  function getGroupTabs (line 325) | function getGroupTabs(groupId: string): Tab[] {
  function getGroupActiveTab (line 331) | function getGroupActiveTab(groupId: string): Tab | undefined {
  function getGroupActiveTabId (line 337) | function getGroupActiveTabId(groupId: string): string {
  function getTabResult (line 344) | function getTabResult(tabId: string): TabResult | undefined {
  function setTabResult (line 348) | function setTabResult(tabId: string, partial: Partial<TabResult>): void {
  function clearTabResult (line 362) | function clearTabResult(tabId: string): void {
  function setActiveTab (line 370) | function setActiveTab(id: string, groupId?: string): void {
  function setFocusedGroup (line 382) | function setFocusedGroup(groupId: string): void {
  function openHomeTab (line 388) | function openHomeTab(): void {
  function openQueryTab (line 412) | function openQueryTab(sql = '', targetGroupId?: string): void {
  type SavedQueryTabInput (line 423) | interface SavedQueryTabInput {
  function openSavedQueryTab (line 429) | function openSavedQueryTab(savedQuery: SavedQueryTabInput, targetGroupId...
  function openTableTab (line 463) | function openTableTab(database: string, table: string, targetGroupId?: s...
  function openDatabaseTab (line 487) | function openDatabaseTab(database: string, targetGroupId?: string): void {
  function openDashboardTab (line 512) | function openDashboardTab(dashboardId: string, name = 'Dashboard', targe...
  function openSingletonTab (line 538) | function openSingletonTab(type: SingletonTab['type'], name: string, targ...
  function closeTab (line 565) | function closeTab(id: string): void {
  function updateTabSQL (line 617) | function updateTabSQL(id: string, sql: string): void {
  function renameTab (line 626) | function renameTab(id: string, name: string): void {
  function markQueryTabSaved (line 632) | function markQueryTabSaved(id: string, options: { savedQueryId?: string;...
  function isTabDirty (line 648) | function isTabDirty(id: string): boolean {
  function reorderTab (line 658) | function reorderTab(groupId: string, fromIndex: number, toIndex: number)...
  function splitTabToSide (line 680) | function splitTabToSide(tabId: string, side: 'left' | 'right'): void {
  function splitTab (line 719) | function splitTab(tabId: string): void {
  function moveTabToGroup (line 732) | function moveTabToGroup(tabId: string, targetGroupId: string): void {
  function unsplit (line 779) | function unsplit(): void {
  function modelEditEqual (line 792) | function modelEditEqual(a: ModelEditState, b: ModelEditState): boolean {
  type ModelTabInput (line 802) | interface ModelTabInput {
  function openModelTab (line 815) | function openModelTab(model: ModelTabInput, targetGroupId?: string): void {
  function updateModelTabEdit (line 854) | function updateModelTabEdit(tabId: string, partial: Partial<ModelEditSta...
  function markModelTabSaved (line 866) | function markModelTabSaved(tabId: string, model: { name: string; status:...
  function updateModelTabStatus (line 875) | function updateModelTabStatus(tabId: string, status: string, lastError: ...

FILE: ui/src/lib/stores/theme.svelte.ts
  type Theme (line 1) | type Theme = 'dark' | 'light'
  function getTheme (line 8) | function getTheme(): Theme {
  function toggleTheme (line 12) | function toggleTheme(): void {
  function applyTheme (line 18) | function applyTheme(t: Theme): void {

FILE: ui/src/lib/stores/toast.svelte.ts
  type ToastType (line 4) | type ToastType = 'info' | 'success' | 'error' | 'warning'
  constant DEFAULT_DURATION (line 6) | const DEFAULT_DURATION: Record<ToastType, number> = {
  function normalizeMessage (line 13) | function normalizeMessage(message: string): string {
  function resolveToastOptions (line 17) | function resolveToastOptions(
  function addToast (line 30) | function addToast(message: string, type: ToastType = 'info', duration?: ...
  function removeToast (line 48) | function removeToast(id: number | string): void {
  function dismiss (line 52) | function dismiss(id?: number | string): void {
  function getToasts (line 56) | function getToasts() {
  function success (line 60) | function success(message: string, optionsOrDuration?: number | ExternalT...
  function error (line 66) | function error(message: string, optionsOrDuration?: number | ExternalToa...
  function warning (line 72) | function warning(message: string, optionsOrDuration?: number | ExternalT...
  function info (line 78) | function info(message: string, optionsOrDuration?: number | ExternalToas...

FILE: ui/src/lib/types/alerts.ts
  type AlertChannelType (line 1) | type AlertChannelType = 'smtp' | 'resend' | 'brevo'
  type AlertSeverity (line 2) | type AlertSeverity = 'info' | 'warn' | 'error' | 'critical'
  type AlertEventType (line 3) | type AlertEventType = 'policy.violation' | 'schedule.failed' | 'schedule...
  type AlertChannel (line 5) | interface AlertChannel {
  type AlertRuleRoute (line 17) | interface AlertRuleRoute {
  type AlertRule (line 36) | interface AlertRule {
  type AlertEvent (line 52) | interface AlertEvent {

FILE: ui/src/lib/types/api.ts
  type ApiResponse (line 2) | interface ApiResponse<T = unknown> {
  type Session (line 9) | interface Session {
  type Connection (line 21) | interface Connection {
  type HostInfo (line 31) | interface HostInfo {
  type LicenseInfo (line 46) | interface LicenseInfo {
  type SavedQuery (line 55) | interface SavedQuery {
  type Dashboard (line 65) | interface Dashboard {
  type Panel (line 75) | interface Panel {
  type Schedule (line 92) | interface Schedule {
  type ScheduleRun (line 111) | interface ScheduleRun {
  type PanelConfig (line 123) | interface PanelConfig {
  type AuditLog (line 132) | interface AuditLog {
  type AdminStats (line 143) | interface AdminStats {

FILE: ui/src/lib/types/brain.ts
  type SchemaContextEntry (line 1) | interface SchemaContextEntry {
  type BrainChat (line 7) | interface BrainChat {
  type BrainMessage (line 23) | interface BrainMessage {
  type BrainArtifact (line 34) | interface BrainArtifact {
  type BrainModelOption (line 45) | interface BrainModelOption {
  type BrainProviderAdmin (line 58) | interface BrainProviderAdmin {
  type BrainSkill (line 71) | interface BrainSkill {

FILE: ui/src/lib/types/governance.ts
  type GovernanceSettings (line 3) | interface GovernanceSettings {
  type SyncState (line 11) | interface SyncState {
  type SyncResult (line 24) | interface SyncResult {
  type GovernanceOverview (line 35) | interface GovernanceOverview {
  type GovDatabase (line 55) | interface GovDatabase {
  type GovTable (line 65) | interface GovTable {
  type GovColumn (line 81) | interface GovColumn {
  type SchemaChange (line 98) | interface SchemaChange {
  type QueryLogEntry (line 113) | interface QueryLogEntry {
  type TopQuery (line 134) | interface TopQuery {
  type ColumnLineageEdge (line 145) | interface ColumnLineageEdge {
  type LineageEdge (line 150) | interface LineageEdge {
  type LineageNode (line 163) | interface LineageNode {
  type LineageGraph (line 171) | interface LineageGraph {
  type TagEntry (line 178) | interface TagEntry {
  type ChUser (line 192) | interface ChUser {
  type ChRole (line 202) | interface ChRole {
  type AccessMatrixEntry (line 209) | interface AccessMatrixEntry {
  type OverPermission (line 220) | interface OverPermission {
  type Policy (line 233) | interface Policy {
  type PolicyViolation (line 251) | interface PolicyViolation {
  type GovernanceObjectComment (line 266) | interface GovernanceObjectComment {
  type GovernanceIncident (line 279) | interface GovernanceIncident {
  type GovernanceIncidentComment (line 300) | interface GovernanceIncidentComment {

FILE: ui/src/lib/types/models.ts
  type Materialization (line 1) | type Materialization = 'view' | 'table'
  type ModelStatus (line 2) | type ModelStatus = 'draft' | 'success' | 'error'
  type RunStatus (line 3) | type RunStatus = 'running' | 'success' | 'partial' | 'error'
  type ResultStatus (line 4) | type ResultStatus = 'pending' | 'running' | 'success' | 'error' | 'skipped'
  type Model (line 6) | interface Model {
  type ModelRun (line 24) | interface ModelRun {
  type ModelRunResult (line 38) | interface ModelRunResult {
  type DAGNode (line 52) | interface DAGNode {
  type DAGEdge (line 63) | interface DAGEdge {
  type ModelDAG (line 69) | interface ModelDAG {
  type ValidationError (line 74) | interface ValidationError {
  type ValidationResult (line 80) | interface ValidationResult {
  type ModelSchedule (line 85) | interface ModelSchedule {
  type Pipeline (line 100) | interface Pipeline {
  type ModelEditState (line 106) | interface ModelEditState {

FILE: ui/src/lib/types/pipelines.ts
  type PipelineStatus (line 1) | type PipelineStatus = 'draft' | 'stopped' | 'starting' | 'running' | 'er...
  type NodeType (line 3) | type NodeType =
  type Pipeline (line 10) | interface Pipeline {
  type PipelineNode (line 25) | interface PipelineNode {
  type PipelineEdge (line 37) | interface PipelineEdge {
  type PipelineGraph (line 47) | interface PipelineGraph {
  type PipelineRun (line 52) | interface PipelineRun {
  type PipelineRunLog (line 66) | interface PipelineRunLog {
  type ConnectorFieldDef (line 74) | interface ConnectorFieldDef {
  constant SOURCE_NODE_TYPES (line 85) | const SOURCE_NODE_TYPES: { type: NodeType; label: string; description: s...
  constant SINK_NODE_TYPES (line 92) | const SINK_NODE_TYPES: { type: NodeType; label: string; description: str...
  constant CONNECTOR_FIELDS (line 96) | const CONNECTOR_FIELDS: Record<NodeType, ConnectorFieldDef[]> = {

FILE: ui/src/lib/types/query.ts
  type ColumnMeta (line 2) | interface ColumnMeta {
  type CompactResult (line 8) | interface CompactResult {
  type QueryStats (line 16) | interface QueryStats {
  type ExplorerDataResponse (line 23) | interface ExplorerDataResponse {
  type LegacyQueryResult (line 34) | interface LegacyQueryResult {
  type SampleQueryResult (line 43) | interface SampleQueryResult extends LegacyQueryResult {
  type QueryPlanNode (line 48) | interface QueryPlanNode {
  type QueryPlanResult (line 55) | interface QueryPlanResult {
  type QueryProfileResult (line 62) | interface QueryProfileResult {
  type TableEstimate (line 70) | interface TableEstimate {
  type QueryEstimateResult (line 79) | interface QueryEstimateResult {
  type StreamMessage (line 89) | type StreamMessage =

FILE: ui/src/lib/types/schema.ts
  type Database (line 2) | interface Database {
  type Table (line 10) | interface Table {
  type Column (line 19) | interface Column {

FILE: ui/src/lib/utils/calendar.ts
  function daysInMonth (line 2) | function daysInMonth(year: number, month: number): number {
  function firstDayOfWeek (line 7) | function firstDayOfWeek(year: number, month: number): number {
  function buildMonthGrid (line 15) | function buildMonthGrid(year: number, month: number): (Date | null)[][] {
  function shiftMonth (line 40) | function shiftMonth(year: number, month: number, delta: number): { year:...
  function isSameDay (line 46) | function isSameDay(a: Date, b: Date): boolean {
  function isInRange (line 55) | function isInRange(date: Date, from: Date, to: Date): boolean {
  function isToday (line 63) | function isToday(date: Date): boolean {
  function monthName (line 68) | function monthName(month: number): string {

FILE: ui/src/lib/utils/ch-types.ts
  type DisplayType (line 2) | type DisplayType = 'number' | 'string' | 'date' | 'bool' | 'json' | 'nul...
  function getDisplayType (line 4) | function getDisplayType(chType: string): DisplayType {
  function isRightAligned (line 17) | function isRightAligned(chType: string): boolean {

FILE: ui/src/lib/utils/chart-transform.ts
  type ColumnMeta (line 4) | interface ColumnMeta {
  constant DEFAULT_COLORS (line 9) | const DEFAULT_COLORS = [
  constant TIME_RANGES (line 20) | const TIME_RANGES = [
  type ExtendedPreset (line 30) | interface ExtendedPreset {
  constant EXTENDED_PRESETS (line 36) | const EXTENDED_PRESETS: ExtendedPreset[] = [
  constant DATE_TYPES (line 57) | const DATE_TYPES = new Set([
  constant NUMERIC_TYPES (line 62) | const NUMERIC_TYPES = new Set([
  function isDateType (line 68) | function isDateType(chType: string): boolean {
  function isNumericType (line 73) | function isNumericType(chType: string): boolean {
  function isCategoricalX (line 80) | function isCategoricalX(meta: ColumnMeta[], xColumn: string): boolean {
  function toUPlotData (line 90) | function toUPlotData(
  function getStatValue (line 127) | function getStatValue(data: Record<string, unknown>[], meta: ColumnMeta[...

FILE: ui/src/lib/utils/dashboard-time.ts
  type DashboardTimeRangePayload (line 1) | interface DashboardTimeRangePayload {
  function encodeAbsoluteDashboardRange (line 9) | function encodeAbsoluteDashboardRange(fromISO: string, toISO: string): s...
  function decodeAbsoluteDashboardRange (line 13) | function decodeAbsoluteDashboardRange(value: string): { from: string; to...
  function normalizeRelative (line 25) | function normalizeRelative(value: string, fallback: string): string {
  function isAbsoluteToken (line 62) | function isAbsoluteToken(value: string): boolean {
  constant PRESET_LABELS (line 70) | const PRESET_LABELS: Record<string, string> = {
  constant DURATION_LABELS (line 81) | const DURATION_LABELS: Record<string, string> = {
  function resolveNamedPreset (line 94) | function resolveNamedPreset(name: string): { from: string; to: string } ...
  function formatDashboardTimeRangeLabel (line 146) | function formatDashboardTimeRangeLabel(value: string): string {
  function toDashboardTimeRangePayload (line 162) | function toDashboardTimeRangePayload(value: string): DashboardTimeRangeP...

FILE: ui/src/lib/utils/export.ts
  function normalizeScalar (line 3) | function normalizeScalar(val: unknown): string {
  function escapeDelimited (line 9) | function escapeDelimited(val: unknown, delimiter: ',' | '\t'): string {
  function rowsToObjects (line 22) | function rowsToObjects(meta: ColumnMeta[], data: unknown[][]): Record<st...
  function generateCSV (line 32) | function generateCSV(meta: ColumnMeta[], data: unknown[][]): string {
  function generateTSV (line 39) | function generateTSV(meta: ColumnMeta[], data: unknown[][]): string {
  function generateJSONLines (line 46) | function generateJSONLines(meta: ColumnMeta[], data: unknown[][]): string {
  function generateJSON (line 51) | function generateJSON(meta: ColumnMeta[], data: unknown[][]): string {
  function generateJSONCompact (line 56) | function generateJSONCompact(meta: ColumnMeta[], data: unknown[][]): str...
  function generateMarkdown (line 65) | function generateMarkdown(meta: ColumnMeta[], data: unknown[][]): string {
  function escapeSQLString (line 77) | function escapeSQLString(value: string): string {
  function generateSQLInsert (line 82) | function generateSQLInsert(meta: ColumnMeta[], data: unknown[][], table ...
  function generateXML (line 101) | function generateXML(meta: ColumnMeta[], data: unknown[][]): string {
  function copyToClipboard (line 128) | async function copyToClipboard(text: string): Promise<void> {
  function downloadFile (line 133) | function downloadFile(content: string, filename: string, mimeType: strin...

FILE: ui/src/lib/utils/format.ts
  function formatNumber (line 2) | function formatNumber(n: number): string {
  function formatBytes (line 7) | function formatBytes(bytes: number): string {
  function formatElapsed (line 16) | function formatElapsed(seconds: number): string {
  function formatDuration (line 26) | function formatDuration(ms: number): string {

FILE: ui/src/lib/utils/grid-layout.ts
  constant COLS (line 3) | const COLS = 12
  constant ROW_H (line 4) | const ROW_H = 60
  constant GAP (line 5) | const GAP = 16
  constant MIN_W (line 6) | const MIN_W = 2
  constant MIN_H (line 7) | const MIN_H = 2
  type LayoutItem (line 9) | interface LayoutItem {
  function gridToPixel (line 18) | function gridToPixel(
  function calcColW (line 31) | function calcColW(containerWidth: number): number {
  function rectsOverlap (line 36) | function rectsOverlap(a: LayoutItem, b: LayoutItem): boolean {
  function compact (line 52) | function compact(items: LayoutItem[], movedId?: string): LayoutItem[] {
  function containerHeight (line 103) | function containerHeight(items: LayoutItem[]): number {

FILE: ui/src/lib/utils/lineage-layout.ts
  constant LAYER_GAP (line 4) | const LAYER_GAP = 300
  constant NODE_GAP (line 5) | const NODE_GAP = 140
  constant NODE_WIDTH (line 6) | const NODE_WIDTH = 220
  type LineageFlowNode (line 8) | interface LineageFlowNode extends Node {
  function layoutLineageGraph (line 22) | function layoutLineageGraph(

FILE: ui/src/lib/utils/safe-json.ts
  function getFallbackParser (line 37) | function getFallbackParser() {
  function safeParse (line 51) | function safeParse(text: string): any {

FILE: ui/src/lib/utils/sql.ts
  constant WRITE_PATTERN (line 1) | const WRITE_PATTERN = /^\s*(INSERT|CREATE|DROP|ALTER|TRUNCATE|RENAME|ATT...
  function isWriteQuery (line 4) | function isWriteQuery(query: string): boolean {

FILE: ui/src/lib/utils/stats.ts
  type ColumnStats (line 4) | interface ColumnStats {
  constant DISTINCT_SAMPLE (line 26) | const DISTINCT_SAMPLE = 10000
  function computeColumnStats (line 29) | function computeColumnStats(meta: ColumnMeta[], data: unknown[][]): Colu...

FILE: ui/src/lib/utils/uuid.ts
  function bytesToHex (line 1) | function bytesToHex(bytes: Uint8Array): string {
  function createUUID (line 5) | function createUUID(): string {

FILE: ui/vite.config.ts
  method manualChunks (line 41) | manualChunks(id) {
Condensed preview — 274 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,460K chars).
[
  {
    "path": ".dockerignore",
    "chars": 133,
    "preview": ".git\n.github\n.claude\n.DS_Store\nch-ui\nch-ui-server.pid\ndata\ndist\ntmp\nnode_modules\nui/node_modules\nui/.svelte-kit\nui/dist\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "chars": 1813,
    "preview": "name: Bug Report\ndescription: Found something that doesn't work as expected?\nbody:\n  - type: dropdown\n    id: os\n    att"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.yml",
    "chars": 815,
    "preview": "name: Feature Request\ndescription: Tell us about something ch-UI doesn't do yet, but should!\nbody:\n  - type: textarea\n  "
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 4350,
    "preview": "name: Release\n\non:\n  push:\n    tags:\n      - 'v*'\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n\nperm"
  },
  {
    "path": ".gitignore",
    "chars": 740,
    "preview": "# Logs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\n# Dependencies\nnode_module"
  },
  {
    "path": ".gitpod.yml",
    "chars": 891,
    "preview": "image: gitpod/workspace-full\n\ntasks:\n  - name: ClickHouse\n    init: docker pull clickhouse/clickhouse-server:latest\n    "
  },
  {
    "path": "Dockerfile",
    "chars": 1069,
    "preview": "# syntax=docker/dockerfile:1.7\n\nFROM oven/bun:1.2.23 AS ui-builder\nWORKDIR /src/ui\n\nCOPY ui/package.json ui/bun.lock ./\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 10769,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "Makefile",
    "chars": 1734,
    "preview": "# CH-UI Makefile\n# Single binary: server + agent + embedded frontend\n\nVERSION ?= $(shell cat VERSION 2>/dev/null || git "
  },
  {
    "path": "README.md",
    "chars": 16666,
    "preview": "<p align=\"center\">\n  <img src=\"ui/src/assets/logo.png\" alt=\"CH-UI Logo\" width=\"88\" />\n</p>\n\n<h1 align=\"center\">CH-UI</h1"
  },
  {
    "path": "VERSION",
    "chars": 7,
    "preview": "v2.0.23"
  },
  {
    "path": "ch-ui.conf",
    "chars": 2988,
    "preview": "upstream ch-ui {\n    server 127.0.0.1:3488;\n    keepalive 64;\n}\n\n# ─── HTTP → HTTPS redirect ───────────────────────────"
  },
  {
    "path": "cmd/connect.go",
    "chars": 7061,
    "preview": "package cmd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sysc"
  },
  {
    "path": "cmd/connect_detach_unix.go",
    "chars": 182,
    "preview": "//go:build darwin || linux\n\npackage cmd\n\nimport (\n\t\"os/exec\"\n\t\"syscall\"\n)\n\nfunc setProcessDetachedAttr(cmd *exec.Cmd) {\n"
  },
  {
    "path": "cmd/connect_detach_windows.go",
    "chars": 120,
    "preview": "//go:build windows\n\npackage cmd\n\nimport \"os/exec\"\n\nfunc setProcessDetachedAttr(cmd *exec.Cmd) {\n\t// No-op on windows.\n}\n"
  },
  {
    "path": "cmd/connect_process_unix.go",
    "chars": 252,
    "preview": "//go:build darwin || linux\n\npackage cmd\n\nimport (\n\t\"os\"\n\t\"syscall\"\n)\n\nfunc isProcessRunning(pid int) bool {\n\tif pid <= 0"
  },
  {
    "path": "cmd/connect_process_windows.go",
    "chars": 89,
    "preview": "//go:build windows\n\npackage cmd\n\nfunc isProcessRunning(pid int) bool {\n\treturn pid > 0\n}\n"
  },
  {
    "path": "cmd/root.go",
    "chars": 1383,
    "preview": "package cmd\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar rootCmd = &cobra.Command{\n\tUse:"
  },
  {
    "path": "cmd/server.go",
    "chars": 12859,
    "preview": "package cmd\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"log/slog\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"path/filep"
  },
  {
    "path": "cmd/service.go",
    "chars": 7788,
    "preview": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/caioricciuti/ch-ui/connector/config\"\n\t\"github.com/caio"
  },
  {
    "path": "cmd/tunnel.go",
    "chars": 8949,
    "preview": "package cmd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\n\tserverconfig \"github.com/caioricciuti/ch-ui/interna"
  },
  {
    "path": "cmd/uninstall.go",
    "chars": 10099,
    "preview": "package cmd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"git"
  },
  {
    "path": "cmd/update.go",
    "chars": 10179,
    "preview": "package cmd\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n"
  },
  {
    "path": "cmd/version.go",
    "chars": 404,
    "preview": "package cmd\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/version\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar version"
  },
  {
    "path": "connector/clickhouse.go",
    "chars": 13129,
    "preview": "package connector\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\""
  },
  {
    "path": "connector/config/config.go",
    "chars": 6146,
    "preview": "package config\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gopkg.in/yaml.v3\"\n)\n\n// Config h"
  },
  {
    "path": "connector/connector.go",
    "chars": 13848,
    "preview": "package connector\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"s"
  },
  {
    "path": "connector/hostinfo.go",
    "chars": 1764,
    "preview": "package connector\n\nimport (\n\t\"os\"\n\t\"runtime\"\n\t\"time\"\n)\n\n// HostInfo contains system metrics from the host machine\ntype H"
  },
  {
    "path": "connector/hostinfo_unix.go",
    "chars": 362,
    "preview": "//go:build !windows\n\npackage connector\n\nimport \"syscall\"\n\n// getDiskInfo returns total and free disk space for the root "
  },
  {
    "path": "connector/hostinfo_windows.go",
    "chars": 727,
    "preview": "//go:build windows\n\npackage connector\n\nimport (\n\t\"syscall\"\n\t\"unsafe\"\n)\n\n// getDiskInfo returns total and free disk space"
  },
  {
    "path": "connector/protocol.go",
    "chars": 3151,
    "preview": "package connector\n\n// GatewayMessage represents messages received from the CH-UI tunnel server.\ntype GatewayMessage stru"
  },
  {
    "path": "connector/service/launchd.go",
    "chars": 5336,
    "preview": "package service\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nconst launchdPlistTemplate ="
  },
  {
    "path": "connector/service/service.go",
    "chars": 4711,
    "preview": "package service\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n)\n\nconst (\n\tServiceName      = "
  },
  {
    "path": "connector/service/systemd.go",
    "chars": 5386,
    "preview": "package service\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nconst systemdServiceTemplate = `[Unit]\nDescri"
  },
  {
    "path": "connector/ui/ui.go",
    "chars": 9739,
    "preview": "package ui\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fatih/color\"\n)\n\n// UI handles formatted termina"
  },
  {
    "path": "docs/brain/SKILLS.md",
    "chars": 2201,
    "preview": "# Brain Skills\n\nThis file defines the default instruction set used by Brain across all chats.\nAdmins can copy this conte"
  },
  {
    "path": "docs/cant-login.md",
    "chars": 2078,
    "preview": "# Can't Login?\n\nUse this guide when CH-UI loads but sign-in fails, local connection is wrong, or you are blocked by retr"
  },
  {
    "path": "docs/legal/privacy-policy.md",
    "chars": 2491,
    "preview": "# Privacy Policy\n\n**Effective date:** February 12, 2026\n**Last updated:** February 12, 2026\n\nCH-UI (\"we\", \"our\", \"us\") i"
  },
  {
    "path": "docs/legal/terms-of-service.md",
    "chars": 2575,
    "preview": "# Terms of Service\n\n**Effective date:** February 12, 2026\n**Last updated:** February 12, 2026\n\nThese terms govern your u"
  },
  {
    "path": "docs/license.md",
    "chars": 2830,
    "preview": "# CH-UI Licensing\n\nCH-UI uses a dual-license model: open source core + commercial Pro modules.\n\n---\n\n## CH-UI Core (Comm"
  },
  {
    "path": "docs/production-runbook.md",
    "chars": 3762,
    "preview": "# CH-UI Production Runbook (VM2 Server + VM1 Connector)\n\nThis runbook covers a production topology where:\n\n- **VM2** run"
  },
  {
    "path": "frontend.go",
    "chars": 288,
    "preview": "package main\n\nimport (\n\t\"embed\"\n\t\"io/fs\"\n\t\"log/slog\"\n)\n\n//go:embed all:ui/dist\nvar uiDistFS embed.FS\n\nfunc frontendFS() "
  },
  {
    "path": "go.mod",
    "chars": 2737,
    "preview": "module github.com/caioricciuti/ch-ui\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/IBM/sarama v1.47.0\n\tgithub.com/fatih/color v1.18."
  },
  {
    "path": "go.sum",
    "chars": 116927,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
  },
  {
    "path": "internal/alerts/dispatcher.go",
    "chars": 23446,
    "preview": "package alerts\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"math\"\n\t\"net/http\""
  },
  {
    "path": "internal/brain/provider.go",
    "chars": 13708,
    "preview": "package brain\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"st"
  },
  {
    "path": "internal/brain/provider_test.go",
    "chars": 4220,
    "preview": "package brain\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing"
  },
  {
    "path": "internal/config/config.go",
    "chars": 5863,
    "preview": "package config\n\nimport (\n\t\"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/caioricciuti/"
  },
  {
    "path": "internal/config/secret.go",
    "chars": 2516,
    "preview": "package config\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nconst (\n\t// Defau"
  },
  {
    "path": "internal/config/secret_test.go",
    "chars": 1633,
    "preview": "package config\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestEnsureAppSecretKeyConfigured(t *testing.T) {\n\tcfg := &C"
  },
  {
    "path": "internal/crypto/aes.go",
    "chars": 3467,
    "preview": "package crypto\n\nimport (\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"golang.org/x"
  },
  {
    "path": "internal/database/alert_digests.go",
    "chars": 9686,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\ntype"
  },
  {
    "path": "internal/database/alerts.go",
    "chars": 32505,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\ntype"
  },
  {
    "path": "internal/database/audit_logs.go",
    "chars": 3660,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/google/uuid\"\n)\n\n// AuditLogParams holds param"
  },
  {
    "path": "internal/database/audit_logs_test.go",
    "chars": 2814,
    "preview": "package database\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc openTestDB(t *testing.T) *DB {\n\tt.Helper()\n\tdbPath := fil"
  },
  {
    "path": "internal/database/brain.go",
    "chars": 33449,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// BrainProvider stor"
  },
  {
    "path": "internal/database/cleanup.go",
    "chars": 1536,
    "preview": "package database\n\nimport (\n\t\"log/slog\"\n\t\"time\"\n)\n\n// StartCleanupJobs launches background goroutines that periodically c"
  },
  {
    "path": "internal/database/connections.go",
    "chars": 8347,
    "preview": "package database\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// C"
  },
  {
    "path": "internal/database/dashboards.go",
    "chars": 14682,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\nconst (\n\tsystemDashboardName    "
  },
  {
    "path": "internal/database/database.go",
    "chars": 1935,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\n\t_ \"modernc.org/sqlite\""
  },
  {
    "path": "internal/database/migrations.go",
    "chars": 39233,
    "preview": "package database\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\t\"strings\"\n\n\t\"github.com/google/uuid\"\n)\n\nfunc (db *DB) runMigrations() err"
  },
  {
    "path": "internal/database/migrations_guardrails_test.go",
    "chars": 1071,
    "preview": "package database\n\nimport \"testing\"\n\nfunc TestGuardrailColumnsExistAfterMigrations(t *testing.T) {\n\tdb := openTestDB(t)\n\n"
  },
  {
    "path": "internal/database/models.go",
    "chars": 18719,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// Model represents a SQL model "
  },
  {
    "path": "internal/database/pipelines.go",
    "chars": 15964,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// Pipeline represents a data in"
  },
  {
    "path": "internal/database/rate_limits.go",
    "chars": 3166,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"time\"\n)\n\n// RateLimitEntry represents a rate limit record.\ntype Rate"
  },
  {
    "path": "internal/database/saved_queries.go",
    "chars": 3978,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// SavedQuery represents a saved"
  },
  {
    "path": "internal/database/schedules.go",
    "chars": 9073,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// Schedule represents a schedul"
  },
  {
    "path": "internal/database/sessions.go",
    "chars": 5516,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// Session represents an authent"
  },
  {
    "path": "internal/database/settings.go",
    "chars": 2836,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Setting keys for governance features.\nconst ("
  },
  {
    "path": "internal/database/user_roles.go",
    "chars": 2853,
    "preview": "package database\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n)\n\n// UserRole represents a CH-UI role assignment for a ClickHouse use"
  },
  {
    "path": "internal/embedded/embedded.go",
    "chars": 3105,
    "preview": "package embedded\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/caioricciuti/ch-ui/connector\"\n\tconnconfig"
  },
  {
    "path": "internal/governance/guardrails.go",
    "chars": 10373,
    "preview": "package governance\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/caioricciuti/ch-ui/in"
  },
  {
    "path": "internal/governance/guardrails_test.go",
    "chars": 5509,
    "preview": "package governance\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/database\"\n)\n\ntype gua"
  },
  {
    "path": "internal/governance/harvester_access.go",
    "chars": 8117,
    "preview": "package governance\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// Default number of in"
  },
  {
    "path": "internal/governance/harvester_metadata.go",
    "chars": 9787,
    "preview": "package governance\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/google/uuid"
  },
  {
    "path": "internal/governance/harvester_querylog.go",
    "chars": 11037,
    "preview": "package governance\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time"
  },
  {
    "path": "internal/governance/incidents.go",
    "chars": 12347,
    "preview": "package governance\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// ── Object commen"
  },
  {
    "path": "internal/governance/lineage.go",
    "chars": 10082,
    "preview": "package governance\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// ── Regex patterns for table r"
  },
  {
    "path": "internal/governance/policy_engine.go",
    "chars": 7526,
    "preview": "package governance\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// ── Store interf"
  },
  {
    "path": "internal/governance/store.go",
    "chars": 62384,
    "preview": "package governance\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/databas"
  },
  {
    "path": "internal/governance/syncer.go",
    "chars": 11477,
    "preview": "package governance\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/caioricciuti/c"
  },
  {
    "path": "internal/governance/types.go",
    "chars": 14493,
    "preview": "package governance\n\n// ── Sensitivity tag constants ────────────────────────────────────────────────\n\ntype SensitivityTa"
  },
  {
    "path": "internal/langfuse/langfuse.go",
    "chars": 8376,
    "preview": "package langfuse\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go"
  },
  {
    "path": "internal/license/license.go",
    "chars": 4133,
    "preview": "package license\n\nimport (\n\t\"crypto/ed25519\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\""
  },
  {
    "path": "internal/license/pubkey.go",
    "chars": 81,
    "preview": "package license\n\nimport _ \"embed\"\n\n//go:embed public.pem\nvar publicKeyPEM []byte\n"
  },
  {
    "path": "internal/license/public.pem",
    "chars": 113,
    "preview": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA62CBTMWey4wS4Fknr/5Sfk7k1J7+4MYpBfxBPvKXRFg=\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "internal/license/tokens.go",
    "chars": 687,
    "preview": "package license\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/google/uuid\"\n)\n\n// GenerateT"
  },
  {
    "path": "internal/models/dag.go",
    "chars": 3758,
    "preview": "package models\n\nimport \"fmt\"\n\n// DepGraph is the resolved DAG of model dependencies.\ntype DepGraph struct {\n\t// Order is"
  },
  {
    "path": "internal/models/ref.go",
    "chars": 2492,
    "preview": "package models\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// stripSQLComments removes single-line (-- ...) and block (/* ."
  },
  {
    "path": "internal/models/runner.go",
    "chars": 10195,
    "preview": "package models\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/crypto\"\n\t\"github.c"
  },
  {
    "path": "internal/models/scheduler.go",
    "chars": 2412,
    "preview": "package models\n\nimport (\n\t\"log/slog\"\n\t\"time\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/database\"\n\t\"github.com/caioricciu"
  },
  {
    "path": "internal/pipelines/clickhouse_sink.go",
    "chars": 5515,
    "preview": "package pipelines\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github."
  },
  {
    "path": "internal/pipelines/database_source.go",
    "chars": 4129,
    "preview": "package pipelines\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"time\"\n\n\t_ \"github.com/go-sq"
  },
  {
    "path": "internal/pipelines/helpers.go",
    "chars": 855,
    "preview": "package pipelines\n\n// intField extracts an int from a config map with a default fallback.\nfunc intField(fields map[strin"
  },
  {
    "path": "internal/pipelines/kafka.go",
    "chars": 4513,
    "preview": "package pipelines\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.co"
  },
  {
    "path": "internal/pipelines/kafka_scram.go",
    "chars": 900,
    "preview": "package pipelines\n\nimport (\n\t\"crypto/sha256\"\n\t\"crypto/sha512\"\n\t\"hash\"\n\n\t\"github.com/xdg-go/scram\"\n)\n\n// SHA256 and SHA51"
  },
  {
    "path": "internal/pipelines/registry.go",
    "chars": 460,
    "preview": "package pipelines\n\nimport \"fmt\"\n\n// NewSource returns a SourceConnector for the given node type.\nfunc NewSource(nodeType"
  },
  {
    "path": "internal/pipelines/runner.go",
    "chars": 8537,
    "preview": "package pipelines\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/caioricciuti/ch"
  },
  {
    "path": "internal/pipelines/s3_source.go",
    "chars": 5916,
    "preview": "package pipelines\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\""
  },
  {
    "path": "internal/pipelines/types.go",
    "chars": 1552,
    "preview": "package pipelines\n\nimport (\n\t\"context\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\n// Record represents a single data record flowing throu"
  },
  {
    "path": "internal/pipelines/webhook.go",
    "chars": 5307,
    "preview": "package pipelines\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n"
  },
  {
    "path": "internal/queryproc/variables.go",
    "chars": 11523,
    "preview": "package queryproc\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// TimeRange represents a dashboar"
  },
  {
    "path": "internal/queryproc/variables_test.go",
    "chars": 1225,
    "preview": "package queryproc\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestParseRelativeTime_NowMinusMinutes(t *testing.T) {\n"
  },
  {
    "path": "internal/scheduler/cron.go",
    "chars": 2564,
    "preview": "package scheduler\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// parseField parses a single cron field (e.g. \"*/5\", \"1-15"
  },
  {
    "path": "internal/scheduler/runner.go",
    "chars": 7567,
    "preview": "package scheduler\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/caioricciuti/ch-ui/interna"
  },
  {
    "path": "internal/server/handlers/admin.go",
    "chars": 25686,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-chi/chi/v"
  },
  {
    "path": "internal/server/handlers/admin_brain.go",
    "chars": 20083,
    "preview": "package handlers\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\tbrain"
  },
  {
    "path": "internal/server/handlers/admin_governance.go",
    "chars": 3106,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/dat"
  },
  {
    "path": "internal/server/handlers/admin_langfuse.go",
    "chars": 5923,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/crypto\"\n\t\"g"
  },
  {
    "path": "internal/server/handlers/auth.go",
    "chars": 23705,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/g"
  },
  {
    "path": "internal/server/handlers/auth_helpers_test.go",
    "chars": 1082,
    "preview": "package handlers\n\nimport \"testing\"\n\nfunc TestUserRateLimitKeyScopedByConnection(t *testing.T) {\n\tk1 := userRateLimitKey("
  },
  {
    "path": "internal/server/handlers/brain.go",
    "chars": 31883,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\tbraincore \"git"
  },
  {
    "path": "internal/server/handlers/connections.go",
    "chars": 10308,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-chi/chi/v"
  },
  {
    "path": "internal/server/handlers/dashboards.go",
    "chars": 18353,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-chi/chi/v5\"\n\n\t\"g"
  },
  {
    "path": "internal/server/handlers/governance.go",
    "chars": 61912,
    "preview": "package handlers\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strin"
  },
  {
    "path": "internal/server/handlers/governance_alerts.go",
    "chars": 25943,
    "preview": "package handlers\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t"
  },
  {
    "path": "internal/server/handlers/governance_auditlog.go",
    "chars": 1539,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/caioricciuti/ch-"
  },
  {
    "path": "internal/server/handlers/governance_querylog.go",
    "chars": 4556,
    "preview": "package handlers\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/caioricciuti/ch-ui"
  },
  {
    "path": "internal/server/handlers/health.go",
    "chars": 455,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/version\"\n)\n\nty"
  },
  {
    "path": "internal/server/handlers/license.go",
    "chars": 2140,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/config\"\n\t\"g"
  },
  {
    "path": "internal/server/handlers/models.go",
    "chars": 19034,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/go-chi/chi/v5\"\n\n\t\"github"
  },
  {
    "path": "internal/server/handlers/pipelines.go",
    "chars": 14032,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/go-chi/chi/v5\"\n\n"
  },
  {
    "path": "internal/server/handlers/query.go",
    "chars": 57012,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"tim"
  },
  {
    "path": "internal/server/handlers/query_guardrails_test.go",
    "chars": 3448,
    "preview": "package handlers\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/caioricci"
  },
  {
    "path": "internal/server/handlers/query_upload.go",
    "chars": 36074,
    "preview": "package handlers\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/csv\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"math\""
  },
  {
    "path": "internal/server/handlers/saved_queries.go",
    "chars": 9648,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/go-chi/chi/v5\"\n\n\t\"github.co"
  },
  {
    "path": "internal/server/handlers/schedules.go",
    "chars": 14347,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/g"
  },
  {
    "path": "internal/server/handlers/view_graph.go",
    "chars": 7577,
    "preview": "package handlers\n\nimport (\n\t\"encoding/json\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/caioricci"
  },
  {
    "path": "internal/server/middleware/context.go",
    "chars": 714,
    "preview": "package middleware\n\nimport (\n\t\"context\"\n\t\"net/http\"\n)\n\ntype contextKey string\n\nconst (\n\tsessionKey contextKey = \"session"
  },
  {
    "path": "internal/server/middleware/cors.go",
    "chars": 1871,
    "preview": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n)\n\n// CORSConfig holds CORS configuration.\ntype CORSConfi"
  },
  {
    "path": "internal/server/middleware/license.go",
    "chars": 612,
    "preview": "package middleware\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/config\"\n)\n\n// RequirePro returns a mi"
  },
  {
    "path": "internal/server/middleware/logging.go",
    "chars": 1465,
    "preview": "package middleware\n\nimport (\n\t\"bufio\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n)\n\n// responseWriter wraps http.ResponseWri"
  },
  {
    "path": "internal/server/middleware/ratelimit.go",
    "chars": 5050,
    "preview": "package middleware\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/database\"\n)\n"
  },
  {
    "path": "internal/server/middleware/ratelimit_test.go",
    "chars": 4522,
    "preview": "package middleware\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/database\"\n)\n\n"
  },
  {
    "path": "internal/server/middleware/security.go",
    "chars": 1058,
    "preview": "package middleware\n\nimport \"net/http\"\n\n// SecurityHeaders adds security headers to responses.\nfunc SecurityHeaders(isPro"
  },
  {
    "path": "internal/server/middleware/session.go",
    "chars": 2637,
    "preview": "package middleware\n\nimport (\n\t\"encoding/json\"\n\t\"log/slog\"\n\t\"net/http\"\n\n\t\"github.com/caioricciuti/ch-ui/internal/database"
  },
  {
    "path": "internal/server/server.go",
    "chars": 10081,
    "preview": "package server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/caioricciut"
  },
  {
    "path": "internal/tunnel/api.go",
    "chars": 6879,
    "preview": "package tunnel\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/gorilla/websocket\"\n)"
  },
  {
    "path": "internal/tunnel/gateway.go",
    "chars": 14520,
    "preview": "package tunnel\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sy"
  },
  {
    "path": "internal/tunnel/protocol.go",
    "chars": 4197,
    "preview": "package tunnel\n\nimport \"encoding/json\"\n\n// AgentMessage represents messages from the tunnel agent to the gateway.\ntype A"
  },
  {
    "path": "internal/version/version.go",
    "chars": 157,
    "preview": "package version\n\nvar (\n\tVersion   = \"dev\"\n\tCommit    = \"none\"\n\tBuildDate = \"unknown\"\n)\n\nfunc Set(v, c, d string) {\n\tVers"
  },
  {
    "path": "license/public.pem",
    "chars": 113,
    "preview": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA62CBTMWey4wS4Fknr/5Sfk7k1J7+4MYpBfxBPvKXRFg=\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "main.go",
    "chars": 287,
    "preview": "package main\n\nimport (\n\t\"github.com/caioricciuti/ch-ui/cmd\"\n\t\"github.com/caioricciuti/ch-ui/internal/version\"\n)\n\nvar (\n\t"
  },
  {
    "path": "ui/.gitignore",
    "chars": 253,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
  },
  {
    "path": "ui/README.md",
    "chars": 3082,
    "preview": "# Svelte + TS + Vite\n\nThis template should help get you started developing with Svelte and TypeScript in Vite.\n\n## Recom"
  },
  {
    "path": "ui/index.html",
    "chars": 558,
    "preview": "<!doctype html>\n<html lang=\"en\" class=\"h-full\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content="
  },
  {
    "path": "ui/package.json",
    "chars": 1307,
    "preview": "{\n  \"name\": \"ui\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"bu"
  },
  {
    "path": "ui/src/App.svelte",
    "chars": 1006,
    "preview": "<script lang=\"ts\">\n  import { onMount } from 'svelte'\n  import { initSession, isAuthenticated, isLoading } from './lib/s"
  },
  {
    "path": "ui/src/app.css",
    "chars": 11984,
    "preview": "@import \"tailwindcss\";\n\n@custom-variant dark (&:where(.dark, .dark *));\n\n@theme {\n  --font-sans: \"Inter\", \"SF Pro Text\","
  },
  {
    "path": "ui/src/lib/api/alerts.ts",
    "chars": 3099,
    "preview": "import { apiDel, apiGet, apiPost, apiPut } from './client'\nimport type { AlertChannel, AlertEvent, AlertRule } from '../"
  },
  {
    "path": "ui/src/lib/api/auth.ts",
    "chars": 1241,
    "preview": "import { apiGet, apiPost } from './client'\nimport type { Session, Connection } from '../types/api'\n\ninterface LoginParam"
  },
  {
    "path": "ui/src/lib/api/brain.ts",
    "chars": 5934,
    "preview": "import { withBase } from '../basePath'\nimport { apiDel, apiGet, apiPost, apiPut } from './client'\nimport type {\n  BrainA"
  },
  {
    "path": "ui/src/lib/api/client.ts",
    "chars": 2749,
    "preview": "import { withBase } from '../basePath'\nimport { safeParse } from '../utils/safe-json'\n\n/** Base fetch wrapper with crede"
  },
  {
    "path": "ui/src/lib/api/governance.ts",
    "chars": 10057,
    "preview": "import { apiGet, apiPost, apiPut, apiDel } from './client'\nimport type {\n  GovernanceOverview,\n  GovernanceSettings,\n  S"
  },
  {
    "path": "ui/src/lib/api/models.ts",
    "chars": 2190,
    "preview": "import { apiGet, apiPost, apiPut, apiDel } from './client'\nimport type { Model, ModelRun, ModelRunResult, ModelDAG, Vali"
  },
  {
    "path": "ui/src/lib/api/pipelines.ts",
    "chars": 2041,
    "preview": "import { apiGet, apiPost, apiPut, apiDel } from './client'\nimport type { Pipeline, PipelineGraph, PipelineRun, PipelineR"
  },
  {
    "path": "ui/src/lib/api/query.ts",
    "chars": 6380,
    "preview": "import { apiGet, apiPost } from './client'\nimport type {\n  LegacyQueryResult,\n  ExplorerDataResponse,\n  QueryPlanResult,"
  },
  {
    "path": "ui/src/lib/api/stream.ts",
    "chars": 2098,
    "preview": "import { withBase } from '../basePath'\nimport type { ColumnMeta, QueryStats, StreamMessage } from '../types/query'\nimpor"
  },
  {
    "path": "ui/src/lib/basePath.ts",
    "chars": 1121,
    "preview": "/**\n * Base path utility for subpath deployments.\n *\n * Supports deploying CH-UI behind a reverse proxy at a subpath.\n *"
  },
  {
    "path": "ui/src/lib/components/brain/BrainArtifactCard.svelte",
    "chars": 6121,
    "preview": "<script lang=\"ts\">\n  import type { BrainArtifact } from '../../types/brain'\n  import type { PanelConfig } from '../../ty"
  },
  {
    "path": "ui/src/lib/components/brain/BrainEmptyState.svelte",
    "chars": 322,
    "preview": "<script lang=\"ts\">\n  import { Brain } from 'lucide-svelte'\n</script>\n\n<div class=\"h-full flex items-center justify-cente"
  },
  {
    "path": "ui/src/lib/components/brain/BrainHeader.svelte",
    "chars": 2055,
    "preview": "<script lang=\"ts\">\n  import type { BrainModelOption } from '../../types/brain'\n  import type { ComboboxOption } from '.."
  },
  {
    "path": "ui/src/lib/components/brain/BrainInput.svelte",
    "chars": 4607,
    "preview": "<script lang=\"ts\">\n  import Button from '../common/Button.svelte'\n  import { Send, Database as DbIcon, X } from 'lucide-"
  },
  {
    "path": "ui/src/lib/components/brain/BrainMentionDropdown.svelte",
    "chars": 3426,
    "preview": "<script lang=\"ts\">\n  import { getDatabases, loadTables } from '../../stores/schema.svelte'\n  import { Database as DbIcon"
  },
  {
    "path": "ui/src/lib/components/brain/BrainMessage.svelte",
    "chars": 3017,
    "preview": "<script lang=\"ts\">\n  import type { BrainArtifact, BrainMessage as BrainMessageType } from '../../types/brain'\n  import {"
  },
  {
    "path": "ui/src/lib/components/brain/BrainSidebar.svelte",
    "chars": 3979,
    "preview": "<script lang=\"ts\">\n  import type { BrainChat } from '../../types/brain'\n  import Spinner from '../common/Spinner.svelte'"
  },
  {
    "path": "ui/src/lib/components/brain/BrainSqlBlock.svelte",
    "chars": 2551,
    "preview": "<script lang=\"ts\">\n  import type { BrainArtifact } from '../../types/brain'\n  import { copyToClipboard } from '../../uti"
  },
  {
    "path": "ui/src/lib/components/brain/brain-markdown.ts",
    "chars": 3360,
    "preview": "import { Marked } from 'marked'\n\nexport interface MessageSegment {\n  type: 'markdown' | 'sql'\n  content: string\n  html?:"
  },
  {
    "path": "ui/src/lib/components/common/Button.svelte",
    "chars": 2098,
    "preview": "<script lang=\"ts\">\n  import type { Snippet } from 'svelte'\n\n  interface Props {\n    variant?: 'primary' | 'secondary' | "
  },
  {
    "path": "ui/src/lib/components/common/Combobox.svelte",
    "chars": 5288,
    "preview": "<script lang=\"ts\">\n  import { tick } from 'svelte'\n  import { Search, ChevronDown, Check } from 'lucide-svelte'\n\n  expor"
  },
  {
    "path": "ui/src/lib/components/common/ConfirmDialog.svelte",
    "chars": 1048,
    "preview": "<script lang=\"ts\">\n  import Modal from './Modal.svelte'\n  import Button from './Button.svelte'\n\n  interface Props {\n    "
  },
  {
    "path": "ui/src/lib/components/common/ContextMenu.svelte",
    "chars": 4393,
    "preview": "<script lang=\"ts\">\n  import { tick } from 'svelte'\n\n  export interface ContextMenuItem {\n    id: string\n    label?: stri"
  },
  {
    "path": "ui/src/lib/components/common/HelpTip.svelte",
    "chars": 3971,
    "preview": "<script lang=\"ts\">\n  import { onDestroy, tick } from 'svelte'\n  import { HelpCircle } from 'lucide-svelte'\n\n  interface "
  },
  {
    "path": "ui/src/lib/components/common/InputDialog.svelte",
    "chars": 1798,
    "preview": "<script lang=\"ts\">\n  import Modal from './Modal.svelte'\n  import Button from './Button.svelte'\n\n  interface Props {\n    "
  },
  {
    "path": "ui/src/lib/components/common/MiniTrendChart.svelte",
    "chars": 1529,
    "preview": "<script lang=\"ts\">\n  import { onMount } from 'svelte'\n  import uPlot from 'uplot'\n  import 'uplot/dist/uPlot.min.css'\n\n "
  },
  {
    "path": "ui/src/lib/components/common/Modal.svelte",
    "chars": 1477,
    "preview": "<script lang=\"ts\">\n  import type { Snippet } from 'svelte'\n  import { X } from 'lucide-svelte'\n\n  interface Props {\n    "
  },
  {
    "path": "ui/src/lib/components/common/ProRequired.svelte",
    "chars": 3466,
    "preview": "<script lang=\"ts\">\n  import { openSingletonTab } from '../../stores/tabs.svelte'\n  import {\n    ShieldAlert,\n    Setting"
  },
  {
    "path": "ui/src/lib/components/common/Sheet.svelte",
    "chars": 1639,
    "preview": "<script lang=\"ts\">\n  import type { Snippet } from 'svelte'\n  import { fly, fade } from 'svelte/transition'\n  import { X "
  },
  {
    "path": "ui/src/lib/components/common/Spinner.svelte",
    "chars": 613,
    "preview": "<script lang=\"ts\">\n  interface Props {\n    size?: 'sm' | 'md' | 'lg'\n    class?: string\n  }\n\n  let { size = 'md', class:"
  },
  {
    "path": "ui/src/lib/components/common/Toast.svelte",
    "chars": 1014,
    "preview": "<script lang=\"ts\">\n  import { Toaster } from 'svelte-sonner'\n</script>\n\n<Toaster\n  position=\"top-right\"\n  richColors={tr"
  },
  {
    "path": "ui/src/lib/components/dashboard/ChartPanel.svelte",
    "chars": 8776,
    "preview": "<script lang=\"ts\">\n  import { onMount, onDestroy } from 'svelte'\n  import uPlot from 'uplot'\n  import 'uplot/dist/uPlot."
  },
  {
    "path": "ui/src/lib/components/dashboard/DashboardGrid.svelte",
    "chars": 12589,
    "preview": "<script lang=\"ts\">\n  import type { Panel, PanelConfig } from '../../types/api'\n  import { apiPut } from '../../api/clien"
  },
  {
    "path": "ui/src/lib/components/dashboard/PanelEditor.svelte",
    "chars": 16350,
    "preview": "<script lang=\"ts\">\n  import type { Panel, PanelConfig } from '../../types/api'\n  import type { ColumnMeta } from '../../"
  },
  {
    "path": "ui/src/lib/components/dashboard/TimeRangeSelector.svelte",
    "chars": 7479,
    "preview": "<script lang=\"ts\">\n  import {\n    decodeAbsoluteDashboardRange,\n    encodeAbsoluteDashboardRange,\n    formatDashboardTim"
  },
  {
    "path": "ui/src/lib/components/dashboard/time-picker/CalendarMonth.svelte",
    "chars": 3033,
    "preview": "<script lang=\"ts\">\n  import { buildMonthGrid, isSameDay, isInRange, isToday, monthName } from '../../../utils/calendar'\n"
  },
  {
    "path": "ui/src/lib/components/dashboard/time-picker/DualCalendar.svelte",
    "chars": 1624,
    "preview": "<script lang=\"ts\">\n  import { shiftMonth } from '../../../utils/calendar'\n  import { ChevronLeft, ChevronRight } from 'l"
  },
  {
    "path": "ui/src/lib/components/dashboard/time-picker/PresetList.svelte",
    "chars": 2201,
    "preview": "<script lang=\"ts\">\n  import { EXTENDED_PRESETS } from '../../../utils/chart-transform'\n\n  interface Props {\n    currentV"
  },
  {
    "path": "ui/src/lib/components/dashboard/time-picker/TimeInput.svelte",
    "chars": 1901,
    "preview": "<script lang=\"ts\">\n  import { Clock3 } from 'lucide-svelte'\n\n  interface Props {\n    label: string\n    value: string\n   "
  },
  {
    "path": "ui/src/lib/components/dashboard/time-picker/TimezoneSelect.svelte",
    "chars": 563,
    "preview": "<script lang=\"ts\">\n  interface Props {\n    value: string\n    onchange: (tz: string) => void\n  }\n\n  let { value, onchange"
  },
  {
    "path": "ui/src/lib/components/editor/InsightsPanel.svelte",
    "chars": 19310,
    "preview": "<script lang=\"ts\">\n  import type { ColumnMeta, QueryPlanNode, QueryStats, QueryEstimateResult } from '../../types/query'"
  },
  {
    "path": "ui/src/lib/components/editor/ResultFooter.svelte",
    "chars": 10693,
    "preview": "<script lang=\"ts\">\n  import type { ColumnMeta, QueryStats } from '../../types/query'\n  import { formatNumber, formatElap"
  },
  {
    "path": "ui/src/lib/components/editor/ResultPanel.svelte",
    "chars": 4249,
    "preview": "<script lang=\"ts\">\n  import type { ColumnMeta, QueryPlanNode, QueryStats, QueryEstimateResult } from '../../types/query'"
  },
  {
    "path": "ui/src/lib/components/editor/SchemaPanel.svelte",
    "chars": 2324,
    "preview": "<script lang=\"ts\">\n  import type { ColumnMeta } from '../../types/query'\n  import { getDisplayType } from '../../utils/c"
  },
  {
    "path": "ui/src/lib/components/editor/SqlEditor.svelte",
    "chars": 8395,
    "preview": "<script lang=\"ts\">\n  import { onMount, onDestroy } from 'svelte'\n  import { EditorView, keymap } from '@codemirror/view'"
  },
  {
    "path": "ui/src/lib/components/editor/StatsPanel.svelte",
    "chars": 4169,
    "preview": "<script lang=\"ts\">\n  import type { ColumnStats } from '../../utils/stats'\n  import { formatNumber } from '../../utils/fo"
  },
  {
    "path": "ui/src/lib/components/editor/Toolbar.svelte",
    "chars": 2422,
    "preview": "<script lang=\"ts\">\n  import Button from '../common/Button.svelte'\n  import { Play, Square, AlignLeft, BookOpen, Save, Za"
  },
  {
    "path": "ui/src/lib/components/explorer/DataPreview.svelte",
    "chars": 2803,
    "preview": "<script lang=\"ts\">\n  import type { ColumnMeta } from '../../types/query'\n  import { fetchExplorerData } from '../../api/"
  },
  {
    "path": "ui/src/lib/components/explorer/DatabaseTree.svelte",
    "chars": 67279,
    "preview": "<script lang=\"ts\">\n  import { onMount } from \"svelte\";\n  import {\n    getDatabases,\n    isSchemaLoading,\n    loadDatabas"
  },
  {
    "path": "ui/src/lib/components/governance/LineageGraph.svelte",
    "chars": 3301,
    "preview": "<script lang=\"ts\">\n  import {\n    SvelteFlow,\n    Controls,\n    Background,\n    MiniMap,\n    type Edge,\n    type NodeTyp"
  },
  {
    "path": "ui/src/lib/components/governance/LineageTableNode.svelte",
    "chars": 3629,
    "preview": "<script lang=\"ts\">\n  import { Handle, Position } from '@xyflow/svelte'\n  import { Database, ChevronDown, ChevronRight } "
  }
]

// ... and 74 more files (download for full content)

About this extraction

This page contains the full source code of the caioricciuti/ch-ui GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 274 files (2.1 MB), approximately 577.0k tokens, and a symbol index with 1864 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!