diff --git a/packages/core/src/setup/prompts.ts b/packages/core/src/setup/prompts.ts index bfe4aad109..a3ce543f32 100644 --- a/packages/core/src/setup/prompts.ts +++ b/packages/core/src/setup/prompts.ts @@ -1,10 +1,34 @@ import { BASE_CATEGORY_ENUM } from "@posthog/core/setup/types"; -export const WIZARD_PROMPT = `/instrument-integration +// Appended to every prompt that performs the initial PostHog SDK install/init. Writing env vars to +// .env files does nothing for the deployed app, and no run can read the hosting +// provider's env, so the checklist is unconditional. Where it gets delivered is +// not: these prompts run in cloud, worktree and local modes (WorkspaceMode), and +// a PR is never opened automatically in any of them — hence the two-branch phrasing. +export const DEPLOYMENT_ENV_VAR_PROMPT = ` -After the integration is wired up, also instrument error tracking and session replay (run \`/instrument-error-tracking\`, then add session replay if the framework's posthog-js config supports it). +## Required: hand off an env var checklist -Run autonomously with sensible defaults — do not ask the user questions. If the PostHog API key isn't already in the project's env files and you can't read it from the PostHog MCP server, leave a placeholder env var and note it in the PR body rather than blocking.`; +Adding env vars to \`.env\`, \`.env.local\` or \`.env.example\` only covers local development. It does not set them on the deployed app, so production will send no events until someone sets them in the hosting provider. You cannot read or set the hosting provider's env from here, so always assume they are missing and always hand off the checklist below. + +Detect the deployment target from repo-root markers: \`vercel.json\` or \`.vercel/\` → Vercel; \`netlify.toml\` → Netlify; \`wrangler.toml\` or \`wrangler.jsonc\` → Cloudflare; \`fly.toml\` → Fly.io. If nothing matches, call it "your hosting provider" and give the generic steps. + +Deliver it wherever this run ends, replacing in the headings below with the detected provider name (or "your hosting provider" if none matched): +- If you open a pull request, its body must lead with the checklist — above the summary of changes — under the heading "Before you merge: set environment variables in ". +- If you are working against a local checkout and not opening a pull request, print the same checklist to the user as the final message of the run, under the heading "Before you deploy: set environment variables in ". + +Either way the checklist contains: + +- Every env var key name the integration reads, exactly as it appears in the code (e.g. the PostHog public key and host vars for this framework). +- Steps for the detected provider: + - Vercel: \`vercel env add production\` per key, or Project Settings → Environment Variables. + - Netlify: \`netlify env:set \` per key, or Site configuration → Environment variables. + - Cloudflare: \`wrangler secret put \` per key, or \`[vars]\` in the wrangler config for non-secret values. + - Fly.io: \`fly secrets set =\` per key. + - Unknown provider: instruct the user to add each key wherever their production environment defines env vars, and to redeploy. +- A plain statement that until these are set in , the production deployment will send no events to PostHog. + +Never put secret values in a PR body — key names only. Point the user at their PostHog project settings for the value.`; const DISCOVERY_PROMPT_BASE = `You are analyzing this codebase to find the highest-value first tasks for the developer. diff --git a/packages/core/src/setup/suggestions.test.ts b/packages/core/src/setup/suggestions.test.ts index 610f19ff0e..e847fac1a9 100644 --- a/packages/core/src/setup/suggestions.test.ts +++ b/packages/core/src/setup/suggestions.test.ts @@ -67,7 +67,7 @@ describe("buildPosthogSetupSuggestion", () => { it("returns the install suggestion when not installed", () => { const task = buildPosthogSetupSuggestion("not_installed"); expect(task.id).toBe("posthog-setup"); - expect(task.prompt).toBe("/instrument-integration"); + expect(task.prompt?.startsWith("/instrument-integration")).toBe(true); }); it("returns the finish-init suggestion when installed but not initialized", () => { @@ -75,4 +75,56 @@ describe("buildPosthogSetupSuggestion", () => { expect(task.id).toBe("posthog-finish-init"); expect(task.prompt).toContain("skip install steps"); }); + + it.each(["not_installed", "installed_no_init"] as const)( + "requires a hosting-provider env var checklist (%s)", + (state) => { + const prompt = buildPosthogSetupSuggestion(state).prompt ?? ""; + expect(prompt).toContain("production will send no events"); + expect(prompt).toContain("Never put secret values in a PR body"); + }, + ); + + // The prompt runs in cloud, worktree and local modes, and no mode opens a PR + // automatically — so it has to name a delivery target for both endings. + it.each(["not_installed", "installed_no_init"] as const)( + "routes the checklist to a PR body or the final message, per run type (%s)", + (state) => { + const prompt = buildPosthogSetupSuggestion(state).prompt ?? ""; + expect(prompt).toContain( + "Before you merge: set environment variables in ", + ); + expect(prompt).toContain( + "Before you deploy: set environment variables in ", + ); + expect(prompt).toContain("not opening a pull request"); + }, + ); + + it.each([ + ["Vercel", "vercel env add production"], + ["Netlify", "netlify env:set "], + ["Cloudflare", "wrangler secret put "], + ["Fly.io", "fly secrets set ="], + ])("gives %s-specific env var steps", (provider, command) => { + const prompt = buildPosthogSetupSuggestion("not_installed").prompt ?? ""; + expect(prompt).toContain(provider); + expect(prompt).toContain(command); + }); + + it.each([ + ["vercel.json", "Vercel"], + ["netlify.toml", "Netlify"], + ["wrangler.toml", "Cloudflare"], + ["fly.toml", "Fly.io"], + ])("maps the %s marker to %s", (marker, provider) => { + const prompt = buildPosthogSetupSuggestion("not_installed").prompt ?? ""; + expect(prompt).toContain(`\`${marker}\``); + expect(prompt).toContain(provider); + }); + + it("falls back to a generic provider when no marker matches", () => { + const prompt = buildPosthogSetupSuggestion("not_installed").prompt ?? ""; + expect(prompt).toContain('call it "your hosting provider"'); + }); }); diff --git a/packages/core/src/setup/suggestions.ts b/packages/core/src/setup/suggestions.ts index fa406195c0..82462f85d4 100644 --- a/packages/core/src/setup/suggestions.ts +++ b/packages/core/src/setup/suggestions.ts @@ -1,3 +1,4 @@ +import { DEPLOYMENT_ENV_VAR_PROMPT } from "@posthog/core/setup/prompts"; import type { DiscoveredTask } from "@posthog/core/setup/types"; export interface StaleFlagPayload { @@ -62,8 +63,8 @@ export function buildPosthogSetupSuggestion( impact: "Without PostHog wired in, you have no visibility into how users interact with the product, no error or session-replay coverage, and no way to gate releases behind feature flags.", recommendation: - 'Click "Implement as new task" — the agent runs the bundled instrument-integration skill, sets up env vars, installs the SDK with your project\'s package manager, and opens a PR.', - prompt: "/instrument-integration", + 'Click "Implement as new task" — the agent runs the bundled instrument-integration skill, sets up env vars, installs the SDK with your project\'s package manager, and opens a PR listing the env vars you still need to set in your hosting provider.', + prompt: `/instrument-integration${DEPLOYMENT_ENV_VAR_PROMPT}`, }; } return { @@ -76,8 +77,9 @@ export function buildPosthogSetupSuggestion( impact: "Until init runs, all PostHog calls are no-ops — you'll see no events in the project, no error reports, and no session replays despite the SDK being installed.", recommendation: - 'Click "Implement as new task" — the agent adds the init call and provider component for your framework, sets up the public-token + host env vars, and opens a PR. The SDK package itself is left alone.', - prompt: - "/instrument-integration\n\nThe SDK is already declared in this repo — skip install steps and focus on adding the init call, provider, and env vars.", + 'Click "Implement as new task" — the agent adds the init call and provider component for your framework, sets up the public-token + host env vars, and opens a PR listing the env vars you still need to set in your hosting provider. The SDK package itself is left alone.', + prompt: `/instrument-integration + +The SDK is already declared in this repo — skip install steps and focus on adding the init call, provider, and env vars.${DEPLOYMENT_ENV_VAR_PROMPT}`, }; }