code-agnostic 0.3.15__tar.gz → 0.3.16__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.15 → code_agnostic-0.3.16}/PKG-INFO +10 -7
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/README.md +9 -6
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/__init__.py +1 -1
- code_agnostic-0.3.16/code_agnostic/agents/compilers.py +87 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/codex/mapper.py +28 -2
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/codex/schema.json +72 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/agents.py +6 -1
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/apply.py +9 -6
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/rules.py +6 -1
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/skills.py +63 -13
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/status.py +1 -1
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/helpers.py +7 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/core/repository.py +24 -8
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/lossiness.py +12 -2
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/status.py +2 -1
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/tui/renderers.py +57 -13
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/tui/tables.py +3 -1
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic.egg-info/PKG-INFO +10 -7
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/pyproject.toml +1 -1
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_agents.py +20 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_apps.py +4 -3
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_explain_lossiness.py +33 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_import.py +24 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_rules.py +16 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_skills.py +123 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_status.py +26 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_workspaces.py +41 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_common_repository.py +3 -4
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_project_config_sync.py +31 -0
- code_agnostic-0.3.15/code_agnostic/agents/compilers.py +0 -45
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/LICENSE +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/__main__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/agents/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/agents/claude.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/agents/codex.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/agents/models.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/agents/opencode.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/agents/parser.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/app_id.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/apps_service.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/claude/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/claude/config_repository.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/claude/mapper.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/claude/service.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/codex/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/codex/config_repository.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/codex/schema_repository.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/codex/service.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/common/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/common/compiled_planning.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/common/framework.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/common/interfaces/service.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/common/loader.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/common/models.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/common/schema.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/common/symlink_planning.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/common/utils.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/cursor/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/cursor/config_repository.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/cursor/mapper.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/cursor/schema.json +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/cursor/schema_repository.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/cursor/service.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/opencode/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/opencode/config_repository.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/opencode/mapper.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/opencode/schema.json +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/opencode/schema_repository.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/apps/opencode/service.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/aliases.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/apps.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/import_.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/mcp.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/plan.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/projects.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/restore.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/validate.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/commands/workspaces.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/cli/options.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/constants.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/core/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/core/project_repository.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/core/workspace_repository.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/errors.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/executor.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/generated_artifacts.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/git_exclude_service.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/imports/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/imports/adapters.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/imports/filesystem.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/imports/models.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/imports/service.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/mcp_service.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/models.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/planner.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/project_artifacts.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/rules/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/rules/compilers.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/rules/models.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/rules/parser.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/rules/repository.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/skills/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/skills/compilers.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/skills/install_sources.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/skills/models.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/skills/parser.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/spec/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/spec/loaders.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/tui/__init__.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/tui/enums.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/tui/import_selector.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/tui/sections.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/utils.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/validation.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/workspace_artifacts.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic/workspaces.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic.egg-info/SOURCES.txt +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic.egg-info/dependency_links.txt +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic.egg-info/entry_points.txt +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic.egg-info/requires.txt +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/code_agnostic.egg-info/top_level.txt +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/setup.cfg +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_aliases.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_apply_apps.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_apply_codex.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_apply_cursor.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_apply_target.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_flags.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_git_exclude.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_import_interactive.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_mcp.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_module_organization.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_plan.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_projects.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_restore.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_validate.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_cli_workspace_resolution.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_common_mcp_to_dto.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_compiled_planning.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_dto_to_common_mcp.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_git_exclude_service.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_mcp_service.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_planner_executor.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_planner_rules.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_skill_install_sources.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_symlink_planning.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_sync_plan_model.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_transactional_executor.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_utils.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_version.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_workspace_config_sync.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/tests/test_workspace_repo_status.py +0 -0
- {code_agnostic-0.3.15 → code_agnostic-0.3.16}/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.16
|
|
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
|
|
@@ -264,12 +264,14 @@ Global skills live under `~/.config/code-agnostic/skills`. Workspace-local
|
|
|
264
264
|
skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be
|
|
265
265
|
inspected with `code-agnostic skills list -w <name>`. Project-local skills live
|
|
266
266
|
under `~/.config/code-agnostic/projects/<name>/skills` and are generated into
|
|
267
|
-
the registered project directory by `plan` / `apply`.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
skills
|
|
271
|
-
|
|
272
|
-
`.
|
|
267
|
+
the registered project directory by `plan` / `apply`. The current first-class
|
|
268
|
+
list/remove commands cover global and workspace scopes; project skill inventory
|
|
269
|
+
and removal are not supported yet, and `code-agnostic projects list` only shows
|
|
270
|
+
a coarse `[skills]` marker today. Codex generated skill outputs are written to
|
|
271
|
+
`~/.agents/skills`, while Codex agents and config remain under `CODEX_HOME` when
|
|
272
|
+
set, defaulting to `~/.codex`. Claude Code generated skills and agents are
|
|
273
|
+
written under `~/.claude/skills` and `~/.claude/agents`, with workspace/project
|
|
274
|
+
copies under repo-local `.claude/skills` and `.claude/agents`.
|
|
273
275
|
|
|
274
276
|
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.
|
|
275
277
|
|
|
@@ -355,6 +357,7 @@ The compiler migration is documented in:
|
|
|
355
357
|
- [x] Interactive TUI for import selection
|
|
356
358
|
- [x] Claude Code support
|
|
357
359
|
- [x] Project-scoped skill installs and sync
|
|
360
|
+
- [ ] Project-scoped skill inventory and remove commands
|
|
358
361
|
- [ ] `rules add` / `skills add` / `agents add` commands (open `$EDITOR` with template)
|
|
359
362
|
- [ ] Shell auto-complete
|
|
360
363
|
- [ ] Full TUI mode (command palette + menus)
|
|
@@ -239,12 +239,14 @@ Global skills live under `~/.config/code-agnostic/skills`. Workspace-local
|
|
|
239
239
|
skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be
|
|
240
240
|
inspected with `code-agnostic skills list -w <name>`. Project-local skills live
|
|
241
241
|
under `~/.config/code-agnostic/projects/<name>/skills` and are generated into
|
|
242
|
-
the registered project directory by `plan` / `apply`.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
skills
|
|
246
|
-
|
|
247
|
-
`.
|
|
242
|
+
the registered project directory by `plan` / `apply`. The current first-class
|
|
243
|
+
list/remove commands cover global and workspace scopes; project skill inventory
|
|
244
|
+
and removal are not supported yet, and `code-agnostic projects list` only shows
|
|
245
|
+
a coarse `[skills]` marker today. Codex generated skill outputs are written to
|
|
246
|
+
`~/.agents/skills`, while Codex agents and config remain under `CODEX_HOME` when
|
|
247
|
+
set, defaulting to `~/.codex`. Claude Code generated skills and agents are
|
|
248
|
+
written under `~/.claude/skills` and `~/.claude/agents`, with workspace/project
|
|
249
|
+
copies under repo-local `.claude/skills` and `.claude/agents`.
|
|
248
250
|
|
|
249
251
|
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.
|
|
250
252
|
|
|
@@ -330,6 +332,7 @@ The compiler migration is documented in:
|
|
|
330
332
|
- [x] Interactive TUI for import selection
|
|
331
333
|
- [x] Claude Code support
|
|
332
334
|
- [x] Project-scoped skill installs and sync
|
|
335
|
+
- [ ] Project-scoped skill inventory and remove commands
|
|
333
336
|
- [ ] `rules add` / `skills add` / `agents add` commands (open `$EDITOR` with template)
|
|
334
337
|
- [ ] Shell auto-complete
|
|
335
338
|
- [ ] Full TUI mode (command palette + menus)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Per-editor agent compilers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from code_agnostic.agents.claude import serialize_claude_agent
|
|
11
|
+
from code_agnostic.agents.codex import serialize_codex_agent
|
|
12
|
+
from code_agnostic.agents.models import Agent
|
|
13
|
+
from code_agnostic.agents.opencode import serialize_opencode_agent
|
|
14
|
+
from code_agnostic.errors import InvalidConfigSchemaError
|
|
15
|
+
|
|
16
|
+
_CURSOR_AGENT_FRONTMATTER_KEYS = frozenset(
|
|
17
|
+
{"name", "description", "model", "readonly", "is_background"}
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class IAgentCompiler(ABC):
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def compile(self, agent: Agent) -> str:
|
|
24
|
+
"""Return compiled agent content for target editor."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class OpenCodeAgentCompiler(IAgentCompiler):
|
|
28
|
+
"""Cross-compile for OpenCode agents."""
|
|
29
|
+
|
|
30
|
+
def compile(self, agent: Agent) -> str:
|
|
31
|
+
return serialize_opencode_agent(agent)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CursorAgentCompiler(IAgentCompiler):
|
|
35
|
+
"""Cross-compile for Cursor subagents."""
|
|
36
|
+
|
|
37
|
+
def compile(self, agent: Agent) -> str:
|
|
38
|
+
return _serialize_cursor_agent(agent)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _serialize_cursor_agent(agent: Agent) -> str:
|
|
42
|
+
overrides = dict(agent.metadata.app_overrides.get("cursor", {}))
|
|
43
|
+
fm: dict[str, Any] = {}
|
|
44
|
+
if agent.metadata.name:
|
|
45
|
+
fm["name"] = agent.metadata.name
|
|
46
|
+
if agent.metadata.description:
|
|
47
|
+
fm["description"] = agent.metadata.description
|
|
48
|
+
|
|
49
|
+
model = overrides.pop("model", agent.metadata.model)
|
|
50
|
+
if model:
|
|
51
|
+
fm["model"] = model
|
|
52
|
+
|
|
53
|
+
if agent.metadata.tools.write is False:
|
|
54
|
+
fm["readonly"] = True
|
|
55
|
+
|
|
56
|
+
for key in sorted(overrides):
|
|
57
|
+
if key not in _CURSOR_AGENT_FRONTMATTER_KEYS:
|
|
58
|
+
allowed = ", ".join(sorted(_CURSOR_AGENT_FRONTMATTER_KEYS))
|
|
59
|
+
raise InvalidConfigSchemaError(
|
|
60
|
+
agent.source_path,
|
|
61
|
+
f"x-cursor.{key} is not supported in cursor agent "
|
|
62
|
+
f"frontmatter; allowed keys: {allowed}",
|
|
63
|
+
)
|
|
64
|
+
fm[key] = overrides[key]
|
|
65
|
+
|
|
66
|
+
parts: list[str] = []
|
|
67
|
+
if fm:
|
|
68
|
+
parts.append("---")
|
|
69
|
+
parts.append(yaml.dump(fm, default_flow_style=False, sort_keys=False).rstrip())
|
|
70
|
+
parts.append("---")
|
|
71
|
+
parts.append("")
|
|
72
|
+
parts.append(agent.content)
|
|
73
|
+
return "\n".join(parts)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class CodexAgentCompiler(IAgentCompiler):
|
|
77
|
+
"""Cross-compile for Codex subagents."""
|
|
78
|
+
|
|
79
|
+
def compile(self, agent: Agent) -> str:
|
|
80
|
+
return serialize_codex_agent(agent)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ClaudeAgentCompiler(IAgentCompiler):
|
|
84
|
+
"""Cross-compile for Claude Code subagents."""
|
|
85
|
+
|
|
86
|
+
def compile(self, agent: Agent) -> str:
|
|
87
|
+
return serialize_claude_agent(agent)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from copy import deepcopy
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
from code_agnostic.apps.common.interfaces.mapper import IAppMCPMapper
|
|
6
7
|
from code_agnostic.apps.common.models import MCPServerDTO, MCPServerType
|
|
8
|
+
from code_agnostic.errors import InvalidConfigSchemaError
|
|
7
9
|
|
|
8
10
|
_ENV_PATTERN = re.compile(r"^\$\{(?:env:)?([A-Z_][A-Z0-9_]*)\}$")
|
|
9
11
|
_BEARER_PATTERN = re.compile(r"^Bearer\s+\$\{(?:env:)?([A-Z_][A-Z0-9_]*)\}$")
|
|
@@ -34,6 +36,29 @@ def _encode_tool_timeout(timeout_ms: int) -> int | float:
|
|
|
34
36
|
return int(seconds) if timeout_ms % 1000 == 0 else seconds
|
|
35
37
|
|
|
36
38
|
|
|
39
|
+
def _codex_env_var_name(server_name: str, entry: Any) -> str | None:
|
|
40
|
+
if isinstance(entry, str):
|
|
41
|
+
return entry
|
|
42
|
+
if not isinstance(entry, dict):
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
name = entry.get("name")
|
|
46
|
+
if not isinstance(name, str):
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
source = entry.get("source", "local")
|
|
50
|
+
if source in (None, "local"):
|
|
51
|
+
return name
|
|
52
|
+
|
|
53
|
+
detail = (
|
|
54
|
+
"Codex env_vars entries with source = 'remote' cannot be imported "
|
|
55
|
+
"into common MCP config without losing remote-executor semantics"
|
|
56
|
+
if source == "remote"
|
|
57
|
+
else f"Unsupported Codex env_vars source: {source!r}"
|
|
58
|
+
)
|
|
59
|
+
raise InvalidConfigSchemaError(Path(f"mcp_servers.{server_name}.env_vars"), detail)
|
|
60
|
+
|
|
61
|
+
|
|
37
62
|
class CodexMCPMapper(IAppMCPMapper):
|
|
38
63
|
def to_common(self, payload: dict[str, Any]) -> dict[str, MCPServerDTO]:
|
|
39
64
|
mapped: dict[str, MCPServerDTO] = {}
|
|
@@ -49,8 +74,9 @@ class CodexMCPMapper(IAppMCPMapper):
|
|
|
49
74
|
env: dict[str, str] = {}
|
|
50
75
|
env_vars = server.get("env_vars")
|
|
51
76
|
if isinstance(env_vars, list):
|
|
52
|
-
for
|
|
53
|
-
|
|
77
|
+
for entry in env_vars:
|
|
78
|
+
key = _codex_env_var_name(name, entry)
|
|
79
|
+
if key is not None:
|
|
54
80
|
env[key] = f"${{{key}}}"
|
|
55
81
|
env_table = server.get("env")
|
|
56
82
|
if isinstance(env_table, dict):
|
|
@@ -218,6 +218,14 @@
|
|
|
218
218
|
],
|
|
219
219
|
"description": "Reviewer for approval prompts unless overridden by per-app settings."
|
|
220
220
|
},
|
|
221
|
+
"default_tools_approval_mode": {
|
|
222
|
+
"allOf": [
|
|
223
|
+
{
|
|
224
|
+
"$ref": "#/definitions/AppToolApproval"
|
|
225
|
+
}
|
|
226
|
+
],
|
|
227
|
+
"description": "Approval mode for tools unless overridden by per-app or per-tool settings."
|
|
228
|
+
},
|
|
221
229
|
"destructive_enabled": {
|
|
222
230
|
"description": "Whether tools with `destructive_hint = true` are allowed by default.",
|
|
223
231
|
"type": "boolean"
|
|
@@ -354,6 +362,13 @@
|
|
|
354
362
|
"CodeModeConfigToml": {
|
|
355
363
|
"additionalProperties": false,
|
|
356
364
|
"properties": {
|
|
365
|
+
"direct_only_tool_namespaces": {
|
|
366
|
+
"description": "Exact tool namespaces to expose only as direct model tools. These tools bypass deferral, remain top-level in code-mode-only sessions, and are omitted from the nested code-mode tool surface.",
|
|
367
|
+
"items": {
|
|
368
|
+
"type": "string"
|
|
369
|
+
},
|
|
370
|
+
"type": "array"
|
|
371
|
+
},
|
|
357
372
|
"enabled": {
|
|
358
373
|
"type": "boolean"
|
|
359
374
|
},
|
|
@@ -593,6 +608,12 @@
|
|
|
593
608
|
"responses_websockets_v2": {
|
|
594
609
|
"type": "boolean"
|
|
595
610
|
},
|
|
611
|
+
"rollout_budget": {
|
|
612
|
+
"$ref": "#/definitions/FeatureToml_for_RolloutBudgetConfigToml"
|
|
613
|
+
},
|
|
614
|
+
"respect_system_proxy": {
|
|
615
|
+
"type": "boolean"
|
|
616
|
+
},
|
|
596
617
|
"runtime_metrics": {
|
|
597
618
|
"type": "boolean"
|
|
598
619
|
},
|
|
@@ -617,6 +638,9 @@
|
|
|
617
638
|
"skill_mcp_dependency_install": {
|
|
618
639
|
"type": "boolean"
|
|
619
640
|
},
|
|
641
|
+
"sleep_tool": {
|
|
642
|
+
"type": "boolean"
|
|
643
|
+
},
|
|
620
644
|
"sqlite": {
|
|
621
645
|
"type": "boolean"
|
|
622
646
|
},
|
|
@@ -887,6 +911,16 @@
|
|
|
887
911
|
}
|
|
888
912
|
]
|
|
889
913
|
},
|
|
914
|
+
"FeatureToml_for_RolloutBudgetConfigToml": {
|
|
915
|
+
"anyOf": [
|
|
916
|
+
{
|
|
917
|
+
"type": "boolean"
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
"$ref": "#/definitions/RolloutBudgetConfigToml"
|
|
921
|
+
}
|
|
922
|
+
]
|
|
923
|
+
},
|
|
890
924
|
"FeedbackConfigToml": {
|
|
891
925
|
"additionalProperties": false,
|
|
892
926
|
"properties": {
|
|
@@ -2551,6 +2585,35 @@
|
|
|
2551
2585
|
}
|
|
2552
2586
|
]
|
|
2553
2587
|
},
|
|
2588
|
+
"RolloutBudgetConfigToml": {
|
|
2589
|
+
"additionalProperties": false,
|
|
2590
|
+
"properties": {
|
|
2591
|
+
"enabled": {
|
|
2592
|
+
"type": "boolean"
|
|
2593
|
+
},
|
|
2594
|
+
"limit_tokens": {
|
|
2595
|
+
"format": "int64",
|
|
2596
|
+
"minimum": 1.0,
|
|
2597
|
+
"type": "integer"
|
|
2598
|
+
},
|
|
2599
|
+
"prefill_token_weight": {
|
|
2600
|
+
"format": "double",
|
|
2601
|
+
"minimum": 0.0,
|
|
2602
|
+
"type": "number"
|
|
2603
|
+
},
|
|
2604
|
+
"reminder_interval_tokens": {
|
|
2605
|
+
"format": "int64",
|
|
2606
|
+
"minimum": 1.0,
|
|
2607
|
+
"type": "integer"
|
|
2608
|
+
},
|
|
2609
|
+
"sampling_token_weight": {
|
|
2610
|
+
"format": "double",
|
|
2611
|
+
"minimum": 0.0,
|
|
2612
|
+
"type": "number"
|
|
2613
|
+
}
|
|
2614
|
+
},
|
|
2615
|
+
"type": "object"
|
|
2616
|
+
},
|
|
2554
2617
|
"SandboxMode": {
|
|
2555
2618
|
"enum": [
|
|
2556
2619
|
"read-only",
|
|
@@ -4748,6 +4811,12 @@
|
|
|
4748
4811
|
"responses_websockets_v2": {
|
|
4749
4812
|
"type": "boolean"
|
|
4750
4813
|
},
|
|
4814
|
+
"rollout_budget": {
|
|
4815
|
+
"$ref": "#/definitions/FeatureToml_for_RolloutBudgetConfigToml"
|
|
4816
|
+
},
|
|
4817
|
+
"respect_system_proxy": {
|
|
4818
|
+
"type": "boolean"
|
|
4819
|
+
},
|
|
4751
4820
|
"runtime_metrics": {
|
|
4752
4821
|
"type": "boolean"
|
|
4753
4822
|
},
|
|
@@ -4772,6 +4841,9 @@
|
|
|
4772
4841
|
"skill_mcp_dependency_install": {
|
|
4773
4842
|
"type": "boolean"
|
|
4774
4843
|
},
|
|
4844
|
+
"sleep_tool": {
|
|
4845
|
+
"type": "boolean"
|
|
4846
|
+
},
|
|
4775
4847
|
"sqlite": {
|
|
4776
4848
|
"type": "boolean"
|
|
4777
4849
|
},
|
|
@@ -5,7 +5,11 @@ import shutil
|
|
|
5
5
|
import click
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
|
|
8
|
-
from code_agnostic.cli.helpers import
|
|
8
|
+
from code_agnostic.cli.helpers import (
|
|
9
|
+
reject_symlinked_source_dir,
|
|
10
|
+
validate_resource_name,
|
|
11
|
+
workspace_config_root,
|
|
12
|
+
)
|
|
9
13
|
from code_agnostic.cli.options import workspace_option
|
|
10
14
|
from code_agnostic.core.repository import CoreRepository
|
|
11
15
|
from code_agnostic.tui import SyncConsoleUI
|
|
@@ -36,6 +40,7 @@ def agents_remove(obj: dict[str, str], name: str, workspace: str | None) -> None
|
|
|
36
40
|
validate_resource_name(name, "agent")
|
|
37
41
|
core = CoreRepository()
|
|
38
42
|
root = workspace_config_root(core, workspace)
|
|
43
|
+
reject_symlinked_source_dir(root / "agents", "agents")
|
|
39
44
|
agent_dir = root / "agents" / name
|
|
40
45
|
if agent_dir.is_dir():
|
|
41
46
|
shutil.rmtree(agent_dir)
|
|
@@ -35,15 +35,18 @@ def _apply_next_steps(plan: SyncPlan, target: str) -> str | None:
|
|
|
35
35
|
if len(workspace_names) > 3:
|
|
36
36
|
restore_lines.append("- code-agnostic restore -w <workspace>")
|
|
37
37
|
|
|
38
|
+
project_names = sorted(
|
|
39
|
+
{action.project for action in plan.actions if action.project}
|
|
40
|
+
)
|
|
41
|
+
for project_name in project_names[:3]:
|
|
42
|
+
restore_lines.append(f"- code-agnostic restore --project {project_name}")
|
|
43
|
+
if len(project_names) > 3:
|
|
44
|
+
restore_lines.append("- code-agnostic restore --project <project>")
|
|
45
|
+
|
|
38
46
|
if restore_lines:
|
|
39
|
-
lines.append("Repair
|
|
47
|
+
lines.append("Repair managed outputs from the active synced revision.")
|
|
40
48
|
lines.extend(restore_lines)
|
|
41
49
|
|
|
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
50
|
return "\n".join(lines)
|
|
48
51
|
|
|
49
52
|
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
import click
|
|
4
4
|
from rich.console import Console
|
|
5
5
|
|
|
6
|
-
from code_agnostic.cli.helpers import
|
|
6
|
+
from code_agnostic.cli.helpers import (
|
|
7
|
+
reject_symlinked_source_dir,
|
|
8
|
+
validate_resource_name,
|
|
9
|
+
workspace_config_root,
|
|
10
|
+
)
|
|
7
11
|
from code_agnostic.cli.options import workspace_option
|
|
8
12
|
from code_agnostic.core.repository import CoreRepository
|
|
9
13
|
from code_agnostic.rules.repository import RulesRepository
|
|
@@ -40,6 +44,7 @@ def rules_remove(obj: dict[str, str], name: str, workspace: str | None) -> None:
|
|
|
40
44
|
validate_resource_name(name, "rule")
|
|
41
45
|
core = CoreRepository()
|
|
42
46
|
root = workspace_config_root(core, workspace)
|
|
47
|
+
reject_symlinked_source_dir(root / "rules", "rules")
|
|
43
48
|
|
|
44
49
|
repo = RulesRepository(root)
|
|
45
50
|
if not repo.remove_rule(name):
|
|
@@ -6,7 +6,11 @@ import shutil
|
|
|
6
6
|
import click
|
|
7
7
|
from rich.console import Console
|
|
8
8
|
|
|
9
|
-
from code_agnostic.cli.helpers import
|
|
9
|
+
from code_agnostic.cli.helpers import (
|
|
10
|
+
reject_symlinked_source_dir,
|
|
11
|
+
validate_resource_name,
|
|
12
|
+
workspace_config_root,
|
|
13
|
+
)
|
|
10
14
|
from code_agnostic.cli.options import workspace_option
|
|
11
15
|
from code_agnostic.core.repository import CoreRepository
|
|
12
16
|
from code_agnostic.errors import SyncAppError
|
|
@@ -15,6 +19,7 @@ from code_agnostic.skills.install_sources import (
|
|
|
15
19
|
cleanup_skill_install_resolution,
|
|
16
20
|
resolve_skill_install_source,
|
|
17
21
|
)
|
|
22
|
+
from code_agnostic.spec.loaders import load_skill_bundle
|
|
18
23
|
from code_agnostic.tui import SyncConsoleUI
|
|
19
24
|
from code_agnostic.utils import compact_home_path
|
|
20
25
|
|
|
@@ -94,9 +99,18 @@ def _preflight_skill_destinations(
|
|
|
94
99
|
if destination.exists():
|
|
95
100
|
raise click.ClickException(f"Skill already exists: {scope}:{name}")
|
|
96
101
|
installs.append((name, source_dir, destination))
|
|
102
|
+
_validate_skill_source_metadata(source_dirs)
|
|
97
103
|
return installs
|
|
98
104
|
|
|
99
105
|
|
|
106
|
+
def _rollback_skill_destinations(destinations: list[Path]) -> None:
|
|
107
|
+
for destination in reversed(destinations):
|
|
108
|
+
if destination.is_symlink() or destination.is_file():
|
|
109
|
+
destination.unlink()
|
|
110
|
+
elif destination.is_dir():
|
|
111
|
+
shutil.rmtree(destination)
|
|
112
|
+
|
|
113
|
+
|
|
100
114
|
def _validate_skill_source_names(source_dirs: tuple[Path, ...]) -> list[str]:
|
|
101
115
|
names: list[str] = []
|
|
102
116
|
seen_names: set[str] = set()
|
|
@@ -110,6 +124,12 @@ def _validate_skill_source_names(source_dirs: tuple[Path, ...]) -> list[str]:
|
|
|
110
124
|
return names
|
|
111
125
|
|
|
112
126
|
|
|
127
|
+
def _validate_skill_source_metadata(source_dirs: tuple[Path, ...]) -> None:
|
|
128
|
+
for source_dir in source_dirs:
|
|
129
|
+
if (source_dir / "meta.yaml").exists() and (source_dir / "prompt.md").exists():
|
|
130
|
+
load_skill_bundle(source_dir)
|
|
131
|
+
|
|
132
|
+
|
|
113
133
|
@click.group(
|
|
114
134
|
help=(
|
|
115
135
|
"Manage source skill definitions across global, workspace, and project scopes."
|
|
@@ -159,6 +179,7 @@ def skills_install(
|
|
|
159
179
|
project=project,
|
|
160
180
|
)
|
|
161
181
|
try:
|
|
182
|
+
reject_symlinked_source_dir(root / "skills", "skills")
|
|
162
183
|
if resolution is None:
|
|
163
184
|
resolution = resolve_skill_install_source(
|
|
164
185
|
source,
|
|
@@ -168,27 +189,44 @@ def skills_install(
|
|
|
168
189
|
for _name, _source_dir, destination in installs:
|
|
169
190
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
170
191
|
installed = False
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
192
|
+
try:
|
|
193
|
+
for name, source_dir, destination in installs:
|
|
194
|
+
shutil.copytree(source_dir, destination)
|
|
195
|
+
installed = True
|
|
196
|
+
if scope_note is not None:
|
|
197
|
+
click.echo(scope_note)
|
|
198
|
+
scope_note = None
|
|
199
|
+
click.echo(f"Installed {scope} skill: {name}")
|
|
200
|
+
click.echo(f"Source: {compact_home_path(source_dir)}")
|
|
201
|
+
click.echo(f"Destination: {compact_home_path(destination)}")
|
|
202
|
+
except Exception as exc:
|
|
203
|
+
_rollback_skill_destinations(
|
|
204
|
+
[destination for _name, _source_dir, destination in installs]
|
|
205
|
+
)
|
|
206
|
+
raise click.ClickException(f"Skill install failed: {exc}") from exc
|
|
180
207
|
if installed:
|
|
181
208
|
click.echo("Next:")
|
|
182
209
|
click.echo(" code-agnostic plan")
|
|
183
210
|
click.echo(" code-agnostic apply")
|
|
184
|
-
|
|
211
|
+
if scope.startswith("project:"):
|
|
212
|
+
click.echo(
|
|
213
|
+
"Note: project skills are separate; bare "
|
|
214
|
+
"`code-agnostic skills list` shows only global skills."
|
|
215
|
+
)
|
|
216
|
+
except SyncAppError as exc:
|
|
185
217
|
raise click.ClickException(str(exc)) from exc
|
|
186
218
|
finally:
|
|
187
219
|
if resolution is not None:
|
|
188
220
|
cleanup_skill_install_resolution(resolution)
|
|
189
221
|
|
|
190
222
|
|
|
191
|
-
@skills.command(
|
|
223
|
+
@skills.command(
|
|
224
|
+
"list",
|
|
225
|
+
help=(
|
|
226
|
+
"List global skills, or workspace skills with -w. Project skills are "
|
|
227
|
+
"not listed here yet; use projects list for a project marker."
|
|
228
|
+
),
|
|
229
|
+
)
|
|
192
230
|
@workspace_option()
|
|
193
231
|
@click.pass_obj
|
|
194
232
|
def skills_list(obj: dict[str, str], workspace: str | None) -> None:
|
|
@@ -219,6 +257,11 @@ def skills_list(obj: dict[str, str], workspace: str | None) -> None:
|
|
|
219
257
|
"- code-agnostic plan\n"
|
|
220
258
|
"- code-agnostic apply"
|
|
221
259
|
)
|
|
260
|
+
if workspace is None:
|
|
261
|
+
empty_message += (
|
|
262
|
+
"\n- Project skills are separate; run "
|
|
263
|
+
"code-agnostic projects list to see which projects contain skills."
|
|
264
|
+
)
|
|
222
265
|
ui.render_list(
|
|
223
266
|
"skills",
|
|
224
267
|
["Skill", "Scope", "Format", "Source"],
|
|
@@ -227,7 +270,13 @@ def skills_list(obj: dict[str, str], workspace: str | None) -> None:
|
|
|
227
270
|
)
|
|
228
271
|
|
|
229
272
|
|
|
230
|
-
@skills.command(
|
|
273
|
+
@skills.command(
|
|
274
|
+
"remove",
|
|
275
|
+
help=(
|
|
276
|
+
"Remove a global skill, or a workspace skill with -w. Project skill "
|
|
277
|
+
"removal is not supported yet."
|
|
278
|
+
),
|
|
279
|
+
)
|
|
231
280
|
@click.option("--name", required=True, help="Skill name to remove.")
|
|
232
281
|
@workspace_option()
|
|
233
282
|
@click.pass_obj
|
|
@@ -235,6 +284,7 @@ def skills_remove(obj: dict[str, str], name: str, workspace: str | None) -> None
|
|
|
235
284
|
validate_resource_name(name, "skill")
|
|
236
285
|
core = CoreRepository()
|
|
237
286
|
root = workspace_config_root(core, workspace)
|
|
287
|
+
reject_symlinked_source_dir(root / "skills", "skills")
|
|
238
288
|
skill_dir = root / "skills" / name
|
|
239
289
|
if not skill_dir.exists():
|
|
240
290
|
raise click.ClickException(f"Skill not found: {name}")
|
|
@@ -38,7 +38,7 @@ def _status_row_for_app(app_name: str, apps: AppsService) -> EditorStatusRow:
|
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
@click.command(help="Show sync status for editors and
|
|
41
|
+
@click.command(help="Show sync status for editors, workspaces, and projects.")
|
|
42
42
|
@app_option()
|
|
43
43
|
@verbose_option()
|
|
44
44
|
@click.pass_obj
|
|
@@ -57,6 +57,13 @@ def validate_resource_name(name: str, resource_type: str) -> None:
|
|
|
57
57
|
raise click.ClickException(f"Invalid {resource_type} name: {name}")
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
def reject_symlinked_source_dir(path: Path, resource_type: str) -> None:
|
|
61
|
+
if path.is_symlink():
|
|
62
|
+
raise click.ClickException(
|
|
63
|
+
f"Refusing to modify symlinked {resource_type} source directory: {path}"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
60
67
|
def status_row_for_app(app_name: str, plan, apps: AppsService) -> EditorStatusRow:
|
|
61
68
|
if not apps.is_enabled(app_name):
|
|
62
69
|
return EditorStatusRow(
|
|
@@ -221,23 +221,39 @@ class CoreRepository(BaseSourceRepository):
|
|
|
221
221
|
|
|
222
222
|
result: list[dict[str, str]] = []
|
|
223
223
|
seen_names: set[str] = set()
|
|
224
|
+
seen_paths: set[Path] = set()
|
|
224
225
|
for item in payload:
|
|
225
226
|
if not isinstance(item, dict):
|
|
226
|
-
|
|
227
|
+
raise InvalidConfigSchemaError(
|
|
228
|
+
self.workspaces_path, "entries must be JSON objects"
|
|
229
|
+
)
|
|
227
230
|
name = item.get("name")
|
|
228
231
|
path = item.get("path")
|
|
229
232
|
if not isinstance(name, str) or not isinstance(path, str):
|
|
230
|
-
|
|
233
|
+
raise InvalidConfigSchemaError(
|
|
234
|
+
self.workspaces_path, "entries must contain string name and path"
|
|
235
|
+
)
|
|
231
236
|
normalized_name = name.strip()
|
|
232
237
|
normalized_path = str(Path(path).expanduser().resolve())
|
|
233
|
-
if
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
):
|
|
238
|
-
|
|
238
|
+
if not normalized_name:
|
|
239
|
+
raise InvalidConfigSchemaError(
|
|
240
|
+
self.workspaces_path, "workspace name cannot be empty"
|
|
241
|
+
)
|
|
242
|
+
if _is_path_like_config_name(normalized_name):
|
|
243
|
+
raise InvalidConfigSchemaError(
|
|
244
|
+
self.workspaces_path, f"invalid workspace name: {normalized_name}"
|
|
245
|
+
)
|
|
246
|
+
if normalized_name in seen_names:
|
|
247
|
+
raise InvalidConfigSchemaError(
|
|
248
|
+
self.workspaces_path, f"duplicate workspace name: {normalized_name}"
|
|
249
|
+
)
|
|
250
|
+
if Path(normalized_path) in seen_paths:
|
|
251
|
+
raise InvalidConfigSchemaError(
|
|
252
|
+
self.workspaces_path, f"duplicate workspace path: {normalized_path}"
|
|
253
|
+
)
|
|
239
254
|
result.append({"name": normalized_name, "path": normalized_path})
|
|
240
255
|
seen_names.add(normalized_name)
|
|
256
|
+
seen_paths.add(Path(normalized_path))
|
|
241
257
|
return result
|
|
242
258
|
|
|
243
259
|
def save_workspaces(self, workspaces: list[dict[str, str]]) -> None:
|
|
@@ -248,6 +248,16 @@ class LossinessExplainer:
|
|
|
248
248
|
reason="target does not support agent nickname_candidates",
|
|
249
249
|
)
|
|
250
250
|
)
|
|
251
|
+
if agent.metadata.model_reasoning_effort:
|
|
252
|
+
findings.extend(
|
|
253
|
+
self._findings_for_targets(
|
|
254
|
+
resource_path=resource_path,
|
|
255
|
+
property_name="reasoning_effort",
|
|
256
|
+
targets=("cursor",),
|
|
257
|
+
app=app,
|
|
258
|
+
reason="target does not support agent reasoning_effort",
|
|
259
|
+
)
|
|
260
|
+
)
|
|
251
261
|
if agent.metadata.sandbox_mode:
|
|
252
262
|
findings.extend(
|
|
253
263
|
self._findings_for_targets(
|
|
@@ -263,7 +273,7 @@ class LossinessExplainer:
|
|
|
263
273
|
self._findings_for_targets(
|
|
264
274
|
resource_path=resource_path,
|
|
265
275
|
property_name="tools.read",
|
|
266
|
-
targets=("codex"
|
|
276
|
+
targets=("cursor", "codex"),
|
|
267
277
|
app=app,
|
|
268
278
|
reason="target does not support agent read permissions",
|
|
269
279
|
)
|
|
@@ -283,7 +293,7 @@ class LossinessExplainer:
|
|
|
283
293
|
self._findings_for_targets(
|
|
284
294
|
resource_path=resource_path,
|
|
285
295
|
property_name="tools.mcp",
|
|
286
|
-
targets=("codex"
|
|
296
|
+
targets=("cursor", "codex"),
|
|
287
297
|
app=app,
|
|
288
298
|
reason="target does not support agent MCP permissions",
|
|
289
299
|
)
|