Skip to content
Draft
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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,10 @@ npx @posthog/wizard@latest --ci --signup \
--install-dir .
```

Optional flags: `--name "Your Name"`, `--region eu` (default `us`).
Optional flags: `--name "Your Name"`, `--region eu` (default `us`),
`--goal "the task you were given"`. An agent that starts the wizard should pass
`--goal`. PostHog records it on the new account's signup as the prompt that led
to PostHog. A browser signup that the wizard starts records it too.

### Provision only

Expand Down
1 change: 1 addition & 0 deletions src/cli/runners/run-non-interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export function runNonInteractive(
localPosthog: options.localPosthog as boolean | undefined,
apiKey: (options.apiKey as string) ?? readApiKeyFromEnv() ?? undefined,
email: options.email as string | undefined,
goal: options.goal as string | undefined,
projectId: options.projectId as string | undefined,
baseUrl: options.baseUrl as string | undefined,
benchmark: options.benchmark as boolean | undefined,
Expand Down
1 change: 1 addition & 0 deletions src/cli/runners/run-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function tuiSessionArgs(
apiKey: options.apiKey as string | undefined,
projectId: options.projectId as string | undefined,
email: options.email as string | undefined,
goal: options.goal as string | undefined,
baseUrl: options.baseUrl as string | undefined,
benchmark: options.benchmark as boolean | undefined,
yaraReport: options.yaraReport as boolean | undefined,
Expand Down
5 changes: 5 additions & 0 deletions src/cli/wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ export const GLOBAL_OPTIONS = {
'Email address for signup (used with --signup)\nenv: POSTHOG_WIZARD_EMAIL',
type: 'string' as const,
},
goal: {
describe:
'The task you or your agent are using PostHog for, sent with a new signup\nenv: POSTHOG_WIZARD_GOAL',
type: 'string' as const,
},
// ── Internal modes ─────────────────────────────────────────────────
// Hidden from `--help`.
// NB: the experimental headless flag is deliberately NOT global. Supported
Expand Down
4 changes: 4 additions & 0 deletions src/services/session/wizard-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export interface WizardSession extends ProgramSession {
/** A pre-issued gateway token a dev or test `--ci` run logs in with; the login puts it on the credentials. */
ciGateway: GatewayCredential | null;
email?: string;
/** `--goal`: the task the run was started for, sent with a new signup. */
goal?: string;
region?: CloudRegion;

/**
Expand Down Expand Up @@ -89,6 +91,7 @@ export type SessionArgs = {
localPosthog?: boolean;
apiKey?: string;
email?: string;
goal?: string;
region?: CloudRegion;
baseUrl?: string;
integration?: Integration;
Expand All @@ -115,6 +118,7 @@ export function buildSession(args: SessionArgs): WizardSession {
localMcp: local.localMcp,
apiKey: args.apiKey,
email: args.email,
goal: args.goal?.trim() || undefined,
region: args.region,
// `--local-posthog` is sugar over `--base-url`, which every downstream URL
// helper already honours. An explicit `--base-url` is more specific, so it wins.
Expand Down
37 changes: 37 additions & 0 deletions src/shared/utils/__tests__/provisioning.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,43 @@ describe('provisionNewAccount', () => {
);
});

it('sends the goal in the account configuration', async () => {
mockedAxios.post
.mockResolvedValueOnce({
data: { id: 'req_6', type: 'oauth', oauth: { code: 'code_6' } },
})
.mockResolvedValueOnce({
data: {
token_type: 'bearer',
access_token: 'pha_6',
refresh_token: 'phr_6',
expires_in: 3600,
},
})
.mockResolvedValueOnce({
data: {
status: 'complete',
id: '99',
complete: {
access_configuration: {
api_key: 'phc_us',
host: 'https://us.posthog.com',
},
},
},
});

await provisionNewAccount('goal@example.com', '', 'US', {
goal: 'Track signups in my Next.js app',
});

const accountCall = mockedAxios.post.mock.calls[0];
expect((accountCall[1] as Record<string, unknown>).configuration).toEqual({
region: 'US',
goal: 'Track signups in my Next.js app',
});
});

it('routes US provisioning to the US host and client', async () => {
mockedAxios.post
.mockResolvedValueOnce({
Expand Down
3 changes: 3 additions & 0 deletions src/shared/utils/provisioning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ export async function provisionNewAccount(
orgName?: string;
projectName?: string;
baseUrl?: string;
/** `--goal`, recorded on the new account's signup. */
goal?: string;
/** Scope list to request; defaults to `WIZARD_PROVISIONING_SCOPES`. */
scopes?: readonly string[];
},
Expand All @@ -319,6 +321,7 @@ export async function provisionNewAccount(
configuration: {
region,
...(opts?.orgName ? { organization_name: opts.orgName } : {}),
...(opts?.goal ? { goal: opts.goal } : {}),
},
},
{
Expand Down
1 change: 1 addition & 0 deletions src/tui/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export async function oauthLogin(
signup: session.signup,
projectId: session.projectId,
email: session.email,
goal: session.goal,
region: session.region,
baseUrl: session.baseUrl,
localMcp: session.localMcp,
Expand Down
8 changes: 8 additions & 0 deletions src/tui/auth/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ interface OAuthConfig {
* OAuth server and selects the matching client ID.
*/
baseUrl?: string;
/**
* `--goal`. Rides on the authorize URL, so a signup the browser starts from
* it records the goal as the prompt that led to PostHog.
*/
goal?: string;
}

function getLocalOAuthOrigin(port: number): string {
Expand Down Expand Up @@ -494,6 +499,9 @@ export async function performOAuthFlow(
// Pre-select this project on the consent screen so the user just clicks Authorize.
authUrl.searchParams.set('team_id', String(config.projectId));
}
if (config.goal) {
authUrl.searchParams.set('goal', config.goal);
}

// UTM-tag both kickoff URLs so the journey into the app is
// attributable to the wizard command that started it.
Expand Down
8 changes: 8 additions & 0 deletions src/tui/auth/project-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export async function getOrAskForProjectData(
/** Where login progress and errors are shown. */
store: WizardStore;
email?: string;
/** `--goal`, sent with a new signup. */
goal?: string;
region?: CloudRegion;
/** Explicit base URL override (`--base-url`, from `session.baseUrl`). When
* set, pins every PostHog origin and bypasses region resolution. */
Expand Down Expand Up @@ -134,6 +136,7 @@ export async function getOrAskForProjectData(
store,
signup: _options.signup,
email: _options.email,
goal: _options.goal,
region: _options.region,
baseUrl: _options.baseUrl,
programId: _options.programId,
Expand Down Expand Up @@ -175,6 +178,7 @@ async function askForWizardLogin(options: {
store: WizardStore;
signup: boolean;
email?: string;
goal?: string;
region?: CloudRegion;
/** Explicit base URL override (`--base-url`); pins every PostHog origin. */
baseUrl?: string;
Expand All @@ -200,6 +204,7 @@ async function askForWizardLogin(options: {
options.programId,
options.scopeAdditions,
options.projectId,
options.goal,
);
}

Expand All @@ -214,6 +219,7 @@ async function askForWizardLogin(options: {
signup: false,
projectId: options.projectId,
baseUrl: options.baseUrl,
goal: options.goal,
},
store,
);
Expand Down Expand Up @@ -333,6 +339,7 @@ async function askForProvisioningSignup(
programId?: ProgramId | null,
scopeAdditions?: readonly string[],
projectId?: number,
goal?: string,
): Promise<ProjectData> {
if (!email || !email.includes('@')) {
store.pushStatus(
Expand All @@ -351,6 +358,7 @@ async function askForProvisioningSignup(
orgName,
projectName,
baseUrl,
goal,
scopes: scopeAdditions
? withScopeAdditions(WIZARD_PROVISIONING_SCOPES, scopeAdditions)
: getProvisioningScopesForProgram(programId),
Expand Down
Loading