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-githubCreate GitHub Issues with rich context, formatted markdown, and embedded screenshots.
@ewjdev/anyclick-jiraCreate Jira issues with Atlassian Document Format descriptions and automatic screenshot attachments.
@ewjdev/anyclick-cursorLaunch Cursor's Cloud Agent to automatically fix issues from feedback.
@ewjdev/anyclick-cursor-localRun 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-githubBrowser-Side Setup
Use the HTTP adapter to send feedback to your API endpoint:
'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:
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 repositoriespublic_repo– For public repositories only
Custom Markdown Formatting
import { formatFeedbackAsMarkdown } from '@ewjdev/anyclick-github/server';
// Use the built-in formatterconst markdown = formatFeedbackAsMarkdown(payload);
// Or create custom formattingfunction 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-jiraServer-Side Setup
Create an API route that receives feedback and creates Jira issues:
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
JIRA_URL=https://your-company.atlassian.netJIRA_EMAIL=your-email@company.comJIRA_API_TOKEN=your-api-token-hereJIRA_PROJECT_KEY=PROJGetting Your Jira API Token
- Go to id.atlassian.com/manage-profile/security/api-tokens
- Click "Create API token"
- Give it a label (e.g., "Anyclick Feedback")
- Copy the token and add it to your
.env.localfile
Multi-Adapter Setup (GitHub + Jira)
Submit feedback to both GitHub and Jira simultaneously:
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-cursorUsage
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 itemsconst 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 fixesCursor Local Agent
Run cursor-agent locally during development. Feedback is saved to disk and opens directly in Cursor.
Installation
npm install @ewjdev/anyclick-cursor-localStart 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-serverBrowser-Side Setup
import { FeedbackProvider } from '@ewjdev/anyclick-react';import { createLocalAdapter } from '@ewjdev/anyclick-cursor-local';
// Only use in developmentconst localAdapter = createLocalAdapter({ serverUrl: 'http://localhost:3847', projectPath: '/path/to/your/project',});
// Conditional adapter based on environmentconst adapter = process.env.NODE_ENV === 'development' ? localAdapter : productionAdapter;
<FeedbackProvider adapter={adapter}> {children}</FeedbackProvider>Server Configuration
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:
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:
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, }; }, };}
// Usageconst adapter = createMultiAdapter([ githubAdapter, slackAdapter, analyticsAdapter,]);