@@ -71,6 +71,114 @@ export function getPendingUpdateNotification(): string | null {
7171 return pendingNotification ;
7272}
7373
74+ /**
75+ * Determines if the version gap is large enough to warrant auto-update.
76+ * Conditions (either triggers auto-update):
77+ * 1. New major > current major
78+ * 2. Same major, but new minor - current minor > 3
79+ */
80+ export function isMajorUpgrade ( latest : string , current : string ) : boolean {
81+ const [ latestMajor , latestMinor ] = latest . split ( "." ) . map ( Number ) ;
82+ const [ currentMajor , currentMinor ] = current . split ( "." ) . map ( Number ) ;
83+
84+ // Condition 1: major version bump
85+ if ( latestMajor > currentMajor ) return true ;
86+
87+ // Condition 2: same major, minor gap > 3
88+ if ( latestMajor === currentMajor && latestMinor - currentMinor > 3 ) return true ;
89+
90+ return false ;
91+ }
92+
93+ /**
94+ * Perform auto-update: install latest version globally and update agent skill.
95+ * Returns true if update succeeded, false otherwise.
96+ */
97+ export async function performAutoUpdate (
98+ currentVersion : string ,
99+ latestVersion : string ,
100+ ) : Promise < boolean > {
101+ const isTTY = process . stderr . isTTY ;
102+ const green = isTTY ? "\x1b[32m" : "" ;
103+ const yellow = isTTY ? "\x1b[33m" : "" ;
104+ const cyan = isTTY ? "\x1b[36m" : "" ;
105+ const dim = isTTY ? "\x1b[2m" : "" ;
106+ const reset = isTTY ? "\x1b[0m" : "" ;
107+
108+ const [ latestMajor ] = latestVersion . split ( "." ) . map ( Number ) ;
109+ const [ currentMajor ] = currentVersion . split ( "." ) . map ( Number ) ;
110+ const isMajorBump = latestMajor > currentMajor ;
111+
112+ process . stderr . write ( "\n" ) ;
113+ process . stderr . write ( ` ${ yellow } ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${ reset } \n` ) ;
114+ if ( isMajorBump ) {
115+ process . stderr . write (
116+ ` ${ yellow } ⚡ Major update detected: ${ currentVersion } → ${ latestVersion } ${ reset } \n` ,
117+ ) ;
118+ } else {
119+ process . stderr . write (
120+ ` ${ yellow } ⚡ Significant update detected: ${ currentVersion } → ${ latestVersion } ${ reset } \n` ,
121+ ) ;
122+ }
123+ process . stderr . write ( ` ${ dim } Auto-updating to keep your CLI up to date...${ reset } \n` ) ;
124+ process . stderr . write ( ` ${ yellow } ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${ reset } \n\n` ) ;
125+
126+ const cmd = `npm install -g ${ NPM_PACKAGE } @latest` ;
127+
128+ try {
129+ const { execSync } = await import ( "child_process" ) ;
130+ execSync ( cmd , { stdio : "inherit" } ) ;
131+
132+ // Verify installed version
133+ let newVer : string | null = null ;
134+ try {
135+ const rawVer = execSync ( "bl --version 2>/dev/null" , { encoding : "utf-8" } ) . trim ( ) ;
136+ newVer = rawVer . replace ( / ^ b l \s + / , "" ) ;
137+ } catch {
138+ /* ignore */
139+ }
140+
141+ // Update cached state
142+ try {
143+ const { writeFileSync } = await import ( "fs" ) ;
144+ const { join } = await import ( "path" ) ;
145+ const { getConfigDir } = await import ( "bailian-cli-core" ) ;
146+ const stateFile = join ( getConfigDir ( ) , "update-state.json" ) ;
147+ writeFileSync (
148+ stateFile ,
149+ JSON . stringify ( { lastChecked : Date . now ( ) , latestVersion : newVer ?? latestVersion } ) ,
150+ ) ;
151+ } catch {
152+ /* ignore */
153+ }
154+
155+ process . stderr . write (
156+ ` ${ green } ✓ Update complete: ${ currentVersion } → ${ newVer ?? latestVersion } ${ reset } \n` ,
157+ ) ;
158+ process . stderr . write ( ` ${ dim } Run ${ cyan } bl --version${ reset } ${ dim } to verify.${ reset } \n\n` ) ;
159+
160+ // Update agent skill
161+ try {
162+ const { execSync : exec } = await import ( "child_process" ) ;
163+ process . stderr . write ( ` ${ dim } Syncing agent skill...${ reset } \n` ) ;
164+ exec ( `npx skills add modelstudioai/cli --all -g -y` , { stdio : "inherit" } ) ;
165+ process . stderr . write ( ` ${ green } ✓ Agent skill updated.${ reset } \n\n` ) ;
166+ } catch {
167+ process . stderr . write (
168+ ` ${ yellow } Agent skill sync skipped (run manually: npx skills add modelstudioai/cli --all -g -y)${ reset } \n\n` ,
169+ ) ;
170+ }
171+
172+ // Clear pending notification
173+ pendingNotification = null ;
174+ return true ;
175+ } catch {
176+ process . stderr . write ( ` ${ yellow } ⚠ Auto-update failed. Please run manually:${ reset } \n` ) ;
177+ process . stderr . write ( ` ${ cyan } ${ cmd } ${ reset } \n\n` ) ;
178+ return false ;
179+ }
180+ }
181+
74182export async function checkForUpdate ( currentVersion : string ) : Promise < void > {
75183 // Skip in CI / non-TTY environments
76184 if ( process . env . CI || ! process . stderr . isTTY ) return ;
0 commit comments