@@ -3,6 +3,7 @@ package daemon
33import (
44 "context"
55 "encoding/json"
6+ "errors"
67 "fmt"
78 "net/http"
89 "os"
@@ -16,6 +17,14 @@ const codexUsageCacheTTL = 10 * time.Minute
1617var (
1718 loadCodexAuthFunc = loadCodexAuth
1819 fetchCodexUsageFunc = fetchCodexUsage
20+ codexPathExistsFunc = codexPathExists
21+ )
22+
23+ var (
24+ errCodexDirMissing = errors .New ("codex directory missing" )
25+ errCodexAuthFileMissing = errors .New ("codex auth file missing" )
26+ errCodexAuthInvalid = errors .New ("codex auth invalid" )
27+ errCodexTokenInvalid = errors .New ("codex token invalid" )
1928)
2029
2130// CodexRateLimitData holds the parsed rate limit data from the Codex API
@@ -63,15 +72,79 @@ type codexIDTokenClaims struct {
6372 AccountID string `json:"accountId"`
6473}
6574
75+ func codexConfigDirPath () (string , error ) {
76+ homeDir , err := os .UserHomeDir ()
77+ if err != nil {
78+ return "" , fmt .Errorf ("failed to get home directory: %w" , err )
79+ }
80+
81+ return filepath .Join (homeDir , ".codex" ), nil
82+ }
83+
84+ func codexAuthFilePath () (string , error ) {
85+ dir , err := codexConfigDirPath ()
86+ if err != nil {
87+ return "" , err
88+ }
89+
90+ return filepath .Join (dir , "auth.json" ), nil
91+ }
92+
93+ func codexPathExists (path string ) (bool , error ) {
94+ _ , err := os .Stat (path )
95+ if err == nil {
96+ return true , nil
97+ }
98+ if errors .Is (err , os .ErrNotExist ) {
99+ return false , nil
100+ }
101+ return false , err
102+ }
103+
104+ func codexInstallationStatus () (bool , error ) {
105+ dirPath , err := codexConfigDirPath ()
106+ if err != nil {
107+ return false , err
108+ }
109+ exists , err := codexPathExistsFunc (dirPath )
110+ if err != nil {
111+ return false , err
112+ }
113+ if ! exists {
114+ return false , errCodexDirMissing
115+ }
116+
117+ authPath , err := codexAuthFilePath ()
118+ if err != nil {
119+ return false , err
120+ }
121+ exists , err = codexPathExistsFunc (authPath )
122+ if err != nil {
123+ return false , err
124+ }
125+ if ! exists {
126+ return false , errCodexAuthFileMissing
127+ }
128+
129+ return true , nil
130+ }
131+
132+ func CodexInstallationStatus () (bool , error ) {
133+ return codexInstallationStatus ()
134+ }
135+
66136// loadCodexAuth reads the Codex authentication data from ~/.codex/auth.json.
67137func loadCodexAuth () (* codexAuthData , error ) {
68- homeDir , err := os . UserHomeDir ()
138+ authPath , err := codexAuthFilePath ()
69139 if err != nil {
70- return nil , fmt . Errorf ( "failed to get home directory: %w" , err )
140+ return nil , err
71141 }
72142
73- data , err := os .ReadFile (filepath . Join ( homeDir , ".codex" , "auth.json" ) )
143+ data , err := os .ReadFile (authPath )
74144 if err != nil {
145+ if errors .Is (err , os .ErrNotExist ) {
146+ return nil , errCodexAuthFileMissing
147+ }
75148 return nil , fmt .Errorf ("codex auth file read failed: %w" , err )
76149 }
77150
@@ -81,7 +154,7 @@ func loadCodexAuth() (*codexAuthData, error) {
81154 }
82155
83156 if auth .TokenData == nil || auth .TokenData .AccessToken == "" {
84- return nil , fmt . Errorf ( "no access token found in codex auth" )
157+ return nil , errCodexAuthInvalid
85158 }
86159
87160 accountID := ""
@@ -133,6 +206,9 @@ func fetchCodexUsage(ctx context.Context, auth *codexAuthData) (*CodexRateLimitD
133206 defer resp .Body .Close ()
134207
135208 if resp .StatusCode != http .StatusOK {
209+ if resp .StatusCode == http .StatusUnauthorized || resp .StatusCode == http .StatusForbidden {
210+ return nil , errCodexTokenInvalid
211+ }
136212 return nil , fmt .Errorf ("codex usage API returned status %d" , resp .StatusCode )
137213 }
138214
@@ -172,3 +248,18 @@ func shortenCodexAPIError(err error) string {
172248
173249 return "network"
174250}
251+
252+ func CodexSyncSkipReason (err error ) (string , bool ) {
253+ switch {
254+ case errors .Is (err , errCodexDirMissing ):
255+ return "missing_codex_dir" , true
256+ case errors .Is (err , errCodexAuthFileMissing ):
257+ return "missing_auth_file" , true
258+ case errors .Is (err , errCodexAuthInvalid ):
259+ return "invalid_auth" , true
260+ case errors .Is (err , errCodexTokenInvalid ):
261+ return "invalid_auth_token" , true
262+ default :
263+ return "" , false
264+ }
265+ }
0 commit comments