Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/lib/agent-runner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { FrameworkConfig } from './framework-config';
import {
getWelcomeMessage,
SPINNER_MESSAGE,
type FrameworkConfig,
} from './framework-config';
import type { WizardOptions } from '../utils/types';
import {
abort,
Expand Down Expand Up @@ -36,15 +40,18 @@ export async function runAgentWizard(
options: WizardOptions,
): Promise<void> {
// Setup phase
printWelcome({ wizardName: config.ui.welcomeMessage });
printWelcome({ wizardName: getWelcomeMessage(config.metadata.name) });

clack.log.info(
`🧙 The wizard has chosen you to try the next-generation agent integration for ${config.metadata.name}.\n\nStand by for the good stuff, and let the robot minders know how it goes:\n\[email protected]`,
);

const aiConsent = await askForAIConsent(options);
if (!aiConsent) {
await abort(config.metadata.abortMessage, 0);
await abort(
`This wizard uses an LLM agent to intelligently modify your project. Please view the docs to set up ${config.metadata.name} manually instead: ${config.metadata.docsUrl}`,
0,
);
}

const cloudRegion = options.cloudRegion ?? (await askForCloudRegion());
Expand Down Expand Up @@ -127,7 +134,7 @@ export async function runAgentWizard(
spinner,
{
estimatedDurationMinutes: config.ui.estimatedDurationMinutes,
spinnerMessage: config.ui.spinnerMessage,
spinnerMessage: SPINNER_MESSAGE,
successMessage: config.ui.successMessage,
errorMessage: 'Integration failed',
},
Expand Down
25 changes: 25 additions & 0 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,37 @@ export const INTEGRATION_CONFIG = {
nextSteps:
'• Call posthog.identify() when a user signs into your app\n• Call posthog.capture() to capture custom events in your app\n• Use posthog.isFeatureEnabled() for feature flags',
},
[Integration.reactRouter]: {
name: 'React Router',
filterPatterns: ['**/*.{tsx,ts,jsx,js}'],
ignorePatterns: [
'node_modules',
'dist',
'build',
'public',
'static',
'assets',
],
detect: async (options) => {
const packageJson = await getPackageDotJson(options);
return hasPackageInstalled('react-router', packageJson);
},
generateFilesRules: '',
filterFilesRules: '',
docsUrl:
'https://posthog-git-react-post-hog.vercel.app/docs/libraries/react-router',
defaultChanges:
'• Installed posthog-js package\n• Added PostHogProvider to the root of the app\n• Integrated PostHog with React Router for pageview tracking',
nextSteps:
'• Call posthog.identify() when a user signs into your app\n• Call posthog.capture() to capture custom events in your app',
},
} as const satisfies Record<Integration, IntegrationConfig>;

export const INTEGRATION_ORDER = [
Integration.nextjs,
Integration.astro,
Integration.svelte,
Integration.reactNative,
Integration.reactRouter,
Integration.react,
] as const;
7 changes: 7 additions & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ export enum Integration {
svelte = 'svelte',
reactNative = 'react-native',
astro = 'astro',
reactRouter = 'react-router',
}

export enum FeatureFlagDefinition {
ReactRouter = 'wizard-react-router',
}

export function getIntegrationDescription(type: string): string {
Expand All @@ -18,6 +23,8 @@ export function getIntegrationDescription(type: string): string {
return 'Svelte';
case Integration.astro:
return 'Astro';
case Integration.reactRouter:
return 'React Router';
default:
throw new Error(`Unknown integration ${type}`);
}
Expand Down
22 changes: 13 additions & 9 deletions src/lib/framework-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ export interface FrameworkMetadata {
/** URL to framework-specific PostHog docs */
docsUrl: string;

/** Message shown when user declines AI consent */
abortMessage: string;

/**
* Optional URL to docs for users with unsupported framework versions.
* If not provided, defaults to docsUrl.
Expand Down Expand Up @@ -102,12 +99,6 @@ export interface PromptConfig {
* UI messaging configuration
*/
export interface UIConfig {
/** Welcome message for wizard start */
welcomeMessage: string;

/** Spinner message while agent runs */
spinnerMessage: string;

/** Success message when agent completes */
successMessage: string;

Expand All @@ -120,3 +111,16 @@ export interface UIConfig {
/** Generate "Next steps" bullets from context */
getOutroNextSteps: (context: any) => string[];
}

/**
* Generate welcome message from framework name
*/
export function getWelcomeMessage(frameworkName: string): string {
return `PostHog ${frameworkName} wizard (agent-powered)`;
}

/**
* Shared spinner message for all frameworks
*/
export const SPINNER_MESSAGE =
'Writing your PostHog setup with events, error capture and more...';
10 changes: 2 additions & 8 deletions src/nextjs/nextjs-wizard-agent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* Simplified Next.js wizard using posthog-agent with PostHog MCP */
import type { WizardOptions } from '../utils/types';
import type { FrameworkConfig } from '../lib/framework-config';
import { enableDebugLogs } from '../utils/debug';
import { runAgentWizard } from '../lib/agent-runner';
import { Integration } from '../lib/constants';
Expand All @@ -21,14 +20,12 @@ import {
*/
const MINIMUM_NEXTJS_VERSION = '15.3.0';

const NEXTJS_AGENT_CONFIG: FrameworkConfig = {
const NEXTJS_AGENT_CONFIG = {
metadata: {
name: 'Next.js',
integration: Integration.nextjs,
docsUrl: 'https://posthog.com/docs/libraries/next-js',
unsupportedVersionDocsUrl: 'https://posthog.com/docs/libraries/next-js',
abortMessage:
'This wizard uses an LLM agent to intelligently modify your project. Please view the docs to setup Next.js manually instead: https://posthog.com/docs/libraries/next-js',
gatherContext: async (options: WizardOptions) => {
const router = await getNextJsRouter(options);
return { router };
Expand All @@ -44,7 +41,7 @@ const NEXTJS_AGENT_CONFIG: FrameworkConfig = {

environment: {
uploadToHosting: true,
getEnvVars: (apiKey, host) => ({
getEnvVars: (apiKey: string, host: string) => ({
NEXT_PUBLIC_POSTHOG_KEY: apiKey,
NEXT_PUBLIC_POSTHOG_HOST: host,
}),
Expand All @@ -68,9 +65,6 @@ const NEXTJS_AGENT_CONFIG: FrameworkConfig = {
},

ui: {
welcomeMessage: 'PostHog Next.js wizard (agent-powered)',
spinnerMessage:
'Writing your PostHog setup with events, error capture and more...',
successMessage: 'PostHog integration complete',
estimatedDurationMinutes: 8,
getOutroChanges: (context: any) => {
Expand Down
142 changes: 142 additions & 0 deletions src/react-router/react-router-wizard-agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/* React Router wizard using posthog-agent with PostHog MCP */
import type { WizardOptions } from '../utils/types';
import type { FrameworkConfig } from '../lib/framework-config';
import { enableDebugLogs } from '../utils/debug';
import { runAgentWizard } from '../lib/agent-runner';
import { Integration } from '../lib/constants';
import { getPackageVersion } from '../utils/package-json';
import { getPackageDotJson } from '../utils/clack-utils';
import clack from '../utils/clack';
import chalk from 'chalk';
import * as semver from 'semver';
import {
getReactRouterMode,
getReactRouterModeName,
getReactRouterVersionBucket,
ReactRouterMode,
} from './utils';

/**
* React Router framework configuration for the universal agent runner.
*/
const MINIMUM_REACT_ROUTER_VERSION = '6.0.0';

const REACT_ROUTER_AGENT_CONFIG: FrameworkConfig = {
metadata: {
name: 'React Router',
integration: Integration.reactRouter,
docsUrl: 'https://posthog.com/docs/libraries/react',
unsupportedVersionDocsUrl: 'https://posthog.com/docs/libraries/react',
gatherContext: async (options: WizardOptions) => {
const routerMode = await getReactRouterMode(options);
return { routerMode };
},
},

detection: {
packageName: 'react-router',
packageDisplayName: 'React Router',
getVersion: (packageJson: any) =>
getPackageVersion('react-router', packageJson),
getVersionBucket: getReactRouterVersionBucket,
},

environment: {
uploadToHosting: false,
getEnvVars: (apiKey: string, host: string) => ({
REACT_APP_POSTHOG_KEY: apiKey,
REACT_APP_POSTHOG_HOST: host,
}),
},

analytics: {
getTags: (context: any) => {
const routerMode = context.routerMode as ReactRouterMode;
return {
routerMode: routerMode || 'unknown',
};
},
},

prompts: {
getAdditionalContextLines: (context: any) => {
const routerMode = context.routerMode as ReactRouterMode;
const modeName = routerMode
? getReactRouterModeName(routerMode)
: 'unknown';

// Map router mode to framework ID for MCP docs resource
const frameworkIdMap: Record<ReactRouterMode, string> = {
[ReactRouterMode.V6]: 'react-react-router-6',
[ReactRouterMode.V7_FRAMEWORK]: 'react-react-router-7-framework',
[ReactRouterMode.V7_DATA]: 'react-react-router-7-data',
[ReactRouterMode.V7_DECLARATIVE]: 'react-react-router-7-declarative',
};

const frameworkId = routerMode
? frameworkIdMap[routerMode]
: ReactRouterMode.V7_FRAMEWORK;

return [
`Router mode: ${modeName}`,
`Framework docs ID: ${frameworkId} (use posthog://docs/frameworks/${frameworkId} for documentation)`,
];
},
},

ui: {
successMessage: 'PostHog integration complete',
estimatedDurationMinutes: 8,
getOutroChanges: (context: any) => {
const routerMode = context.routerMode as ReactRouterMode;
const modeName = routerMode
? getReactRouterModeName(routerMode)
: 'React Router';
return [
`Analyzed your React Router project structure (${modeName})`,
`Created and configured PostHog initializers`,
`Integrated PostHog into your application`,
];
},
getOutroNextSteps: () => [
'Start your development server to see PostHog in action',
'Visit your PostHog dashboard to see incoming events',
],
},
};

/**
* React Router wizard powered by the universal agent runner.
*/
export async function runReactRouterWizardAgent(
options: WizardOptions,
): Promise<void> {
if (options.debug) {
enableDebugLogs();
}

// Check React Router version - agent wizard requires >= 6.0.0
const packageJson = await getPackageDotJson(options);
const reactRouterVersion = getPackageVersion('react-router', packageJson);

if (reactRouterVersion) {
const coercedVersion = semver.coerce(reactRouterVersion);
if (
coercedVersion &&
semver.lt(coercedVersion, MINIMUM_REACT_ROUTER_VERSION)
) {
const docsUrl =
REACT_ROUTER_AGENT_CONFIG.metadata.unsupportedVersionDocsUrl ??
REACT_ROUTER_AGENT_CONFIG.metadata.docsUrl;

clack.log.warn(
`Sorry: the wizard can't help you with React Router ${reactRouterVersion}. Upgrade to React Router ${MINIMUM_REACT_ROUTER_VERSION} or later, or check out the manual setup guide.`,
);
clack.log.info(`Setup React Router manually: ${chalk.cyan(docsUrl)}`);
clack.outro('PostHog wizard will see you next time!');
return;
}
}

await runAgentWizard(REACT_ROUTER_AGENT_CONFIG, options);
}
Loading
Loading