code-agnostic 0.3.13__tar.gz → 0.3.15__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.13 → code_agnostic-0.3.15}/PKG-INFO +26 -6
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/README.md +25 -5
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/__init__.py +1 -1
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/claude/mapper.py +5 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/codex/mapper.py +3 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/codex/schema.json +40 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/common/models.py +2 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/common/utils.py +8 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/cursor/mapper.py +6 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/cursor/schema.json +2 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/opencode/mapper.py +5 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/opencode/schema.json +4 -0
- code_agnostic-0.3.15/code_agnostic/cli/commands/apply.py +83 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/import_.py +10 -2
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/mcp.py +3 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/restore.py +10 -5
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/skills.py +85 -34
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/helpers.py +14 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/options.py +9 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/core/repository.py +24 -2
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/executor.py +21 -6
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/imports/service.py +1 -1
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/lossiness.py +47 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/mcp_service.py +5 -0
- code_agnostic-0.3.15/code_agnostic/skills/install_sources.py +369 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/spec/loaders.py +2 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/spec/schemas/mcp.base.schema.json +22 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/spec/schemas/mcp.v1.schema.json +9 -1
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/tui/renderers.py +9 -1
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic.egg-info/PKG-INFO +26 -6
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic.egg-info/SOURCES.txt +2 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/pyproject.toml +1 -1
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_apply_cursor.py +1 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_apps.py +32 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_explain_lossiness.py +22 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_import.py +1 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_import_interactive.py +2 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_mcp.py +42 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_projects.py +26 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_restore.py +58 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_skills.py +93 -6
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_workspaces.py +26 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_common_mcp_to_dto.py +12 -1
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_common_repository.py +2 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_dto_to_common_mcp.py +4 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_mcp_service.py +20 -0
- code_agnostic-0.3.15/tests/test_skill_install_sources.py +182 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_transactional_executor.py +80 -0
- code_agnostic-0.3.13/code_agnostic/cli/commands/apply.py +0 -42
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/LICENSE +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/__main__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/agents/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/agents/claude.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/agents/codex.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/agents/compilers.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/agents/models.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/agents/opencode.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/agents/parser.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/app_id.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/apps_service.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/claude/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/claude/config_repository.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/claude/service.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/codex/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/codex/config_repository.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/codex/schema_repository.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/codex/service.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/common/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/common/compiled_planning.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/common/framework.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/common/interfaces/service.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/common/loader.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/common/schema.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/common/symlink_planning.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/cursor/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/cursor/config_repository.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/cursor/schema_repository.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/cursor/service.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/opencode/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/opencode/config_repository.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/opencode/schema_repository.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/apps/opencode/service.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/aliases.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/agents.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/apps.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/plan.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/projects.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/rules.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/status.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/validate.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/cli/commands/workspaces.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/constants.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/core/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/core/project_repository.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/core/workspace_repository.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/errors.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/generated_artifacts.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/git_exclude_service.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/imports/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/imports/adapters.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/imports/filesystem.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/imports/models.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/models.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/planner.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/project_artifacts.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/rules/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/rules/compilers.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/rules/models.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/rules/parser.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/rules/repository.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/skills/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/skills/compilers.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/skills/models.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/skills/parser.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/spec/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/status.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/tui/__init__.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/tui/enums.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/tui/import_selector.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/tui/sections.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/tui/tables.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/utils.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/validation.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/workspace_artifacts.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic/workspaces.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic.egg-info/dependency_links.txt +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic.egg-info/entry_points.txt +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic.egg-info/requires.txt +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/code_agnostic.egg-info/top_level.txt +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/setup.cfg +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_agents.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_aliases.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_apply_apps.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_apply_codex.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_apply_target.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_flags.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_git_exclude.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_module_organization.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_plan.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_rules.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_status.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_validate.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_cli_workspace_resolution.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_compiled_planning.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_git_exclude_service.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_planner_executor.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_planner_rules.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_project_config_sync.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_symlink_planning.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_sync_plan_model.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_utils.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_version.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_workspace_config_sync.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/tests/test_workspace_repo_status.py +0 -0
- {code_agnostic-0.3.13 → code_agnostic-0.3.15}/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.15
|
|
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
|
|
@@ -171,6 +171,7 @@ If managed outputs need repair after an apply, restore the active synced revisio
|
|
|
171
171
|
```bash
|
|
172
172
|
code-agnostic restore
|
|
173
173
|
code-agnostic restore -w myproject
|
|
174
|
+
code-agnostic restore --project myproject
|
|
174
175
|
```
|
|
175
176
|
|
|
176
177
|
### MCP management
|
|
@@ -179,11 +180,14 @@ Add, remove, and list MCP servers without editing JSON by hand.
|
|
|
179
180
|
|
|
180
181
|
```bash
|
|
181
182
|
code-agnostic mcp add github --command npx --args @modelcontextprotocol/server-github --env GITHUB_TOKEN
|
|
183
|
+
code-agnostic mcp add local-docs --command uvx --args docs-mcp --cwd ~/code/docs
|
|
182
184
|
code-agnostic mcp list
|
|
183
185
|
code-agnostic mcp remove github
|
|
184
186
|
```
|
|
185
187
|
|
|
186
188
|
Env vars without a value (`--env GITHUB_TOKEN`) are stored as `${GITHUB_TOKEN}` references.
|
|
189
|
+
Cursor-local MCP `envFile` can be represented in source config and is reported
|
|
190
|
+
as lossy for targets that do not support environment files.
|
|
187
191
|
|
|
188
192
|
### Rules with metadata
|
|
189
193
|
|
|
@@ -230,7 +234,8 @@ code-agnostic skills list
|
|
|
230
234
|
code-agnostic agents list
|
|
231
235
|
```
|
|
232
236
|
|
|
233
|
-
Install a
|
|
237
|
+
Install copies a skill into managed source first for the chosen scope. Run
|
|
238
|
+
`plan` / `apply` afterward to generate target app files.
|
|
234
239
|
|
|
235
240
|
```bash
|
|
236
241
|
code-agnostic skills install ./my-skill --global
|
|
@@ -241,6 +246,20 @@ code-agnostic plan
|
|
|
241
246
|
code-agnostic apply
|
|
242
247
|
```
|
|
243
248
|
|
|
249
|
+
Remote GitHub-style sources are also supported:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
code-agnostic skills install owner/repo --global
|
|
253
|
+
code-agnostic skills install https://github.com/owner/repo --workspace myworkspace
|
|
254
|
+
code-agnostic skills install https://github.com/owner/repo/tree/main/path/to/skills --skill reviewer --skill triage --project myproject
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Remote sources may be an `owner/repo` shorthand, a GitHub repository URL, or a
|
|
258
|
+
GitHub tree URL. When a remote source contains multiple skill candidates,
|
|
259
|
+
repeat `--skill` to select the intended skills; otherwise install fails instead
|
|
260
|
+
of guessing. Remote installs still copy the selected skill into the
|
|
261
|
+
`code-agnostic` source of truth before any generated target outputs are written.
|
|
262
|
+
|
|
244
263
|
Global skills live under `~/.config/code-agnostic/skills`. Workspace-local
|
|
245
264
|
skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be
|
|
246
265
|
inspected with `code-agnostic skills list -w <name>`. Project-local skills live
|
|
@@ -252,7 +271,7 @@ skills and agents are written under `~/.claude/skills` and `~/.claude/agents`,
|
|
|
252
271
|
with workspace/project copies under repo-local `.claude/skills` and
|
|
253
272
|
`.claude/agents`.
|
|
254
273
|
|
|
255
|
-
|
|
274
|
+
If a target app discovers user-created repo-local skill folders such as `.agents/skills`, `.opencode/skills`, or `.claude/skills`, treat those as unmanaged app inputs unless they were generated from `code-agnostic` project source. Workspace and project sync write only the exact generated paths recorded in their `.sync-state.json` files.
|
|
256
275
|
|
|
257
276
|
Planned convenience command:
|
|
258
277
|
|
|
@@ -266,7 +285,7 @@ first implementation slice.
|
|
|
266
285
|
|
|
267
286
|
### Workspaces
|
|
268
287
|
|
|
269
|
-
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 write project-root `opencode.json` files that 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"]`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, and Claude; user-created
|
|
288
|
+
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 write project-root `opencode.json` files that 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"]`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, and Claude; user-created target app skill folders remain unmanaged unless they were generated from managed source.
|
|
270
289
|
|
|
271
290
|
Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
|
|
272
291
|
|
|
@@ -302,7 +321,8 @@ code-agnostic import plan -a codex -i # interactive TUI picker
|
|
|
302
321
|
only the selected sections. Conflicts are skipped by default, so rerun with
|
|
303
322
|
`--on-conflict overwrite` only after reviewing the preview. Use `--include`,
|
|
304
323
|
`--exclude`, `--source-root`, and `--follow-symlinks` to narrow what gets
|
|
305
|
-
imported.
|
|
324
|
+
imported. `--source-root` means the source app config root to read from, not the
|
|
325
|
+
`code-agnostic` hub root.
|
|
306
326
|
|
|
307
327
|
### CLI conventions
|
|
308
328
|
|
|
@@ -334,7 +354,7 @@ The compiler migration is documented in:
|
|
|
334
354
|
- [x] Per-workspace git-exclude customization
|
|
335
355
|
- [x] Interactive TUI for import selection
|
|
336
356
|
- [x] Claude Code support
|
|
337
|
-
- [
|
|
357
|
+
- [x] Project-scoped skill installs and sync
|
|
338
358
|
- [ ] `rules add` / `skills add` / `agents add` commands (open `$EDITOR` with template)
|
|
339
359
|
- [ ] Shell auto-complete
|
|
340
360
|
- [ ] Full TUI mode (command palette + menus)
|
|
@@ -146,6 +146,7 @@ If managed outputs need repair after an apply, restore the active synced revisio
|
|
|
146
146
|
```bash
|
|
147
147
|
code-agnostic restore
|
|
148
148
|
code-agnostic restore -w myproject
|
|
149
|
+
code-agnostic restore --project myproject
|
|
149
150
|
```
|
|
150
151
|
|
|
151
152
|
### MCP management
|
|
@@ -154,11 +155,14 @@ Add, remove, and list MCP servers without editing JSON by hand.
|
|
|
154
155
|
|
|
155
156
|
```bash
|
|
156
157
|
code-agnostic mcp add github --command npx --args @modelcontextprotocol/server-github --env GITHUB_TOKEN
|
|
158
|
+
code-agnostic mcp add local-docs --command uvx --args docs-mcp --cwd ~/code/docs
|
|
157
159
|
code-agnostic mcp list
|
|
158
160
|
code-agnostic mcp remove github
|
|
159
161
|
```
|
|
160
162
|
|
|
161
163
|
Env vars without a value (`--env GITHUB_TOKEN`) are stored as `${GITHUB_TOKEN}` references.
|
|
164
|
+
Cursor-local MCP `envFile` can be represented in source config and is reported
|
|
165
|
+
as lossy for targets that do not support environment files.
|
|
162
166
|
|
|
163
167
|
### Rules with metadata
|
|
164
168
|
|
|
@@ -205,7 +209,8 @@ code-agnostic skills list
|
|
|
205
209
|
code-agnostic agents list
|
|
206
210
|
```
|
|
207
211
|
|
|
208
|
-
Install a
|
|
212
|
+
Install copies a skill into managed source first for the chosen scope. Run
|
|
213
|
+
`plan` / `apply` afterward to generate target app files.
|
|
209
214
|
|
|
210
215
|
```bash
|
|
211
216
|
code-agnostic skills install ./my-skill --global
|
|
@@ -216,6 +221,20 @@ code-agnostic plan
|
|
|
216
221
|
code-agnostic apply
|
|
217
222
|
```
|
|
218
223
|
|
|
224
|
+
Remote GitHub-style sources are also supported:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
code-agnostic skills install owner/repo --global
|
|
228
|
+
code-agnostic skills install https://github.com/owner/repo --workspace myworkspace
|
|
229
|
+
code-agnostic skills install https://github.com/owner/repo/tree/main/path/to/skills --skill reviewer --skill triage --project myproject
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Remote sources may be an `owner/repo` shorthand, a GitHub repository URL, or a
|
|
233
|
+
GitHub tree URL. When a remote source contains multiple skill candidates,
|
|
234
|
+
repeat `--skill` to select the intended skills; otherwise install fails instead
|
|
235
|
+
of guessing. Remote installs still copy the selected skill into the
|
|
236
|
+
`code-agnostic` source of truth before any generated target outputs are written.
|
|
237
|
+
|
|
219
238
|
Global skills live under `~/.config/code-agnostic/skills`. Workspace-local
|
|
220
239
|
skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be
|
|
221
240
|
inspected with `code-agnostic skills list -w <name>`. Project-local skills live
|
|
@@ -227,7 +246,7 @@ skills and agents are written under `~/.claude/skills` and `~/.claude/agents`,
|
|
|
227
246
|
with workspace/project copies under repo-local `.claude/skills` and
|
|
228
247
|
`.claude/agents`.
|
|
229
248
|
|
|
230
|
-
|
|
249
|
+
If a target app discovers user-created repo-local skill folders such as `.agents/skills`, `.opencode/skills`, or `.claude/skills`, treat those as unmanaged app inputs unless they were generated from `code-agnostic` project source. Workspace and project sync write only the exact generated paths recorded in their `.sync-state.json` files.
|
|
231
250
|
|
|
232
251
|
Planned convenience command:
|
|
233
252
|
|
|
@@ -241,7 +260,7 @@ first implementation slice.
|
|
|
241
260
|
|
|
242
261
|
### Workspaces
|
|
243
262
|
|
|
244
|
-
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 write project-root `opencode.json` files that 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"]`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, and Claude; user-created
|
|
263
|
+
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 write project-root `opencode.json` files that 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"]`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, and Claude; user-created target app skill folders remain unmanaged unless they were generated from managed source.
|
|
245
264
|
|
|
246
265
|
Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
|
|
247
266
|
|
|
@@ -277,7 +296,8 @@ code-agnostic import plan -a codex -i # interactive TUI picker
|
|
|
277
296
|
only the selected sections. Conflicts are skipped by default, so rerun with
|
|
278
297
|
`--on-conflict overwrite` only after reviewing the preview. Use `--include`,
|
|
279
298
|
`--exclude`, `--source-root`, and `--follow-symlinks` to narrow what gets
|
|
280
|
-
imported.
|
|
299
|
+
imported. `--source-root` means the source app config root to read from, not the
|
|
300
|
+
`code-agnostic` hub root.
|
|
281
301
|
|
|
282
302
|
### CLI conventions
|
|
283
303
|
|
|
@@ -309,7 +329,7 @@ The compiler migration is documented in:
|
|
|
309
329
|
- [x] Per-workspace git-exclude customization
|
|
310
330
|
- [x] Interactive TUI for import selection
|
|
311
331
|
- [x] Claude Code support
|
|
312
|
-
- [
|
|
332
|
+
- [x] Project-scoped skill installs and sync
|
|
313
333
|
- [ ] `rules add` / `skills add` / `agents add` commands (open `$EDITOR` with template)
|
|
314
334
|
- [ ] Shell auto-complete
|
|
315
335
|
- [ ] Full TUI mode (command palette + menus)
|
|
@@ -40,6 +40,9 @@ class ClaudeMCPMapper(IAppMCPMapper):
|
|
|
40
40
|
type=MCPServerType.STDIO,
|
|
41
41
|
command=command,
|
|
42
42
|
args=_as_list(server.get("args")),
|
|
43
|
+
cwd=server.get("cwd")
|
|
44
|
+
if isinstance(server.get("cwd"), str)
|
|
45
|
+
else None,
|
|
43
46
|
env=_as_str_dict(server.get("env")),
|
|
44
47
|
headers=_as_str_dict(server.get("headers")),
|
|
45
48
|
timeout_ms=_timeout(server.get("timeout")),
|
|
@@ -70,6 +73,8 @@ class ClaudeMCPMapper(IAppMCPMapper):
|
|
|
70
73
|
out["command"] = server.command
|
|
71
74
|
if server.args:
|
|
72
75
|
out["args"] = deepcopy(server.args)
|
|
76
|
+
if server.cwd is not None:
|
|
77
|
+
out["cwd"] = server.cwd
|
|
73
78
|
else:
|
|
74
79
|
if not server.url:
|
|
75
80
|
continue
|
|
@@ -82,6 +82,7 @@ class CodexMCPMapper(IAppMCPMapper):
|
|
|
82
82
|
args=[str(item) for item in server.get("args", [])]
|
|
83
83
|
if isinstance(server.get("args"), list)
|
|
84
84
|
else [],
|
|
85
|
+
cwd=server.get("cwd") if isinstance(server.get("cwd"), str) else None,
|
|
85
86
|
url=url if isinstance(url, str) else None,
|
|
86
87
|
timeout_ms=_coerce_timeout_ms(server.get("tool_timeout_sec")),
|
|
87
88
|
headers=headers,
|
|
@@ -100,6 +101,8 @@ class CodexMCPMapper(IAppMCPMapper):
|
|
|
100
101
|
out["command"] = server.command
|
|
101
102
|
if server.args:
|
|
102
103
|
out["args"] = deepcopy(server.args)
|
|
104
|
+
if server.cwd is not None:
|
|
105
|
+
out["cwd"] = server.cwd
|
|
103
106
|
else:
|
|
104
107
|
if not server.url:
|
|
105
108
|
continue
|
|
@@ -210,6 +210,14 @@
|
|
|
210
210
|
"additionalProperties": false,
|
|
211
211
|
"description": "Default settings that apply to all apps.",
|
|
212
212
|
"properties": {
|
|
213
|
+
"approvals_reviewer": {
|
|
214
|
+
"allOf": [
|
|
215
|
+
{
|
|
216
|
+
"$ref": "#/definitions/ApprovalsReviewer"
|
|
217
|
+
}
|
|
218
|
+
],
|
|
219
|
+
"description": "Reviewer for approval prompts unless overridden by per-app settings."
|
|
220
|
+
},
|
|
213
221
|
"destructive_enabled": {
|
|
214
222
|
"description": "Whether tools with `destructive_hint = true` are allowed by default.",
|
|
215
223
|
"type": "boolean"
|
|
@@ -576,6 +584,9 @@
|
|
|
576
584
|
"request_rule": {
|
|
577
585
|
"type": "boolean"
|
|
578
586
|
},
|
|
587
|
+
"resize_all_images": {
|
|
588
|
+
"type": "boolean"
|
|
589
|
+
},
|
|
579
590
|
"responses_websockets": {
|
|
580
591
|
"type": "boolean"
|
|
581
592
|
},
|
|
@@ -588,6 +599,9 @@
|
|
|
588
599
|
"search_tool": {
|
|
589
600
|
"type": "boolean"
|
|
590
601
|
},
|
|
602
|
+
"secret_auth_storage": {
|
|
603
|
+
"type": "boolean"
|
|
604
|
+
},
|
|
591
605
|
"shell_snapshot": {
|
|
592
606
|
"type": "boolean"
|
|
593
607
|
},
|
|
@@ -621,6 +635,9 @@
|
|
|
621
635
|
"terminal_visualization_instructions": {
|
|
622
636
|
"type": "boolean"
|
|
623
637
|
},
|
|
638
|
+
"token_budget": {
|
|
639
|
+
"type": "boolean"
|
|
640
|
+
},
|
|
624
641
|
"tool_call_mcp_elicitation": {
|
|
625
642
|
"type": "boolean"
|
|
626
643
|
},
|
|
@@ -2436,6 +2453,13 @@
|
|
|
2436
2453
|
},
|
|
2437
2454
|
"type": "object"
|
|
2438
2455
|
},
|
|
2456
|
+
"RealtimeConversationArchitecture": {
|
|
2457
|
+
"enum": [
|
|
2458
|
+
"realtimeapi",
|
|
2459
|
+
"avas"
|
|
2460
|
+
],
|
|
2461
|
+
"type": "string"
|
|
2462
|
+
},
|
|
2439
2463
|
"RealtimeConversationVersion": {
|
|
2440
2464
|
"enum": [
|
|
2441
2465
|
"v1",
|
|
@@ -2446,6 +2470,9 @@
|
|
|
2446
2470
|
"RealtimeToml": {
|
|
2447
2471
|
"additionalProperties": false,
|
|
2448
2472
|
"properties": {
|
|
2473
|
+
"architecture": {
|
|
2474
|
+
"$ref": "#/definitions/RealtimeConversationArchitecture"
|
|
2475
|
+
},
|
|
2449
2476
|
"transport": {
|
|
2450
2477
|
"$ref": "#/definitions/RealtimeTransport"
|
|
2451
2478
|
},
|
|
@@ -4482,6 +4509,10 @@
|
|
|
4482
4509
|
"description": "Experimental / do not use. Replaces the built-in realtime start instructions inserted into developer messages when realtime becomes active.",
|
|
4483
4510
|
"type": "string"
|
|
4484
4511
|
},
|
|
4512
|
+
"experimental_realtime_webrtc_call_base_url": {
|
|
4513
|
+
"description": "Experimental / do not use. Overrides only the WebRTC realtime call creation base URL. This is separate from `experimental_realtime_ws_base_url` because WebRTC call creation is HTTP, while sideband control is websocket.",
|
|
4514
|
+
"type": "string"
|
|
4515
|
+
},
|
|
4485
4516
|
"experimental_realtime_ws_backend_prompt": {
|
|
4486
4517
|
"description": "Experimental / do not use. Overrides only the realtime conversation websocket transport instructions (the `Op::RealtimeConversation` `/ws` session.update instructions) without changing normal prompts.",
|
|
4487
4518
|
"type": "string"
|
|
@@ -4708,6 +4739,9 @@
|
|
|
4708
4739
|
"request_rule": {
|
|
4709
4740
|
"type": "boolean"
|
|
4710
4741
|
},
|
|
4742
|
+
"resize_all_images": {
|
|
4743
|
+
"type": "boolean"
|
|
4744
|
+
},
|
|
4711
4745
|
"responses_websockets": {
|
|
4712
4746
|
"type": "boolean"
|
|
4713
4747
|
},
|
|
@@ -4720,6 +4754,9 @@
|
|
|
4720
4754
|
"search_tool": {
|
|
4721
4755
|
"type": "boolean"
|
|
4722
4756
|
},
|
|
4757
|
+
"secret_auth_storage": {
|
|
4758
|
+
"type": "boolean"
|
|
4759
|
+
},
|
|
4723
4760
|
"shell_snapshot": {
|
|
4724
4761
|
"type": "boolean"
|
|
4725
4762
|
},
|
|
@@ -4753,6 +4790,9 @@
|
|
|
4753
4790
|
"terminal_visualization_instructions": {
|
|
4754
4791
|
"type": "boolean"
|
|
4755
4792
|
},
|
|
4793
|
+
"token_budget": {
|
|
4794
|
+
"type": "boolean"
|
|
4795
|
+
},
|
|
4756
4796
|
"tool_call_mcp_elicitation": {
|
|
4757
4797
|
"type": "boolean"
|
|
4758
4798
|
},
|
|
@@ -22,6 +22,8 @@ class MCPServerDTO:
|
|
|
22
22
|
type: MCPServerType
|
|
23
23
|
command: str | None = None
|
|
24
24
|
args: list[str] = field(default_factory=list)
|
|
25
|
+
cwd: str | None = None
|
|
26
|
+
env_file: str | None = None
|
|
25
27
|
url: str | None = None
|
|
26
28
|
timeout_ms: int | None = None
|
|
27
29
|
headers: dict[str, str] = field(default_factory=dict)
|
|
@@ -67,6 +67,8 @@ def common_mcp_to_dto(mcp_servers: dict[str, Any]) -> dict[str, MCPServerDTO]:
|
|
|
67
67
|
|
|
68
68
|
command = raw.get("command")
|
|
69
69
|
args = raw.get("args")
|
|
70
|
+
cwd = raw.get("cwd")
|
|
71
|
+
env_file = raw.get("envFile")
|
|
70
72
|
url = raw.get("url")
|
|
71
73
|
timeout_ms = _coerce_timeout_ms(raw.get("timeout"))
|
|
72
74
|
|
|
@@ -96,6 +98,8 @@ def common_mcp_to_dto(mcp_servers: dict[str, Any]) -> dict[str, MCPServerDTO]:
|
|
|
96
98
|
type=MCPServerType.STDIO,
|
|
97
99
|
command=command,
|
|
98
100
|
args=[str(item) for item in args] if isinstance(args, list) else [],
|
|
101
|
+
cwd=cwd if isinstance(cwd, str) else None,
|
|
102
|
+
env_file=env_file if isinstance(env_file, str) else None,
|
|
99
103
|
timeout_ms=timeout_ms,
|
|
100
104
|
headers={str(k): str(v) for k, v in headers.items()}
|
|
101
105
|
if isinstance(headers, dict)
|
|
@@ -133,6 +137,10 @@ def dto_to_common_mcp(servers: dict[str, MCPServerDTO]) -> dict[str, Any]:
|
|
|
133
137
|
if server.command:
|
|
134
138
|
item["command"] = server.command
|
|
135
139
|
item["args"] = [str(arg) for arg in server.args]
|
|
140
|
+
if server.cwd is not None:
|
|
141
|
+
item["cwd"] = server.cwd
|
|
142
|
+
if server.env_file is not None:
|
|
143
|
+
item["envFile"] = server.env_file
|
|
136
144
|
elif server.url:
|
|
137
145
|
item["url"] = server.url
|
|
138
146
|
else:
|
|
@@ -22,6 +22,9 @@ class CursorMCPMapper(IAppMCPMapper):
|
|
|
22
22
|
for item in server.get("args", [])
|
|
23
23
|
if isinstance(item, (str, int, float, bool))
|
|
24
24
|
],
|
|
25
|
+
env_file=server.get("envFile")
|
|
26
|
+
if isinstance(server.get("envFile"), str)
|
|
27
|
+
else None,
|
|
25
28
|
timeout_ms=(
|
|
26
29
|
int(server["timeout"])
|
|
27
30
|
if isinstance(server.get("timeout"), int)
|
|
@@ -82,9 +85,12 @@ class CursorMCPMapper(IAppMCPMapper):
|
|
|
82
85
|
if server.type == MCPServerType.STDIO:
|
|
83
86
|
if not server.command:
|
|
84
87
|
continue
|
|
88
|
+
out["type"] = "stdio"
|
|
85
89
|
out["command"] = server.command
|
|
86
90
|
if server.args:
|
|
87
91
|
out["args"] = deepcopy(server.args)
|
|
92
|
+
if server.env_file is not None:
|
|
93
|
+
out["envFile"] = server.env_file
|
|
88
94
|
else:
|
|
89
95
|
if not server.url:
|
|
90
96
|
continue
|
|
@@ -23,12 +23,14 @@
|
|
|
23
23
|
"type": "object",
|
|
24
24
|
"required": ["command"],
|
|
25
25
|
"properties": {
|
|
26
|
+
"type": { "const": "stdio" },
|
|
26
27
|
"command": { "type": "string", "minLength": 1 },
|
|
27
28
|
"args": {
|
|
28
29
|
"type": "array",
|
|
29
30
|
"items": { "type": "string" }
|
|
30
31
|
},
|
|
31
32
|
"timeout": { "type": "integer", "minimum": 0 },
|
|
33
|
+
"envFile": { "type": "string", "minLength": 1 },
|
|
32
34
|
"env": { "$ref": "#/$defs/stringMap" },
|
|
33
35
|
"headers": { "$ref": "#/$defs/stringMap" },
|
|
34
36
|
"auth": { "$ref": "#/$defs/stringMap" }
|
|
@@ -32,6 +32,9 @@ class OpenCodeMCPMapper(IAppMCPMapper):
|
|
|
32
32
|
type=MCPServerType.STDIO,
|
|
33
33
|
command=command,
|
|
34
34
|
args=args,
|
|
35
|
+
cwd=server.get("cwd")
|
|
36
|
+
if isinstance(server.get("cwd"), str)
|
|
37
|
+
else None,
|
|
35
38
|
timeout_ms=(
|
|
36
39
|
int(server["timeout"])
|
|
37
40
|
if isinstance(server.get("timeout"), int)
|
|
@@ -90,6 +93,8 @@ class OpenCodeMCPMapper(IAppMCPMapper):
|
|
|
90
93
|
continue
|
|
91
94
|
out["type"] = "local"
|
|
92
95
|
out["command"] = [server.command, *server.args]
|
|
96
|
+
if server.cwd is not None:
|
|
97
|
+
out["cwd"] = server.cwd
|
|
93
98
|
else:
|
|
94
99
|
if not server.url:
|
|
95
100
|
continue
|
|
@@ -564,6 +564,10 @@
|
|
|
564
564
|
},
|
|
565
565
|
"description": "Command and arguments to run the MCP server"
|
|
566
566
|
},
|
|
567
|
+
"cwd": {
|
|
568
|
+
"type": "string",
|
|
569
|
+
"description": "Working directory for the MCP server process. Relative paths resolve from the workspace directory."
|
|
570
|
+
},
|
|
567
571
|
"environment": {
|
|
568
572
|
"type": "object",
|
|
569
573
|
"additionalProperties": {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Apply command."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from code_agnostic.apps.apps_service import AppsService
|
|
7
|
+
from code_agnostic.cli.options import app_option, verbose_option
|
|
8
|
+
from code_agnostic.core.repository import CoreRepository
|
|
9
|
+
from code_agnostic.models import SyncPlan
|
|
10
|
+
from code_agnostic.tui import SyncConsoleUI
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _apply_next_steps(plan: SyncPlan, target: str) -> str | None:
|
|
14
|
+
if not plan.actions:
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
normalized_target = target.lower()
|
|
18
|
+
target_flag = "" if normalized_target == "all" else f" -a {normalized_target}"
|
|
19
|
+
lines = [
|
|
20
|
+
"Managed outputs were written. Check drift after target files change.",
|
|
21
|
+
f"- code-agnostic status{target_flag}",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
restore_lines = []
|
|
25
|
+
if any(
|
|
26
|
+
action.workspace is None and action.project is None for action in plan.actions
|
|
27
|
+
):
|
|
28
|
+
restore_lines.append("- code-agnostic restore")
|
|
29
|
+
|
|
30
|
+
workspace_names = sorted(
|
|
31
|
+
{action.workspace for action in plan.actions if action.workspace}
|
|
32
|
+
)
|
|
33
|
+
for workspace_name in workspace_names[:3]:
|
|
34
|
+
restore_lines.append(f"- code-agnostic restore -w {workspace_name}")
|
|
35
|
+
if len(workspace_names) > 3:
|
|
36
|
+
restore_lines.append("- code-agnostic restore -w <workspace>")
|
|
37
|
+
|
|
38
|
+
if restore_lines:
|
|
39
|
+
lines.append("Repair global/workspace outputs from the active synced revision.")
|
|
40
|
+
lines.extend(restore_lines)
|
|
41
|
+
|
|
42
|
+
if any(action.project is not None for action in plan.actions):
|
|
43
|
+
lines.append(
|
|
44
|
+
"Project outputs are checked by status; project restore is not available yet."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return "\n".join(lines)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@click.command(help="Apply planned sync changes.")
|
|
51
|
+
@app_option()
|
|
52
|
+
@verbose_option()
|
|
53
|
+
@click.pass_obj
|
|
54
|
+
def apply(obj: dict[str, str], app: str, verbose: bool) -> None:
|
|
55
|
+
target = app or "all"
|
|
56
|
+
ui = SyncConsoleUI(Console())
|
|
57
|
+
core = CoreRepository()
|
|
58
|
+
apps = AppsService(core)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
scoped_plan = apps.plan_for_target(target)
|
|
62
|
+
except Exception as exc:
|
|
63
|
+
raise click.ClickException(f"Fatal: {exc}")
|
|
64
|
+
|
|
65
|
+
ui.render_plan(scoped_plan, mode=f"apply:{target.lower()}", verbose=verbose)
|
|
66
|
+
|
|
67
|
+
if not scoped_plan.actions and not scoped_plan.errors:
|
|
68
|
+
ui.render_apply_result(applied=0, failed=0, failures=[])
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
if scoped_plan.errors:
|
|
72
|
+
raise click.ClickException(
|
|
73
|
+
"Apply aborted due to planning/parsing errors above."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
applied, failed, failures = apps.execute_plan(scoped_plan)
|
|
77
|
+
next_steps = (
|
|
78
|
+
_apply_next_steps(scoped_plan, target) if applied > 0 and failed == 0 else None
|
|
79
|
+
)
|
|
80
|
+
ui.render_apply_result(applied, failed, failures, next_steps=next_steps)
|
|
81
|
+
|
|
82
|
+
if failed:
|
|
83
|
+
raise click.exceptions.Exit(1)
|
|
@@ -45,7 +45,11 @@ def import_group() -> None:
|
|
|
45
45
|
default=ConflictPolicy.SKIP.value,
|
|
46
46
|
show_default=True,
|
|
47
47
|
)
|
|
48
|
-
@click.option(
|
|
48
|
+
@click.option(
|
|
49
|
+
"--source-root",
|
|
50
|
+
type=click.Path(path_type=Path),
|
|
51
|
+
help="Source app config root to read from.",
|
|
52
|
+
)
|
|
49
53
|
@click.option("--follow-symlinks", is_flag=True, default=False)
|
|
50
54
|
@click.option(
|
|
51
55
|
"-i",
|
|
@@ -125,7 +129,11 @@ def import_plan(
|
|
|
125
129
|
default=ConflictPolicy.SKIP.value,
|
|
126
130
|
show_default=True,
|
|
127
131
|
)
|
|
128
|
-
@click.option(
|
|
132
|
+
@click.option(
|
|
133
|
+
"--source-root",
|
|
134
|
+
type=click.Path(path_type=Path),
|
|
135
|
+
help="Source app config root to read from.",
|
|
136
|
+
)
|
|
129
137
|
@click.option("--follow-symlinks", is_flag=True, default=False)
|
|
130
138
|
@click.option(
|
|
131
139
|
"-i",
|
|
@@ -63,6 +63,7 @@ def mcp_list(obj: dict[str, str], workspace: str | None) -> None:
|
|
|
63
63
|
@click.option(
|
|
64
64
|
"--args", "args_str", default=None, help="Comma-separated args for stdio server."
|
|
65
65
|
)
|
|
66
|
+
@click.option("--cwd", default=None, help="Working directory for stdio server.")
|
|
66
67
|
@click.option("--url", default=None, help="URL for HTTP/SSE server.")
|
|
67
68
|
@click.option(
|
|
68
69
|
"--timeout-ms",
|
|
@@ -92,6 +93,7 @@ def mcp_add(
|
|
|
92
93
|
name: str,
|
|
93
94
|
command: str | None,
|
|
94
95
|
args_str: str | None,
|
|
96
|
+
cwd: str | None,
|
|
95
97
|
url: str | None,
|
|
96
98
|
timeout_ms: int | None,
|
|
97
99
|
env_pairs: tuple[str, ...],
|
|
@@ -110,6 +112,7 @@ def mcp_add(
|
|
|
110
112
|
name=name,
|
|
111
113
|
command=command,
|
|
112
114
|
args=args or None,
|
|
115
|
+
cwd=cwd,
|
|
113
116
|
url=url,
|
|
114
117
|
timeout_ms=timeout_ms,
|
|
115
118
|
env=env or None,
|
|
@@ -2,26 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
4
|
|
|
5
|
-
from code_agnostic.cli.helpers import require_workspace_entry
|
|
6
|
-
from code_agnostic.cli.options import workspace_option
|
|
5
|
+
from code_agnostic.cli.helpers import require_project_entry, require_workspace_entry
|
|
6
|
+
from code_agnostic.cli.options import project_option, workspace_option
|
|
7
7
|
from code_agnostic.core.repository import CoreRepository
|
|
8
8
|
from code_agnostic.executor import SyncExecutor
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@click.command(
|
|
12
|
-
help="Restore the active synced revision for the global root or a
|
|
12
|
+
help="Restore the active synced revision for the global root, a workspace, or a project."
|
|
13
13
|
)
|
|
14
|
+
@project_option()
|
|
14
15
|
@workspace_option()
|
|
15
16
|
@click.pass_obj
|
|
16
|
-
def restore(obj: dict[str, str], workspace: str | None) -> None:
|
|
17
|
+
def restore(obj: dict[str, str], workspace: str | None, project: str | None) -> None:
|
|
17
18
|
core = CoreRepository()
|
|
18
19
|
|
|
20
|
+
if workspace is not None and project is not None:
|
|
21
|
+
raise click.ClickException("Choose only one scope: --workspace or --project.")
|
|
19
22
|
if workspace is not None:
|
|
20
23
|
require_workspace_entry(core, workspace)
|
|
24
|
+
if project is not None:
|
|
25
|
+
require_project_entry(core, project)
|
|
21
26
|
|
|
22
27
|
executor = SyncExecutor(core=core)
|
|
23
28
|
try:
|
|
24
|
-
result = executor.restore_active_revision(workspace=workspace)
|
|
29
|
+
result = executor.restore_active_revision(workspace=workspace, project=project)
|
|
25
30
|
except FileNotFoundError as exc:
|
|
26
31
|
raise click.ClickException(str(exc))
|
|
27
32
|
|