conduct-cli 0.7.26__tar.gz → 0.7.28__tar.gz
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.
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/PKG-INFO +1 -1
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/pyproject.toml +1 -1
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/guard.py +6 -2
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/main.py +41 -2
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli.egg-info/PKG-INFO +1 -1
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/README.md +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/setup.cfg +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/setup.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/guardmcp.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/hooks/__init__.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/hooks/base.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/hooks/posttooluse.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/hooks/precompact.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/hooks/pretooluse.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/hooks/session_parser.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/hooks/session_report_push.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/hooks/session_start.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/hooks/stop.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/log_util.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/mcp_server.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/memory.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/paxel.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli/tool_groups.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli.egg-info/requires.txt +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/src/conduct_cli.egg-info/top_level.txt +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_advisory_and_hook.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_bash_operator_signature.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_blast_radius.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_command_word_matcher.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_drain_daemon_health.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_guard_policy.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_guard_savings.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_hook_dispatch.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_journal_drain.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_log_util.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_proxy_coverage.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_proxy_env.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_signed_policy.py +0 -0
- {conduct_cli-0.7.26 → conduct_cli-0.7.28}/tests/test_switch.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "conduct-cli"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.28"
|
|
8
8
|
description = "CLI for Conduct AI — secure, govern, install agents, manage projects, run tests"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -1266,8 +1266,12 @@ def cmd_guard_sync(args):
|
|
|
1266
1266
|
fresh_agent_token = installed.get("agent_token") or ""
|
|
1267
1267
|
if fresh_agent_token:
|
|
1268
1268
|
cfg["agent_token"] = fresh_agent_token
|
|
1269
|
-
|
|
1270
|
-
|
|
1269
|
+
if installed.get("user_email"):
|
|
1270
|
+
cfg["user_email"] = installed["user_email"]
|
|
1271
|
+
if installed.get("clerk_user_id"):
|
|
1272
|
+
cfg["clerk_user_id"] = installed["clerk_user_id"]
|
|
1273
|
+
_save_guard_config(cfg)
|
|
1274
|
+
if not fresh_agent_token:
|
|
1271
1275
|
print(f" {YELLOW}Warning: server returned no token — proxy env may be stale{RESET}")
|
|
1272
1276
|
except Exception as e:
|
|
1273
1277
|
print(f" {YELLOW}Warning: could not refresh token ({e}) — using cached value{RESET}")
|
|
@@ -6,6 +6,7 @@ import os
|
|
|
6
6
|
import subprocess
|
|
7
7
|
import sys
|
|
8
8
|
import time
|
|
9
|
+
import urllib.error
|
|
9
10
|
import urllib.parse
|
|
10
11
|
import urllib.request
|
|
11
12
|
from pathlib import Path
|
|
@@ -426,6 +427,36 @@ def _find_free_port() -> int:
|
|
|
426
427
|
return s.getsockname()[1]
|
|
427
428
|
|
|
428
429
|
|
|
430
|
+
def _exchange_clerk_token(api_url: str, clerk_token: str, workspace_id: str) -> dict:
|
|
431
|
+
"""POST /token (RFC 8693) to exchange a Clerk JWT for cond_agt_* + cond_ref_*."""
|
|
432
|
+
_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange"
|
|
433
|
+
_TYPE = "urn:ietf:params:oauth:token-type:jwt"
|
|
434
|
+
body = urllib.parse.urlencode({
|
|
435
|
+
"grant_type": _GRANT,
|
|
436
|
+
"subject_token": clerk_token,
|
|
437
|
+
"subject_token_type": _TYPE,
|
|
438
|
+
"resource": workspace_id,
|
|
439
|
+
}).encode()
|
|
440
|
+
req = urllib.request.Request(
|
|
441
|
+
f"{api_url}/token",
|
|
442
|
+
data=body,
|
|
443
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
444
|
+
method="POST",
|
|
445
|
+
)
|
|
446
|
+
try:
|
|
447
|
+
with urllib.request.urlopen(req, timeout=15) as r:
|
|
448
|
+
data = json.loads(r.read())
|
|
449
|
+
return {
|
|
450
|
+
"agent_token": data["access_token"],
|
|
451
|
+
"refresh_token": data.get("refresh_token", ""),
|
|
452
|
+
"workspace_id": data.get("workspace_id", workspace_id),
|
|
453
|
+
}
|
|
454
|
+
except urllib.error.HTTPError as e:
|
|
455
|
+
body_text = e.read().decode(errors="replace")
|
|
456
|
+
print(f"{RED}Token exchange failed ({e.code}): {body_text[:120]}{RESET}")
|
|
457
|
+
sys.exit(1)
|
|
458
|
+
|
|
459
|
+
|
|
429
460
|
def _web_login_flow(api_url: str, web_url: str) -> dict:
|
|
430
461
|
"""Open browser → localhost callback → return {agent_token, refresh_token, workspace_id}."""
|
|
431
462
|
import http.server
|
|
@@ -480,8 +511,16 @@ def _web_login_flow(api_url: str, web_url: str) -> dict:
|
|
|
480
511
|
|
|
481
512
|
server.shutdown()
|
|
482
513
|
|
|
483
|
-
if not result.get("
|
|
484
|
-
print(f"{RED}Login failed — missing
|
|
514
|
+
if not result.get("workspace_id"):
|
|
515
|
+
print(f"{RED}Login failed — missing workspace in callback. Try again.{RESET}")
|
|
516
|
+
sys.exit(1)
|
|
517
|
+
|
|
518
|
+
# RFC 8693: web page relays Clerk JWT; CLI exchanges it at POST /token
|
|
519
|
+
if result.get("clerk_token") and not result.get("agent_token"):
|
|
520
|
+
result = _exchange_clerk_token(api_url, result["clerk_token"], result["workspace_id"])
|
|
521
|
+
|
|
522
|
+
if not result.get("agent_token"):
|
|
523
|
+
print(f"{RED}Login failed — token exchange failed. Try again.{RESET}")
|
|
485
524
|
sys.exit(1)
|
|
486
525
|
|
|
487
526
|
return result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|