1212
1313from __future__ import annotations
1414
15+ import shlex
1516import shutil
1617import sys
1718
@@ -965,15 +966,18 @@ def _get_systemd_unit(cw_cmd: str, working_dir: Path) -> str:
965966 Returns:
966967 Systemd unit file content as a string
967968 """
969+ # Quote paths to handle spaces and special characters
970+ quoted_cmd = shlex .quote (cw_cmd )
971+ quoted_dir = shlex .quote (str (working_dir ))
968972 return f"""[Unit]
969973Description=CodeWeaver MCP Server - Semantic Code Search
970974Documentation=https://github.com/knitli/codeweaver
971975After=network.target
972976
973977[Service]
974978Type=simple
975- ExecStart={ cw_cmd } start --foreground
976- WorkingDirectory={ working_dir }
979+ ExecStart={ quoted_cmd } start --foreground
980+ WorkingDirectory={ quoted_dir }
977981Restart=on-failure
978982RestartSec=5
979983
@@ -989,6 +993,17 @@ def _get_systemd_unit(cw_cmd: str, working_dir: Path) -> str:
989993"""
990994
991995
996+ def _escape_xml (text : str ) -> str :
997+ """Escape special characters for XML content."""
998+ return (
999+ text .replace ("&" , "&" )
1000+ .replace ("<" , "<" )
1001+ .replace (">" , ">" )
1002+ .replace ('"' , """ )
1003+ .replace ("'" , "'" )
1004+ )
1005+
1006+
9921007def _get_launchd_plist (cw_cmd : str , working_dir : Path ) -> str :
9931008 """Generate launchd user agent plist file content.
9941009
@@ -999,6 +1014,12 @@ def _get_launchd_plist(cw_cmd: str, working_dir: Path) -> str:
9991014 Returns:
10001015 Launchd plist file content as a string
10011016 """
1017+ # Escape paths for XML to handle special characters
1018+ escaped_cmd = _escape_xml (cw_cmd )
1019+ escaped_dir = _escape_xml (str (working_dir ))
1020+ escaped_log = _escape_xml (str (Path .home () / "Library" / "Logs" / "codeweaver.log" ))
1021+ escaped_err_log = _escape_xml (str (Path .home () / "Library" / "Logs" / "codeweaver.error.log" ))
1022+
10021023 return f"""<?xml version="1.0" encoding="UTF-8"?>
10031024<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
10041025<plist version="1.0">
@@ -1008,13 +1029,13 @@ def _get_launchd_plist(cw_cmd: str, working_dir: Path) -> str:
10081029
10091030 <key>ProgramArguments</key>
10101031 <array>
1011- <string>{ cw_cmd } </string>
1032+ <string>{ escaped_cmd } </string>
10121033 <string>start</string>
10131034 <string>--foreground</string>
10141035 </array>
10151036
10161037 <key>WorkingDirectory</key>
1017- <string>{ working_dir } </string>
1038+ <string>{ escaped_dir } </string>
10181039
10191040 <key>RunAtLoad</key>
10201041 <true/>
@@ -1026,10 +1047,10 @@ def _get_launchd_plist(cw_cmd: str, working_dir: Path) -> str:
10261047 </dict>
10271048
10281049 <key>StandardOutPath</key>
1029- <string>{ Path . home () / 'Library' / 'Logs' / 'codeweaver.log' } </string>
1050+ <string>{ escaped_log } </string>
10301051
10311052 <key>StandardErrorPath</key>
1032- <string>{ Path . home () / 'Library' / 'Logs' / 'codeweaver.error.log' } </string>
1053+ <string>{ escaped_err_log } </string>
10331054
10341055 <!-- Environment variables (uncomment and set if needed) -->
10351056 <!--
@@ -1178,6 +1199,69 @@ def _show_windows_instructions(display: StatusDisplay, cw_cmd: str, working_dir:
11781199 display .print_info ("\n Alternatively, use Task Scheduler to run CodeWeaver at login." )
11791200
11801201
1202+ def _uninstall_systemd_service (display : StatusDisplay , error_handler : CLIErrorHandler ) -> None :
1203+ """Uninstall the systemd user service on Linux."""
1204+ import subprocess
1205+
1206+ service_file = Path .home () / ".config" / "systemd" / "user" / "codeweaver.service"
1207+ if service_file .exists ():
1208+ try :
1209+ subprocess .run (["systemctl" , "--user" , "stop" , "codeweaver.service" ], capture_output = True )
1210+ subprocess .run (["systemctl" , "--user" , "disable" , "codeweaver.service" ], capture_output = True )
1211+ service_file .unlink ()
1212+ subprocess .run (["systemctl" , "--user" , "daemon-reload" ], check = True , capture_output = True )
1213+ display .print_success ("Removed systemd service" )
1214+ except Exception as e :
1215+ error_handler .handle_error (CodeWeaverError (f"Failed to remove service: { e } " ), "Service removal" )
1216+ else :
1217+ display .print_warning ("Service not installed" )
1218+
1219+
1220+ def _uninstall_launchd_service (display : StatusDisplay , error_handler : CLIErrorHandler ) -> None :
1221+ """Uninstall the launchd user agent on macOS."""
1222+ import subprocess
1223+
1224+ plist_file = Path .home () / "Library" / "LaunchAgents" / "li.knit.codeweaver.plist"
1225+ if plist_file .exists ():
1226+ try :
1227+ subprocess .run (["launchctl" , "unload" , str (plist_file )], capture_output = True )
1228+ plist_file .unlink ()
1229+ display .print_success ("Removed launchd agent" )
1230+ except Exception as e :
1231+ error_handler .handle_error (CodeWeaverError (f"Failed to remove agent: { e } " ), "Service removal" )
1232+ else :
1233+ display .print_warning ("Agent not installed" )
1234+
1235+
1236+ def _show_systemd_management_commands (display : StatusDisplay ) -> None :
1237+ """Show systemd management commands after successful installation."""
1238+ display .print_section ("Management Commands" )
1239+ display .print_list (
1240+ [
1241+ "Status: systemctl --user status codeweaver.service" ,
1242+ "Stop: systemctl --user stop codeweaver.service" ,
1243+ "Start: systemctl --user start codeweaver.service" ,
1244+ "Logs: journalctl --user -u codeweaver.service -f" ,
1245+ "Disable: systemctl --user disable codeweaver.service" ,
1246+ ],
1247+ title = "" ,
1248+ )
1249+
1250+
1251+ def _show_launchd_management_commands (display : StatusDisplay ) -> None :
1252+ """Show launchd management commands after successful installation."""
1253+ display .print_section ("Management Commands" )
1254+ display .print_list (
1255+ [
1256+ "Status: launchctl list | grep codeweaver" ,
1257+ "Stop: launchctl unload ~/Library/LaunchAgents/li.knit.codeweaver.plist" ,
1258+ "Start: launchctl load ~/Library/LaunchAgents/li.knit.codeweaver.plist" ,
1259+ "Logs: tail -f ~/Library/Logs/codeweaver.log" ,
1260+ ],
1261+ title = "" ,
1262+ )
1263+
1264+
11811265@app .command
11821266def service (
11831267 * ,
@@ -1239,33 +1323,9 @@ def service(
12391323 # Handle uninstallation
12401324 display .print_section ("Uninstalling Service" )
12411325 if platform == "linux" :
1242- import subprocess
1243-
1244- service_file = Path .home () / ".config" / "systemd" / "user" / "codeweaver.service"
1245- if service_file .exists ():
1246- try :
1247- subprocess .run (["systemctl" , "--user" , "stop" , "codeweaver.service" ], capture_output = True )
1248- subprocess .run (["systemctl" , "--user" , "disable" , "codeweaver.service" ], capture_output = True )
1249- service_file .unlink ()
1250- subprocess .run (["systemctl" , "--user" , "daemon-reload" ], check = True , capture_output = True )
1251- display .print_success ("Removed systemd service" )
1252- except Exception as e :
1253- error_handler .handle_error (CodeWeaverError (f"Failed to remove service: { e } " ), "Service removal" )
1254- else :
1255- display .print_warning ("Service not installed" )
1326+ _uninstall_systemd_service (display , error_handler )
12561327 elif platform == "darwin" :
1257- import subprocess
1258-
1259- plist_file = Path .home () / "Library" / "LaunchAgents" / "li.knit.codeweaver.plist"
1260- if plist_file .exists ():
1261- try :
1262- subprocess .run (["launchctl" , "unload" , str (plist_file )], capture_output = True )
1263- plist_file .unlink ()
1264- display .print_success ("Removed launchd agent" )
1265- except Exception as e :
1266- error_handler .handle_error (CodeWeaverError (f"Failed to remove agent: { e } " ), "Service removal" )
1267- else :
1268- display .print_warning ("Agent not installed" )
1328+ _uninstall_launchd_service (display , error_handler )
12691329 elif platform == "win32" :
12701330 display .print_info ("To remove Windows service:" )
12711331 display .print_info (" nssm remove CodeWeaver confirm" )
@@ -1275,32 +1335,11 @@ def service(
12751335 display .print_section ("Installing Service" )
12761336
12771337 if platform == "linux" :
1278- success = _install_systemd_service (display , cw_cmd , project_path , enable )
1279- if success :
1280- display .print_section ("Management Commands" )
1281- display .print_list (
1282- [
1283- "Status: systemctl --user status codeweaver.service" ,
1284- "Stop: systemctl --user stop codeweaver.service" ,
1285- "Start: systemctl --user start codeweaver.service" ,
1286- "Logs: journalctl --user -u codeweaver.service -f" ,
1287- "Disable: systemctl --user disable codeweaver.service" ,
1288- ],
1289- title = "" ,
1290- )
1338+ if _install_systemd_service (display , cw_cmd , project_path , enable ):
1339+ _show_systemd_management_commands (display )
12911340 elif platform == "darwin" :
1292- success = _install_launchd_service (display , cw_cmd , project_path , enable )
1293- if success :
1294- display .print_section ("Management Commands" )
1295- display .print_list (
1296- [
1297- "Status: launchctl list | grep codeweaver" ,
1298- "Stop: launchctl unload ~/Library/LaunchAgents/li.knit.codeweaver.plist" ,
1299- "Start: launchctl load ~/Library/LaunchAgents/li.knit.codeweaver.plist" ,
1300- "Logs: tail -f ~/Library/Logs/codeweaver.log" ,
1301- ],
1302- title = "" ,
1303- )
1341+ if _install_launchd_service (display , cw_cmd , project_path , enable ):
1342+ _show_launchd_management_commands (display )
13041343 elif platform == "win32" :
13051344 _show_windows_instructions (display , cw_cmd , project_path )
13061345 else :
0 commit comments