Select → Slice → Pack → Count → Copy
Ask smart questions across selected GitHub repos with a gorgeous, zero-bloat web app. Powered by Repomix for slicing/packing and Gemini for token accounting/answers.
- Node.js 18+ (20+ recommended)
- npm or pnpm
- GitHub Personal Access Token (create one)
- Gemini API Key (create one)
npm installCopy the example env file and add your keys:
cp .env.local.example .env.localEdit .env.local and add your tokens:
GITHUB_TOKEN=ghp_your_token_here
GEMINI_API_KEY=your_gemini_key_hereNote: See
.env.local.examplefor detailed instructions on GitHub token types (classic vs fine-grained).
npm run devOpen http://localhost:3000 in your browser.
npm run test:localThis runs Repomix against a local fixture repo to verify packing works.
- Framework: Next.js 14 (App Router)
- Runtime: Node.js (for repomix library dependencies)
- Styling: Tailwind CSS
- Packing: Repomix (CLI tool)
- Token Counting: Google Gemini API
- Repo Listing: Octokit (GitHub REST API)
vana-query/
├── app/
│ ├── page.tsx # Main UI
│ ├── layout.tsx # Root layout
│ ├── globals.css # Tailwind styles
│ └── api/
│ ├── repos/route.ts # GET /api/repos - List org/user repos
│ ├── pack/route.ts # POST /api/pack - Pack repos with Repomix
│ └── tokens/route.ts # POST /api/tokens - Count tokens with Gemini
├── lib/
│ ├── types.ts # Type definitions (SSOT)
│ ├── config.ts # App configuration
│ ├── github.ts # GitHub API client (Octokit wrapper)
│ ├── repomix.ts # Repomix CLI wrapper
│ └── gemini.ts # Gemini API client
├── components/ # (Future: extract UI components)
├── test/
│ ├── fixtures/ # Test data
│ ├── local-test.ts # Local integration test
│ └── local-runner.js # Test runner
├── .env.local.example # Environment template
└── README.md # This file
┌─────────────────────────────────────────────────────────────┐
│ User enters org name, GitHub token, Gemini key │
└─────────────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ GET /api/repos?org=acme │
│ • Octokit lists repos │
│ • Returns: name, branch, size, last updated │
└─────────────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ User selects repos, configures globs, enters prompt │
└─────────────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ POST /api/pack │
│ • For each repo: packRemoteRepo (runRemoteAction) │
│ • Assemble outputs into single context │
│ • Return: combined text + stats │
└─────────────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ POST /api/tokens │
│ • Call Gemini countTokens API │
│ • Return: total tokens, limit, status (under/near/over) │
└─────────────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ User copies to clipboard or downloads .txt │
└─────────────────────────────────────────────────────────────┘
List repositories for an organization or user.
Headers:
X-GitHub-Token: Your GitHub PAT
Query Params:
org(required): Organization or usernametype(optional):orgoruser(default:org)
Response:
{
"success": true,
"data": [
{
"name": "repo-name",
"fullName": "org/repo-name",
"defaultBranch": "main",
"pushedAt": "2025-01-15T10:30:00Z",
"size": 12345,
"private": false,
"description": "A cool repo"
}
]
}Pack multiple repositories using Repomix.
Headers:
X-GitHub-Token: Your GitHub PAT
Body:
{
"repos": [
{ "fullName": "org/repo-a", "branch": "main" },
{ "fullName": "org/repo-b" }
],
"sliceConfig": {
"includeGlobs": ["**/*.ts", "**/*.tsx"],
"ignoreGlobs": ["**/*.test.ts"],
"respectGitignore": true,
"useDefaultPatterns": true,
"reducers": {
"compress": false,
"removeComments": false,
"removeEmptyLines": false
}
},
"userPrompt": "Explain the authentication flow"
}Response:
{
"success": true,
"data": {
"repos": [
{
"repo": "org/repo-a",
"branch": "main",
"output": "... packed content ...",
"stats": {
"fileCount": 42,
"approxChars": 125000,
"approxTokens": 31250
}
}
],
"combined": {
"output": "... assembled context ...",
"totalChars": 125000,
"totalTokens": 31250
},
"errors": []
}
}Count tokens using Gemini API (authoritative).
Headers:
X-Gemini-Key: Your Gemini API key
Body:
{
"modelId": "gemini-1.5-flash",
"contextText": "... your packed context ...",
"userPrompt": "Optional user prompt"
}Response:
{
"success": true,
"data": {
"totalTokens": 31458,
"contextTokens": 31250,
"promptTokens": 208,
"modelLimit": 1000000,
"status": "under"
}
}Status Values:
under: < 70% of model limit (green)near: 70-95% of model limit (amber)over: > 95% of model limit (red)
Controlled via lib/config.ts:
- Timeout: 60s per repo (adjust for large repos)
- Max file size: 1MB per file
- Max total size: 50MB per repo
Supported models (configured in lib/config.ts):
gemini-1.5-flash: 1M token limitgemini-1.5-pro: 2M token limitgemini-2.0-flash-exp: 1M token limit
Vana Source Query automatically respects AI ignore files in repository roots. This allows repo owners to exclude sensitive files, build artifacts, or noise from LLM context.
Supported formats (all use gitignore syntax):
.aiignore— Emerging industry standard (JetBrains, proposed universal format).aiexclude— Google Gemini Code Assist.cursorignore— Cursor IDE.codeiumignore— Codeium.agentignore— Generic format.geminiignore— Google Gemini
If multiple files exist, patterns are merged and deduplicated.
Example .aiignore:
# Security - exclude all environment files
.env*
*.key
*.pem
credentials/
# Build artifacts - reduce noise
dist/
build/
*.min.js
# Testing - not relevant for code analysis
**/*.test.ts
**/*.spec.ts
coverage/
# Vendored code - already documented elsewhere
vendor/
third_party/
node_modules/
Priority Order (highest to lowest):
- User-specified
ignoreGlobs(UI input) - AI ignore file patterns (repo owner intent)
.gitignorepatterns (ifrespectGitignoreenabled)- Repomix default patterns
Syntax: Standard gitignore patterns (same as .gitignore):
**/*.test.ts- Match all test files recursivelydist/- Match directory*.key- Match by extension# comment- Comments start with#
Security Note: Even if secrets are accidentally committed to git, AI ignore files provide defense-in-depth by preventing them from being sent to LLMs.
npm run test:localThis:
- Packs
test/fixtures/sample-repousing Repomix - Tests include/ignore globs
- Assembles context with user prompt
- Verifies output format
The local test uses Repomix in local mode (no GitHub/Gemini calls). Useful for:
- Verifying Repomix installation
- Testing glob patterns
- Debugging packing logic
-
Install Vercel CLI:
npm install -g vercel
-
Deploy:
vercel
-
Add Environment Variables in Vercel dashboard:
GITHUB_TOKEN(optional: fallback if user doesn't provide token)GEMINI_API_KEY(optional: fallback)
-
Configure Function Settings:
- Runtime: Node.js (NOT Edge)
- Max duration: 60s (Pro plan) or 300s (Enterprise)
Any platform supporting:
- Node.js runtime
- Writable
/tmpdirectory (for temp output files) - 60s+ execution timeout
Examples: Railway, Render, Fly.io, Cloud Run
Cause: Large repo or slow GitHub API response.
Solutions:
- Add more specific
includeGlobsto reduce file count - Increase timeout in
lib/config.ts - Use a smaller subset of repos
Cause: Hit GitHub API rate limit (5000/hr for authenticated).
Solutions:
- Wait for rate limit reset (check error message for time)
- Use fine-grained token with fewer repos
- Reduce number of parallel packing operations
Cause: Token is invalid, expired, or lacks permissions.
Solutions:
- Create new token at https://github.com/settings/tokens/new
- Ensure
reposcope (classic) orContents: Read(fine-grained) - For orgs, ensure token has org access
Solution: Copy the packed context anyway (button remains enabled) and paste into:
- Gemini AI Studio (may support larger contexts)
- Claude (3M+ token limit on some models)
- GPT-4 (128k+ token limit)
- No complex abstractions: Plain functions, clear data flow
- Data > objects: JSON in/out, no classes where functions suffice
- Explicit > implicit: All config visible, no magic defaults
- Authoritative token counts: Use Gemini's API, not estimates
- Fail fast: Timeouts, validation, clear errors
- Observable: Console logs at every step
- Ship MVP first: Core flow works end-to-end
- Iterate: Add features based on real usage
- Test early: Local test path ensures packing works
- Validate assumptions: Spike Repomix in serverless first
- Minimal viable product: No auth, no DB, no complexity
- Measure: Add telemetry later based on user feedback
- In-app Chat: Stream responses from Gemini with packed context
- Branch Selector: Choose branch per repo
- Submodule Detection: Detect and warn about submodules
- Reducers UI: Enable compress, remove comments, remove empty lines
- OAuth: GitHub OAuth instead of PAT
- Caching: Edge cache for repeated packs (5-10min TTL)
- User Presets: Save slice configs per org
- Audit Logs: Track pack history (requires DB)
- Collaborative Sessions: Share pack results via URL
This is a lean startup project. Contributions welcome, but keep it simple:
- Fork and clone
- Run tests:
npm run test:local - Make changes: Follow existing patterns
- Test end-to-end: Run
npm run devand verify UI works - Submit PR: Clear description, no breaking changes
MIT
- Repomix: https://github.com/yamadashy/repomix
- Octokit: https://github.com/octokit/octokit.js
- Google Gemini: https://ai.google.dev/
Built with ❤️ by a staff engineer from Stripe, now at a lean startup.
Inspired by Rich Hickey (simplicity), John Carmack (guarantees), and Uncle Bob (clean code).