@@ -8,6 +8,7 @@ import { SessionManager, getDefaultDataDir } from "./session-manager.js";
88import { runLoginCommand } from "./human-handoff.js" ; import { createAdapter } from "./create-adapter.js" ;
99import type { FrameworkConfig , DaemonStatus , AdapterStatus } from "./types.js" ;
1010import { getLogger } from "./logger.js" ;
11+ import { readCoreVersion , satisfies , readAdapterVersion } from "./version-check.js" ;
1112
1213const log = getLogger ( "cli" ) ;
1314
@@ -28,6 +29,8 @@ async function main(): Promise<void> {
2829 return cmdStatus ( args ) ;
2930 case "config" :
3031 return cmdConfig ( args ) ;
32+ case "doctor" :
33+ return cmdDoctor ( args ) ;
3134 case "create-adapter" : {
3235 const name = args [ 0 ] ;
3336 if ( ! name ) {
@@ -386,6 +389,7 @@ Commands:
386389 status Show daemon status
387390 config cursor Generate Cursor MCP settings JSON
388391 create-adapter <name> Scaffold a new standalone adapter package
392+ doctor Check version compatibility for all configured adapters
389393
390394Options (start):
391395 --adapter <pkg> Add an adapter by npm package name (repeatable)
@@ -398,11 +402,149 @@ Examples:
398402 browserkit logout linkedin
399403 browserkit reload google-discover
400404 browserkit status
405+ browserkit doctor
401406 browserkit config cursor
402407 browserkit create-adapter my-site
403408` ) ;
404409}
405410
411+ // ─── doctor ──────────────────────────────────────────────────────────────────
412+
413+ async function cmdDoctor(args: string[]): Promise< void > {
414+ const config = await resolveConfig ( args ) ;
415+ const coreVer = readCoreVersion ( ) ;
416+
417+ const col = ( s : string , w : number ) => s . slice ( 0 , w ) . padEnd ( w ) ;
418+ const issues : string [ ] = [ ] ;
419+
420+ // ── Environment summary ───────────────────────────────────────────────────
421+ console . log ( ) ;
422+ console . log ( ` @browserkit-dev/core ${ coreVer } ` ) ;
423+ const patchrightVer = getPatchrightVersion ( ) ;
424+ console . log ( ` patchright ${ patchrightVer ?? "(not found)" } ` ) ;
425+ console . log ( ` node ${ process . versions . node } ` ) ;
426+ console . log ( ) ;
427+
428+ // ── Adapter table ─────────────────────────────────────────────────────────
429+ const adapterKeys = Object . keys ( config . adapters ?? { } ) ;
430+ if ( adapterKeys . length === 0 ) {
431+ console . log ( " No adapters configured." ) ;
432+ console . log ( ) ;
433+ return ;
434+ }
435+
436+ console.log(
437+ ` ${ col ( "Adapter" , 36 ) } ${ col ( "Version" , 10 ) } ${ col ( "Core req" , 12 ) } Status`
438+ );
439+ console.log(` ${ "-" . repeat ( 72 ) } `);
440+
441+ for (const key of adapterKeys) {
442+ const adapterConfig = config . adapters [ key ] ;
443+ if ( ! adapterConfig ) continue ;
444+
445+ // Try to load the adapter module to read its metadata
446+ let adapterVersion = readAdapterVersion ( key ) ?? "?" ;
447+ let minCoreVer : string | undefined ;
448+ let reqs : Record < string , unknown > | undefined ;
449+ let loadError : string | undefined ;
450+
451+ try {
452+ const mod = await import ( key ) . catch ( ( ) => null ) ;
453+ const adapter = mod ?. default ?? mod ;
454+ if ( adapter && typeof adapter === "object" ) {
455+ minCoreVer = typeof adapter . minCoreVersion === "string" ? adapter . minCoreVersion : undefined ;
456+ reqs = adapter . requirements ;
457+ }
458+ } catch ( err ) {
459+ loadError = err instanceof Error ? err . message . split ( "\n" ) [ 0 ] : String ( err ) ;
460+ }
461+
462+ // Short display name
463+ const displayName = key . startsWith ( "/" ) || key . startsWith ( "." )
464+ ? path . basename ( path . dirname ( key ) )
465+ : key . replace ( "@browserkit-dev/" , "" ) ;
466+
467+ let status : string ;
468+ if ( loadError ) {
469+ status = `ERROR — ${ loadError } ` ;
470+ issues . push ( `${ displayName } : failed to load — ${ loadError } ` ) ;
471+ } else if ( minCoreVer && ! satisfies ( coreVer , minCoreVer ) ) {
472+ status = `MISMATCH — needs core >= ${ minCoreVer } (have ${ coreVer } )` ;
473+ issues . push ( `${ displayName } : needs @browserkit-dev/core >= ${ minCoreVer } ` ) ;
474+ } else {
475+ status = "OK" ;
476+ }
477+
478+ const coreReq = minCoreVer ? `>= ${ minCoreVer } ` : "(any)" ;
479+ console . log ( ` ${ col ( displayName , 36 ) } ${ col ( adapterVersion , 10 ) } ${ col ( coreReq , 12 ) } ${ status } ` ) ;
480+
481+ // Requirements vs config mismatch hints
482+ if ( reqs && typeof reqs === "object" ) {
483+ const r = reqs as {
484+ chromeChannelRequired ?: boolean ;
485+ deviceEmulation ?: string ;
486+ useCloakBrowser ?: boolean ;
487+ headedLoginRequired ?: boolean ;
488+ } ;
489+ if ( r . chromeChannelRequired && ! adapterConfig . channel ) {
490+ const msg = ` ${ " " . repeat ( 36 ) } hint: adapter recommends channel:"chrome" in config` ;
491+ console . log ( msg ) ;
492+ issues . push ( `${ displayName } : missing channel:"chrome" in config` ) ;
493+ }
494+ if ( r . deviceEmulation && ! adapterConfig . deviceEmulation ) {
495+ const msg = ` ${ " " . repeat ( 36 ) } hint: adapter recommends deviceEmulation:"${ r . deviceEmulation } "` ;
496+ console . log ( msg ) ;
497+ issues . push ( `${ displayName } : missing deviceEmulation:"${ r . deviceEmulation } " in config` ) ;
498+ }
499+ if ( r . useCloakBrowser && ! adapterConfig . antiDetection ?. useCloakBrowser ) {
500+ const msg = ` ${ " " . repeat ( 36 ) } hint: adapter recommends antiDetection.useCloakBrowser:true` ;
501+ console . log ( msg ) ;
502+ issues . push ( `${ displayName } : missing antiDetection.useCloakBrowser:true in config` ) ;
503+ }
504+ }
505+ }
506+
507+ // ── Patchright peer dep check ─────────────────────────────────────────────
508+ console.log();
509+ const patchrightOk = checkPatchrightPeer(patchrightVer);
510+ if (patchrightOk) {
511+ console . log ( ` patchright: OK (${ patchrightVer } satisfies peer dep ^1.51.0)` ) ;
512+ } else {
513+ const msg = `patchright ${ patchrightVer ?? "(not found)" } — expected ^1.51.0` ;
514+ console . log ( ` patchright: WARN — ${ msg } ` ) ;
515+ issues . push ( msg ) ;
516+ }
517+
518+ // ── Summary ───────────────────────────────────────────────────────────────
519+ console.log();
520+ if (issues.length === 0) {
521+ console . log ( " Everything looks good." ) ;
522+ } else {
523+ console . log ( ` ${ issues . length } issue${ issues . length > 1 ? "s" : "" } found.` ) ;
524+ }
525+ console.log();
526+
527+ process.exit(issues.length > 0 ? 1 : 0 ) ;
528+ }
529+
530+ function getPatchrightVersion ( ) : string | null {
531+ try {
532+ const pkgPath = new URL ( "../node_modules/patchright/package.json" , import . meta. url ) ;
533+ const pkg = JSON . parse ( fs . readFileSync ( pkgPath , "utf8" ) ) as { version ? : string } ;
534+ return pkg . version ?? null ;
535+ } catch {
536+ return null ;
537+ }
538+ }
539+
540+ function checkPatchrightPeer ( version : string | null ) : boolean {
541+ if ( ! version ) return false ;
542+ // peer dep is ^1.51.0 — major must be 1, minor >= 51
543+ const parts = version . split ( "." ) . map ( Number ) ;
544+ if ( parts . length < 2 ) return false ;
545+ return parts [ 0 ] === 1 && ( parts [ 1 ] ?? 0 ) >= 51 ;
546+ }
547+
406548function readPackageVersion(): string {
407549 try {
408550 const pkgPath = new URL ( "../package.json" , import . meta. url ) ;
0 commit comments