mtrx-cli 0.1.13 → 0.1.15
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.
- package/package.json +3 -1
- package/src/matrx/__init__.py +1 -1
- package/src/matrx/cli/cursor_hooks.py +186 -0
- package/src/matrx/cli/cursor_launcher.py +106 -0
- package/src/matrx/cli/main.py +30 -85
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mtrx-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "MATRX CLI for routing Codex, Claude, and Cursor through Matrx",
|
|
5
5
|
"homepage": "https://mtrx.so",
|
|
6
6
|
"repository": {
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
"src/matrx/cli/__init__.py",
|
|
28
28
|
"src/matrx/cli/cursor_ca.py",
|
|
29
29
|
"src/matrx/cli/cursor_config.py",
|
|
30
|
+
"src/matrx/cli/cursor_hooks.py",
|
|
30
31
|
"src/matrx/cli/cursor_daemon.py",
|
|
32
|
+
"src/matrx/cli/cursor_launcher.py",
|
|
31
33
|
"src/matrx/cli/cursor_proxy.py",
|
|
32
34
|
"src/matrx/cli/cursor_service.py",
|
|
33
35
|
"src/matrx/cli/launcher.py",
|
package/src/matrx/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.15"
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cursor Hooks integration for MTRX telemetry.
|
|
3
|
+
|
|
4
|
+
Uses Cursor's official Hooks API (https://cursor.com/docs/hooks) to send
|
|
5
|
+
session/agent events to MTRX. Works regardless of how Cursor was launched
|
|
6
|
+
(Dock, Spotlight, CLI) - same clean model as Claude/Codex.
|
|
7
|
+
|
|
8
|
+
Configures ~/.cursor/hooks.json and a Python script that POSTs to
|
|
9
|
+
POST /v1/telemetry/cursor/hooks.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from matrx.cli.state import config_dir
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
_CURSOR_HOME = Path.home() / ".cursor"
|
|
24
|
+
_HOOKS_JSON = _CURSOR_HOME / "hooks.json"
|
|
25
|
+
_HOOKS_DIR = _CURSOR_HOME / "hooks"
|
|
26
|
+
_MTRX_HOOK_SCRIPT = _HOOKS_DIR / "mtrx-telemetry.py"
|
|
27
|
+
_CONFIG_PATH = "cursor-hooks-config.json"
|
|
28
|
+
|
|
29
|
+
_MTRX_HOOK_MARKER = "# MTRX cursor hooks (managed by mtrx cursor)"
|
|
30
|
+
_MTRX_HOOKS_KEY = "_mtrx_managed_hooks"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _hooks_config_path() -> Path:
|
|
34
|
+
return config_dir() / _CONFIG_PATH
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _read_hooks_json() -> dict:
|
|
38
|
+
if not _HOOKS_JSON.exists():
|
|
39
|
+
return {"version": 1, "hooks": {}}
|
|
40
|
+
try:
|
|
41
|
+
return json.loads(_HOOKS_JSON.read_text(encoding="utf-8"))
|
|
42
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
43
|
+
logger.debug("cursor_hooks: could not read hooks.json: %s", exc)
|
|
44
|
+
return {"version": 1, "hooks": {}}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _write_hooks_json(data: dict) -> bool:
|
|
48
|
+
try:
|
|
49
|
+
_HOOKS_JSON.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
_HOOKS_JSON.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
51
|
+
return True
|
|
52
|
+
except OSError as exc:
|
|
53
|
+
logger.debug("cursor_hooks: could not write hooks.json: %s", exc)
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _hook_script_content() -> str:
|
|
58
|
+
return '''#!/usr/bin/env python3
|
|
59
|
+
"""MTRX Cursor hooks telemetry — forwards events to MTRX. Managed by mtrx cursor."""
|
|
60
|
+
from __future__ import annotations
|
|
61
|
+
|
|
62
|
+
import json
|
|
63
|
+
import os
|
|
64
|
+
import sys
|
|
65
|
+
from pathlib import Path
|
|
66
|
+
|
|
67
|
+
def _config_path() -> Path:
|
|
68
|
+
config_dir = Path(os.environ.get("MTRX_CONFIG_DIR", Path.home() / ".config" / "mtrx"))
|
|
69
|
+
return config_dir / "cursor-hooks-config.json"
|
|
70
|
+
|
|
71
|
+
def main() -> None:
|
|
72
|
+
try:
|
|
73
|
+
payload = json.load(sys.stdin)
|
|
74
|
+
except (json.JSONDecodeError, EOFError):
|
|
75
|
+
print("{}")
|
|
76
|
+
return
|
|
77
|
+
cfg_path = _config_path()
|
|
78
|
+
if not cfg_path.exists():
|
|
79
|
+
print("{}")
|
|
80
|
+
return
|
|
81
|
+
try:
|
|
82
|
+
cfg = json.loads(cfg_path.read_text(encoding="utf-8"))
|
|
83
|
+
except (json.JSONDecodeError, OSError):
|
|
84
|
+
print("{}")
|
|
85
|
+
return
|
|
86
|
+
url = (cfg.get("matrx_base_url") or "").rstrip("/") + "/v1/telemetry/cursor/hooks"
|
|
87
|
+
key = (cfg.get("matrx_key") or "").strip()
|
|
88
|
+
if not url or not key or not key.startswith("mx_"):
|
|
89
|
+
print("{}")
|
|
90
|
+
return
|
|
91
|
+
try:
|
|
92
|
+
import urllib.request
|
|
93
|
+
req = urllib.request.Request(
|
|
94
|
+
url,
|
|
95
|
+
data=json.dumps(payload).encode(),
|
|
96
|
+
headers={"Content-Type": "application/json", "X-Matrx-Key": key},
|
|
97
|
+
method="POST",
|
|
98
|
+
)
|
|
99
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
100
|
+
pass
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
print("{}")
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
main()
|
|
107
|
+
'''
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def install_mtrx_hooks(matrx_key: str, matrx_base_url: str) -> bool:
|
|
111
|
+
"""
|
|
112
|
+
Install MTRX hooks: create script, config, and merge into hooks.json.
|
|
113
|
+
|
|
114
|
+
Hooks: sessionEnd, stop — fire-and-forget telemetry to MTRX.
|
|
115
|
+
"""
|
|
116
|
+
base = (matrx_base_url or "").rstrip("/")
|
|
117
|
+
if not base or not matrx_key or not matrx_key.startswith("mx_"):
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
# Write hook script
|
|
121
|
+
_HOOKS_DIR.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
script_path = _HOOKS_DIR / "mtrx-telemetry.py"
|
|
123
|
+
try:
|
|
124
|
+
script_path.write_text(_hook_script_content(), encoding="utf-8")
|
|
125
|
+
script_path.chmod(0o755)
|
|
126
|
+
except OSError as exc:
|
|
127
|
+
logger.debug("cursor_hooks: could not write script: %s", exc)
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
# Write config (script reads this at runtime)
|
|
131
|
+
cfg_path = _hooks_config_path()
|
|
132
|
+
config_dir().mkdir(parents=True, exist_ok=True)
|
|
133
|
+
try:
|
|
134
|
+
cfg_path.write_text(
|
|
135
|
+
json.dumps({"matrx_key": matrx_key, "matrx_base_url": base}),
|
|
136
|
+
encoding="utf-8",
|
|
137
|
+
)
|
|
138
|
+
cfg_path.chmod(0o600)
|
|
139
|
+
except OSError as exc:
|
|
140
|
+
logger.debug("cursor_hooks: could not write config: %s", exc)
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
# Merge into hooks.json — use ./hooks/mtrx-telemetry.py (relative to ~/.cursor/)
|
|
144
|
+
hooks = _read_hooks_json()
|
|
145
|
+
if "hooks" not in hooks:
|
|
146
|
+
hooks["hooks"] = {}
|
|
147
|
+
if "version" not in hooks:
|
|
148
|
+
hooks["version"] = 1
|
|
149
|
+
|
|
150
|
+
managed = {
|
|
151
|
+
"sessionEnd": [{"command": "./hooks/mtrx-telemetry.py"}],
|
|
152
|
+
"stop": [{"command": "./hooks/mtrx-telemetry.py"}],
|
|
153
|
+
}
|
|
154
|
+
for hook_name, defs in managed.items():
|
|
155
|
+
existing = hooks["hooks"].get(hook_name) or []
|
|
156
|
+
# Avoid duplicate
|
|
157
|
+
our_cmd = "./hooks/mtrx-telemetry.py"
|
|
158
|
+
if not any(d.get("command") == our_cmd for d in existing):
|
|
159
|
+
existing.extend(defs)
|
|
160
|
+
hooks["hooks"][hook_name] = existing
|
|
161
|
+
|
|
162
|
+
return _write_hooks_json(hooks)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def remove_mtrx_hooks() -> bool:
|
|
166
|
+
"""Remove MTRX hooks from hooks.json and delete config + script."""
|
|
167
|
+
removed = False
|
|
168
|
+
hooks = _read_hooks_json()
|
|
169
|
+
if "hooks" in hooks:
|
|
170
|
+
our_cmd = "./hooks/mtrx-telemetry.py"
|
|
171
|
+
for hook_name in ("sessionEnd", "stop"):
|
|
172
|
+
defs = hooks["hooks"].get(hook_name) or []
|
|
173
|
+
new_defs = [d for d in defs if d.get("command") != our_cmd]
|
|
174
|
+
if len(new_defs) != len(defs):
|
|
175
|
+
removed = True
|
|
176
|
+
hooks["hooks"][hook_name] = new_defs if new_defs else []
|
|
177
|
+
if removed:
|
|
178
|
+
_write_hooks_json(hooks)
|
|
179
|
+
_hooks_config_path().unlink(missing_ok=True)
|
|
180
|
+
_MTRX_HOOK_SCRIPT.unlink(missing_ok=True)
|
|
181
|
+
return removed
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def is_mtrx_hooks_installed() -> bool:
|
|
185
|
+
"""Return True if MTRX hooks are configured."""
|
|
186
|
+
return _hooks_config_path().exists() and _MTRX_HOOK_SCRIPT.exists()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cross-platform launcher for Cursor IDE with proxy environment variables.
|
|
3
|
+
|
|
4
|
+
Cursor must be started with HTTP_PROXY, HTTPS_PROXY, and NODE_EXTRA_CA_CERTS
|
|
5
|
+
for the MITM proxy to work. Launching from Dock/Spotlight does not pass
|
|
6
|
+
these env vars; this module finds the Cursor executable and launches it
|
|
7
|
+
with the correct environment on any platform.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import platform
|
|
15
|
+
import shutil
|
|
16
|
+
import subprocess
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _cursor_executable_darwin() -> str | None:
|
|
23
|
+
"""macOS: /Applications/Cursor.app/Contents/MacOS/Cursor."""
|
|
24
|
+
app = Path("/Applications/Cursor.app")
|
|
25
|
+
exe = app / "Contents" / "MacOS" / "Cursor"
|
|
26
|
+
if exe.exists():
|
|
27
|
+
return str(exe)
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _cursor_executable_linux() -> str | None:
|
|
32
|
+
"""Linux: cursor in PATH or common install locations."""
|
|
33
|
+
cursor = shutil.which("cursor")
|
|
34
|
+
if cursor:
|
|
35
|
+
return cursor
|
|
36
|
+
for path in (
|
|
37
|
+
Path.home() / ".local" / "share" / "cursor" / "bin" / "cursor",
|
|
38
|
+
Path.home() / ".local" / "bin" / "cursor",
|
|
39
|
+
):
|
|
40
|
+
if path.exists():
|
|
41
|
+
return str(path)
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _cursor_executable_windows() -> str | None:
|
|
46
|
+
"""Windows: Cursor.exe in Local AppData."""
|
|
47
|
+
local = os.environ.get("LOCALAPPDATA", "")
|
|
48
|
+
if local:
|
|
49
|
+
for parts in (("Programs", "cursor", "Cursor.exe"), ("Cursor", "Cursor.exe")):
|
|
50
|
+
exe = Path(local) / Path(*parts)
|
|
51
|
+
if exe.exists():
|
|
52
|
+
return str(exe)
|
|
53
|
+
alt = Path(local) / "cursor" / "Cursor.exe"
|
|
54
|
+
if alt.exists():
|
|
55
|
+
return str(alt)
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def find_cursor_executable() -> str | None:
|
|
60
|
+
"""Return the path to the Cursor executable for the current platform."""
|
|
61
|
+
system = platform.system()
|
|
62
|
+
if system == "Darwin":
|
|
63
|
+
return _cursor_executable_darwin()
|
|
64
|
+
if system == "Linux":
|
|
65
|
+
return _cursor_executable_linux()
|
|
66
|
+
if system == "Windows":
|
|
67
|
+
return _cursor_executable_windows()
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def launch_cursor_with_proxy(
|
|
72
|
+
proxy_url: str,
|
|
73
|
+
ca_cert_path: str,
|
|
74
|
+
) -> bool:
|
|
75
|
+
"""
|
|
76
|
+
Launch Cursor with HTTP_PROXY, HTTPS_PROXY, and NODE_EXTRA_CA_CERTS set.
|
|
77
|
+
|
|
78
|
+
Returns True if Cursor was launched, False if executable not found.
|
|
79
|
+
"""
|
|
80
|
+
exe = find_cursor_executable()
|
|
81
|
+
if not exe:
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
env = os.environ.copy()
|
|
85
|
+
env["HTTP_PROXY"] = proxy_url
|
|
86
|
+
env["HTTPS_PROXY"] = proxy_url
|
|
87
|
+
env["NODE_EXTRA_CA_CERTS"] = str(ca_cert_path)
|
|
88
|
+
|
|
89
|
+
kwargs: dict = {}
|
|
90
|
+
if platform.system() == "Windows":
|
|
91
|
+
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
subprocess.Popen(
|
|
95
|
+
[exe],
|
|
96
|
+
env=env,
|
|
97
|
+
stdin=subprocess.DEVNULL,
|
|
98
|
+
stdout=subprocess.DEVNULL,
|
|
99
|
+
stderr=subprocess.DEVNULL,
|
|
100
|
+
start_new_session=(platform.system() != "Windows"),
|
|
101
|
+
**kwargs,
|
|
102
|
+
)
|
|
103
|
+
return True
|
|
104
|
+
except OSError as exc:
|
|
105
|
+
logger.warning("Failed to launch Cursor: %s", exc)
|
|
106
|
+
return False
|
package/src/matrx/cli/main.py
CHANGED
|
@@ -120,6 +120,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
120
120
|
cursor.add_argument("--route", choices=["direct", "matrx"])
|
|
121
121
|
cursor.add_argument("--status", action="store_true", help="Check proxy status")
|
|
122
122
|
cursor.add_argument("--stop", action="store_true", help="Stop the proxy service")
|
|
123
|
+
cursor.add_argument(
|
|
124
|
+
"--launch",
|
|
125
|
+
action="store_true",
|
|
126
|
+
help="Launch Cursor with proxy env (required for traffic to flow)",
|
|
127
|
+
)
|
|
123
128
|
|
|
124
129
|
return parser
|
|
125
130
|
|
|
@@ -501,8 +506,13 @@ def _cmd_use(args) -> int:
|
|
|
501
506
|
def _restore_cursor_if_needed() -> None:
|
|
502
507
|
import json as _json
|
|
503
508
|
|
|
509
|
+
from matrx.cli.cursor_hooks import remove_mtrx_hooks
|
|
504
510
|
from matrx.cli.cursor_service import is_proxy_running, uninstall_service
|
|
505
511
|
|
|
512
|
+
# Remove MTRX Cursor hooks
|
|
513
|
+
if remove_mtrx_hooks():
|
|
514
|
+
print("Cursor hooks removed.")
|
|
515
|
+
|
|
506
516
|
# Stop the MITM proxy service if it's running
|
|
507
517
|
if is_proxy_running():
|
|
508
518
|
uninstall_service()
|
|
@@ -831,39 +841,23 @@ def _cmd_launch(tool: str, route: str | None, remainder: list[str]) -> int:
|
|
|
831
841
|
|
|
832
842
|
|
|
833
843
|
def _cmd_cursor(args) -> int:
|
|
834
|
-
import
|
|
835
|
-
|
|
836
|
-
from matrx.cli.cursor_ca import (
|
|
837
|
-
ca_cert_path,
|
|
838
|
-
ca_exists,
|
|
839
|
-
generate_ca,
|
|
840
|
-
is_ca_trusted,
|
|
841
|
-
trust_ca_system,
|
|
842
|
-
)
|
|
843
|
-
from matrx.cli.cursor_proxy import DEFAULT_PORT, PROXY_HOST
|
|
844
|
-
from matrx.cli.cursor_service import (
|
|
845
|
-
get_proxy_status,
|
|
846
|
-
install_service,
|
|
847
|
-
is_proxy_running,
|
|
848
|
-
uninstall_service,
|
|
849
|
-
)
|
|
844
|
+
from matrx.cli.cursor_hooks import install_mtrx_hooks, is_mtrx_hooks_installed
|
|
850
845
|
|
|
851
846
|
route = args.route
|
|
852
847
|
|
|
853
|
-
# --status:
|
|
848
|
+
# --status: report hooks status
|
|
854
849
|
if args.status:
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
print("
|
|
858
|
-
print(f" requests processed: {status.get('requests', '?')}")
|
|
850
|
+
if is_mtrx_hooks_installed():
|
|
851
|
+
print("MTRX Cursor hooks: active")
|
|
852
|
+
print(" sessionEnd, stop → telemetry to MTRX")
|
|
859
853
|
else:
|
|
860
|
-
print("MTRX Cursor
|
|
854
|
+
print("MTRX Cursor hooks: not installed")
|
|
861
855
|
return 0
|
|
862
856
|
|
|
863
|
-
# --stop: tear down
|
|
857
|
+
# --stop: tear down
|
|
864
858
|
if args.stop:
|
|
865
859
|
_restore_cursor_if_needed()
|
|
866
|
-
print("Cursor route set to direct — MTRX
|
|
860
|
+
print("Cursor route set to direct — MTRX hooks disabled.")
|
|
867
861
|
return 0
|
|
868
862
|
|
|
869
863
|
state = load_state()
|
|
@@ -872,12 +866,12 @@ def _cmd_cursor(args) -> int:
|
|
|
872
866
|
|
|
873
867
|
if effective_route == "direct":
|
|
874
868
|
_restore_cursor_if_needed()
|
|
875
|
-
print("Cursor route set to direct — MTRX
|
|
869
|
+
print("Cursor route set to direct — MTRX hooks disabled.")
|
|
876
870
|
if cursor_is_running():
|
|
877
871
|
print(" Restart Cursor for settings to take effect.")
|
|
878
872
|
return 0
|
|
879
873
|
|
|
880
|
-
# --- matrx route:
|
|
874
|
+
# --- matrx route: install Cursor Hooks (official, works like Claude/Codex) ---
|
|
881
875
|
|
|
882
876
|
try:
|
|
883
877
|
state, login_changed = _complete_matrx_login(state)
|
|
@@ -910,66 +904,17 @@ def _cmd_cursor(args) -> int:
|
|
|
910
904
|
"Use `mtrx use cursor direct` to opt out.",
|
|
911
905
|
)
|
|
912
906
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
print("
|
|
916
|
-
|
|
917
|
-
print(
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
print("Trusting MTRX CA certificate (may require password)...")
|
|
922
|
-
if trust_ca_system():
|
|
923
|
-
print(" CA trusted in system keychain.")
|
|
924
|
-
else:
|
|
925
|
-
print(
|
|
926
|
-
f" [warn] Could not auto-trust CA. Cursor needs NODE_EXTRA_CA_CERTS={ca_cert_path()}"
|
|
927
|
-
)
|
|
928
|
-
print(
|
|
929
|
-
" You can manually trust it or set the env var before launching Cursor."
|
|
930
|
-
)
|
|
931
|
-
|
|
932
|
-
# Step 3: Install and start the proxy service
|
|
933
|
-
proxy_url = f"http://{PROXY_HOST}:{DEFAULT_PORT}"
|
|
934
|
-
if is_proxy_running():
|
|
935
|
-
print(f"MTRX proxy already running on {proxy_url}")
|
|
936
|
-
else:
|
|
937
|
-
print("Starting MTRX Cursor proxy service...")
|
|
938
|
-
if install_service(
|
|
939
|
-
matrx_key=mx_key,
|
|
940
|
-
matrx_base_url=matrx_base_url,
|
|
941
|
-
host=PROXY_HOST,
|
|
942
|
-
port=DEFAULT_PORT,
|
|
943
|
-
):
|
|
944
|
-
print(f" Proxy running on {proxy_url}")
|
|
945
|
-
else:
|
|
946
|
-
print("[warn] Proxy service may not have started. Check logs at:")
|
|
947
|
-
print(f" {config_dir() / 'logs' / 'cursor-proxy.err.log'}")
|
|
948
|
-
|
|
949
|
-
# Step 4: Configure Cursor's settings.json
|
|
950
|
-
conf_dir = config_dir()
|
|
951
|
-
conf_dir.mkdir(parents=True, exist_ok=True)
|
|
952
|
-
|
|
953
|
-
previous = configure_cursor_proxy_settings(
|
|
954
|
-
proxy_url=proxy_url,
|
|
955
|
-
ca_cert_path=str(ca_cert_path()),
|
|
956
|
-
)
|
|
957
|
-
prev_path = conf_dir / "cursor-proxy-previous-settings.json"
|
|
958
|
-
prev_path.write_text(_json.dumps(previous), encoding="utf-8")
|
|
959
|
-
|
|
960
|
-
print()
|
|
961
|
-
print("Cursor configured to route ALL traffic through MTRX.")
|
|
962
|
-
print(f" proxy: {proxy_url}")
|
|
963
|
-
print(f" ca_cert: {ca_cert_path()}")
|
|
964
|
-
print(f" telemetry: {matrx_base_url}/v1/telemetry/cursor")
|
|
965
|
-
print()
|
|
966
|
-
if cursor_is_running():
|
|
967
|
-
print(" Cursor is running — restart it for settings to take effect.")
|
|
907
|
+
if install_mtrx_hooks(mx_key, matrx_base_url):
|
|
908
|
+
print()
|
|
909
|
+
print("Cursor configured with MTRX hooks — Chat & Agent usage flows to MTRX.")
|
|
910
|
+
print(f" telemetry: {matrx_base_url}/v1/telemetry/cursor/hooks")
|
|
911
|
+
print()
|
|
912
|
+
print(" Works with Cursor from Dock, Spotlight, or CLI — no special launch needed.")
|
|
913
|
+
print(" Check status: mtrx cursor --status")
|
|
914
|
+
print(" To disable: mtrx use cursor direct")
|
|
968
915
|
else:
|
|
969
|
-
print("
|
|
970
|
-
|
|
971
|
-
print(" Check status: mtrx cursor --status")
|
|
972
|
-
print(" To disable: mtrx use cursor direct")
|
|
916
|
+
print("[warn] Could not install Cursor hooks. Check ~/.cursor/ is writable.", file=sys.stderr)
|
|
917
|
+
return 1
|
|
973
918
|
return 0
|
|
974
919
|
|
|
975
920
|
|