feat: support Codex and Claude local surfaces

This commit is contained in:
thanhnv
2026-07-24 16:33:21 +07:00
parent 34a6b013c0
commit cc666db9de
9 changed files with 119 additions and 26 deletions
+48 -7
View File
@@ -60,10 +60,35 @@ VSCODE_EXTENSION_IDS = {
PROMPT_MARKER_START = "<!-- CASAN_PROMPT_ENFORCEMENT_START -->"
PROMPT_MARKER_END = "<!-- CASAN_PROMPT_ENFORCEMENT_END -->"
CLIENT_LABELS = {
"claude": "Claude Code",
"codex": "Codex",
"claude": "Claude Code (CLI + VS Code + JetBrains)",
"codex": "Codex local (desktop + CLI + IDE)",
"vscode-copilot": "VS Code / @casan",
}
CLIENT_SURFACES = {
"claude": {
"supported_local": [
"claude-code-cli",
"claude-code-vscode",
"claude-code-jetbrains",
],
"not_covered": ["claude-desktop", "claude-web"],
"contract": "shared_claude_code_project_settings",
},
"codex": {
"supported_local": [
"codex-desktop-local",
"codex-cli",
"codex-ide-extension-local",
],
"not_covered": ["codex-cloud", "codex-web"],
"contract": "shared_codex_local_project_hooks",
},
"vscode-copilot": {
"supported_local": ["vscode-copilot-explicit-at-casan"],
"not_covered": ["github-copilot-built-in-chat"],
"contract": "casan_owned_explicit_route",
},
}
RUNTIME_MODES = ("managed", "vendored")
@@ -131,7 +156,7 @@ def _render_init(result):
print(_color("1", "Next steps"))
print(" 1. Run `casan doctor`")
if "codex" in result["clients"]:
print(" 2. In Codex, open `/hooks` and trust this project's hook.")
print(" 2. In a local Codex client, open `/hooks` and trust this project's hook.")
def _render_verify(result):
@@ -173,6 +198,9 @@ def _render_doctor(result):
for client, item in result["client_checks"].items():
label = CLIENT_LABELS.get(client, client)
print(" %s %s" % (_mark(item.get("ready", False)), label))
surfaces = item.get("supported_surfaces") or []
if surfaces:
print(" Surfaces: %s" % ", ".join(surfaces))
reason = item.get("smoke", {}).get("reason")
if reason and not item.get("ready"):
print(" %s" % reason)
@@ -903,8 +931,8 @@ def select_clients(values, interactive):
while True:
sys.stderr.write(
"\nClient integrations\n\n"
" 1) Claude Code (CLI + official VS Code extension)\n"
" 2) Codex (CLI + official VS Code extension)\n"
" 1) Claude Code (CLI + VS Code + JetBrains)\n"
" 2) Codex local (desktop app + CLI + IDE extension)\n"
" 3) GitHub Copilot in VS Code via explicit @casan route\n\n"
"Select clients (comma-separated) [1,2]: ")
sys.stderr.flush()
@@ -1108,7 +1136,7 @@ def cmd_init(args):
# ── .casan/config.json ──
cfg_dir = os.path.join(target, ".casan")
cfg = {
"schema_version": "21.1",
"schema_version": "21.2",
"project_id": project,
"created_at": now_iso(),
"enforcement_mode": args.mode,
@@ -1121,6 +1149,13 @@ def cmd_init(args):
if "vscode-copilot" in clients else "disabled",
"vscode-native-copilot": "unsupported_global_interception",
},
"client_surfaces": {
client: {
"enabled": client in clients,
**surface,
}
for client, surface in CLIENT_SURFACES.items()
},
"harness_version": version,
"adoption_model": (
"managed-global" if runtime_mode == "managed"
@@ -1511,7 +1546,13 @@ def cmd_doctor(args):
line.strip().lower() for line in listed.stdout.splitlines() if line.strip()}
for client in clients:
item = {"configured": True}
surface = CLIENT_SURFACES.get(client, {})
item = {
"configured": True,
"supported_surfaces": list(surface.get("supported_local", [])),
"not_covered_surfaces": list(surface.get("not_covered", [])),
"surface_contract": surface.get("contract"),
}
if client == "claude":
commands = _commands_in_hooks(
os.path.join(target, ".claude", "settings.json"))