code-agnostic 0.3.5__tar.gz → 0.3.8__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.
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/PKG-INFO +58 -23
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/README.md +55 -22
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/__init__.py +1 -1
- code_agnostic-0.3.8/code_agnostic/agents/claude.py +67 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/agents/codex.py +6 -3
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/agents/compilers.py +8 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/agents/parser.py +1 -1
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/app_id.py +15 -1
- code_agnostic-0.3.8/code_agnostic/apps/claude/__init__.py +1 -0
- code_agnostic-0.3.8/code_agnostic/apps/claude/config_repository.py +59 -0
- code_agnostic-0.3.8/code_agnostic/apps/claude/mapper.py +86 -0
- code_agnostic-0.3.8/code_agnostic/apps/claude/service.py +184 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/codex/schema.json +12 -0
- code_agnostic-0.3.8/code_agnostic/apps/common/compiled_planning.py +115 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/common/interfaces/repositories.py +10 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/common/interfaces/service.py +30 -14
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/common/loader.py +1 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/opencode/service.py +2 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/workspaces.py +6 -1
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/constants.py +3 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/executor.py +46 -2
- code_agnostic-0.3.8/code_agnostic/generated_artifacts.py +134 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/git_exclude_service.py +27 -19
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/imports/adapters.py +27 -10
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/imports/service.py +10 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/lossiness.py +65 -7
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/models.py +11 -2
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/planner.py +131 -13
- code_agnostic-0.3.8/code_agnostic/skills/compilers.py +107 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/skills/parser.py +1 -1
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/spec/loaders.py +2 -2
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/spec/schemas/agent.v1.schema.json +2 -1
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/spec/schemas/rule.v1.schema.json +2 -1
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/spec/schemas/skill.v1.schema.json +2 -1
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/status.py +28 -49
- code_agnostic-0.3.8/code_agnostic/workspace_artifacts.py +238 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic.egg-info/PKG-INFO +58 -23
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic.egg-info/SOURCES.txt +7 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic.egg-info/requires.txt +2 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/pyproject.toml +4 -2
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_apply_apps.py +38 -12
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_explain_lossiness.py +37 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_plan.py +14 -4
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_workspaces.py +58 -19
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_common_mcp_to_dto.py +16 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_git_exclude_service.py +56 -11
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_sync_plan_model.py +23 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_transactional_executor.py +102 -3
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_version.py +5 -1
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_workspace_config_sync.py +396 -17
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_workspace_repo_status.py +57 -11
- code_agnostic-0.3.5/code_agnostic/apps/common/compiled_planning.py +0 -140
- code_agnostic-0.3.5/code_agnostic/skills/compilers.py +0 -75
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/LICENSE +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/__main__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/agents/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/agents/models.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/agents/opencode.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/apps_service.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/codex/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/codex/config_repository.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/codex/mapper.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/codex/schema_repository.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/codex/service.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/common/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/common/framework.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/common/models.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/common/schema.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/common/symlink_planning.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/common/utils.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/cursor/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/cursor/config_repository.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/cursor/mapper.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/cursor/schema.json +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/cursor/schema_repository.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/cursor/service.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/opencode/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/opencode/config_repository.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/opencode/mapper.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/opencode/schema.json +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/apps/opencode/schema_repository.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/aliases.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/agents.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/apply.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/apps.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/import_.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/mcp.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/plan.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/restore.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/rules.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/skills.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/status.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/commands/validate.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/helpers.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/cli/options.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/core/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/core/repository.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/core/workspace_repository.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/errors.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/imports/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/imports/filesystem.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/imports/models.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/mcp_service.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/rules/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/rules/compilers.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/rules/models.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/rules/parser.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/rules/repository.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/skills/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/skills/models.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/spec/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/tui/__init__.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/tui/enums.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/tui/import_selector.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/tui/renderers.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/tui/sections.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/tui/tables.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/utils.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/validation.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic/workspaces.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic.egg-info/dependency_links.txt +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic.egg-info/entry_points.txt +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/code_agnostic.egg-info/top_level.txt +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/setup.cfg +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_agents.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_aliases.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_apply_codex.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_apply_cursor.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_apply_target.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_apps.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_flags.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_git_exclude.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_import.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_import_interactive.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_mcp.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_module_organization.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_restore.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_rules.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_skills.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_status.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_validate.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_cli_workspace_resolution.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_common_repository.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_compiled_planning.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_dto_to_common_mcp.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_mcp_service.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_planner_executor.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_planner_rules.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_symlink_planning.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_utils.py +0 -0
- {code_agnostic-0.3.5 → code_agnostic-0.3.8}/tests/test_workspaces.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-agnostic
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
4
4
|
Summary: Centralized hub for LLM coding config: MCP, skills, rules, and agents.
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -19,6 +19,8 @@ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
|
19
19
|
Requires-Dist: jsonschema>=4.0; extra == "dev"
|
|
20
20
|
Requires-Dist: ruff>=0.8.0; extra == "dev"
|
|
21
21
|
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
22
|
+
Requires-Dist: types-jsonschema>=4.0; extra == "dev"
|
|
23
|
+
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
|
|
22
24
|
Dynamic: license-file
|
|
23
25
|
|
|
24
26
|
# code-agnostic
|
|
@@ -53,9 +55,10 @@ AI coding tools each want config in a different place and format. When you use m
|
|
|
53
55
|
~/.config/opencode/ Compiled & synced for OpenCode
|
|
54
56
|
~/.cursor/ Compiled & synced for Cursor
|
|
55
57
|
~/.codex/ Compiled & synced for Codex
|
|
58
|
+
~/.claude.json and ~/.claude/ Compiled & synced for Claude Code
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
Each resource is cross-compiled to the target editor's native format. Rules become `.mdc` files for Cursor, `AGENTS.md` sections for OpenCode/Codex,
|
|
61
|
+
Each resource is cross-compiled to the target editor's native format. Rules become `.mdc` files for Cursor, `AGENTS.md` sections for OpenCode/Codex, and `CLAUDE.local.md` memory for Claude Code.
|
|
59
62
|
|
|
60
63
|
Legacy single-file rules, `skills/<name>/SKILL.md`, and markdown agents are still supported for migration, but bundle directories are the preferred source format for new config.
|
|
61
64
|
|
|
@@ -94,6 +97,7 @@ code-agnostic import apply -a codex
|
|
|
94
97
|
# Enable target editors
|
|
95
98
|
code-agnostic apps enable -a cursor
|
|
96
99
|
code-agnostic apps enable -a opencode
|
|
100
|
+
code-agnostic apps enable -a claude
|
|
97
101
|
|
|
98
102
|
# Preview and apply
|
|
99
103
|
code-agnostic validate
|
|
@@ -103,25 +107,25 @@ code-agnostic apply
|
|
|
103
107
|
|
|
104
108
|
## Editor compatibility
|
|
105
109
|
|
|
106
|
-
| Feature | OpenCode | Cursor | Codex |
|
|
107
|
-
|
|
108
|
-
| MCP sync | yes | yes | yes |
|
|
109
|
-
| Rules sync (cross-compiled) | yes | yes | yes |
|
|
110
|
-
| Skills sync | yes | yes | yes |
|
|
111
|
-
| Agents sync | yes | yes | yes |
|
|
112
|
-
| Workspace root `AGENTS.md` link | yes | yes | yes |
|
|
113
|
-
| Native repo config include for workspace `AGENTS.md` | yes | -- | -- |
|
|
114
|
-
| Repo/subdir gets shared workspace
|
|
115
|
-
| Nested `AGENTS.md` discovery | -- | yes | yes |
|
|
116
|
-
| Workspace propagation | yes |
|
|
117
|
-
| Import from | yes | yes | yes |
|
|
118
|
-
| Interactive import (TUI) | yes | yes | yes |
|
|
110
|
+
| Feature | OpenCode | Cursor | Codex | Claude Code |
|
|
111
|
+
|---------|:--------:|:------:|:-----:|:-----------:|
|
|
112
|
+
| MCP sync | yes | yes | yes | yes |
|
|
113
|
+
| Rules sync (cross-compiled) | yes | yes | yes | yes |
|
|
114
|
+
| Skills sync | yes | yes | yes | yes |
|
|
115
|
+
| Agents sync | yes | yes | yes | yes |
|
|
116
|
+
| Workspace root `AGENTS.md` link | yes | yes | yes | yes |
|
|
117
|
+
| Native repo config include for workspace `AGENTS.md` | yes | -- | -- | -- |
|
|
118
|
+
| Repo/subdir gets shared workspace instructions today | yes | -- | yes | yes |
|
|
119
|
+
| Nested `AGENTS.md` discovery | -- | yes | yes | -- |
|
|
120
|
+
| Workspace propagation | yes | yes | yes | yes |
|
|
121
|
+
| Import from | yes | yes | yes | yes |
|
|
122
|
+
| Interactive import (TUI) | yes | yes | yes | yes |
|
|
119
123
|
|
|
120
|
-
Cursor workspace propagation
|
|
124
|
+
Cursor workspace propagation writes repo-local MCP, skills, and agents when those resources exist in the workspace source config.
|
|
121
125
|
|
|
122
|
-
OpenCode workspace configs include the shared workspace `AGENTS.md` natively via `instructions`, so repos under the workspace get both repo-local and shared workspace instructions. Codex repos receive workspace instructions through a generated `AGENTS.override.md`, which is added to each repo's `.git/info/exclude`.
|
|
126
|
+
OpenCode workspace configs include the shared workspace `AGENTS.md` natively via `instructions`, so repos under the workspace get both repo-local and shared workspace instructions. Codex repos receive workspace instructions through a generated `AGENTS.override.md`, which is added to each repo's `.git/info/exclude`. Claude Code receives workspace instructions through generated `CLAUDE.local.md` files, never by editing committed `CLAUDE.md`.
|
|
123
127
|
|
|
124
|
-
Cursor documents `AGENTS.md` support in project roots and subdirectories. `code-agnostic`
|
|
128
|
+
Cursor documents `AGENTS.md` support in project roots and subdirectories. `code-agnostic` does not copy or link the shared workspace `AGENTS.md` into child repos; Cursor will load repo-local or nested `AGENTS.md` files that already exist in the opened project. Codex documents nested `AGENTS.md` discovery, but not a native config include for an extra workspace file.
|
|
125
129
|
|
|
126
130
|
## Features
|
|
127
131
|
|
|
@@ -195,7 +199,9 @@ code-agnostic plan
|
|
|
195
199
|
code-agnostic apply
|
|
196
200
|
```
|
|
197
201
|
|
|
198
|
-
Global skills live under `~/.config/code-agnostic/skills`. Workspace-local skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be inspected with `code-agnostic skills list -w <name>`. Codex generated skill outputs are written to `~/.agents/skills`, while Codex agents and config remain under `~/.codex`.
|
|
202
|
+
Global skills live under `~/.config/code-agnostic/skills`. Workspace-local skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be inspected with `code-agnostic skills list -w <name>`. Codex generated skill outputs are written to `~/.agents/skills`, while Codex agents and config remain under `~/.codex`. Claude Code generated skills and agents are written under `~/.claude/skills` and `~/.claude/agents`, with workspace copies under repo-local `.claude/skills` and `.claude/agents`.
|
|
203
|
+
|
|
204
|
+
Project-local skills are not first-class source inputs in `code-agnostic` yet. If a target app discovers repo-local skill folders such as `.agents/skills`, `.opencode/skills`, or user-created `.claude/skills`, treat those as unmanaged app inputs. Workspace sync writes only the exact generated paths recorded in `.sync-state.json`.
|
|
199
205
|
|
|
200
206
|
Planned convenience command:
|
|
201
207
|
|
|
@@ -207,9 +213,9 @@ That command should copy the skill into the source of truth and then run the nor
|
|
|
207
213
|
|
|
208
214
|
### Workspaces
|
|
209
215
|
|
|
210
|
-
Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs reference the shared workspace file through `instructions`. Repo-local app config, skills, and agents are propagated for OpenCode and
|
|
216
|
+
Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Repo-local app config, skills, and agents are propagated for OpenCode, Cursor, Codex, and Claude.
|
|
211
217
|
|
|
212
|
-
|
|
218
|
+
Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
|
|
213
219
|
|
|
214
220
|
```bash
|
|
215
221
|
code-agnostic workspaces add --name myproject --path ~/code/myproject
|
|
@@ -234,6 +240,7 @@ Migrate existing config from any supported editor into the hub.
|
|
|
234
240
|
```bash
|
|
235
241
|
code-agnostic import plan -a codex
|
|
236
242
|
code-agnostic import apply -a codex
|
|
243
|
+
code-agnostic import plan -a claude
|
|
237
244
|
code-agnostic import apply -a cursor --include mcp --on-conflict overwrite
|
|
238
245
|
code-agnostic import plan -a codex -i # interactive TUI picker
|
|
239
246
|
```
|
|
@@ -266,7 +273,8 @@ The compiler migration is documented in:
|
|
|
266
273
|
- [x] Cross-compilation for skills and agents
|
|
267
274
|
- [x] Per-workspace git-exclude customization
|
|
268
275
|
- [x] Interactive TUI for import selection
|
|
269
|
-
- [
|
|
276
|
+
- [x] Claude Code support
|
|
277
|
+
- [ ] Project-scoped skill installs and sync
|
|
270
278
|
- [ ] `rules add` / `skills add` / `agents add` commands (open `$EDITOR` with template)
|
|
271
279
|
- [ ] Planner integration for cross-compiled skills and agents
|
|
272
280
|
- [ ] Shell auto-complete
|
|
@@ -279,10 +287,37 @@ uv sync --dev
|
|
|
279
287
|
uv run pytest
|
|
280
288
|
```
|
|
281
289
|
|
|
290
|
+
Before pushing release-prep work, run the supported Python matrix locally. Tox
|
|
291
|
+
delegates each environment to `uv run --python`, so `uv` can provide the
|
|
292
|
+
requested interpreter when it is not already installed:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
uvx tox run -p auto
|
|
296
|
+
uvx tox run -e uv310 -- tests/test_version.py -q
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
For a hermetic Linux matrix that does not depend on locally installed Python
|
|
300
|
+
versions, run the Docker matrix:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
./scripts/run-docker-matrix.sh
|
|
304
|
+
./scripts/run-docker-matrix.sh tests/test_version.py -q
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Limit the Docker matrix while iterating:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
PYTHON_VERSIONS="3.10 3.14" ./scripts/run-docker-matrix.sh tests/test_version.py -q
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
The release gate still requires GitHub Actions to pass because the published
|
|
314
|
+
workflow is the source of truth for OS coverage across Ubuntu, macOS, and
|
|
315
|
+
Windows on every supported Python version.
|
|
316
|
+
|
|
282
317
|
Real app-ingestion E2E is gated because it requires installed target CLIs and
|
|
283
318
|
uses each tool's own introspection surface:
|
|
284
319
|
|
|
285
320
|
```bash
|
|
286
321
|
CODE_AGNOSTIC_REAL_APP_E2E=1 uv run pytest tests/e2e/test_real_app_ingestion_e2e.py -q
|
|
287
|
-
CODE_AGNOSTIC_REAL_APP_E2E=1 CODE_AGNOSTIC_REAL_APP_TARGETS=codex,opencode uv run pytest tests/e2e/test_real_app_ingestion_e2e.py -q
|
|
322
|
+
CODE_AGNOSTIC_REAL_APP_E2E=1 CODE_AGNOSTIC_REAL_APP_TARGETS=codex,opencode,claude uv run pytest tests/e2e/test_real_app_ingestion_e2e.py -q
|
|
288
323
|
```
|
|
@@ -30,9 +30,10 @@ AI coding tools each want config in a different place and format. When you use m
|
|
|
30
30
|
~/.config/opencode/ Compiled & synced for OpenCode
|
|
31
31
|
~/.cursor/ Compiled & synced for Cursor
|
|
32
32
|
~/.codex/ Compiled & synced for Codex
|
|
33
|
+
~/.claude.json and ~/.claude/ Compiled & synced for Claude Code
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
Each resource is cross-compiled to the target editor's native format. Rules become `.mdc` files for Cursor, `AGENTS.md` sections for OpenCode/Codex,
|
|
36
|
+
Each resource is cross-compiled to the target editor's native format. Rules become `.mdc` files for Cursor, `AGENTS.md` sections for OpenCode/Codex, and `CLAUDE.local.md` memory for Claude Code.
|
|
36
37
|
|
|
37
38
|
Legacy single-file rules, `skills/<name>/SKILL.md`, and markdown agents are still supported for migration, but bundle directories are the preferred source format for new config.
|
|
38
39
|
|
|
@@ -71,6 +72,7 @@ code-agnostic import apply -a codex
|
|
|
71
72
|
# Enable target editors
|
|
72
73
|
code-agnostic apps enable -a cursor
|
|
73
74
|
code-agnostic apps enable -a opencode
|
|
75
|
+
code-agnostic apps enable -a claude
|
|
74
76
|
|
|
75
77
|
# Preview and apply
|
|
76
78
|
code-agnostic validate
|
|
@@ -80,25 +82,25 @@ code-agnostic apply
|
|
|
80
82
|
|
|
81
83
|
## Editor compatibility
|
|
82
84
|
|
|
83
|
-
| Feature | OpenCode | Cursor | Codex |
|
|
84
|
-
|
|
85
|
-
| MCP sync | yes | yes | yes |
|
|
86
|
-
| Rules sync (cross-compiled) | yes | yes | yes |
|
|
87
|
-
| Skills sync | yes | yes | yes |
|
|
88
|
-
| Agents sync | yes | yes | yes |
|
|
89
|
-
| Workspace root `AGENTS.md` link | yes | yes | yes |
|
|
90
|
-
| Native repo config include for workspace `AGENTS.md` | yes | -- | -- |
|
|
91
|
-
| Repo/subdir gets shared workspace
|
|
92
|
-
| Nested `AGENTS.md` discovery | -- | yes | yes |
|
|
93
|
-
| Workspace propagation | yes |
|
|
94
|
-
| Import from | yes | yes | yes |
|
|
95
|
-
| Interactive import (TUI) | yes | yes | yes |
|
|
85
|
+
| Feature | OpenCode | Cursor | Codex | Claude Code |
|
|
86
|
+
|---------|:--------:|:------:|:-----:|:-----------:|
|
|
87
|
+
| MCP sync | yes | yes | yes | yes |
|
|
88
|
+
| Rules sync (cross-compiled) | yes | yes | yes | yes |
|
|
89
|
+
| Skills sync | yes | yes | yes | yes |
|
|
90
|
+
| Agents sync | yes | yes | yes | yes |
|
|
91
|
+
| Workspace root `AGENTS.md` link | yes | yes | yes | yes |
|
|
92
|
+
| Native repo config include for workspace `AGENTS.md` | yes | -- | -- | -- |
|
|
93
|
+
| Repo/subdir gets shared workspace instructions today | yes | -- | yes | yes |
|
|
94
|
+
| Nested `AGENTS.md` discovery | -- | yes | yes | -- |
|
|
95
|
+
| Workspace propagation | yes | yes | yes | yes |
|
|
96
|
+
| Import from | yes | yes | yes | yes |
|
|
97
|
+
| Interactive import (TUI) | yes | yes | yes | yes |
|
|
96
98
|
|
|
97
|
-
Cursor workspace propagation
|
|
99
|
+
Cursor workspace propagation writes repo-local MCP, skills, and agents when those resources exist in the workspace source config.
|
|
98
100
|
|
|
99
|
-
OpenCode workspace configs include the shared workspace `AGENTS.md` natively via `instructions`, so repos under the workspace get both repo-local and shared workspace instructions. Codex repos receive workspace instructions through a generated `AGENTS.override.md`, which is added to each repo's `.git/info/exclude`.
|
|
101
|
+
OpenCode workspace configs include the shared workspace `AGENTS.md` natively via `instructions`, so repos under the workspace get both repo-local and shared workspace instructions. Codex repos receive workspace instructions through a generated `AGENTS.override.md`, which is added to each repo's `.git/info/exclude`. Claude Code receives workspace instructions through generated `CLAUDE.local.md` files, never by editing committed `CLAUDE.md`.
|
|
100
102
|
|
|
101
|
-
Cursor documents `AGENTS.md` support in project roots and subdirectories. `code-agnostic`
|
|
103
|
+
Cursor documents `AGENTS.md` support in project roots and subdirectories. `code-agnostic` does not copy or link the shared workspace `AGENTS.md` into child repos; Cursor will load repo-local or nested `AGENTS.md` files that already exist in the opened project. Codex documents nested `AGENTS.md` discovery, but not a native config include for an extra workspace file.
|
|
102
104
|
|
|
103
105
|
## Features
|
|
104
106
|
|
|
@@ -172,7 +174,9 @@ code-agnostic plan
|
|
|
172
174
|
code-agnostic apply
|
|
173
175
|
```
|
|
174
176
|
|
|
175
|
-
Global skills live under `~/.config/code-agnostic/skills`. Workspace-local skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be inspected with `code-agnostic skills list -w <name>`. Codex generated skill outputs are written to `~/.agents/skills`, while Codex agents and config remain under `~/.codex`.
|
|
177
|
+
Global skills live under `~/.config/code-agnostic/skills`. Workspace-local skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be inspected with `code-agnostic skills list -w <name>`. Codex generated skill outputs are written to `~/.agents/skills`, while Codex agents and config remain under `~/.codex`. Claude Code generated skills and agents are written under `~/.claude/skills` and `~/.claude/agents`, with workspace copies under repo-local `.claude/skills` and `.claude/agents`.
|
|
178
|
+
|
|
179
|
+
Project-local skills are not first-class source inputs in `code-agnostic` yet. If a target app discovers repo-local skill folders such as `.agents/skills`, `.opencode/skills`, or user-created `.claude/skills`, treat those as unmanaged app inputs. Workspace sync writes only the exact generated paths recorded in `.sync-state.json`.
|
|
176
180
|
|
|
177
181
|
Planned convenience command:
|
|
178
182
|
|
|
@@ -184,9 +188,9 @@ That command should copy the skill into the source of truth and then run the nor
|
|
|
184
188
|
|
|
185
189
|
### Workspaces
|
|
186
190
|
|
|
187
|
-
Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs reference the shared workspace file through `instructions`. Repo-local app config, skills, and agents are propagated for OpenCode and
|
|
191
|
+
Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Repo-local app config, skills, and agents are propagated for OpenCode, Cursor, Codex, and Claude.
|
|
188
192
|
|
|
189
|
-
|
|
193
|
+
Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
|
|
190
194
|
|
|
191
195
|
```bash
|
|
192
196
|
code-agnostic workspaces add --name myproject --path ~/code/myproject
|
|
@@ -211,6 +215,7 @@ Migrate existing config from any supported editor into the hub.
|
|
|
211
215
|
```bash
|
|
212
216
|
code-agnostic import plan -a codex
|
|
213
217
|
code-agnostic import apply -a codex
|
|
218
|
+
code-agnostic import plan -a claude
|
|
214
219
|
code-agnostic import apply -a cursor --include mcp --on-conflict overwrite
|
|
215
220
|
code-agnostic import plan -a codex -i # interactive TUI picker
|
|
216
221
|
```
|
|
@@ -243,7 +248,8 @@ The compiler migration is documented in:
|
|
|
243
248
|
- [x] Cross-compilation for skills and agents
|
|
244
249
|
- [x] Per-workspace git-exclude customization
|
|
245
250
|
- [x] Interactive TUI for import selection
|
|
246
|
-
- [
|
|
251
|
+
- [x] Claude Code support
|
|
252
|
+
- [ ] Project-scoped skill installs and sync
|
|
247
253
|
- [ ] `rules add` / `skills add` / `agents add` commands (open `$EDITOR` with template)
|
|
248
254
|
- [ ] Planner integration for cross-compiled skills and agents
|
|
249
255
|
- [ ] Shell auto-complete
|
|
@@ -256,10 +262,37 @@ uv sync --dev
|
|
|
256
262
|
uv run pytest
|
|
257
263
|
```
|
|
258
264
|
|
|
265
|
+
Before pushing release-prep work, run the supported Python matrix locally. Tox
|
|
266
|
+
delegates each environment to `uv run --python`, so `uv` can provide the
|
|
267
|
+
requested interpreter when it is not already installed:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
uvx tox run -p auto
|
|
271
|
+
uvx tox run -e uv310 -- tests/test_version.py -q
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
For a hermetic Linux matrix that does not depend on locally installed Python
|
|
275
|
+
versions, run the Docker matrix:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
./scripts/run-docker-matrix.sh
|
|
279
|
+
./scripts/run-docker-matrix.sh tests/test_version.py -q
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Limit the Docker matrix while iterating:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
PYTHON_VERSIONS="3.10 3.14" ./scripts/run-docker-matrix.sh tests/test_version.py -q
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The release gate still requires GitHub Actions to pass because the published
|
|
289
|
+
workflow is the source of truth for OS coverage across Ubuntu, macOS, and
|
|
290
|
+
Windows on every supported Python version.
|
|
291
|
+
|
|
259
292
|
Real app-ingestion E2E is gated because it requires installed target CLIs and
|
|
260
293
|
uses each tool's own introspection surface:
|
|
261
294
|
|
|
262
295
|
```bash
|
|
263
296
|
CODE_AGNOSTIC_REAL_APP_E2E=1 uv run pytest tests/e2e/test_real_app_ingestion_e2e.py -q
|
|
264
|
-
CODE_AGNOSTIC_REAL_APP_E2E=1 CODE_AGNOSTIC_REAL_APP_TARGETS=codex,opencode uv run pytest tests/e2e/test_real_app_ingestion_e2e.py -q
|
|
297
|
+
CODE_AGNOSTIC_REAL_APP_E2E=1 CODE_AGNOSTIC_REAL_APP_TARGETS=codex,opencode,claude uv run pytest tests/e2e/test_real_app_ingestion_e2e.py -q
|
|
265
298
|
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Claude Code subagent Markdown conversion."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import yaml # type: ignore[import-untyped]
|
|
10
|
+
|
|
11
|
+
from code_agnostic.agents.models import Agent
|
|
12
|
+
|
|
13
|
+
_SAFE_FILE_STEM_RE = re.compile(r"[^A-Za-z0-9_-]+")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def normalize_claude_agent_filename(name: str, fallback: str) -> str:
|
|
17
|
+
candidate = name.strip() or fallback.strip()
|
|
18
|
+
normalized = _SAFE_FILE_STEM_RE.sub("-", candidate).strip("-_")
|
|
19
|
+
return normalized or fallback
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def serialize_claude_agent(agent: Agent) -> str:
|
|
23
|
+
fm: dict[str, Any] = {}
|
|
24
|
+
if agent.metadata.name:
|
|
25
|
+
fm["name"] = agent.metadata.name
|
|
26
|
+
else:
|
|
27
|
+
fm["name"] = agent.name
|
|
28
|
+
|
|
29
|
+
description = agent.metadata.description or agent.metadata.name or agent.name
|
|
30
|
+
if description:
|
|
31
|
+
fm["description"] = description
|
|
32
|
+
|
|
33
|
+
model = agent.metadata.effective_value("claude", "model")
|
|
34
|
+
if model:
|
|
35
|
+
fm["model"] = model
|
|
36
|
+
reasoning_effort = agent.metadata.effective_value("claude", "reasoning_effort")
|
|
37
|
+
if reasoning_effort:
|
|
38
|
+
fm["effort"] = reasoning_effort
|
|
39
|
+
|
|
40
|
+
for key, value in agent.metadata.app_passthrough(
|
|
41
|
+
"claude",
|
|
42
|
+
consumed_keys={
|
|
43
|
+
"model",
|
|
44
|
+
"reasoning_effort",
|
|
45
|
+
"sandbox_mode",
|
|
46
|
+
"nickname_candidates",
|
|
47
|
+
},
|
|
48
|
+
).items():
|
|
49
|
+
if key in fm:
|
|
50
|
+
continue
|
|
51
|
+
fm[key] = value
|
|
52
|
+
|
|
53
|
+
parts = [
|
|
54
|
+
"---",
|
|
55
|
+
yaml.dump(fm, default_flow_style=False, sort_keys=False).rstrip(),
|
|
56
|
+
"---",
|
|
57
|
+
"",
|
|
58
|
+
agent.content,
|
|
59
|
+
]
|
|
60
|
+
return "\n".join(parts)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def claude_agent_target_path(target_dir: Path, agent: Agent) -> Path:
|
|
64
|
+
return (
|
|
65
|
+
target_dir
|
|
66
|
+
/ f"{normalize_claude_agent_filename(agent.metadata.name, agent.name)}.md"
|
|
67
|
+
)
|
|
@@ -69,10 +69,13 @@ def serialize_codex_agent(agent: Agent) -> str:
|
|
|
69
69
|
description = agent.metadata.description or agent.metadata.name or agent.name
|
|
70
70
|
|
|
71
71
|
doc = tomlkit.document()
|
|
72
|
-
doc.add("name", agent.metadata.name or agent.name)
|
|
73
|
-
doc.add("description", description)
|
|
72
|
+
doc.add("name", tomlkit.item(agent.metadata.name or agent.name))
|
|
73
|
+
doc.add("description", tomlkit.item(description))
|
|
74
74
|
if agent.metadata.nickname_candidates:
|
|
75
|
-
doc.add(
|
|
75
|
+
doc.add(
|
|
76
|
+
"nickname_candidates",
|
|
77
|
+
tomlkit.item(list(agent.metadata.nickname_candidates)),
|
|
78
|
+
)
|
|
76
79
|
model = agent.metadata.effective_value("codex", "model")
|
|
77
80
|
if model:
|
|
78
81
|
doc.add("model", model)
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
|
|
7
|
+
from code_agnostic.agents.claude import serialize_claude_agent
|
|
7
8
|
from code_agnostic.agents.codex import serialize_codex_agent
|
|
8
9
|
from code_agnostic.agents.models import Agent
|
|
9
10
|
from code_agnostic.agents.opencode import serialize_opencode_agent
|
|
@@ -35,3 +36,10 @@ class CodexAgentCompiler(IAgentCompiler):
|
|
|
35
36
|
|
|
36
37
|
def compile(self, agent: Agent) -> str:
|
|
37
38
|
return serialize_codex_agent(agent)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ClaudeAgentCompiler(IAgentCompiler):
|
|
42
|
+
"""Cross-compile for Claude Code subagents."""
|
|
43
|
+
|
|
44
|
+
def compile(self, agent: Agent) -> str:
|
|
45
|
+
return serialize_claude_agent(agent)
|
|
@@ -19,7 +19,7 @@ from code_agnostic.agents.models import (
|
|
|
19
19
|
from code_agnostic.spec.loaders import load_agent_bundle
|
|
20
20
|
|
|
21
21
|
_FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
|
|
22
|
-
_APP_OVERRIDE_PREFIXES = ("cursor", "codex", "opencode")
|
|
22
|
+
_APP_OVERRIDE_PREFIXES = ("cursor", "codex", "opencode", "claude")
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def parse_agent(path: Path) -> Agent:
|
|
@@ -2,6 +2,8 @@ from enum import Enum
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
|
|
4
4
|
from code_agnostic.constants import (
|
|
5
|
+
CLAUDE_CONFIG_FILENAME,
|
|
6
|
+
CLAUDE_PROJECT_DIRNAME,
|
|
5
7
|
CODEX_CONFIG_FILENAME,
|
|
6
8
|
CODEX_PROJECT_DIRNAME,
|
|
7
9
|
CURSOR_CONFIG_FILENAME,
|
|
@@ -16,6 +18,7 @@ class AppId(str, Enum):
|
|
|
16
18
|
OPENCODE = "opencode"
|
|
17
19
|
CURSOR = "cursor"
|
|
18
20
|
CODEX = "codex"
|
|
21
|
+
CLAUDE = "claude"
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
@dataclass(frozen=True)
|
|
@@ -60,7 +63,7 @@ APP_CATALOG: dict[AppId, AppMetadata] = {
|
|
|
60
63
|
toggleable=True,
|
|
61
64
|
importable=True,
|
|
62
65
|
supports_import_agents=True,
|
|
63
|
-
supports_workspace_propagation=
|
|
66
|
+
supports_workspace_propagation=True,
|
|
64
67
|
project_dir_name=CURSOR_PROJECT_DIRNAME,
|
|
65
68
|
config_filename=CURSOR_CONFIG_FILENAME,
|
|
66
69
|
),
|
|
@@ -75,6 +78,17 @@ APP_CATALOG: dict[AppId, AppMetadata] = {
|
|
|
75
78
|
project_dir_name=CODEX_PROJECT_DIRNAME,
|
|
76
79
|
config_filename=CODEX_CONFIG_FILENAME,
|
|
77
80
|
),
|
|
81
|
+
AppId.CLAUDE: AppMetadata(
|
|
82
|
+
app_id=AppId.CLAUDE,
|
|
83
|
+
label="Claude Code",
|
|
84
|
+
targetable=True,
|
|
85
|
+
toggleable=True,
|
|
86
|
+
importable=True,
|
|
87
|
+
supports_import_agents=True,
|
|
88
|
+
supports_workspace_propagation=True,
|
|
89
|
+
project_dir_name=CLAUDE_PROJECT_DIRNAME,
|
|
90
|
+
config_filename=CLAUDE_CONFIG_FILENAME,
|
|
91
|
+
),
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Claude Code app support."""
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from code_agnostic.apps.common.interfaces.repositories import IAppConfigRepository
|
|
5
|
+
from code_agnostic.constants import (
|
|
6
|
+
AGENTS_DIRNAME,
|
|
7
|
+
CLAUDE_CONFIG_FILENAME,
|
|
8
|
+
CLAUDE_PROJECT_DIRNAME,
|
|
9
|
+
SKILLS_DIRNAME,
|
|
10
|
+
)
|
|
11
|
+
from code_agnostic.errors import InvalidConfigSchemaError, InvalidJsonFormatError
|
|
12
|
+
from code_agnostic.utils import read_json_safe, write_json
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ClaudeConfigRepository(IAppConfigRepository):
|
|
16
|
+
def __init__(
|
|
17
|
+
self, root: Path | None = None, config_path: Path | None = None
|
|
18
|
+
) -> None:
|
|
19
|
+
self._root = root or (Path.home() / CLAUDE_PROJECT_DIRNAME)
|
|
20
|
+
self._config_path = config_path or (Path.home() / CLAUDE_CONFIG_FILENAME)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def root(self) -> Path:
|
|
24
|
+
return self._root
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def config_path(self) -> Path:
|
|
28
|
+
return self._config_path
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def skills_dir(self) -> Path:
|
|
32
|
+
return self.root / SKILLS_DIRNAME
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def agents_dir(self) -> Path:
|
|
36
|
+
return self.root / AGENTS_DIRNAME
|
|
37
|
+
|
|
38
|
+
def load_config(self) -> dict[str, Any]:
|
|
39
|
+
payload, error = read_json_safe(self.config_path)
|
|
40
|
+
if error is not None:
|
|
41
|
+
raise InvalidJsonFormatError(self.config_path, error)
|
|
42
|
+
if payload is None:
|
|
43
|
+
return {}
|
|
44
|
+
if not isinstance(payload, dict):
|
|
45
|
+
raise InvalidConfigSchemaError(self.config_path, "must be a JSON object")
|
|
46
|
+
return payload
|
|
47
|
+
|
|
48
|
+
def save_config(self, payload: dict[str, Any]) -> None:
|
|
49
|
+
write_json(self.config_path, payload)
|
|
50
|
+
|
|
51
|
+
def load_mcp_payload(self) -> dict[str, Any]:
|
|
52
|
+
payload = self.load_config()
|
|
53
|
+
mcp = payload.get("mcpServers")
|
|
54
|
+
return mcp if isinstance(mcp, dict) else {}
|
|
55
|
+
|
|
56
|
+
def save_mcp_payload(self, payload: dict[str, Any]) -> None:
|
|
57
|
+
config = self.load_config()
|
|
58
|
+
config["mcpServers"] = payload
|
|
59
|
+
self.save_config(config)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from code_agnostic.apps.common.interfaces.mapper import IAppMCPMapper
|
|
5
|
+
from code_agnostic.apps.common.models import MCPServerDTO, MCPServerType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _as_list(value: Any) -> list[str]:
|
|
9
|
+
if isinstance(value, list):
|
|
10
|
+
return [str(item) for item in value]
|
|
11
|
+
if isinstance(value, str):
|
|
12
|
+
return [value]
|
|
13
|
+
return []
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _as_str_dict(value: Any) -> dict[str, str]:
|
|
17
|
+
return {str(k): str(v) for k, v in value.items()} if isinstance(value, dict) else {}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _timeout(value: Any) -> int | None:
|
|
21
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
22
|
+
return value
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ClaudeMCPMapper(IAppMCPMapper):
|
|
27
|
+
def to_common(self, payload: dict[str, Any]) -> dict[str, MCPServerDTO]:
|
|
28
|
+
mapped: dict[str, MCPServerDTO] = {}
|
|
29
|
+
for name, server in payload.items():
|
|
30
|
+
if not isinstance(server, dict):
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
server_type = server.get("type")
|
|
34
|
+
command = server.get("command")
|
|
35
|
+
if server_type == "stdio" or isinstance(command, str):
|
|
36
|
+
if not isinstance(command, str) or not command:
|
|
37
|
+
continue
|
|
38
|
+
mapped[name] = MCPServerDTO(
|
|
39
|
+
name=name,
|
|
40
|
+
type=MCPServerType.STDIO,
|
|
41
|
+
command=command,
|
|
42
|
+
args=_as_list(server.get("args")),
|
|
43
|
+
env=_as_str_dict(server.get("env")),
|
|
44
|
+
headers=_as_str_dict(server.get("headers")),
|
|
45
|
+
timeout_ms=_timeout(server.get("timeout")),
|
|
46
|
+
)
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
url = server.get("url")
|
|
50
|
+
if not isinstance(url, str) or not url:
|
|
51
|
+
continue
|
|
52
|
+
mapped[name] = MCPServerDTO(
|
|
53
|
+
name=name,
|
|
54
|
+
type=MCPServerType.HTTP,
|
|
55
|
+
url=url,
|
|
56
|
+
env=_as_str_dict(server.get("env")),
|
|
57
|
+
headers=_as_str_dict(server.get("headers")),
|
|
58
|
+
timeout_ms=_timeout(server.get("timeout")),
|
|
59
|
+
)
|
|
60
|
+
return mapped
|
|
61
|
+
|
|
62
|
+
def from_common(self, servers: dict[str, MCPServerDTO]) -> dict[str, Any]:
|
|
63
|
+
mapped: dict[str, Any] = {}
|
|
64
|
+
for name, server in servers.items():
|
|
65
|
+
out: dict[str, Any] = {}
|
|
66
|
+
if server.type == MCPServerType.STDIO:
|
|
67
|
+
if not server.command:
|
|
68
|
+
continue
|
|
69
|
+
out["type"] = "stdio"
|
|
70
|
+
out["command"] = server.command
|
|
71
|
+
if server.args:
|
|
72
|
+
out["args"] = deepcopy(server.args)
|
|
73
|
+
else:
|
|
74
|
+
if not server.url:
|
|
75
|
+
continue
|
|
76
|
+
out["type"] = "http"
|
|
77
|
+
out["url"] = server.url
|
|
78
|
+
|
|
79
|
+
if server.env:
|
|
80
|
+
out["env"] = deepcopy(server.env)
|
|
81
|
+
if server.headers:
|
|
82
|
+
out["headers"] = deepcopy(server.headers)
|
|
83
|
+
if server.timeout_ms is not None:
|
|
84
|
+
out["timeout"] = server.timeout_ms
|
|
85
|
+
mapped[name] = out
|
|
86
|
+
return mapped
|