code-agnostic 0.3.14__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.14 → code_agnostic-0.3.15}/PKG-INFO +8 -3
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/README.md +7 -2
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/__init__.py +1 -1
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/claude/mapper.py +5 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/codex/mapper.py +3 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/codex/schema.json +40 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/common/models.py +2 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/common/utils.py +8 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/cursor/mapper.py +6 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/cursor/schema.json +2 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/opencode/mapper.py +5 -0
- {code_agnostic-0.3.14 → 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.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/import_.py +10 -2
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/mcp.py +3 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/restore.py +10 -5
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/skills.py +12 -5
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/helpers.py +14 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/options.py +9 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/core/repository.py +24 -2
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/executor.py +21 -6
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/imports/service.py +1 -1
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/lossiness.py +47 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/mcp_service.py +5 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/spec/loaders.py +2 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/spec/schemas/mcp.base.schema.json +22 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/spec/schemas/mcp.v1.schema.json +9 -1
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/tui/renderers.py +9 -1
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic.egg-info/PKG-INFO +8 -3
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/pyproject.toml +1 -1
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_apply_cursor.py +1 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_apps.py +32 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_explain_lossiness.py +22 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_import.py +1 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_import_interactive.py +2 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_mcp.py +42 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_projects.py +26 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_restore.py +58 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_skills.py +5 -5
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_workspaces.py +26 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_common_mcp_to_dto.py +12 -1
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_common_repository.py +2 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_dto_to_common_mcp.py +4 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_mcp_service.py +20 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_transactional_executor.py +80 -0
- code_agnostic-0.3.14/code_agnostic/cli/commands/apply.py +0 -42
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/LICENSE +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/__main__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/agents/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/agents/claude.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/agents/codex.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/agents/compilers.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/agents/models.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/agents/opencode.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/agents/parser.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/app_id.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/apps_service.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/claude/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/claude/config_repository.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/claude/service.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/codex/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/codex/config_repository.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/codex/schema_repository.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/codex/service.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/common/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/common/compiled_planning.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/common/framework.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/common/interfaces/service.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/common/loader.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/common/schema.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/common/symlink_planning.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/cursor/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/cursor/config_repository.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/cursor/schema_repository.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/cursor/service.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/opencode/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/opencode/config_repository.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/opencode/schema_repository.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/apps/opencode/service.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/aliases.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/agents.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/apps.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/plan.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/projects.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/rules.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/status.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/validate.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/cli/commands/workspaces.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/constants.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/core/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/core/project_repository.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/core/workspace_repository.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/errors.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/generated_artifacts.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/git_exclude_service.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/imports/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/imports/adapters.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/imports/filesystem.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/imports/models.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/models.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/planner.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/project_artifacts.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/rules/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/rules/compilers.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/rules/models.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/rules/parser.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/rules/repository.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/skills/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/skills/compilers.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/skills/install_sources.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/skills/models.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/skills/parser.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/spec/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/status.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/tui/__init__.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/tui/enums.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/tui/import_selector.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/tui/sections.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/tui/tables.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/utils.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/validation.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/workspace_artifacts.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic/workspaces.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic.egg-info/SOURCES.txt +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic.egg-info/dependency_links.txt +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic.egg-info/entry_points.txt +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic.egg-info/requires.txt +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/code_agnostic.egg-info/top_level.txt +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/setup.cfg +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_agents.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_aliases.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_apply_apps.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_apply_codex.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_apply_target.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_flags.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_git_exclude.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_module_organization.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_plan.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_rules.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_status.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_validate.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_cli_workspace_resolution.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_compiled_planning.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_git_exclude_service.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_planner_executor.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_planner_rules.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_project_config_sync.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_skill_install_sources.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_symlink_planning.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_sync_plan_model.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_utils.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_version.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_workspace_config_sync.py +0 -0
- {code_agnostic-0.3.14 → code_agnostic-0.3.15}/tests/test_workspace_repo_status.py +0 -0
- {code_agnostic-0.3.14 → 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
|
|
|
@@ -317,7 +321,8 @@ code-agnostic import plan -a codex -i # interactive TUI picker
|
|
|
317
321
|
only the selected sections. Conflicts are skipped by default, so rerun with
|
|
318
322
|
`--on-conflict overwrite` only after reviewing the preview. Use `--include`,
|
|
319
323
|
`--exclude`, `--source-root`, and `--follow-symlinks` to narrow what gets
|
|
320
|
-
imported.
|
|
324
|
+
imported. `--source-root` means the source app config root to read from, not the
|
|
325
|
+
`code-agnostic` hub root.
|
|
321
326
|
|
|
322
327
|
### CLI conventions
|
|
323
328
|
|
|
@@ -349,7 +354,7 @@ The compiler migration is documented in:
|
|
|
349
354
|
- [x] Per-workspace git-exclude customization
|
|
350
355
|
- [x] Interactive TUI for import selection
|
|
351
356
|
- [x] Claude Code support
|
|
352
|
-
- [
|
|
357
|
+
- [x] Project-scoped skill installs and sync
|
|
353
358
|
- [ ] `rules add` / `skills add` / `agents add` commands (open `$EDITOR` with template)
|
|
354
359
|
- [ ] Shell auto-complete
|
|
355
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
|
|
|
@@ -292,7 +296,8 @@ code-agnostic import plan -a codex -i # interactive TUI picker
|
|
|
292
296
|
only the selected sections. Conflicts are skipped by default, so rerun with
|
|
293
297
|
`--on-conflict overwrite` only after reviewing the preview. Use `--include`,
|
|
294
298
|
`--exclude`, `--source-root`, and `--follow-symlinks` to narrow what gets
|
|
295
|
-
imported.
|
|
299
|
+
imported. `--source-root` means the source app config root to read from, not the
|
|
300
|
+
`code-agnostic` hub root.
|
|
296
301
|
|
|
297
302
|
### CLI conventions
|
|
298
303
|
|
|
@@ -324,7 +329,7 @@ The compiler migration is documented in:
|
|
|
324
329
|
- [x] Per-workspace git-exclude customization
|
|
325
330
|
- [x] Interactive TUI for import selection
|
|
326
331
|
- [x] Claude Code support
|
|
327
|
-
- [
|
|
332
|
+
- [x] Project-scoped skill installs and sync
|
|
328
333
|
- [ ] `rules add` / `skills add` / `agents add` commands (open `$EDITOR` with template)
|
|
329
334
|
- [ ] Shell auto-complete
|
|
330
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
|
|
|
@@ -79,7 +79,8 @@ def _install_root(
|
|
|
79
79
|
|
|
80
80
|
raise click.ClickException(
|
|
81
81
|
"No unique project/workspace scope detected. Use --global, --project, "
|
|
82
|
-
"or --workspace."
|
|
82
|
+
"or --workspace. To use this directory as a project, run: "
|
|
83
|
+
"code-agnostic projects add --name <name> --path ."
|
|
83
84
|
)
|
|
84
85
|
|
|
85
86
|
|
|
@@ -111,15 +112,14 @@ def _validate_skill_source_names(source_dirs: tuple[Path, ...]) -> list[str]:
|
|
|
111
112
|
|
|
112
113
|
@click.group(
|
|
113
114
|
help=(
|
|
114
|
-
"Manage source skill definitions
|
|
115
|
-
"pass -w/--workspace for workspace source."
|
|
115
|
+
"Manage source skill definitions across global, workspace, and project scopes."
|
|
116
116
|
)
|
|
117
117
|
)
|
|
118
118
|
def skills() -> None:
|
|
119
119
|
pass
|
|
120
120
|
|
|
121
121
|
|
|
122
|
-
@skills.command("install", help="Install a skill source into source config.")
|
|
122
|
+
@skills.command("install", help="Install a skill source into managed source config.")
|
|
123
123
|
@click.argument("source")
|
|
124
124
|
@click.option(
|
|
125
125
|
"--skill",
|
|
@@ -167,14 +167,20 @@ def skills_install(
|
|
|
167
167
|
installs = _preflight_skill_destinations(resolution.skill_dirs, root, scope)
|
|
168
168
|
for _name, _source_dir, destination in installs:
|
|
169
169
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
170
|
+
installed = False
|
|
170
171
|
for name, source_dir, destination in installs:
|
|
171
172
|
shutil.copytree(source_dir, destination)
|
|
173
|
+
installed = True
|
|
172
174
|
if scope_note is not None:
|
|
173
175
|
click.echo(scope_note)
|
|
174
176
|
scope_note = None
|
|
175
177
|
click.echo(f"Installed {scope} skill: {name}")
|
|
176
178
|
click.echo(f"Source: {compact_home_path(source_dir)}")
|
|
177
179
|
click.echo(f"Destination: {compact_home_path(destination)}")
|
|
180
|
+
if installed:
|
|
181
|
+
click.echo("Next:")
|
|
182
|
+
click.echo(" code-agnostic plan")
|
|
183
|
+
click.echo(" code-agnostic apply")
|
|
178
184
|
except SkillInstallSourceError as exc:
|
|
179
185
|
raise click.ClickException(str(exc)) from exc
|
|
180
186
|
finally:
|
|
@@ -206,9 +212,10 @@ def skills_list(obj: dict[str, str], workspace: str | None) -> None:
|
|
|
206
212
|
if workspace
|
|
207
213
|
else "No global skills configured"
|
|
208
214
|
)
|
|
215
|
+
install_scope = f"--workspace {workspace}" if workspace else "--global"
|
|
209
216
|
empty_message = (
|
|
210
217
|
f"{scope_message} in {skill_dir}.\n"
|
|
211
|
-
f"-
|
|
218
|
+
f"- code-agnostic skills install <source> {install_scope}\n"
|
|
212
219
|
"- code-agnostic plan\n"
|
|
213
220
|
"- code-agnostic apply"
|
|
214
221
|
)
|
|
@@ -14,6 +14,10 @@ def _workspace_entries_by_name(core: CoreRepository) -> dict[str, dict[str, str]
|
|
|
14
14
|
return {item["name"]: item for item in core.load_workspaces()}
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def _project_entries_by_name(core: CoreRepository) -> dict[str, dict[str, str]]:
|
|
18
|
+
return {item["name"]: item for item in core.load_projects()}
|
|
19
|
+
|
|
20
|
+
|
|
17
21
|
def require_workspace_entry(core: CoreRepository, workspace: str) -> dict[str, str]:
|
|
18
22
|
try:
|
|
19
23
|
entry = _workspace_entries_by_name(core).get(workspace)
|
|
@@ -24,6 +28,16 @@ def require_workspace_entry(core: CoreRepository, workspace: str) -> dict[str, s
|
|
|
24
28
|
return entry
|
|
25
29
|
|
|
26
30
|
|
|
31
|
+
def require_project_entry(core: CoreRepository, project: str) -> dict[str, str]:
|
|
32
|
+
try:
|
|
33
|
+
entry = _project_entries_by_name(core).get(project)
|
|
34
|
+
except SyncAppError as exc:
|
|
35
|
+
raise click.ClickException(str(exc)) from exc
|
|
36
|
+
if entry is None:
|
|
37
|
+
raise click.ClickException(f"Project not found: {project}")
|
|
38
|
+
return entry
|
|
39
|
+
|
|
40
|
+
|
|
27
41
|
def workspace_config_root(core: CoreRepository, workspace: str | None) -> Path:
|
|
28
42
|
if workspace is None:
|
|
29
43
|
return core.root
|
|
@@ -79,5 +79,14 @@ def workspace_option(required: bool = False) -> Callable:
|
|
|
79
79
|
)
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def project_option(required: bool = False) -> Callable:
|
|
83
|
+
return click.option(
|
|
84
|
+
"--project",
|
|
85
|
+
required=required,
|
|
86
|
+
default=None,
|
|
87
|
+
help="Project name.",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
82
91
|
def verbose_option() -> Callable:
|
|
83
92
|
return click.option("-v", "--verbose", is_flag=True, default=False)
|