code-agnostic 0.3.12__tar.gz → 0.3.14__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.12 → code_agnostic-0.3.14}/PKG-INFO +39 -16
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/README.md +38 -15
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/__init__.py +1 -1
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/__main__.py +2 -0
- code_agnostic-0.3.14/code_agnostic/cli/commands/projects.py +78 -0
- code_agnostic-0.3.14/code_agnostic/cli/commands/skills.py +236 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/status.py +20 -2
- code_agnostic-0.3.14/code_agnostic/core/project_repository.py +10 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/core/repository.py +90 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/executor.py +122 -41
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/models.py +15 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/planner.py +124 -1
- code_agnostic-0.3.14/code_agnostic/project_artifacts.py +39 -0
- code_agnostic-0.3.14/code_agnostic/skills/install_sources.py +369 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/status.py +98 -1
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/tui/renderers.py +62 -30
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/tui/tables.py +39 -3
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic.egg-info/PKG-INFO +39 -16
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic.egg-info/SOURCES.txt +7 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/pyproject.toml +1 -1
- code_agnostic-0.3.14/tests/test_cli_projects.py +227 -0
- code_agnostic-0.3.14/tests/test_cli_skills.py +421 -0
- code_agnostic-0.3.14/tests/test_project_config_sync.py +242 -0
- code_agnostic-0.3.14/tests/test_skill_install_sources.py +182 -0
- code_agnostic-0.3.12/code_agnostic/cli/commands/skills.py +0 -77
- code_agnostic-0.3.12/tests/test_cli_skills.py +0 -133
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/LICENSE +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/claude.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/codex.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/compilers.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/models.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/opencode.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/agents/parser.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/app_id.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/apps_service.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/claude/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/claude/config_repository.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/claude/mapper.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/claude/service.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/codex/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/codex/config_repository.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/codex/mapper.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/codex/schema.json +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/codex/schema_repository.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/codex/service.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/compiled_planning.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/framework.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/interfaces/service.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/loader.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/models.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/schema.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/symlink_planning.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/common/utils.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/cursor/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/cursor/config_repository.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/cursor/mapper.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/cursor/schema.json +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/cursor/schema_repository.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/cursor/service.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/opencode/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/opencode/config_repository.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/opencode/mapper.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/opencode/schema.json +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/opencode/schema_repository.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/apps/opencode/service.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/aliases.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/agents.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/apply.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/apps.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/import_.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/mcp.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/plan.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/restore.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/rules.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/validate.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/commands/workspaces.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/helpers.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/cli/options.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/constants.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/core/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/core/workspace_repository.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/errors.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/generated_artifacts.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/git_exclude_service.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/imports/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/imports/adapters.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/imports/filesystem.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/imports/models.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/imports/service.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/lossiness.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/mcp_service.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/rules/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/rules/compilers.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/rules/models.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/rules/parser.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/rules/repository.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/skills/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/skills/compilers.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/skills/models.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/skills/parser.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/loaders.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/tui/__init__.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/tui/enums.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/tui/import_selector.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/tui/sections.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/utils.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/validation.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/workspace_artifacts.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic/workspaces.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic.egg-info/dependency_links.txt +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic.egg-info/entry_points.txt +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic.egg-info/requires.txt +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/code_agnostic.egg-info/top_level.txt +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/setup.cfg +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_agents.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_aliases.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_apply_apps.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_apply_codex.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_apply_cursor.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_apply_target.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_apps.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_explain_lossiness.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_flags.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_git_exclude.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_import.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_import_interactive.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_mcp.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_module_organization.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_plan.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_restore.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_rules.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_status.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_validate.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_workspace_resolution.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_cli_workspaces.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_common_mcp_to_dto.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_common_repository.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_compiled_planning.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_dto_to_common_mcp.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_git_exclude_service.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_mcp_service.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_planner_executor.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_planner_rules.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_symlink_planning.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_sync_plan_model.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_transactional_executor.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_utils.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_version.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_workspace_config_sync.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/tests/test_workspace_repo_status.py +0 -0
- {code_agnostic-0.3.12 → code_agnostic-0.3.14}/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.14
|
|
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
|
|
@@ -66,19 +66,19 @@ Today the implementation is still mixed: some assets are compiled and some are s
|
|
|
66
66
|
|
|
67
67
|
## Scope model
|
|
68
68
|
|
|
69
|
-
`code-agnostic` has
|
|
69
|
+
`code-agnostic` has three managed source scopes:
|
|
70
70
|
|
|
71
71
|
- global source config under `~/.config/code-agnostic/`, synced to enabled
|
|
72
72
|
user-level app config;
|
|
73
73
|
- workspace source config under `~/.config/code-agnostic/workspaces/<name>/`,
|
|
74
|
-
propagated into repos inside a registered workspace
|
|
74
|
+
propagated into repos inside a registered workspace;
|
|
75
|
+
- project source config under `~/.config/code-agnostic/projects/<name>/`,
|
|
76
|
+
synced to exactly one registered project directory.
|
|
75
77
|
|
|
76
78
|
Workspace sync may generate repo-local outputs, but those outputs are not
|
|
77
|
-
source. Project-local skill folders
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
installs are planned so a single registered project can have managed local
|
|
81
|
-
source config without bypassing the hub.
|
|
79
|
+
source. Project-local skill folders generated inside a repo, such as
|
|
80
|
+
`.agents/skills` or `.opencode/skills`, are also generated outputs; install
|
|
81
|
+
skills into managed project source first, then run `plan` / `apply`.
|
|
82
82
|
|
|
83
83
|
## Install
|
|
84
84
|
|
|
@@ -230,21 +230,44 @@ code-agnostic skills list
|
|
|
230
230
|
code-agnostic agents list
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
-
|
|
233
|
+
Install copies a skill into managed source first for the chosen scope. Run
|
|
234
|
+
`plan` / `apply` afterward to generate target app files.
|
|
234
235
|
|
|
235
236
|
```bash
|
|
236
|
-
|
|
237
|
-
|
|
237
|
+
code-agnostic skills install ./my-skill --global
|
|
238
|
+
code-agnostic skills install ./my-skill --workspace myworkspace
|
|
239
|
+
code-agnostic projects add --name myproject --path .
|
|
240
|
+
code-agnostic skills install ./my-skill --project myproject
|
|
238
241
|
code-agnostic plan
|
|
239
242
|
code-agnostic apply
|
|
240
243
|
```
|
|
241
244
|
|
|
242
|
-
|
|
243
|
-
then use the normal `plan` / `apply` workflow.
|
|
245
|
+
Remote GitHub-style sources are also supported:
|
|
244
246
|
|
|
245
|
-
|
|
247
|
+
```bash
|
|
248
|
+
code-agnostic skills install owner/repo --global
|
|
249
|
+
code-agnostic skills install https://github.com/owner/repo --workspace myworkspace
|
|
250
|
+
code-agnostic skills install https://github.com/owner/repo/tree/main/path/to/skills --skill reviewer --skill triage --project myproject
|
|
251
|
+
```
|
|
246
252
|
|
|
247
|
-
|
|
253
|
+
Remote sources may be an `owner/repo` shorthand, a GitHub repository URL, or a
|
|
254
|
+
GitHub tree URL. When a remote source contains multiple skill candidates,
|
|
255
|
+
repeat `--skill` to select the intended skills; otherwise install fails instead
|
|
256
|
+
of guessing. Remote installs still copy the selected skill into the
|
|
257
|
+
`code-agnostic` source of truth before any generated target outputs are written.
|
|
258
|
+
|
|
259
|
+
Global skills live under `~/.config/code-agnostic/skills`. Workspace-local
|
|
260
|
+
skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be
|
|
261
|
+
inspected with `code-agnostic skills list -w <name>`. Project-local skills live
|
|
262
|
+
under `~/.config/code-agnostic/projects/<name>/skills` and are generated into
|
|
263
|
+
the registered project directory by `plan` / `apply`. Codex generated skill
|
|
264
|
+
outputs are written to `~/.agents/skills`, while Codex agents and config remain
|
|
265
|
+
under `CODEX_HOME` when set, defaulting to `~/.codex`. Claude Code generated
|
|
266
|
+
skills and agents are written under `~/.claude/skills` and `~/.claude/agents`,
|
|
267
|
+
with workspace/project copies under repo-local `.claude/skills` and
|
|
268
|
+
`.claude/agents`.
|
|
269
|
+
|
|
270
|
+
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.
|
|
248
271
|
|
|
249
272
|
Planned convenience command:
|
|
250
273
|
|
|
@@ -258,7 +281,7 @@ first implementation slice.
|
|
|
258
281
|
|
|
259
282
|
### Workspaces
|
|
260
283
|
|
|
261
|
-
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
|
|
284
|
+
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.
|
|
262
285
|
|
|
263
286
|
Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
|
|
264
287
|
|
|
@@ -41,19 +41,19 @@ Today the implementation is still mixed: some assets are compiled and some are s
|
|
|
41
41
|
|
|
42
42
|
## Scope model
|
|
43
43
|
|
|
44
|
-
`code-agnostic` has
|
|
44
|
+
`code-agnostic` has three managed source scopes:
|
|
45
45
|
|
|
46
46
|
- global source config under `~/.config/code-agnostic/`, synced to enabled
|
|
47
47
|
user-level app config;
|
|
48
48
|
- workspace source config under `~/.config/code-agnostic/workspaces/<name>/`,
|
|
49
|
-
propagated into repos inside a registered workspace
|
|
49
|
+
propagated into repos inside a registered workspace;
|
|
50
|
+
- project source config under `~/.config/code-agnostic/projects/<name>/`,
|
|
51
|
+
synced to exactly one registered project directory.
|
|
50
52
|
|
|
51
53
|
Workspace sync may generate repo-local outputs, but those outputs are not
|
|
52
|
-
source. Project-local skill folders
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
installs are planned so a single registered project can have managed local
|
|
56
|
-
source config without bypassing the hub.
|
|
54
|
+
source. Project-local skill folders generated inside a repo, such as
|
|
55
|
+
`.agents/skills` or `.opencode/skills`, are also generated outputs; install
|
|
56
|
+
skills into managed project source first, then run `plan` / `apply`.
|
|
57
57
|
|
|
58
58
|
## Install
|
|
59
59
|
|
|
@@ -205,21 +205,44 @@ code-agnostic skills list
|
|
|
205
205
|
code-agnostic agents list
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
Install copies a skill into managed source first for the chosen scope. Run
|
|
209
|
+
`plan` / `apply` afterward to generate target app files.
|
|
209
210
|
|
|
210
211
|
```bash
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
code-agnostic skills install ./my-skill --global
|
|
213
|
+
code-agnostic skills install ./my-skill --workspace myworkspace
|
|
214
|
+
code-agnostic projects add --name myproject --path .
|
|
215
|
+
code-agnostic skills install ./my-skill --project myproject
|
|
213
216
|
code-agnostic plan
|
|
214
217
|
code-agnostic apply
|
|
215
218
|
```
|
|
216
219
|
|
|
217
|
-
|
|
218
|
-
then use the normal `plan` / `apply` workflow.
|
|
220
|
+
Remote GitHub-style sources are also supported:
|
|
219
221
|
|
|
220
|
-
|
|
222
|
+
```bash
|
|
223
|
+
code-agnostic skills install owner/repo --global
|
|
224
|
+
code-agnostic skills install https://github.com/owner/repo --workspace myworkspace
|
|
225
|
+
code-agnostic skills install https://github.com/owner/repo/tree/main/path/to/skills --skill reviewer --skill triage --project myproject
|
|
226
|
+
```
|
|
221
227
|
|
|
222
|
-
|
|
228
|
+
Remote sources may be an `owner/repo` shorthand, a GitHub repository URL, or a
|
|
229
|
+
GitHub tree URL. When a remote source contains multiple skill candidates,
|
|
230
|
+
repeat `--skill` to select the intended skills; otherwise install fails instead
|
|
231
|
+
of guessing. Remote installs still copy the selected skill into the
|
|
232
|
+
`code-agnostic` source of truth before any generated target outputs are written.
|
|
233
|
+
|
|
234
|
+
Global skills live under `~/.config/code-agnostic/skills`. Workspace-local
|
|
235
|
+
skills live under `~/.config/code-agnostic/workspaces/<name>/skills` and can be
|
|
236
|
+
inspected with `code-agnostic skills list -w <name>`. Project-local skills live
|
|
237
|
+
under `~/.config/code-agnostic/projects/<name>/skills` and are generated into
|
|
238
|
+
the registered project directory by `plan` / `apply`. Codex generated skill
|
|
239
|
+
outputs are written to `~/.agents/skills`, while Codex agents and config remain
|
|
240
|
+
under `CODEX_HOME` when set, defaulting to `~/.codex`. Claude Code generated
|
|
241
|
+
skills and agents are written under `~/.claude/skills` and `~/.claude/agents`,
|
|
242
|
+
with workspace/project copies under repo-local `.claude/skills` and
|
|
243
|
+
`.claude/agents`.
|
|
244
|
+
|
|
245
|
+
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.
|
|
223
246
|
|
|
224
247
|
Planned convenience command:
|
|
225
248
|
|
|
@@ -233,7 +256,7 @@ first implementation slice.
|
|
|
233
256
|
|
|
234
257
|
### Workspaces
|
|
235
258
|
|
|
236
|
-
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
|
|
259
|
+
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.
|
|
237
260
|
|
|
238
261
|
Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
|
|
239
262
|
|
|
@@ -10,6 +10,7 @@ from code_agnostic.cli.commands.explain_lossiness import explain_lossiness
|
|
|
10
10
|
from code_agnostic.cli.commands.import_ import import_group
|
|
11
11
|
from code_agnostic.cli.commands.mcp import mcp
|
|
12
12
|
from code_agnostic.cli.commands.plan import plan
|
|
13
|
+
from code_agnostic.cli.commands.projects import projects
|
|
13
14
|
from code_agnostic.cli.commands.restore import restore
|
|
14
15
|
from code_agnostic.cli.commands.rules import rules
|
|
15
16
|
from code_agnostic.cli.commands.skills import skills
|
|
@@ -40,6 +41,7 @@ cli.add_command(explain_lossiness)
|
|
|
40
41
|
# Register command groups
|
|
41
42
|
cli.add_command(apps)
|
|
42
43
|
cli.add_command(workspaces)
|
|
44
|
+
cli.add_command(projects)
|
|
43
45
|
cli.add_command(rules)
|
|
44
46
|
cli.add_command(skills)
|
|
45
47
|
cli.add_command(agents_group)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Projects group commands."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from code_agnostic.core.project_repository import ProjectConfigRepository
|
|
8
|
+
from code_agnostic.core.repository import CoreRepository
|
|
9
|
+
from code_agnostic.errors import SyncAppError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group(help="Manage project roots for project-local source config.")
|
|
13
|
+
def projects() -> None:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@projects.command("add", help="Add a project by name and path.")
|
|
18
|
+
@click.option("--name", required=True, help="Project name.")
|
|
19
|
+
@click.option(
|
|
20
|
+
"--path",
|
|
21
|
+
required=True,
|
|
22
|
+
type=click.Path(path_type=Path),
|
|
23
|
+
help="Project root path.",
|
|
24
|
+
)
|
|
25
|
+
@click.pass_obj
|
|
26
|
+
def projects_add(obj: dict[str, str], name: str, path: Path) -> None:
|
|
27
|
+
core = CoreRepository()
|
|
28
|
+
try:
|
|
29
|
+
core.add_project(name, path)
|
|
30
|
+
except (ValueError, SyncAppError) as exc:
|
|
31
|
+
raise click.ClickException(str(exc))
|
|
32
|
+
click.echo(f"Project added: {name.strip()}")
|
|
33
|
+
click.echo(str(path.expanduser().resolve()))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@projects.command("remove", help="Unregister a project by name.")
|
|
37
|
+
@click.option("--name", required=True, help="Project name to unregister.")
|
|
38
|
+
@click.pass_obj
|
|
39
|
+
def projects_remove(obj: dict[str, str], name: str) -> None:
|
|
40
|
+
core = CoreRepository()
|
|
41
|
+
try:
|
|
42
|
+
removed = core.remove_project(name)
|
|
43
|
+
except SyncAppError as exc:
|
|
44
|
+
raise click.ClickException(str(exc))
|
|
45
|
+
if not removed:
|
|
46
|
+
raise click.ClickException(f"Project not found: {name}")
|
|
47
|
+
click.echo(f"Project unregistered: {name}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@projects.command("list", help="List configured projects.")
|
|
51
|
+
@click.pass_obj
|
|
52
|
+
def projects_list(obj: dict[str, str]) -> None:
|
|
53
|
+
core = CoreRepository()
|
|
54
|
+
try:
|
|
55
|
+
items = core.load_projects()
|
|
56
|
+
except SyncAppError as exc:
|
|
57
|
+
raise click.ClickException(str(exc))
|
|
58
|
+
|
|
59
|
+
if not items:
|
|
60
|
+
click.echo("No projects configured.")
|
|
61
|
+
click.echo("code-agnostic projects add --name <name> --path <path>")
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
for item in items:
|
|
65
|
+
project_source = ProjectConfigRepository(
|
|
66
|
+
root=core.project_config_dir(item["name"])
|
|
67
|
+
)
|
|
68
|
+
markers = []
|
|
69
|
+
if project_source.has_mcp():
|
|
70
|
+
markers.append("mcp")
|
|
71
|
+
if project_source.has_rules():
|
|
72
|
+
markers.append("rules")
|
|
73
|
+
if project_source.has_skills():
|
|
74
|
+
markers.append("skills")
|
|
75
|
+
if project_source.has_agents():
|
|
76
|
+
markers.append("agents")
|
|
77
|
+
suffix = f" [{', '.join(markers)}]" if markers else ""
|
|
78
|
+
click.echo(f"{item['name']}: {item['path']}{suffix}")
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Skills group commands."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from code_agnostic.cli.helpers import validate_resource_name, workspace_config_root
|
|
10
|
+
from code_agnostic.cli.options import workspace_option
|
|
11
|
+
from code_agnostic.core.repository import CoreRepository
|
|
12
|
+
from code_agnostic.errors import SyncAppError
|
|
13
|
+
from code_agnostic.skills.install_sources import (
|
|
14
|
+
SkillInstallSourceError,
|
|
15
|
+
cleanup_skill_install_resolution,
|
|
16
|
+
resolve_skill_install_source,
|
|
17
|
+
)
|
|
18
|
+
from code_agnostic.tui import SyncConsoleUI
|
|
19
|
+
from code_agnostic.utils import compact_home_path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _entries_containing_cwd(
|
|
23
|
+
entries: list[dict[str, str]], cwd: Path
|
|
24
|
+
) -> list[dict[str, str]]:
|
|
25
|
+
matches = []
|
|
26
|
+
for entry in entries:
|
|
27
|
+
root = Path(entry["path"]).expanduser().resolve()
|
|
28
|
+
if cwd == root or cwd.is_relative_to(root):
|
|
29
|
+
matches.append(entry)
|
|
30
|
+
return matches
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _project_entries_by_name(core: CoreRepository) -> dict[str, dict[str, str]]:
|
|
34
|
+
return {item["name"]: item for item in core.load_projects()}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _install_root(
|
|
38
|
+
core: CoreRepository,
|
|
39
|
+
*,
|
|
40
|
+
global_scope: bool,
|
|
41
|
+
workspace: str | None,
|
|
42
|
+
project: str | None,
|
|
43
|
+
) -> tuple[Path, str, str | None]:
|
|
44
|
+
explicit_count = sum(
|
|
45
|
+
[global_scope, workspace is not None, project is not None],
|
|
46
|
+
)
|
|
47
|
+
if explicit_count > 1:
|
|
48
|
+
raise click.ClickException(
|
|
49
|
+
"Choose only one scope: --global, --workspace, or --project."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
if global_scope:
|
|
54
|
+
return core.root, "global", None
|
|
55
|
+
if workspace is not None:
|
|
56
|
+
return (
|
|
57
|
+
workspace_config_root(core, workspace),
|
|
58
|
+
f"workspace:{workspace}",
|
|
59
|
+
None,
|
|
60
|
+
)
|
|
61
|
+
if project is not None:
|
|
62
|
+
projects = _project_entries_by_name(core)
|
|
63
|
+
if project not in projects:
|
|
64
|
+
raise click.ClickException(f"Project not found: {project}")
|
|
65
|
+
return core.project_config_dir(project), f"project:{project}", None
|
|
66
|
+
|
|
67
|
+
cwd = Path.cwd().resolve()
|
|
68
|
+
project_matches = _entries_containing_cwd(core.load_projects(), cwd)
|
|
69
|
+
if len(project_matches) == 1:
|
|
70
|
+
name = project_matches[0]["name"]
|
|
71
|
+
return core.project_config_dir(name), f"project:{name}", None
|
|
72
|
+
|
|
73
|
+
workspace_matches = _entries_containing_cwd(core.load_workspaces(), cwd)
|
|
74
|
+
if len(project_matches) == 0 and len(workspace_matches) == 1:
|
|
75
|
+
name = workspace_matches[0]["name"]
|
|
76
|
+
return core.workspace_config_dir(name), f"workspace:{name}", None
|
|
77
|
+
except SyncAppError as exc:
|
|
78
|
+
raise click.ClickException(str(exc)) from exc
|
|
79
|
+
|
|
80
|
+
raise click.ClickException(
|
|
81
|
+
"No unique project/workspace scope detected. Use --global, --project, "
|
|
82
|
+
"or --workspace."
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _preflight_skill_destinations(
|
|
87
|
+
source_dirs: tuple[Path, ...], root: Path, scope: str
|
|
88
|
+
) -> list[tuple[str, Path, Path]]:
|
|
89
|
+
installs: list[tuple[str, Path, Path]] = []
|
|
90
|
+
names = _validate_skill_source_names(source_dirs)
|
|
91
|
+
for name, source_dir in zip(names, source_dirs, strict=True):
|
|
92
|
+
destination = root / "skills" / name
|
|
93
|
+
if destination.exists():
|
|
94
|
+
raise click.ClickException(f"Skill already exists: {scope}:{name}")
|
|
95
|
+
installs.append((name, source_dir, destination))
|
|
96
|
+
return installs
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _validate_skill_source_names(source_dirs: tuple[Path, ...]) -> list[str]:
|
|
100
|
+
names: list[str] = []
|
|
101
|
+
seen_names: set[str] = set()
|
|
102
|
+
for source_dir in source_dirs:
|
|
103
|
+
name = source_dir.name
|
|
104
|
+
validate_resource_name(name, "skill")
|
|
105
|
+
if name in seen_names:
|
|
106
|
+
raise click.ClickException(f"Duplicate skill name in source: {name}")
|
|
107
|
+
seen_names.add(name)
|
|
108
|
+
names.append(name)
|
|
109
|
+
return names
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@click.group(
|
|
113
|
+
help=(
|
|
114
|
+
"Manage source skill definitions. Commands use global source by default; "
|
|
115
|
+
"pass -w/--workspace for workspace source."
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
def skills() -> None:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@skills.command("install", help="Install a skill source into source config.")
|
|
123
|
+
@click.argument("source")
|
|
124
|
+
@click.option(
|
|
125
|
+
"--skill",
|
|
126
|
+
"skill_selectors",
|
|
127
|
+
multiple=True,
|
|
128
|
+
help="Select a skill by name or source path; repeat to install multiple.",
|
|
129
|
+
)
|
|
130
|
+
@click.option("--global", "global_scope", is_flag=True, help="Install globally.")
|
|
131
|
+
@workspace_option()
|
|
132
|
+
@click.option("--project", help="Install into a registered project source.")
|
|
133
|
+
@click.pass_obj
|
|
134
|
+
def skills_install(
|
|
135
|
+
obj: dict[str, str],
|
|
136
|
+
source: str,
|
|
137
|
+
skill_selectors: tuple[str, ...],
|
|
138
|
+
global_scope: bool,
|
|
139
|
+
workspace: str | None,
|
|
140
|
+
project: str | None,
|
|
141
|
+
) -> None:
|
|
142
|
+
resolution = None
|
|
143
|
+
source_path = Path(source).expanduser()
|
|
144
|
+
if source_path.exists():
|
|
145
|
+
try:
|
|
146
|
+
resolution = resolve_skill_install_source(
|
|
147
|
+
source,
|
|
148
|
+
skill_selectors=skill_selectors,
|
|
149
|
+
)
|
|
150
|
+
_validate_skill_source_names(resolution.skill_dirs)
|
|
151
|
+
except SkillInstallSourceError as exc:
|
|
152
|
+
raise click.ClickException(str(exc)) from exc
|
|
153
|
+
|
|
154
|
+
core = CoreRepository()
|
|
155
|
+
root, scope, scope_note = _install_root(
|
|
156
|
+
core,
|
|
157
|
+
global_scope=global_scope,
|
|
158
|
+
workspace=workspace,
|
|
159
|
+
project=project,
|
|
160
|
+
)
|
|
161
|
+
try:
|
|
162
|
+
if resolution is None:
|
|
163
|
+
resolution = resolve_skill_install_source(
|
|
164
|
+
source,
|
|
165
|
+
skill_selectors=skill_selectors,
|
|
166
|
+
)
|
|
167
|
+
installs = _preflight_skill_destinations(resolution.skill_dirs, root, scope)
|
|
168
|
+
for _name, _source_dir, destination in installs:
|
|
169
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
170
|
+
for name, source_dir, destination in installs:
|
|
171
|
+
shutil.copytree(source_dir, destination)
|
|
172
|
+
if scope_note is not None:
|
|
173
|
+
click.echo(scope_note)
|
|
174
|
+
scope_note = None
|
|
175
|
+
click.echo(f"Installed {scope} skill: {name}")
|
|
176
|
+
click.echo(f"Source: {compact_home_path(source_dir)}")
|
|
177
|
+
click.echo(f"Destination: {compact_home_path(destination)}")
|
|
178
|
+
except SkillInstallSourceError as exc:
|
|
179
|
+
raise click.ClickException(str(exc)) from exc
|
|
180
|
+
finally:
|
|
181
|
+
if resolution is not None:
|
|
182
|
+
cleanup_skill_install_resolution(resolution)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@skills.command("list", help="List global skills, or workspace skills with -w.")
|
|
186
|
+
@workspace_option()
|
|
187
|
+
@click.pass_obj
|
|
188
|
+
def skills_list(obj: dict[str, str], workspace: str | None) -> None:
|
|
189
|
+
ui = SyncConsoleUI(Console())
|
|
190
|
+
core = CoreRepository()
|
|
191
|
+
root = workspace_config_root(core, workspace)
|
|
192
|
+
skill_sources = CoreRepository(root).list_skill_sources()
|
|
193
|
+
scope = f"workspace:{workspace}" if workspace else "global"
|
|
194
|
+
rows = [
|
|
195
|
+
[
|
|
196
|
+
source.name,
|
|
197
|
+
scope,
|
|
198
|
+
"bundle" if (source / "meta.yaml").exists() else "legacy",
|
|
199
|
+
compact_home_path(source),
|
|
200
|
+
]
|
|
201
|
+
for source in skill_sources
|
|
202
|
+
]
|
|
203
|
+
skill_dir = compact_home_path(root / "skills")
|
|
204
|
+
scope_message = (
|
|
205
|
+
f"No workspace skills configured for {workspace}"
|
|
206
|
+
if workspace
|
|
207
|
+
else "No global skills configured"
|
|
208
|
+
)
|
|
209
|
+
empty_message = (
|
|
210
|
+
f"{scope_message} in {skill_dir}.\n"
|
|
211
|
+
f"- Copy a skill into {skill_dir}/<name>\n"
|
|
212
|
+
"- code-agnostic plan\n"
|
|
213
|
+
"- code-agnostic apply"
|
|
214
|
+
)
|
|
215
|
+
ui.render_list(
|
|
216
|
+
"skills",
|
|
217
|
+
["Skill", "Scope", "Format", "Source"],
|
|
218
|
+
rows,
|
|
219
|
+
empty_message,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@skills.command("remove", help="Remove a global skill, or a workspace skill with -w.")
|
|
224
|
+
@click.option("--name", required=True, help="Skill name to remove.")
|
|
225
|
+
@workspace_option()
|
|
226
|
+
@click.pass_obj
|
|
227
|
+
def skills_remove(obj: dict[str, str], name: str, workspace: str | None) -> None:
|
|
228
|
+
validate_resource_name(name, "skill")
|
|
229
|
+
core = CoreRepository()
|
|
230
|
+
root = workspace_config_root(core, workspace)
|
|
231
|
+
skill_dir = root / "skills" / name
|
|
232
|
+
if not skill_dir.exists():
|
|
233
|
+
raise click.ClickException(f"Skill not found: {name}")
|
|
234
|
+
shutil.rmtree(skill_dir)
|
|
235
|
+
scope = f"workspace:{workspace}" if workspace else "global"
|
|
236
|
+
click.echo(f"Removed {scope} skill: {name}")
|
|
@@ -11,6 +11,8 @@ from code_agnostic.errors import SyncAppError
|
|
|
11
11
|
from code_agnostic.models import (
|
|
12
12
|
EditorStatusRow,
|
|
13
13
|
EditorSyncStatus,
|
|
14
|
+
ProjectStatusRow,
|
|
15
|
+
ProjectSyncStatus,
|
|
14
16
|
WorkspaceStatusRow,
|
|
15
17
|
WorkspaceSyncStatus,
|
|
16
18
|
)
|
|
@@ -70,12 +72,28 @@ def status(obj: dict[str, str], app: str, verbose: bool) -> None:
|
|
|
70
72
|
repos=[],
|
|
71
73
|
)
|
|
72
74
|
]
|
|
75
|
+
try:
|
|
76
|
+
project_rows = status_service.build_project_status(
|
|
77
|
+
core, app_services=enabled_services
|
|
78
|
+
)
|
|
79
|
+
except SyncAppError as exc:
|
|
80
|
+
project_rows = [
|
|
81
|
+
ProjectStatusRow(
|
|
82
|
+
name="projects",
|
|
83
|
+
path=str(core.projects_path),
|
|
84
|
+
status=ProjectSyncStatus.ERROR,
|
|
85
|
+
detail=str(exc),
|
|
86
|
+
)
|
|
87
|
+
]
|
|
73
88
|
ui.render_status(
|
|
74
89
|
editor_rows,
|
|
75
90
|
workspace_rows,
|
|
91
|
+
project_rows,
|
|
76
92
|
)
|
|
77
93
|
|
|
78
|
-
if
|
|
79
|
-
row.status ==
|
|
94
|
+
if (
|
|
95
|
+
any(row.status == EditorSyncStatus.ERROR for row in editor_rows)
|
|
96
|
+
or any(row.status == WorkspaceSyncStatus.ERROR for row in workspace_rows)
|
|
97
|
+
or any(row.status == ProjectSyncStatus.ERROR for row in project_rows)
|
|
80
98
|
):
|
|
81
99
|
raise click.exceptions.Exit(1)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from code_agnostic.core.workspace_repository import WorkspaceConfigRepository
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ProjectConfigRepository(WorkspaceConfigRepository):
|
|
7
|
+
"""Source repository for project-level config."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, root: Path) -> None:
|
|
10
|
+
super().__init__(root)
|