@@ -53,6 +53,8 @@ function defaultSettings() {
5353 additionalContextPaths : [ ] ,
5454 lastAppliedChange : null ,
5555 authState : "logged-in" ,
56+ authDetail : "Auth not checked yet." ,
57+ authCheckedAt : Date . now ( ) ,
5658 model : "gpt-5.3-codex" ,
5759 contextPolicy : "selection-first" ,
5860 debugLogging : false
@@ -119,6 +121,15 @@ function buildPreviewDiff(before, after) {
119121 }
120122 return out . join ( "\n" ) ;
121123}
124+ function compactOutput ( stdout , stderr ) {
125+ const joined = `${ stdout }
126+ ${ stderr } `. replace ( / \s + / g, " " ) . trim ( ) ;
127+ return joined . length > 0 ? joined : "(no output)" ;
128+ }
129+ function containsAny ( source , patterns ) {
130+ const lowered = source . toLowerCase ( ) ;
131+ return patterns . some ( ( pattern ) => lowered . includes ( pattern ) ) ;
132+ }
122133function normalizeSettings ( raw ) {
123134 const fallback = defaultSettings ( ) ;
124135 if ( ! raw || typeof raw !== "object" ) {
@@ -160,6 +171,8 @@ function normalizeSettings(raw) {
160171 additionalContextPaths,
161172 lastAppliedChange,
162173 authState : isAuthState ( source . authState ) ? source . authState : fallback . authState ,
174+ authDetail : typeof source . authDetail === "string" && source . authDetail . trim ( ) . length > 0 ? source . authDetail : fallback . authDetail ,
175+ authCheckedAt : typeof source . authCheckedAt === "number" ? source . authCheckedAt : fallback . authCheckedAt ,
163176 model : typeof source . model === "string" && source . model . trim ( ) . length > 0 ? source . model : fallback . model ,
164177 contextPolicy : isContextPolicy ( source . contextPolicy ) ? source . contextPolicy : fallback . contextPolicy ,
165178 debugLogging : Boolean ( source . debugLogging )
@@ -183,7 +196,9 @@ var CopilotSidebarView = class extends import_obsidian.ItemView {
183196 const root = container . createDiv ( { cls : "copilot-sidebar-root" } ) ;
184197 const header = root . createDiv ( { cls : "copilot-sidebar-header" } ) ;
185198 const title = header . createDiv ( { text : "Copilot Sidebar" , cls : "copilot-sidebar-title" } ) ;
186- const authBadge = header . createDiv ( { cls : "copilot-auth-badge" } ) ;
199+ const authMeta = header . createDiv ( { cls : "copilot-auth-meta" } ) ;
200+ const authBadge = authMeta . createDiv ( { cls : "copilot-auth-badge" } ) ;
201+ const authDetail = authMeta . createDiv ( { cls : "copilot-auth-detail" } ) ;
187202 const controlRow = root . createDiv ( { cls : "copilot-sidebar-controls" } ) ;
188203 const newSessionButton = controlRow . createEl ( "button" , {
189204 text : "New Session" ,
@@ -201,8 +216,8 @@ var CopilotSidebarView = class extends import_obsidian.ItemView {
201216 text : "Add Active Context" ,
202217 cls : "copilot-button"
203218 } ) ;
204- const authCycleButton = controlRow . createEl ( "button" , {
205- text : "Cycle Auth" ,
219+ const refreshAuthButton = controlRow . createEl ( "button" , {
220+ text : "Refresh Auth" ,
206221 cls : "copilot-button"
207222 } ) ;
208223 const layout = root . createDiv ( { cls : "copilot-sidebar-layout" } ) ;
@@ -237,8 +252,8 @@ var CopilotSidebarView = class extends import_obsidian.ItemView {
237252 addContextButton . addEventListener ( "click" , ( ) => {
238253 void this . plugin . addActiveNoteToContext ( ) ;
239254 } ) ;
240- authCycleButton . addEventListener ( "click" , ( ) => {
241- void this . plugin . cycleAuthState ( ) ;
255+ refreshAuthButton . addEventListener ( "click" , ( ) => {
256+ void this . plugin . refreshAuthStatus ( "manual" ) ;
242257 } ) ;
243258 composerButton . addEventListener ( "click" , ( ) => {
244259 void this . submitComposer ( ) ;
@@ -252,6 +267,7 @@ var CopilotSidebarView = class extends import_obsidian.ItemView {
252267 this . elements = {
253268 title,
254269 authBadge,
270+ authMeta : authDetail ,
255271 sessionList,
256272 contextList,
257273 pendingList,
@@ -275,14 +291,17 @@ var CopilotSidebarView = class extends import_obsidian.ItemView {
275291 this . elements . title . setText ( activeSession ? activeSession . title : "Copilot Sidebar" ) ;
276292 this . elements . authBadge . setText ( `Auth: ${ snapshot . authState } ` ) ;
277293 this . elements . authBadge . className = `copilot-auth-badge auth-${ snapshot . authState } ` ;
294+ const checkedAt = new Date ( snapshot . authCheckedAt ) . toLocaleTimeString ( ) ;
295+ const compactDetail = snapshot . authDetail . length > 180 ? `${ snapshot . authDetail . slice ( 0 , 177 ) } ...` : snapshot . authDetail ;
296+ this . elements . authMeta . setText ( `${ compactDetail } | checked ${ checkedAt } ` ) ;
278297 this . renderSessions ( snapshot ) ;
279298 this . renderContextNotes ( snapshot ) ;
280299 this . renderPendingChanges ( snapshot ) ;
281300 this . renderMessages ( activeSession ) ;
282301 this . renderPreview ( snapshot ) ;
283302 this . elements . composerButton . disabled = snapshot . isStreaming ;
284303 this . elements . composerInput . disabled = snapshot . isStreaming ;
285- this . elements . composerInput . placeholder = snapshot . authState === "logged-in" ? "Ask Copilot about this vault..." : "Auth state is not logged-in. Use Cycle Auth (mock) to simulate recovery ." ;
304+ this . elements . composerInput . placeholder = snapshot . authState === "logged-in" ? "Ask Copilot about this vault..." : "Auth state is not logged-in. Use Refresh Auth to re-check login and entitlement ." ;
286305 }
287306 renderSessions ( snapshot ) {
288307 if ( ! this . elements ) {
@@ -496,6 +515,14 @@ var CopilotSidebarPlugin = class extends import_obsidian.Plugin {
496515 await this . undoLastAppliedChange ( ) ;
497516 }
498517 } ) ;
518+ this . addCommand ( {
519+ id : "refresh-auth-status" ,
520+ name : "Refresh auth status" ,
521+ callback : async ( ) => {
522+ await this . refreshAuthStatus ( "manual" ) ;
523+ }
524+ } ) ;
525+ void this . refreshAuthStatus ( "startup" ) ;
499526 }
500527 async onunload ( ) {
501528 for ( const timer of this . activeStreamTimers ) {
@@ -523,6 +550,8 @@ var CopilotSidebarPlugin = class extends import_obsidian.Plugin {
523550 additionalContextPaths : [ ...this . settings . additionalContextPaths ] ,
524551 lastAppliedChange : this . settings . lastAppliedChange ? { ...this . settings . lastAppliedChange } : null ,
525552 authState : this . settings . authState ,
553+ authDetail : this . settings . authDetail ,
554+ authCheckedAt : this . settings . authCheckedAt ,
526555 model : this . settings . model ,
527556 isStreaming : this . streaming
528557 } ;
@@ -559,8 +588,125 @@ var CopilotSidebarPlugin = class extends import_obsidian.Plugin {
559588 const currentIndex = AUTH_ORDER . indexOf ( this . settings . authState ) ;
560589 const nextIndex = currentIndex >= 0 ? ( currentIndex + 1 ) % AUTH_ORDER . length : 0 ;
561590 this . settings . authState = AUTH_ORDER [ nextIndex ] ;
591+ this . settings . authDetail = "Manual auth state override." ;
592+ this . settings . authCheckedAt = Date . now ( ) ;
562593 await this . persistAndRender ( ) ;
563594 }
595+ async refreshAuthStatus ( trigger = "manual" ) {
596+ const probe = this . probeAuthStatusFromGh ( ) ;
597+ this . settings . authState = probe . state ;
598+ this . settings . authDetail = probe . detail ;
599+ this . settings . authCheckedAt = probe . checkedAt ;
600+ await this . persistAndRender ( ) ;
601+ if ( trigger === "manual" ) {
602+ new import_obsidian . Notice ( `Auth check: ${ probe . state } ` ) ;
603+ }
604+ }
605+ probeAuthStatusFromGh ( ) {
606+ const checkedAt = Date . now ( ) ;
607+ const ghVersion = this . runLocalCommand ( "gh" , [ "--version" ] , 2500 ) ;
608+ if ( ghVersion . status !== 0 ) {
609+ return {
610+ state : "offline" ,
611+ detail : "gh CLI not found. Install/authenticate GitHub CLI." ,
612+ checkedAt
613+ } ;
614+ }
615+ const authStatus = this . runLocalCommand ( "gh" , [ "auth" , "status" , "-h" , "github.com" ] , 6e3 ) ;
616+ const authOutput = compactOutput ( authStatus . stdout , authStatus . stderr ) ;
617+ if ( authStatus . status !== 0 ) {
618+ if ( containsAny ( authOutput , [ "expired" , "token" , "not logged" , "authentication" ] ) ) {
619+ return {
620+ state : "token-expired" ,
621+ detail : `GitHub auth issue: ${ authOutput } ` ,
622+ checkedAt
623+ } ;
624+ }
625+ return {
626+ state : "offline" ,
627+ detail : `Unable to reach GitHub auth status: ${ authOutput } ` ,
628+ checkedAt
629+ } ;
630+ }
631+ const copilotStatus = this . runLocalCommand ( "gh" , [ "copilot" , "status" ] , 6e3 ) ;
632+ const copilotOutput = compactOutput ( copilotStatus . stdout , copilotStatus . stderr ) ;
633+ if ( copilotStatus . status === 0 ) {
634+ return {
635+ state : "logged-in" ,
636+ detail : `GitHub login and Copilot status confirmed. ${ copilotOutput } ` ,
637+ checkedAt
638+ } ;
639+ }
640+ if ( containsAny ( copilotOutput , [ "not entitled" , "not enabled" , "no entitlement" , "not subscribed" ] ) ) {
641+ return {
642+ state : "no-entitlement" ,
643+ detail : `GitHub login ok but Copilot entitlement missing. ${ copilotOutput } ` ,
644+ checkedAt
645+ } ;
646+ }
647+ if ( containsAny ( copilotOutput , [ "token" , "authentication" , "not logged" , "login" ] ) ) {
648+ return {
649+ state : "token-expired" ,
650+ detail : `Copilot auth needs refresh. ${ copilotOutput } ` ,
651+ checkedAt
652+ } ;
653+ }
654+ if ( containsAny ( copilotOutput , [ "unknown command" , "usage:" ] ) ) {
655+ return {
656+ state : "logged-in" ,
657+ detail : "GitHub login detected. Copilot status command unavailable in this gh build." ,
658+ checkedAt
659+ } ;
660+ }
661+ return {
662+ state : "logged-in" ,
663+ detail : `GitHub login detected. Copilot status inconclusive: ${ copilotOutput } ` ,
664+ checkedAt
665+ } ;
666+ }
667+ runLocalCommand ( command , args , timeoutMs ) {
668+ const dynamicRequire = this . getDynamicRequire ( ) ;
669+ if ( ! dynamicRequire ) {
670+ return {
671+ status : - 1 ,
672+ stdout : "" ,
673+ stderr : "dynamic require is unavailable in this runtime" ,
674+ error : "require-unavailable"
675+ } ;
676+ }
677+ try {
678+ const childProcess = dynamicRequire ( "node:child_process" ) ;
679+ const result = childProcess . spawnSync ( command , args , {
680+ encoding : "utf8" ,
681+ timeout : timeoutMs
682+ } ) ;
683+ const error = result . error instanceof Error ? result . error . message : null ;
684+ return {
685+ status : result . status ?? - 1 ,
686+ stdout : typeof result . stdout === "string" ? result . stdout : "" ,
687+ stderr : typeof result . stderr === "string" ? result . stderr : "" ,
688+ error
689+ } ;
690+ } catch ( error ) {
691+ return {
692+ status : - 1 ,
693+ stdout : "" ,
694+ stderr : "" ,
695+ error : error instanceof Error ? error . message : String ( error )
696+ } ;
697+ }
698+ }
699+ getDynamicRequire ( ) {
700+ const fromGlobal = globalThis . require ;
701+ if ( typeof fromGlobal === "function" ) {
702+ return fromGlobal ;
703+ }
704+ const fromWindow = globalThis . window ?. require ;
705+ if ( typeof fromWindow === "function" ) {
706+ return fromWindow ;
707+ }
708+ return null ;
709+ }
564710 async addActiveNoteToContext ( ) {
565711 const activeFile = this . app . workspace . getActiveFile ( ) ;
566712 if ( ! ( activeFile instanceof import_obsidian . TFile ) ) {
0 commit comments