Adapters

Adapters connect anyclick to external services. Use pre-built adapters for GitHub and Cursor, or implement the adapter interface for custom integrations.

Available Adapters

@ewjdev/anyclick-github

Create GitHub Issues with rich context, formatted markdown, and embedded screenshots.

@ewjdev/anyclick-jira

Create Jira issues with Atlassian Document Format descriptions and automatic screenshot attachments.

@ewjdev/anyclick-cursor

Launch Cursor's Cloud Agent to automatically fix issues from feedback.

@ewjdev/anyclick-cursor-local

Run cursor-agent locally during development for instant code fixes.

GitHub Adapter

The GitHub adapter creates Issues in your repository with full context, labels, and embedded screenshots.

Installation

npm install @ewjdev/anyclick-github

Browser-Side Setup

Use the HTTP adapter to send feedback to your API endpoint:

app/providers.tsx
'use client';
import { FeedbackProvider } from '@ewjdev/anyclick-react';
import { createHttpAdapter } from '@ewjdev/anyclick-github';
const adapter = createHttpAdapter({
endpoint: '/api/feedback',
// Optional: custom headers
headers: {
'X-Custom-Header': 'value',
},
});
export function Providers({ children }) {
return (
<FeedbackProvider adapter={adapter}>
{children}
</FeedbackProvider>
);
}

Server-Side Setup

Create an API route that receives feedback and creates GitHub Issues:

app/api/feedback/route.ts
import { createGitHubAdapter, formatFeedbackAsMarkdown } from '@ewjdev/anyclick-github/server';
import type { FeedbackPayload } from '@ewjdev/anyclick-core';
const repoName = process.env.GITHUB_REPO!;
const [owner, repo] = repoName.split("/");
const github = createGitHubAdapter({
owner,
repo,
token: process.env.GITHUB_TOKEN!,
// Optional: customize labels based on feedback type
getLabels: (payload) => {
const labels = ['feedback'];
if (payload.type === 'issue') labels.push('bug');
if (payload.type === 'feature') labels.push('enhancement');
return labels;
},
// Optional: custom title format
getTitle: (payload) => {
const prefix = payload.type === 'issue' ? '🐛' : '✨';
return `${prefix} Feedback: ${payload.element.selector}`;
},
});
export async function POST(request: Request) {
try {
const payload: FeedbackPayload = await request.json();
const result = await github.submit(payload);
return Response.json(result);
} catch (error) {
console.error('Feedback error:', error);
return Response.json(
{ success: false, error: 'Failed to create issue' },
{ status: 500 }
);
}
}

GitHub Token Scopes

Create a Personal Access Token with these scopes:

  • repo – Full control of private repositories
  • public_repo – For public repositories only

Custom Markdown Formatting

import { formatFeedbackAsMarkdown } from '@ewjdev/anyclick-github/server';
// Use the built-in formatter
const markdown = formatFeedbackAsMarkdown(payload);
// Or create custom formatting
function customFormat(payload: FeedbackPayload): string {
return `
## ${payload.type === 'issue' ? '🐛 Bug Report' : '✨ Feature Request'}
**Element:** \`${payload.element.selector}\`
**Comment:** ${payload.comment || 'No comment provided'}
### Page Context
- URL: ${payload.page.url}
- Viewport: ${payload.page.viewportWidth}x${payload.page.viewportHeight}
### Element Details
\`\`\`html
${payload.element.outerHTML}
\`\`\`
`;
}

Jira Adapter

The Jira adapter creates issues in your Jira Cloud instance with rich Atlassian Document Format descriptions and automatic screenshot attachments.

Installation

npm install @ewjdev/anyclick-jira

Server-Side Setup

Create an API route that receives feedback and creates Jira issues:

app/api/feedback/route.ts
import { createJiraAdapter } from '@ewjdev/anyclick-jira/server';
import type { AnyclickPayload } from '@ewjdev/anyclick-core';
const jira = createJiraAdapter({
jiraUrl: process.env.JIRA_URL!, // e.g., https://company.atlassian.net
email: process.env.JIRA_EMAIL!,
apiToken: process.env.JIRA_API_TOKEN!,
projectKey: process.env.JIRA_PROJECT_KEY!, // e.g., "PROJ"
// Optional: customize issue types
issueTypeMapping: {
issue: 'Bug',
feature: 'Story',
like: 'Task',
},
// Optional: default labels
defaultLabels: ['ui-feedback'],
});
export async function POST(request: Request) {
try {
const payload: AnyclickPayload = await request.json();
const issue = await jira.createIssue(payload);
return Response.json({
success: true,
issueKey: issue.key,
url: issue.url,
});
} catch (error) {
console.error('Jira error:', error);
return Response.json(
{ success: false, error: 'Failed to create Jira issue' },
{ status: 500 }
);
}
}

Environment Variables

.env.local
JIRA_URL=https://your-company.atlassian.net
JIRA_EMAIL=your-email@company.com
JIRA_API_TOKEN=your-api-token-here
JIRA_PROJECT_KEY=PROJ

Getting Your Jira API Token

  1. Go to id.atlassian.com/manage-profile/security/api-tokens
  2. Click "Create API token"
  3. Give it a label (e.g., "Anyclick Feedback")
  4. Copy the token and add it to your .env.local file

Multi-Adapter Setup (GitHub + Jira)

Submit feedback to both GitHub and Jira simultaneously:

app/api/feedback/route.ts
import { createGitHubAdapter } from '@ewjdev/anyclick-github/server';
import { createJiraAdapter } from '@ewjdev/anyclick-jira/server';
import type { AnyclickPayload } from '@ewjdev/anyclick-core';
export async function POST(request: Request) {
const payload: AnyclickPayload = await request.json();
const results = [];
// Submit to GitHub
if (process.env.GITHUB_TOKEN) {
try {
const github = createGitHubAdapter({
token: process.env.GITHUB_TOKEN,
owner: 'your-org',
repo: 'your-repo',
});
const issue = await github.createIssue(payload);
results.push({ adapter: 'GitHub', success: true, url: issue.htmlUrl });
} catch (error) {
results.push({ adapter: 'GitHub', success: false });
}
}
// Submit to Jira
if (process.env.JIRA_URL) {
try {
const jira = createJiraAdapter({
jiraUrl: process.env.JIRA_URL,
email: process.env.JIRA_EMAIL!,
apiToken: process.env.JIRA_API_TOKEN!,
projectKey: process.env.JIRA_PROJECT_KEY!,
});
const issue = await jira.createIssue(payload);
results.push({ adapter: 'Jira', success: true, url: issue.url });
} catch (error) {
results.push({ adapter: 'Jira', success: false });
}
}
const hasSuccess = results.some(r => r.success);
return Response.json({ success: hasSuccess, results });
}

Custom Fields

Include custom fields like Epic Link or Team:

const jira = createJiraAdapter({
jiraUrl: process.env.JIRA_URL!,
email: process.env.JIRA_EMAIL!,
apiToken: process.env.JIRA_API_TOKEN!,
projectKey: 'PROJ',
customFields: {
// Epic link
customfield_10008: 'PROJ-123',
// Team
customfield_16300: { id: '12345' },
},
});

Cursor Cloud Agent

Launch Cursor's Cloud Agent to automatically analyze and fix issues based on feedback.

Installation

npm install @ewjdev/anyclick-cursor

Usage

app/providers.tsx
import { FeedbackProvider } from '@ewjdev/anyclick-react';
import { createCursorAdapter } from '@ewjdev/anyclick-cursor';
const cursorAdapter = createCursorAdapter({
// Cursor Cloud Agent configuration
apiKey: process.env.NEXT_PUBLIC_CURSOR_API_KEY,
projectId: 'your-project-id',
});
// Use with custom menu items
const menuItems = [
{ type: 'issue', label: 'Report Issue', showComment: true },
{
type: 'cursor_cloud',
label: 'Fix with AI',
showComment: true,
requiredRoles: ['developer'],
},
];
<FeedbackProvider
adapter={cursorAdapter}
menuItems={menuItems}
>
{children}
</FeedbackProvider>

Format for Cursor Agent

import { formatForCursorAgent } from '@ewjdev/anyclick-cursor';
const agentPrompt = formatForCursorAgent(payload);
// Returns a structured prompt optimized for AI code fixes

Cursor Local Agent

Run cursor-agent locally during development. Feedback is saved to disk and opens directly in Cursor.

Installation

npm install @ewjdev/anyclick-cursor-local

Start the Local Server

The local adapter requires a server to receive feedback and invoke Cursor:

# Add to your package.json scripts
"scripts": {
"feedback-server": "anyclick-local-server"
}
# Run the server (default port: 3847)
npm run feedback-server

Browser-Side Setup

app/providers.tsx
import { FeedbackProvider } from '@ewjdev/anyclick-react';
import { createLocalAdapter } from '@ewjdev/anyclick-cursor-local';
// Only use in development
const localAdapter = createLocalAdapter({
serverUrl: 'http://localhost:3847',
projectPath: '/path/to/your/project',
});
// Conditional adapter based on environment
const adapter = process.env.NODE_ENV === 'development'
? localAdapter
: productionAdapter;
<FeedbackProvider adapter={adapter}>
{children}
</FeedbackProvider>

Server Configuration

feedback-server.config.js
module.exports = {
port: 3847,
outputDir: '.feedback',
// Custom handler when feedback is received
onFeedback: async (payload) => {
// Save to file, open in Cursor, etc.
console.log('Received feedback:', payload.type);
},
// Format the feedback as a Cursor-compatible prompt
formatPrompt: (payload) => {
return `Fix this ${payload.type}: ${payload.comment}`;
},
};

⚠️ Development Only

The local adapter is designed for development workflows only. Don't expose the local server in production.

Building Custom Adapters

Implement the FeedbackAdapter interface to create custom integrations:

adapters/slack.ts
import type { FeedbackAdapter, FeedbackPayload, FeedbackResult } from '@ewjdev/anyclick-core';
interface SlackAdapterConfig {
webhookUrl: string;
channel?: string;
}
export function createSlackAdapter(config: SlackAdapterConfig): FeedbackAdapter {
return {
async submit(payload: FeedbackPayload): Promise<FeedbackResult> {
try {
const message = formatSlackMessage(payload);
const response = await fetch(config.webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
channel: config.channel,
...message,
}),
});
if (!response.ok) {
throw new Error(`Slack API error: ${response.status}`);
}
return {
success: true,
id: `slack-${Date.now()}`,
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
},
};
}
function formatSlackMessage(payload: FeedbackPayload) {
return {
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: `${payload.type === 'issue' ? '🐛' : '✨'} New Feedback`,
},
},
{
type: 'section',
fields: [
{ type: 'mrkdwn', text: `*Type:* ${payload.type}` },
{ type: 'mrkdwn', text: `*Page:* ${payload.page.url}` },
],
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*Comment:* ${payload.comment || 'No comment'}`,
},
},
],
};
}

Combining Multiple Adapters

Send feedback to multiple destinations:

adapters/multi.ts
import type { FeedbackAdapter, FeedbackPayload, FeedbackResult } from '@ewjdev/anyclick-core';
export function createMultiAdapter(adapters: FeedbackAdapter[]): FeedbackAdapter {
return {
async submit(payload: FeedbackPayload): Promise<FeedbackResult> {
const results = await Promise.allSettled(
adapters.map(adapter => adapter.submit(payload))
);
const successes = results.filter(
(r): r is PromiseFulfilledResult<FeedbackResult> =>
r.status === 'fulfilled' && r.value.success
);
return {
success: successes.length > 0,
id: successes[0]?.value.id,
url: successes[0]?.value.url,
error: successes.length === 0 ? 'All adapters failed' : undefined,
};
},
};
}
// Usage
const adapter = createMultiAdapter([
githubAdapter,
slackAdapter,
analyticsAdapter,
]);

Examples