Skip to content
Open
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: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ npx @posthog/wizard revenue
Requires PostHog and Stripe SDKs already installed. Supports `--ci` with the
same flags as the main wizard.

## Data Warehouse

Detect data sources your project already uses (Postgres, MySQL, MongoDB,
Snowflake, BigQuery, Stripe, …) and connect them to PostHog's data warehouse:

```bash
npx @posthog/wizard warehouse
```

The wizard scans your dependencies and `.env` key names (never the values) to
identify sources. Database and API-key sources are created from the terminal;
OAuth sources open the PostHog app's new-source flow in your browser. The main
wizard also offers this as a follow-up when it detects a source during a normal
run.

## Headless signup + install (agents / CI)

For a fully non-interactive first-run (no existing PostHog account, no TTY,
Expand Down
7 changes: 7 additions & 0 deletions src/lib/programs/posthog-integration/detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
gatherFrameworkContext,
checkFrameworkVersion,
} from '@lib/detection/index';
import { detectWarehouseSources } from '@lib/warehouse-sources/detect';

export async function detectPostHogIntegration(
ctx: ProgramReadyContext,
Expand Down Expand Up @@ -69,5 +70,11 @@ export async function detectPostHogIntegration(
ctx.addDiscoveredFeature(feature);
}

// Data warehouse source discovery — drives the post-run soft prompt.
const warehouseSources = detectWarehouseSources(installDir);
if (warehouseSources.length > 0) {
ctx.setFrameworkContext('detectedWarehouseSources', warehouseSources);
}

ctx.setDetectionComplete();
}
13 changes: 13 additions & 0 deletions src/lib/programs/posthog-integration/steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@ export const POSTHOG_INTEGRATION_PROGRAM: ProgramStep[] = [
screenId: 'outro',
isComplete: (session) => session.outroDismissed,
},
{
id: 'warehouse-offer',
label: 'Data warehouse',
screenId: 'warehouse-offer',
// Only shown when the detect step found a data warehouse source.
show: (session) =>
(
(session.frameworkContext.detectedWarehouseSources as
| unknown[]
| undefined) ?? []
).length > 0,
isComplete: (session) => session.warehouseOfferDismissed,
},
{
id: 'keep-skills',
label: 'Keep Skills',
Expand Down
3 changes: 3 additions & 0 deletions src/lib/programs/program-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import type { ProgramConfig } from './program-step.js';
import { posthogIntegrationConfig } from './posthog-integration/index.js';
import { revenueAnalyticsConfig } from './revenue-analytics/index.js';
import { warehouseSourceConfig } from './warehouse-source/index.js';
import { auditConfig } from './audit/index.js';
import { eventsAuditConfig } from './events-audit/index.js';
import { audit3000Config } from './audit-3000/index.js';
Expand All @@ -36,6 +37,7 @@ const agentSkillConfig: ProgramConfig = {
export const PROGRAM_REGISTRY = [
posthogIntegrationConfig,
revenueAnalyticsConfig,
warehouseSourceConfig,
auditConfig,
eventsAuditConfig,
audit3000Config,
Expand All @@ -54,6 +56,7 @@ export const PROGRAM_REGISTRY = [
export const Program = {
PostHogIntegration: posthogIntegrationConfig.id,
RevenueAnalyticsSetup: revenueAnalyticsConfig.id,
WarehouseSource: warehouseSourceConfig.id,
Migration: migrationConfig.id,
Audit: auditConfig.id,
EventsAudit: eventsAuditConfig.id,
Expand Down
7 changes: 7 additions & 0 deletions src/lib/programs/warehouse-source/content/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Warehouse-source learn-deck. Delegates to the generic skill deck; replace
* this re-export with a program-specific script when the data warehouse
* narrative grows its own diagrams or talking points.
*/

export { getContentBlocks } from '@lib/programs/agent-skill/content/index';
82 changes: 82 additions & 0 deletions src/lib/programs/warehouse-source/detect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Warehouse-source program detection step.
*
* Thin adapter over `detectWarehouseSources` that writes results into
* frameworkContext for the intro screen, plus the `[ABORT]` cases the
* data-warehouse-source-setup skill can emit.
*/

import { existsSync, statSync } from 'fs';
import type { WizardSession } from '@lib/wizard-session';
import type { AbortCase } from '@lib/agent/agent-runner';
import { detectWarehouseSources } from '@lib/warehouse-sources/detect';

/** Structured detection errors rendered by the intro screen. */
export type WarehouseDetectError =
| {
kind: 'bad-directory';
path: string;
reason: 'missing' | 'not-dir' | 'unreadable';
}
| { kind: 'no-sources' };

/** `[ABORT] <reason>` cases the skill can emit. */
export const WAREHOUSE_ABORT_CASES: AbortCase[] = [
{
// Skill emits: [ABORT] No data source detected
match: /^no data source detected$/i,
message: 'No data source detected',
body:
'The agent could not confirm a data warehouse source to connect. ' +
'Run this command from a project that uses a supported source ' +
'(a database, Stripe, etc.).',
docsUrl: 'https://posthog.com/docs/data-warehouse',
},
{
// Skill emits: [ABORT] Source creation failed
match: /^source creation failed$/i,
message: 'Source creation failed',
body:
'PostHog could not create the data warehouse source with the ' +
'credentials provided. Double-check the connection details and try ' +
'again, or set the source up directly in the PostHog app.',
docsUrl: 'https://posthog.com/docs/data-warehouse',
},
];

/**
* Scan `session.installDir` for warehouse-source signals. Writes the detected
* sources (or a `detectError`) into frameworkContext for the intro screen.
*/
export function detectWarehousePrerequisites(
session: WizardSession,
setFrameworkContext: (key: string, value: unknown) => void,
): void {
const fail = (error: WarehouseDetectError) =>
setFrameworkContext('detectError', error);

const installDir = session.installDir;

if (!existsSync(installDir)) {
fail({ kind: 'bad-directory', path: installDir, reason: 'missing' });
return;
}
try {
if (!statSync(installDir).isDirectory()) {
fail({ kind: 'bad-directory', path: installDir, reason: 'not-dir' });
return;
}
} catch {
fail({ kind: 'bad-directory', path: installDir, reason: 'unreadable' });
return;
}

const sources = detectWarehouseSources(installDir);

if (sources.length === 0) {
fail({ kind: 'no-sources' });
return;
}

setFrameworkContext('detectedWarehouseSources', sources);
}
73 changes: 73 additions & 0 deletions src/lib/programs/warehouse-source/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { ProgramConfig } from '@lib/programs/program-step';
import type { ProgramRun } from '@lib/agent/agent-runner';
import type { WizardSession } from '@lib/wizard-session';
import type { DetectedSource } from '@lib/warehouse-sources/types';
import { WAREHOUSE_SOURCE_PROGRAM } from './steps.js';
import { WAREHOUSE_ABORT_CASES } from './detect.js';
import { getContentBlocks } from './content/index.js';

function getDetectedSources(session: WizardSession): DetectedSource[] {
return (
(session.frameworkContext.detectedWarehouseSources as
| DetectedSource[]
| undefined) ?? []
);
}

/**
* Inject the detected sources (and their creation mode) into the prompt so the
* skill knows what to set up. The *how* — in-CLI creation vs deep-link, field
* collection, validation — lives in the skill, not here.
*/
function buildPrompt(session: WizardSession): string {
const sources = getDetectedSources(session);
if (sources.length === 0) {
return 'Set up a data warehouse source for this project.';
}

const lines = sources.map(
(s) =>
`- ${s.label} (kind: ${s.kind}, mode: ${s.mode}) — ${s.matchedSignal}`,
);

return [
'The wizard detected the following data warehouse sources in this project:',
...lines,
'',
'Set these up in PostHog following the skill instructions: create `in-cli` ' +
'sources directly via the PostHog MCP after collecting credentials; for ' +
'`deep-link` sources, provide the user the pre-filled new-source URL.',
].join('\n');
}

export const warehouseSourceConfig: ProgramConfig = {
command: 'warehouse',
description:
'Detect and connect a data warehouse source (Postgres, Stripe, …)',
id: 'warehouse-source',
skillId: 'data-warehouse-source-setup',
steps: WAREHOUSE_SOURCE_PROGRAM,
getContentBlocks,
reportFile: 'posthog-warehouse-report.md',
allowedTools: ['Agent'],
run: (session: WizardSession): Promise<ProgramRun> =>
Promise.resolve({
skillId: 'data-warehouse-source-setup',
integrationLabel: 'data-warehouse-source-setup',
customPrompt: () => buildPrompt(session),
successMessage: 'Data warehouse source connected!',
reportFile: 'posthog-warehouse-report.md',
docsUrl: 'https://posthog.com/docs/data-warehouse',
spinnerMessage: 'Connecting your data source...',
estimatedDurationMinutes: 5,
abortCases: WAREHOUSE_ABORT_CASES,
}),
requires: ['posthog-integration'],
};

export { WAREHOUSE_SOURCE_PROGRAM } from './steps.js';
export {
detectWarehousePrerequisites,
WAREHOUSE_ABORT_CASES,
type WarehouseDetectError,
} from './detect.js';
54 changes: 54 additions & 0 deletions src/lib/programs/warehouse-source/steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Warehouse-source program step list.
*
* The detect step scans for warehouse-source signals. The skill install and
* agent run live in the program runner (see agent-runner.ts). The skill drives
* both in-CLI source creation and deep-link emission per detected source.
*/

import type { ProgramStep } from '@lib/programs/program-step';
import { RunPhase } from '@lib/wizard-session';
import { detectWarehousePrerequisites } from './detect.js';

export const WAREHOUSE_SOURCE_PROGRAM: ProgramStep[] = [
{
id: 'detect',
label: 'Detecting data sources',
// Headless step: no screen. onReady scans installDir and writes the
// detected sources (or a detectError) to frameworkContext for the
// intro screen to render.
onReady: (ctx) =>
detectWarehousePrerequisites(ctx.session, ctx.setFrameworkContext),
},
{
id: 'intro',
label: 'Welcome',
screenId: 'warehouse-intro',
gate: (session) => session.setupConfirmed,
},
{
id: 'auth',
label: 'Authentication',
screenId: 'auth',
isComplete: (session) => session.credentials !== null,
},
{
id: 'run',
label: 'Data warehouse',
screenId: 'run',
isComplete: (session) =>
session.runPhase === RunPhase.Completed ||
session.runPhase === RunPhase.Error,
},
{
id: 'outro',
label: 'Done',
screenId: 'outro',
isComplete: (session) => session.outroDismissed,
},
{
id: 'skills',
label: 'Skills',
screenId: 'keep-skills',
},
];
Loading
Loading