mtrx-cli 0.1.5 → 0.1.6
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/launcher.py +11 -1
- package/src/matrx/cli/main.py +9 -90
package/package.json
CHANGED
package/src/matrx/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.6"
|
|
@@ -221,7 +221,16 @@ def codex_auth_path() -> Path:
|
|
|
221
221
|
|
|
222
222
|
def claude_oauth_available() -> bool:
|
|
223
223
|
token = read_claude_oauth_token()
|
|
224
|
-
|
|
224
|
+
if token:
|
|
225
|
+
return True
|
|
226
|
+
state = _read_claude_app_state()
|
|
227
|
+
oauth_account = state.get("oauthAccount")
|
|
228
|
+
if not isinstance(oauth_account, dict):
|
|
229
|
+
return False
|
|
230
|
+
return bool(
|
|
231
|
+
(oauth_account.get("accountUuid") or "").strip()
|
|
232
|
+
or (oauth_account.get("emailAddress") or "").strip()
|
|
233
|
+
)
|
|
225
234
|
|
|
226
235
|
|
|
227
236
|
def read_claude_oauth_token() -> str | None:
|
|
@@ -500,6 +509,7 @@ def _build_claude_env(
|
|
|
500
509
|
env_b64 = base64.b64encode(json.dumps(env_snap).encode()).decode() if env_snap else ""
|
|
501
510
|
custom_headers = "\n".join(
|
|
502
511
|
[
|
|
512
|
+
f"x-matrx-key: {mx_key}",
|
|
503
513
|
"x-matrx-agent-id: claude-cli",
|
|
504
514
|
"x-matrx-provider: claude_code",
|
|
505
515
|
f"x-matrx-session-id: {session_id}",
|
package/src/matrx/cli/main.py
CHANGED
|
@@ -7,7 +7,6 @@ import os
|
|
|
7
7
|
import subprocess
|
|
8
8
|
import sys
|
|
9
9
|
import threading
|
|
10
|
-
import time
|
|
11
10
|
import urllib.parse
|
|
12
11
|
import webbrowser
|
|
13
12
|
|
|
@@ -263,103 +262,23 @@ def _complete_codex_login() -> None:
|
|
|
263
262
|
raise ValueError("Codex login did not complete successfully")
|
|
264
263
|
|
|
265
264
|
|
|
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
|
-
|
|
345
265
|
def _complete_claude_login(state: dict) -> tuple[dict, bool]:
|
|
346
|
-
token = (read_claude_oauth_token() or "").strip()
|
|
347
266
|
imported = (state.get("auth", {}).get("claude_code", {}).get("oauth_token") or "").strip()
|
|
348
|
-
if
|
|
349
|
-
return state, False
|
|
350
|
-
if _has_server_claude_subscription(state):
|
|
267
|
+
if imported or claude_oauth_available():
|
|
351
268
|
return state, False
|
|
352
269
|
if not _is_interactive_terminal():
|
|
353
270
|
raise ValueError(
|
|
354
|
-
"Claude
|
|
355
|
-
"Run `mtrx login claude-code --import` or rerun interactively to connect in the browser"
|
|
271
|
+
"Claude login required. Run: claude auth login"
|
|
356
272
|
)
|
|
357
273
|
|
|
358
|
-
print("Claude
|
|
359
|
-
if not _prompt_yes_no("
|
|
360
|
-
raise ValueError("Claude
|
|
274
|
+
print("Claude login required.")
|
|
275
|
+
if not _prompt_yes_no("Run `claude auth login` now?", default=True):
|
|
276
|
+
raise ValueError("Claude login cancelled")
|
|
361
277
|
|
|
362
|
-
|
|
278
|
+
executable = find_executable("claude") or "claude"
|
|
279
|
+
result = subprocess.run([executable, "auth", "login"], check=False)
|
|
280
|
+
if result.returncode != 0 or not claude_oauth_available():
|
|
281
|
+
raise ValueError("Claude login did not complete successfully")
|
|
363
282
|
|
|
364
283
|
return state, False
|
|
365
284
|
|