From 3d29ae328f906215e758a37a16a4e9cc3604af5c Mon Sep 17 00:00:00 2001 From: Danilo Campos Date: Thu, 2 Apr 2026 14:48:24 +0100 Subject: [PATCH] Cloudflare detection and context augmentation --- src/lib/agent-runner.ts | 21 +++++++- src/lib/cloudflare-detection.ts | 90 +++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/lib/cloudflare-detection.ts diff --git a/src/lib/agent-runner.ts b/src/lib/agent-runner.ts index d8686ae7..06c50d5c 100644 --- a/src/lib/agent-runner.ts +++ b/src/lib/agent-runner.ts @@ -39,6 +39,10 @@ import { registerCleanup, } from '../utils/wizard-abort'; import { formatScanReport, writeScanReport } from './yara-hooks'; +import { + detectCloudflareTarget, + fetchCloudflareReference, +} from './cloudflare-detection'; /** * Build a WizardOptions bag from a WizardSession (for code that still expects WizardOptions). @@ -210,6 +214,15 @@ export async function runAgentWizard( analytics.setTag(key, value); }); + // Detect Cloudflare Workers target and fetch runtime reference if needed + const isCloudflare = await detectCloudflareTarget(session.installDir); + let cloudflareReference: string | null = null; + if (isCloudflare) { + logToFile('[agent-runner] Cloudflare Workers target detected'); + analytics.setTag('cloudflare', 'true'); + cloudflareReference = await fetchCloudflareReference(skillsBaseUrl); + } + const integrationPrompt = buildIntegrationPrompt( config, { @@ -220,6 +233,7 @@ export async function runAgentWizard( projectId, }, frameworkContext, + cloudflareReference, ); // Initialize and run agent @@ -407,6 +421,7 @@ function buildIntegrationPrompt( projectId: number; }, frameworkContext: Record, + cloudflareReference?: string | null, ): string { const additionalLines = config.prompts.getAdditionalContextLines ? config.prompts.getAdditionalContextLines(frameworkContext) @@ -417,6 +432,10 @@ function buildIntegrationPrompt( ? '\n' + additionalLines.map((line) => `- ${line}`).join('\n') : ''; + const runtimeOverrides = cloudflareReference + ? `\n\n---\n\n${cloudflareReference}` + : ''; + return `You have access to the PostHog MCP server which provides skills to integrate PostHog into this ${ config.metadata.name } project. @@ -459,7 +478,7 @@ STEP 5: Set up environment variables for PostHog using the wizard-tools MCP serv - Reference these environment variables in the code files you create instead of hardcoding the public token and host. Important: Use the detect_package_manager tool (from the wizard-tools MCP server) to determine which package manager the project uses. Do not manually search for lockfiles or config files. Always install packages as a background task. Don't await completion; proceed with other work immediately after starting the installation. You must read a file immediately before attempting to write it, even if you have previously read it; failure to do so will cause a tool failure. - +${runtimeOverrides} `; } diff --git a/src/lib/cloudflare-detection.ts b/src/lib/cloudflare-detection.ts new file mode 100644 index 00000000..3a84ddb6 --- /dev/null +++ b/src/lib/cloudflare-detection.ts @@ -0,0 +1,90 @@ +import fg from 'fast-glob'; +import fs from 'fs'; +import path from 'path'; +import { logToFile } from '../utils/debug'; + +/** + * Detect whether the project targets Cloudflare Workers. + * + * Checks for: + * 1. wrangler.toml / wrangler.jsonc / wrangler.json in project root + * 2. Cloudflare adapter packages in dependencies (@react-router/cloudflare, + * @astrojs/cloudflare, @sveltejs/adapter-cloudflare, etc.) + */ +export async function detectCloudflareTarget( + installDir: string, +): Promise { + // Check for wrangler config files + const wranglerFiles = await fg('wrangler.@(toml|jsonc|json)', { + cwd: installDir, + dot: true, + }); + if (wranglerFiles.length > 0) { + logToFile( + `[cloudflare-detection] detected via wrangler config: ${wranglerFiles[0]}`, + ); + return true; + } + + // Check for Cloudflare adapter/platform packages in deps + try { + const packageJsonPath = path.join(installDir, 'package.json'); + const content = fs.readFileSync(packageJsonPath, 'utf-8'); + const packageJson = JSON.parse(content); + const allDeps = { + ...packageJson.dependencies, + ...packageJson.devDependencies, + }; + + const cloudflarePackages = Object.keys(allDeps).filter( + (dep) => + dep === '@react-router/cloudflare' || + dep === '@astrojs/cloudflare' || + dep === '@sveltejs/adapter-cloudflare' || + dep === '@sveltejs/adapter-cloudflare-workers' || + dep === '@cloudflare/workers-types' || + dep === 'wrangler', + ); + + if (cloudflarePackages.length > 0) { + logToFile( + `[cloudflare-detection] detected via packages: ${cloudflarePackages.join( + ', ', + )}`, + ); + return true; + } + } catch { + // package.json not found or invalid + } + + return false; +} + +/** + * Fetch the Cloudflare Workers reference from the skills server. + * Returns the markdown content, or null on failure. + */ +export async function fetchCloudflareReference( + skillsBaseUrl: string, +): Promise { + try { + const url = `${skillsBaseUrl}/cloudflare-workers.md`; + logToFile(`[cloudflare-detection] fetching reference from ${url}`); + const resp = await fetch(url); + if (resp.ok) { + const text = await resp.text(); + logToFile( + `[cloudflare-detection] loaded reference (${text.length} chars)`, + ); + return text; + } + logToFile( + `[cloudflare-detection] reference fetch failed: HTTP ${resp.status}`, + ); + return null; + } catch (err: any) { + logToFile(`[cloudflare-detection] reference fetch error: ${err.message}`); + return null; + } +}