@@ -185,6 +185,11 @@ const cli = yargs(hideBin(process.argv))
185185 'Run a specific context-mill skill by ID\nenv: POSTHOG_WIZARD_SKILL' ,
186186 type : 'string' ,
187187 } ,
188+ name : {
189+ describe :
190+ 'Name for account creation with --ci --signup\nenv: POSTHOG_WIZARD_NAME' ,
191+ type : 'string' ,
192+ } ,
188193 } ) ;
189194 } ,
190195 ( argv ) => {
@@ -193,25 +198,81 @@ const cli = yargs(hideBin(process.argv))
193198 // CI mode validation and TTY check
194199 if ( options . ci ) {
195200 if ( ! options . region ) options . region = 'us' ;
196- if ( ! options . apiKey ) {
201+ if ( ! options . installDir ) {
197202 setUI ( new LoggingUI ( ) ) ;
198203 getUI ( ) . intro ( 'PostHog Wizard' ) ;
199204 getUI ( ) . log . error (
200- 'CI mode requires --api-key (personal API key phx_xxx )' ,
205+ 'CI mode requires --install-dir (directory to install in )' ,
201206 ) ;
202207 process . exit ( 1 ) ;
203208 return ;
204209 }
205- if ( ! options . installDir ) {
210+ if ( ! options . apiKey && ! options . signup ) {
206211 setUI ( new LoggingUI ( ) ) ;
207212 getUI ( ) . intro ( 'PostHog Wizard' ) ;
208213 getUI ( ) . log . error (
209- 'CI mode requires --install-dir (directory to install in)' ,
214+ 'CI mode requires --api-key (personal API key phx_xxx). ' +
215+ 'To create a new account instead, use --signup --email you@example.com.' ,
216+ ) ;
217+ process . exit ( 1 ) ;
218+ return ;
219+ }
220+ if ( ! options . apiKey && options . signup && ! options . email ) {
221+ setUI ( new LoggingUI ( ) ) ;
222+ getUI ( ) . intro ( 'PostHog Wizard' ) ;
223+ getUI ( ) . log . error (
224+ 'CI --signup requires --email to create a new account.' ,
210225 ) ;
211226 process . exit ( 1 ) ;
212227 return ;
213228 }
214229 void ( async ( ) => {
230+ // If --signup but no existing key, provision a new account first and
231+ // use its personal API key for the rest of the CI install.
232+ if ( ! options . apiKey && options . signup ) {
233+ setUI ( new LoggingUI ( ) ) ;
234+ getUI ( ) . intro ( 'PostHog Wizard' ) ;
235+ try {
236+ const { provisionNewAccount } = await import (
237+ './src/utils/provisioning.js'
238+ ) ;
239+ const signupRegion = ( options . region as string ) . toUpperCase ( ) as
240+ | 'US'
241+ | 'EU' ;
242+ getUI ( ) . log . info (
243+ `Provisioning new PostHog account for ${ String (
244+ options . email ,
245+ ) } in ${ signupRegion } ...`,
246+ ) ;
247+ const result = await provisionNewAccount (
248+ options . email as string ,
249+ options . name ?? '' ,
250+ signupRegion ,
251+ ) ;
252+ if ( ! result . personalApiKey ) {
253+ getUI ( ) . log . error (
254+ 'Provisioning succeeded but no personal API key was returned — cannot continue install.' ,
255+ ) ;
256+ process . exit ( 1 ) ;
257+ return ;
258+ }
259+ getUI ( ) . log . success ( 'Account ready.' ) ;
260+ getUI ( ) . log . info ( ` Project API Key: ${ result . projectApiKey } ` ) ;
261+ getUI ( ) . log . info ( ` Personal API Key: ${ result . personalApiKey } ` ) ;
262+ getUI ( ) . log . info ( ` Host: ${ result . host } ` ) ;
263+ options . apiKey = result . personalApiKey ;
264+ if ( options . projectId == null ) {
265+ options . projectId = result . projectId ;
266+ }
267+ } catch ( error ) {
268+ const msg =
269+ error instanceof Error ? error . message : String ( error ) ;
270+ getUI ( ) . log . error ( `Provisioning failed: ${ msg } ` ) ;
271+ process . exit ( 1 ) ;
272+ return ;
273+ }
274+ }
275+
215276 const { posthogIntegrationConfig } = await import (
216277 './src/lib/workflows/posthog-integration/index.js'
217278 ) ;
@@ -428,6 +489,90 @@ const cli = yargs(hideBin(process.argv))
428489 . help ( ) ;
429490 } ) ;
430491
492+ cli . command (
493+ 'provision' ,
494+ 'Create a new PostHog account (headless, no TUI)' ,
495+ ( yargs ) => {
496+ return yargs
497+ . options ( {
498+ email : {
499+ describe : 'Email address for the new account' ,
500+ type : 'string' as const ,
501+ demandOption : true ,
502+ } ,
503+ region : {
504+ describe : 'Cloud region (us or eu)' ,
505+ choices : [ 'us' , 'eu' ] as const ,
506+ default : 'us' ,
507+ } ,
508+ name : {
509+ describe : 'Name for the new account' ,
510+ type : 'string' as const ,
511+ default : '' ,
512+ } ,
513+ json : {
514+ describe :
515+ 'Emit JSON result to stdout (defaults to true when stdout is not a TTY)' ,
516+ type : 'boolean' as const ,
517+ } ,
518+ } )
519+ . example ( 'wizard provision --email matt+test@posthog.com --region us' , '' )
520+ . example (
521+ 'wizard provision --email user@example.com --region eu --json' ,
522+ '' ,
523+ ) ;
524+ } ,
525+ ( argv ) => {
526+ const email = argv . email ;
527+ const region = argv . region . toUpperCase ( ) as 'US' | 'EU' ;
528+ const name = argv . name ?? '' ;
529+ const jsonMode =
530+ argv . json === undefined ? ! process . stdout . isTTY : argv . json ;
531+
532+ if ( ! jsonMode ) {
533+ setUI ( new LoggingUI ( ) ) ;
534+ }
535+
536+ void ( async ( ) => {
537+ try {
538+ const { provisionNewAccount } = await import (
539+ './src/utils/provisioning.js'
540+ ) ;
541+ if ( ! jsonMode ) {
542+ getUI ( ) . log . info ( `Provisioning account for ${ email } in ${ region } ...` ) ;
543+ }
544+ const result = await provisionNewAccount ( email , name , region ) ;
545+ if ( jsonMode ) {
546+ process . stdout . write ( `${ JSON . stringify ( result ) } \n` ) ;
547+ } else {
548+ getUI ( ) . log . success ( 'Account provisioned successfully:' ) ;
549+ getUI ( ) . log . info ( ` API Key: ${ result . projectApiKey } ` ) ;
550+ getUI ( ) . log . info ( ` Host: ${ result . host } ` ) ;
551+ getUI ( ) . log . info ( ` Project ID: ${ result . projectId } ` ) ;
552+ getUI ( ) . log . info ( ` Account ID: ${ result . accountId } ` ) ;
553+ getUI ( ) . log . info ( ` Access Token: ${ result . accessToken } ` ) ;
554+ getUI ( ) . log . info ( ` Refresh Token: ${ result . refreshToken } ` ) ;
555+ if ( result . personalApiKey ) {
556+ getUI ( ) . log . info ( ` Personal API Key: ${ result . personalApiKey } ` ) ;
557+ }
558+ }
559+ process . exit ( 0 ) ;
560+ } catch ( error ) {
561+ const msg = error instanceof Error ? error . message : String ( error ) ;
562+ const code = msg . includes ( 'already associated' )
563+ ? 'email_exists'
564+ : 'provisioning_failed' ;
565+ if ( jsonMode ) {
566+ process . stderr . write ( `${ JSON . stringify ( { error : msg , code } ) } \n` ) ;
567+ } else {
568+ getUI ( ) . log . error ( `Provisioning failed: ${ msg } ` ) ;
569+ }
570+ process . exit ( 1 ) ;
571+ }
572+ } ) ( ) ;
573+ } ,
574+ ) ;
575+
431576// ── Skill-based workflow subcommands (derived from registry) ─────────
432577for ( const wfConfig of getSubcommandWorkflows ( ) ) {
433578 cli . command (
0 commit comments