codexmgr 0.3.0__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. {codexmgr-0.3.0 → codexmgr-0.4.1}/PKG-INFO +35 -11
  2. {codexmgr-0.3.0 → codexmgr-0.4.1}/README.md +34 -10
  3. {codexmgr-0.3.0 → codexmgr-0.4.1}/pyproject.toml +1 -1
  4. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/__init__.py +1 -1
  5. codexmgr-0.4.1/src/codexmgr/agents/file.py +43 -0
  6. codexmgr-0.4.1/src/codexmgr/commands/codex.py +114 -0
  7. codexmgr-0.4.1/src/codexmgr/commands/codex_launch.py +102 -0
  8. codexmgr-0.4.1/src/codexmgr/core/managed_text.py +78 -0
  9. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/core/paths.py +24 -0
  10. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/interface/cli.py +10 -2
  11. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/project/generated.py +2 -1
  12. codexmgr-0.4.1/src/codexmgr/project/gitignore.py +43 -0
  13. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_codex_cli.py +157 -74
  14. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_paths.py +12 -1
  15. codexmgr-0.4.1/tests/test_project_gitignore.py +156 -0
  16. {codexmgr-0.3.0 → codexmgr-0.4.1}/uv.lock +1 -1
  17. codexmgr-0.3.0/src/codexmgr/agents/file.py +0 -95
  18. codexmgr-0.3.0/src/codexmgr/commands/codex.py +0 -224
  19. {codexmgr-0.3.0 → codexmgr-0.4.1}/.github/actions/install_package/action.yml +0 -0
  20. {codexmgr-0.3.0 → codexmgr-0.4.1}/.github/workflows/py_test.yml +0 -0
  21. {codexmgr-0.3.0 → codexmgr-0.4.1}/.github/workflows/version_publish_main.yml +0 -0
  22. {codexmgr-0.3.0 → codexmgr-0.4.1}/.gitignore +0 -0
  23. {codexmgr-0.3.0 → codexmgr-0.4.1}/pytest.toml +0 -0
  24. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/agents/__init__.py +0 -0
  25. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/agents/manager.py +0 -0
  26. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/agents/renderer.py +0 -0
  27. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/commands/__init__.py +0 -0
  28. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/commands/codex_jit.py +0 -0
  29. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/commands/config_mutations.py +0 -0
  30. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/commands/health.py +0 -0
  31. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/commands/navigation.py +0 -0
  32. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/commands/rules.py +0 -0
  33. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/core/__init__.py +0 -0
  34. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/core/errors.py +0 -0
  35. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/core/options.py +0 -0
  36. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/core/toml_io.py +0 -0
  37. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/__init__.py +0 -0
  38. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/cli.py +0 -0
  39. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/config.py +0 -0
  40. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/copies.py +0 -0
  41. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/listing.py +0 -0
  42. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/resolution.py +0 -0
  43. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/sources.py +0 -0
  44. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/hooks/__init__.py +0 -0
  45. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/hooks/config.py +0 -0
  46. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/hooks/copies.py +0 -0
  47. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/hooks/listing.py +0 -0
  48. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/hooks/merge.py +0 -0
  49. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/hooks/resolution.py +0 -0
  50. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/hooks/sources.py +0 -0
  51. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/interface/__init__.py +0 -0
  52. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/interface/parser.py +0 -0
  53. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/interface/parsers/__init__.py +0 -0
  54. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/interface/parsers/agents.py +0 -0
  55. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/interface/parsers/mcp.py +0 -0
  56. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/mcp/__init__.py +0 -0
  57. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/mcp/apply.py +0 -0
  58. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/mcp/cli.py +0 -0
  59. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/mcp/config.py +0 -0
  60. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/mcp/discovery.py +0 -0
  61. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/mcp/fields.py +0 -0
  62. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/mcp/project.py +0 -0
  63. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/mcp/resolution.py +0 -0
  64. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/mcp/sources.py +0 -0
  65. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/mcp/tables.py +0 -0
  66. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/packages/__init__.py +0 -0
  67. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/packages/cli.py +0 -0
  68. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/packages/config.py +0 -0
  69. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/packages/listing.py +0 -0
  70. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/packages/mutation.py +0 -0
  71. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/packages/sources.py +0 -0
  72. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/project/__init__.py +0 -0
  73. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/project/apply.py +0 -0
  74. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/project/config.py +0 -0
  75. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/project/resolution.py +0 -0
  76. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/project/snapshot.py +0 -0
  77. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/project/state.py +0 -0
  78. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/project/sync.py +0 -0
  79. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/py.typed +0 -0
  80. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/rules/__init__.py +0 -0
  81. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/rules/config.py +0 -0
  82. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/rules/copies.py +0 -0
  83. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/rules/listing.py +0 -0
  84. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/rules/resolution.py +0 -0
  85. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/rules/sources.py +0 -0
  86. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/rules/tree.py +0 -0
  87. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/skills/__init__.py +0 -0
  88. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/skills/config.py +0 -0
  89. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/skills/copies.py +0 -0
  90. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/skills/listing.py +0 -0
  91. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/skills/resolution.py +0 -0
  92. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/skills/sources.py +0 -0
  93. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/__init__.py +0 -0
  94. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/app.py +0 -0
  95. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/cli.py +0 -0
  96. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/diff.py +0 -0
  97. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/focus.py +0 -0
  98. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/items.py +0 -0
  99. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/mcp_items.py +0 -0
  100. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/models.py +0 -0
  101. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/mutations.py +0 -0
  102. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/package_refs.py +0 -0
  103. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/package_selection.py +0 -0
  104. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/package_state.py +0 -0
  105. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/panels.py +0 -0
  106. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/rendering.py +0 -0
  107. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/rule_items.py +0 -0
  108. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/rule_tree.py +0 -0
  109. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/sections.py +0 -0
  110. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/staged_packages.py +0 -0
  111. {codexmgr-0.3.0 → codexmgr-0.4.1}/src/codexmgr/tui/state.py +0 -0
  112. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/conftest.py +0 -0
  113. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_agents_cli.py +0 -0
  114. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_agents_file.py +0 -0
  115. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_agentsmd_list_cli.py +0 -0
  116. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_agentsmd_tools_cli.py +0 -0
  117. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_apply_check_cli.py +0 -0
  118. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_cd_cli.py +0 -0
  119. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_cli.py +0 -0
  120. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_empty_skill_config_cli.py +0 -0
  121. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_home_resolution_cli.py +0 -0
  122. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_hooks_cli.py +0 -0
  123. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_mcp_apply_cli.py +0 -0
  124. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_mcp_cli.py +0 -0
  125. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_package_metadata.py +0 -0
  126. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_packages_cli.py +0 -0
  127. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_python_compatibility.py +0 -0
  128. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_renderer.py +0 -0
  129. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_rules_cli.py +0 -0
  130. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_skill_copies_cli.py +0 -0
  131. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_skill_home_sources_cli.py +0 -0
  132. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_skill_list_cli.py +0 -0
  133. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_skills_cli.py +0 -0
  134. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_status_doctor_cli.py +0 -0
  135. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_sync_cli.py +0 -0
  136. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_toml_io.py +0 -0
  137. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_tui_app.py +0 -0
  138. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_tui_selection_focus.py +0 -0
  139. {codexmgr-0.3.0 → codexmgr-0.4.1}/tests/test_tui_state.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codexmgr
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: Manage project-local Codex configuration from reusable templates
5
5
  Project-URL: Repository, https://github.com/JulianKimmig/codexmgr/
6
6
  Keywords: agents,cli,codex,configuration,skills
@@ -40,7 +40,7 @@ Use `codexmgr` when a project should:
40
40
  - enable packaged Codex setups made from those reusable pieces
41
41
  - keep reusable MCP server definitions out of the user-level Codex config
42
42
  - check whether generated Codex files match the project config
43
- - run `codex` with project `.codex/config.toml` values passed as `-c` overrides
43
+ - run `codex` with the project `.codex` directory as its local Codex home
44
44
 
45
45
  The basic model has three parts:
46
46
 
@@ -92,7 +92,8 @@ codexmgr setup
92
92
  ```
93
93
 
94
94
  `setup` creates `.codex/`, creates `.codex/codexmgr.toml` if it is missing, and
95
- runs `apply`. Existing project config is preserved.
95
+ runs `apply`. Existing project config is preserved. Apply also creates or
96
+ refreshes codexmgr's managed block in `.codex/.gitignore`.
96
97
 
97
98
  Create a reusable `AGENTS.md` snippet in the manager home. This command creates
98
99
  `$CODEXMGR_HOME/agentsmd/coding.toml` and refuses to overwrite an existing file:
@@ -146,6 +147,9 @@ file for you, and you can also edit it by hand when that is clearer.
146
147
 
147
148
  `apply` resolves the source config and may write or update these managed files:
148
149
 
150
+ - `.codex/.gitignore`: preserves manual rules outside its managed block,
151
+ ignores every unknown `.codex` entry by default, and explicitly exposes the
152
+ codexmgr-owned files and directories listed below
149
153
  - `.codex/codexmgr.lock`: resolved AGENTS.md, agent, skill, hook, rule, and MCP
150
154
  state
151
155
  - `.codex/config.toml`: project-local Codex config, including generated
@@ -368,6 +372,12 @@ managed project files, and refreshes generated state.
368
372
  writing them. `apply --diff` also avoids writing and prints unified diffs for
369
373
  the expected generated-file changes.
370
374
 
375
+ The `.codex/.gitignore` managed block uses a default-ignore rule rather than a
376
+ list of known runtime filenames. New caches, databases, sessions, and other
377
+ state created by future Codex versions therefore remain outside Git without a
378
+ codexmgr update. Project configuration, custom agents, and hook support files
379
+ remain visible to Git.
380
+
371
381
  `doctor` checks project setup, home environment variables, project TOML syntax,
372
382
  referenced snippets, enabled skills, enabled custom agents, enabled hook
373
383
  bundles, enabled rules, and stale generated files.
@@ -460,14 +470,28 @@ Codex wrapper command:
460
470
 
461
471
  ```bash
462
472
  codexmgr codex <args...>
463
- ```
464
-
465
- `codexmgr codex` applies the current project config, flattens
466
- `.codex/config.toml` into `-c key=value` overrides, and forwards the remaining
467
- arguments to the real `codex` command.
468
-
469
- User-provided `-c` or `--config` overrides are merged after project config.
470
- Scalar values replace earlier values, while list values append.
473
+ codexmgr codex --simple <args...>
474
+ ```
475
+
476
+ By default, `codexmgr codex` applies the current project config, launches the
477
+ real `codex` command with `CODEX_HOME` set to
478
+ `<project>/.codex/.runtime`, and forwards Codex arguments unchanged. For a
479
+ trusted project, Codex discovers the tracked `.codex/config.toml` as its
480
+ project configuration independently of `CODEX_HOME`. This keeps generated MCP
481
+ servers and other shared project settings in Git while mutable Codex state is
482
+ written below the ignored `.codex/.runtime` directory. User-provided `-c` and
483
+ `--config` arguments retain their normal Codex behavior.
484
+
485
+ The wrapper links the global authentication file into
486
+ `.codex/.runtime/auth.json`. It uses `CODEX_GLOBAL_AUTH` when that variable is
487
+ non-empty and otherwise uses `$HOME/.codex/auth.json`. A missing auth source
488
+ produces a warning but does not stop Codex, which can then authenticate within
489
+ the project runtime home.
490
+
491
+ Use `--simple` immediately after `codex` to run the basic command without
492
+ applying project state, changing the child `CODEX_HOME`, or managing an auth
493
+ link. This launch wrapper is intentionally CLI-only because the TUI does not
494
+ embed or proxy external terminal sessions.
471
495
 
472
496
  The wrapper can run with a just-in-time package/profile overlay without changing
473
497
  `.codex/codexmgr.toml`. Put Codex arguments after `--` when using this syntax:
@@ -16,7 +16,7 @@ Use `codexmgr` when a project should:
16
16
  - enable packaged Codex setups made from those reusable pieces
17
17
  - keep reusable MCP server definitions out of the user-level Codex config
18
18
  - check whether generated Codex files match the project config
19
- - run `codex` with project `.codex/config.toml` values passed as `-c` overrides
19
+ - run `codex` with the project `.codex` directory as its local Codex home
20
20
 
21
21
  The basic model has three parts:
22
22
 
@@ -68,7 +68,8 @@ codexmgr setup
68
68
  ```
69
69
 
70
70
  `setup` creates `.codex/`, creates `.codex/codexmgr.toml` if it is missing, and
71
- runs `apply`. Existing project config is preserved.
71
+ runs `apply`. Existing project config is preserved. Apply also creates or
72
+ refreshes codexmgr's managed block in `.codex/.gitignore`.
72
73
 
73
74
  Create a reusable `AGENTS.md` snippet in the manager home. This command creates
74
75
  `$CODEXMGR_HOME/agentsmd/coding.toml` and refuses to overwrite an existing file:
@@ -122,6 +123,9 @@ file for you, and you can also edit it by hand when that is clearer.
122
123
 
123
124
  `apply` resolves the source config and may write or update these managed files:
124
125
 
126
+ - `.codex/.gitignore`: preserves manual rules outside its managed block,
127
+ ignores every unknown `.codex` entry by default, and explicitly exposes the
128
+ codexmgr-owned files and directories listed below
125
129
  - `.codex/codexmgr.lock`: resolved AGENTS.md, agent, skill, hook, rule, and MCP
126
130
  state
127
131
  - `.codex/config.toml`: project-local Codex config, including generated
@@ -344,6 +348,12 @@ managed project files, and refreshes generated state.
344
348
  writing them. `apply --diff` also avoids writing and prints unified diffs for
345
349
  the expected generated-file changes.
346
350
 
351
+ The `.codex/.gitignore` managed block uses a default-ignore rule rather than a
352
+ list of known runtime filenames. New caches, databases, sessions, and other
353
+ state created by future Codex versions therefore remain outside Git without a
354
+ codexmgr update. Project configuration, custom agents, and hook support files
355
+ remain visible to Git.
356
+
347
357
  `doctor` checks project setup, home environment variables, project TOML syntax,
348
358
  referenced snippets, enabled skills, enabled custom agents, enabled hook
349
359
  bundles, enabled rules, and stale generated files.
@@ -436,14 +446,28 @@ Codex wrapper command:
436
446
 
437
447
  ```bash
438
448
  codexmgr codex <args...>
439
- ```
440
-
441
- `codexmgr codex` applies the current project config, flattens
442
- `.codex/config.toml` into `-c key=value` overrides, and forwards the remaining
443
- arguments to the real `codex` command.
444
-
445
- User-provided `-c` or `--config` overrides are merged after project config.
446
- Scalar values replace earlier values, while list values append.
449
+ codexmgr codex --simple <args...>
450
+ ```
451
+
452
+ By default, `codexmgr codex` applies the current project config, launches the
453
+ real `codex` command with `CODEX_HOME` set to
454
+ `<project>/.codex/.runtime`, and forwards Codex arguments unchanged. For a
455
+ trusted project, Codex discovers the tracked `.codex/config.toml` as its
456
+ project configuration independently of `CODEX_HOME`. This keeps generated MCP
457
+ servers and other shared project settings in Git while mutable Codex state is
458
+ written below the ignored `.codex/.runtime` directory. User-provided `-c` and
459
+ `--config` arguments retain their normal Codex behavior.
460
+
461
+ The wrapper links the global authentication file into
462
+ `.codex/.runtime/auth.json`. It uses `CODEX_GLOBAL_AUTH` when that variable is
463
+ non-empty and otherwise uses `$HOME/.codex/auth.json`. A missing auth source
464
+ produces a warning but does not stop Codex, which can then authenticate within
465
+ the project runtime home.
466
+
467
+ Use `--simple` immediately after `codex` to run the basic command without
468
+ applying project state, changing the child `CODEX_HOME`, or managing an auth
469
+ link. This launch wrapper is intentionally CLI-only because the TUI does not
470
+ embed or proxy external terminal sessions.
447
471
 
448
472
  The wrapper can run with a just-in-time package/profile overlay without changing
449
473
  `.codex/codexmgr.toml`. Put Codex arguments after `--` when using this syntax:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codexmgr"
3
- version = "0.3.0"
3
+ version = "0.4.1"
4
4
  description = "Manage project-local Codex configuration from reusable templates"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.3.0"
5
+ __version__ = "0.4.1"
@@ -0,0 +1,43 @@
1
+ """Helpers for preserving manual AGENTS.md content around a managed block."""
2
+
3
+ from pathlib import Path
4
+
5
+ from ..core.managed_text import render_managed_block
6
+
7
+ BEGIN_MARKER = "<!-- BEGIN CODEXMGR GENERATED -->"
8
+ END_MARKER = "<!-- END CODEXMGR GENERATED -->"
9
+
10
+
11
+ def write_managed_agents_md(path: Path, generated_markdown: str) -> None:
12
+ """Write generated markdown into the CODEXMGR managed block.
13
+
14
+ Args:
15
+ path: Project AGENTS.md path to create or update.
16
+ generated_markdown: Markdown content to place inside the managed block.
17
+
18
+ Returns:
19
+ None. The file is written with UTF-8 encoding.
20
+ """
21
+ current = path.read_text(encoding="utf-8") if path.exists() else ""
22
+ updated = render_managed_agents_md(current, generated_markdown)
23
+ path.write_text(updated, encoding="utf-8")
24
+
25
+
26
+ def render_managed_agents_md(current: str, generated_markdown: str) -> str:
27
+ """Render AGENTS.md content with an updated managed block.
28
+
29
+ Args:
30
+ current: Existing AGENTS.md content, or an empty string for a new file.
31
+ generated_markdown: Markdown content for the managed block.
32
+
33
+ Returns:
34
+ Full AGENTS.md content with the codexmgr managed block replaced or
35
+ appended.
36
+ """
37
+ return render_managed_block(
38
+ current,
39
+ generated_markdown,
40
+ begin_marker=BEGIN_MARKER,
41
+ end_marker=END_MARKER,
42
+ artifact_name="AGENTS.md",
43
+ )
@@ -0,0 +1,114 @@
1
+ """Orchestration and subprocess execution for the external Codex command."""
2
+
3
+ import subprocess
4
+ from pathlib import Path
5
+ from typing import TextIO
6
+
7
+ from ..core.errors import CommandError
8
+ from .codex_jit import parse_codex_jit_request, run_with_jit_overlay
9
+ from .codex_launch import (
10
+ build_project_codex_environment,
11
+ parse_codex_launch_request,
12
+ )
13
+
14
+
15
+ def run_codex_command(
16
+ cwd: Path,
17
+ codex_home: Path,
18
+ codexmgr_home: Path,
19
+ codex_args: list[str],
20
+ stderr: TextIO,
21
+ ) -> int:
22
+ """Select a launch mode and run the external Codex executable.
23
+
24
+ Args:
25
+ cwd: Project working directory for the external Codex process.
26
+ codex_home: Codex home used to resolve project skills.
27
+ codexmgr_home: codexmgr home containing package sources.
28
+ codex_args: Raw arguments after ``codexmgr codex``.
29
+ stderr: Stream that receives launch warnings.
30
+
31
+ Returns:
32
+ The external Codex process return code.
33
+ """
34
+ launch_request = parse_codex_launch_request(codex_args)
35
+ if launch_request.simple:
36
+ return run_codex(cwd, launch_request.codex_args)
37
+
38
+ request = parse_codex_jit_request(launch_request.codex_args)
39
+
40
+ def run_project_codex(project_cwd: Path, args: list[str]) -> int:
41
+ """Launch Codex with the prepared project-local environment.
42
+
43
+ Args:
44
+ project_cwd: Project working directory for the child process.
45
+ args: Arguments to proxy to the Codex executable.
46
+
47
+ Returns:
48
+ The external Codex process return code.
49
+ """
50
+ environment = build_project_codex_environment(project_cwd, stderr)
51
+ return _run_subprocess(project_cwd, args, environment)
52
+
53
+ if request.enabled:
54
+ return run_with_jit_overlay(
55
+ request,
56
+ cwd,
57
+ codex_home,
58
+ codexmgr_home,
59
+ run_project_codex,
60
+ )
61
+ from ..project.apply import apply_project_config
62
+
63
+ apply_project_config(cwd, codex_home, codexmgr_home)
64
+ return run_project_codex(cwd, launch_request.codex_args)
65
+
66
+
67
+ def run_codex(cwd: Path, codex_args: list[str]) -> int:
68
+ """Run the basic external Codex command without project modifications.
69
+
70
+ Args:
71
+ cwd: Project working directory for the external codex process.
72
+ codex_args: Arguments to pass through to codex.
73
+
74
+ Returns:
75
+ The external codex process return code.
76
+ """
77
+ return _run_subprocess(cwd, codex_args)
78
+
79
+
80
+ def build_codex_command(cwd: Path, codex_args: list[str]) -> list[str]:
81
+ """Build an argument-preserving external Codex command invocation.
82
+
83
+ Args:
84
+ cwd: Project working directory, retained for the public helper contract.
85
+ codex_args: Arguments to pass through to codex.
86
+
87
+ Returns:
88
+ The complete argv for the external codex process.
89
+ """
90
+ return ["codex", *codex_args]
91
+
92
+
93
+ def _run_subprocess(
94
+ cwd: Path,
95
+ codex_args: list[str],
96
+ environment: dict[str, str] | None = None,
97
+ ) -> int:
98
+ """Run Codex and translate executable lookup failures.
99
+
100
+ Args:
101
+ cwd: Working directory for the external Codex process.
102
+ codex_args: Arguments to pass through to Codex.
103
+ environment: Optional explicit child process environment.
104
+
105
+ Returns:
106
+ The external Codex process return code.
107
+ """
108
+ command = build_codex_command(cwd, codex_args)
109
+ try:
110
+ if environment is None:
111
+ return subprocess.run(command, cwd=cwd).returncode
112
+ return subprocess.run(command, cwd=cwd, env=environment).returncode
113
+ except FileNotFoundError as exc:
114
+ raise CommandError("codex command not found") from exc
@@ -0,0 +1,102 @@
1
+ """Launch-mode parsing and project-local Codex environment preparation."""
2
+
3
+ import os
4
+ from collections.abc import Mapping
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import TextIO
8
+
9
+ from ..core.paths import project_codex_dir, project_codex_runtime_dir
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class CodexLaunchRequest:
14
+ """A parsed request to launch Codex in managed or simple mode.
15
+
16
+ Attributes:
17
+ simple: Whether to bypass all codexmgr project preparation.
18
+ codex_args: Arguments to proxy to the external Codex executable.
19
+ """
20
+
21
+ simple: bool
22
+ codex_args: list[str]
23
+
24
+
25
+ def parse_codex_launch_request(codex_args: list[str]) -> CodexLaunchRequest:
26
+ """Parse the optional leading codexmgr ``--simple`` launch flag.
27
+
28
+ Args:
29
+ codex_args: Raw arguments following ``codexmgr codex``.
30
+
31
+ Returns:
32
+ The selected launch mode and external Codex arguments.
33
+ """
34
+ if codex_args[:1] == ["--simple"]:
35
+ return CodexLaunchRequest(simple=True, codex_args=codex_args[1:])
36
+ return CodexLaunchRequest(simple=False, codex_args=list(codex_args))
37
+
38
+
39
+ def build_project_codex_environment(
40
+ cwd: Path,
41
+ stderr: TextIO,
42
+ environ: Mapping[str, str] | None = None,
43
+ ) -> dict[str, str]:
44
+ """Build the child environment for a project-local Codex launch.
45
+
46
+ Args:
47
+ cwd: Project directory whose local Codex home may be selected.
48
+ stderr: Stream that receives a missing-auth warning.
49
+ environ: Source process environment, or ``os.environ`` when omitted.
50
+
51
+ Returns:
52
+ A copied child environment. ``CODEX_HOME`` points to
53
+ ``.codex/.runtime`` when the project ``.codex`` directory contains
54
+ ``config.toml``.
55
+ """
56
+ environment = dict(os.environ if environ is None else environ)
57
+ config_dir = project_codex_dir(cwd)
58
+ if not (config_dir / "config.toml").is_file():
59
+ return environment
60
+
61
+ runtime_dir = project_codex_runtime_dir(cwd)
62
+ runtime_dir.mkdir(parents=True, exist_ok=True)
63
+ environment["CODEX_HOME"] = str(runtime_dir)
64
+ global_auth = _global_auth_path(environment)
65
+ if global_auth.is_file():
66
+ _replace_auth_link(runtime_dir / "auth.json", global_auth)
67
+ else:
68
+ stderr.write(
69
+ "codexmgr codex: warning: no global auth found at "
70
+ f"{global_auth}; you may need to auth once.\n"
71
+ )
72
+ return environment
73
+
74
+
75
+ def _global_auth_path(environment: Mapping[str, str]) -> Path:
76
+ """Resolve the read-only global authentication source.
77
+
78
+ Args:
79
+ environment: Launch environment containing HOME and optional override.
80
+
81
+ Returns:
82
+ ``CODEX_GLOBAL_AUTH`` when non-empty, otherwise HOME's Codex auth path.
83
+ """
84
+ configured_auth = environment.get("CODEX_GLOBAL_AUTH")
85
+ if configured_auth:
86
+ return Path(configured_auth)
87
+ return Path(environment["HOME"]) / ".codex" / "auth.json"
88
+
89
+
90
+ def _replace_auth_link(auth_link: Path, global_auth: Path) -> None:
91
+ """Replace the project auth entry with a link to global authentication.
92
+
93
+ Args:
94
+ auth_link: Project-local ``.codex/.runtime/auth.json`` link path.
95
+ global_auth: Existing global authentication source file.
96
+
97
+ Returns:
98
+ None. Any existing file or symbolic link at ``auth_link`` is replaced.
99
+ """
100
+ if auth_link.exists() or auth_link.is_symlink():
101
+ auth_link.unlink()
102
+ auth_link.symlink_to(global_auth)
@@ -0,0 +1,78 @@
1
+ """Reusable rendering for marker-delimited codexmgr-managed text blocks."""
2
+
3
+ from ..core.errors import CommandError
4
+
5
+
6
+ def render_managed_block(
7
+ current: str,
8
+ generated: str,
9
+ *,
10
+ begin_marker: str,
11
+ end_marker: str,
12
+ artifact_name: str,
13
+ ) -> str:
14
+ """Replace or append one marker-delimited generated text block.
15
+
16
+ Args:
17
+ current: Existing artifact content, or an empty string when missing.
18
+ generated: Generated content to place between the markers.
19
+ begin_marker: Exact line marking the beginning of managed content.
20
+ end_marker: Exact line marking the end of managed content.
21
+ artifact_name: Human-readable artifact name for validation errors.
22
+
23
+ Returns:
24
+ Full artifact content with one current codexmgr-managed block.
25
+
26
+ Raises:
27
+ CommandError: If existing managed-block markers are malformed.
28
+ """
29
+ begin_count = current.count(begin_marker)
30
+ end_count = current.count(end_marker)
31
+ if begin_count != end_count:
32
+ raise CommandError(
33
+ f"Incomplete CODEXMGR generated block in {artifact_name}"
34
+ )
35
+ if begin_count > 1:
36
+ raise CommandError(f"Multiple CODEXMGR generated blocks in {artifact_name}")
37
+
38
+ block = _format_block(generated, begin_marker, end_marker)
39
+ if begin_count == 0:
40
+ return _append_block(current, block)
41
+
42
+ begin_index = current.index(begin_marker)
43
+ end_index = current.index(end_marker, begin_index)
44
+ after_index = end_index + len(end_marker)
45
+ return f"{current[:begin_index]}{block}{current[after_index:]}"
46
+
47
+
48
+ def _append_block(current: str, block: str) -> str:
49
+ """Append a formatted managed block to existing content.
50
+
51
+ Args:
52
+ current: Existing artifact content.
53
+ block: Fully formatted block including both markers.
54
+
55
+ Returns:
56
+ Content ending in the appended managed block and a newline.
57
+ """
58
+ if not current:
59
+ return f"{block}\n"
60
+ trimmed = current.rstrip("\n")
61
+ return f"{trimmed}\n\n{block}\n"
62
+
63
+
64
+ def _format_block(generated: str, begin_marker: str, end_marker: str) -> str:
65
+ """Wrap generated content in its managed-block markers.
66
+
67
+ Args:
68
+ generated: Generated text to normalize and wrap.
69
+ begin_marker: Beginning marker line.
70
+ end_marker: Ending marker line.
71
+
72
+ Returns:
73
+ Marker-delimited content without a trailing newline.
74
+ """
75
+ body = generated.rstrip("\n")
76
+ if not body:
77
+ return f"{begin_marker}\n{end_marker}"
78
+ return f"{begin_marker}\n{body}\n{end_marker}"
@@ -36,6 +36,18 @@ def project_codex_dir(cwd: Path) -> Path:
36
36
  return cwd / ".codex"
37
37
 
38
38
 
39
+ def project_codex_runtime_dir(cwd: Path) -> Path:
40
+ """Return the project-local Codex runtime-home path.
41
+
42
+ Args:
43
+ cwd: Project directory.
44
+
45
+ Returns:
46
+ The ignored ``.codex/.runtime`` directory used as ``CODEX_HOME``.
47
+ """
48
+ return project_codex_dir(cwd) / ".runtime"
49
+
50
+
39
51
  def config_path(cwd: Path) -> Path:
40
52
  """Return the project codexmgr.toml path.
41
53
 
@@ -72,6 +84,18 @@ def codex_config_path(cwd: Path) -> Path:
72
84
  return project_codex_dir(cwd) / "config.toml"
73
85
 
74
86
 
87
+ def codex_gitignore_path(cwd: Path) -> Path:
88
+ """Return the managed project Codex ignore-file path.
89
+
90
+ Args:
91
+ cwd: Project directory.
92
+
93
+ Returns:
94
+ Path to .codex/.gitignore.
95
+ """
96
+ return project_codex_dir(cwd) / ".gitignore"
97
+
98
+
75
99
  def agents_md_path(cwd: Path) -> Path:
76
100
  """Return the project AGENTS.md path.
77
101
 
@@ -65,7 +65,7 @@ def main(
65
65
  )
66
66
 
67
67
  try:
68
- return _dispatch(args, project_dir, codex_dir, codexmgr_dir, out)
68
+ return _dispatch(args, project_dir, codex_dir, codexmgr_dir, out, err)
69
69
  except CommandError as exc:
70
70
  err.write(f"{exc}\n")
71
71
  return 1
@@ -100,6 +100,7 @@ def _dispatch(
100
100
  codex_home: Path,
101
101
  codexmgr_home: Path,
102
102
  stdout: TextIO,
103
+ stderr: TextIO,
103
104
  ) -> int:
104
105
  """Run the parsed command.
105
106
 
@@ -109,6 +110,7 @@ def _dispatch(
109
110
  codex_home: Global Codex home for resolving named skills.
110
111
  codexmgr_home: codexmgr home for resolving named AGENTS.md templates.
111
112
  stdout: Stream for command output.
113
+ stderr: Stream for command warnings.
112
114
 
113
115
  Returns:
114
116
  A process-style exit code where zero means success.
@@ -136,7 +138,13 @@ def _dispatch(
136
138
  return run_codexmgr_home_action(codexmgr_home, args.cd_action, stdout)
137
139
 
138
140
  if args.command == "codex":
139
- return run_codex_command(cwd, codex_home, codexmgr_home, args.codex_args)
141
+ return run_codex_command(
142
+ cwd,
143
+ codex_home,
144
+ codexmgr_home,
145
+ args.codex_args,
146
+ stderr,
147
+ )
140
148
 
141
149
  if args.command == "agentsmd" and args.agentsmd_command == "list":
142
150
  options = list_agentsmd_options(codexmgr_home)
@@ -3,6 +3,7 @@
3
3
  from pathlib import Path
4
4
  from typing import Any
5
5
 
6
+ from .gitignore import build_project_gitignore_file
6
7
  from .state import GeneratedFile
7
8
  from ..agents.file import render_managed_agents_md
8
9
  from ..agents.renderer import render_agents_markdown
@@ -67,7 +68,7 @@ def build_generated_files(
67
68
  Returns:
68
69
  Expected generated files in write order.
69
70
  """
70
- files: list[GeneratedFile] = []
71
+ files: list[GeneratedFile] = [build_project_gitignore_file(cwd)]
71
72
  if lock_data:
72
73
  files.append(GeneratedFile(lock_path(cwd), dump_toml(lock_data)))
73
74
  if "agents_md" in config:
@@ -0,0 +1,43 @@
1
+ """Managed ignore rules for project-local Codex runtime state."""
2
+
3
+ from pathlib import Path
4
+
5
+ from .state import GeneratedFile
6
+ from ..core.managed_text import render_managed_block
7
+ from ..core.paths import codex_gitignore_path
8
+
9
+ BEGIN_MARKER = "# BEGIN CODEXMGR GENERATED"
10
+ END_MARKER = "# END CODEXMGR GENERATED"
11
+ MANAGED_IGNORE_RULES = """*
12
+ !/.gitignore
13
+ !/codexmgr.toml
14
+ !/codexmgr.lock
15
+ !/config.toml
16
+ !/hooks.json
17
+ !/agents/
18
+ !/agents/**
19
+ !/hooks/
20
+ !/hooks/**
21
+ __pycache__/
22
+ """
23
+
24
+
25
+ def build_project_gitignore_file(cwd: Path) -> GeneratedFile:
26
+ """Build the future-proof project Codex ignore-file state.
27
+
28
+ Args:
29
+ cwd: Project directory containing the local ``.codex`` home.
30
+
31
+ Returns:
32
+ Generated file preserving manual content outside codexmgr's block.
33
+ """
34
+ path = codex_gitignore_path(cwd)
35
+ current = path.read_text(encoding="utf-8") if path.exists() else ""
36
+ content = render_managed_block(
37
+ current,
38
+ MANAGED_IGNORE_RULES,
39
+ begin_marker=BEGIN_MARKER,
40
+ end_marker=END_MARKER,
41
+ artifact_name=".codex/.gitignore",
42
+ )
43
+ return GeneratedFile(path, content)