codemesh 0.1.1__py3-none-any.whl
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.
- codemesh/__init__.py +5 -0
- codemesh/__main__.py +8 -0
- codemesh/cli/__init__.py +3 -0
- codemesh/cli/init.py +208 -0
- codemesh/cli/install_cmd.py +208 -0
- codemesh/cli/main.py +469 -0
- codemesh/context/__init__.py +3 -0
- codemesh/context/builder.py +388 -0
- codemesh/db/__init__.py +3 -0
- codemesh/db/connection.py +66 -0
- codemesh/db/queries.py +696 -0
- codemesh/db/schema.py +125 -0
- codemesh/embedding/__init__.py +3 -0
- codemesh/extraction/__init__.py +7 -0
- codemesh/extraction/languages/__init__.py +95 -0
- codemesh/extraction/languages/c_family.py +614 -0
- codemesh/extraction/languages/go.py +397 -0
- codemesh/extraction/languages/java.py +603 -0
- codemesh/extraction/languages/python.py +718 -0
- codemesh/extraction/languages/rust.py +435 -0
- codemesh/extraction/languages/swift.py +464 -0
- codemesh/extraction/languages/typescript.py +1222 -0
- codemesh/extraction/orchestrator.py +218 -0
- codemesh/graph/__init__.py +8 -0
- codemesh/graph/query_manager.py +117 -0
- codemesh/graph/traverser.py +107 -0
- codemesh/indexer.py +240 -0
- codemesh/mcp/__init__.py +3 -0
- codemesh/mcp/server.py +60 -0
- codemesh/mcp/tools.py +605 -0
- codemesh/querier.py +269 -0
- codemesh/resolution/__init__.py +7 -0
- codemesh/resolution/frameworks/__init__.py +15 -0
- codemesh/resolution/frameworks/django.py +30 -0
- codemesh/resolution/frameworks/fastapi.py +23 -0
- codemesh/resolution/import_resolver.py +69 -0
- codemesh/resolution/name_matcher.py +30 -0
- codemesh/resolution/resolver.py +268 -0
- codemesh/retrieval/__init__.py +7 -0
- codemesh/search/__init__.py +3 -0
- codemesh/sync/__init__.py +3 -0
- codemesh/sync/watcher.py +135 -0
- codemesh/types.py +148 -0
- codemesh/viz/__init__.py +0 -0
- codemesh/viz/graph_builder.py +162 -0
- codemesh/viz/server.py +122 -0
- codemesh/viz/templates/index.html +359 -0
- codemesh-0.1.1.dist-info/METADATA +337 -0
- codemesh-0.1.1.dist-info/RECORD +52 -0
- codemesh-0.1.1.dist-info/WHEEL +4 -0
- codemesh-0.1.1.dist-info/entry_points.txt +2 -0
- codemesh-0.1.1.dist-info/licenses/LICENSE +21 -0
codemesh/__init__.py
ADDED
codemesh/__main__.py
ADDED
codemesh/cli/__init__.py
ADDED
codemesh/cli/init.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
"""Project initialization — creates .codemesh/ directory and agent instructions."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
_CLAUDE_MD_TEMPLATE = """## CodeMesh
|
|
12
|
+
|
|
13
|
+
CodeMesh builds a semantic knowledge graph of codebases for faster, smarter code exploration.
|
|
14
|
+
|
|
15
|
+
### If `.codemesh/` exists in the project
|
|
16
|
+
|
|
17
|
+
**NEVER call `codemesh_explore` or `codemesh_context` directly in the main session.** These tools return large amounts of source code that fills up main session context. Instead, ALWAYS spawn an Explore agent for any exploration question (e.g., "how does X work?", "explain the Y system", "where is Z implemented?").
|
|
18
|
+
|
|
19
|
+
**When spawning Explore agents**, include this instruction in the prompt:
|
|
20
|
+
|
|
21
|
+
> This project has CodeMesh initialized (.codemesh/ exists). Use `codemesh_explore` as your PRIMARY tool — it returns full source code sections from all relevant files in one call.
|
|
22
|
+
>
|
|
23
|
+
> **Rules:**
|
|
24
|
+
> 1. Follow the explore call budget in the `codemesh_explore` tool description — it scales automatically based on project size.
|
|
25
|
+
> 2. Do NOT re-read files that codemesh_explore already returned source code for. The source sections are complete and authoritative.
|
|
26
|
+
> 3. Only fall back to grep/glob/read for files listed under "Additional relevant files" if you need more detail, or if codemesh returned no results.
|
|
27
|
+
|
|
28
|
+
**The main session may only use these lightweight tools directly** (for targeted lookups before making edits, not for exploration):
|
|
29
|
+
|
|
30
|
+
| Tool | Use For |
|
|
31
|
+
|------|---------|
|
|
32
|
+
| `codemesh_search` | Find symbols by name |
|
|
33
|
+
| `codemesh_callers` / `codemesh_callees` | Trace call flow |
|
|
34
|
+
| `codemesh_impact` | Check what's affected before editing |
|
|
35
|
+
| `codemesh_node` | Get a single symbol's details |
|
|
36
|
+
|
|
37
|
+
### If `.codemesh/` does NOT exist
|
|
38
|
+
|
|
39
|
+
At the start of a session, ask the user if they'd like to initialize CodeMesh:
|
|
40
|
+
|
|
41
|
+
"I notice this project doesn't have CodeMesh initialized. Would you like me to run `codemesh init -i` to build a code knowledge graph?"
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
_CURSOR_RULES_TEMPLATE = """---
|
|
45
|
+
description: CodeMesh code intelligence
|
|
46
|
+
globs: *
|
|
47
|
+
alwaysApply: true
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## CodeMesh
|
|
51
|
+
|
|
52
|
+
CodeMesh builds a semantic knowledge graph of codebases for faster, smarter code exploration.
|
|
53
|
+
|
|
54
|
+
### If `.codemesh/` exists in the project
|
|
55
|
+
|
|
56
|
+
Use CodeMesh MCP tools as your PRIMARY exploration method:
|
|
57
|
+
|
|
58
|
+
1. `codemesh_explore` — Return source for several related symbols grouped by file, plus a relationship map, in one call. Use this as your main exploration tool.
|
|
59
|
+
2. `codemesh_callers` / `codemesh_callees` — Trace call flow
|
|
60
|
+
3. `codemesh_impact` — Check what's affected before editing
|
|
61
|
+
4. `codemesh_search` — Find symbols by name
|
|
62
|
+
|
|
63
|
+
### If `.codemesh/` does NOT exist
|
|
64
|
+
|
|
65
|
+
At the start of a session, ask the user if they'd like to initialize CodeMesh:
|
|
66
|
+
|
|
67
|
+
"I notice this project doesn't have CodeMesh initialized. Would you like me to run `codemesh init -i` to build a code knowledge graph?"
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
_CODEX_TEMPLATE = """## CodeMesh
|
|
71
|
+
|
|
72
|
+
CodeMesh builds a semantic knowledge graph of codebases for faster, smarter code exploration.
|
|
73
|
+
|
|
74
|
+
### If `.codemesh/` exists in the project
|
|
75
|
+
|
|
76
|
+
Use CodeMesh MCP tools as your PRIMARY exploration method:
|
|
77
|
+
- `codemesh_explore` — Return source for related symbols grouped by file (main exploration tool)
|
|
78
|
+
- `codemesh_callers` / `codemesh_callees` — Trace call flow
|
|
79
|
+
- `codemesh_impact` — Check what's affected before editing
|
|
80
|
+
- `codemesh_search` — Find symbols by name
|
|
81
|
+
|
|
82
|
+
### If `.codemesh/` does NOT exist
|
|
83
|
+
|
|
84
|
+
At the start of a session, ask: "I notice this project doesn't have CodeMesh initialized. Would you like me to run `codemesh init -i` to build a code knowledge graph?"
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
_HERMES_TEMPLATE = """## CodeMesh
|
|
88
|
+
|
|
89
|
+
CodeMesh builds a semantic knowledge graph of codebases for faster, smarter code exploration.
|
|
90
|
+
|
|
91
|
+
### If `.codemesh/` exists in the project
|
|
92
|
+
|
|
93
|
+
Use CodeMesh MCP tools as your PRIMARY exploration method:
|
|
94
|
+
- `codemesh_explore` — Return source for related symbols grouped by file (main exploration tool)
|
|
95
|
+
- `codemesh_callers` / `codemesh_callees` — Trace call flow
|
|
96
|
+
- `codemesh_impact` — Check what's affected before editing
|
|
97
|
+
- `codemesh_search` — Find symbols by name
|
|
98
|
+
|
|
99
|
+
### If `.codemesh/` does NOT exist
|
|
100
|
+
|
|
101
|
+
At the start of a session, ask: "I notice this project doesn't have CodeMesh initialized. Would you like me to run `codemesh init -i` to build a code knowledge graph?"
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def init_project(root: Path, interactive: bool = False) -> dict:
|
|
106
|
+
"""Initialize CodeMesh in a project.
|
|
107
|
+
|
|
108
|
+
Creates .codemesh/ directory and writes agent instruction files.
|
|
109
|
+
Returns a dict with paths created.
|
|
110
|
+
"""
|
|
111
|
+
root = root.resolve()
|
|
112
|
+
codemesh_dir = root / ".codemesh"
|
|
113
|
+
|
|
114
|
+
created = {}
|
|
115
|
+
|
|
116
|
+
# Create .codemesh directory
|
|
117
|
+
codemesh_dir.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
created["codemesh_dir"] = str(codemesh_dir)
|
|
119
|
+
|
|
120
|
+
# Write CLAUDE.md (project-level instructions for Claude Code)
|
|
121
|
+
claude_md = root / "CLAUDE.md"
|
|
122
|
+
if interactive and claude_md.exists() and not _confirm_overwrite("CLAUDE.md"):
|
|
123
|
+
# Write to .codemesh/CLAUDE.md instead
|
|
124
|
+
claude_md = codemesh_dir / "CLAUDE.md"
|
|
125
|
+
claude_md.write_text(_CLAUDE_MD_TEMPLATE)
|
|
126
|
+
created["claude_md"] = str(claude_md)
|
|
127
|
+
|
|
128
|
+
# Write Cursor rules
|
|
129
|
+
cursor_dir = root / ".cursor" / "rules"
|
|
130
|
+
cursor_dir.mkdir(parents=True, exist_ok=True)
|
|
131
|
+
cursor_rules = cursor_dir / "codemesh.mdc"
|
|
132
|
+
cursor_rules.write_text(_CURSOR_RULES_TEMPLATE)
|
|
133
|
+
created["cursor_rules"] = str(cursor_rules)
|
|
134
|
+
|
|
135
|
+
# Write AGENTS.md (global instructions for Codex/opencode)
|
|
136
|
+
agents_md = root / "AGENTS.md"
|
|
137
|
+
if interactive and agents_md.exists() and not _confirm_overwrite("AGENTS.md"):
|
|
138
|
+
agents_md = codemesh_dir / "AGENTS.md"
|
|
139
|
+
agents_md.write_text(_CODEX_TEMPLATE)
|
|
140
|
+
created["agents_md"] = str(agents_md)
|
|
141
|
+
|
|
142
|
+
# Write Hermes instructions
|
|
143
|
+
hermes_md = codemesh_dir / "HERMES.md"
|
|
144
|
+
hermes_md.write_text(_HERMES_TEMPLATE)
|
|
145
|
+
created["hermes_md"] = str(hermes_md)
|
|
146
|
+
|
|
147
|
+
# Write config file
|
|
148
|
+
config_file = codemesh_dir / "config.json"
|
|
149
|
+
if not config_file.exists():
|
|
150
|
+
import json
|
|
151
|
+
|
|
152
|
+
config = {
|
|
153
|
+
"version": 1,
|
|
154
|
+
"root": str(root),
|
|
155
|
+
"ignore": [".git", "node_modules", "__pycache__", ".venv", "venv"],
|
|
156
|
+
}
|
|
157
|
+
config_file.write_text(json.dumps(config, indent=2))
|
|
158
|
+
created["config"] = str(config_file)
|
|
159
|
+
|
|
160
|
+
return created
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _confirm_overwrite(name: str) -> bool:
|
|
164
|
+
"""Ask user to confirm overwriting an existing file."""
|
|
165
|
+
response = input(f"{name} already exists. Overwrite? [y/N] ").strip().lower()
|
|
166
|
+
return response in ("y", "yes")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def uninit_project(root: Path, force: bool = False) -> dict:
|
|
170
|
+
"""Remove CodeMesh from a project.
|
|
171
|
+
|
|
172
|
+
Removes .codemesh/ directory and agent instruction files.
|
|
173
|
+
Returns a dict with paths removed.
|
|
174
|
+
"""
|
|
175
|
+
root = root.resolve()
|
|
176
|
+
codemesh_dir = root / ".codemesh"
|
|
177
|
+
removed = []
|
|
178
|
+
|
|
179
|
+
# Remove instruction files
|
|
180
|
+
for f in ["CLAUDE.md", "AGENTS.md"]:
|
|
181
|
+
p = root / f
|
|
182
|
+
if p.exists():
|
|
183
|
+
content = p.read_text()
|
|
184
|
+
if "CodeMesh" in content and (force or _confirm_remove(f)):
|
|
185
|
+
p.unlink()
|
|
186
|
+
removed.append(str(p))
|
|
187
|
+
|
|
188
|
+
# Remove Cursor rules
|
|
189
|
+
cursor_rules = root / ".cursor" / "rules" / "codemesh.mdc"
|
|
190
|
+
if cursor_rules.exists() and (force or _confirm_remove("cursor rules")):
|
|
191
|
+
cursor_rules.unlink()
|
|
192
|
+
removed.append(str(cursor_rules))
|
|
193
|
+
|
|
194
|
+
# Remove .codemesh directory
|
|
195
|
+
if codemesh_dir.exists():
|
|
196
|
+
import shutil
|
|
197
|
+
|
|
198
|
+
if force or _confirm_remove(".codemesh/"):
|
|
199
|
+
shutil.rmtree(codemesh_dir)
|
|
200
|
+
removed.append(str(codemesh_dir))
|
|
201
|
+
|
|
202
|
+
return {"removed": removed}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _confirm_remove(name: str) -> bool:
|
|
206
|
+
"""Ask user to confirm removing a file."""
|
|
207
|
+
response = input(f"Remove {name}? [y/N] ").strip().lower()
|
|
208
|
+
return response in ("y", "yes")
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
"""Install command — auto-configures CodeMesh MCP server for AI coding agents."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
_CLAUDE_MCP_CONFIG = {
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"codemesh": {
|
|
15
|
+
"type": "stdio",
|
|
16
|
+
"command": "codemesh",
|
|
17
|
+
"args": ["serve", "--transport", "stdio"],
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_CLAUDE_PERMISSIONS = {
|
|
23
|
+
"permissions": {
|
|
24
|
+
"allow": [
|
|
25
|
+
"mcp__codemesh__codemesh_search",
|
|
26
|
+
"mcp__codemesh__codemesh_context",
|
|
27
|
+
"mcp__codemesh__codemesh_callers",
|
|
28
|
+
"mcp__codemesh__codemesh_callees",
|
|
29
|
+
"mcp__codemesh__codemesh_impact",
|
|
30
|
+
"mcp__codemesh__codemesh_node",
|
|
31
|
+
"mcp__codemesh__codemesh_status",
|
|
32
|
+
"mcp__codemesh__codemesh_files",
|
|
33
|
+
"mcp__codemesh__codemesh_explore",
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _find_claude_json_dir() -> Path | None:
|
|
40
|
+
"""Find the Claude Code configuration directory."""
|
|
41
|
+
candidates = [
|
|
42
|
+
Path.home() / ".claude",
|
|
43
|
+
Path.home() / ".config" / "claude",
|
|
44
|
+
]
|
|
45
|
+
for c in candidates:
|
|
46
|
+
if c.exists():
|
|
47
|
+
return c
|
|
48
|
+
# Create the default location
|
|
49
|
+
claude_dir = Path.home() / ".claude"
|
|
50
|
+
claude_dir.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
return claude_dir
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _merge_json_file(path: Path, new_data: dict) -> dict:
|
|
55
|
+
"""Merge new data into an existing JSON file."""
|
|
56
|
+
existing: dict = {}
|
|
57
|
+
if path.exists():
|
|
58
|
+
try:
|
|
59
|
+
existing = json.loads(path.read_text())
|
|
60
|
+
except (json.JSONDecodeError, OSError):
|
|
61
|
+
existing = {}
|
|
62
|
+
|
|
63
|
+
# Merge MCP servers
|
|
64
|
+
if "mcpServers" in new_data:
|
|
65
|
+
existing.setdefault("mcpServers", {})
|
|
66
|
+
existing["mcpServers"].update(new_data["mcpServers"])
|
|
67
|
+
|
|
68
|
+
# Merge permissions
|
|
69
|
+
if "permissions" in new_data and "allow" in new_data["permissions"]:
|
|
70
|
+
existing.setdefault("permissions", {"allow": []})
|
|
71
|
+
perms_allow = existing["permissions"].get("allow", [])
|
|
72
|
+
for item in new_data["permissions"]["allow"]:
|
|
73
|
+
if item not in perms_allow:
|
|
74
|
+
perms_allow.append(item)
|
|
75
|
+
existing["permissions"]["allow"] = perms_allow
|
|
76
|
+
|
|
77
|
+
return existing
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def install_claude(root: Path, global_config: bool = True) -> dict:
|
|
81
|
+
"""Configure Claude Code to use CodeMesh MCP server.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
root: Project root (for project-local config)
|
|
85
|
+
global_config: If True, write to ~/.claude.json (global).
|
|
86
|
+
If False, write to .claude.json in project root.
|
|
87
|
+
"""
|
|
88
|
+
result = {"claude_json": None, "claude_settings": None, "claude_md": None}
|
|
89
|
+
|
|
90
|
+
if global_config:
|
|
91
|
+
claude_dir = _find_claude_json_dir()
|
|
92
|
+
if claude_dir is None:
|
|
93
|
+
return result
|
|
94
|
+
claude_json = claude_dir / "claude.json"
|
|
95
|
+
claude_settings = claude_dir / "settings.json"
|
|
96
|
+
else:
|
|
97
|
+
claude_json = root / ".claude.json"
|
|
98
|
+
claude_settings = root / ".claude_settings.json"
|
|
99
|
+
|
|
100
|
+
# Merge MCP config
|
|
101
|
+
if claude_json.exists():
|
|
102
|
+
existing = json.loads(claude_json.read_text())
|
|
103
|
+
existing_mcp = existing.get("mcpServers", {})
|
|
104
|
+
if "codemesh" in existing_mcp:
|
|
105
|
+
result["claude_json"] = str(claude_json) + " (already configured)"
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
merged = _merge_json_file(claude_json, _CLAUDE_MCP_CONFIG)
|
|
109
|
+
claude_json.write_text(json.dumps(merged, indent=2))
|
|
110
|
+
result["claude_json"] = str(claude_json)
|
|
111
|
+
|
|
112
|
+
# Merge permissions
|
|
113
|
+
merged_settings = _merge_json_file(claude_settings, _CLAUDE_PERMISSIONS)
|
|
114
|
+
claude_settings.write_text(json.dumps(merged_settings, indent=2))
|
|
115
|
+
result["claude_settings"] = str(claude_settings)
|
|
116
|
+
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def install_cursor(root: Path) -> dict:
|
|
121
|
+
"""Configure Cursor to use CodeMesh MCP server.
|
|
122
|
+
|
|
123
|
+
Cursor uses .cursor/mcp.json in the project directory.
|
|
124
|
+
"""
|
|
125
|
+
result = {"cursor_mcp": None}
|
|
126
|
+
|
|
127
|
+
cursor_dir = root / ".cursor"
|
|
128
|
+
cursor_dir.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
mcp_json = cursor_dir / "mcp.json"
|
|
130
|
+
|
|
131
|
+
config = {}
|
|
132
|
+
if mcp_json.exists():
|
|
133
|
+
try:
|
|
134
|
+
config = json.loads(mcp_json.read_text())
|
|
135
|
+
except (json.JSONDecodeError, OSError):
|
|
136
|
+
config = {}
|
|
137
|
+
|
|
138
|
+
config.setdefault("mcpServers", {})
|
|
139
|
+
if "codemesh" in config.get("mcpServers", {}):
|
|
140
|
+
result["cursor_mcp"] = str(mcp_json) + " (already configured)"
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
config["mcpServers"]["codemesh"] = {
|
|
144
|
+
"type": "stdio",
|
|
145
|
+
"command": "codemesh",
|
|
146
|
+
"args": ["serve", "--transport", "stdio"],
|
|
147
|
+
}
|
|
148
|
+
mcp_json.write_text(json.dumps(config, indent=2))
|
|
149
|
+
result["cursor_mcp"] = str(mcp_json)
|
|
150
|
+
|
|
151
|
+
return result
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def install_codex(root: Path) -> dict:
|
|
155
|
+
"""Configure Codex CLI to use CodeMesh MCP server.
|
|
156
|
+
|
|
157
|
+
Codex uses ~/.codex/config.json.
|
|
158
|
+
"""
|
|
159
|
+
result = {"codex_config": None}
|
|
160
|
+
|
|
161
|
+
codex_dir = Path.home() / ".codex"
|
|
162
|
+
codex_dir.mkdir(parents=True, exist_ok=True)
|
|
163
|
+
config_file = codex_dir / "config.json"
|
|
164
|
+
|
|
165
|
+
config: dict = {}
|
|
166
|
+
if config_file.exists():
|
|
167
|
+
try:
|
|
168
|
+
config = json.loads(config_file.read_text())
|
|
169
|
+
except (json.JSONDecodeError, OSError):
|
|
170
|
+
config = {}
|
|
171
|
+
|
|
172
|
+
config.setdefault("mcpServers", {})
|
|
173
|
+
if "codemesh" in config.get("mcpServers", {}):
|
|
174
|
+
result["codex_config"] = str(config_file) + " (already configured)"
|
|
175
|
+
return result
|
|
176
|
+
|
|
177
|
+
config["mcpServers"]["codemesh"] = {
|
|
178
|
+
"type": "stdio",
|
|
179
|
+
"command": "codemesh",
|
|
180
|
+
"args": ["serve", "--transport", "stdio"],
|
|
181
|
+
}
|
|
182
|
+
config_file.write_text(json.dumps(config, indent=2))
|
|
183
|
+
result["codex_config"] = str(config_file)
|
|
184
|
+
|
|
185
|
+
return result
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def detect_agents() -> list[str]:
|
|
189
|
+
"""Detect which AI coding agents are installed."""
|
|
190
|
+
agents = []
|
|
191
|
+
|
|
192
|
+
# Check for Claude Code
|
|
193
|
+
claude_dir = _find_claude_json_dir()
|
|
194
|
+
if claude_dir and claude_dir.exists():
|
|
195
|
+
agents.append("claude")
|
|
196
|
+
|
|
197
|
+
# Check for Cursor
|
|
198
|
+
cursor_check = Path.home() / ".cursor"
|
|
199
|
+
if cursor_check.exists():
|
|
200
|
+
agents.append("cursor")
|
|
201
|
+
|
|
202
|
+
# Check for Codex CLI
|
|
203
|
+
import shutil
|
|
204
|
+
|
|
205
|
+
if shutil.which("codex"):
|
|
206
|
+
agents.append("codex")
|
|
207
|
+
|
|
208
|
+
return agents
|