We use cookies to enhance your experience on the site
CodeWorlds
Back to collections
Guide31 min read

Greptile

Greptile is an API for building AI assistants with codebase context. Indexes repositories and provides intelligent context for custom tools. Ideal for creating PR review bots, custom chatbots, documentation generators and internal AI tools.

Greptile - Codebase AI API

Czym jest Greptile?

Greptile to API, ktΓ³re pozwala na budowanie wΕ‚asnych narzΔ™dzi AI z peΕ‚nym kontekstem twojego codebase. W przeciwieΕ„stwie od gotowych narzΔ™dzi jak GitHub Copilot czy Cody, Greptile dostarcza infrastrukturΔ™ i API do tworzenia wΕ‚asnych rozwiΔ…zaΕ„ - od chatbotΓ³w przez PR review boty po generatory dokumentacji.

Greptile indeksuje twoje repozytoria z GitHub lub GitLab, tworzy embeddings i udostępnia proste API do przeszukiwania i odpowiadania na pytania o kodzie. Dzięki temu możesz zbudować custom AI assistants, które rozumieją twój konkretny projekt, jego architekturę i konwencje.

Dlaczego Greptile?

Kluczowe zalety Greptile:

  1. Proste API - REST API, Ε‚atwe do integracji w kilka godzin
  2. PeΕ‚na customization - Zbuduj dokΕ‚adnie to, czego potrzebujesz
  3. Codebase context - AI rozumie caΕ‚y projekt, nie tylko pojedynczy plik
  4. Multi-repo support - PoΕ‚Δ…cz wiedzΔ™ z wielu repozytoriΓ³w
  5. Privacy-first - MoΕΌliwoΕ›Δ‡ self-hostingu na Enterprise
  6. Model-agnostic - UΕΌywaj wΕ‚asnych modeli LLM lub Greptile default
  7. Real-time updates - Automatyczna aktualizacja indeksu przy zmianach

Greptile vs alternatywy

CechaGreptileOpenAI API + RAGSourcegraph API
TypAPI dla codebaseOgΓ³lne LLM APICode search API
SetupMinutyDni/tygodnieGodziny
Codebase contextβœ… Automatyczne⚠️ DIYβœ… Tak
Multi-repoβœ… Tak⚠️ Manualβœ… Tak
Custom toolingβœ… GΕ‚Γ³wny use caseβœ… Tak⚠️ Ograniczone
Koszt wejΕ›ciaNiskiWysoki (dev time)Średni
MaintenanceMinimalneWysokieŚrednie

Jak dziaΕ‚a Greptile?

Architektura

Code
TEXT
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Twoja aplikacja                       β”‚
β”‚  (Slack bot, CLI tool, Web app, VS Code extension)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚ REST API
                          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Greptile API                         β”‚
β”‚                                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚   Query     β”‚  β”‚   Index     β”‚  β”‚     Search      β”‚ β”‚
β”‚  β”‚  Endpoint   β”‚  β”‚  Endpoint   β”‚  β”‚    Endpoint     β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚              Codebase Index                        β”‚  β”‚
β”‚  β”‚  - Embeddings (semantic understanding)            β”‚  β”‚
β”‚  β”‚  - Code graph (dependencies, definitions)         β”‚  β”‚
β”‚  β”‚  - File relationships                             β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚
                          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  GitHub / GitLab                         β”‚
β”‚              (Twoje repozytoria)                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Proces indeksowania

  1. PoΕ‚Δ…cz repo - Greptile pobiera kod z GitHub/GitLab
  2. Parsowanie - Kod jest analizowany i dzielony na chunks
  3. Embeddings - Tworzenie wektorowych reprezentacji dla semantic search
  4. Indeksowanie - Budowanie indeksu dla szybkiego wyszukiwania
  5. Aktualizacja - Webhooks automatycznie aktualizujΔ… indeks

Instalacja i konfiguracja

Uzyskanie API key

  1. Zarejestruj siΔ™ na greptile.com
  2. PrzejdΕΊ do Dashboard β†’ API Keys
  3. Kliknij "Create API Key"
  4. Skopiuj klucz (widoczny tylko raz)

Zmienne Ε›rodowiskowe

Code
Bash
# .env
GREPTILE_API_KEY=your_api_key_here
GITHUB_TOKEN=ghp_xxx  # Opcjonalnie, dla prywatnych repo

API Reference

PoΕ‚Δ…czenie repozytorium

Code
TypeScript
async function connectRepository(owner: string, repo: string) {
  const response = await fetch('https://api.greptile.com/v2/repositories', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      remote: 'github',
      repository: `${owner}/${repo}`,
      branch: 'main',
    }),
  })

  if (!response.ok) {
    throw new Error(`Failed to connect repository: ${response.statusText}`)
  }

  return await response.json()
}

const result = await connectRepository('myorg', 'my-project')
console.log('Repository connected:', result)

Sprawdzenie statusu indeksowania

Code
TypeScript
async function getIndexStatus(owner: string, repo: string) {
  const response = await fetch(
    `https://api.greptile.com/v2/repositories/${owner}/${repo}/status`,
    {
      headers: {
        'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      },
    }
  )

  return await response.json()
}

const status = await getIndexStatus('myorg', 'my-project')
console.log('Index status:', status)
// { status: 'completed', files_indexed: 1234, last_updated: '...' }

Query - Pytanie o codebase

Code
TypeScript
interface QueryRequest {
  messages: Array<{
    role: 'user' | 'assistant'
    content: string
  }>
  repositories: Array<{
    remote: 'github' | 'gitlab'
    repository: string
    branch: string
  }>
  sessionId?: string
  stream?: boolean
}

interface QueryResponse {
  message: string
  sources: Array<{
    repository: string
    filepath: string
    linestart: number
    lineend: number
    summary: string
  }>
}

async function queryCodebase(
  question: string,
  repos: string[]
): Promise<QueryResponse> {
  const response = await fetch('https://api.greptile.com/v2/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messages: [
        {
          role: 'user',
          content: question,
        },
      ],
      repositories: repos.map(repo => ({
        remote: 'github',
        repository: repo,
        branch: 'main',
      })),
    }),
  })

  if (!response.ok) {
    throw new Error(`Query failed: ${response.statusText}`)
  }

  return await response.json()
}

const answer = await queryCodebase(
  'How does authentication work in this codebase?',
  ['myorg/backend', 'myorg/auth-service']
)

console.log('Answer:', answer.message)
console.log('Sources:', answer.sources)

Search - Wyszukiwanie bez AI

Code
TypeScript
interface SearchRequest {
  query: string
  repositories: string[]
  options?: {
    limit?: number
    includeContent?: boolean
  }
}

interface SearchResult {
  repository: string
  filepath: string
  linestart: number
  lineend: number
  content: string
  score: number
}

async function searchCode(
  query: string,
  repos: string[]
): Promise<SearchResult[]> {
  const response = await fetch('https://api.greptile.com/v2/search', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      query,
      repositories: repos.map(repo => `github:${repo}`),
      options: {
        limit: 10,
        includeContent: true,
      },
    }),
  })

  return await response.json()
}

const results = await searchCode(
  'user authentication',
  ['myorg/backend']
)

results.forEach(result => {
  console.log(`${result.filepath}:${result.linestart}`)
  console.log(result.content)
})

Streaming responses

Code
TypeScript
async function streamQuery(question: string, repos: string[]) {
  const response = await fetch('https://api.greptile.com/v2/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messages: [{ role: 'user', content: question }],
      repositories: repos.map(repo => ({
        remote: 'github',
        repository: repo,
        branch: 'main',
      })),
      stream: true,
    }),
  })

  const reader = response.body?.getReader()
  const decoder = new TextDecoder()

  while (reader) {
    const { done, value } = await reader.read()
    if (done) break

    const chunk = decoder.decode(value)
    process.stdout.write(chunk)
  }
}

Use Cases

1. Custom AI Chat

Zbuduj wΕ‚asnego chatbota, ktΓ³ry zna twΓ³j codebase:

TSapp/api/chat/route.ts
TypeScript
// app/api/chat/route.ts
import { NextRequest, NextResponse } from 'next/server'

const GREPTILE_API_KEY = process.env.GREPTILE_API_KEY!
const REPOS = ['myorg/frontend', 'myorg/backend', 'myorg/docs']

export async function POST(request: NextRequest) {
  const { message, history } = await request.json()

  try {
    const response = await fetch('https://api.greptile.com/v2/query', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${GREPTILE_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        messages: [
          ...history.map((msg: any) => ({
            role: msg.role,
            content: msg.content,
          })),
          { role: 'user', content: message },
        ],
        repositories: REPOS.map(repo => ({
          remote: 'github',
          repository: repo,
          branch: 'main',
        })),
      }),
    })

    const data = await response.json()

    return NextResponse.json({
      answer: data.message,
      sources: data.sources,
    })
  } catch (error) {
    console.error('Greptile error:', error)
    return NextResponse.json(
      { error: 'Failed to process query' },
      { status: 500 }
    )
  }
}

2. PR Review Bot

Automatyczny code review dla pull requestΓ³w:

TSlib/pr-reviewer.ts
TypeScript
// lib/pr-reviewer.ts
import { Octokit } from '@octokit/rest'

const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })

interface PRReviewResult {
  summary: string
  issues: Array<{
    file: string
    line: number
    severity: 'error' | 'warning' | 'suggestion'
    message: string
  }>
}

async function reviewPR(
  owner: string,
  repo: string,
  prNumber: number
): Promise<PRReviewResult> {
  const { data: diff } = await octokit.pulls.get({
    owner,
    repo,
    pull_number: prNumber,
    mediaType: { format: 'diff' },
  })

  const { data: pr } = await octokit.pulls.get({
    owner,
    repo,
    pull_number: prNumber,
  })

  const response = await fetch('https://api.greptile.com/v2/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messages: [
        {
          role: 'user',
          content: `Review this pull request diff considering our codebase conventions and patterns.

PR Title: ${pr.title}
PR Description: ${pr.body || 'No description'}

Diff:
${diff}

Please identify:
1. Potential bugs or issues
2. Code style inconsistencies with our codebase
3. Missing error handling
4. Security concerns
5. Performance issues

Format each issue as:
FILE: [filename]
LINE: [line number]
SEVERITY: [error|warning|suggestion]
MESSAGE: [description]`,
        },
      ],
      repositories: [
        {
          remote: 'github',
          repository: `${owner}/${repo}`,
          branch: 'main',
        },
      ],
    }),
  })

  const data = await response.json()

  return parseReviewResponse(data.message)
}

function parseReviewResponse(response: string): PRReviewResult {
  const issues: PRReviewResult['issues'] = []
  const lines = response.split('\n')

  let currentIssue: Partial<PRReviewResult['issues'][0]> = {}

  for (const line of lines) {
    if (line.startsWith('FILE:')) {
      currentIssue.file = line.replace('FILE:', '').trim()
    } else if (line.startsWith('LINE:')) {
      currentIssue.line = parseInt(line.replace('LINE:', '').trim())
    } else if (line.startsWith('SEVERITY:')) {
      currentIssue.severity = line.replace('SEVERITY:', '').trim() as any
    } else if (line.startsWith('MESSAGE:')) {
      currentIssue.message = line.replace('MESSAGE:', '').trim()

      if (currentIssue.file && currentIssue.message) {
        issues.push(currentIssue as PRReviewResult['issues'][0])
        currentIssue = {}
      }
    }
  }

  return {
    summary: response.split('\n\n')[0] || 'Review completed',
    issues,
  }
}

// app/api/webhooks/github/route.ts
export async function POST(request: NextRequest) {
  const event = request.headers.get('x-github-event')
  const payload = await request.json()

  if (event === 'pull_request' && payload.action === 'opened') {
    const { owner, repo } = payload.repository
    const prNumber = payload.pull_request.number

    const review = await reviewPR(owner.login, repo, prNumber)

    await octokit.pulls.createReview({
      owner: owner.login,
      repo,
      pull_number: prNumber,
      body: `## AI Code Review\n\n${review.summary}`,
      event: 'COMMENT',
      comments: review.issues.map(issue => ({
        path: issue.file,
        line: issue.line,
        body: `**${issue.severity.toUpperCase()}**: ${issue.message}`,
      })),
    })
  }

  return NextResponse.json({ received: true })
}

3. Documentation Generator

Automatyczne generowanie dokumentacji z kodu:

TSscripts/generate-docs.ts
TypeScript
// scripts/generate-docs.ts
interface DocSection {
  title: string
  content: string
  codeExamples: string[]
}

async function generateDocumentation(
  repo: string,
  module: string
): Promise<DocSection[]> {
  const response = await fetch('https://api.greptile.com/v2/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messages: [
        {
          role: 'user',
          content: `Generate comprehensive documentation for the ${module} module.

Include:
1. Overview - what this module does
2. Installation/Setup - how to set it up
3. API Reference - all public functions/classes with:
   - Description
   - Parameters
   - Return values
   - Example usage
4. Best Practices - recommended patterns
5. Common Issues - troubleshooting

Format as markdown with code examples.`,
        },
      ],
      repositories: [
        {
          remote: 'github',
          repository: repo,
          branch: 'main',
        },
      ],
    }),
  })

  const data = await response.json()

  return parseMarkdownSections(data.message)
}

async function main() {
  const docs = await generateDocumentation(
    'myorg/backend',
    'authentication'
  )

  const markdown = docs
    .map(section => `## ${section.title}\n\n${section.content}`)
    .join('\n\n')

  fs.writeFileSync('docs/authentication.md', markdown)
}

4. Slack Bot

Slack bot odpowiadajΔ…cy na pytania o kodzie:

TSapp/api/slack/route.ts
TypeScript
// app/api/slack/route.ts
import { NextRequest, NextResponse } from 'next/server'
import crypto from 'crypto'

const SLACK_SIGNING_SECRET = process.env.SLACK_SIGNING_SECRET!
const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN!
const GREPTILE_API_KEY = process.env.GREPTILE_API_KEY!

function verifySlackSignature(
  signature: string,
  timestamp: string,
  body: string
): boolean {
  const sigBasestring = `v0:${timestamp}:${body}`
  const mySignature = 'v0=' + crypto
    .createHmac('sha256', SLACK_SIGNING_SECRET)
    .update(sigBasestring)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(mySignature),
    Buffer.from(signature)
  )
}

export async function POST(request: NextRequest) {
  const body = await request.text()
  const timestamp = request.headers.get('x-slack-request-timestamp')!
  const signature = request.headers.get('x-slack-signature')!

  if (!verifySlackSignature(signature, timestamp, body)) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
  }

  const payload = JSON.parse(body)

  if (payload.type === 'url_verification') {
    return NextResponse.json({ challenge: payload.challenge })
  }

  if (payload.event?.type === 'app_mention') {
    const { text, channel, thread_ts, ts } = payload.event
    const question = text.replace(/<@[^>]+>/g, '').trim()

    await handleQuestion(channel, thread_ts || ts, question)
  }

  return NextResponse.json({ ok: true })
}

async function handleQuestion(
  channel: string,
  threadTs: string,
  question: string
) {
  const response = await fetch('https://api.greptile.com/v2/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messages: [{ role: 'user', content: question }],
      repositories: [
        { remote: 'github', repository: 'myorg/backend', branch: 'main' },
        { remote: 'github', repository: 'myorg/frontend', branch: 'main' },
      ],
    }),
  })

  const data = await response.json()

  const sources = data.sources
    .slice(0, 3)
    .map((s: any) => `β€’ \`${s.filepath}:${s.linestart}\``)
    .join('\n')

  await fetch('https://slack.com/api/chat.postMessage', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${SLACK_BOT_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      channel,
      thread_ts: threadTs,
      text: data.message,
      blocks: [
        {
          type: 'section',
          text: { type: 'mrkdwn', text: data.message },
        },
        {
          type: 'context',
          elements: [
            {
              type: 'mrkdwn',
              text: `*Sources:*\n${sources}`,
            },
          ],
        },
      ],
    }),
  })
}

5. VS Code Extension

WΕ‚asne rozszerzenie VS Code z Greptile:

TSextension/src/extension.ts
TypeScript
// extension/src/extension.ts
import * as vscode from 'vscode'

const GREPTILE_API_KEY = process.env.GREPTILE_API_KEY!

export function activate(context: vscode.ExtensionContext) {
  const explainCommand = vscode.commands.registerCommand(
    'greptile.explain',
    async () => {
      const editor = vscode.window.activeTextEditor
      if (!editor) return

      const selection = editor.selection
      const selectedText = editor.document.getText(selection)

      if (!selectedText) {
        vscode.window.showWarningMessage('Please select some code first')
        return
      }

      const answer = await queryGreptile(
        `Explain this code in the context of our codebase:\n\n${selectedText}`
      )

      const panel = vscode.window.createWebviewPanel(
        'greptileExplanation',
        'Code Explanation',
        vscode.ViewColumn.Beside,
        {}
      )

      panel.webview.html = `
        <html>
          <body>
            <h2>Explanation</h2>
            <pre>${answer.message}</pre>
            <h3>Related files</h3>
            <ul>
              ${answer.sources.map((s: any) =>
                `<li>${s.filepath}:${s.linestart}</li>`
              ).join('')}
            </ul>
          </body>
        </html>
      `
    }
  )

  const askCommand = vscode.commands.registerCommand(
    'greptile.ask',
    async () => {
      const question = await vscode.window.showInputBox({
        prompt: 'Ask about the codebase',
        placeHolder: 'How does authentication work?',
      })

      if (!question) return

      const answer = await queryGreptile(question)

      vscode.window.showInformationMessage(
        answer.message.substring(0, 200) + '...'
      )
    }
  )

  context.subscriptions.push(explainCommand, askCommand)
}

async function queryGreptile(question: string) {
  const workspaceFolders = vscode.workspace.workspaceFolders || []
  const repos = workspaceFolders.map(folder => {
    return getGitRemote(folder.uri.fsPath)
  }).filter(Boolean)

  const response = await fetch('https://api.greptile.com/v2/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messages: [{ role: 'user', content: question }],
      repositories: repos.map(repo => ({
        remote: 'github',
        repository: repo,
        branch: 'main',
      })),
    }),
  })

  return await response.json()
}

6. CLI Tool

WΕ‚asne narzΔ™dzie CLI:

Code
TypeScript
#!/usr/bin/env node
// cli/greptile-cli.ts

import { Command } from 'commander'
import inquirer from 'inquirer'
import chalk from 'chalk'
import ora from 'ora'

const program = new Command()

program
  .name('greptile')
  .description('Query your codebase using AI')
  .version('1.0.0')

program
  .command('ask <question>')
  .description('Ask a question about the codebase')
  .option('-r, --repo <repos...>', 'Repositories to search')
  .action(async (question, options) => {
    const spinner = ora('Querying codebase...').start()

    try {
      const response = await fetch('https://api.greptile.com/v2/query', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          messages: [{ role: 'user', content: question }],
          repositories: options.repo.map((repo: string) => ({
            remote: 'github',
            repository: repo,
            branch: 'main',
          })),
        }),
      })

      const data = await response.json()
      spinner.stop()

      console.log('\n' + chalk.green('Answer:'))
      console.log(data.message)

      console.log('\n' + chalk.blue('Sources:'))
      data.sources.forEach((source: any) => {
        console.log(chalk.gray(`  ${source.filepath}:${source.linestart}`))
      })
    } catch (error) {
      spinner.fail('Query failed')
      console.error(error)
    }
  })

program
  .command('chat')
  .description('Start an interactive chat session')
  .option('-r, --repo <repos...>', 'Repositories to search')
  .action(async (options) => {
    const history: any[] = []

    console.log(chalk.blue('Starting Greptile chat. Type "exit" to quit.\n'))

    while (true) {
      const { question } = await inquirer.prompt([
        {
          type: 'input',
          name: 'question',
          message: chalk.green('You:'),
        },
      ])

      if (question.toLowerCase() === 'exit') break

      const spinner = ora('Thinking...').start()

      const response = await fetch('https://api.greptile.com/v2/query', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          messages: [
            ...history,
            { role: 'user', content: question },
          ],
          repositories: options.repo.map((repo: string) => ({
            remote: 'github',
            repository: repo,
            branch: 'main',
          })),
        }),
      })

      const data = await response.json()
      spinner.stop()

      history.push({ role: 'user', content: question })
      history.push({ role: 'assistant', content: data.message })

      console.log(chalk.yellow('\nGreptile:'))
      console.log(data.message + '\n')
    }
  })

program
  .command('search <query>')
  .description('Search for code (without AI)')
  .option('-r, --repo <repos...>', 'Repositories to search')
  .option('-n, --limit <number>', 'Max results', '10')
  .action(async (query, options) => {
    const spinner = ora('Searching...').start()

    try {
      const response = await fetch('https://api.greptile.com/v2/search', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          query,
          repositories: options.repo.map((r: string) => `github:${r}`),
          options: { limit: parseInt(options.limit) },
        }),
      })

      const results = await response.json()
      spinner.stop()

      results.forEach((result: any, i: number) => {
        console.log(chalk.blue(`\n${i + 1}. ${result.filepath}:${result.linestart}`))
        console.log(chalk.gray(result.content))
      })
    } catch (error) {
      spinner.fail('Search failed')
      console.error(error)
    }
  })

program.parse()

Integracje

GitHub App

TSapp/api/github/install/route.ts
TypeScript
// app/api/github/install/route.ts

export async function POST(request: NextRequest) {
  const { action, repositories, installation } = await request.json()

  if (action === 'created' || action === 'added') {
    for (const repo of repositories) {
      await fetch('https://api.greptile.com/v2/repositories', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          remote: 'github',
          repository: repo.full_name,
          branch: 'main',
          githubInstallationId: installation.id,
        }),
      })
    }
  }

  return NextResponse.json({ ok: true })
}

Webhooks dla real-time updates

TSapp/api/webhooks/github/push/route.ts
TypeScript
// app/api/webhooks/github/push/route.ts

export async function POST(request: NextRequest) {
  const { repository, ref } = await request.json()

  if (ref !== 'refs/heads/main') {
    return NextResponse.json({ skipped: true })
  }

  await fetch(
    `https://api.greptile.com/v2/repositories/${repository.full_name}/reindex`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      },
    }
  )

  return NextResponse.json({ reindexed: true })
}

Error handling

Code
TypeScript
interface GreptileError {
  error: string
  code: string
  details?: any
}

async function safeQuery(
  question: string,
  repos: string[]
): Promise<{ success: true; data: any } | { success: false; error: GreptileError }> {
  try {
    const response = await fetch('https://api.greptile.com/v2/query', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        messages: [{ role: 'user', content: question }],
        repositories: repos.map(repo => ({
          remote: 'github',
          repository: repo,
          branch: 'main',
        })),
      }),
    })

    if (!response.ok) {
      const error = await response.json()
      return { success: false, error }
    }

    const data = await response.json()
    return { success: true, data }
  } catch (error) {
    return {
      success: false,
      error: {
        error: 'Network error',
        code: 'NETWORK_ERROR',
        details: error,
      },
    }
  }
}

const result = await safeQuery('How does auth work?', ['myorg/backend'])

if (result.success) {
  console.log(result.data.message)
} else {
  switch (result.error.code) {
    case 'RATE_LIMIT':
      console.log('Rate limited, try again later')
      break
    case 'NOT_INDEXED':
      console.log('Repository not indexed yet')
      break
    case 'UNAUTHORIZED':
      console.log('Invalid API key')
      break
    default:
      console.log('Error:', result.error.error)
  }
}

Cennik

Plany Greptile

PlanCenaQueriesReposFunkcje
Free$025/dzieΕ„3Basic API
Pro$20/mo1000/mo10+ Priority, Webhooks
Team$50/mo5000/mo50+ Team sharing, Analytics
EnterpriseCustomUnlimitedUnlimited+ Self-host, SSO, SLA

Kluczowe limity

  • Free: 25 queries/dzieΕ„, 3 repozytoria
  • Pro: ~33 queries/dzieΕ„ Ε›rednio, wiΔ™cej przy burst
  • Overage: $0.02 per dodatkowy query

Best practices

Optymalizacja queries

Code
TypeScript
// ❌ Zbyt ogólne pytanie
"What does this code do?"

// βœ… Konkretne pytanie z kontekstem
"How does the UserService handle password reset flow? Include the email template used."

Efektywne indeksowanie

Code
JSON
// .greptileignore
node_modules/
dist/
build/
*.min.js
*.map
coverage/
__tests__/
*.test.ts

Caching responses

Code
TypeScript
import { LRUCache } from 'lru-cache'

const cache = new LRUCache<string, any>({
  max: 100,
  ttl: 1000 * 60 * 15, // 15 minutes
})

async function cachedQuery(question: string, repos: string[]) {
  const cacheKey = `${question}:${repos.join(',')}`

  if (cache.has(cacheKey)) {
    return cache.get(cacheKey)
  }

  const result = await queryGreptile(question, repos)
  cache.set(cacheKey, result)
  return result
}

FAQ - NajczΔ™Ε›ciej zadawane pytania

Czym Greptile rΓ³ΕΌni siΔ™ od uΕΌywania OpenAI z wΕ‚asnym RAG?

Greptile oszczΔ™dza dziesiΔ…tki godzin setupu:

  • Automatyczne parsowanie kodu
  • Optymalne chunking dla kodu
  • Code-aware embeddings
  • Gotowy API endpoint
  • Automatyczne updates

Czy Greptile wspiera prywatne repozytoria?

Tak, wystarczy przekazać GitHub/GitLab token z odpowiednimi uprawnieniami.

Jak czΔ™sto indeks jest aktualizowany?

  • Automatycznie przez webhooks (przy kaΕΌdym push)
  • MoΕΌesz teΕΌ wymusiΔ‡ reindeksowanie przez API
  • PeΕ‚ny reindex trwa kilka minut dla Ε›rednich repo

Czy mogę użyć własnego modelu LLM?

Na Enterprise - tak. Możesz podłączyć własne modele lub użyć Greptile tylko do context retrieval.

Jaki model LLM uΕΌywa Greptile?

Domyślnie Claude (Anthropic). Na wyższych planach można wybierać między modelami.


Greptile - Codebase AI API

What is Greptile?

Greptile is an API that lets you build your own AI tools with full codebase context. Unlike ready-made tools like GitHub Copilot or Cody, Greptile provides the infrastructure and API for creating your own solutions - from chatbots through PR review bots to documentation generators.

Greptile indexes your repositories from GitHub or GitLab, creates embeddings, and exposes a simple API for searching and answering questions about code. This way you can build custom AI assistants that understand your specific project, its architecture, and conventions.

Why Greptile?

Key advantages of Greptile:

  1. Simple API - REST API, easy to integrate in a few hours
  2. Full customization - Build exactly what you need
  3. Codebase context - AI understands the entire project, not just a single file
  4. Multi-repo support - Combine knowledge from multiple repositories
  5. Privacy-first - Self-hosting option available on Enterprise
  6. Model-agnostic - Use your own LLM models or Greptile defaults
  7. Real-time updates - Automatic index updates on changes

Greptile vs alternatives

FeatureGreptileOpenAI API + RAGSourcegraph API
TypeAPI for codebaseGeneral LLM APICode search API
SetupMinutesDays/weeksHours
Codebase contextβœ… Automatic⚠️ DIYβœ… Yes
Multi-repoβœ… Yes⚠️ Manualβœ… Yes
Custom toolingβœ… Main use caseβœ… Yes⚠️ Limited
Entry costLowHigh (dev time)Medium
MaintenanceMinimalHighMedium

How does Greptile work?

Architecture

Code
TEXT
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Your application                      β”‚
β”‚  (Slack bot, CLI tool, Web app, VS Code extension)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚ REST API
                          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Greptile API                         β”‚
β”‚                                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚   Query     β”‚  β”‚   Index     β”‚  β”‚     Search      β”‚ β”‚
β”‚  β”‚  Endpoint   β”‚  β”‚  Endpoint   β”‚  β”‚    Endpoint     β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚              Codebase Index                        β”‚  β”‚
β”‚  β”‚  - Embeddings (semantic understanding)            β”‚  β”‚
β”‚  β”‚  - Code graph (dependencies, definitions)         β”‚  β”‚
β”‚  β”‚  - File relationships                             β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚
                          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  GitHub / GitLab                         β”‚
β”‚              (Your repositories)                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Indexing process

  1. Connect repo - Greptile pulls the code from GitHub/GitLab
  2. Parsing - The code is analyzed and split into chunks
  3. Embeddings - Creating vector representations for semantic search
  4. Indexing - Building an index for fast retrieval
  5. Updating - Webhooks automatically update the index

Installation and setup

Getting an API key

  1. Sign up at greptile.com
  2. Go to Dashboard β†’ API Keys
  3. Click "Create API Key"
  4. Copy the key (visible only once)

Environment variables

Code
Bash
# .env
GREPTILE_API_KEY=your_api_key_here
GITHUB_TOKEN=ghp_xxx  # Optional, for private repos

API reference

Connecting a repository

Code
TypeScript
async function connectRepository(owner: string, repo: string) {
  const response = await fetch('https://api.greptile.com/v2/repositories', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      remote: 'github',
      repository: `${owner}/${repo}`,
      branch: 'main',
    }),
  })

  if (!response.ok) {
    throw new Error(`Failed to connect repository: ${response.statusText}`)
  }

  return await response.json()
}

const result = await connectRepository('myorg', 'my-project')
console.log('Repository connected:', result)

Checking index status

Code
TypeScript
async function getIndexStatus(owner: string, repo: string) {
  const response = await fetch(
    `https://api.greptile.com/v2/repositories/${owner}/${repo}/status`,
    {
      headers: {
        'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      },
    }
  )

  return await response.json()
}

const status = await getIndexStatus('myorg', 'my-project')
console.log('Index status:', status)
// { status: 'completed', files_indexed: 1234, last_updated: '...' }

Query - asking about the codebase

Code
TypeScript
interface QueryRequest {
  messages: Array<{
    role: 'user' | 'assistant'
    content: string
  }>
  repositories: Array<{
    remote: 'github' | 'gitlab'
    repository: string
    branch: string
  }>
  sessionId?: string
  stream?: boolean
}

interface QueryResponse {
  message: string
  sources: Array<{
    repository: string
    filepath: string
    linestart: number
    lineend: number
    summary: string
  }>
}

async function queryCodebase(
  question: string,
  repos: string[]
): Promise<QueryResponse> {
  const response = await fetch('https://api.greptile.com/v2/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messages: [
        {
          role: 'user',
          content: question,
        },
      ],
      repositories: repos.map(repo => ({
        remote: 'github',
        repository: repo,
        branch: 'main',
      })),
    }),
  })

  if (!response.ok) {
    throw new Error(`Query failed: ${response.statusText}`)
  }

  return await response.json()
}

const answer = await queryCodebase(
  'How does authentication work in this codebase?',
  ['myorg/backend', 'myorg/auth-service']
)

console.log('Answer:', answer.message)
console.log('Sources:', answer.sources)

Search - searching without AI

Code
TypeScript
interface SearchRequest {
  query: string
  repositories: string[]
  options?: {
    limit?: number
    includeContent?: boolean
  }
}

interface SearchResult {
  repository: string
  filepath: string
  linestart: number
  lineend: number
  content: string
  score: number
}

async function searchCode(
  query: string,
  repos: string[]
): Promise<SearchResult[]> {
  const response = await fetch('https://api.greptile.com/v2/search', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      query,
      repositories: repos.map(repo => `github:${repo}`),
      options: {
        limit: 10,
        includeContent: true,
      },
    }),
  })

  return await response.json()
}

const results = await searchCode(
  'user authentication',
  ['myorg/backend']
)

results.forEach(result => {
  console.log(`${result.filepath}:${result.linestart}`)
  console.log(result.content)
})

Streaming responses

Code
TypeScript
async function streamQuery(question: string, repos: string[]) {
  const response = await fetch('https://api.greptile.com/v2/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messages: [{ role: 'user', content: question }],
      repositories: repos.map(repo => ({
        remote: 'github',
        repository: repo,
        branch: 'main',
      })),
      stream: true,
    }),
  })

  const reader = response.body?.getReader()
  const decoder = new TextDecoder()

  while (reader) {
    const { done, value } = await reader.read()
    if (done) break

    const chunk = decoder.decode(value)
    process.stdout.write(chunk)
  }
}

Use cases

1. Custom AI chat

Build your own chatbot that knows your codebase:

TSapp/api/chat/route.ts
TypeScript
// app/api/chat/route.ts
import { NextRequest, NextResponse } from 'next/server'

const GREPTILE_API_KEY = process.env.GREPTILE_API_KEY!
const REPOS = ['myorg/frontend', 'myorg/backend', 'myorg/docs']

export async function POST(request: NextRequest) {
  const { message, history } = await request.json()

  try {
    const response = await fetch('https://api.greptile.com/v2/query', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${GREPTILE_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        messages: [
          ...history.map((msg: any) => ({
            role: msg.role,
            content: msg.content,
          })),
          { role: 'user', content: message },
        ],
        repositories: REPOS.map(repo => ({
          remote: 'github',
          repository: repo,
          branch: 'main',
        })),
      }),
    })

    const data = await response.json()

    return NextResponse.json({
      answer: data.message,
      sources: data.sources,
    })
  } catch (error) {
    console.error('Greptile error:', error)
    return NextResponse.json(
      { error: 'Failed to process query' },
      { status: 500 }
    )
  }
}

2. PR review bot

Automatic code review for pull requests:

TSlib/pr-reviewer.ts
TypeScript
// lib/pr-reviewer.ts
import { Octokit } from '@octokit/rest'

const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })

interface PRReviewResult {
  summary: string
  issues: Array<{
    file: string
    line: number
    severity: 'error' | 'warning' | 'suggestion'
    message: string
  }>
}

async function reviewPR(
  owner: string,
  repo: string,
  prNumber: number
): Promise<PRReviewResult> {
  const { data: diff } = await octokit.pulls.get({
    owner,
    repo,
    pull_number: prNumber,
    mediaType: { format: 'diff' },
  })

  const { data: pr } = await octokit.pulls.get({
    owner,
    repo,
    pull_number: prNumber,
  })

  const response = await fetch('https://api.greptile.com/v2/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messages: [
        {
          role: 'user',
          content: `Review this pull request diff considering our codebase conventions and patterns.

PR Title: ${pr.title}
PR Description: ${pr.body || 'No description'}

Diff:
${diff}

Please identify:
1. Potential bugs or issues
2. Code style inconsistencies with our codebase
3. Missing error handling
4. Security concerns
5. Performance issues

Format each issue as:
FILE: [filename]
LINE: [line number]
SEVERITY: [error|warning|suggestion]
MESSAGE: [description]`,
        },
      ],
      repositories: [
        {
          remote: 'github',
          repository: `${owner}/${repo}`,
          branch: 'main',
        },
      ],
    }),
  })

  const data = await response.json()

  return parseReviewResponse(data.message)
}

function parseReviewResponse(response: string): PRReviewResult {
  const issues: PRReviewResult['issues'] = []
  const lines = response.split('\n')

  let currentIssue: Partial<PRReviewResult['issues'][0]> = {}

  for (const line of lines) {
    if (line.startsWith('FILE:')) {
      currentIssue.file = line.replace('FILE:', '').trim()
    } else if (line.startsWith('LINE:')) {
      currentIssue.line = parseInt(line.replace('LINE:', '').trim())
    } else if (line.startsWith('SEVERITY:')) {
      currentIssue.severity = line.replace('SEVERITY:', '').trim() as any
    } else if (line.startsWith('MESSAGE:')) {
      currentIssue.message = line.replace('MESSAGE:', '').trim()

      if (currentIssue.file && currentIssue.message) {
        issues.push(currentIssue as PRReviewResult['issues'][0])
        currentIssue = {}
      }
    }
  }

  return {
    summary: response.split('\n\n')[0] || 'Review completed',
    issues,
  }
}

// app/api/webhooks/github/route.ts
export async function POST(request: NextRequest) {
  const event = request.headers.get('x-github-event')
  const payload = await request.json()

  if (event === 'pull_request' && payload.action === 'opened') {
    const { owner, repo } = payload.repository
    const prNumber = payload.pull_request.number

    const review = await reviewPR(owner.login, repo, prNumber)

    await octokit.pulls.createReview({
      owner: owner.login,
      repo,
      pull_number: prNumber,
      body: `## AI Code Review\n\n${review.summary}`,
      event: 'COMMENT',
      comments: review.issues.map(issue => ({
        path: issue.file,
        line: issue.line,
        body: `**${issue.severity.toUpperCase()}**: ${issue.message}`,
      })),
    })
  }

  return NextResponse.json({ received: true })
}

3. Documentation generator

Automatic documentation generation from code:

TSscripts/generate-docs.ts
TypeScript
// scripts/generate-docs.ts
interface DocSection {
  title: string
  content: string
  codeExamples: string[]
}

async function generateDocumentation(
  repo: string,
  module: string
): Promise<DocSection[]> {
  const response = await fetch('https://api.greptile.com/v2/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messages: [
        {
          role: 'user',
          content: `Generate comprehensive documentation for the ${module} module.

Include:
1. Overview - what this module does
2. Installation/Setup - how to set it up
3. API Reference - all public functions/classes with:
   - Description
   - Parameters
   - Return values
   - Example usage
4. Best Practices - recommended patterns
5. Common Issues - troubleshooting

Format as markdown with code examples.`,
        },
      ],
      repositories: [
        {
          remote: 'github',
          repository: repo,
          branch: 'main',
        },
      ],
    }),
  })

  const data = await response.json()

  return parseMarkdownSections(data.message)
}

async function main() {
  const docs = await generateDocumentation(
    'myorg/backend',
    'authentication'
  )

  const markdown = docs
    .map(section => `## ${section.title}\n\n${section.content}`)
    .join('\n\n')

  fs.writeFileSync('docs/authentication.md', markdown)
}

4. Slack bot

A Slack bot that answers questions about the code:

TSapp/api/slack/route.ts
TypeScript
// app/api/slack/route.ts
import { NextRequest, NextResponse } from 'next/server'
import crypto from 'crypto'

const SLACK_SIGNING_SECRET = process.env.SLACK_SIGNING_SECRET!
const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN!
const GREPTILE_API_KEY = process.env.GREPTILE_API_KEY!

function verifySlackSignature(
  signature: string,
  timestamp: string,
  body: string
): boolean {
  const sigBasestring = `v0:${timestamp}:${body}`
  const mySignature = 'v0=' + crypto
    .createHmac('sha256', SLACK_SIGNING_SECRET)
    .update(sigBasestring)
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(mySignature),
    Buffer.from(signature)
  )
}

export async function POST(request: NextRequest) {
  const body = await request.text()
  const timestamp = request.headers.get('x-slack-request-timestamp')!
  const signature = request.headers.get('x-slack-signature')!

  if (!verifySlackSignature(signature, timestamp, body)) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
  }

  const payload = JSON.parse(body)

  if (payload.type === 'url_verification') {
    return NextResponse.json({ challenge: payload.challenge })
  }

  if (payload.event?.type === 'app_mention') {
    const { text, channel, thread_ts, ts } = payload.event
    const question = text.replace(/<@[^>]+>/g, '').trim()

    await handleQuestion(channel, thread_ts || ts, question)
  }

  return NextResponse.json({ ok: true })
}

async function handleQuestion(
  channel: string,
  threadTs: string,
  question: string
) {
  const response = await fetch('https://api.greptile.com/v2/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messages: [{ role: 'user', content: question }],
      repositories: [
        { remote: 'github', repository: 'myorg/backend', branch: 'main' },
        { remote: 'github', repository: 'myorg/frontend', branch: 'main' },
      ],
    }),
  })

  const data = await response.json()

  const sources = data.sources
    .slice(0, 3)
    .map((s: any) => `β€’ \`${s.filepath}:${s.linestart}\``)
    .join('\n')

  await fetch('https://slack.com/api/chat.postMessage', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${SLACK_BOT_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      channel,
      thread_ts: threadTs,
      text: data.message,
      blocks: [
        {
          type: 'section',
          text: { type: 'mrkdwn', text: data.message },
        },
        {
          type: 'context',
          elements: [
            {
              type: 'mrkdwn',
              text: `*Sources:*\n${sources}`,
            },
          ],
        },
      ],
    }),
  })
}

5. VS Code extension

A custom VS Code extension with Greptile:

TSextension/src/extension.ts
TypeScript
// extension/src/extension.ts
import * as vscode from 'vscode'

const GREPTILE_API_KEY = process.env.GREPTILE_API_KEY!

export function activate(context: vscode.ExtensionContext) {
  const explainCommand = vscode.commands.registerCommand(
    'greptile.explain',
    async () => {
      const editor = vscode.window.activeTextEditor
      if (!editor) return

      const selection = editor.selection
      const selectedText = editor.document.getText(selection)

      if (!selectedText) {
        vscode.window.showWarningMessage('Please select some code first')
        return
      }

      const answer = await queryGreptile(
        `Explain this code in the context of our codebase:\n\n${selectedText}`
      )

      const panel = vscode.window.createWebviewPanel(
        'greptileExplanation',
        'Code Explanation',
        vscode.ViewColumn.Beside,
        {}
      )

      panel.webview.html = `
        <html>
          <body>
            <h2>Explanation</h2>
            <pre>${answer.message}</pre>
            <h3>Related files</h3>
            <ul>
              ${answer.sources.map((s: any) =>
                `<li>${s.filepath}:${s.linestart}</li>`
              ).join('')}
            </ul>
          </body>
        </html>
      `
    }
  )

  const askCommand = vscode.commands.registerCommand(
    'greptile.ask',
    async () => {
      const question = await vscode.window.showInputBox({
        prompt: 'Ask about the codebase',
        placeHolder: 'How does authentication work?',
      })

      if (!question) return

      const answer = await queryGreptile(question)

      vscode.window.showInformationMessage(
        answer.message.substring(0, 200) + '...'
      )
    }
  )

  context.subscriptions.push(explainCommand, askCommand)
}

async function queryGreptile(question: string) {
  const workspaceFolders = vscode.workspace.workspaceFolders || []
  const repos = workspaceFolders.map(folder => {
    return getGitRemote(folder.uri.fsPath)
  }).filter(Boolean)

  const response = await fetch('https://api.greptile.com/v2/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${GREPTILE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messages: [{ role: 'user', content: question }],
      repositories: repos.map(repo => ({
        remote: 'github',
        repository: repo,
        branch: 'main',
      })),
    }),
  })

  return await response.json()
}

6. CLI tool

A custom CLI tool:

Code
TypeScript
#!/usr/bin/env node
// cli/greptile-cli.ts

import { Command } from 'commander'
import inquirer from 'inquirer'
import chalk from 'chalk'
import ora from 'ora'

const program = new Command()

program
  .name('greptile')
  .description('Query your codebase using AI')
  .version('1.0.0')

program
  .command('ask <question>')
  .description('Ask a question about the codebase')
  .option('-r, --repo <repos...>', 'Repositories to search')
  .action(async (question, options) => {
    const spinner = ora('Querying codebase...').start()

    try {
      const response = await fetch('https://api.greptile.com/v2/query', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          messages: [{ role: 'user', content: question }],
          repositories: options.repo.map((repo: string) => ({
            remote: 'github',
            repository: repo,
            branch: 'main',
          })),
        }),
      })

      const data = await response.json()
      spinner.stop()

      console.log('\n' + chalk.green('Answer:'))
      console.log(data.message)

      console.log('\n' + chalk.blue('Sources:'))
      data.sources.forEach((source: any) => {
        console.log(chalk.gray(`  ${source.filepath}:${source.linestart}`))
      })
    } catch (error) {
      spinner.fail('Query failed')
      console.error(error)
    }
  })

program
  .command('chat')
  .description('Start an interactive chat session')
  .option('-r, --repo <repos...>', 'Repositories to search')
  .action(async (options) => {
    const history: any[] = []

    console.log(chalk.blue('Starting Greptile chat. Type "exit" to quit.\n'))

    while (true) {
      const { question } = await inquirer.prompt([
        {
          type: 'input',
          name: 'question',
          message: chalk.green('You:'),
        },
      ])

      if (question.toLowerCase() === 'exit') break

      const spinner = ora('Thinking...').start()

      const response = await fetch('https://api.greptile.com/v2/query', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          messages: [
            ...history,
            { role: 'user', content: question },
          ],
          repositories: options.repo.map((repo: string) => ({
            remote: 'github',
            repository: repo,
            branch: 'main',
          })),
        }),
      })

      const data = await response.json()
      spinner.stop()

      history.push({ role: 'user', content: question })
      history.push({ role: 'assistant', content: data.message })

      console.log(chalk.yellow('\nGreptile:'))
      console.log(data.message + '\n')
    }
  })

program
  .command('search <query>')
  .description('Search for code (without AI)')
  .option('-r, --repo <repos...>', 'Repositories to search')
  .option('-n, --limit <number>', 'Max results', '10')
  .action(async (query, options) => {
    const spinner = ora('Searching...').start()

    try {
      const response = await fetch('https://api.greptile.com/v2/search', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          query,
          repositories: options.repo.map((r: string) => `github:${r}`),
          options: { limit: parseInt(options.limit) },
        }),
      })

      const results = await response.json()
      spinner.stop()

      results.forEach((result: any, i: number) => {
        console.log(chalk.blue(`\n${i + 1}. ${result.filepath}:${result.linestart}`))
        console.log(chalk.gray(result.content))
      })
    } catch (error) {
      spinner.fail('Search failed')
      console.error(error)
    }
  })

program.parse()

Integrations

GitHub App

TSapp/api/github/install/route.ts
TypeScript
// app/api/github/install/route.ts

export async function POST(request: NextRequest) {
  const { action, repositories, installation } = await request.json()

  if (action === 'created' || action === 'added') {
    for (const repo of repositories) {
      await fetch('https://api.greptile.com/v2/repositories', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          remote: 'github',
          repository: repo.full_name,
          branch: 'main',
          githubInstallationId: installation.id,
        }),
      })
    }
  }

  return NextResponse.json({ ok: true })
}

Webhooks for real-time updates

TSapp/api/webhooks/github/push/route.ts
TypeScript
// app/api/webhooks/github/push/route.ts

export async function POST(request: NextRequest) {
  const { repository, ref } = await request.json()

  if (ref !== 'refs/heads/main') {
    return NextResponse.json({ skipped: true })
  }

  await fetch(
    `https://api.greptile.com/v2/repositories/${repository.full_name}/reindex`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
      },
    }
  )

  return NextResponse.json({ reindexed: true })
}

Error handling

Code
TypeScript
interface GreptileError {
  error: string
  code: string
  details?: any
}

async function safeQuery(
  question: string,
  repos: string[]
): Promise<{ success: true; data: any } | { success: false; error: GreptileError }> {
  try {
    const response = await fetch('https://api.greptile.com/v2/query', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.GREPTILE_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        messages: [{ role: 'user', content: question }],
        repositories: repos.map(repo => ({
          remote: 'github',
          repository: repo,
          branch: 'main',
        })),
      }),
    })

    if (!response.ok) {
      const error = await response.json()
      return { success: false, error }
    }

    const data = await response.json()
    return { success: true, data }
  } catch (error) {
    return {
      success: false,
      error: {
        error: 'Network error',
        code: 'NETWORK_ERROR',
        details: error,
      },
    }
  }
}

const result = await safeQuery('How does auth work?', ['myorg/backend'])

if (result.success) {
  console.log(result.data.message)
} else {
  switch (result.error.code) {
    case 'RATE_LIMIT':
      console.log('Rate limited, try again later')
      break
    case 'NOT_INDEXED':
      console.log('Repository not indexed yet')
      break
    case 'UNAUTHORIZED':
      console.log('Invalid API key')
      break
    default:
      console.log('Error:', result.error.error)
  }
}

Pricing

Greptile plans

PlanPriceQueriesReposFeatures
Free$025/day3Basic API
Pro$20/mo1000/mo10+ Priority, Webhooks
Team$50/mo5000/mo50+ Team sharing, Analytics
EnterpriseCustomUnlimitedUnlimited+ Self-host, SSO, SLA

Key limits

  • Free: 25 queries/day, 3 repositories
  • Pro: ~33 queries/day on average, more with burst
  • Overage: $0.02 per additional query

Best practices

Optimizing queries

Code
TypeScript
// ❌ Too general question
"What does this code do?"

// βœ… Specific question with context
"How does the UserService handle password reset flow? Include the email template used."

Efficient indexing

Code
JSON
// .greptileignore
node_modules/
dist/
build/
*.min.js
*.map
coverage/
__tests__/
*.test.ts

Caching responses

Code
TypeScript
import { LRUCache } from 'lru-cache'

const cache = new LRUCache<string, any>({
  max: 100,
  ttl: 1000 * 60 * 15, // 15 minutes
})

async function cachedQuery(question: string, repos: string[]) {
  const cacheKey = `${question}:${repos.join(',')}`

  if (cache.has(cacheKey)) {
    return cache.get(cacheKey)
  }

  const result = await queryGreptile(question, repos)
  cache.set(cacheKey, result)
  return result
}

FAQ - frequently asked questions

How is Greptile different from using OpenAI with your own RAG?

Greptile saves dozens of hours of setup:

  • Automatic code parsing
  • Optimal chunking for code
  • Code-aware embeddings
  • Ready-to-use API endpoint
  • Automatic updates

Does Greptile support private repositories?

Yes, you just need to provide a GitHub/GitLab token with the appropriate permissions.

How often is the index updated?

  • Automatically via webhooks (on every push)
  • You can also force reindexing through the API
  • A full reindex takes a few minutes for medium-sized repos

Can I use my own LLM model?

On Enterprise - yes. You can connect your own models or use Greptile solely for context retrieval.

What LLM model does Greptile use?

Claude (Anthropic) by default. On higher plans you can choose between models.