@@ -98,6 +98,8 @@ function stagedLoginStarter(
9898 opts : {
9999 onStart ?: ( input : StagedLoginInput ) => void ;
100100 deniedStarts ?: number ;
101+ /** Emit a device_code event via `notify` on the first start (Meta flow). */
102+ emitDeviceCode ?: boolean ;
101103 } = { } ,
102104) : {
103105 start : OAuthLoginStarter ;
@@ -124,6 +126,13 @@ function stagedLoginStarter(
124126 start : async ( input ) => {
125127 events . starts . push ( input ) ;
126128 opts . onStart ?.( input ) ;
129+ if ( opts . emitDeviceCode === true && events . starts . length === 1 ) {
130+ input . notify ?.( {
131+ type : "device_code" ,
132+ verificationUri : "https://auth.meta.com/device?code=ABCD-1234" ,
133+ userCode : "ABCD-1234" ,
134+ } ) ;
135+ }
127136 input . signal . addEventListener ( "abort" , ( ) => {
128137 events . aborted = true ;
129138 } ) ;
@@ -349,7 +358,9 @@ describe("provider setup pure helpers", () => {
349358 { name : "codex/default" } ,
350359 ] ) ;
351360 expect ( rows . map ( ( r ) => r . id ) ) . toContain ( CUSTOM_CHOICE_ID ) ;
352- expect ( rows . map ( ( r ) => r . id ) ) . toEqual ( choices . map ( ( c ) => c . id ) ) ;
361+ // Alt+A rows are keyed by the pick-list row id (two Meta rows stay
362+ // distinct), which mirrors the onboarding list.
363+ expect ( rows . map ( ( r ) => r . id ) ) . toEqual ( choices . map ( ( c ) => c . rowId ) ) ;
353364 const openai = rows . find ( ( r ) => r . id === "openai" ) ;
354365 const codex = rows . find ( ( r ) => r . id === "codex" ) ;
355366 const custom = rows . find ( ( r ) => r . id === CUSTOM_CHOICE_ID ) ;
@@ -485,6 +496,21 @@ async function mountConfig(
485496 return { done, harness } ;
486497}
487498
499+ /** Recording clipboard port for the Meta device-flow copy test. */
500+ function recordingClipboard ( ) : {
501+ clipboard : { writeText : ( t : string ) => void ; writes : string [ ] } ;
502+ } {
503+ const writes : string [ ] = [ ] ;
504+ return {
505+ clipboard : {
506+ writes,
507+ writeText : ( text : string ) => {
508+ writes . push ( text ) ;
509+ } ,
510+ } ,
511+ } ;
512+ }
513+
488514async function mountSetup (
489515 onSubmit : ProviderSetupSubmit = async ( ) => undefined ,
490516 showTelemetryNotice = false ,
@@ -597,6 +623,7 @@ async function mountLogin(opts: {
597623 onSubmit ?: ProviderSetupSubmit ;
598624 loginTimeoutMs ?: number ;
599625 listOAuthProfiles ?: OAuthProfileLister ;
626+ clipboard ?: ProviderSetupConfig [ "clipboard" ] ;
600627} ) : Promise < { done : Promise < boolean > ; harness : Harness } > {
601628 return mountConfig ( {
602629 onSubmit : opts . onSubmit ?? ( async ( ) => undefined ) ,
@@ -605,6 +632,7 @@ async function mountLogin(opts: {
605632 ...( opts . loginTimeoutMs !== undefined
606633 ? { loginTimeoutMs : opts . loginTimeoutMs }
607634 : { } ) ,
635+ ...( opts . clipboard !== undefined ? { clipboard : opts . clipboard } : { } ) ,
608636 } ) ;
609637}
610638
@@ -1189,6 +1217,60 @@ describe("runProviderSetup sign-in", () => {
11891217 harness . pressKey ( "Ctrl+C" ) ;
11901218 expect ( await done ) . toBe ( false ) ;
11911219 } ) ;
1220+
1221+ test ( "the Meta device flow copies the verification URI to the clipboard" , async ( ) => {
1222+ const rec = recordingClipboard ( ) ;
1223+ const { start } = stagedLoginStarter ( { emitDeviceCode : true } ) ;
1224+ const { done, harness } = await mountLogin ( {
1225+ start,
1226+ clipboard : rec . clipboard ,
1227+ } ) ;
1228+ await pickRow ( harness , PROVIDER_IDS , "meta" ) ;
1229+ await nameOAuthAccount ( harness ) ;
1230+ const frame = harness . captureCharFrame ( ) ;
1231+ expect ( frame ) . toContain ( "https://auth.meta.com/device?code=ABCD-1234" ) ;
1232+ expect ( frame ) . toContain ( "ABCD-1234" ) ;
1233+ expect ( rec . clipboard . writes ) . toEqual ( [
1234+ "https://auth.meta.com/device?code=ABCD-1234" ,
1235+ ] ) ;
1236+ harness . pressKey ( "Ctrl+C" ) ;
1237+ expect ( await done ) . toBe ( false ) ;
1238+ } ) ;
1239+
1240+ test ( "the Meta chooser exposes two arrow-navigable rows with distinct ids" , ( ) => {
1241+ const metaRows = providerChoiceRows ( providerChoices ( ) ) . filter ( ( r ) =>
1242+ r . label . includes ( "Meta" ) ,
1243+ ) ;
1244+ expect ( metaRows . map ( ( r ) => r . id ) ) . toEqual ( [ "meta" , "meta/api" ] ) ;
1245+ expect ( providerChoiceById ( "meta" ) ?. oauth ) . toBe ( "meta" ) ;
1246+ expect ( providerChoiceById ( "meta/api" ) ?. oauth ) . toBeNull ( ) ;
1247+ expect ( providerChoiceById ( "meta/api" ) ?. baseURL ) . toBe (
1248+ "https://api.meta.ai/v1" ,
1249+ ) ;
1250+ expect ( providerChoiceById ( "meta/api" ) ?. defaultModel ) . toBe ( "muse-spark-1.3" ) ;
1251+ } ) ;
1252+
1253+ test ( "picking the Meta API-key row walks the key path, not the sign-in" , async ( ) => {
1254+ const seen : ProviderFormValues [ ] = [ ] ;
1255+ const { done, harness } = await mountSetup ( async ( values ) => {
1256+ seen . push ( { ...values } ) ;
1257+ } ) ;
1258+ await pickRow ( harness , PROVIDER_IDS , "meta/api" ) ;
1259+ await flush ( harness ) ;
1260+ expect ( harness . captureCharFrame ( ) ) . toContain ( "step 2 of 4" ) ;
1261+ // Instance name step, then key — not the login step.
1262+ harness . pressKey ( "Enter" ) ;
1263+ await harness . renderOnce ( ) ;
1264+ type ( harness , "LLM|test-key" ) ;
1265+ harness . pressKey ( "Enter" ) ;
1266+ await harness . renderOnce ( ) ;
1267+ harness . pressKey ( "Enter" ) ;
1268+ await harness . renderOnce ( ) ;
1269+ expect ( await done ) . toBe ( true ) ;
1270+ expect ( seen [ 0 ] ?. name ) . toBe ( "meta/default" ) ;
1271+ expect ( seen [ 0 ] ?. apiKey ) . toBe ( "LLM|test-key" ) ;
1272+ expect ( seen [ 0 ] ?. model ) . toBe ( "muse-spark-1.3" ) ;
1273+ } ) ;
11921274} ) ;
11931275
11941276describe ( "runProviderSetup" , ( ) => {
0 commit comments