code-context-engine 0.4.10__tar.gz → 0.4.12__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 (88) hide show
  1. {code_context_engine-0.4.10/src/code_context_engine.egg-info → code_context_engine-0.4.12}/PKG-INFO +7 -3
  2. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/README.md +5 -1
  3. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/pyproject.toml +2 -2
  4. {code_context_engine-0.4.10 → code_context_engine-0.4.12/src/code_context_engine.egg-info}/PKG-INFO +7 -3
  5. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/code_context_engine.egg-info/SOURCES.txt +1 -0
  6. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/editors.py +65 -2
  7. code_context_engine-0.4.12/tests/test_editors_opencode.py +74 -0
  8. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/LICENSE +0 -0
  9. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/setup.cfg +0 -0
  10. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/code_context_engine.egg-info/dependency_links.txt +0 -0
  11. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/code_context_engine.egg-info/entry_points.txt +0 -0
  12. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/code_context_engine.egg-info/requires.txt +0 -0
  13. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/code_context_engine.egg-info/top_level.txt +0 -0
  14. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/__init__.py +0 -0
  15. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/cli.py +0 -0
  16. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/cli_style.py +0 -0
  17. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/compression/__init__.py +0 -0
  18. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/compression/compressor.py +0 -0
  19. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/compression/ollama_client.py +0 -0
  20. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/compression/output_rules.py +0 -0
  21. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/compression/prompts.py +0 -0
  22. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/compression/quality.py +0 -0
  23. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/config.py +0 -0
  24. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/dashboard/__init__.py +0 -0
  25. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/dashboard/_page.py +0 -0
  26. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/dashboard/server.py +0 -0
  27. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/event_bus.py +0 -0
  28. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/indexer/__init__.py +0 -0
  29. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/indexer/chunker.py +0 -0
  30. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/indexer/embedder.py +0 -0
  31. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/indexer/embedding_cache.py +0 -0
  32. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/indexer/git_hooks.py +0 -0
  33. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/indexer/git_indexer.py +0 -0
  34. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/indexer/ignorefile.py +0 -0
  35. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/indexer/manifest.py +0 -0
  36. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/indexer/pipeline.py +0 -0
  37. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/indexer/secrets.py +0 -0
  38. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/indexer/watcher.py +0 -0
  39. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/integration/__init__.py +0 -0
  40. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/integration/bootstrap.py +0 -0
  41. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/integration/git_context.py +0 -0
  42. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/integration/mcp_server.py +0 -0
  43. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/integration/session_capture.py +0 -0
  44. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/memory/__init__.py +0 -0
  45. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/memory/compressor.py +0 -0
  46. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/memory/db.py +0 -0
  47. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/memory/extractive.py +0 -0
  48. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/memory/grammar.py +0 -0
  49. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/memory/hook_installer.py +0 -0
  50. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/memory/hook_server.py +0 -0
  51. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/memory/hooks.py +0 -0
  52. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/memory/migrate.py +0 -0
  53. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/models.py +0 -0
  54. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/pricing.py +0 -0
  55. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/project_commands.py +0 -0
  56. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/retrieval/__init__.py +0 -0
  57. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/retrieval/confidence.py +0 -0
  58. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/retrieval/query_parser.py +0 -0
  59. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/retrieval/retriever.py +0 -0
  60. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/serve_http.py +0 -0
  61. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/services.py +0 -0
  62. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/storage/__init__.py +0 -0
  63. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/storage/backend.py +0 -0
  64. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/storage/fts_store.py +0 -0
  65. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/storage/graph_store.py +0 -0
  66. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/storage/local_backend.py +0 -0
  67. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/storage/remote_backend.py +0 -0
  68. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/storage/vector_store.py +0 -0
  69. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/src/context_engine/utils.py +0 -0
  70. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_cli_init_probe.py +0 -0
  71. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_cli_mcp_config.py +0 -0
  72. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_cli_savings.py +0 -0
  73. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_cli_savings_buckets.py +0 -0
  74. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_cli_savings_e2e.py +0 -0
  75. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_cli_serve.py +0 -0
  76. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_cli_sessions_export.py +0 -0
  77. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_cli_sessions_status.py +0 -0
  78. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_cli_smoke.py +0 -0
  79. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_cli_uninstall.py +0 -0
  80. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_config.py +0 -0
  81. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_e2e.py +0 -0
  82. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_event_bus.py +0 -0
  83. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_models.py +0 -0
  84. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_project_commands.py +0 -0
  85. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_real_life.py +0 -0
  86. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_services.py +0 -0
  87. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_token_efficiency.py +0 -0
  88. {code_context_engine-0.4.10 → code_context_engine-0.4.12}/tests/test_token_packing.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-engine
3
- Version: 0.4.10
4
- Summary: Index your codebase. AI searches instead of re-reading files. 94% token savings, benchmarked on FastAPI. Works with Claude Code, Cursor, VS Code, Gemini CLI, and Codex.
3
+ Version: 0.4.12
4
+ Summary: Index your codebase. AI searches instead of re-reading files. 94% token savings, benchmarked on FastAPI. Works with Claude Code, Cursor, VS Code, Gemini CLI, Codex, and OpenCode.
5
5
  Author-email: Fazle Elahee <felahee@gmail.com>, Raj <rajkumar.sakti@gmail.com>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/elara-labs/code-context-engine
@@ -78,7 +78,8 @@ Dynamic: license-file
78
78
  <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/VS_Code-007ACC?style=for-the-badge&logo=visual-studio-code&logoColor=white" alt="VS Code" height="36"></a>&nbsp;&nbsp;
79
79
  <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/Cursor-000000?style=for-the-badge&logo=cursor&logoColor=white" alt="Cursor" height="36"></a>&nbsp;&nbsp;
80
80
  <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/Gemini_CLI-4285F4?style=for-the-badge&logo=google&logoColor=white" alt="Gemini CLI" height="36"></a>&nbsp;&nbsp;
81
- <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/Codex_CLI-412991?style=for-the-badge&logo=openai&logoColor=white" alt="Codex CLI" height="36"></a>
81
+ <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/Codex_CLI-412991?style=for-the-badge&logo=openai&logoColor=white" alt="Codex CLI" height="36"></a>&nbsp;&nbsp;
82
+ <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/OpenCode-22C55E?style=for-the-badge&logo=terminal&logoColor=white" alt="OpenCode" height="36"></a>
82
83
  </p>
83
84
 
84
85
  <p align="center">
@@ -125,6 +126,7 @@ Restart your editor. Done. Every question now hits the index instead of re-readi
125
126
  | Cursor | `.cursor/mcp.json` | `.cursorrules` |
126
127
  | Gemini CLI | `.gemini/settings.json` | `GEMINI.md` |
127
128
  | OpenAI Codex | `.codex/config.toml` | |
129
+ | OpenCode | `opencode.json` | |
128
130
 
129
131
  Multiple editors in the same project? All get configured in one command.
130
132
 
@@ -455,3 +457,5 @@ MIT. See [LICENSE](LICENSE).
455
457
  <p align="center">
456
458
  <strong>If CCE saves you tokens, give it a star.</strong>
457
459
  </p>
460
+
461
+ <!-- mcp-name: io.github.elara-labs/code-context-engine -->
@@ -29,7 +29,8 @@
29
29
  <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/VS_Code-007ACC?style=for-the-badge&logo=visual-studio-code&logoColor=white" alt="VS Code" height="36"></a>&nbsp;&nbsp;
30
30
  <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/Cursor-000000?style=for-the-badge&logo=cursor&logoColor=white" alt="Cursor" height="36"></a>&nbsp;&nbsp;
31
31
  <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/Gemini_CLI-4285F4?style=for-the-badge&logo=google&logoColor=white" alt="Gemini CLI" height="36"></a>&nbsp;&nbsp;
32
- <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/Codex_CLI-412991?style=for-the-badge&logo=openai&logoColor=white" alt="Codex CLI" height="36"></a>
32
+ <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/Codex_CLI-412991?style=for-the-badge&logo=openai&logoColor=white" alt="Codex CLI" height="36"></a>&nbsp;&nbsp;
33
+ <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/OpenCode-22C55E?style=for-the-badge&logo=terminal&logoColor=white" alt="OpenCode" height="36"></a>
33
34
  </p>
34
35
 
35
36
  <p align="center">
@@ -76,6 +77,7 @@ Restart your editor. Done. Every question now hits the index instead of re-readi
76
77
  | Cursor | `.cursor/mcp.json` | `.cursorrules` |
77
78
  | Gemini CLI | `.gemini/settings.json` | `GEMINI.md` |
78
79
  | OpenAI Codex | `.codex/config.toml` | |
80
+ | OpenCode | `opencode.json` | |
79
81
 
80
82
  Multiple editors in the same project? All get configured in one command.
81
83
 
@@ -406,3 +408,5 @@ MIT. See [LICENSE](LICENSE).
406
408
  <p align="center">
407
409
  <strong>If CCE saves you tokens, give it a star.</strong>
408
410
  </p>
411
+
412
+ <!-- mcp-name: io.github.elara-labs/code-context-engine -->
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "code-context-engine"
3
- version = "0.4.10"
4
- description = "Index your codebase. AI searches instead of re-reading files. 94% token savings, benchmarked on FastAPI. Works with Claude Code, Cursor, VS Code, Gemini CLI, and Codex."
3
+ version = "0.4.12"
4
+ description = "Index your codebase. AI searches instead of re-reading files. 94% token savings, benchmarked on FastAPI. Works with Claude Code, Cursor, VS Code, Gemini CLI, Codex, and OpenCode."
5
5
  readme = {file = "README.md", content-type = "text/markdown"}
6
6
  license = "MIT"
7
7
  authors = [
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-engine
3
- Version: 0.4.10
4
- Summary: Index your codebase. AI searches instead of re-reading files. 94% token savings, benchmarked on FastAPI. Works with Claude Code, Cursor, VS Code, Gemini CLI, and Codex.
3
+ Version: 0.4.12
4
+ Summary: Index your codebase. AI searches instead of re-reading files. 94% token savings, benchmarked on FastAPI. Works with Claude Code, Cursor, VS Code, Gemini CLI, Codex, and OpenCode.
5
5
  Author-email: Fazle Elahee <felahee@gmail.com>, Raj <rajkumar.sakti@gmail.com>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/elara-labs/code-context-engine
@@ -78,7 +78,8 @@ Dynamic: license-file
78
78
  <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/VS_Code-007ACC?style=for-the-badge&logo=visual-studio-code&logoColor=white" alt="VS Code" height="36"></a>&nbsp;&nbsp;
79
79
  <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/Cursor-000000?style=for-the-badge&logo=cursor&logoColor=white" alt="Cursor" height="36"></a>&nbsp;&nbsp;
80
80
  <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/Gemini_CLI-4285F4?style=for-the-badge&logo=google&logoColor=white" alt="Gemini CLI" height="36"></a>&nbsp;&nbsp;
81
- <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/Codex_CLI-412991?style=for-the-badge&logo=openai&logoColor=white" alt="Codex CLI" height="36"></a>
81
+ <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/Codex_CLI-412991?style=for-the-badge&logo=openai&logoColor=white" alt="Codex CLI" height="36"></a>&nbsp;&nbsp;
82
+ <a href="#install-and-see-savings-in-60-seconds"><img src="https://img.shields.io/badge/OpenCode-22C55E?style=for-the-badge&logo=terminal&logoColor=white" alt="OpenCode" height="36"></a>
82
83
  </p>
83
84
 
84
85
  <p align="center">
@@ -125,6 +126,7 @@ Restart your editor. Done. Every question now hits the index instead of re-readi
125
126
  | Cursor | `.cursor/mcp.json` | `.cursorrules` |
126
127
  | Gemini CLI | `.gemini/settings.json` | `GEMINI.md` |
127
128
  | OpenAI Codex | `.codex/config.toml` | |
129
+ | OpenCode | `opencode.json` | |
128
130
 
129
131
  Multiple editors in the same project? All get configured in one command.
130
132
 
@@ -455,3 +457,5 @@ MIT. See [LICENSE](LICENSE).
455
457
  <p align="center">
456
458
  <strong>If CCE saves you tokens, give it a star.</strong>
457
459
  </p>
460
+
461
+ <!-- mcp-name: io.github.elara-labs/code-context-engine -->
@@ -76,6 +76,7 @@ tests/test_cli_smoke.py
76
76
  tests/test_cli_uninstall.py
77
77
  tests/test_config.py
78
78
  tests/test_e2e.py
79
+ tests/test_editors_opencode.py
79
80
  tests/test_event_bus.py
80
81
  tests/test_models.py
81
82
  tests/test_project_commands.py
@@ -1,8 +1,8 @@
1
1
  """Multi-editor MCP configuration.
2
2
 
3
3
  Detects installed editors and writes MCP server config in each editor's
4
- format. Supports Claude Code, VS Code/Copilot, Cursor, Gemini CLI, and
5
- OpenAI Codex CLI.
4
+ format. Supports Claude Code, VS Code/Copilot, Cursor, Gemini CLI,
5
+ OpenAI Codex CLI, and OpenCode.
6
6
  """
7
7
  from __future__ import annotations
8
8
 
@@ -50,6 +50,13 @@ EDITORS: dict[str, dict] = {
50
50
  "format": "toml",
51
51
  "detect": [".codex"],
52
52
  },
53
+ "opencode": {
54
+ "name": "OpenCode",
55
+ "config_path": "opencode.json",
56
+ "servers_key": "mcp",
57
+ "format": "opencode",
58
+ "detect": ["opencode.json", "opencode.jsonc"],
59
+ },
53
60
  }
54
61
 
55
62
  # ── Instruction file definitions ──────────────────────────────────────
@@ -129,6 +136,9 @@ def configure_mcp(project_dir: Path, editor_key: str) -> bool:
129
136
  if editor.get("format") == "toml":
130
137
  return _configure_toml(config_path, command, str(project_dir))
131
138
 
139
+ if editor.get("format") == "opencode":
140
+ return _configure_opencode(config_path, command, str(project_dir))
141
+
132
142
  servers_key = editor["servers_key"]
133
143
  entry = {"command": command, "args": ["serve", "--project-dir", str(project_dir)]}
134
144
 
@@ -154,6 +164,53 @@ def configure_mcp(project_dir: Path, editor_key: str) -> bool:
154
164
  return True
155
165
 
156
166
 
167
+ def _configure_opencode(config_path: Path, command: str, project_dir: str) -> bool:
168
+ """Add CCE to OpenCode's opencode.json. Returns True if changed.
169
+
170
+ OpenCode uses a different MCP entry format: type "local" with command
171
+ as an array (not a string + args).
172
+ """
173
+ # OpenCode may also have opencode.jsonc; if the .jsonc exists and .json
174
+ # doesn't, use the .jsonc path instead.
175
+ jsonc_path = config_path.with_suffix(".jsonc")
176
+ if jsonc_path.exists() and not config_path.exists():
177
+ config_path = jsonc_path
178
+
179
+ entry = {
180
+ "type": "local",
181
+ "command": [command, "serve", "--project-dir", project_dir],
182
+ }
183
+
184
+ if config_path.exists():
185
+ try:
186
+ content = config_path.read_text()
187
+ # Strip JSONC comments for parsing
188
+ data = json.loads(_strip_jsonc_comments(content))
189
+ except (json.JSONDecodeError, OSError):
190
+ data = {}
191
+ else:
192
+ data = {}
193
+
194
+ servers = data.setdefault("mcp", {})
195
+ if "context-engine" in servers:
196
+ existing = servers["context-engine"]
197
+ if existing.get("command") == entry["command"] and existing.get("type") == "local":
198
+ return False
199
+ servers["context-engine"] = entry
200
+ atomic_write_text(config_path, json.dumps(data, indent=2) + "\n")
201
+ return True
202
+
203
+ servers["context-engine"] = entry
204
+ atomic_write_text(config_path, json.dumps(data, indent=2) + "\n")
205
+ return True
206
+
207
+
208
+ def _strip_jsonc_comments(text: str) -> str:
209
+ """Strip single-line // comments from JSONC content for JSON parsing."""
210
+ import re
211
+ return re.sub(r'//.*?$', '', text, flags=re.MULTILINE)
212
+
213
+
157
214
  def _configure_toml(config_path: Path, command: str, project_dir: str) -> bool:
158
215
  """Add CCE to a TOML config file (Codex). Returns True if changed."""
159
216
  block = _codex_toml_block(command, project_dir)
@@ -174,6 +231,12 @@ def remove_mcp(project_dir: Path, editor_key: str) -> str | None:
174
231
  editor = EDITORS[editor_key]
175
232
  config_path = project_dir / editor["config_path"]
176
233
 
234
+ # OpenCode may use .jsonc instead of .json
235
+ if editor.get("format") == "opencode":
236
+ jsonc_path = config_path.with_suffix(".jsonc")
237
+ if jsonc_path.exists() and not config_path.exists():
238
+ config_path = jsonc_path
239
+
177
240
  if not config_path.exists():
178
241
  return None
179
242
 
@@ -0,0 +1,74 @@
1
+ """Tests for OpenCode MCP configuration in editors.py."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from unittest.mock import patch
6
+
7
+ from context_engine.editors import (
8
+ configure_mcp, detect_editors, remove_mcp,
9
+ )
10
+
11
+
12
+ def test_detect_opencode_json(tmp_path):
13
+ (tmp_path / "opencode.json").write_text("{}")
14
+ detected = detect_editors(tmp_path)
15
+ assert "opencode" in detected
16
+
17
+
18
+ def test_detect_opencode_jsonc(tmp_path):
19
+ (tmp_path / "opencode.jsonc").write_text("{}")
20
+ detected = detect_editors(tmp_path)
21
+ assert "opencode" in detected
22
+
23
+
24
+ def test_configure_opencode_creates_config(tmp_path):
25
+ with patch("context_engine.editors.resolve_cce_binary", return_value="/usr/bin/cce"):
26
+ changed = configure_mcp(tmp_path, "opencode")
27
+ assert changed is True
28
+
29
+ data = json.loads((tmp_path / "opencode.json").read_text())
30
+ entry = data["mcp"]["context-engine"]
31
+ assert entry["type"] == "local"
32
+ assert entry["command"] == ["/usr/bin/cce", "serve", "--project-dir", str(tmp_path)]
33
+
34
+
35
+ def test_configure_opencode_idempotent(tmp_path):
36
+ with patch("context_engine.editors.resolve_cce_binary", return_value="/usr/bin/cce"):
37
+ configure_mcp(tmp_path, "opencode")
38
+ changed = configure_mcp(tmp_path, "opencode")
39
+ assert changed is False
40
+
41
+
42
+ def test_configure_opencode_preserves_existing(tmp_path):
43
+ existing = {"model": "anthropic/claude-sonnet-4-5", "mcp": {"other": {"type": "local"}}}
44
+ (tmp_path / "opencode.json").write_text(json.dumps(existing))
45
+
46
+ with patch("context_engine.editors.resolve_cce_binary", return_value="/usr/bin/cce"):
47
+ configure_mcp(tmp_path, "opencode")
48
+
49
+ data = json.loads((tmp_path / "opencode.json").read_text())
50
+ assert data["model"] == "anthropic/claude-sonnet-4-5"
51
+ assert "other" in data["mcp"]
52
+ assert "context-engine" in data["mcp"]
53
+
54
+
55
+ def test_configure_opencode_uses_jsonc_if_exists(tmp_path):
56
+ (tmp_path / "opencode.jsonc").write_text('{\n // my config\n "model": "test"\n}')
57
+
58
+ with patch("context_engine.editors.resolve_cce_binary", return_value="/usr/bin/cce"):
59
+ changed = configure_mcp(tmp_path, "opencode")
60
+ assert changed is True
61
+
62
+ # Should write to .jsonc since that's what existed
63
+ data = json.loads((tmp_path / "opencode.jsonc").read_text())
64
+ assert "context-engine" in data["mcp"]
65
+ assert data["model"] == "test"
66
+
67
+
68
+ def test_remove_opencode(tmp_path):
69
+ config = {"mcp": {"context-engine": {"type": "local", "command": ["/usr/bin/cce"]}}}
70
+ (tmp_path / "opencode.json").write_text(json.dumps(config))
71
+
72
+ result = remove_mcp(tmp_path, "opencode")
73
+ assert result is not None
74
+ assert "opencode" in result.lower()