77 * - Otherwise → PKCE loopback.
88 */
99
10+ import type { StoredSession } from '../auth/store.ts'
1011import { styleText } from 'node:util'
1112import * as p from '@clack/prompts'
1213import { defineCommand } from 'citty'
1314import { runDeviceFlow } from '../auth/device-flow.ts'
1415import { isGhaOidcAvailable , runOidcExchange } from '../auth/oidc.ts'
1516import { runPkceFlow } from '../auth/pkce-flow.ts'
16- import { saveSession } from '../auth/store.ts'
17+ import { loadSession , saveSession } from '../auth/store.ts'
1718import { getRegistryBase } from '../registry/client.ts'
1819import { track } from '../telemetry.ts'
1920import { version } from '../version.ts'
@@ -28,12 +29,33 @@ function shouldUseDevice(force: boolean): boolean {
2829 return ! process . env . DISPLAY && ! process . env . WAYLAND_DISPLAY
2930}
3031
32+ export type LoginRequirement
33+ = | { _tag : 'Reuse' , session : StoredSession }
34+ | { _tag : 'Authenticate' }
35+
36+ export function resolveLoginRequirement (
37+ session : StoredSession | null ,
38+ nowSeconds = Math . floor ( Date . now ( ) / 1000 ) ,
39+ ) : LoginRequirement {
40+ if ( ! session )
41+ return { _tag : 'Authenticate' }
42+ if ( session . refreshToken || session . expiresAt === undefined || session . expiresAt > nowSeconds )
43+ return { _tag : 'Reuse' , session }
44+ return { _tag : 'Authenticate' }
45+ }
46+
3147export const loginCommandDef = defineCommand ( {
3248 meta : { name : 'login' , description : 'Authenticate with skilld.dev' } ,
3349 args : {
3450 device : { type : 'boolean' , description : 'Use RFC 8628 device flow' } ,
3551 } ,
3652 async run ( { args } ) {
53+ const requirement = resolveLoginRequirement ( await loadSession ( ) )
54+ if ( requirement . _tag === 'Reuse' ) {
55+ p . log . success ( `Already logged in as ${ styleText ( 'cyan' , `@${ requirement . session . login } ` ) } ` )
56+ return
57+ }
58+
3759 const registryBase = getRegistryBase ( )
3860
3961 if ( isGhaOidcAvailable ( ) ) {
0 commit comments