devsync 0.6.0__tar.gz → 0.8.0__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 (93) hide show
  1. {devsync-0.6.0/devsync.egg-info → devsync-0.8.0}/PKG-INFO +14 -14
  2. {devsync-0.6.0 → devsync-0.8.0}/README.md +12 -12
  3. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/__main__.py +1 -1
  4. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/base.py +2 -2
  5. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/capability_registry.py +22 -1
  6. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/claude.py +3 -3
  7. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/claude_desktop.py +3 -3
  8. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/cline.py +3 -3
  9. devsync-0.8.0/devsync/ai_tools/codex.py +219 -0
  10. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/copilot.py +3 -3
  11. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/cursor.py +3 -3
  12. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/detector.py +12 -9
  13. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/kiro.py +3 -3
  14. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/mcp_syncer.py +9 -8
  15. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/roo.py +4 -4
  16. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/translator.py +1 -1
  17. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/winsurf.py +3 -3
  18. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/delete.py +3 -3
  19. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/download.py +6 -6
  20. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/install.py +10 -10
  21. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/install_new.py +10 -10
  22. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/list.py +8 -8
  23. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/main.py +19 -19
  24. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/mcp_configure.py +9 -8
  25. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/mcp_install.py +5 -4
  26. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/mcp_sync.py +5 -4
  27. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/package.py +10 -7
  28. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/package_create.py +4 -4
  29. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/package_install.py +13 -11
  30. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/template_backup.py +9 -8
  31. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/template_init.py +1 -1
  32. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/template_install.py +12 -10
  33. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/template_list.py +2 -2
  34. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/template_uninstall.py +3 -3
  35. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/template_update.py +8 -8
  36. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/template_validate.py +4 -4
  37. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/tools.py +1 -1
  38. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/uninstall.py +5 -5
  39. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/update.py +8 -8
  40. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/component_detector.py +7 -6
  41. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/conflict_resolution.py +5 -5
  42. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/git_operations.py +2 -2
  43. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/mcp/credentials.py +3 -3
  44. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/mcp/manager.py +5 -5
  45. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/models.py +3 -2
  46. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/package_creator.py +3 -3
  47. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/package_manifest.py +1 -1
  48. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/repository.py +1 -1
  49. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/secret_detector.py +1 -1
  50. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/template_manifest.py +1 -1
  51. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/storage/library.py +4 -4
  52. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/storage/package_tracker.py +2 -2
  53. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/storage/template_library.py +9 -7
  54. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/storage/template_tracker.py +5 -4
  55. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/storage/tracker.py +5 -5
  56. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/tui/__init__.py +1 -1
  57. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/tui/installer.py +4 -4
  58. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/utils/backup.py +14 -14
  59. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/utils/dotenv.py +2 -2
  60. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/utils/git_helpers.py +2 -2
  61. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/utils/paths.py +22 -1
  62. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/utils/project.py +4 -2
  63. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/utils/streaming.py +1 -1
  64. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/utils/ui.py +1 -1
  65. {devsync-0.6.0 → devsync-0.8.0/devsync.egg-info}/PKG-INFO +14 -14
  66. devsync-0.8.0/devsync.egg-info/SOURCES.txt +88 -0
  67. devsync-0.8.0/devsync.egg-info/entry_points.txt +2 -0
  68. devsync-0.8.0/devsync.egg-info/top_level.txt +1 -0
  69. {devsync-0.6.0 → devsync-0.8.0}/pyproject.toml +6 -6
  70. devsync-0.6.0/devsync.egg-info/SOURCES.txt +0 -87
  71. devsync-0.6.0/devsync.egg-info/entry_points.txt +0 -2
  72. devsync-0.6.0/devsync.egg-info/top_level.txt +0 -1
  73. {devsync-0.6.0 → devsync-0.8.0}/LICENSE +0 -0
  74. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/__init__.py +0 -0
  75. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/ai_tools/__init__.py +0 -0
  76. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/__init__.py +0 -0
  77. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/cli/template.py +0 -0
  78. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/__init__.py +0 -0
  79. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/checksum.py +0 -0
  80. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/mcp/__init__.py +0 -0
  81. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/mcp/set_manager.py +0 -0
  82. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/mcp/validator.py +0 -0
  83. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/core/version.py +0 -0
  84. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/storage/__init__.py +0 -0
  85. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/storage/mcp_tracker.py +0 -0
  86. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/utils/__init__.py +0 -0
  87. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/utils/atomic_write.py +0 -0
  88. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/utils/logging.py +0 -0
  89. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/utils/namespace.py +0 -0
  90. {devsync-0.6.0/aiconfigkit → devsync-0.8.0/devsync}/utils/validation.py +0 -0
  91. {devsync-0.6.0 → devsync-0.8.0}/devsync.egg-info/dependency_links.txt +0 -0
  92. {devsync-0.6.0 → devsync-0.8.0}/devsync.egg-info/requires.txt +0 -0
  93. {devsync-0.6.0 → devsync-0.8.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devsync
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: Distribute and sync dev tool configurations across teams
5
5
  Author-email: Troy Larson <troy@calvinware.com>
6
6
  License: MIT License
@@ -8,7 +8,7 @@ Project-URL: Homepage, https://github.com/troylar/devsync
8
8
  Project-URL: Documentation, https://github.com/troylar/devsync#readme
9
9
  Project-URL: Repository, https://github.com/troylar/devsync
10
10
  Project-URL: Issues, https://github.com/troylar/devsync/issues
11
- Keywords: cli,ai,config,mcp,cursor,copilot,claude,cline,kiro,roo,windsurf
11
+ Keywords: cli,ai,config,mcp,cursor,copilot,claude,cline,codex,kiro,roo,windsurf
12
12
  Classifier: Development Status :: 3 - Alpha
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Programming Language :: Python :: 3
@@ -48,7 +48,7 @@ Requires-Dist: types-PyYAML>=6.0.12.20240808; extra == "dev"
48
48
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
49
49
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
50
50
 
51
- **Works with:** Claude Code • Claude Desktop • Cline • Cursor • GitHub Copilot • Kiro • Roo Code • Windsurf
51
+ **Works with:** Claude Code • Claude Desktop • Cline • Codex CLI • Cursor • GitHub Copilot • Kiro • Roo Code • Windsurf
52
52
 
53
53
  </div>
54
54
 
@@ -298,16 +298,16 @@ Any IDE-specific content from Git repositories:
298
298
 
299
299
  Complete configuration bundles with multiple component types:
300
300
 
301
- | Component | Claude | Cline | Cursor | Kiro | Roo Code | Windsurf | Copilot |
302
- |-----------|--------|-------|--------|------|----------|----------|---------|
303
- | Instructions | `.claude/rules/` | `.clinerules/` | `.cursor/rules/` | `.kiro/steering/` | `.roo/rules/` | `.windsurf/rules/` | `.github/instructions/` |
304
- | MCP Servers | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
305
- | Hooks | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
306
- | Commands | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
307
- | Skills | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
308
- | Workflows | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
309
- | Memory Files | ✅ (CLAUDE.md) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
310
- | Resources | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
301
+ | Component | Claude | Cline | Codex CLI | Cursor | Kiro | Roo Code | Windsurf | Copilot |
302
+ |-----------|--------|-------|----------|--------|------|----------|----------|---------|
303
+ | Instructions | `.claude/rules/` | `.clinerules/` | `AGENTS.md` | `.cursor/rules/` | `.kiro/steering/` | `.roo/rules/` | `.windsurf/rules/` | `.github/instructions/` |
304
+ | MCP Servers | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
305
+ | Hooks | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
306
+ | Commands | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
307
+ | Skills | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
308
+ | Workflows | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
309
+ | Memory Files | ✅ (CLAUDE.md) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
310
+ | Resources | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
311
311
 
312
312
  ### MCP Server Configurations
313
313
 
@@ -327,7 +327,7 @@ Model Context Protocol server setups for enhanced AI capabilities:
327
327
 
328
328
  - Python 3.10 or higher
329
329
  - Git (for cloning template repositories)
330
- - One of: Claude Code, Claude Desktop, Cline, Cursor, GitHub Copilot, Kiro, Roo Code, or Windsurf
330
+ - One of: Claude Code, Claude Desktop, Cline, Codex CLI, Cursor, GitHub Copilot, Kiro, Roo Code, or Windsurf
331
331
 
332
332
  ### Install DevSync
333
333
 
@@ -10,7 +10,7 @@
10
10
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
11
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
12
12
 
13
- **Works with:** Claude Code • Claude Desktop • Cline • Cursor • GitHub Copilot • Kiro • Roo Code • Windsurf
13
+ **Works with:** Claude Code • Claude Desktop • Cline • Codex CLI • Cursor • GitHub Copilot • Kiro • Roo Code • Windsurf
14
14
 
15
15
  </div>
16
16
 
@@ -260,16 +260,16 @@ Any IDE-specific content from Git repositories:
260
260
 
261
261
  Complete configuration bundles with multiple component types:
262
262
 
263
- | Component | Claude | Cline | Cursor | Kiro | Roo Code | Windsurf | Copilot |
264
- |-----------|--------|-------|--------|------|----------|----------|---------|
265
- | Instructions | `.claude/rules/` | `.clinerules/` | `.cursor/rules/` | `.kiro/steering/` | `.roo/rules/` | `.windsurf/rules/` | `.github/instructions/` |
266
- | MCP Servers | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
267
- | Hooks | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
268
- | Commands | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
269
- | Skills | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
270
- | Workflows | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
271
- | Memory Files | ✅ (CLAUDE.md) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
272
- | Resources | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
263
+ | Component | Claude | Cline | Codex CLI | Cursor | Kiro | Roo Code | Windsurf | Copilot |
264
+ |-----------|--------|-------|----------|--------|------|----------|----------|---------|
265
+ | Instructions | `.claude/rules/` | `.clinerules/` | `AGENTS.md` | `.cursor/rules/` | `.kiro/steering/` | `.roo/rules/` | `.windsurf/rules/` | `.github/instructions/` |
266
+ | MCP Servers | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
267
+ | Hooks | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
268
+ | Commands | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
269
+ | Skills | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
270
+ | Workflows | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
271
+ | Memory Files | ✅ (CLAUDE.md) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
272
+ | Resources | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
273
273
 
274
274
  ### MCP Server Configurations
275
275
 
@@ -289,7 +289,7 @@ Model Context Protocol server setups for enhanced AI capabilities:
289
289
 
290
290
  - Python 3.10 or higher
291
291
  - Git (for cloning template repositories)
292
- - One of: Claude Code, Claude Desktop, Cline, Cursor, GitHub Copilot, Kiro, Roo Code, or Windsurf
292
+ - One of: Claude Code, Claude Desktop, Cline, Codex CLI, Cursor, GitHub Copilot, Kiro, Roo Code, or Windsurf
293
293
 
294
294
  ### Install DevSync
295
295
 
@@ -1,6 +1,6 @@
1
1
  """Entry point for running devsync as a module."""
2
2
 
3
- from aiconfigkit.cli.main import app
3
+ from devsync.cli.main import app
4
4
 
5
5
  if __name__ == "__main__":
6
6
  app()
@@ -5,14 +5,14 @@ from abc import ABC, abstractmethod
5
5
  from pathlib import Path
6
6
  from typing import Optional
7
7
 
8
- from aiconfigkit.core.models import AIToolType, InstallationScope, Instruction
8
+ from devsync.core.models import AIToolType, InstallationScope, Instruction
9
9
 
10
10
 
11
11
  class AITool(ABC):
12
12
  """
13
13
  Abstract base class for AI coding tool integrations.
14
14
 
15
- Each AI tool (Cline, Cursor, Copilot, Kiro, Roo Code, Winsurf, Claude) implements this interface
15
+ Each AI tool (Cline, Codex, Cursor, Copilot, Kiro, Roo Code, Winsurf, Claude) implements this interface
16
16
  to provide tool-specific installation logic.
17
17
  """
18
18
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  from dataclasses import dataclass
4
4
 
5
- from aiconfigkit.core.models import AIToolType, ComponentType
5
+ from devsync.core.models import AIToolType, ComponentType
6
6
 
7
7
 
8
8
  @dataclass
@@ -187,6 +187,27 @@ CAPABILITY_REGISTRY: dict[AIToolType, IDECapability] = {
187
187
  "Slash commands in .roo/commands/. Global rules at ~/.roo/rules/."
188
188
  ),
189
189
  ),
190
+ AIToolType.CODEX: IDECapability(
191
+ tool_type=AIToolType.CODEX,
192
+ tool_name="OpenAI Codex CLI",
193
+ supported_components={
194
+ ComponentType.INSTRUCTION,
195
+ ComponentType.RESOURCE,
196
+ },
197
+ instructions_directory="", # AGENTS.md at project root
198
+ instruction_file_extension=".md",
199
+ supports_project_scope=True,
200
+ supports_global_scope=False,
201
+ mcp_config_path=None,
202
+ mcp_project_config_path=None,
203
+ hooks_directory=None,
204
+ commands_directory=None,
205
+ notes=(
206
+ "OpenAI Codex CLI uses a single AGENTS.md file at the project root. "
207
+ "DevSync manages sections within this file using HTML comment markers. "
208
+ "No MCP, hooks, or commands support."
209
+ ),
210
+ ),
190
211
  AIToolType.COPILOT: IDECapability(
191
212
  tool_type=AIToolType.COPILOT,
192
213
  tool_name="GitHub Copilot",
@@ -2,9 +2,9 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from aiconfigkit.ai_tools.base import AITool
6
- from aiconfigkit.core.models import AIToolType
7
- from aiconfigkit.utils.paths import get_claude_config_dir
5
+ from devsync.ai_tools.base import AITool
6
+ from devsync.core.models import AIToolType
7
+ from devsync.utils.paths import get_claude_config_dir
8
8
 
9
9
 
10
10
  class ClaudeTool(AITool):
@@ -2,9 +2,9 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from aiconfigkit.ai_tools.base import AITool
6
- from aiconfigkit.core.models import AIToolType
7
- from aiconfigkit.utils.paths import get_claude_desktop_config_path
5
+ from devsync.ai_tools.base import AITool
6
+ from devsync.core.models import AIToolType
7
+ from devsync.utils.paths import get_claude_desktop_config_path
8
8
 
9
9
 
10
10
  class ClaudeDesktopTool(AITool):
@@ -2,9 +2,9 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from aiconfigkit.ai_tools.base import AITool
6
- from aiconfigkit.core.models import AIToolType
7
- from aiconfigkit.utils.paths import get_cline_config_dir
5
+ from devsync.ai_tools.base import AITool
6
+ from devsync.core.models import AIToolType
7
+ from devsync.utils.paths import get_cline_config_dir
8
8
 
9
9
 
10
10
  class ClineTool(AITool):
@@ -0,0 +1,219 @@
1
+ """OpenAI Codex CLI AI tool integration."""
2
+
3
+ import re
4
+ import shutil
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ from devsync.ai_tools.base import AITool
9
+ from devsync.core.models import AIToolType, InstallationScope, Instruction
10
+
11
+ START_MARKER = "<!-- devsync:start:{name} -->"
12
+ END_MARKER = "<!-- devsync:end:{name} -->"
13
+ SECTION_PATTERN = r"<!-- devsync:start:{name} -->\n.*?\n<!-- devsync:end:{name} -->"
14
+
15
+
16
+ class CodexTool(AITool):
17
+ """Integration for OpenAI Codex CLI.
18
+
19
+ Codex CLI reads a single AGENTS.md file at the project root.
20
+ DevSync manages individual instruction sections using HTML comment markers:
21
+
22
+ <!-- devsync:start:instruction-name -->
23
+ ... instruction content ...
24
+ <!-- devsync:end:instruction-name -->
25
+ """
26
+
27
+ @property
28
+ def tool_type(self) -> AIToolType:
29
+ """Return the AI tool type identifier."""
30
+ return AIToolType.CODEX
31
+
32
+ @property
33
+ def tool_name(self) -> str:
34
+ """Return human-readable tool name."""
35
+ return "OpenAI Codex CLI"
36
+
37
+ def is_installed(self) -> bool:
38
+ """Check if OpenAI Codex CLI is installed on the system.
39
+
40
+ Returns:
41
+ True if codex binary is found on PATH
42
+ """
43
+ return shutil.which("codex") is not None
44
+
45
+ def get_instructions_directory(self) -> Path:
46
+ """Get the directory where instructions should be installed.
47
+
48
+ Raises:
49
+ NotImplementedError: Codex CLI only supports project-level installation
50
+ """
51
+ raise NotImplementedError(
52
+ f"{self.tool_name} global installation is not supported. "
53
+ "OpenAI Codex CLI uses project-level AGENTS.md only. "
54
+ "Please use project-level installation instead (--scope project)."
55
+ )
56
+
57
+ def get_instruction_file_extension(self) -> str:
58
+ """Get the file extension for Codex instructions.
59
+
60
+ Returns:
61
+ File extension including the dot
62
+ """
63
+ return ".md"
64
+
65
+ def get_project_instructions_directory(self, project_root: Path) -> Path:
66
+ """Get the directory for project-specific Codex instructions.
67
+
68
+ AGENTS.md lives at the project root, so the directory is the root itself.
69
+
70
+ Args:
71
+ project_root: Path to the project root directory
72
+
73
+ Returns:
74
+ Path to project root (AGENTS.md lives at root)
75
+ """
76
+ return project_root
77
+
78
+ def get_instruction_path(
79
+ self,
80
+ instruction_name: str,
81
+ scope: InstallationScope = InstallationScope.GLOBAL,
82
+ project_root: Optional[Path] = None,
83
+ ) -> Path:
84
+ """Get the path to AGENTS.md.
85
+
86
+ Always returns project_root / AGENTS.md regardless of instruction name.
87
+
88
+ Args:
89
+ instruction_name: Name of the instruction (unused for path)
90
+ scope: Installation scope (must be PROJECT)
91
+ project_root: Project root path
92
+
93
+ Returns:
94
+ Path to AGENTS.md
95
+
96
+ Raises:
97
+ ValueError: If scope is PROJECT but project_root is None
98
+ NotImplementedError: If scope is GLOBAL
99
+ """
100
+ if scope == InstallationScope.GLOBAL:
101
+ raise NotImplementedError(
102
+ f"{self.tool_name} global installation is not supported. "
103
+ "Please use project-level installation instead (--scope project)."
104
+ )
105
+ if project_root is None:
106
+ raise ValueError("project_root is required for PROJECT scope")
107
+ return project_root / "AGENTS.md"
108
+
109
+ def instruction_exists(
110
+ self,
111
+ instruction_name: str,
112
+ scope: InstallationScope = InstallationScope.GLOBAL,
113
+ project_root: Optional[Path] = None,
114
+ ) -> bool:
115
+ """Check if an instruction section exists in AGENTS.md.
116
+
117
+ Args:
118
+ instruction_name: Name of the instruction
119
+ scope: Installation scope
120
+ project_root: Project root path
121
+
122
+ Returns:
123
+ True if the instruction's section markers exist in AGENTS.md
124
+ """
125
+ try:
126
+ path = self.get_instruction_path(instruction_name, scope, project_root)
127
+ if not path.exists():
128
+ return False
129
+ content = path.read_text(encoding="utf-8")
130
+ start = START_MARKER.format(name=instruction_name)
131
+ return start in content
132
+ except (FileNotFoundError, ValueError, NotImplementedError):
133
+ return False
134
+
135
+ def install_instruction(
136
+ self,
137
+ instruction: Instruction,
138
+ overwrite: bool = False,
139
+ scope: InstallationScope = InstallationScope.GLOBAL,
140
+ project_root: Optional[Path] = None,
141
+ ) -> Path:
142
+ """Install an instruction as a section in AGENTS.md.
143
+
144
+ Appends a new section with markers, or replaces an existing section
145
+ if overwrite is True.
146
+
147
+ Args:
148
+ instruction: Instruction to install
149
+ overwrite: Whether to overwrite existing section
150
+ scope: Installation scope
151
+ project_root: Project root path
152
+
153
+ Returns:
154
+ Path to AGENTS.md
155
+
156
+ Raises:
157
+ FileExistsError: If instruction section exists and overwrite=False
158
+ """
159
+ path = self.get_instruction_path(instruction.name, scope, project_root)
160
+
161
+ start = START_MARKER.format(name=instruction.name)
162
+ end = END_MARKER.format(name=instruction.name)
163
+ section = f"{start}\n{instruction.content}\n{end}"
164
+
165
+ if path.exists():
166
+ content = path.read_text(encoding="utf-8")
167
+ if start in content:
168
+ if not overwrite:
169
+ raise FileExistsError(f"Instruction already exists in AGENTS.md: {instruction.name}")
170
+ pattern = SECTION_PATTERN.format(name=re.escape(instruction.name))
171
+ content = re.sub(pattern, section, content, flags=re.DOTALL)
172
+ path.write_text(content, encoding="utf-8")
173
+ return path
174
+ if content and not content.endswith("\n"):
175
+ content += "\n"
176
+ content += "\n" + section + "\n"
177
+ path.write_text(content, encoding="utf-8")
178
+ else:
179
+ path.parent.mkdir(parents=True, exist_ok=True)
180
+ path.write_text(section + "\n", encoding="utf-8")
181
+
182
+ return path
183
+
184
+ def uninstall_instruction(
185
+ self,
186
+ instruction_name: str,
187
+ scope: InstallationScope = InstallationScope.GLOBAL,
188
+ project_root: Optional[Path] = None,
189
+ ) -> bool:
190
+ """Remove an instruction section from AGENTS.md.
191
+
192
+ Args:
193
+ instruction_name: Name of instruction to remove
194
+ scope: Installation scope
195
+ project_root: Project root path
196
+
197
+ Returns:
198
+ True if section was removed, False if it didn't exist
199
+ """
200
+ try:
201
+ path = self.get_instruction_path(instruction_name, scope, project_root)
202
+ if not path.exists():
203
+ return False
204
+
205
+ content = path.read_text(encoding="utf-8")
206
+ start = START_MARKER.format(name=instruction_name)
207
+ if start not in content:
208
+ return False
209
+
210
+ pattern = SECTION_PATTERN.format(name=re.escape(instruction_name))
211
+ new_content = re.sub(pattern, "", content, flags=re.DOTALL)
212
+ # Clean up extra blank lines
213
+ new_content = re.sub(r"\n{3,}", "\n\n", new_content).strip()
214
+ if new_content:
215
+ new_content += "\n"
216
+ path.write_text(new_content, encoding="utf-8")
217
+ return True
218
+ except (FileNotFoundError, ValueError, NotImplementedError):
219
+ return False
@@ -2,9 +2,9 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from aiconfigkit.ai_tools.base import AITool
6
- from aiconfigkit.core.models import AIToolType
7
- from aiconfigkit.utils.paths import get_copilot_config_dir
5
+ from devsync.ai_tools.base import AITool
6
+ from devsync.core.models import AIToolType
7
+ from devsync.utils.paths import get_copilot_config_dir
8
8
 
9
9
 
10
10
  class CopilotTool(AITool):
@@ -2,9 +2,9 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from aiconfigkit.ai_tools.base import AITool
6
- from aiconfigkit.core.models import AIToolType
7
- from aiconfigkit.utils.paths import get_cursor_config_dir, get_cursor_mcp_config_path
5
+ from devsync.ai_tools.base import AITool
6
+ from devsync.core.models import AIToolType
7
+ from devsync.utils.paths import get_cursor_config_dir, get_cursor_mcp_config_path
8
8
 
9
9
 
10
10
  class CursorTool(AITool):
@@ -2,15 +2,16 @@
2
2
 
3
3
  from typing import Optional
4
4
 
5
- from aiconfigkit.ai_tools.base import AITool
6
- from aiconfigkit.ai_tools.claude import ClaudeTool
7
- from aiconfigkit.ai_tools.cline import ClineTool
8
- from aiconfigkit.ai_tools.copilot import CopilotTool
9
- from aiconfigkit.ai_tools.cursor import CursorTool
10
- from aiconfigkit.ai_tools.kiro import KiroTool
11
- from aiconfigkit.ai_tools.roo import RooTool
12
- from aiconfigkit.ai_tools.winsurf import WinsurfTool
13
- from aiconfigkit.core.models import AIToolType
5
+ from devsync.ai_tools.base import AITool
6
+ from devsync.ai_tools.claude import ClaudeTool
7
+ from devsync.ai_tools.cline import ClineTool
8
+ from devsync.ai_tools.codex import CodexTool
9
+ from devsync.ai_tools.copilot import CopilotTool
10
+ from devsync.ai_tools.cursor import CursorTool
11
+ from devsync.ai_tools.kiro import KiroTool
12
+ from devsync.ai_tools.roo import RooTool
13
+ from devsync.ai_tools.winsurf import WinsurfTool
14
+ from devsync.core.models import AIToolType
14
15
 
15
16
 
16
17
  class AIToolDetector:
@@ -26,6 +27,7 @@ class AIToolDetector:
26
27
  AIToolType.KIRO: KiroTool(),
27
28
  AIToolType.CLINE: ClineTool(),
28
29
  AIToolType.ROO: RooTool(),
30
+ AIToolType.CODEX: CodexTool(),
29
31
  }
30
32
 
31
33
  def detect_installed_tools(self) -> list[AITool]:
@@ -87,6 +89,7 @@ class AIToolDetector:
87
89
  AIToolType.KIRO,
88
90
  AIToolType.CLINE,
89
91
  AIToolType.ROO,
92
+ AIToolType.CODEX,
90
93
  ]
91
94
 
92
95
  for tool_type in priority:
@@ -2,9 +2,9 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from aiconfigkit.ai_tools.base import AITool
6
- from aiconfigkit.core.models import AIToolType
7
- from aiconfigkit.utils.paths import get_kiro_config_dir
5
+ from devsync.ai_tools.base import AITool
6
+ from devsync.core.models import AIToolType
7
+ from devsync.utils.paths import get_kiro_config_dir
8
8
 
9
9
 
10
10
  class KiroTool(AITool):
@@ -5,12 +5,13 @@ import logging
5
5
  from pathlib import Path
6
6
  from typing import Any, Optional
7
7
 
8
- from aiconfigkit.ai_tools.base import AITool
9
- from aiconfigkit.ai_tools.detector import AIToolDetector
10
- from aiconfigkit.core.mcp.credentials import CredentialManager
11
- from aiconfigkit.core.mcp.manager import MCPManager
12
- from aiconfigkit.core.models import EnvironmentConfig, InstallationScope, MCPServer, MCPTemplate
13
- from aiconfigkit.utils.atomic_write import atomic_write
8
+ from devsync.ai_tools.base import AITool
9
+ from devsync.ai_tools.detector import AIToolDetector
10
+ from devsync.core.mcp.credentials import CredentialManager
11
+ from devsync.core.mcp.manager import MCPManager
12
+ from devsync.core.models import EnvironmentConfig, InstallationScope, MCPServer, MCPTemplate
13
+ from devsync.utils.atomic_write import atomic_write
14
+ from devsync.utils.paths import _resolve_data_dir
14
15
 
15
16
  logger = logging.getLogger(__name__)
16
17
 
@@ -61,10 +62,10 @@ class MCPSyncer:
61
62
  Initialize MCP syncer.
62
63
 
63
64
  Args:
64
- library_root: Root directory for MCP library (defaults to ~/.instructionkit/library/)
65
+ library_root: Root directory for MCP library (defaults to ~/.devsync/library/)
65
66
  project_root: Project root directory (defaults to current directory)
66
67
  """
67
- self.library_root = library_root or Path.home() / ".instructionkit" / "library"
68
+ self.library_root = library_root or _resolve_data_dir(Path.home(), ".devsync", [".instructionkit"]) / "library"
68
69
  self.project_root = project_root or Path.cwd()
69
70
 
70
71
  self.mcp_manager = MCPManager(self.library_root)
@@ -2,9 +2,9 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from aiconfigkit.ai_tools.base import AITool
6
- from aiconfigkit.core.models import AIToolType
7
- from aiconfigkit.utils.paths import get_roo_config_dir
5
+ from devsync.ai_tools.base import AITool
6
+ from devsync.core.models import AIToolType
7
+ from devsync.utils.paths import get_roo_config_dir
8
8
 
9
9
 
10
10
  class RooTool(AITool):
@@ -61,7 +61,7 @@ class RooTool(AITool):
61
61
  """
62
62
  if not self.is_installed():
63
63
  raise FileNotFoundError(f"{self.tool_name} is not installed")
64
- from aiconfigkit.utils.paths import get_home_directory
64
+ from devsync.utils.paths import get_home_directory
65
65
 
66
66
  global_dir = get_home_directory() / ".roo" / "rules"
67
67
  global_dir.mkdir(parents=True, exist_ok=True)
@@ -5,7 +5,7 @@ from dataclasses import dataclass
5
5
  from pathlib import Path
6
6
  from typing import Any
7
7
 
8
- from aiconfigkit.core.models import (
8
+ from devsync.core.models import (
9
9
  AIToolType,
10
10
  CommandComponent,
11
11
  ComponentType,
@@ -2,9 +2,9 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from aiconfigkit.ai_tools.base import AITool
6
- from aiconfigkit.core.models import AIToolType
7
- from aiconfigkit.utils.paths import get_windsurf_mcp_config_path, get_winsurf_config_dir
5
+ from devsync.ai_tools.base import AITool
6
+ from devsync.core.models import AIToolType
7
+ from devsync.utils.paths import get_windsurf_mcp_config_path, get_winsurf_config_dir
8
8
 
9
9
 
10
10
  class WinsurfTool(AITool):
@@ -4,9 +4,9 @@ import typer
4
4
  from rich.console import Console
5
5
  from rich.prompt import Confirm
6
6
 
7
- from aiconfigkit.storage.library import LibraryManager
8
- from aiconfigkit.storage.tracker import InstallationTracker
9
- from aiconfigkit.utils.ui import print_error, print_success, print_warning
7
+ from devsync.storage.library import LibraryManager
8
+ from devsync.storage.tracker import InstallationTracker
9
+ from devsync.utils.ui import print_error, print_success, print_warning
10
10
 
11
11
  console = Console()
12
12
 
@@ -9,12 +9,12 @@ import typer
9
9
  from rich.console import Console
10
10
  from rich.progress import Progress, SpinnerColumn, TextColumn
11
11
 
12
- from aiconfigkit.core.checksum import calculate_file_checksum
13
- from aiconfigkit.core.git_operations import GitOperations, RepositoryOperationError
14
- from aiconfigkit.core.models import LibraryInstruction
15
- from aiconfigkit.core.repository import RepositoryParser
16
- from aiconfigkit.storage.library import LibraryManager
17
- from aiconfigkit.utils.ui import print_error, print_success
12
+ from devsync.core.checksum import calculate_file_checksum
13
+ from devsync.core.git_operations import GitOperations, RepositoryOperationError
14
+ from devsync.core.models import LibraryInstruction
15
+ from devsync.core.repository import RepositoryParser
16
+ from devsync.storage.library import LibraryManager
17
+ from devsync.utils.ui import print_error, print_success
18
18
 
19
19
  console = Console()
20
20
 
@@ -7,22 +7,22 @@ from typing import Optional
7
7
  from rich.console import Console
8
8
  from rich.progress import Progress, SpinnerColumn, TextColumn
9
9
 
10
- from aiconfigkit.ai_tools.base import AITool
11
- from aiconfigkit.ai_tools.detector import get_detector
12
- from aiconfigkit.core.checksum import ChecksumValidator
13
- from aiconfigkit.core.conflict_resolution import (
10
+ from devsync.ai_tools.base import AITool
11
+ from devsync.ai_tools.detector import get_detector
12
+ from devsync.core.checksum import ChecksumValidator
13
+ from devsync.core.conflict_resolution import (
14
14
  ConflictResolver,
15
15
  )
16
- from aiconfigkit.core.git_operations import GitOperations
17
- from aiconfigkit.core.models import (
16
+ from devsync.core.git_operations import GitOperations
17
+ from devsync.core.models import (
18
18
  ConflictResolution,
19
19
  InstallationRecord,
20
20
  InstallationScope,
21
21
  )
22
- from aiconfigkit.core.repository import RepositoryParser
23
- from aiconfigkit.storage.tracker import InstallationTracker
24
- from aiconfigkit.utils.project import find_project_root
25
- from aiconfigkit.utils.validation import is_valid_git_url, normalize_repo_url
22
+ from devsync.core.repository import RepositoryParser
23
+ from devsync.storage.tracker import InstallationTracker
24
+ from devsync.utils.project import find_project_root
25
+ from devsync.utils.validation import is_valid_git_url, normalize_repo_url
26
26
 
27
27
  console = Console()
28
28