codexmgr 0.4.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 (137) hide show
  1. {codexmgr-0.4.0 → codexmgr-0.4.1}/PKG-INFO +14 -9
  2. {codexmgr-0.4.0 → codexmgr-0.4.1}/README.md +13 -8
  3. {codexmgr-0.4.0 → codexmgr-0.4.1}/pyproject.toml +1 -1
  4. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/__init__.py +1 -1
  5. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/commands/codex_launch.py +12 -8
  6. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/core/paths.py +12 -0
  7. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_codex_cli.py +26 -12
  8. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_paths.py +12 -1
  9. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_project_gitignore.py +3 -2
  10. {codexmgr-0.4.0 → codexmgr-0.4.1}/uv.lock +1 -1
  11. {codexmgr-0.4.0 → codexmgr-0.4.1}/.github/actions/install_package/action.yml +0 -0
  12. {codexmgr-0.4.0 → codexmgr-0.4.1}/.github/workflows/py_test.yml +0 -0
  13. {codexmgr-0.4.0 → codexmgr-0.4.1}/.github/workflows/version_publish_main.yml +0 -0
  14. {codexmgr-0.4.0 → codexmgr-0.4.1}/.gitignore +0 -0
  15. {codexmgr-0.4.0 → codexmgr-0.4.1}/pytest.toml +0 -0
  16. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/agents/__init__.py +0 -0
  17. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/agents/file.py +0 -0
  18. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/agents/manager.py +0 -0
  19. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/agents/renderer.py +0 -0
  20. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/commands/__init__.py +0 -0
  21. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/commands/codex.py +0 -0
  22. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/commands/codex_jit.py +0 -0
  23. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/commands/config_mutations.py +0 -0
  24. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/commands/health.py +0 -0
  25. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/commands/navigation.py +0 -0
  26. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/commands/rules.py +0 -0
  27. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/core/__init__.py +0 -0
  28. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/core/errors.py +0 -0
  29. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/core/managed_text.py +0 -0
  30. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/core/options.py +0 -0
  31. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/core/toml_io.py +0 -0
  32. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/__init__.py +0 -0
  33. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/cli.py +0 -0
  34. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/config.py +0 -0
  35. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/copies.py +0 -0
  36. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/listing.py +0 -0
  37. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/resolution.py +0 -0
  38. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/custom_agents/sources.py +0 -0
  39. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/hooks/__init__.py +0 -0
  40. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/hooks/config.py +0 -0
  41. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/hooks/copies.py +0 -0
  42. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/hooks/listing.py +0 -0
  43. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/hooks/merge.py +0 -0
  44. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/hooks/resolution.py +0 -0
  45. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/hooks/sources.py +0 -0
  46. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/interface/__init__.py +0 -0
  47. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/interface/cli.py +0 -0
  48. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/interface/parser.py +0 -0
  49. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/interface/parsers/__init__.py +0 -0
  50. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/interface/parsers/agents.py +0 -0
  51. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/interface/parsers/mcp.py +0 -0
  52. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/mcp/__init__.py +0 -0
  53. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/mcp/apply.py +0 -0
  54. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/mcp/cli.py +0 -0
  55. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/mcp/config.py +0 -0
  56. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/mcp/discovery.py +0 -0
  57. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/mcp/fields.py +0 -0
  58. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/mcp/project.py +0 -0
  59. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/mcp/resolution.py +0 -0
  60. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/mcp/sources.py +0 -0
  61. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/mcp/tables.py +0 -0
  62. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/packages/__init__.py +0 -0
  63. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/packages/cli.py +0 -0
  64. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/packages/config.py +0 -0
  65. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/packages/listing.py +0 -0
  66. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/packages/mutation.py +0 -0
  67. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/packages/sources.py +0 -0
  68. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/project/__init__.py +0 -0
  69. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/project/apply.py +0 -0
  70. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/project/config.py +0 -0
  71. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/project/generated.py +0 -0
  72. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/project/gitignore.py +0 -0
  73. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/project/resolution.py +0 -0
  74. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/project/snapshot.py +0 -0
  75. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/project/state.py +0 -0
  76. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/project/sync.py +0 -0
  77. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/py.typed +0 -0
  78. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/rules/__init__.py +0 -0
  79. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/rules/config.py +0 -0
  80. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/rules/copies.py +0 -0
  81. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/rules/listing.py +0 -0
  82. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/rules/resolution.py +0 -0
  83. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/rules/sources.py +0 -0
  84. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/rules/tree.py +0 -0
  85. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/skills/__init__.py +0 -0
  86. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/skills/config.py +0 -0
  87. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/skills/copies.py +0 -0
  88. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/skills/listing.py +0 -0
  89. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/skills/resolution.py +0 -0
  90. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/skills/sources.py +0 -0
  91. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/__init__.py +0 -0
  92. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/app.py +0 -0
  93. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/cli.py +0 -0
  94. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/diff.py +0 -0
  95. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/focus.py +0 -0
  96. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/items.py +0 -0
  97. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/mcp_items.py +0 -0
  98. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/models.py +0 -0
  99. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/mutations.py +0 -0
  100. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/package_refs.py +0 -0
  101. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/package_selection.py +0 -0
  102. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/package_state.py +0 -0
  103. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/panels.py +0 -0
  104. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/rendering.py +0 -0
  105. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/rule_items.py +0 -0
  106. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/rule_tree.py +0 -0
  107. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/sections.py +0 -0
  108. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/staged_packages.py +0 -0
  109. {codexmgr-0.4.0 → codexmgr-0.4.1}/src/codexmgr/tui/state.py +0 -0
  110. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/conftest.py +0 -0
  111. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_agents_cli.py +0 -0
  112. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_agents_file.py +0 -0
  113. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_agentsmd_list_cli.py +0 -0
  114. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_agentsmd_tools_cli.py +0 -0
  115. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_apply_check_cli.py +0 -0
  116. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_cd_cli.py +0 -0
  117. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_cli.py +0 -0
  118. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_empty_skill_config_cli.py +0 -0
  119. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_home_resolution_cli.py +0 -0
  120. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_hooks_cli.py +0 -0
  121. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_mcp_apply_cli.py +0 -0
  122. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_mcp_cli.py +0 -0
  123. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_package_metadata.py +0 -0
  124. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_packages_cli.py +0 -0
  125. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_python_compatibility.py +0 -0
  126. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_renderer.py +0 -0
  127. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_rules_cli.py +0 -0
  128. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_skill_copies_cli.py +0 -0
  129. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_skill_home_sources_cli.py +0 -0
  130. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_skill_list_cli.py +0 -0
  131. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_skills_cli.py +0 -0
  132. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_status_doctor_cli.py +0 -0
  133. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_sync_cli.py +0 -0
  134. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_toml_io.py +0 -0
  135. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_tui_app.py +0 -0
  136. {codexmgr-0.4.0 → codexmgr-0.4.1}/tests/test_tui_selection_focus.py +0 -0
  137. {codexmgr-0.4.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.4.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
@@ -474,14 +474,19 @@ codexmgr codex --simple <args...>
474
474
  ```
475
475
 
476
476
  By default, `codexmgr codex` applies the current project config, launches the
477
- real `codex` command with `CODEX_HOME` set to `<project>/.codex`, and forwards
478
- Codex arguments unchanged. Codex reads `.codex/config.toml` directly, so
479
- user-provided `-c` and `--config` arguments retain their normal Codex behavior.
480
-
481
- The wrapper links the global authentication file into `.codex/auth.json`. It
482
- uses `CODEX_GLOBAL_AUTH` when that variable is non-empty and otherwise uses
483
- `$HOME/.codex/auth.json`. A missing auth source produces a warning but does not
484
- stop Codex, which can then authenticate normally.
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.
485
490
 
486
491
  Use `--simple` immediately after `codex` to run the basic command without
487
492
  applying project state, changing the child `CODEX_HOME`, or managing an auth
@@ -450,14 +450,19 @@ codexmgr codex --simple <args...>
450
450
  ```
451
451
 
452
452
  By default, `codexmgr codex` applies the current project config, launches the
453
- real `codex` command with `CODEX_HOME` set to `<project>/.codex`, and forwards
454
- Codex arguments unchanged. Codex reads `.codex/config.toml` directly, so
455
- user-provided `-c` and `--config` arguments retain their normal Codex behavior.
456
-
457
- The wrapper links the global authentication file into `.codex/auth.json`. It
458
- uses `CODEX_GLOBAL_AUTH` when that variable is non-empty and otherwise uses
459
- `$HOME/.codex/auth.json`. A missing auth source produces a warning but does not
460
- stop Codex, which can then authenticate normally.
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.
461
466
 
462
467
  Use `--simple` immediately after `codex` to run the basic command without
463
468
  applying project state, changing the child `CODEX_HOME`, or managing an auth
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codexmgr"
3
- version = "0.4.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.4.0"
5
+ __version__ = "0.4.1"
@@ -6,6 +6,8 @@ from dataclasses import dataclass
6
6
  from pathlib import Path
7
7
  from typing import TextIO
8
8
 
9
+ from ..core.paths import project_codex_dir, project_codex_runtime_dir
10
+
9
11
 
10
12
  @dataclass(frozen=True)
11
13
  class CodexLaunchRequest:
@@ -47,19 +49,21 @@ def build_project_codex_environment(
47
49
  environ: Source process environment, or ``os.environ`` when omitted.
48
50
 
49
51
  Returns:
50
- A copied child environment. ``CODEX_HOME`` points to ``.codex`` when
51
- that directory contains ``config.toml``.
52
+ A copied child environment. ``CODEX_HOME`` points to
53
+ ``.codex/.runtime`` when the project ``.codex`` directory contains
54
+ ``config.toml``.
52
55
  """
53
56
  environment = dict(os.environ if environ is None else environ)
54
- project_codex_dir = cwd / ".codex"
55
- if not (project_codex_dir / "config.toml").is_file():
57
+ config_dir = project_codex_dir(cwd)
58
+ if not (config_dir / "config.toml").is_file():
56
59
  return environment
57
60
 
58
- project_codex_dir.mkdir(parents=True, exist_ok=True)
59
- environment["CODEX_HOME"] = str(project_codex_dir)
61
+ runtime_dir = project_codex_runtime_dir(cwd)
62
+ runtime_dir.mkdir(parents=True, exist_ok=True)
63
+ environment["CODEX_HOME"] = str(runtime_dir)
60
64
  global_auth = _global_auth_path(environment)
61
65
  if global_auth.is_file():
62
- _replace_auth_link(project_codex_dir / "auth.json", global_auth)
66
+ _replace_auth_link(runtime_dir / "auth.json", global_auth)
63
67
  else:
64
68
  stderr.write(
65
69
  "codexmgr codex: warning: no global auth found at "
@@ -87,7 +91,7 @@ def _replace_auth_link(auth_link: Path, global_auth: Path) -> None:
87
91
  """Replace the project auth entry with a link to global authentication.
88
92
 
89
93
  Args:
90
- auth_link: Project-local ``.codex/auth.json`` link path.
94
+ auth_link: Project-local ``.codex/.runtime/auth.json`` link path.
91
95
  global_auth: Existing global authentication source file.
92
96
 
93
97
  Returns:
@@ -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
 
@@ -9,7 +9,7 @@ from codexmgr.interface.cli import main
9
9
 
10
10
 
11
11
  def test_build_codex_command_proxies_args_without_flattening_project_config(workspace):
12
- """The command relies on project CODEX_HOME instead of injected overrides."""
12
+ """The command relies on project config instead of injected overrides."""
13
13
  project, _ = workspace
14
14
  (project / ".codex").mkdir()
15
15
  (project / ".codex" / "config.toml").write_text(
@@ -111,21 +111,26 @@ def test_build_codex_command_keeps_repeated_user_lists(workspace):
111
111
  ]
112
112
 
113
113
 
114
- def test_codex_subcommand_uses_project_home_auth_and_return_code(
114
+ def test_codex_subcommand_uses_project_runtime_home_auth_and_return_code(
115
115
  workspace,
116
116
  monkeypatch,
117
117
  ):
118
- """Default launch uses project CODEX_HOME and links the global auth file."""
118
+ """Default launch isolates runtime state and links the global auth file."""
119
119
  project, codex_home = workspace
120
120
  (project / ".codex").mkdir()
121
- (project / ".codex" / "codexmgr.toml").write_text("", encoding="utf-8")
121
+ (project / ".codex" / "codexmgr.toml").write_text(
122
+ '[mcp.servers.example]\ncommand = "example"\n',
123
+ encoding="utf-8",
124
+ )
122
125
  user_home = project.parent / "user-home"
123
126
  global_auth = user_home / ".codex" / "auth.json"
124
127
  global_auth.parent.mkdir(parents=True)
125
128
  global_auth.write_text('{"token": "test"}\n', encoding="utf-8")
126
129
  stale_auth = project.parent / "stale-auth.json"
127
130
  stale_auth.write_text('{"token": "stale"}\n', encoding="utf-8")
128
- (project / ".codex" / "auth.json").symlink_to(stale_auth)
131
+ runtime_home = project / ".codex" / ".runtime"
132
+ runtime_home.mkdir()
133
+ (runtime_home / "auth.json").symlink_to(stale_auth)
129
134
  monkeypatch.delenv("CODEX_GLOBAL_AUTH", raising=False)
130
135
  monkeypatch.setenv("HOME", str(user_home))
131
136
  monkeypatch.setenv("CODEXMGR_LAUNCH_TEST", "forwarded")
@@ -136,6 +141,9 @@ def test_codex_subcommand_uses_project_home_auth_and_return_code(
136
141
  captured["cwd"] = cwd
137
142
  captured["codex_home"] = env["CODEX_HOME"]
138
143
  captured["environment_marker"] = env["CODEXMGR_LAUNCH_TEST"]
144
+ captured["runtime_config_exists"] = (
145
+ project / ".codex" / ".runtime" / "config.toml"
146
+ ).exists()
139
147
  return SimpleNamespace(returncode=42)
140
148
 
141
149
  monkeypatch.setattr("codexmgr.commands.codex.subprocess.run", fake_run)
@@ -155,10 +163,15 @@ def test_codex_subcommand_uses_project_home_auth_and_return_code(
155
163
  assert captured == {
156
164
  "command": ["codex", "--help"],
157
165
  "cwd": project,
158
- "codex_home": str(project / ".codex"),
166
+ "codex_home": str(runtime_home),
159
167
  "environment_marker": "forwarded",
168
+ "runtime_config_exists": False,
160
169
  }
161
- auth_link = project / ".codex" / "auth.json"
170
+ assert runtime_home.is_dir()
171
+ assert tomllib.loads(
172
+ (project / ".codex" / "config.toml").read_text(encoding="utf-8")
173
+ )["mcp_servers"]["example"] == {"command": "example"}
174
+ auth_link = runtime_home / "auth.json"
162
175
  assert auth_link.is_symlink()
163
176
  assert auth_link.samefile(global_auth)
164
177
 
@@ -199,9 +212,10 @@ def test_codex_subcommand_warns_when_global_auth_is_missing(workspace, monkeypat
199
212
  assert captured == {
200
213
  "command": ["codex", "exec", "hello"],
201
214
  "cwd": project,
202
- "codex_home": str(project / ".codex"),
215
+ "codex_home": str(project / ".codex" / ".runtime"),
203
216
  }
204
- assert not (project / ".codex" / "auth.json").exists()
217
+ assert (project / ".codex" / ".runtime").is_dir()
218
+ assert not (project / ".codex" / ".runtime" / "auth.json").exists()
205
219
 
206
220
 
207
221
  def test_codex_subcommand_simple_mode_runs_basic_codex(workspace, monkeypatch):
@@ -282,7 +296,7 @@ enabled = ["example"]
282
296
  assert captured == {
283
297
  "command": ["codex", "exec", "hello"],
284
298
  "cwd": project,
285
- "codex_home": str(project / ".codex"),
299
+ "codex_home": str(project / ".codex" / ".runtime"),
286
300
  }
287
301
 
288
302
 
@@ -385,7 +399,7 @@ skills = ["strict-skill"]
385
399
  assert stdout.getvalue() == ""
386
400
  assert stderr.getvalue() == ""
387
401
  assert captured["cwd"] == project
388
- assert captured["codex_home"] == str(project / ".codex")
402
+ assert captured["codex_home"] == str(project / ".codex" / ".runtime")
389
403
  assert captured["codexmgr_toml"] == ""
390
404
  assert captured["config"]["skills"]["config"] == [
391
405
  {"path": str(base_skill.resolve()), "enabled": True},
@@ -442,7 +456,7 @@ def test_codex_jit_overlay_snapshots_and_restores_rules(
442
456
  assert stdout.getvalue() == ""
443
457
  assert stderr.getvalue() == ""
444
458
  assert captured["command"] == ["codex", "exec", "hello"]
445
- assert captured["codex_home"] == str(project / ".codex")
459
+ assert captured["codex_home"] == str(project / ".codex" / ".runtime")
446
460
  assert captured["rule"] == "# Components\n"
447
461
  assert not (project / ".rules").exists()
448
462
 
@@ -2,7 +2,11 @@ from pathlib import Path
2
2
 
3
3
  import pytest
4
4
 
5
- from codexmgr.core.paths import global_codex_dir, global_codexmgr_dir
5
+ from codexmgr.core.paths import (
6
+ global_codex_dir,
7
+ global_codexmgr_dir,
8
+ project_codex_runtime_dir,
9
+ )
6
10
 
7
11
 
8
12
  def test_defaults_to_home_codex_directory(monkeypatch: pytest.MonkeyPatch):
@@ -33,3 +37,10 @@ def test_codexmgr_home_uses_environment_variable(monkeypatch: pytest.MonkeyPatch
33
37
  monkeypatch.setenv("CODEXMGR_HOME", "/tmp/custom-codexmgr-home")
34
38
 
35
39
  assert global_codexmgr_dir() == Path("/tmp/custom-codexmgr-home")
40
+
41
+
42
+ def test_project_codex_runtime_directory_is_nested_below_project_config():
43
+ """Mutable Codex state has a dedicated home below the project config."""
44
+ project = Path("/tmp/example-project")
45
+
46
+ assert project_codex_runtime_dir(project) == project / ".codex" / ".runtime"
@@ -75,7 +75,8 @@ def test_managed_gitignore_ignores_unknown_runtime_state_only(workspace, run_cli
75
75
  """Git ignores future runtime names but exposes every codexmgr-owned path."""
76
76
  project, codex_home = workspace
77
77
  run_cli(["setup"], project, codex_home)
78
- runtime_file = project / ".codex" / "future-runtime-state.bin"
78
+ runtime_file = project / ".codex" / ".runtime" / "future-runtime-state.bin"
79
+ runtime_file.parent.mkdir()
79
80
  runtime_file.write_text("runtime\n", encoding="utf-8")
80
81
  agent_file = project / ".codex" / "agents" / "local.toml"
81
82
  agent_file.parent.mkdir()
@@ -91,7 +92,7 @@ def test_managed_gitignore_ignores_unknown_runtime_state_only(workspace, run_cli
91
92
  text=True,
92
93
  )
93
94
 
94
- assert _git_check_ignore(project, ".codex/future-runtime-state.bin") == 0
95
+ assert _git_check_ignore(project, ".codex/.runtime/future-runtime-state.bin") == 0
95
96
  for managed_path in [
96
97
  ".codex/.gitignore",
97
98
  ".codex/codexmgr.toml",
@@ -4,7 +4,7 @@ requires-python = ">=3.11"
4
4
 
5
5
  [[package]]
6
6
  name = "codexmgr"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  source = { editable = "." }
9
9
  dependencies = [
10
10
  { name = "textual" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes