code-agnostic 0.3.8__tar.gz → 0.3.10__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.
Files changed (157) hide show
  1. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/PKG-INFO +55 -10
  2. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/README.md +54 -9
  3. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/__init__.py +1 -1
  4. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/__main__.py +4 -0
  5. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/codex/schema.json +44 -17
  6. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/opencode/schema.json +5 -11
  7. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/mcp.py +29 -10
  8. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/skills.py +11 -4
  9. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/status.py +21 -4
  10. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/workspaces.py +26 -15
  11. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/helpers.py +5 -1
  12. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/core/repository.py +4 -2
  13. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/executor.py +10 -0
  14. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/imports/service.py +5 -1
  15. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/lossiness.py +30 -0
  16. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/mcp_service.py +4 -2
  17. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/status.py +69 -10
  18. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/tui/renderers.py +73 -2
  19. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/tui/tables.py +1 -1
  20. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/utils.py +2 -2
  21. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic.egg-info/PKG-INFO +55 -10
  22. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/pyproject.toml +1 -1
  23. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_aliases.py +1 -1
  24. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_explain_lossiness.py +32 -0
  25. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_flags.py +1 -1
  26. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_import.py +25 -0
  27. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_mcp.py +7 -4
  28. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_skills.py +1 -1
  29. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_status.py +59 -0
  30. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_workspaces.py +44 -1
  31. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_common_repository.py +20 -0
  32. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_transactional_executor.py +6 -3
  33. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_utils.py +45 -0
  34. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_workspace_config_sync.py +19 -11
  35. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/LICENSE +0 -0
  36. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/agents/__init__.py +0 -0
  37. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/agents/claude.py +0 -0
  38. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/agents/codex.py +0 -0
  39. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/agents/compilers.py +0 -0
  40. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/agents/models.py +0 -0
  41. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/agents/opencode.py +0 -0
  42. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/agents/parser.py +0 -0
  43. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/__init__.py +0 -0
  44. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/app_id.py +0 -0
  45. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/apps_service.py +0 -0
  46. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/claude/__init__.py +0 -0
  47. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/claude/config_repository.py +0 -0
  48. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/claude/mapper.py +0 -0
  49. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/claude/service.py +0 -0
  50. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/codex/__init__.py +0 -0
  51. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/codex/config_repository.py +0 -0
  52. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/codex/mapper.py +0 -0
  53. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/codex/schema_repository.py +0 -0
  54. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/codex/service.py +0 -0
  55. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/common/__init__.py +0 -0
  56. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/common/compiled_planning.py +0 -0
  57. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/common/framework.py +0 -0
  58. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/common/interfaces/__init__.py +0 -0
  59. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/common/interfaces/mapper.py +0 -0
  60. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/common/interfaces/repositories.py +0 -0
  61. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/common/interfaces/service.py +0 -0
  62. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/common/loader.py +0 -0
  63. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/common/models.py +0 -0
  64. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/common/schema.py +0 -0
  65. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/common/symlink_planning.py +0 -0
  66. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/common/utils.py +0 -0
  67. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/cursor/__init__.py +0 -0
  68. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/cursor/config_repository.py +0 -0
  69. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/cursor/mapper.py +0 -0
  70. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/cursor/schema.json +0 -0
  71. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/cursor/schema_repository.py +0 -0
  72. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/cursor/service.py +0 -0
  73. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/opencode/__init__.py +0 -0
  74. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/opencode/config_repository.py +0 -0
  75. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/opencode/mapper.py +0 -0
  76. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/opencode/schema_repository.py +0 -0
  77. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/apps/opencode/service.py +0 -0
  78. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/__init__.py +0 -0
  79. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/aliases.py +0 -0
  80. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/__init__.py +0 -0
  81. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/agents.py +0 -0
  82. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/apply.py +0 -0
  83. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/apps.py +0 -0
  84. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/explain_lossiness.py +0 -0
  85. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/import_.py +0 -0
  86. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/plan.py +0 -0
  87. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/restore.py +0 -0
  88. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/rules.py +0 -0
  89. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/commands/validate.py +0 -0
  90. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/cli/options.py +0 -0
  91. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/constants.py +0 -0
  92. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/core/__init__.py +0 -0
  93. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/core/workspace_repository.py +0 -0
  94. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/errors.py +0 -0
  95. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/generated_artifacts.py +0 -0
  96. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/git_exclude_service.py +0 -0
  97. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/imports/__init__.py +0 -0
  98. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/imports/adapters.py +0 -0
  99. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/imports/filesystem.py +0 -0
  100. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/imports/models.py +0 -0
  101. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/models.py +0 -0
  102. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/planner.py +0 -0
  103. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/rules/__init__.py +0 -0
  104. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/rules/compilers.py +0 -0
  105. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/rules/models.py +0 -0
  106. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/rules/parser.py +0 -0
  107. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/rules/repository.py +0 -0
  108. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/skills/__init__.py +0 -0
  109. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/skills/compilers.py +0 -0
  110. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/skills/models.py +0 -0
  111. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/skills/parser.py +0 -0
  112. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/spec/__init__.py +0 -0
  113. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/spec/loaders.py +0 -0
  114. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/spec/schemas/agent.v1.schema.json +0 -0
  115. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/spec/schemas/mcp.base.schema.json +0 -0
  116. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/spec/schemas/mcp.v1.schema.json +0 -0
  117. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/spec/schemas/rule.v1.schema.json +0 -0
  118. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/spec/schemas/skill.v1.schema.json +0 -0
  119. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/tui/__init__.py +0 -0
  120. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/tui/enums.py +0 -0
  121. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/tui/import_selector.py +0 -0
  122. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/tui/sections.py +0 -0
  123. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/validation.py +0 -0
  124. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/workspace_artifacts.py +0 -0
  125. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic/workspaces.py +0 -0
  126. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic.egg-info/SOURCES.txt +0 -0
  127. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic.egg-info/dependency_links.txt +0 -0
  128. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic.egg-info/entry_points.txt +0 -0
  129. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic.egg-info/requires.txt +0 -0
  130. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/code_agnostic.egg-info/top_level.txt +0 -0
  131. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/setup.cfg +0 -0
  132. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_agents.py +0 -0
  133. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_apply_apps.py +0 -0
  134. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_apply_codex.py +0 -0
  135. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_apply_cursor.py +0 -0
  136. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_apply_target.py +0 -0
  137. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_apps.py +0 -0
  138. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_git_exclude.py +0 -0
  139. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_import_interactive.py +0 -0
  140. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_module_organization.py +0 -0
  141. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_plan.py +0 -0
  142. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_restore.py +0 -0
  143. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_rules.py +0 -0
  144. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_validate.py +0 -0
  145. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_cli_workspace_resolution.py +0 -0
  146. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_common_mcp_to_dto.py +0 -0
  147. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_compiled_planning.py +0 -0
  148. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_dto_to_common_mcp.py +0 -0
  149. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_git_exclude_service.py +0 -0
  150. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_mcp_service.py +0 -0
  151. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_planner_executor.py +0 -0
  152. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_planner_rules.py +0 -0
  153. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_symlink_planning.py +0 -0
  154. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_sync_plan_model.py +0 -0
  155. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_version.py +0 -0
  156. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/tests/test_workspace_repo_status.py +0 -0
  157. {code_agnostic-0.3.8 → code_agnostic-0.3.10}/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.8
3
+ Version: 0.3.10
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
@@ -64,6 +64,21 @@ Legacy single-file rules, `skills/<name>/SKILL.md`, and markdown agents are stil
64
64
 
65
65
  Today the implementation is still mixed: some assets are compiled and some are symlinked. The active migration plan is to move to generated outputs everywhere with a strict compiler contract instead of implicit per-app behavior.
66
66
 
67
+ ## Scope model
68
+
69
+ `code-agnostic` has two managed source scopes today:
70
+
71
+ - global source config under `~/.config/code-agnostic/`, synced to enabled
72
+ user-level app config;
73
+ - workspace source config under `~/.config/code-agnostic/workspaces/<name>/`,
74
+ propagated into repos inside a registered workspace.
75
+
76
+ Project-local skill folders that users create directly inside a repo, such as
77
+ `.agents/skills` or `.opencode/skills`, are app-native inputs but are not
78
+ managed as source by `code-agnostic` yet. First-class project-scoped installs
79
+ are planned so a single registered project can have managed local source config
80
+ without bypassing the hub.
81
+
67
82
  ## Install
68
83
 
69
84
  ```bash
@@ -121,6 +136,10 @@ code-agnostic apply
121
136
  | Import from | yes | yes | yes | yes |
122
137
  | Interactive import (TUI) | yes | yes | yes | yes |
123
138
 
139
+ `yes` means the resource type is synced for that editor. Some metadata is still
140
+ target-specific or lossy; run `code-agnostic explain-lossiness` to see fields
141
+ that are omitted or rejected for a selected target.
142
+
124
143
  Cursor workspace propagation writes repo-local MCP, skills, and agents when those resources exist in the workspace source config.
125
144
 
126
145
  OpenCode workspace configs include the shared workspace `AGENTS.md` natively via `instructions`, so repos under the workspace get both repo-local and shared workspace instructions. Codex repos receive workspace instructions through a generated `AGENTS.override.md`, which is added to each repo's `.git/info/exclude`. Claude Code receives workspace instructions through generated `CLAUDE.local.md` files, never by editing committed `CLAUDE.md`.
@@ -137,10 +156,15 @@ Plan-then-apply workflow. Preview every change before it touches disk.
137
156
  code-agnostic validate # check canonical source files
138
157
  code-agnostic plan -a cursor # dry-run for one editor
139
158
  code-agnostic plan # dry-run for all
140
- code-agnostic apply # apply changes
141
- code-agnostic status # check drift
159
+ code-agnostic apply # apply changes for all enabled editors
160
+ code-agnostic status # check drift and disabled app states
161
+ code-agnostic explain-lossiness # show fields omitted or rejected per editor
142
162
  ```
143
163
 
164
+ Bare `plan` and `apply` target every enabled editor; bare `status` also shows
165
+ disabled app states. Use `-a codex`, `-a cursor`, `-a opencode`, or `-a claude`
166
+ when you want one editor at a time.
167
+
144
168
  If managed outputs need repair after an apply, restore the active synced revision:
145
169
 
146
170
  ```bash
@@ -162,19 +186,31 @@ Env vars without a value (`--env GITHUB_TOKEN`) are stored as `${GITHUB_TOKEN}`
162
186
 
163
187
  ### Rules with metadata
164
188
 
165
- Rules live in `rules/` as markdown files with optional YAML frontmatter:
189
+ New rules should use bundle directories with schema-validated metadata and a
190
+ separate prompt body:
166
191
 
167
- ```markdown
168
- ---
192
+ ```text
193
+ rules/python-style/
194
+ ├── meta.yaml
195
+ └── prompt.md
196
+ ```
197
+
198
+ ```yaml
199
+ # rules/python-style/meta.yaml
200
+ spec_version: v1
201
+ kind: rule
169
202
  description: "Python coding standards"
170
203
  globs: ["*.py"]
171
204
  always_apply: false
172
- ---
205
+ ```
173
206
 
207
+ ```markdown
208
+ <!-- rules/python-style/prompt.md -->
174
209
  Always use type hints. Prefer dataclasses over dicts.
175
210
  ```
176
211
 
177
212
  Cross-compiled per editor: Cursor gets `.mdc` files with native frontmatter, OpenCode/Codex get `AGENTS.md` sections.
213
+ Legacy single-file rule markdown with YAML frontmatter remains supported for migration.
178
214
 
179
215
  ```bash
180
216
  code-agnostic rules list
@@ -183,7 +219,10 @@ code-agnostic rules remove --name python-style
183
219
 
184
220
  ### Skills and agents
185
221
 
186
- Canonical YAML frontmatter format, cross-compiled per editor. Install or edit skills in the `code-agnostic` source of truth, then run `plan` / `apply`; do not hand-copy generated skills into `.codex`, `.cursor`, or OpenCode directories.
222
+ Use bundle directories for new skills and agents, then let `code-agnostic`
223
+ cross-compile them per editor. Install or edit skills in the `code-agnostic`
224
+ source of truth, then run `plan` / `apply`; do not hand-copy generated skills
225
+ into `.codex`, `.cursor`, `.agents`, or OpenCode directories.
187
226
 
188
227
  ```bash
189
228
  code-agnostic skills list
@@ -213,7 +252,7 @@ That command should copy the skill into the source of truth and then run the nor
213
252
 
214
253
  ### Workspaces
215
254
 
216
- Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Repo-local app config, skills, and agents are propagated for OpenCode, Cursor, Codex, and Claude.
255
+ Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, and Claude; user-created project-local skill folders remain unmanaged until project-scoped installs are supported.
217
256
 
218
257
  Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
219
258
 
@@ -245,6 +284,12 @@ code-agnostic import apply -a cursor --include mcp --on-conflict overwrite
245
284
  code-agnostic import plan -a codex -i # interactive TUI picker
246
285
  ```
247
286
 
287
+ `import plan` previews what will be copied into the hub; `import apply` writes
288
+ only the selected sections. Conflicts are skipped by default, so rerun with
289
+ `--on-conflict overwrite` only after reviewing the preview. Use `--include`,
290
+ `--exclude`, `--source-root`, and `--follow-symlinks` to narrow what gets
291
+ imported.
292
+
248
293
  ### CLI conventions
249
294
 
250
295
  All commands use named flags (`-a`, `-w`, `-v`). Singular aliases work too: `app` = `apps`, `workspace` = `workspaces`.
@@ -264,7 +309,7 @@ The compiler migration is documented in:
264
309
 
265
310
  - [x] Plan/apply/status sync engine
266
311
  - [x] MCP server sync across editors
267
- - [x] Skills and agents sync (symlink-based)
312
+ - [x] Skills and agents sync across editors
268
313
  - [x] Workspace propagation into git repos
269
314
  - [x] Import from existing editor configs
270
315
  - [x] Consistent CLI with named flags and aliases
@@ -39,6 +39,21 @@ Legacy single-file rules, `skills/<name>/SKILL.md`, and markdown agents are stil
39
39
 
40
40
  Today the implementation is still mixed: some assets are compiled and some are symlinked. The active migration plan is to move to generated outputs everywhere with a strict compiler contract instead of implicit per-app behavior.
41
41
 
42
+ ## Scope model
43
+
44
+ `code-agnostic` has two managed source scopes today:
45
+
46
+ - global source config under `~/.config/code-agnostic/`, synced to enabled
47
+ user-level app config;
48
+ - workspace source config under `~/.config/code-agnostic/workspaces/<name>/`,
49
+ propagated into repos inside a registered workspace.
50
+
51
+ Project-local skill folders that users create directly inside a repo, such as
52
+ `.agents/skills` or `.opencode/skills`, are app-native inputs but are not
53
+ managed as source by `code-agnostic` yet. First-class project-scoped installs
54
+ are planned so a single registered project can have managed local source config
55
+ without bypassing the hub.
56
+
42
57
  ## Install
43
58
 
44
59
  ```bash
@@ -96,6 +111,10 @@ code-agnostic apply
96
111
  | Import from | yes | yes | yes | yes |
97
112
  | Interactive import (TUI) | yes | yes | yes | yes |
98
113
 
114
+ `yes` means the resource type is synced for that editor. Some metadata is still
115
+ target-specific or lossy; run `code-agnostic explain-lossiness` to see fields
116
+ that are omitted or rejected for a selected target.
117
+
99
118
  Cursor workspace propagation writes repo-local MCP, skills, and agents when those resources exist in the workspace source config.
100
119
 
101
120
  OpenCode workspace configs include the shared workspace `AGENTS.md` natively via `instructions`, so repos under the workspace get both repo-local and shared workspace instructions. Codex repos receive workspace instructions through a generated `AGENTS.override.md`, which is added to each repo's `.git/info/exclude`. Claude Code receives workspace instructions through generated `CLAUDE.local.md` files, never by editing committed `CLAUDE.md`.
@@ -112,10 +131,15 @@ Plan-then-apply workflow. Preview every change before it touches disk.
112
131
  code-agnostic validate # check canonical source files
113
132
  code-agnostic plan -a cursor # dry-run for one editor
114
133
  code-agnostic plan # dry-run for all
115
- code-agnostic apply # apply changes
116
- code-agnostic status # check drift
134
+ code-agnostic apply # apply changes for all enabled editors
135
+ code-agnostic status # check drift and disabled app states
136
+ code-agnostic explain-lossiness # show fields omitted or rejected per editor
117
137
  ```
118
138
 
139
+ Bare `plan` and `apply` target every enabled editor; bare `status` also shows
140
+ disabled app states. Use `-a codex`, `-a cursor`, `-a opencode`, or `-a claude`
141
+ when you want one editor at a time.
142
+
119
143
  If managed outputs need repair after an apply, restore the active synced revision:
120
144
 
121
145
  ```bash
@@ -137,19 +161,31 @@ Env vars without a value (`--env GITHUB_TOKEN`) are stored as `${GITHUB_TOKEN}`
137
161
 
138
162
  ### Rules with metadata
139
163
 
140
- Rules live in `rules/` as markdown files with optional YAML frontmatter:
164
+ New rules should use bundle directories with schema-validated metadata and a
165
+ separate prompt body:
141
166
 
142
- ```markdown
143
- ---
167
+ ```text
168
+ rules/python-style/
169
+ ├── meta.yaml
170
+ └── prompt.md
171
+ ```
172
+
173
+ ```yaml
174
+ # rules/python-style/meta.yaml
175
+ spec_version: v1
176
+ kind: rule
144
177
  description: "Python coding standards"
145
178
  globs: ["*.py"]
146
179
  always_apply: false
147
- ---
180
+ ```
148
181
 
182
+ ```markdown
183
+ <!-- rules/python-style/prompt.md -->
149
184
  Always use type hints. Prefer dataclasses over dicts.
150
185
  ```
151
186
 
152
187
  Cross-compiled per editor: Cursor gets `.mdc` files with native frontmatter, OpenCode/Codex get `AGENTS.md` sections.
188
+ Legacy single-file rule markdown with YAML frontmatter remains supported for migration.
153
189
 
154
190
  ```bash
155
191
  code-agnostic rules list
@@ -158,7 +194,10 @@ code-agnostic rules remove --name python-style
158
194
 
159
195
  ### Skills and agents
160
196
 
161
- Canonical YAML frontmatter format, cross-compiled per editor. Install or edit skills in the `code-agnostic` source of truth, then run `plan` / `apply`; do not hand-copy generated skills into `.codex`, `.cursor`, or OpenCode directories.
197
+ Use bundle directories for new skills and agents, then let `code-agnostic`
198
+ cross-compile them per editor. Install or edit skills in the `code-agnostic`
199
+ source of truth, then run `plan` / `apply`; do not hand-copy generated skills
200
+ into `.codex`, `.cursor`, `.agents`, or OpenCode directories.
162
201
 
163
202
  ```bash
164
203
  code-agnostic skills list
@@ -188,7 +227,7 @@ That command should copy the skill into the source of truth and then run the nor
188
227
 
189
228
  ### Workspaces
190
229
 
191
- Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Repo-local app config, skills, and agents are propagated for OpenCode, Cursor, Codex, and Claude.
230
+ Register workspace directories. Workspace rules are compiled into a canonical `AGENTS.md` at the workspace root. Repos keep their own repo-specific `AGENTS.md`; Codex receives the workspace rules through generated, git-excluded `AGENTS.override.md` files, while OpenCode workspace configs reference the shared workspace file through `instructions`. Claude receives generated `CLAUDE.local.md` files and project MCP entries in `~/.claude.json["projects"][absolute_repo_path]["mcpServers"]`. Workspace source config, skills, and agents are propagated into repo-local generated paths for OpenCode, Cursor, Codex, and Claude; user-created project-local skill folders remain unmanaged until project-scoped installs are supported.
192
231
 
193
232
  Cursor propagation intentionally stays to repo-local MCP, skills, and agents; it does not copy the shared workspace `AGENTS.md` into child repos.
194
233
 
@@ -220,6 +259,12 @@ code-agnostic import apply -a cursor --include mcp --on-conflict overwrite
220
259
  code-agnostic import plan -a codex -i # interactive TUI picker
221
260
  ```
222
261
 
262
+ `import plan` previews what will be copied into the hub; `import apply` writes
263
+ only the selected sections. Conflicts are skipped by default, so rerun with
264
+ `--on-conflict overwrite` only after reviewing the preview. Use `--include`,
265
+ `--exclude`, `--source-root`, and `--follow-symlinks` to narrow what gets
266
+ imported.
267
+
223
268
  ### CLI conventions
224
269
 
225
270
  All commands use named flags (`-a`, `-w`, `-v`). Singular aliases work too: `app` = `apps`, `workspace` = `workspaces`.
@@ -239,7 +284,7 @@ The compiler migration is documented in:
239
284
 
240
285
  - [x] Plan/apply/status sync engine
241
286
  - [x] MCP server sync across editors
242
- - [x] Skills and agents sync (symlink-based)
287
+ - [x] Skills and agents sync across editors
243
288
  - [x] Workspace propagation into git repos
244
289
  - [x] Import from existing editor configs
245
290
  - [x] Consistent CLI with named flags and aliases
@@ -1,3 +1,3 @@
1
1
  __all__ = ["__version__"]
2
2
 
3
- __version__ = "0.3.8"
3
+ __version__ = "0.3.10"
@@ -16,6 +16,7 @@ from code_agnostic.cli.commands.skills import skills
16
16
  from code_agnostic.cli.commands.status import status
17
17
  from code_agnostic.cli.commands.validate import validate
18
18
  from code_agnostic.cli.commands.workspaces import workspaces
19
+ from code_agnostic.errors import SyncAppError
19
20
 
20
21
 
21
22
  @click.group(
@@ -52,6 +53,9 @@ def main() -> int:
52
53
  except click.exceptions.Exit as exc:
53
54
  code = exc.exit_code
54
55
  return code if isinstance(code, int) else 1
56
+ except SyncAppError as exc:
57
+ click.ClickException(str(exc)).show()
58
+ return 2
55
59
  except click.ClickException as exc:
56
60
  exc.show()
57
61
  return 2
@@ -102,6 +102,14 @@
102
102
  "additionalProperties": false,
103
103
  "description": "Config values for a single app/connector.",
104
104
  "properties": {
105
+ "approvals_reviewer": {
106
+ "allOf": [
107
+ {
108
+ "$ref": "#/definitions/ApprovalsReviewer"
109
+ }
110
+ ],
111
+ "description": "Reviewer for approval prompts from this app, overriding the thread default."
112
+ },
105
113
  "default_tools_approval_mode": {
106
114
  "allOf": [
107
115
  {
@@ -347,6 +355,22 @@
347
355
  },
348
356
  "type": "object"
349
357
  },
358
+ "CodeModeConfigToml": {
359
+ "additionalProperties": false,
360
+ "properties": {
361
+ "enabled": {
362
+ "type": "boolean"
363
+ },
364
+ "excluded_tool_namespaces": {
365
+ "description": "Exact tool namespaces to omit from the code-mode nested tool surface.",
366
+ "items": {
367
+ "type": "string"
368
+ },
369
+ "type": "array"
370
+ }
371
+ },
372
+ "type": "object"
373
+ },
350
374
  "ConfigProfile": {
351
375
  "additionalProperties": false,
352
376
  "description": "Collection of common configuration options that a user can define as a unit in `config.toml`.",
@@ -402,7 +426,7 @@
402
426
  "type": "boolean"
403
427
  },
404
428
  "code_mode": {
405
- "type": "boolean"
429
+ "$ref": "#/definitions/FeatureToml_for_CodeModeConfigToml"
406
430
  },
407
431
  "code_mode_only": {
408
432
  "type": "boolean"
@@ -548,9 +572,6 @@
548
572
  "request_rule": {
549
573
  "type": "boolean"
550
574
  },
551
- "responses_websocket_response_processed": {
552
- "type": "boolean"
553
- },
554
575
  "responses_websockets": {
555
576
  "type": "boolean"
556
577
  },
@@ -593,6 +614,9 @@
593
614
  "terminal_resize_reflow": {
594
615
  "type": "boolean"
595
616
  },
617
+ "terminal_visualization_instructions": {
618
+ "type": "boolean"
619
+ },
596
620
  "tool_call_mcp_elicitation": {
597
621
  "type": "boolean"
598
622
  },
@@ -822,6 +846,16 @@
822
846
  }
823
847
  ]
824
848
  },
849
+ "FeatureToml_for_CodeModeConfigToml": {
850
+ "anyOf": [
851
+ {
852
+ "type": "boolean"
853
+ },
854
+ {
855
+ "$ref": "#/definitions/CodeModeConfigToml"
856
+ }
857
+ ]
858
+ },
825
859
  "FeatureToml_for_MultiAgentV2ConfigToml": {
826
860
  "anyOf": [
827
861
  {
@@ -2472,15 +2506,8 @@
2472
2506
  "type": "string"
2473
2507
  },
2474
2508
  "ReasoningEffort": {
2475
- "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
2476
- "enum": [
2477
- "none",
2478
- "minimal",
2479
- "low",
2480
- "medium",
2481
- "high",
2482
- "xhigh"
2483
- ],
2509
+ "description": "A non-empty reasoning effort value advertised by the model.",
2510
+ "minLength": 1,
2484
2511
  "type": "string"
2485
2512
  },
2486
2513
  "ReasoningSummary": {
@@ -4525,7 +4552,7 @@
4525
4552
  "type": "boolean"
4526
4553
  },
4527
4554
  "code_mode": {
4528
- "type": "boolean"
4555
+ "$ref": "#/definitions/FeatureToml_for_CodeModeConfigToml"
4529
4556
  },
4530
4557
  "code_mode_only": {
4531
4558
  "type": "boolean"
@@ -4671,9 +4698,6 @@
4671
4698
  "request_rule": {
4672
4699
  "type": "boolean"
4673
4700
  },
4674
- "responses_websocket_response_processed": {
4675
- "type": "boolean"
4676
- },
4677
4701
  "responses_websockets": {
4678
4702
  "type": "boolean"
4679
4703
  },
@@ -4716,6 +4740,9 @@
4716
4740
  "terminal_resize_reflow": {
4717
4741
  "type": "boolean"
4718
4742
  },
4743
+ "terminal_visualization_instructions": {
4744
+ "type": "boolean"
4745
+ },
4719
4746
  "tool_call_mcp_elicitation": {
4720
4747
  "type": "boolean"
4721
4748
  },
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "branch": {
59
59
  "type": "string",
60
- "description": "Branch or ref Scout should clone and inspect"
60
+ "description": "Branch or ref to clone and inspect"
61
61
  }
62
62
  },
63
63
  "required": [
@@ -154,12 +154,6 @@
154
154
  "websearch": {
155
155
  "$ref": "#/$defs/PermissionActionConfig"
156
156
  },
157
- "repo_clone": {
158
- "$ref": "#/$defs/PermissionRuleConfig"
159
- },
160
- "repo_overview": {
161
- "$ref": "#/$defs/PermissionRuleConfig"
162
- },
163
157
  "lsp": {
164
158
  "$ref": "#/$defs/PermissionRuleConfig"
165
159
  },
@@ -797,6 +791,9 @@
797
791
  "type": "string",
798
792
  "$ref": "https://models.dev/model-schema.json#/$defs/Model"
799
793
  },
794
+ "variant": {
795
+ "type": "string"
796
+ },
800
797
  "subtask": {
801
798
  "type": "boolean"
802
799
  }
@@ -961,9 +958,6 @@
961
958
  "explore": {
962
959
  "$ref": "#/$defs/AgentConfig"
963
960
  },
964
- "scout": {
965
- "$ref": "#/$defs/AgentConfig"
966
- },
967
961
  "title": {
968
962
  "$ref": "#/$defs/AgentConfig"
969
963
  },
@@ -1179,7 +1173,7 @@
1179
1173
  },
1180
1174
  "prune": {
1181
1175
  "type": "boolean",
1182
- "description": "Enable pruning of old tool outputs (default: true)"
1176
+ "description": "Enable pruning of old tool outputs (default: false)"
1183
1177
  },
1184
1178
  "tail_turns": {
1185
1179
  "minimum": 0,
@@ -5,6 +5,7 @@ from rich.console import Console
5
5
 
6
6
  from code_agnostic.cli.options import workspace_option
7
7
  from code_agnostic.core.repository import CoreRepository
8
+ from code_agnostic.errors import SyncAppError
8
9
  from code_agnostic.imports.models import ConflictPolicy
9
10
  from code_agnostic.mcp_service import MCPManagementService
10
11
  from code_agnostic.tui import SyncConsoleUI
@@ -17,12 +18,17 @@ def _parse_env_pair(raw: str) -> tuple[str, str]:
17
18
  return raw, f"${{{raw}}}"
18
19
 
19
20
 
20
- @click.group(help="Manage MCP server definitions in the hub config.")
21
+ @click.group(
22
+ help=(
23
+ "Manage source MCP server definitions. Commands use global source by "
24
+ "default; pass -w/--workspace for workspace source."
25
+ )
26
+ )
21
27
  def mcp() -> None:
22
28
  pass
23
29
 
24
30
 
25
- @mcp.command("list", help="List configured MCP servers.")
31
+ @mcp.command("list", help="List global MCP servers, or workspace servers with -w.")
26
32
  @workspace_option()
27
33
  @click.pass_obj
28
34
  def mcp_list(obj: dict[str, str], workspace: str | None) -> None:
@@ -31,17 +37,27 @@ def mcp_list(obj: dict[str, str], workspace: str | None) -> None:
31
37
  service = MCPManagementService(core)
32
38
  try:
33
39
  servers = service.list_servers(workspace=workspace)
34
- except ValueError as exc:
40
+ except (ValueError, SyncAppError) as exc:
35
41
  raise click.ClickException(str(exc))
42
+ scope = f"workspace:{workspace}" if workspace else "global"
36
43
  rows = [
37
- [name, dto.command or dto.url or ""] for name, dto in sorted(servers.items())
44
+ [name, scope, dto.command or dto.url or ""]
45
+ for name, dto in sorted(servers.items())
38
46
  ]
47
+ empty_message = (
48
+ f"No workspace MCP servers configured for {workspace}."
49
+ if workspace
50
+ else "No global MCP servers configured."
51
+ )
39
52
  ui.render_list(
40
- "mcp servers", ["Server", "Command / URL"], rows, "No MCP servers configured."
53
+ "mcp servers",
54
+ ["Server", "Scope", "Command / URL"],
55
+ rows,
56
+ empty_message,
41
57
  )
42
58
 
43
59
 
44
- @mcp.command("add", help="Add an MCP server definition.")
60
+ @mcp.command("add", help="Add a global MCP server, or a workspace server with -w.")
45
61
  @click.argument("name")
46
62
  @click.option("--command", default=None, help="Command for stdio server.")
47
63
  @click.option(
@@ -101,12 +117,14 @@ def mcp_add(
101
117
  workspace=workspace,
102
118
  on_conflict=ConflictPolicy(on_conflict),
103
119
  )
104
- except ValueError as exc:
120
+ except (ValueError, SyncAppError) as exc:
105
121
  raise click.ClickException(str(exc))
106
122
  click.echo(msg)
107
123
 
108
124
 
109
- @mcp.command("remove", help="Remove an MCP server definition.")
125
+ @mcp.command(
126
+ "remove", help="Remove a global MCP server, or a workspace server with -w."
127
+ )
110
128
  @click.argument("name")
111
129
  @workspace_option()
112
130
  @click.pass_obj
@@ -115,8 +133,9 @@ def mcp_remove(obj: dict[str, str], name: str, workspace: str | None) -> None:
115
133
  service = MCPManagementService(core)
116
134
  try:
117
135
  removed = service.remove_server(name, workspace=workspace)
118
- except ValueError as exc:
136
+ except (ValueError, SyncAppError) as exc:
119
137
  raise click.ClickException(str(exc))
120
138
  if not removed:
121
139
  raise click.ClickException(f"Server not found: {name}")
122
- click.echo(f"Removed: {name}")
140
+ scope = f"workspace:{workspace}" if workspace else "global"
141
+ click.echo(f"Removed {scope} MCP server: {name}")
@@ -12,12 +12,18 @@ from code_agnostic.tui import SyncConsoleUI
12
12
  from code_agnostic.utils import compact_home_path
13
13
 
14
14
 
15
- @click.group(help="Manage skill definitions in the hub config.")
15
+ @click.group(
16
+ help=(
17
+ "Manage source skill definitions. Commands use global source by default; "
18
+ "pass -w/--workspace for workspace source. Repo-local skill folders are "
19
+ "unmanaged until project-scoped installs are supported."
20
+ )
21
+ )
16
22
  def skills() -> None:
17
23
  pass
18
24
 
19
25
 
20
- @skills.command("list", help="List configured skills.")
26
+ @skills.command("list", help="List global skills, or workspace skills with -w.")
21
27
  @workspace_option()
22
28
  @click.pass_obj
23
29
  def skills_list(obj: dict[str, str], workspace: str | None) -> None:
@@ -49,7 +55,7 @@ def skills_list(obj: dict[str, str], workspace: str | None) -> None:
49
55
  )
50
56
 
51
57
 
52
- @skills.command("remove", help="Remove a skill by name.")
58
+ @skills.command("remove", help="Remove a global skill, or a workspace skill with -w.")
53
59
  @click.option("--name", required=True, help="Skill name to remove.")
54
60
  @workspace_option()
55
61
  @click.pass_obj
@@ -60,4 +66,5 @@ def skills_remove(obj: dict[str, str], name: str, workspace: str | None) -> None
60
66
  if not skill_dir.exists():
61
67
  raise click.ClickException(f"Skill not found: {name}")
62
68
  shutil.rmtree(skill_dir)
63
- click.echo(f"Removed: {name}")
69
+ scope = f"workspace:{workspace}" if workspace else "global"
70
+ click.echo(f"Removed {scope} skill: {name}")
@@ -7,7 +7,13 @@ from code_agnostic.apps.apps_service import AppsService
7
7
  from code_agnostic.cli.helpers import status_row_for_app
8
8
  from code_agnostic.cli.options import app_option, verbose_option
9
9
  from code_agnostic.core.repository import CoreRepository
10
- from code_agnostic.models import EditorStatusRow, EditorSyncStatus, WorkspaceSyncStatus
10
+ from code_agnostic.errors import SyncAppError
11
+ from code_agnostic.models import (
12
+ EditorStatusRow,
13
+ EditorSyncStatus,
14
+ WorkspaceStatusRow,
15
+ WorkspaceSyncStatus,
16
+ )
11
17
  from code_agnostic.status import StatusService
12
18
  from code_agnostic.tui import SyncConsoleUI
13
19
 
@@ -50,9 +56,20 @@ def status(obj: dict[str, str], app: str, verbose: bool) -> None:
50
56
 
51
57
  status_service = StatusService()
52
58
  enabled_services = apps._resolve_services_for_target(normalized_target)
53
- workspace_rows = status_service.build_workspace_status(
54
- core, app_services=enabled_services
55
- )
59
+ try:
60
+ workspace_rows = status_service.build_workspace_status(
61
+ core, app_services=enabled_services
62
+ )
63
+ except SyncAppError as exc:
64
+ workspace_rows = [
65
+ WorkspaceStatusRow(
66
+ name="workspaces",
67
+ path=str(core.workspaces_path),
68
+ status=WorkspaceSyncStatus.ERROR,
69
+ detail=str(exc),
70
+ repos=[],
71
+ )
72
+ ]
56
73
  ui.render_status(
57
74
  editor_rows,
58
75
  workspace_rows,