@@ -124,6 +124,9 @@ export default function HomeClient({ release }: { release: Release }) {
124124 const [ downloadOS , setDownloadOS ] = useState <
125125 'auto' | 'windows' | 'macos' | 'linux'
126126 > ( 'auto' ) ;
127+ const [ downloadArch , setDownloadArch ] = useState <
128+ 'auto' | 'x86_64' | 'arm64' | 'armv7' | 'i386'
129+ > ( 'auto' ) ;
127130
128131 function detectOS ( ) : 'windows' | 'macos' | 'linux' | 'unknown' {
129132 if ( typeof navigator === 'undefined' ) return 'unknown' ;
@@ -147,6 +150,25 @@ export default function HomeClient({ release }: { release: Release }) {
147150 return 'unknown' ;
148151 }
149152
153+ function detectArch ( ) :
154+ | 'x86_64'
155+ | 'arm64'
156+ | 'armv7'
157+ | 'i386'
158+ | 'unknown' {
159+ if ( typeof navigator === 'undefined' ) return 'unknown' ;
160+ const ua =
161+ ( navigator . userAgent || navigator . platform || '' ) . toLowerCase ( ) ;
162+
163+ if ( ua . includes ( 'aarch64' ) || ua . includes ( 'arm64' ) ) return 'arm64' ;
164+ if ( ua . includes ( 'armv7' ) || ua . includes ( 'armv7l' ) || ua . includes ( 'armhf' ) )
165+ return 'armv7' ;
166+ if ( ua . includes ( 'amd64' ) || ua . includes ( 'x86_64' ) || ua . includes ( 'wow64' ) || ua . includes ( 'win64' ) || ua . includes ( 'x64' ) )
167+ return 'x86_64' ;
168+ if ( ua . includes ( 'i386' ) || ua . includes ( 'i686' ) || ua . includes ( 'ia32' ) ) return 'i386' ;
169+ return 'unknown' ;
170+ }
171+
150172 function findAssetForOS ( rel : any , os : string ) {
151173 if ( ! rel || ! rel . assets ) return null ;
152174 const assets = rel . assets as {
@@ -162,20 +184,56 @@ export default function HomeClient({ release }: { release: Release }) {
162184 return null ;
163185 } ;
164186
165- if ( os === 'windows' ) return tryKeywords ( [ 'windows' , '.exe' , 'win' ] ) ;
166- if ( os === 'macos' )
167- return tryKeywords ( [ 'macos' , 'darwin' , 'mac' , '.dmg' , '.pkg' ] ) ;
168- if ( os === 'linux' )
169- return tryKeywords ( [
170- 'linux' ,
171- '.appimage' ,
172- '.deb' ,
173- '.rpm' ,
174- '.tar.gz' ,
175- '.tar' ,
176- ] ) ;
177-
178- // fallback: try to find any CLI binary
187+ // Accept an "os" string optionally containing an arch with the format "os:arch".
188+ const normalizedOs = ( os || '' ) . toLowerCase ( ) ;
189+
190+ const osKeywordsMap : Record < string , string [ ] > = {
191+ windows : [ 'windows' , '.exe' , 'win' ] ,
192+ macos : [ 'macos' , 'darwin' , 'mac' , '.dmg' , '.pkg' ] ,
193+ linux : [ 'linux' , '.appimage' , '.deb' , '.rpm' , '.tar.gz' , '.tar' ] ,
194+ } ;
195+
196+ const archKeywordsMap : Record < string , string [ ] > = {
197+ x86_64 : [ 'x86_64' , 'amd64' , 'x64' , 'x86-64' , '-amd64' , '_amd64' ] ,
198+ arm64 : [ 'arm64' , 'aarch64' , 'arm64v8' , '-arm64' , '_arm64' ] ,
199+ armv7 : [ 'armv7' , 'armv7l' , 'armhf' , '-armv7' , '_armv7' ] ,
200+ i386 : [ 'i386' , 'i686' , 'ia32' , 'x86' ] ,
201+ } ;
202+
203+ let arch : string | undefined = undefined ;
204+ if ( normalizedOs . includes ( ':' ) ) {
205+ const [ o , a ] = normalizedOs . split ( ':' , 2 ) ;
206+ arch = a ;
207+ }
208+
209+ const osKey = normalizedOs . includes ( ':' ) ? normalizedOs . split ( ':' , 1 ) [ 0 ] : normalizedOs ;
210+ const osKeywords = osKeywordsMap [ osKey ] || [ ] ;
211+ const archKeywords = arch ? archKeywordsMap [ arch ] || [ ] : [ ] ;
212+
213+ // Try to match both OS and arch in the filename
214+ if ( archKeywords . length && osKeywords . length ) {
215+ const found = assets . find ( ( a ) => {
216+ const n = name ( a . name ) ;
217+ const ok = osKeywords . some ( ( k ) => n . includes ( k ) ) ;
218+ const ak = archKeywords . some ( ( k ) => n . includes ( k ) ) ;
219+ return ok && ak ;
220+ } ) ;
221+ if ( found ) return found . browser_download_url ;
222+ }
223+
224+ // If no combined match, try arch-only (if provided)
225+ if ( archKeywords . length ) {
226+ const byArch = tryKeywords ( archKeywords ) ;
227+ if ( byArch ) return byArch ;
228+ }
229+
230+ // Fallback to OS-only matching
231+ if ( osKeywords . length ) {
232+ const byOs = tryKeywords ( osKeywords ) ;
233+ if ( byOs ) return byOs ;
234+ }
235+
236+ // Final fallback: try to find any CLI binary
179237 return tryKeywords ( [ 'cli' , 'chithi' , 'chithi-cli' ] ) ;
180238 }
181239
@@ -185,7 +243,9 @@ export default function HomeClient({ release }: { release: Release }) {
185243 return ;
186244 }
187245 const osToUse = downloadOS === 'auto' ? detectOS ( ) : downloadOS ;
188- const asset = findAssetForOS ( release , osToUse ) ;
246+ const archToUse = downloadArch === 'auto' ? detectArch ( ) : downloadArch ;
247+ const osAndArch = archToUse && archToUse !== 'unknown' ? `${ osToUse } :${ archToUse } ` : osToUse ;
248+ const asset = findAssetForOS ( release , osAndArch ) ;
189249 if ( asset ) {
190250 // redirect to the asset (GitHub release file)
191251 window . location . href = asset ;
@@ -271,6 +331,20 @@ export default function HomeClient({ release }: { release: Release }) {
271331 < option value = "linux" > Linux</ option >
272332 </ select >
273333
334+ < select
335+ value = { downloadArch }
336+ onChange = { ( e ) =>
337+ setDownloadArch ( e . target . value as any )
338+ }
339+ className = "select variant-form-material"
340+ >
341+ < option value = "auto" > Auto-detect</ option >
342+ < option value = "x86_64" > x86_64 (amd64)</ option >
343+ < option value = "arm64" > arm64 (aarch64)</ option >
344+ < option value = "armv7" > armv7 (armhf)</ option >
345+ < option value = "i386" > i386 (x86)</ option >
346+ </ select >
347+
274348 < button
275349 onClick = { handleDownloadClick }
276350 className = "btn preset-filled-primary-500 rounded-full px-6 py-3 font-bold"
0 commit comments