Coverage for sources / agentsmgr / renderers / codex.py: 59%

37 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-04 21:55 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Codex CLI renderer implementation. 

22 

23 Provides path resolution and targeting mode validation for Codex CLI. 

24''' 

25 

26 

27from . import __ 

28from .base import RENDERERS as _RENDERERS 

29from .base import ExplicitTargetMode as _ExplicitTargetMode 

30from .base import RendererBase as _RendererBase 

31 

32 

33class CodexRenderer( _RendererBase ): 

34 ''' Renderer for Codex CLI coder. 

35 

36 Supports both per-user and per-project configuration modes. 

37 

38 Per-project mode stores configuration under 

39 `.auxiliary/configuration/coders/codex/` and exposes it via a 

40 `.codex` symlink at project root (created by population logic). 

41 

42 Per-user mode respects CODEX_HOME environment variable with fallback 

43 to configuration overrides and default location. 

44 ''' 

45 

46 name = 'codex' 

47 modes_available = frozenset( ( 'per-user', 'per-project' ) ) 

48 mode_default = 'per-project' 

49 memory_filename = 'AGENTS.md' 

50 item_types_available = frozenset( ( 'skills', ) ) 

51 

52 def get_template_flavor( self, item_type: str ) -> str: 

53 ''' Determines template flavor for Codex CLI. 

54 

55 Codex uses same markdown format as Claude for all item types, 

56 so always returns 'claude' flavor. 

57 ''' 

58 return 'claude' 

59 

60 def provide_project_symlinks( 

61 self, target: __.Path 

62 ) -> __.cabc.Sequence[ tuple[ __.Path, __.Path ] ]: 

63 ''' Provides symlinks required for Codex CLI in per-project mode. 

64 

65 Codex expects project configuration under `.codex/`. We keep the 

66 canonical configuration under `.auxiliary/configuration/` and 

67 expose it via the standard coder directory symlink. 

68 ''' 

69 return super( ).provide_project_symlinks( target ) 

70 

71 def resolve_base_directory( 

72 self, 

73 mode: _ExplicitTargetMode, 

74 target: __.Path, 

75 configuration: __.cabc.Mapping[ str, __.typx.Any ], 

76 environment: __.cabc.Mapping[ str, str ], 

77 ) -> __.Path: 

78 ''' Resolves base output directory for Codex CLI. 

79 

80 Per-project: `.auxiliary/configuration/coders/codex/` 

81 Per-user: respects precedence: CODEX_HOME environment variable, 

82 configuration file override (home key), or default `~/.codex`. 

83 ''' 

84 self.validate_mode( mode ) 

85 if mode == 'per-project': 

86 return target / ".auxiliary/configuration/coders/codex" 

87 if mode == 'per-user': 87 ↛ 89line 87 didn't jump to line 89 because the condition on line 87 was always true

88 return self._resolve_user_directory( configuration, environment ) 

89 raise __.TargetModeNoSupport( self.name, mode ) 

90 

91 def _resolve_user_directory( 

92 self, 

93 configuration: __.cabc.Mapping[ str, __.typx.Any ], 

94 environment: __.cabc.Mapping[ str, str ], 

95 ) -> __.Path: 

96 ''' Resolves per-user directory following precedence rules. 

97 

98 Precedence order: 

99 1. CODEX_HOME environment variable 

100 2. Configuration file override (home key for this coder) 

101 3. Default ~/.codex location 

102 ''' 

103 if 'CODEX_HOME' in environment: 103 ↛ 106line 103 didn't jump to line 106 because the condition on line 103 was always true

104 directory = __.Path( environment[ 'CODEX_HOME' ] ) 

105 return directory.expanduser( ) 

106 coder_configuration = self._extract_coder_configuration( 

107 configuration ) 

108 if 'home' in coder_configuration: 

109 directory = __.Path( coder_configuration[ 'home' ] ) 

110 return directory.expanduser( ) 

111 return __.Path.home( ) / '.codex' 

112 

113 def _extract_coder_configuration( 

114 self, configuration: __.cabc.Mapping[ str, __.typx.Any ] 

115 ) -> __.cabc.Mapping[ str, __.typx.Any ]: 

116 ''' Extracts configuration for this specific coder. 

117 

118 Looks for coder entry in configuration coders array by name. 

119 ''' 

120 coders = configuration.get( 'coders', ( ) ) 

121 for coder in coders: 

122 if coder.get( 'name' ) == self.name: 

123 return coder 

124 return { } 

125 

126 

127_RENDERERS[ 'codex' ] = CodexRenderer( )