@@ -21,6 +21,15 @@ struct CodexAccountsSectionNotice: Equatable {
2121 let tone : Tone
2222}
2323
24+ struct CodexDiscoveredProfileState : Identifiable , Equatable {
25+ let id : String
26+ let title : String
27+ let subtitle : String ?
28+ let detail : String ?
29+ let isDisplayed : Bool
30+ let isLive : Bool
31+ }
32+
2433struct CodexAccountsSectionState : Equatable {
2534 let visibleAccounts : [ CodexVisibleAccount ]
2635 let activeVisibleAccountID : String ?
@@ -29,6 +38,8 @@ struct CodexAccountsSectionState: Equatable {
2938 let authenticatingManagedAccountID : UUID ?
3039 let isAuthenticatingLiveAccount : Bool
3140 let notice : CodexAccountsSectionNotice ?
41+ let localProfiles : [ CodexDiscoveredProfileState ]
42+ let hasUnavailableSelectedProfile : Bool
3243
3344 var showsActivePicker : Bool {
3445 self . visibleAccounts. count > 1
@@ -84,6 +95,17 @@ struct CodexAccountsSectionState: Equatable {
8495 }
8596 return " Re-auth "
8697 }
98+
99+ var showsLocalProfiles : Bool {
100+ !self . localProfiles. isEmpty || self . hasUnavailableSelectedProfile
101+ }
102+
103+ var localProfilesNotice : CodexAccountsSectionNotice ? {
104+ guard self . hasUnavailableSelectedProfile else { return nil }
105+ return CodexAccountsSectionNotice (
106+ text: " The selected local Codex profile is unavailable. Pick another profile or reload profiles. " ,
107+ tone: . warning)
108+ }
87109}
88110
89111@MainActor
@@ -93,6 +115,9 @@ struct CodexAccountsSectionView: View {
93115 let reauthenticateAccount : ( CodexVisibleAccount ) -> Void
94116 let removeAccount : ( CodexVisibleAccount ) -> Void
95117 let addAccount : ( ) -> Void
118+ let selectLocalProfile : ( String ) -> Void
119+ let reloadLocalProfiles : ( ) -> Void
120+ let openLocalProfilesFolder : ( ) -> Void
96121
97122 var body : some View {
98123 ProviderSettingsSection ( title: " Accounts " ) {
@@ -167,6 +192,56 @@ struct CodexAccountsSectionView: View {
167192 . buttonStyle ( . bordered)
168193 . controlSize ( . small)
169194 . disabled ( self . state. canAddAccount == false )
195+
196+ if self . state. showsLocalProfiles {
197+ Divider ( )
198+
199+ VStack ( alignment: . leading, spacing: 10 ) {
200+ Text ( " Local Profiles (Advanced) " )
201+ . font ( . subheadline. weight ( . semibold) )
202+
203+ Text (
204+ " Reuse existing local Codex profiles/auth files. Selecting one switches CodexBar back to the local live-system account. " )
205+ . font ( . footnote)
206+ . foregroundStyle ( . secondary)
207+
208+ if self . state. localProfiles. isEmpty {
209+ Text ( " No saved local Codex profiles found in ~/.codex/profiles. " )
210+ . font ( . footnote)
211+ . foregroundStyle ( . secondary)
212+ } else {
213+ VStack ( alignment: . leading, spacing: 10 ) {
214+ ForEach ( self . state. localProfiles) { profile in
215+ CodexLocalProfileRowView (
216+ profile: profile,
217+ onSelect: { self . selectLocalProfile ( profile. id) } )
218+ }
219+ }
220+ }
221+
222+ if let notice = self . state. localProfilesNotice {
223+ Text ( notice. text)
224+ . font ( . footnote)
225+ . foregroundStyle ( notice. tone == . warning ? . red : . secondary)
226+ . fixedSize ( horizontal: false , vertical: true )
227+ }
228+
229+ HStack ( spacing: 8 ) {
230+ Button ( " Reload profiles " ) {
231+ self . reloadLocalProfiles ( )
232+ }
233+ . buttonStyle ( . bordered)
234+ . controlSize ( . small)
235+
236+ Button ( " Open profiles folder " ) {
237+ self . openLocalProfilesFolder ( )
238+ }
239+ . buttonStyle ( . bordered)
240+ . controlSize ( . small)
241+ }
242+ }
243+ . disabled ( self . state. isAuthenticatingManagedAccount || self . state. isAuthenticatingLiveAccount)
244+ }
170245 }
171246 }
172247
@@ -223,3 +298,60 @@ private struct CodexAccountsSectionRowView: View {
223298 }
224299 }
225300}
301+
302+ private struct CodexLocalProfileRowView : View {
303+ let profile : CodexDiscoveredProfileState
304+ let onSelect : ( ) -> Void
305+
306+ var body : some View {
307+ HStack ( alignment: . center, spacing: 12 ) {
308+ VStack ( alignment: . leading, spacing: 3 ) {
309+ HStack ( alignment: . firstTextBaseline, spacing: 6 ) {
310+ Text ( self . profile. title)
311+ . font ( . subheadline. weight ( . semibold) )
312+ if self . profile. isDisplayed {
313+ CodexLocalProfileBadgeView ( title: " Displayed " , tone: . emphasized)
314+ }
315+ if self . profile. isLive {
316+ CodexLocalProfileBadgeView ( title: " Live " , tone: . subtle)
317+ }
318+ }
319+ if let subtitle = self . profile. subtitle, !subtitle. isEmpty {
320+ Text ( subtitle)
321+ . font ( . footnote)
322+ . foregroundStyle ( . secondary)
323+ }
324+ if let detail = self . profile. detail, !detail. isEmpty {
325+ Text ( detail)
326+ . font ( . caption)
327+ . foregroundStyle ( . secondary)
328+ }
329+ }
330+
331+ Spacer ( minLength: 8 )
332+
333+ Button ( self . profile. isDisplayed ? " Displayed " : " Display " ) {
334+ self . onSelect ( )
335+ }
336+ . buttonStyle ( . bordered)
337+ . controlSize ( . small)
338+ . disabled ( self . profile. isDisplayed)
339+ }
340+ }
341+ }
342+
343+ private struct CodexLocalProfileBadgeView : View {
344+ enum Tone {
345+ case emphasized
346+ case subtle
347+ }
348+
349+ let title : String
350+ let tone : Tone
351+
352+ var body : some View {
353+ Text ( self . title)
354+ . font ( . caption. weight ( . semibold) )
355+ . foregroundStyle ( self . tone == . emphasized ? Color . accentColor : . secondary)
356+ }
357+ }
0 commit comments