mtrx-cli 0.1.4 → 0.1.5
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 +1 -1
- package/src/matrx/__init__.py +1 -1
- package/src/matrx/cli/main.py +97 -11
package/package.json
CHANGED
package/src/matrx/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.5"
|
package/src/matrx/cli/main.py
CHANGED
|
@@ -7,6 +7,7 @@ import os
|
|
|
7
7
|
import subprocess
|
|
8
8
|
import sys
|
|
9
9
|
import threading
|
|
10
|
+
import time
|
|
10
11
|
import urllib.parse
|
|
11
12
|
import webbrowser
|
|
12
13
|
|
|
@@ -262,22 +263,103 @@ def _complete_codex_login() -> None:
|
|
|
262
263
|
raise ValueError("Codex login did not complete successfully")
|
|
263
264
|
|
|
264
265
|
|
|
266
|
+
def _resolve_matrx_launch_key(state: dict, env: dict[str, str] | None = None) -> str:
|
|
267
|
+
env = env or os.environ
|
|
268
|
+
env_key = (env.get("MTRX_KEY") or "").strip()
|
|
269
|
+
if env_key:
|
|
270
|
+
return env_key
|
|
271
|
+
workspace_binding = get_workspace_binding(state, cwd=env.get("PWD") or os.getcwd()) or {}
|
|
272
|
+
workspace_key = (workspace_binding.get("matrx_key") or "").strip()
|
|
273
|
+
if workspace_key:
|
|
274
|
+
return workspace_key
|
|
275
|
+
return (state.get("auth", {}).get("matrx", {}).get("key") or "").strip()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _matrx_request_context(state: dict) -> tuple[str, dict[str, str]]:
|
|
279
|
+
mx_key = _resolve_matrx_launch_key(state)
|
|
280
|
+
if not mx_key.startswith("mx_"):
|
|
281
|
+
raise ValueError("Matrx login required before connecting Claude Code")
|
|
282
|
+
base_url = ensure_root_url(state.get("auth", {}).get("matrx", {}).get("base_url"))
|
|
283
|
+
return base_url, {"X-Matrx-Key": mx_key}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _list_matrx_subscriptions(state: dict) -> list[dict]:
|
|
287
|
+
base_url, headers = _matrx_request_context(state)
|
|
288
|
+
try:
|
|
289
|
+
with httpx.Client(timeout=15) as client:
|
|
290
|
+
response = client.get(f"{base_url.rstrip('/')}/v1/subscriptions", headers=headers)
|
|
291
|
+
response.raise_for_status()
|
|
292
|
+
except httpx.HTTPStatusError as exc:
|
|
293
|
+
detail = exc.response.text.strip() or f"HTTP {exc.response.status_code}"
|
|
294
|
+
raise ValueError(f"Could not query Matrx subscriptions: {detail}") from exc
|
|
295
|
+
except httpx.HTTPError as exc:
|
|
296
|
+
raise ValueError(f"Could not query Matrx subscriptions: {exc}") from exc
|
|
297
|
+
|
|
298
|
+
payload = response.json()
|
|
299
|
+
subscriptions = payload.get("subscriptions")
|
|
300
|
+
if not isinstance(subscriptions, list):
|
|
301
|
+
return []
|
|
302
|
+
return [entry for entry in subscriptions if isinstance(entry, dict)]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _has_server_claude_subscription(state: dict) -> bool:
|
|
306
|
+
try:
|
|
307
|
+
subscriptions = _list_matrx_subscriptions(state)
|
|
308
|
+
except ValueError:
|
|
309
|
+
return False
|
|
310
|
+
return any((entry.get("provider") or "").strip() == "claude_code" for entry in subscriptions)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _run_claude_subscription_browser_login(state: dict) -> None:
|
|
314
|
+
base_url, headers = _matrx_request_context(state)
|
|
315
|
+
try:
|
|
316
|
+
with httpx.Client(timeout=15) as client:
|
|
317
|
+
response = client.get(
|
|
318
|
+
f"{base_url.rstrip('/')}/v1/subscriptions/claude-code/authorize",
|
|
319
|
+
headers=headers,
|
|
320
|
+
)
|
|
321
|
+
response.raise_for_status()
|
|
322
|
+
except httpx.HTTPStatusError as exc:
|
|
323
|
+
detail = exc.response.text.strip() or f"HTTP {exc.response.status_code}"
|
|
324
|
+
raise ValueError(f"Could not start Claude Code connection: {detail}") from exc
|
|
325
|
+
except httpx.HTTPError as exc:
|
|
326
|
+
raise ValueError(f"Could not start Claude Code connection: {exc}") from exc
|
|
327
|
+
|
|
328
|
+
payload = response.json()
|
|
329
|
+
auth_url = (payload.get("auth_url") or "").strip()
|
|
330
|
+
if not auth_url:
|
|
331
|
+
raise ValueError("Matrx did not return a Claude Code authorization URL")
|
|
332
|
+
|
|
333
|
+
print(f"Open this URL to connect Claude Code to Matrx:\n {auth_url}")
|
|
334
|
+
webbrowser.open(auth_url)
|
|
335
|
+
|
|
336
|
+
deadline = time.monotonic() + 300
|
|
337
|
+
while time.monotonic() < deadline:
|
|
338
|
+
if _has_server_claude_subscription(state):
|
|
339
|
+
return
|
|
340
|
+
time.sleep(2)
|
|
341
|
+
|
|
342
|
+
raise ValueError("Claude Code connection timed out")
|
|
343
|
+
|
|
344
|
+
|
|
265
345
|
def _complete_claude_login(state: dict) -> tuple[dict, bool]:
|
|
266
346
|
token = (read_claude_oauth_token() or "").strip()
|
|
267
|
-
|
|
347
|
+
imported = (state.get("auth", {}).get("claude_code", {}).get("oauth_token") or "").strip()
|
|
348
|
+
if token or imported:
|
|
349
|
+
return state, False
|
|
350
|
+
if _has_server_claude_subscription(state):
|
|
268
351
|
return state, False
|
|
269
352
|
if not _is_interactive_terminal():
|
|
270
|
-
raise ValueError(
|
|
353
|
+
raise ValueError(
|
|
354
|
+
"Claude provider connection required. "
|
|
355
|
+
"Run `mtrx login claude-code --import` or rerun interactively to connect in the browser"
|
|
356
|
+
)
|
|
271
357
|
|
|
272
|
-
print("Claude
|
|
273
|
-
if not _prompt_yes_no("
|
|
274
|
-
raise ValueError("Claude
|
|
358
|
+
print("Claude provider connection required.")
|
|
359
|
+
if not _prompt_yes_no("Open the browser-based Claude Code connection flow now?", default=True):
|
|
360
|
+
raise ValueError("Claude connection cancelled")
|
|
275
361
|
|
|
276
|
-
|
|
277
|
-
result = subprocess.run([executable, "auth", "login"], check=False)
|
|
278
|
-
token = (read_claude_oauth_token() or "").strip()
|
|
279
|
-
if result.returncode != 0 or not token:
|
|
280
|
-
raise ValueError("Claude login did not complete successfully")
|
|
362
|
+
_run_claude_subscription_browser_login(state)
|
|
281
363
|
|
|
282
364
|
return state, False
|
|
283
365
|
|
|
@@ -310,7 +392,11 @@ def _cmd_login(args) -> int:
|
|
|
310
392
|
token = read_claude_oauth_token()
|
|
311
393
|
if not token:
|
|
312
394
|
print(
|
|
313
|
-
|
|
395
|
+
(
|
|
396
|
+
f"No Claude OAuth token found at {claude_credentials_path()}. "
|
|
397
|
+
"If your Claude install no longer writes that file, use the browser-based "
|
|
398
|
+
"`mtrx claude` flow instead, or run `claude setup-token` and import that token."
|
|
399
|
+
),
|
|
314
400
|
file=sys.stderr,
|
|
315
401
|
)
|
|
316
402
|
return 1
|