Self-Debugging QA Agent — Universal bug reporting widget with AI-powered analysis
English | 한국어
npm install inner-lens
# or
yarn add inner-lens
# or
pnpm add inner-lens
# Optional: With Session Replay (see below)
npm install inner-lens [email protected]
# or
yarn add inner-lens [email protected]
# or
pnpm add inner-lens [email protected]npx inner-lens initOr manually:
React / Next.js:
- Configure build to inject git branch:
// next.config.js
const { getGitBranch } = require('inner-lens/build');
module.exports = {
env: {
NEXT_PUBLIC_GIT_BRANCH: getGitBranch(),
},
};- Add the widget:
import { InnerLensWidget } from 'inner-lens/react';
export default function App() {
return (
<>
<YourApp />
<InnerLensWidget
mode="hosted"
repository="your-org/your-repo"
branch={process.env.NEXT_PUBLIC_GIT_BRANCH}
/>
</>
);
}Vue 3 (Vite):
- Configure build:
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { getGitBranch } from 'inner-lens/build';
export default defineConfig({
plugins: [vue()],
define: {
'import.meta.env.VITE_GIT_BRANCH': JSON.stringify(getGitBranch()),
},
});- Add the widget:
<script setup>
import { InnerLensWidget } from 'inner-lens/vue';
</script>
<template>
<YourApp />
<InnerLensWidget
mode="hosted"
repository="your-org/your-repo"
:branch="import.meta.env.VITE_GIT_BRANCH"
/>
</template>Vanilla JS (Vite):
- Configure build (same as Vue)
- Add the widget:
import { InnerLens } from 'inner-lens/vanilla';
const widget = new InnerLens({
mode: 'hosted',
repository: 'your-org/your-repo',
branch: import.meta.env.VITE_GIT_BRANCH,
});
widget.mount();Note: The
branchprop tells the AI analysis engine which code version to analyze. If you only deploy frommain, you can omit it (defaults tomain). ThegetGitBranch()utility auto-detects branch from CI/CD environment variables (Vercel, Netlify, AWS Amplify, Cloudflare Pages, Render, Railway, GitHub Actions, Heroku).
Bug reports like "it doesn't work" waste hours of debugging time.
| Without inner-lens | With inner-lens |
|---|---|
| "The button doesn't work" | Console logs, network errors, DOM state, session replay |
| Hours of back-and-forth | One-click bug reports with full context |
| Manual log collection | Automatic capture with PII masking |
| Guessing what happened | AI-powered root cause analysis |
User clicks "Report Bug"
↓
Widget captures context (logs, actions, performance, DOM)
↓
GitHub Issue created with full context
↓
AI analyzes code & identifies root cause
↓
Analysis posted as comment with fix suggestions
| Hosted (Recommended) | Self-Hosted | |
|---|---|---|
| Setup Time | 2 minutes | 10 minutes |
| Backend Required | No | Yes |
| Issue Author | inner-lens-app[bot] |
Your GitHub account |
| Rate Limit | 10 req/min/IP + 100 req/day per repo | None |
- Install GitHub App
- Add widget with
mode="hosted"andrepositoryprop
<InnerLensWidget mode="hosted" repository="owner/repo" />- Create GitHub Token
- Add backend handler:
// Next.js App Router
import { createFetchHandler } from 'inner-lens/server';
export const POST = createFetchHandler({
githubToken: process.env.GITHUB_TOKEN!,
repository: 'owner/repo',
});- Add widget with
mode="self-hosted"andendpointprop:
<InnerLensWidget
mode="self-hosted"
endpoint="/api/inner-lens/report"
repository="owner/repo"
/>For external API servers (Cloudflare Workers, separate domain, etc.), use fullUrl:
<InnerLensWidget
mode="self-hosted"
fullUrl="https://api.example.com/inner-lens/report"
repository="owner/repo"
/>Other frameworks (Express, Fastify, Hono, Koa...)
// Express
import { createExpressHandler } from 'inner-lens/server';
app.post('/api/report', createExpressHandler({ githubToken, repository }));
// Fastify
import { createFastifyHandler } from 'inner-lens/server';
fastify.post('/api/report', createFastifyHandler({ githubToken, repository }));
// Hono / Bun / Deno
import { createFetchHandler } from 'inner-lens/server';
app.post('/api/report', (c) => createFetchHandler({ githubToken, repository })(c.req.raw));
// Koa
import { createKoaHandler } from 'inner-lens/server';
router.post('/api/report', createKoaHandler({ githubToken, repository }));
// Node.js HTTP
import { createNodeHandler } from 'inner-lens/server';
const handler = createNodeHandler({ githubToken, repository });Enable AI-powered bug analysis with GitHub Actions.
# .github/workflows/inner-lens.yml
name: inner-lens Analysis
on:
issues:
types: [opened]
jobs:
analyze:
if: contains(github.event.issue.labels.*.name, 'inner-lens')
uses: jhlee0409/inner-lens/.github/workflows/analysis-engine.yml@v1
with:
provider: 'anthropic' # or 'openai', 'google'
secrets:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}| Provider | Model (example) | Secret | Notes |
|---|---|---|---|
| Anthropic | claude-sonnet-4-5-20250929 | ANTHROPIC_API_KEY |
model is optional; omit to use provider default |
| OpenAI | gpt-4o | OPENAI_API_KEY |
model is optional; omit to use provider default |
| gemini-2.5-flash | GOOGLE_GENERATIVE_AI_API_KEY |
model is optional; omit to use provider default |
| Option | Required | Type | Default | Description |
|---|---|---|---|---|
provider |
No | string |
anthropic |
AI provider (anthropic, openai, google) |
model |
No | string |
'' |
Optional model name (e.g., claude-sonnet-4-20250514); empty string uses provider default |
language |
No | string |
en |
Analysis output language (en, ko, ja, zh, es, de, fr, pt) |
max_files |
No | number |
25 |
Maximum files to analyze (5-50) |
Secrets (required based on provider):
| Secret | Required |
|---|---|
ANTHROPIC_API_KEY |
When provider: 'anthropic' |
OPENAI_API_KEY |
When provider: 'openai' |
GOOGLE_GENERATIVE_AI_API_KEY |
When provider: 'google' |
Example with all options
jobs:
analyze:
if: contains(github.event.issue.labels.*.name, 'inner-lens')
uses: jhlee0409/inner-lens/.github/workflows/analysis-engine.yml@v1
with:
provider: 'anthropic'
model: 'claude-sonnet-4-20250514'
language: 'ko'
max_files: 30
secrets:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}Record DOM changes to visually replay what the user experienced.
Why use it? See exactly what the user saw — clicks, scrolls, and UI changes — without asking them to reproduce the issue.
When to use it? Complex UI bugs that are hard to reproduce from logs alone.
Note: Adds ~500KB to your bundle. Only enable if needed.
npm install [email protected]<InnerLensWidget mode="hosted" repository="owner/repo" captureSessionReplay={true} />Migration from v0.4.7 or earlier: The
modeprop is now required.
- If using hosted API: add
mode="hosted"- If using custom endpoint: add
mode="self-hosted"
| Option | Type | Default | Description |
|---|---|---|---|
mode |
'hosted' | 'self-hosted' |
required | API mode (see below) |
repository |
string |
- | GitHub repo (owner/repo) |
endpoint |
string |
- | Relative backend URL (self-hosted only) |
fullUrl |
string |
- | Absolute backend URL (self-hosted only) |
branch |
string |
- | Git branch for AI analysis |
language |
string |
en |
UI language (en, ko, ja, zh, es) |
position |
string |
bottom-right |
Button position |
buttonColor |
string |
#6366f1 |
Button color |
buttonSize |
sm|md|lg |
lg |
Trigger button size |
styles |
{ buttonColor?, buttonPosition?, buttonSize? } |
- | Advanced style config (overrides position/color/size) |
hidden |
boolean |
false |
Hide widget |
disabled |
boolean |
false |
Disable widget |
captureSessionReplay |
boolean |
false |
Enable DOM recording |
reporter |
object |
- | User info { name, email?, id? } |
All options
| Option | Type | Default |
|---|---|---|
labels |
string[] |
['inner-lens'] |
captureConsoleLogs |
boolean |
true |
maxLogEntries |
number |
50 |
maskSensitiveData |
boolean |
true |
captureUserActions |
boolean |
true |
captureNavigation |
boolean |
true |
capturePerformance |
boolean |
true |
captureSessionReplay |
boolean |
false |
styles |
{ buttonColor?, buttonPosition?, buttonSize? } |
- |
buttonSize |
sm|md|lg |
lg |
buttonText |
string |
i18n |
dialogTitle |
string |
i18n |
dialogDescription |
string |
i18n |
submitText |
string |
i18n |
cancelText |
string |
i18n |
successMessage |
string |
i18n |
trigger |
ReactNode |
- |
onOpen |
() => void |
- |
onClose |
() => void |
- |
onSuccess |
(url) => void |
- |
onError |
(error) => void |
- |
┌─────────────────────────────────────────────────────────────────────────┐
│ Your Application (Browser) │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────────────┐ │
│ │ User clicks │───►│ Widget │───►│ Client-side Masking │ │
│ │ Report Bug │ │ captures │ │ (30 patterns applied) │ │
│ └─────────────┘ │ context │ │ • Emails → [EMAIL_REDACTED] │ │
│ └──────────────┘ │ • API keys → [*_REDACTED] │ │
│ │ • Tokens → [TOKEN_REDACTED] │ │
│ └──────────────┬──────────────┘ │
└────────────────────────────────────────────────────────┼────────────────┘
│
▼
┌───────────────────────────────────────────┐
│ inner-lens API (Pass-through) │
│ • No data storage │
│ • No logging of report content │
│ • Rate limiting only │
└───────────────────────┬───────────────────┘
│
▼
┌───────────────────────────────────────────┐
│ GitHub Issues API │
│ • Issue created in YOUR repository │
│ • Data stored only in GitHub │
│ • Access controlled by your repo perms │
└───────────────────────────────────────────┘
| Data Type | Purpose | Privacy Notes |
|---|---|---|
| Console logs | Debug errors & warnings | Auto-masked for sensitive content |
| User actions | Understand user journey | Generic selectors only (no personal text) |
| Navigation history | Track page flow | URLs only (query params masked) |
| Browser & OS info | Environment debugging | Summarized (e.g., "Chrome 120", "Windows 10") |
| Performance metrics | Identify slow operations | Timing data only (LCP, FCP, etc.) |
| DOM snapshot (opt-in) | Visual debugging | Session replay disabled by default |
- ❌ IP addresses — Not logged or stored
- ❌ Cookies — Never accessed
- ❌ localStorage/sessionStorage — Never read
- ❌ Geolocation — Not requested
- ❌ Device fingerprints — No unique identifiers
- ❌ Form field values — Input content excluded
- ❌ Full User Agent string — Only browser/OS summary
Sensitive data is masked client-side, before transmission:
| Category | Replacement |
|---|---|
| Email, Phone, SSN | [EMAIL_REDACTED], [PHONE_REDACTED], [SSN_REDACTED] |
| Credit cards | [CARD_REDACTED] |
| Auth tokens, JWTs | [TOKEN_REDACTED], [JWT_REDACTED] |
| API keys (AWS, OpenAI, Anthropic, Google, Stripe, GitHub) | [*_KEY_REDACTED] |
| Database URLs, Private keys | [DATABASE_URL_REDACTED], [PRIVATE_KEY_REDACTED] |
| Discord webhooks, Slack tokens | [DISCORD_WEBHOOK_REDACTED], [SLACK_TOKEN_REDACTED] |
| NPM, SendGrid, Twilio | [NPM_TOKEN_REDACTED], [SENDGRID_KEY_REDACTED], [TWILIO_REDACTED] |
- Client-side first — All masking happens in the browser before data leaves
- No data retention — Hosted API is a pass-through; no logs stored
- User-initiated only — Reports sent only when user clicks submit
- Minimal collection — Only debugging-relevant data captured
- Transparent storage — Data goes to YOUR GitHub repo, nowhere else
| Package | Exports |
|---|---|
inner-lens/react |
InnerLensWidget, useInnerLens |
inner-lens/vue |
InnerLensWidget, useInnerLens |
inner-lens/vanilla |
InnerLens |
| Export | Frameworks |
|---|---|
createFetchHandler |
Next.js, Hono, Bun, Deno, Cloudflare |
createExpressHandler |
Express |
createFastifyHandler |
Fastify |
createKoaHandler |
Koa |
createNodeHandler |
Node.js HTTP |
handleBugReport |
Any |
- Masking:
maskSensitiveData,maskSensitiveObject,validateMasking - Log capture:
initLogCapture,getCapturedLogs,clearCapturedLogs,addCustomLog,restoreConsole(available frominner-lens/vue)
How do I use it with Next.js?
The widget uses browser APIs, so it must run as a client component.
// App Router: Add 'use client' directive
'use client';
import { InnerLensWidget } from 'inner-lens/react';
function BugReportWidget() {
return <InnerLensWidget mode="hosted" repository="owner/repo" />;
}
// Pages Router: Use dynamic import
import dynamic from 'next/dynamic';
const InnerLensWidget = dynamic(
() => import('inner-lens/react').then(m => m.InnerLensWidget),
{ ssr: false }
);Is my data safe?
Sensitive data (emails, API keys, tokens, etc.) is automatically masked before leaving the browser. In Hosted mode, we don't store any data — it goes directly to GitHub.
Widget doesn't appear?
- Check if
hidden={true}prop is set - Verify import path is
'inner-lens/react'(or/vue,/vanilla) - Check browser console (F12) for error messages
Browser compatibility?
inner-lens targets ES2022 and works with modern browsers:
- Chrome 94+
- Firefox 93+
- Safari 15.4+
- Edge 94+
Node.js 20+ is required for server-side features.
Contributions welcome! See CONTRIBUTING.md.
