From 0abf74e9d8f5891c8e2b8eb8abd02b100d6fa060 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 7 Jan 2026 13:33:30 -0500 Subject: [PATCH 1/5] Add react router --- src/lib/config.ts | 25 ++ src/lib/constants.ts | 3 + src/nextjs/nextjs-wizard-agent.ts | 5 +- src/react-router/react-router-wizard-agent.ts | 147 +++++++++ src/react-router/utils.ts | 289 ++++++++++++++++++ src/run.ts | 5 + 6 files changed, 471 insertions(+), 3 deletions(-) create mode 100644 src/react-router/react-router-wizard-agent.ts create mode 100644 src/react-router/utils.ts diff --git a/src/lib/config.ts b/src/lib/config.ts index b06d2138..1edb2b36 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -110,6 +110,30 @@ 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; export const INTEGRATION_ORDER = [ @@ -117,5 +141,6 @@ export const INTEGRATION_ORDER = [ Integration.astro, Integration.svelte, Integration.reactNative, + Integration.reactRouter, Integration.react, ] as const; diff --git a/src/lib/constants.ts b/src/lib/constants.ts index a563dcb7..74ea9411 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -4,6 +4,7 @@ export enum Integration { svelte = 'svelte', reactNative = 'react-native', astro = 'astro', + reactRouter = 'react-router', } export function getIntegrationDescription(type: string): string { @@ -18,6 +19,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}`); } diff --git a/src/nextjs/nextjs-wizard-agent.ts b/src/nextjs/nextjs-wizard-agent.ts index 082c8d20..d1476525 100644 --- a/src/nextjs/nextjs-wizard-agent.ts +++ b/src/nextjs/nextjs-wizard-agent.ts @@ -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'; @@ -21,7 +20,7 @@ import { */ const MINIMUM_NEXTJS_VERSION = '15.3.0'; -const NEXTJS_AGENT_CONFIG: FrameworkConfig = { +const NEXTJS_AGENT_CONFIG = { metadata: { name: 'Next.js', integration: Integration.nextjs, @@ -44,7 +43,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, }), diff --git a/src/react-router/react-router-wizard-agent.ts b/src/react-router/react-router-wizard-agent.ts new file mode 100644 index 00000000..c3084c62 --- /dev/null +++ b/src/react-router/react-router-wizard-agent.ts @@ -0,0 +1,147 @@ +/* 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', + abortMessage: + 'This wizard uses an LLM agent to intelligently modify your project. Please view the docs to setup React Router manually instead: 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.V6]: 'react-react-router-v6', + [ReactRouterMode.V7_FRAMEWORK]: 'react-react-router-v7-framework', + [ReactRouterMode.V7_DATA]: 'react-react-router-v7-data', + [ReactRouterMode.V7_DECLARATIVE]: 'react-react-router-v7-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: { + welcomeMessage: 'PostHog React Router wizard (agent-powered)', + spinnerMessage: + 'Writing your PostHog setup with events, error capture and more...', + 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 { + 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); +} diff --git a/src/react-router/utils.ts b/src/react-router/utils.ts new file mode 100644 index 00000000..3002430b --- /dev/null +++ b/src/react-router/utils.ts @@ -0,0 +1,289 @@ +import { major, minVersion } from 'semver'; +import fg from 'fast-glob'; +import { abortIfCancelled, getPackageDotJson } from '../utils/clack-utils'; +import clack from '../utils/clack'; +import type { WizardOptions } from '../utils/types'; +import { Integration } from '../lib/constants'; +import { getPackageVersion } from '../utils/package-json'; +import chalk from 'chalk'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as semver from 'semver'; + +export enum ReactRouterMode { + V6 = 'v6', // React Router v6 + V7_FRAMEWORK = 'v7-framework', // React Router v7 with react-router.config.ts + V7_DATA = 'v7-data', // React Router v7 with createBrowserRouter + V7_DECLARATIVE = 'v7-declarative', // React Router v7 with BrowserRouter +} + +const IGNORE_PATTERNS = [ + '**/node_modules/**', + '**/dist/**', + '**/build/**', + '**/public/**', + '**/.next/**', +]; + +/** + * Get React Router version bucket for analytics + */ +export function getReactRouterVersionBucket( + version: string | undefined, +): string { + if (!version) { + return 'none'; + } + + try { + const minVer = minVersion(version); + if (!minVer) { + return 'invalid'; + } + const majorVersion = major(minVer); + if (majorVersion >= 6) { + return `${majorVersion}.x`; + } + return `<6.0.0`; + } catch { + return 'unknown'; + } +} + +/** + * Check if react-router.config.ts exists (indicates framework mode - React Router v7) + */ +async function hasReactRouterConfig({ + installDir, +}: Pick): Promise { + const configMatches = await fg('**/react-router.config.@(ts|js|tsx|jsx)', { + dot: true, + cwd: installDir, + ignore: IGNORE_PATTERNS, + }); + + return configMatches.length > 0; +} + +/** + * Search for createBrowserRouter usage in source files + */ +async function hasCreateBrowserRouter({ + installDir, +}: Pick): Promise { + const sourceFiles = await fg('**/*.@(ts|tsx|js|jsx)', { + dot: true, + cwd: installDir, + ignore: IGNORE_PATTERNS, + }); + + for (const file of sourceFiles) { + try { + const filePath = path.join(installDir, file); + const content = fs.readFileSync(filePath, 'utf-8'); + + // Check for createBrowserRouter import or usage + if (content.includes('createBrowserRouter')) { + return true; + } + } catch { + // Skip files that can't be read + continue; + } + } + + return false; +} + +/** + * Search for declarative BrowserRouter usage + */ +async function hasDeclarativeRouter({ + installDir, +}: Pick): Promise { + const sourceFiles = await fg('**/*.@(ts|tsx|js|jsx)', { + dot: true, + cwd: installDir, + ignore: IGNORE_PATTERNS, + }); + + for (const file of sourceFiles) { + try { + const filePath = path.join(installDir, file); + const content = fs.readFileSync(filePath, 'utf-8'); + + // Check for BrowserRouter usage (JSX or import) + if ( + content.includes(' { + const { installDir } = options; + + // First, get the React Router version + const packageJson = await getPackageDotJson(options); + const reactRouterVersion = + getPackageVersion('react-router-dom', packageJson) || + getPackageVersion('react-router', packageJson); + + if (!reactRouterVersion) { + // If we can't detect version, ask the user + clack.log.info( + `Learn more about React Router modes: ${chalk.cyan( + 'https://reactrouter.com/start/modes', + )}`, + ); + const result: ReactRouterMode = await abortIfCancelled( + clack.select({ + message: 'What React Router version and mode are you using?', + options: [ + { + label: 'React Router v6', + value: ReactRouterMode.V6, + }, + { + label: 'React Router v7 - Framework mode', + value: ReactRouterMode.V7_FRAMEWORK, + }, + { + label: 'React Router v7 - Data mode', + value: ReactRouterMode.V7_DATA, + }, + { + label: 'React Router v7 - Declarative mode', + value: ReactRouterMode.V7_DECLARATIVE, + }, + ], + }), + Integration.reactRouter, + ); + return result; + } + + const coercedVersion = semver.coerce(reactRouterVersion); + const majorVersion = coercedVersion ? major(coercedVersion) : null; + + // If v6, return V6 + if (majorVersion === 6) { + clack.log.info('Detected React Router v6'); + return ReactRouterMode.V6; + } + + // If v7, detect the mode + if (majorVersion === 7) { + // First check for framework mode (react-router.config.ts) + const hasConfig = await hasReactRouterConfig({ installDir }); + if (hasConfig) { + clack.log.info('Detected React Router v7 - Framework mode'); + return ReactRouterMode.V7_FRAMEWORK; + } + + // Check for data mode (createBrowserRouter) + const hasDataMode = await hasCreateBrowserRouter({ installDir }); + if (hasDataMode) { + clack.log.info('Detected React Router v7 - Data mode'); + return ReactRouterMode.V7_DATA; + } + + // Check for declarative mode (BrowserRouter) + const hasDeclarative = await hasDeclarativeRouter({ installDir }); + if (hasDeclarative) { + clack.log.info('Detected React Router v7 - Declarative mode'); + return ReactRouterMode.V7_DECLARATIVE; + } + + // If v7 but can't detect mode, ask the user + clack.log.info( + `Learn more about React Router modes: ${chalk.cyan( + 'https://reactrouter.com/start/modes', + )}`, + ); + const result: ReactRouterMode = await abortIfCancelled( + clack.select({ + message: 'What React Router v7 mode are you using?', + options: [ + { + label: 'Framework mode', + value: ReactRouterMode.V7_FRAMEWORK, + }, + { + label: 'Data mode', + value: ReactRouterMode.V7_DATA, + }, + { + label: 'Declarative mode', + value: ReactRouterMode.V7_DECLARATIVE, + }, + ], + }), + Integration.reactRouter, + ); + return result; + } + + // If version is not 6 or 7, default to asking + clack.log.info( + `Learn more about React Router modes: ${chalk.cyan( + 'https://reactrouter.com/start/modes', + )}`, + ); + const result: ReactRouterMode = await abortIfCancelled( + clack.select({ + message: 'What React Router version and mode are you using?', + options: [ + { + label: 'React Router v6', + value: ReactRouterMode.V6, + }, + { + label: 'React Router v7 - Framework mode', + value: ReactRouterMode.V7_FRAMEWORK, + }, + { + label: 'React Router v7 - Data mode', + value: ReactRouterMode.V7_DATA, + }, + { + label: 'React Router v7 - Declarative mode', + value: ReactRouterMode.V7_DECLARATIVE, + }, + ], + }), + Integration.reactRouter, + ); + return result; +} + +/** + * Get human-readable name for React Router mode + */ +export function getReactRouterModeName(mode: ReactRouterMode): string { + switch (mode) { + case ReactRouterMode.V6: + return 'v6'; + case ReactRouterMode.V7_FRAMEWORK: + return 'v7 Framework mode'; + case ReactRouterMode.V7_DATA: + return 'v7 Data mode'; + case ReactRouterMode.V7_DECLARATIVE: + return 'v7 Declarative mode'; + } +} diff --git a/src/run.ts b/src/run.ts index 2a9faf06..7a9dada2 100644 --- a/src/run.ts +++ b/src/run.ts @@ -13,6 +13,7 @@ import { analytics } from './utils/analytics'; import { runSvelteWizard } from './svelte/svelte-wizard'; import { runReactNativeWizard } from './react-native/react-native-wizard'; import { runAstroWizard } from './astro/astro-wizard'; +import { runReactRouterWizardAgent } from './react-router/react-router-wizard-agent'; import { EventEmitter } from 'events'; import chalk from 'chalk'; import { RateLimitError } from './utils/errors'; @@ -81,6 +82,9 @@ export async function runWizard(argv: Args) { case Integration.astro: await runAstroWizard(wizardOptions); break; + case Integration.reactRouter: + await runReactRouterWizardAgent(wizardOptions); + break; default: clack.log.error('No setup wizard selected!'); } @@ -141,6 +145,7 @@ async function getIntegrationForSetup( { value: Integration.nextjs, label: 'Next.js' }, { value: Integration.astro, label: 'Astro' }, { value: Integration.react, label: 'React' }, + { value: Integration.reactRouter, label: 'React Router' }, { value: Integration.svelte, label: 'Svelte' }, { value: Integration.reactNative, label: 'React Native' }, ], From dfc1f720f2356af3ceb2970a0563487698504cad Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Wed, 7 Jan 2026 20:06:52 -0500 Subject: [PATCH 2/5] Add feature flag --- src/lib/constants.ts | 4 ++++ src/run.ts | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 74ea9411..5e9d36e2 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -7,6 +7,10 @@ export enum Integration { reactRouter = 'react-router', } +export enum FeatureFlagDefinition { + ReactRouter = 'wizard-react-router', +} + export function getIntegrationDescription(type: string): string { switch (type) { case Integration.nextjs: diff --git a/src/run.ts b/src/run.ts index 7a9dada2..8df8eef9 100644 --- a/src/run.ts +++ b/src/run.ts @@ -3,7 +3,11 @@ import { abortIfCancelled } from './utils/clack-utils'; import { runNextjsWizardAgent } from './nextjs/nextjs-wizard-agent'; import type { CloudRegion, WizardOptions } from './utils/types'; -import { getIntegrationDescription, Integration } from './lib/constants'; +import { + getIntegrationDescription, + Integration, + FeatureFlagDefinition, +} from './lib/constants'; import { readEnvironment } from './utils/environment'; import clack from './utils/clack'; import path from 'path'; @@ -82,9 +86,17 @@ export async function runWizard(argv: Args) { case Integration.astro: await runAstroWizard(wizardOptions); break; - case Integration.reactRouter: - await runReactRouterWizardAgent(wizardOptions); + case Integration.reactRouter: { + const flagValue = await analytics.getFeatureFlag( + FeatureFlagDefinition.ReactRouter, + ); + if (flagValue === true) { + await runReactRouterWizardAgent(wizardOptions); + } else { + await runReactWizard(wizardOptions); + } break; + } default: clack.log.error('No setup wizard selected!'); } From 8158c727680e4652cbf22813a34bafebdb902679 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Thu, 8 Jan 2026 15:00:30 -0500 Subject: [PATCH 3/5] fix names --- src/react-router/react-router-wizard-agent.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/react-router/react-router-wizard-agent.ts b/src/react-router/react-router-wizard-agent.ts index c3084c62..a23aadf5 100644 --- a/src/react-router/react-router-wizard-agent.ts +++ b/src/react-router/react-router-wizard-agent.ts @@ -69,10 +69,10 @@ const REACT_ROUTER_AGENT_CONFIG: FrameworkConfig = { // Map router mode to framework ID for MCP docs resource const frameworkIdMap: Record = { - [ReactRouterMode.V6]: 'react-react-router-v6', - [ReactRouterMode.V7_FRAMEWORK]: 'react-react-router-v7-framework', - [ReactRouterMode.V7_DATA]: 'react-react-router-v7-data', - [ReactRouterMode.V7_DECLARATIVE]: 'react-react-router-v7-declarative', + [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 From 59186e407e368c377c186818935a9e72d6d257a3 Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Fri, 9 Jan 2026 13:13:59 -0500 Subject: [PATCH 4/5] Use bigger safe tools list --- src/lib/agent-interface.ts | 17 +-- src/lib/safe-tools.ts | 288 +++++++++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+), 8 deletions(-) create mode 100644 src/lib/safe-tools.ts diff --git a/src/lib/agent-interface.ts b/src/lib/agent-interface.ts index 7afcdbe5..1c4a7437 100644 --- a/src/lib/agent-interface.ts +++ b/src/lib/agent-interface.ts @@ -10,6 +10,7 @@ import type { WizardOptions } from '../utils/types'; import { analytics } from '../utils/analytics'; import { WIZARD_INTERACTION_EVENT_NAME } from './constants'; import { getLlmGatewayUrlFromHost } from '../utils/urls'; +import { LINTING_TOOLS } from './safe-tools'; // Dynamic import cache for ESM module let _sdkModule: any = null; @@ -81,6 +82,7 @@ const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun', 'npx']; /** * Safe scripts/commands that can be run with any package manager. * Uses startsWith matching, so 'build' matches 'build', 'build:prod', etc. + * Note: Linting tools are in LINTING_TOOLS and checked separately. */ const SAFE_SCRIPTS = [ // Package installation @@ -95,13 +97,9 @@ const SAFE_SCRIPTS = [ 'type-check', 'check-types', 'types', - // Linting + // Linting/formatting script names (actual tools are in LINTING_TOOLS) 'lint', - 'eslint', - 'next lint', - // Formatting 'format', - 'prettier', ]; /** @@ -129,8 +127,11 @@ function matchesAllowedPrefix(command: string): boolean { // Get the script/command portion (may include args) const scriptPart = parts.slice(scriptIndex).join(' '); - // Check if script starts with any safe script name - return SAFE_SCRIPTS.some((safe) => scriptPart.startsWith(safe)); + // Check if script starts with any safe script name or linting tool + return ( + SAFE_SCRIPTS.some((safe) => scriptPart.startsWith(safe)) || + LINTING_TOOLS.some((tool) => scriptPart.startsWith(tool)) + ); } /** @@ -231,7 +232,7 @@ export function wizardCanUseTool( }); return { behavior: 'deny', - message: `Bash command not allowed. Only install, build, typecheck, and lint commands are permitted.`, + message: `Bash command not allowed. Only install, build, typecheck, lint, and formatting commands are permitted.`, }; } diff --git a/src/lib/safe-tools.ts b/src/lib/safe-tools.ts new file mode 100644 index 00000000..1a89be9d --- /dev/null +++ b/src/lib/safe-tools.ts @@ -0,0 +1,288 @@ +export const LINTING_TOOLS: string[] = [ + // All (general purpose) + 'codespell', + 'cspell', + 'git-diff-check', + 'gitleaks', + 'trufflehog', + + // Amazon States Language + 'asl-validator', + + // Ansible + 'ansible-lint', + + // Apex, Java + 'pmd', + + // Astro, CSS, GraphQL, GritQL, HTML, JavaScript, JSON, JSONC, JSON5, JSX, TSX, Svelte, TypeScript, Vue + 'biome', + + // AWS CloudFormation templates + 'cfn-lint', + 'cfnlint', + + // AWS CloudFormation, Azure ARM, Dockerfile, Helm, Kubernetes, Security, Terraform + 'checkov', + + // AWS CloudFormation, Azure ARM, Dockerfile, Kubernetes, Secrets, Security, Terraform, Vulnerabilities + 'trivy', + + // Azure Resource Manager (ARM) + 'test-aztemplate', + + // Bash / Shell + 'shellcheck', + 'shfmt', + + // Bazel, Starlark + 'buildifier', + + // C, C++ + 'cpplint', + 'clang-format', + 'clang-tidy', + 'cmake-format', + 'iwyu', + 'pragma-once', + + // C#, Dotnet (.NET) + 'dotnet-format', + + // CircleCI Config + 'circleci', + + // Clojure + 'clj-kondo', + + // CoffeeScript + 'coffeelint', + + // Commit messages + 'commitlint', + + // Copy/paste detection + 'jscpd', + + // CSS, SCSS, Sass + 'stylelint', + + // CSS, GraphQL, HTML, JavaScript, JSON, JSONC, JSON5, JSX, TSX, Markdown, TypeScript, Vue, YAML + 'prettier', + + // Cue + 'cue-fmt', + + // Dart + 'dart', + + // Dockerfile / Docker + 'hadolint', + + // Dotenv + 'dotenv-linter', + + // EditorConfig + 'editorconfig-checker', + + // GitHub Actions + 'actionlint', + 'zizmor', + + // Go + 'gofmt', + 'gofumpt', + 'goimports', + 'gokart', + 'golangci-lint', + 'golines', + + // Go, Java, JavaScript, JSON, Python, Ruby, TypeScript, YAML + 'semgrep', + + // GoReleaser + 'goreleaser', + + // GraphQL + 'graphql-schema-linter', + + // Groovy + 'npm-groovy-lint', + + // HAML + 'haml-lint', + + // HTML + 'htmlhint', + + // HTML Templates + 'djlint', + + // Java + 'checkstyle', + 'google-java-format', + + // JavaScript, JSON, TypeScript + 'eslint', + + // Next.js + 'next lint', + + // JavaScript, JSON, Markdown, TypeScript + 'deno', + + // JavaScript, TypeScript + 'rome', + + // JSON, JSONC, JSON5 + 'eslint-plugin-jsonc', + 'eslint-plugin-json', + + // JSX, TSX + 'eslint-plugin-jsx-a11y', + 'eslint-plugin-react', + + // Jupyter Notebook + 'nbqa', + + // Kotlin + 'detekt', + 'ktlint', + + // Kubernetes + 'kubeconform', + 'kube-linter', + + // LaTeX + 'chktex', + + // Lua + 'luacheck', + 'stylua', + + // Markdown + 'markdownlint', + 'markdownlint-cli2', + 'markdown-link-check', + 'markdown-table-prettify', + 'remark-lint', + + // Natural language / Prose + 'textlint', + 'vale', + + // Nix + 'nixpkgs-fmt', + + // OpenAPI + 'spectral', + + // package.json + 'sort-package-json', + + // Perl + 'perlcritic', + 'perltidy', + + // PHP + 'php-cs-fixer', + 'phpcs', + 'phpstan', + 'psalm', + + // PNG + 'oxipng', + + // PowerShell + 'psscriptanalyzer', + + // Prisma + 'prisma', + + // Protocol Buffers (Protobuf) + 'protolint', + 'buf', + + // Python + 'pylint', + 'flake8', + 'isort', + 'ruff', + 'black', + 'autopep8', + 'bandit', + 'mypy', + 'pyright', + 'sourcery', + 'yapf', + + // R + 'lintr', + + // Rego + 'opa', + 'regal', + + // Ruby + 'rubocop', + 'brakeman', + 'rufo', + 'standardrb', + + // Rust + 'clippy', + 'rustfmt', + + // Scala + 'scalafmt', + + // Security, Vulnerabilities + 'osv-scanner', + + // Security, Terraform + 'terrascan', + 'tfsec', + + // Snakemake + 'snakemake --lint', + 'snakefmt', + + // SQL + 'sqlfluff', + 'sql-formatter', + 'sqlfmt', + 'squawk', + + // SVG + 'svgo', + + // Swift + 'stringslint', + 'swiftformat', + 'swiftlint', + + // Terraform + 'tflint', + 'terraform', + 'tofu', + + // Terragrunt + 'terragrunt', + + // Textproto + 'txtpbfmt', + + // TOML + 'taplo', + + // Vue + 'eslint-plugin-vue', + + // XML + 'xmllint', + + // YAML + 'yamllint', +] as const; + +export type LintingTool = (typeof LINTING_TOOLS)[number]; From 643f30c06772b0e07b8d2e2162fa34709ef0e80f Mon Sep 17 00:00:00 2001 From: "Vincent (Wen Yu) Ge" Date: Mon, 12 Jan 2026 13:49:45 -0500 Subject: [PATCH 5/5] Spinner messages --- src/lib/agent-runner.ts | 15 +++++++++---- src/lib/framework-config.ts | 22 +++++++++++-------- src/nextjs/nextjs-wizard-agent.ts | 5 ----- src/react-router/react-router-wizard-agent.ts | 5 ----- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/lib/agent-runner.ts b/src/lib/agent-runner.ts index ea9d9ae3..f21f7781 100644 --- a/src/lib/agent-runner.ts +++ b/src/lib/agent-runner.ts @@ -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, @@ -36,7 +40,7 @@ export async function runAgentWizard( options: WizardOptions, ): Promise { // 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\nwizard@posthog.com`, @@ -44,7 +48,10 @@ export async function runAgentWizard( 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()); @@ -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', }, diff --git a/src/lib/framework-config.ts b/src/lib/framework-config.ts index 7b83ee66..478b1f39 100644 --- a/src/lib/framework-config.ts +++ b/src/lib/framework-config.ts @@ -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. @@ -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; @@ -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...'; diff --git a/src/nextjs/nextjs-wizard-agent.ts b/src/nextjs/nextjs-wizard-agent.ts index d1476525..bb741f22 100644 --- a/src/nextjs/nextjs-wizard-agent.ts +++ b/src/nextjs/nextjs-wizard-agent.ts @@ -26,8 +26,6 @@ const NEXTJS_AGENT_CONFIG = { 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 }; @@ -67,9 +65,6 @@ const NEXTJS_AGENT_CONFIG = { }, 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) => { diff --git a/src/react-router/react-router-wizard-agent.ts b/src/react-router/react-router-wizard-agent.ts index a23aadf5..5d290d5b 100644 --- a/src/react-router/react-router-wizard-agent.ts +++ b/src/react-router/react-router-wizard-agent.ts @@ -27,8 +27,6 @@ const REACT_ROUTER_AGENT_CONFIG: FrameworkConfig = { integration: Integration.reactRouter, docsUrl: 'https://posthog.com/docs/libraries/react', unsupportedVersionDocsUrl: 'https://posthog.com/docs/libraries/react', - abortMessage: - 'This wizard uses an LLM agent to intelligently modify your project. Please view the docs to setup React Router manually instead: https://posthog.com/docs/libraries/react', gatherContext: async (options: WizardOptions) => { const routerMode = await getReactRouterMode(options); return { routerMode }; @@ -87,9 +85,6 @@ const REACT_ROUTER_AGENT_CONFIG: FrameworkConfig = { }, ui: { - welcomeMessage: 'PostHog React Router wizard (agent-powered)', - spinnerMessage: - 'Writing your PostHog setup with events, error capture and more...', successMessage: 'PostHog integration complete', estimatedDurationMinutes: 8, getOutroChanges: (context: any) => {