@@ -292,6 +292,60 @@ def _install_cursor(config_root: Path) -> None:
292292 _install_skills (cursor_root / "skills" )
293293
294294
295+ def _install_zcode (config_root : Path ) -> None :
296+ zcode_root = config_root / ".zcode"
297+ config_path = zcode_root / "cli" / "config.json"
298+ config_path .parent .mkdir (parents = True , exist_ok = True )
299+
300+ if config_path .exists ():
301+ try :
302+ config = json .loads (config_path .read_text (encoding = "utf-8" ))
303+ except (OSError , json .JSONDecodeError ) as exc :
304+ raise InstallError (
305+ f"ZCode config is invalid and was not changed: { config_path } "
306+ ) from exc
307+ if not isinstance (config , dict ):
308+ raise InstallError (
309+ f"ZCode config must be an object: { config_path } "
310+ )
311+ timestamp = datetime .now ().strftime ("%Y%m%d-%H%M%S" )
312+ shutil .copy2 (
313+ config_path ,
314+ config_path .with_name (f"{ config_path .name } .{ timestamp } .bak" ),
315+ )
316+ else :
317+ config = {}
318+
319+ # ZCode nests servers under mcp.servers and also keeps hooks and plugin
320+ # state in this file, so merge instead of rewriting the document.
321+ mcp = config .setdefault ("mcp" , {})
322+ if not isinstance (mcp , dict ):
323+ raise InstallError (
324+ f"ZCode config mcp section must be an object: { config_path } "
325+ )
326+ servers = mcp .setdefault ("servers" , {})
327+ if not isinstance (servers , dict ):
328+ raise InstallError (
329+ f"ZCode config mcp.servers must be an object: { config_path } "
330+ )
331+ servers [SERVER_NAME ] = {
332+ "type" : "stdio" ,
333+ "command" : SERVER_COMMAND [0 ],
334+ "args" : SERVER_COMMAND [1 :],
335+ }
336+ config_path .write_text (
337+ json .dumps (config , ensure_ascii = False , indent = 2 ) + "\n " ,
338+ encoding = "utf-8" ,
339+ )
340+ _install_skills (zcode_root / "skills" )
341+
342+
343+ def _zcode_installed (config_root : Path ) -> bool :
344+ if shutil .which ("zcode" ):
345+ return True
346+ return (config_root / ".zcode" ).exists ()
347+
348+
295349def _cursor_installed (config_root : Path ) -> bool :
296350 if shutil .which ("cursor" ) or shutil .which ("cursor-agent" ):
297351 return True
@@ -311,6 +365,8 @@ def _detect_agents(config_root: Path) -> list[str]:
311365 agents .append ("claude" )
312366 if _cursor_installed (config_root ):
313367 agents .append ("cursor" )
368+ if _zcode_installed (config_root ):
369+ agents .append ("zcode" )
314370 return agents
315371
316372
@@ -339,7 +395,7 @@ def _parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
339395 parser .add_argument (
340396 "--agent" ,
341397 action = "append" ,
342- choices = ("codex" , "claude" , "cursor" ),
398+ choices = ("codex" , "claude" , "cursor" , "zcode" ),
343399 help = "Override automatic Agent detection. May be repeated." ,
344400 )
345401 parser .add_argument (
@@ -392,6 +448,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
392448 "codex" : _install_codex ,
393449 "claude" : _install_claude ,
394450 "cursor" : _install_cursor ,
451+ "zcode" : _install_zcode ,
395452 }
396453 for agent in agents :
397454 try :
@@ -415,7 +472,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
415472 )
416473 if any (agent != "codex" for agent in agents ):
417474 print (
418- "Claude/Cursor: restart, then run: /imreview <PR URL>, "
475+ "Claude/Cursor/ZCode : restart, then run: /imreview <PR URL>, "
419476 "/imcifix <issue URL>, or /imupdate <repository>"
420477 )
421478 return 0
0 commit comments