Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds
Torna alle collezioni
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.