@@ -73,25 +73,48 @@ class GitHubAPIClient {
7373 }
7474
7575 const trimmedToken = token . trim ( )
76+
77+
78+ if ( process . env . NODE_ENV === 'development' ) {
79+ console . log ( '🔍 Token validation debug:' , {
80+ tokenLength : trimmedToken . length ,
81+ tokenPrefix : trimmedToken . substring ( 0 , 10 ) + '...' ,
82+ tokenPattern : trimmedToken . substring ( 0 , 4 )
83+ } )
84+ }
7685
7786
7887 const isClassicToken = / ^ g h p _ [ A - Z a - z 0 - 9 ] { 36 } $ / . test ( trimmedToken )
7988 const isFineGrainedToken = / ^ g i t h u b _ p a t _ [ A - Z a - z 0 - 9 _ ] { 82 } $ / . test ( trimmedToken )
8089 const isGitHubAppToken = / ^ g h s _ [ A - Z a - z 0 - 9 ] { 36 } $ / . test ( trimmedToken )
81-
8290 const isLegacyToken = / ^ [ a - f 0 - 9 ] { 40 } $ / . test ( trimmedToken )
91+
92+
93+ const isOAuthToken = / ^ g h o _ [ A - Z a - z 0 - 9 _ - ] { 16 , } $ / . test ( trimmedToken ) ||
94+ ( ! isClassicToken && ! isFineGrainedToken && ! isGitHubAppToken && ! isLegacyToken &&
95+ / ^ [ A - Z a - z 0 - 9 _ - ] { 20 , 255 } $ / . test ( trimmedToken ) )
96+
97+ if ( process . env . NODE_ENV === 'development' ) {
98+ console . log ( '🔍 Token type checks:' , {
99+ isClassicToken,
100+ isFineGrainedToken,
101+ isGitHubAppToken,
102+ isLegacyToken,
103+ isOAuthToken
104+ } )
105+ }
83106
84- if ( ! isClassicToken && ! isFineGrainedToken && ! isGitHubAppToken && ! isLegacyToken ) {
107+ if ( ! isClassicToken && ! isFineGrainedToken && ! isGitHubAppToken && ! isLegacyToken && ! isOAuthToken ) {
85108 throw new Error (
86109 'Invalid GitHub token format. Expected:\n' +
87110 '- Classic token: ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (40 chars)\n' +
88111 '- Fine-grained token: github_pat_xxxxxxxxxx... (94 chars)\n' +
89112 '- GitHub App token: ghs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (40 chars)\n' +
113+ '- OAuth token: 20-255 character alphanumeric string\n' +
90114 '- Legacy token: 40 character hexadecimal string'
91115 )
92116 }
93117
94- // Additional length validation
95118 if ( trimmedToken . length < 40 ) {
96119 throw new Error ( 'GitHub token is too short. Minimum length is 40 characters.' )
97120 }
@@ -103,16 +126,12 @@ class GitHubAPIClient {
103126 this . githubToken = trimmedToken
104127 }
105128
106- /**
107- * Check if a valid GitHub token is currently set
108- */
129+
109130 hasValidToken ( ) : boolean {
110- return this . githubToken . length >= 40
131+ return this . githubToken . length >= 20
111132 }
112133
113- /**
114- * Clear the current GitHub token
115- */
134+
116135 clearToken ( ) : void {
117136 this . githubToken = ''
118137 }
@@ -131,7 +150,6 @@ class GitHubAPIClient {
131150 'User-Agent' : 'GitHubMon/1.0'
132151 }
133152
134- // Add GitHub token if available for authenticated requests
135153 if ( useGithub && this . githubToken ) {
136154 headers [ 'Authorization' ] = `token ${ this . githubToken } `
137155 }
@@ -143,32 +161,27 @@ class GitHubAPIClient {
143161 if ( response . status === 403 ) {
144162 console . warn ( 'GitHub API rate limit exceeded' )
145163 }
146- // Return fallback data on error
147164 return this . getFallbackData ( endpoint ) as T
148165 }
149166
150167 const data = await response . json ( )
151168
152- // GitHub API response
153169 this . cache . set ( cacheKey , { data, timestamp : Date . now ( ) } )
154170 return data
155171
156172 } catch ( error ) {
157173 console . error ( `API request failed for ${ endpoint } :` , error )
158174
159- // Return cached data if available
160175 if ( cached ) {
161176 console . log ( 'Returning stale cached data due to error' )
162177 return cached . data as T
163178 }
164179
165- // Fallback to mock data
166180 return this . getFallbackData ( endpoint ) as T
167181 }
168182 }
169183
170184 private getFallbackData ( endpoint : string ) : unknown {
171- // Return mock trending repositories if the real API fails
172185 if ( endpoint . includes ( 'search/repositories' ) ) {
173186 return {
174187 items : [
@@ -406,6 +419,60 @@ async getAssignedItems(username?: string): Promise<unknown[]> {
406419 }
407420 }
408421
422+ async getGoodFirstIssues ( ) : Promise < unknown [ ] > {
423+ try {
424+ const endpoint = `/search/issues?q=label:"good first issue"+state:open+type:issue&sort=updated&order=desc&per_page=50`
425+ const response = await this . fetchWithCache < GitHubSearchResponse < GitHubIssueResponse > > ( endpoint , true )
426+
427+ return response . items ?. map ( ( item : GitHubIssueResponse ) => ( {
428+ id : item . id ,
429+ title : item . title ,
430+ repo : item . repository_url
431+ ? item . repository_url . split ( '/' ) . slice ( - 2 ) . join ( '/' )
432+ : 'unknown/unknown' ,
433+ type : 'issue' ,
434+ priority : this . calculatePriority ( item ) ,
435+ url : item . html_url ,
436+ createdAt : item . created_at ,
437+ updatedAt : item . updated_at ,
438+ author : item . user ?. login ,
439+ labels : item . labels ?. map ( ( l : { name : string } ) => l . name ) || [ ] ,
440+ daysOld : Math . floor ( ( Date . now ( ) - new Date ( item . created_at ) . getTime ( ) ) / ( 1000 * 60 * 60 * 24 ) )
441+ } ) ) || [ ]
442+ } catch ( error ) {
443+ console . error ( 'Failed to fetch good first issues:' , error )
444+ return [ ]
445+ }
446+ }
447+
448+ async getEasyFixes ( ) : Promise < unknown [ ] > {
449+ try {
450+ const labels = [ 'easy' , 'easy fix' , 'beginner' , 'starter' , 'help wanted' ]
451+ const labelQuery = labels . map ( label => `label:"${ label } "` ) . join ( ' OR ' )
452+ const endpoint = `/search/issues?q=(${ labelQuery } )+state:open+type:issue&sort=updated&order=desc&per_page=50`
453+ const response = await this . fetchWithCache < GitHubSearchResponse < GitHubIssueResponse > > ( endpoint , true )
454+
455+ return response . items ?. map ( ( item : GitHubIssueResponse ) => ( {
456+ id : item . id ,
457+ title : item . title ,
458+ repo : item . repository_url
459+ ? item . repository_url . split ( '/' ) . slice ( - 2 ) . join ( '/' )
460+ : 'unknown/unknown' ,
461+ type : 'issue' ,
462+ priority : this . calculatePriority ( item ) ,
463+ url : item . html_url ,
464+ createdAt : item . created_at ,
465+ updatedAt : item . updated_at ,
466+ author : item . user ?. login ,
467+ labels : item . labels ?. map ( ( l : { name : string } ) => l . name ) || [ ] ,
468+ daysOld : Math . floor ( ( Date . now ( ) - new Date ( item . created_at ) . getTime ( ) ) / ( 1000 * 60 * 60 * 24 ) )
469+ } ) ) || [ ]
470+ } catch ( error ) {
471+ console . error ( 'Failed to fetch easy fixes:' , error )
472+ return [ ]
473+ }
474+ }
475+
409476 private calculatePriority ( item : GitHubIssueResponse ) : 'low' | 'medium' | 'high' | 'urgent' {
410477 const labels = item . labels ?. map ( ( l : { name : string } ) => l . name . toLowerCase ( ) ) || [ ]
411478 const commentCount = item . comments || 0
0 commit comments