conduct-cli 0.7.48__tar.gz → 0.8.1__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.
Files changed (48) hide show
  1. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/PKG-INFO +1 -1
  2. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/pyproject.toml +1 -1
  3. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/api.py +2 -4
  4. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/guard.py +73 -75
  5. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/hooks/base.py +3 -3
  6. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/hooks/posttooluse.py +2 -2
  7. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/hooks/pretooluse.py +2 -2
  8. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/hooks/session_report_push.py +3 -3
  9. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/hooks/session_start.py +2 -2
  10. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/hooks/stop.py +2 -2
  11. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/main.py +70 -77
  12. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/mcp_server.py +1 -1
  13. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/memory.py +4 -4
  14. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli.egg-info/PKG-INFO +1 -1
  15. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/README.md +0 -0
  16. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/setup.cfg +0 -0
  17. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/setup.py +0 -0
  18. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/__init__.py +0 -0
  19. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/guardmcp.py +0 -0
  20. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/hooks/__init__.py +0 -0
  21. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/hooks/precompact.py +0 -0
  22. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/hooks/session_parser.py +0 -0
  23. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/log_util.py +0 -0
  24. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/paxel.py +0 -0
  25. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli/tool_groups.py +0 -0
  26. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
  27. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
  28. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli.egg-info/entry_points.txt +0 -0
  29. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli.egg-info/requires.txt +0 -0
  30. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/src/conduct_cli.egg-info/top_level.txt +0 -0
  31. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_advisory_and_hook.py +0 -0
  32. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_api_client.py +0 -0
  33. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_bash_operator_signature.py +0 -0
  34. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_blast_radius.py +0 -0
  35. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_cmd_run.py +0 -0
  36. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_command_word_matcher.py +0 -0
  37. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_drain_daemon_health.py +0 -0
  38. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_guard_policy.py +0 -0
  39. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_guard_savings.py +0 -0
  40. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_hook_dispatch.py +0 -0
  41. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_instruction_files.py +0 -0
  42. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_journal_drain.py +0 -0
  43. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_log_util.py +0 -0
  44. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_proxy_coverage.py +0 -0
  45. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_proxy_env.py +0 -0
  46. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_signed_policy.py +0 -0
  47. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_switch.py +0 -0
  48. {conduct_cli-0.7.48 → conduct_cli-0.8.1}/tests/test_token_lifecycle.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.7.48
3
+ Version: 0.8.1
4
4
  Summary: CLI for Conduct AI — secure, govern, install agents, manage projects, run tests
5
5
  Author-email: Conduct AI <hello@conductai.ai>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "conduct-cli"
7
- version = "0.7.48"
7
+ version = "0.8.1"
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" }
@@ -22,11 +22,9 @@ def _friendly(code: int, detail: str) -> str:
22
22
  return detail
23
23
 
24
24
 
25
- def headers(workspace_id: str, token=None, content_type="application/json", api_key=None) -> dict:
25
+ def headers(workspace_id: str, token=None, content_type="application/json") -> dict:
26
26
  h = {"Content-Type": content_type, "X-Workspace-Id": workspace_id}
27
- if api_key:
28
- h["X-Api-Key"] = api_key
29
- elif token:
27
+ if token:
30
28
  h["Authorization"] = f"Bearer {token}"
31
29
  return h
32
30
 
@@ -276,11 +276,12 @@ _PERSONA_LABELS = {
276
276
  }
277
277
 
278
278
 
279
- def _ensure_persona(workspace_id: str, api_key: str, base_url: str) -> str:
279
+ def _ensure_persona(workspace_id: str, base_url: str) -> str:
280
280
  """Prompt for persona if none is set yet. Saves choice to guard config and API.
281
281
 
282
282
  Returns the active persona name. Skips prompt silently if already set.
283
283
  """
284
+ agent_token = _default_agent_token() or ""
284
285
  cfg = _load_guard_config()
285
286
  if cfg.get("persona"):
286
287
  return cfg["persona"]
@@ -310,7 +311,7 @@ def _ensure_persona(workspace_id: str, api_key: str, base_url: str) -> str:
310
311
  "PATCH",
311
312
  f"{base_url}/guard/config/persona",
312
313
  body={"persona": chosen},
313
- api_key=api_key,
314
+ token=agent_token or None,
314
315
  )
315
316
  except Exception:
316
317
  pass # non-fatal — local config still records the choice
@@ -352,7 +353,7 @@ def _require_guard_config() -> dict:
352
353
  if not cfg or not ws:
353
354
  print(f"{RED}Guard not connected. Run: conduct login{RESET}", file=sys.stderr)
354
355
  sys.exit(0)
355
- if not cfg.get("agent_token") and not cfg.get("api_key"):
356
+ if not cfg.get("agent_token"):
356
357
  print(f"{RED}Guard config is missing credentials. Run: conduct login{RESET}", file=sys.stderr)
357
358
  sys.exit(0)
358
359
  return cfg
@@ -522,12 +523,19 @@ def _write_mcp_file(
522
523
 
523
524
 
524
525
  _GUARD_RULES_TEXT = (
526
+ "## ConductGuard — AI Governance Policy\n\n"
527
+ "A ConductGuard MCP server is connected to this session (server name: `conduct-guard`).\n"
528
+ "All Guard policies are enforced through it. You MUST follow these rules:\n\n"
525
529
  "Before executing any shell command, file write, network request, API call, or code change:\n"
526
- "- Call guard_check (conductguard MCP tool) with the action and parameters.\n"
527
- "- If BLOCKED: stop immediately and explain the policy rule to the user.\n"
528
- "- If WARNING: proceed but surface the warning. If ALLOWED: proceed normally.\n"
529
- "- Never write credentials, API keys, or secrets to files or output.\n"
530
- "- Never send PII (emails, names, payment data) to external endpoints."
530
+ "1. Call the `guard_check` tool from the `conduct-guard` MCP server with the action and parameters.\n"
531
+ "2. If the response is BLOCKED: stop immediately and explain the policy rule to the user. Do not proceed.\n"
532
+ "3. If the response is WARNING: proceed but surface the warning message to the user.\n"
533
+ "4. If the response is ALLOWED: proceed normally.\n\n"
534
+ "Always-on rules (no guard_check needed, enforce unconditionally):\n"
535
+ "- Never write credentials, API keys, tokens, or secrets to any file or output.\n"
536
+ "- Never send PII (emails, names, payment data) to external endpoints.\n\n"
537
+ "Use `guard_status` (conduct-guard MCP) to show the user their current policy and team info.\n"
538
+ "Use `guard_activity` at the start of every session with a one-line summary of what you are doing."
531
539
  )
532
540
 
533
541
 
@@ -582,22 +590,10 @@ def _patch_tool_instruction_files(agent_token: str, api_url: str, dry_run: bool
582
590
  MARKER_END = "<!-- /ConductGuard -->"
583
591
  GUARD_RULES = _GUARD_RULES_TEXT
584
592
 
585
- # Pull team instructions from API (non-fatal)
586
- # Only write if admin has explicitly published (version != "v0")
587
- team_content = ""
588
- try:
589
- resp = _req("GET", f"{api_url}/team-os/instructions", token=agent_token)
590
- if (resp.get("version") or "v0") != "v0":
591
- team_content = (resp.get("content") or "").strip()
592
- except Exception:
593
- pass
594
-
595
- parts = [MARKER_START, "## ConductGuard Policy"]
596
- if team_content:
597
- parts.append(team_content)
598
- parts.append(GUARD_RULES)
599
- parts.append(MARKER_END)
600
- guard_block = "\n".join(parts)
593
+ # Guard-only for now — team-os instructions are hidden pending redesign.
594
+ # This block just ensures every AI tool knows to invoke Guard via hooks
595
+ # or the conduct-guard MCP before taking action.
596
+ guard_block = "\n".join([MARKER_START, "## ConductGuard Policy", GUARD_RULES, MARKER_END])
601
597
 
602
598
  repo_root: Path | None = None
603
599
  for parent in [Path.cwd(), *Path.cwd().parents]:
@@ -751,12 +747,26 @@ def _install_codex_hook(hook_path: Path) -> None:
751
747
 
752
748
  # ── HTTP helpers (no third-party deps — mirrors api.py style) ─────────────────
753
749
 
754
- def _req(method: str, url: str, body=None, token: str = None, api_key: str = None, timeout: int = 20) -> dict:
750
+ def _default_agent_token() -> str | None:
751
+ """Read the agent_token minted by `conduct login` from ~/.conduct/config.json.
752
+ This is the CLI's single canonical credential — every call to the central
753
+ API should carry it via Authorization: Bearer."""
754
+ try:
755
+ cfg_path = Path.home() / ".conduct" / "config.json"
756
+ if cfg_path.exists():
757
+ return json.loads(cfg_path.read_text()).get("agent_token") or None
758
+ except Exception:
759
+ pass
760
+ return None
761
+
762
+
763
+ def _req(method: str, url: str, body=None, token: str = None, timeout: int = 20) -> dict:
764
+ # If no explicit token passed, fall back to the login-minted agent_token.
765
+ if not token:
766
+ token = _default_agent_token()
755
767
  headers = {"Content-Type": "application/json"}
756
768
  if token:
757
769
  headers["Authorization"] = f"Bearer {token}"
758
- if api_key:
759
- headers["X-Api-Key"] = api_key
760
770
  data = json.dumps(body).encode() if body is not None else None
761
771
  r = urllib.request.Request(url, data=data, headers=headers, method=method)
762
772
  try:
@@ -892,10 +902,10 @@ def _install_claude_hook(hook_path: Path) -> None:
892
902
 
893
903
  def cmd_guard_install(args):
894
904
  """Called automatically from `conduct login`. Sets up Guard: downloads policies, installs hook + MCP."""
895
- api_key = getattr(args, "api_key", None)
896
- server = (getattr(args, "server", None) or "https://api.conductai.ai").rstrip("/")
905
+ agent_token = getattr(args, "agent_token", None)
906
+ server = (getattr(args, "server", None) or "https://api.conductai.ai").rstrip("/")
897
907
 
898
- # Load workspace_id from ~/.conduct/config.json
908
+ # Load workspace_id (and fallback agent_token) from ~/.conduct/config.json
899
909
  conduct_cfg_path = Path.home() / ".conduct" / "config.json"
900
910
  conduct_cfg: dict = {}
901
911
  if conduct_cfg_path.exists():
@@ -905,7 +915,9 @@ def cmd_guard_install(args):
905
915
  pass
906
916
 
907
917
  workspace_id = conduct_cfg.get("workspace")
908
- if not workspace_id or not api_key:
918
+ if not agent_token:
919
+ agent_token = conduct_cfg.get("agent_token")
920
+ if not workspace_id or not agent_token:
909
921
  return # nothing to do
910
922
 
911
923
  print(f" Setting up Guard…")
@@ -913,16 +925,16 @@ def cmd_guard_install(args):
913
925
  result = _req(
914
926
  "GET",
915
927
  f"{server}/guard/config/installed?workspace_id={workspace_id}",
916
- api_key=api_key,
928
+ token=agent_token or None,
917
929
  )
918
930
 
919
931
  if not result.get("installed"):
920
932
  print(f" {GRAY}Guard not installed for this workspace — skipping{RESET}")
921
933
  return
922
934
 
923
- agent_token = result.get("agent_token") or ""
924
- user_email = result.get("user_email") or ""
925
- clerk_user_id = result.get("clerk_user_id") or ""
935
+ resp_agent_token = result.get("agent_token") or agent_token
936
+ user_email = result.get("user_email") or ""
937
+ clerk_user_id = result.get("clerk_user_id") or ""
926
938
 
927
939
  # Persona selection — prompt once, skip if already chosen
928
940
 
@@ -930,10 +942,9 @@ def cmd_guard_install(args):
930
942
  import time as _time
931
943
  _save_guard_config({
932
944
  "workspace_id": workspace_id,
933
- "agent_token": agent_token,
945
+ "agent_token": resp_agent_token,
934
946
  "user_email": user_email,
935
947
  "clerk_user_id": clerk_user_id,
936
- "api_key": api_key,
937
948
  "api_url": server,
938
949
  "last_synced_at": _time.time(),
939
950
  })
@@ -1243,24 +1254,20 @@ def _report_tools_to_server() -> None:
1243
1254
  base_url = _api_url(cfg)
1244
1255
  email = cfg.get("user_email", "")
1245
1256
  token = cfg.get("agent_token", "")
1246
- api_key = cfg.get("api_key", "")
1247
-
1248
1257
  if not email:
1249
1258
  return
1250
-
1251
- # Also pull conduct API key for X-Api-Key auth (member_token is not accepted by this endpoint)
1252
1259
  conduct_cfg_path = Path.home() / ".conduct" / "config.json"
1253
- conduct_api_key = ""
1260
+ conduct_agent_token = ""
1254
1261
  if conduct_cfg_path.exists():
1255
1262
  try:
1256
- conduct_api_key = json.loads(conduct_cfg_path.read_text()).get("api_key", "")
1263
+ conduct_agent_token = json.loads(conduct_cfg_path.read_text()).get("agent_token", "")
1257
1264
  except Exception:
1258
1265
  pass
1259
1266
 
1260
1267
  payload = json.dumps({"email": email, "tools": tools}).encode()
1261
1268
  headers = {"Content-Type": "application/json"}
1262
- if conduct_api_key and conduct_api_key.startswith("cond_live_"):
1263
- headers["X-Api-Key"] = conduct_api_key
1269
+ if conduct_agent_token and conduct_agent_token.startswith("cond_live_"):
1270
+ headers["Authorization"] = f"Bearer {conduct_agent_token}"
1264
1271
  elif token:
1265
1272
  headers["Authorization"] = f"Bearer {token}"
1266
1273
  req = urllib.request.Request(
@@ -1465,14 +1472,6 @@ def cmd_guard_sync(args):
1465
1472
  except Exception as e:
1466
1473
  print(f" {YELLOW}Warning: could not refresh token ({e}) — using cached value{RESET}")
1467
1474
 
1468
- # Fetch team instructions version so session-start hook can detect staleness
1469
- try:
1470
- ver_resp = _req("GET", f"{base_url}/team-os/instructions/version", token=_token)
1471
- if ver_resp.get("version"):
1472
- _save_guard_config({"instructions_version": ver_resp["version"]})
1473
- except Exception:
1474
- pass # non-fatal
1475
-
1476
1475
  # Write LLM proxy env vars so any AI tool (Claude Code, Cursor, Codex, …)
1477
1476
  # routes through Conduct Guard. Customer-overridable via --proxy-url or
1478
1477
  # CONDUCT_PROXY_URL env var; otherwise fetched from server (workspace_config).
@@ -1868,7 +1867,7 @@ def _ensure_booster(root: Path) -> None:
1868
1867
  pass
1869
1868
 
1870
1869
 
1871
- def _report_savings(cfg: dict, base_url: str, api_key: str = "") -> None:
1870
+ def _report_savings(cfg: dict, base_url: str, agent_token: str = "") -> None:
1872
1871
  import subprocess
1873
1872
 
1874
1873
  rtk_data = {}
@@ -1929,12 +1928,10 @@ def _report_savings(cfg: dict, base_url: str, api_key: str = "") -> None:
1929
1928
  }
1930
1929
 
1931
1930
  try:
1932
- agent_token_evt = cfg.get("agent_token", "")
1931
+ agent_token_evt = cfg.get("agent_token", "") or agent_token
1933
1932
  headers = {"Content-Type": "application/json"}
1934
1933
  if agent_token_evt:
1935
1934
  headers["Authorization"] = f"Bearer {agent_token_evt}"
1936
- elif api_key:
1937
- headers["X-Api-Key"] = api_key
1938
1935
  data = json.dumps(payload).encode()
1939
1936
  req = urllib.request.Request(
1940
1937
  f"{base_url}/guard/savings",
@@ -1963,13 +1960,13 @@ def cmd_guard_status(args):
1963
1960
  cfg = _require_guard_config()
1964
1961
  workspace_id = cfg.get("workspace_id")
1965
1962
  user_email = cfg.get("user_email", "")
1966
- api_key = cfg.get("api_key", "")
1963
+ agent_token = cfg.get("agent_token", "")
1967
1964
  base_url = _api_url(cfg)
1968
1965
 
1969
1966
  # Auto-refresh user_email + clerk_user_id into config if missing
1970
- if (not user_email or not cfg.get("clerk_user_id")) and api_key:
1967
+ if (not user_email or not cfg.get("clerk_user_id")) and agent_token:
1971
1968
  try:
1972
- installed = _req("GET", f"{base_url}/guard/config/installed", api_key=api_key)
1969
+ installed = _req("GET", f"{base_url}/guard/config/installed", token=agent_token or None)
1973
1970
  fetched_email = installed.get("user_email") or ""
1974
1971
  fetched_clerk = installed.get("clerk_user_id") or ""
1975
1972
  if fetched_email:
@@ -1993,7 +1990,7 @@ def cmd_guard_status(args):
1993
1990
  spend = _req(
1994
1991
  "GET",
1995
1992
  f"{base_url}/guard/spend?workspace_id={workspace_id}",
1996
- api_key=api_key,
1993
+ token=agent_token or None,
1997
1994
  )
1998
1995
  except SystemExit:
1999
1996
  pass
@@ -2013,7 +2010,7 @@ def cmd_guard_status(args):
2013
2010
  f"&since={today_iso}"
2014
2011
  f"&limit=20"
2015
2012
  ),
2016
- api_key=api_key,
2013
+ token=agent_token or None,
2017
2014
  )
2018
2015
  if not isinstance(events, list):
2019
2016
  events = events.get("events", [])
@@ -2094,14 +2091,14 @@ def cmd_guard_status(args):
2094
2091
  def cmd_guard_savings(args):
2095
2092
  cfg = _require_guard_config()
2096
2093
  workspace_id = cfg.get("workspace_id")
2097
- api_key = cfg.get("api_key", "")
2094
+ agent_token = cfg.get("agent_token", "")
2098
2095
  base_url = _api_url(cfg)
2099
2096
 
2100
2097
  try:
2101
2098
  data = _req(
2102
2099
  "GET",
2103
2100
  f"{base_url}/guard/savings/team-summary?workspace_id={workspace_id}",
2104
- api_key=api_key,
2101
+ token=agent_token or None,
2105
2102
  )
2106
2103
  except Exception:
2107
2104
  print(f"{RED}Failed to fetch team savings.{RESET}")
@@ -2112,9 +2109,11 @@ def cmd_guard_savings(args):
2112
2109
 
2113
2110
  dev_count = data.get("developer_count", 0)
2114
2111
  total_tok = data.get("total_tokens_saved", 0)
2112
+ total_usd = data.get("total_cost_saved_usd", 0.0)
2115
2113
  per_day = data.get("per_day_usd", 0.0)
2116
2114
  per_month = data.get("per_month_usd", 0.0)
2117
2115
  per_year = data.get("per_year_usd", 0.0)
2116
+ days_obs = data.get("days_observed", 1)
2118
2117
  tools = data.get("tools_installed", [])
2119
2118
  avg_tok = total_tok // dev_count if dev_count else 0
2120
2119
  avg_day_usd = round(per_day / dev_count, 2) if dev_count else 0.0
@@ -2122,8 +2121,8 @@ def cmd_guard_savings(args):
2122
2121
  print()
2123
2122
  print(f"{BOLD}Team Token Savings{RESET} ({dev_count} developer{'s' if dev_count != 1 else ''})")
2124
2123
  print("─" * 52)
2125
- print(f" Total tokens saved: {total_tok:>14,}")
2126
- print(f" Estimated savings: ${per_day:>8.2f}/day · ${per_month:,.0f}/month · ${per_year:,.0f}/year")
2124
+ print(f" Total tokens saved: {total_tok:>14,} (${total_usd:,.2f} over {days_obs} day{'s' if days_obs != 1 else ''})")
2125
+ print(f" Daily rate: ${per_day:>8.2f}/day · projected ${per_month:,.0f}/month · ${per_year:,.0f}/year")
2127
2126
  if dev_count:
2128
2127
  print(f" Avg per developer: {avg_tok:>14,} tokens · ${avg_day_usd:.2f}/day")
2129
2128
  if tools:
@@ -2161,7 +2160,7 @@ def cmd_guard_audit(args):
2161
2160
  cfg = _require_guard_config()
2162
2161
  workspace_id = cfg.get("workspace_id")
2163
2162
  user_email = cfg.get("user_email", "")
2164
- api_key = cfg.get("api_key", "")
2163
+ agent_token = cfg.get("agent_token", "")
2165
2164
  base_url = _api_url(cfg)
2166
2165
 
2167
2166
  since_str = getattr(args, "since", None) or "24h"
@@ -2176,7 +2175,7 @@ def cmd_guard_audit(args):
2176
2175
  f"&since={since_iso}"
2177
2176
  f"&limit=50"
2178
2177
  ),
2179
- api_key=api_key,
2178
+ token=agent_token or None,
2180
2179
  )
2181
2180
  events = events_resp if isinstance(events_resp, list) else events_resp.get("events", [])
2182
2181
 
@@ -2486,7 +2485,7 @@ def _watch_loop():
2486
2485
  c = _cfg()
2487
2486
  api_url = _api_url(c)
2488
2487
  token = c.get("agent_token", "")
2489
- api_key = c.get("api_key", "")
2488
+ agent_token = c.get("agent_token", "")
2490
2489
  agents = []
2491
2490
  for name, config_path, mcp_key in _discover_config_agents():
2492
2491
  agents.append({
@@ -2506,7 +2505,7 @@ def _watch_loop():
2506
2505
  pass
2507
2506
  _req("POST", f"{api_url}/guard/discover/scan",
2508
2507
  body={"triggered_by": "watch", "agents": agents},
2509
- token=token, api_key=api_key)
2508
+ token=token or agent_token)
2510
2509
  except Exception:
2511
2510
  pass
2512
2511
  time.sleep(_WATCH_INTERVAL)
@@ -2662,7 +2661,6 @@ def cmd_guard_discover(args):
2662
2661
  cfg = _load_guard_config()
2663
2662
  api_url = _api_url(cfg)
2664
2663
  token = cfg.get("agent_token", "")
2665
- api_key = cfg.get("api_key", "")
2666
2664
  hdrs = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
2667
2665
 
2668
2666
  # Config file scan — detect AI tools from known config dirs
@@ -2722,7 +2720,7 @@ def cmd_guard_discover(args):
2722
2720
  "proxy_routed": t.get("proxy_routed", False),
2723
2721
  })
2724
2722
  payload = {"triggered_by": "cli", "agents": agents_payload}
2725
- _req("POST", f"{api_url}/guard/discover/scan", body=payload, token=token, api_key=api_key)
2723
+ _req("POST", f"{api_url}/guard/discover/scan", body=payload, token=token)
2726
2724
  except SystemExit:
2727
2725
  pass # 401/network errors — local output still useful
2728
2726
 
@@ -2936,14 +2934,14 @@ def cmd_verify(args) -> None:
2936
2934
 
2937
2935
  cfg = _require_guard_config()
2938
2936
  workspace_id = cfg.get("workspace_id")
2939
- api_key = cfg.get("api_key", "")
2937
+ agent_token = cfg.get("agent_token", "")
2940
2938
  base_url = _api_url(cfg)
2941
2939
 
2942
2940
  # ── fetch evidence from API ───────────────────────────────────────────────
2943
2941
  ev = _req(
2944
2942
  "GET",
2945
2943
  f"{base_url}/guard/verify/evidence?workspace_id={workspace_id}",
2946
- api_key=api_key,
2944
+ token=agent_token or None,
2947
2945
  )
2948
2946
 
2949
2947
  grade = ev.get("grade", "F")
@@ -2960,7 +2958,7 @@ def cmd_verify(args) -> None:
2960
2958
  run_result = _req(
2961
2959
  "POST",
2962
2960
  f"{base_url}/guard/verify/run?workspace_id={workspace_id}",
2963
- api_key=api_key,
2961
+ token=agent_token or None,
2964
2962
  )
2965
2963
  if fmt == "json":
2966
2964
  print(_json.dumps(run_result, indent=2))
@@ -148,14 +148,14 @@ def _refresh_policy_from_api() -> None:
148
148
  try:
149
149
  cfg = load_config()
150
150
  workspace_id = cfg.get("workspace_id")
151
- api_key = cfg.get("api_key", "")
151
+ agent_token = cfg.get("agent_token", "")
152
152
  api_url = cfg.get("api_url", "https://api.conductai.ai").rstrip("/")
153
- if not api_key:
153
+ if not agent_token:
154
154
  return
155
155
  url = f"{api_url}/guard/policies/sync"
156
156
  if workspace_id:
157
157
  url += f"?workspace_id={workspace_id}"
158
- req = urllib.request.Request(url, headers={"X-Api-Key": api_key})
158
+ req = urllib.request.Request(url, headers={"Authorization": f"Bearer {agent_token}"})
159
159
  with urllib.request.urlopen(req, timeout=8) as resp:
160
160
  data = resp.read()
161
161
  POLICY_PATH.parent.mkdir(parents=True, exist_ok=True)
@@ -92,7 +92,7 @@ def _maybe_emit_security_finding(tool_response, session_id, tool_name, tool_inpu
92
92
  if not cfg.get("security_emit_enabled", False):
93
93
  return
94
94
  workspace_id = cfg.get("workspace_id")
95
- api_key = cfg.get("api_key", "")
95
+ agent_token = cfg.get("agent_token", "")
96
96
  api_url = cfg.get("api_url", "https://api.conductai.ai").rstrip("/")
97
97
  if not workspace_id:
98
98
  return
@@ -138,7 +138,7 @@ def _maybe_emit_security_finding(tool_response, session_id, tool_name, tool_inpu
138
138
  "try:\n"
139
139
  f" req = urllib.request.Request(\"{api_url}/security-findings?workspace_id={workspace_id}\","
140
140
  f" data={repr(payload.encode())}, headers={{\"Content-Type\": \"application/json\","
141
- f" \"X-Api-Key\": \"{api_key}\"}}, method=\"POST\")\n"
141
+ f" \"Authorization\": \"Bearer {agent_token}\"}}, method=\"POST\")\n"
142
142
  " urllib.request.urlopen(req, timeout=5)\n"
143
143
  "except: pass\n"
144
144
  )
@@ -156,7 +156,7 @@ def _maybe_sync_policy() -> None:
156
156
  try:
157
157
  cfg = load_config()
158
158
  workspace_id = cfg.get("workspace_id") or cfg.get("workspace")
159
- api_key = cfg.get("agent_token") or cfg.get("api_key", "")
159
+ agent_token = cfg.get("agent_token", "")
160
160
  api_url = cfg.get("api_url", "https://api.conductai.ai").rstrip("/")
161
161
  if not workspace_id:
162
162
  return
@@ -175,7 +175,7 @@ def _maybe_sync_policy() -> None:
175
175
  if time.time() - cache.get("ts", 0) < VERSION_CACHE_TTL:
176
176
  return
177
177
  url = f"{api_url}/guard/policies/sync?workspace_id={workspace_id}"
178
- req = urllib.request.Request(url, headers={"Authorization": f"Bearer {api_key}"} if api_key else {})
178
+ req = urllib.request.Request(url, headers={"Authorization": f"Bearer {agent_token}"} if agent_token else {})
179
179
  with urllib.request.urlopen(req, timeout=2) as resp:
180
180
  remote = json.loads(resp.read())
181
181
 
@@ -33,7 +33,7 @@ def _load_config() -> dict:
33
33
  def run(session_id: str = "") -> None:
34
34
  cfg = _load_config()
35
35
  server = (cfg.get("api_url") or cfg.get("server") or "https://api.conductai.ai").rstrip("/")
36
- api_key = cfg.get("api_key", "")
36
+ agent_token = cfg.get("agent_token", "")
37
37
  member_token = cfg.get("member_token", "")
38
38
  workspace_id = cfg.get("workspace_id", "")
39
39
 
@@ -115,8 +115,8 @@ def run(session_id: str = "") -> None:
115
115
  }
116
116
  if member_token:
117
117
  headers["Authorization"] = f"Bearer {member_token}"
118
- elif api_key:
119
- headers["X-API-Key"] = api_key
118
+ elif agent_token:
119
+ headers["Authorization"] = f"Bearer {agent_token}"
120
120
 
121
121
  req = urllib.request.Request(
122
122
  f"{server}/guard/session-reports",
@@ -124,7 +124,7 @@ def _backfill_prev_session(current_session_id: str, cwd: str | None) -> None:
124
124
  return
125
125
  cfg = json.loads(cfg_path.read_text())
126
126
  server = cfg.get("server", "").rstrip("/")
127
- api_key = cfg.get("api_key", "")
127
+ agent_token = cfg.get("agent_token", "")
128
128
  workspace_id = cfg.get("workspace_id") or cfg.get("workspace", "")
129
129
  if not server:
130
130
  return
@@ -145,7 +145,7 @@ def _backfill_prev_session(current_session_id: str, cwd: str | None) -> None:
145
145
  data=payload,
146
146
  headers={
147
147
  "Content-Type": "application/json",
148
- "X-API-Key": api_key,
148
+ "Authorization": f"Bearer {agent_token}",
149
149
  "X-Workspace-ID": workspace_id,
150
150
  },
151
151
  method="PATCH",
@@ -26,7 +26,7 @@ def _post_intent(session_id: str, ai_tool: str, cwd: str | None) -> None:
26
26
  return
27
27
  cfg = _json.loads(cfg_path.read_text())
28
28
  server = cfg.get("server", "").rstrip("/")
29
- api_key = cfg.get("api_key", "")
29
+ agent_token = cfg.get("agent_token", "")
30
30
  workspace_id = cfg.get("workspace_id") or cfg.get("workspace", "")
31
31
  if not server or not session_id:
32
32
  return
@@ -43,7 +43,7 @@ def _post_intent(session_id: str, ai_tool: str, cwd: str | None) -> None:
43
43
  data=payload,
44
44
  headers={
45
45
  "Content-Type": "application/json",
46
- "X-API-Key": api_key,
46
+ "Authorization": f"Bearer {agent_token}",
47
47
  "X-Workspace-ID": workspace_id,
48
48
  },
49
49
  method="PATCH",
@@ -109,12 +109,10 @@ def _resolve(args, key: str, config_key=None):
109
109
 
110
110
 
111
111
  def _require_auth(args):
112
- """Return (server, workspace_id, api_key, token) — exit if not configured."""
112
+ """Return (server, workspace_id, token) — exit if not configured."""
113
113
  cfg = _load_config()
114
114
  server = _resolve(args, "server") or cfg.get("api_url", "")
115
115
  workspace = _resolve(args, "workspace") or cfg.get("workspace_id") or cfg.get("workspace")
116
- api_key = _resolve(args, "api_key", "api_key")
117
- # agent_token (new) takes precedence over legacy token
118
116
  token = _resolve(args, "token") or cfg.get("agent_token")
119
117
 
120
118
  if not server:
@@ -123,23 +121,21 @@ def _require_auth(args):
123
121
  if not workspace:
124
122
  print(f"{RED}No workspace set. Run: conduct login{RESET}")
125
123
  sys.exit(1)
126
- if not api_key and not token:
124
+ if not token:
127
125
  print(f"{RED}Not authenticated. Run: conduct login{RESET}")
128
126
  sys.exit(1)
129
127
 
130
- return server.rstrip("/"), workspace, api_key, token
128
+ return server.rstrip("/"), workspace, token
131
129
 
132
130
 
133
131
  # ── Stream helper ─────────────────────────────────────────────────────────────
134
132
 
135
- def _stream_run(server: str, workflow_id: str, run_id: str, workspace_id: str, token=None, api_key=None) -> bool:
136
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
133
+ def _stream_run(server: str, workflow_id: str, run_id: str, workspace_id: str, token=None) -> bool:
134
+ hdrs = api.headers(workspace_id, token, "application/json")
137
135
  # SSE endpoint reads auth from query params (EventSource can't set headers)
138
136
  qs_parts = [f"workspace_id={workspace_id}"]
139
137
  if token:
140
138
  qs_parts.append(f"token={token}")
141
- if api_key:
142
- qs_parts.append(f"api_key={api_key}")
143
139
  url = f"{server}/workflows/{workflow_id}/runs/{run_id}/stream?{'&'.join(qs_parts)}"
144
140
 
145
141
  for data in api.stream(url, hdrs):
@@ -348,7 +344,7 @@ def _report_tool_coverage() -> None:
348
344
  try:
349
345
  cfg = _load_config()
350
346
  server = cfg.get("server", "").rstrip("/")
351
- api_key = cfg.get("api_key", "")
347
+ api_key = cfg.get("agent_token", "")
352
348
  token = cfg.get("token", "")
353
349
  email = cfg.get("email", "")
354
350
 
@@ -363,7 +359,7 @@ def _report_tool_coverage() -> None:
363
359
  payload = json.dumps({"email": email, "tools": tools}).encode()
364
360
  headers = {"Content-Type": "application/json"}
365
361
  if api_key and api_key.startswith("cond_live_"):
366
- headers["X-Api-Key"] = api_key
362
+ headers["Authorization"] = f"Bearer {api_key}"
367
363
  elif token:
368
364
  headers["Authorization"] = f"Bearer {token}"
369
365
  req = urllib.request.Request(
@@ -594,8 +590,8 @@ def cmd_login(args):
594
590
 
595
591
 
596
592
  def cmd_agents(args):
597
- server, workspace_id, api_key, token = _require_auth(args)
598
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
593
+ server, workspace_id, token = _require_auth(args)
594
+ hdrs = api.headers(workspace_id, token, "application/json")
599
595
 
600
596
  project_filter = getattr(args, "project", None)
601
597
  url = f"{server}/workflows"
@@ -638,8 +634,8 @@ def cmd_agents(args):
638
634
 
639
635
 
640
636
  def cmd_test(args):
641
- server, workspace_id, api_key, token = _require_auth(args)
642
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
637
+ server, workspace_id, token = _require_auth(args)
638
+ hdrs = api.headers(workspace_id, token, "application/json")
643
639
 
644
640
  agent_names = args.agents
645
641
  run_all = getattr(args, "all", False)
@@ -717,10 +713,10 @@ def cmd_test(args):
717
713
  })
718
714
  return payload
719
715
 
720
- _run_tests(server, workspace_id, api_key, token, hdrs, targets, _build_payload, parallel=parallel)
716
+ _run_tests(server, workspace_id, token, hdrs, targets, _build_payload, parallel=parallel)
721
717
 
722
718
 
723
- def _run_tests(server, workspace_id, api_key, token, hdrs, targets, build_payload, *, parallel: bool = False):
719
+ def _run_tests(server, workspace_id, token, hdrs, targets, build_payload, *, parallel: bool = False):
724
720
  import threading
725
721
 
726
722
  if not parallel:
@@ -739,7 +735,7 @@ def _run_tests(server, workspace_id, api_key, token, hdrs, targets, build_payloa
739
735
  run_id = run.get("run_id")
740
736
  print(f" {GRAY}run: {run_id}{RESET}")
741
737
  try:
742
- ok = _stream_run(server, wf_id, run_id, workspace_id, token, api_key)
738
+ ok = _stream_run(server, wf_id, run_id, workspace_id, token)
743
739
  except Exception:
744
740
  ok = _poll_run(server, wf_id, run_id, hdrs)
745
741
  results.append((name, ok, run_id))
@@ -824,8 +820,8 @@ def _resolve_environment(server: str, workspace_id: str, hdrs: dict, name: str)
824
820
  # ── Environment commands ──────────────────────────────────────────────────────
825
821
 
826
822
  def cmd_environments(args):
827
- server, workspace_id, api_key, token = _require_auth(args)
828
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
823
+ server, workspace_id, token = _require_auth(args)
824
+ hdrs = api.headers(workspace_id, token, "application/json")
829
825
  envs = _list_environments(server, workspace_id, hdrs)
830
826
 
831
827
  if not envs:
@@ -840,8 +836,8 @@ def cmd_environments(args):
840
836
 
841
837
 
842
838
  def cmd_credentials(args):
843
- server, workspace_id, api_key, token = _require_auth(args)
844
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
839
+ server, workspace_id, token = _require_auth(args)
840
+ hdrs = api.headers(workspace_id, token, "application/json")
845
841
  env = _resolve_environment(server, workspace_id, hdrs, args.environment)
846
842
 
847
843
  rows = api.req("GET", f"{server}/credentials/env-vars/{env['id']}", hdrs)
@@ -862,8 +858,8 @@ def cmd_credentials(args):
862
858
  print()
863
859
 
864
860
 
865
- def _do_set_credential(server, workspace_id, api_key, token, env_name, key, value):
866
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
861
+ def _do_set_credential(server, workspace_id, token, env_name, key, value):
862
+ hdrs = api.headers(workspace_id, token, "application/json")
867
863
  env = _resolve_environment(server, workspace_id, hdrs, env_name)
868
864
 
869
865
  existing = api.req("GET", f"{server}/credentials/env-vars/{env['id']}", hdrs)
@@ -875,8 +871,8 @@ def _do_set_credential(server, workspace_id, api_key, token, env_name, key, valu
875
871
  print(f"{GREEN}✓ {key}{RESET} set in environment '{env_name}' {GRAY}({masked}){RESET}")
876
872
 
877
873
 
878
- def _do_delete_credential(server, workspace_id, api_key, token, env_name, key, yes):
879
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
874
+ def _do_delete_credential(server, workspace_id, token, env_name, key, yes):
875
+ hdrs = api.headers(workspace_id, token, "application/json")
880
876
  env = _resolve_environment(server, workspace_id, hdrs, env_name)
881
877
 
882
878
  existing = api.req("GET", f"{server}/credentials/env-vars/{env['id']}", hdrs)
@@ -898,12 +894,12 @@ def _do_delete_credential(server, workspace_id, api_key, token, env_name, key, y
898
894
 
899
895
  def cmd_set(args):
900
896
  if args.set_command == "credential":
901
- server, workspace_id, api_key, token = _require_auth(args)
902
- _do_set_credential(server, workspace_id, api_key, token,
897
+ server, workspace_id, token = _require_auth(args)
898
+ _do_set_credential(server, workspace_id, token,
903
899
  args.environment, args.key, args.value)
904
900
  elif args.set_command == "environment":
905
- server, workspace_id, api_key, token = _require_auth(args)
906
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
901
+ server, workspace_id, token = _require_auth(args)
902
+ hdrs = api.headers(workspace_id, token, "application/json")
907
903
 
908
904
  workflows = api.req("GET", f"{server}/workflows", hdrs)
909
905
  wf = next((w for w in workflows if w["name"].lower() == args.agent.lower()), None)
@@ -936,8 +932,8 @@ def _resolve_project(server: str, workspace_id: str, hdrs: dict, name: str) -> d
936
932
 
937
933
 
938
934
  def cmd_projects(args):
939
- server, workspace_id, api_key, token = _require_auth(args)
940
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
935
+ server, workspace_id, token = _require_auth(args)
936
+ hdrs = api.headers(workspace_id, token, "application/json")
941
937
  projects = _list_projects(server, workspace_id, hdrs)
942
938
 
943
939
  if not projects:
@@ -953,8 +949,8 @@ def cmd_projects(args):
953
949
 
954
950
 
955
951
  def cmd_create(args):
956
- server, workspace_id, api_key, token = _require_auth(args)
957
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
952
+ server, workspace_id, token = _require_auth(args)
953
+ hdrs = api.headers(workspace_id, token, "application/json")
958
954
  parts = args.create_args
959
955
 
960
956
  if parts and parts[0] == "environment":
@@ -975,8 +971,8 @@ def cmd_create(args):
975
971
 
976
972
 
977
973
  def cmd_delete(args):
978
- server, workspace_id, api_key, token = _require_auth(args)
979
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
974
+ server, workspace_id, token = _require_auth(args)
975
+ hdrs = api.headers(workspace_id, token, "application/json")
980
976
  parts = args.delete_args
981
977
 
982
978
  if parts and parts[0] == "environment":
@@ -999,7 +995,7 @@ def cmd_delete(args):
999
995
  if not env_name or not key:
1000
996
  print(f"{RED}Usage: conduct delete credential --environment <name> --key <KEY>{RESET}")
1001
997
  sys.exit(1)
1002
- _do_delete_credential(server, workspace_id, api_key, token, env_name, key, args.yes)
998
+ _do_delete_credential(server, workspace_id, token, env_name, key, args.yes)
1003
999
 
1004
1000
  else:
1005
1001
  # conduct delete [project] <name> [--yes] [--purge]
@@ -1035,8 +1031,8 @@ def cmd_delete(args):
1035
1031
  # ── Playbook commands ─────────────────────────────────────────────────────────
1036
1032
 
1037
1033
  def cmd_playbooks(args):
1038
- server, workspace_id, api_key, token = _require_auth(args)
1039
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
1034
+ server, workspace_id, token = _require_auth(args)
1035
+ hdrs = api.headers(workspace_id, token, "application/json")
1040
1036
  slug = getattr(args, "slug", None)
1041
1037
 
1042
1038
  if slug:
@@ -1080,8 +1076,8 @@ def cmd_playbooks(args):
1080
1076
  # ── Install command ───────────────────────────────────────────────────────────
1081
1077
 
1082
1078
  def cmd_install(args):
1083
- server, workspace_id, api_key, token = _require_auth(args)
1084
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
1079
+ server, workspace_id, token = _require_auth(args)
1080
+ hdrs = api.headers(workspace_id, token, "application/json")
1085
1081
 
1086
1082
  slug = args.slug
1087
1083
 
@@ -1181,8 +1177,8 @@ def cmd_install(args):
1181
1177
  # ── Reset command ─────────────────────────────────────────────────────────────
1182
1178
 
1183
1179
  def cmd_reset(args):
1184
- server, workspace_id, api_key, token = _require_auth(args)
1185
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
1180
+ server, workspace_id, token = _require_auth(args)
1181
+ hdrs = api.headers(workspace_id, token, "application/json")
1186
1182
  proj = _resolve_project(server, workspace_id, hdrs, args.name)
1187
1183
  project_id = proj["id"]
1188
1184
 
@@ -1268,8 +1264,8 @@ _FRIENDLY_NAMES = {
1268
1264
 
1269
1265
 
1270
1266
  def cmd_install_all(args):
1271
- server, workspace_id, api_key, token = _require_auth(args)
1272
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
1267
+ server, workspace_id, token = _require_auth(args)
1268
+ hdrs = api.headers(workspace_id, token, "application/json")
1273
1269
 
1274
1270
  slugs = _ALL_SLUGS
1275
1271
 
@@ -1361,16 +1357,16 @@ def _atomic_write(path: Path, data: dict) -> None:
1361
1357
  def cmd_switch(args):
1362
1358
  cfg = _load_config()
1363
1359
  server = cfg.get("server", "").rstrip("/")
1364
- api_key = cfg.get("api_key", "")
1360
+ api_key = cfg.get("agent_token", "")
1365
1361
  token = cfg.get("token", "")
1366
1362
 
1367
1363
  if not server or (not api_key and not token):
1368
- print(f"{RED}Not logged in. Run: conduct login --server <url> --api-key <key>{RESET}")
1364
+ print(f"{RED}Not logged in. Run: conduct login{RESET}")
1369
1365
  sys.exit(1)
1370
1366
 
1371
1367
  hdrs = {"Content-Type": "application/json"}
1372
1368
  if api_key:
1373
- hdrs["X-Api-Key"] = api_key
1369
+ hdrs["Authorization"] = f"Bearer {api_key}"
1374
1370
  elif token:
1375
1371
  hdrs["Authorization"] = f"Bearer {token}"
1376
1372
 
@@ -1433,7 +1429,7 @@ def cmd_switch(args):
1433
1429
  policy = _guard._req(
1434
1430
  "GET",
1435
1431
  f"{server}/guard/policies/sync?workspace_id={new_id}",
1436
- api_key=api_key,
1432
+ token=token,
1437
1433
  )
1438
1434
  _guard._save_policy(policy)
1439
1435
  rule_count = len(policy.get("rules", []))
@@ -1451,13 +1447,13 @@ def cmd_whoami(args):
1451
1447
 
1452
1448
  workspace_id = cfg.get("workspace", "")
1453
1449
  server = cfg.get("server", "—")
1454
- api_key = cfg.get("api_key", "")
1450
+ api_key = cfg.get("agent_token", "")
1455
1451
 
1456
1452
  # Try to resolve workspace name from /projects
1457
1453
  workspace_name = ""
1458
1454
  if workspace_id and server != "—" and api_key:
1459
1455
  try:
1460
- hdrs = {"Content-Type": "application/json", "X-Api-Key": api_key}
1456
+ hdrs = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
1461
1457
  projects = api.req("GET", f"{server.rstrip('/')}/projects", hdrs)
1462
1458
  match = next((p for p in projects if str(p.get("id", "")) == str(workspace_id)), None)
1463
1459
  if match:
@@ -1804,12 +1800,12 @@ def _render_table(rows: list[dict]) -> str:
1804
1800
  def _fetch_runs(cfg: dict) -> list[dict]:
1805
1801
  """Fetch recent runs from GET /runs."""
1806
1802
  server = cfg.get("server", "").rstrip("/")
1807
- api_key = cfg.get("api_key", "")
1803
+ api_key = cfg.get("agent_token", "")
1808
1804
  ws_id = cfg.get("workspace", "")
1809
1805
  if not all([server, api_key, ws_id]):
1810
1806
  return []
1811
1807
  try:
1812
- hdrs = {"Content-Type": "application/json", "X-Api-Key": api_key}
1808
+ hdrs = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
1813
1809
  data = api.req("GET", f"{server}/runs?limit=15&workspace_id={ws_id}", hdrs)
1814
1810
  runs = data if isinstance(data, list) else data.get("runs", data.get("items", []))
1815
1811
  return runs[:15]
@@ -1820,12 +1816,12 @@ def _fetch_runs(cfg: dict) -> list[dict]:
1820
1816
  def _fetch_guard_activity(cfg: dict, guard_cfg: dict) -> list[dict]:
1821
1817
  """Fetch recent Guard events grouped by developer."""
1822
1818
  server = cfg.get("server", "").rstrip("/")
1823
- api_key = cfg.get("api_key", "")
1819
+ api_key = cfg.get("agent_token", "")
1824
1820
  ws_id = cfg.get("workspace", "")
1825
1821
  if not all([server, api_key, ws_id]):
1826
1822
  return []
1827
1823
  try:
1828
- hdrs = {"Content-Type": "application/json", "X-Api-Key": api_key}
1824
+ hdrs = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
1829
1825
  data = api.req("GET", f"{server}/guard/events?workspace_id={ws_id}&limit=200", hdrs)
1830
1826
  events = data if isinstance(data, list) else data.get("events", [])
1831
1827
  # Group by user_email
@@ -2280,8 +2276,8 @@ def cmd_session_report(args):
2280
2276
 
2281
2277
  # ── 5. Push report to Guard dashboard ────────────────────────────────────
2282
2278
  try:
2283
- server, workspace_id, api_key, token = _require_auth(args)
2284
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
2279
+ server, workspace_id, token = _require_auth(args)
2280
+ hdrs = api.headers(workspace_id, token, "application/json")
2285
2281
  import getpass as _getpass
2286
2282
  email = getattr(args, "developer", None) or _getpass.getuser()
2287
2283
  payload = _json.dumps({
@@ -2313,8 +2309,8 @@ def cmd_session_report(args):
2313
2309
 
2314
2310
  def cmd_emit_finding(args):
2315
2311
  """POST a security finding to /security-findings."""
2316
- server, workspace_id, api_key, token = _require_auth(args)
2317
- hdrs = api.headers(workspace_id, token, "application/json", api_key)
2312
+ server, workspace_id, token = _require_auth(args)
2313
+ hdrs = api.headers(workspace_id, token, "application/json")
2318
2314
 
2319
2315
  from_stdin = getattr(args, "from_stdin", False)
2320
2316
 
@@ -2408,8 +2404,8 @@ def _prompt_issue_choice(issues: list) -> dict:
2408
2404
 
2409
2405
 
2410
2406
  def cmd_run(args):
2411
- server, workspace_id, api_key, token = _require_auth(args)
2412
- json_h = api.headers(workspace_id, token, "application/json", api_key)
2407
+ server, workspace_id, token = _require_auth(args)
2408
+ json_h = api.headers(workspace_id, token, "application/json")
2413
2409
 
2414
2410
  # Fix 1: --input values go into state["inputs"], not top-level state.
2415
2411
  # {{inputs.key}} refs in YAML only resolve when the executor finds state["inputs"].
@@ -2548,7 +2544,7 @@ def cmd_run(args):
2548
2544
  body["__max_turns"] = suggested
2549
2545
  run = api.req("POST", f"{server}/workflows/{workflow_id}/trigger", json_h, body)
2550
2546
  run_id = run.get("run_id") or run.get("id")
2551
- _stream_run(server, workflow_id, run_id, workspace_id, token, api_key)
2547
+ _stream_run(server, workflow_id, run_id, workspace_id, token)
2552
2548
 
2553
2549
 
2554
2550
  # ── conduct sync / test-guard / test-security ────────────────────────────────
@@ -2630,7 +2626,7 @@ def cmd_test_security(args):
2630
2626
  cfg = {}
2631
2627
 
2632
2628
  workspace_id = cfg.get("workspace_id")
2633
- api_key = cfg.get("api_key", "")
2629
+ api_key = cfg.get("agent_token", "")
2634
2630
  user_email = cfg.get("user_email", "")
2635
2631
  api_url = cfg.get("api_url", "https://api.conductai.ai").rstrip("/")
2636
2632
 
@@ -2647,7 +2643,7 @@ def cmd_test_security(args):
2647
2643
  try:
2648
2644
  req = urllib.request.Request(
2649
2645
  f"{api_url}/security-findings?workspace_id={workspace_id}&source_run_id=conduct-test-security",
2650
- headers={"X-Api-Key": api_key},
2646
+ headers={"Authorization": f"Bearer {api_key}"},
2651
2647
  method="DELETE",
2652
2648
  )
2653
2649
  with urllib.request.urlopen(req, timeout=8) as resp:
@@ -2678,7 +2674,7 @@ def cmd_test_security(args):
2678
2674
  req = urllib.request.Request(
2679
2675
  f"{api_url}/security-findings?workspace_id={workspace_id}",
2680
2676
  data=payload,
2681
- headers={"Content-Type": "application/json", "X-Api-Key": api_key},
2677
+ headers={"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"},
2682
2678
  method="POST",
2683
2679
  )
2684
2680
  with urllib.request.urlopen(req, timeout=8) as resp:
@@ -2707,7 +2703,7 @@ def cmd_test_security_verify(args):
2707
2703
  cfg = {}
2708
2704
 
2709
2705
  workspace_id = cfg.get("workspace_id")
2710
- api_key = cfg.get("api_key", "")
2706
+ api_key = cfg.get("agent_token", "")
2711
2707
  user_email = cfg.get("user_email", "")
2712
2708
  api_url = cfg.get("api_url", "https://api.conductai.ai").rstrip("/")
2713
2709
 
@@ -2727,7 +2723,7 @@ def cmd_test_security_verify(args):
2727
2723
  req = urllib.request.Request(
2728
2724
  f"{api_url}/secure/refresh-automation?workspace_id={workspace_id}",
2729
2725
  data=b"{}",
2730
- headers={"Content-Type": "application/json", "X-Api-Key": api_key},
2726
+ headers={"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"},
2731
2727
  method="POST",
2732
2728
  )
2733
2729
  with urllib.request.urlopen(req, timeout=15) as resp:
@@ -2743,7 +2739,7 @@ def cmd_test_security_verify(args):
2743
2739
  try:
2744
2740
  req = urllib.request.Request(
2745
2741
  f"{api_url}/security-findings?workspace_id={workspace_id}&source_run_id=conduct-test-security",
2746
- headers={"X-Api-Key": api_key},
2742
+ headers={"Authorization": f"Bearer {api_key}"},
2747
2743
  method="DELETE",
2748
2744
  )
2749
2745
  with urllib.request.urlopen(req, timeout=8) as resp:
@@ -2773,7 +2769,7 @@ def cmd_test_security_verify(args):
2773
2769
  req = urllib.request.Request(
2774
2770
  f"{api_url}/security-findings?workspace_id={workspace_id}",
2775
2771
  data=payload,
2776
- headers={"Content-Type": "application/json", "X-Api-Key": api_key},
2772
+ headers={"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"},
2777
2773
  method="POST",
2778
2774
  )
2779
2775
  with urllib.request.urlopen(req, timeout=8) as resp:
@@ -2799,7 +2795,7 @@ def cmd_test_security_verify(args):
2799
2795
  try:
2800
2796
  req = urllib.request.Request(
2801
2797
  f"{api_url}/security-findings?workspace_id={workspace_id}&source_run_id=conduct-test-security&limit=100",
2802
- headers={"X-Api-Key": api_key},
2798
+ headers={"Authorization": f"Bearer {api_key}"},
2803
2799
  )
2804
2800
  with urllib.request.urlopen(req, timeout=10) as resp:
2805
2801
  findings = _json.loads(resp.read())
@@ -2890,7 +2886,7 @@ def cmd_test_guard(args):
2890
2886
  sys.exit(0)
2891
2887
 
2892
2888
  workspace_id = cfg.get("workspace_id")
2893
- api_key = cfg.get("api_key", "")
2889
+ api_key = cfg.get("agent_token", "")
2894
2890
  api_url = cfg.get("api_url", "https://api.conductai.ai").rstrip("/")
2895
2891
 
2896
2892
  print(f"\n{BOLD}▶ conduct test-guard — {len(rules)} rule(s){RESET}\n")
@@ -2934,7 +2930,7 @@ def cmd_test_guard(args):
2934
2930
  req = urllib.request.Request(
2935
2931
  f"{api_url}/guard/events/test?workspace_id={workspace_id}",
2936
2932
  data=payload,
2937
- headers={"Content-Type": "application/json", "X-Api-Key": api_key},
2933
+ headers={"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"},
2938
2934
  method="POST",
2939
2935
  )
2940
2936
  with urllib.request.urlopen(req, timeout=5):
@@ -3009,12 +3005,10 @@ def _check_guard_setup(command: str) -> None:
3009
3005
  def cmd_skill(args):
3010
3006
  """conduct skill list | install <slug> | uninstall <slug>"""
3011
3007
  skill_command = getattr(args, "skill_command", None)
3012
- server, workspace, api_key, token = _require_auth(args)
3008
+ server, workspace, token = _require_auth(args)
3013
3009
 
3014
3010
  headers = {"Content-Type": "application/json"}
3015
- if api_key:
3016
- headers["X-Api-Key"] = api_key
3017
- elif token:
3011
+ if token:
3018
3012
  headers["Authorization"] = f"Bearer {token}"
3019
3013
 
3020
3014
  def _req(method, path, expect=None):
@@ -3086,7 +3080,6 @@ def main():
3086
3080
  )
3087
3081
  # Global overrides (optional — config file is preferred)
3088
3082
  parser.add_argument("--server", help="API URL (default: from ~/.conduct/config.json)")
3089
- parser.add_argument("--api-key", dest="api_key", help="CLI API key")
3090
3083
  parser.add_argument("--token", help=argparse.SUPPRESS)
3091
3084
  parser.add_argument("--workspace", help="Workspace ID")
3092
3085
 
@@ -294,7 +294,7 @@ def main() -> None:
294
294
  cfg = _load_conduct_config()
295
295
  server = cfg.get("server", "").rstrip("/")
296
296
  workspace_id = cfg.get("workspace", "")
297
- api_key = cfg.get("api_key")
297
+ api_key = cfg.get("agent_token")
298
298
  token = cfg.get("token")
299
299
 
300
300
  for raw in sys.stdin:
@@ -45,7 +45,7 @@ def post_session_to_api(session_id: str, transcript_path: str | None, repo: str
45
45
  if not cfg:
46
46
  return False
47
47
  server = cfg.get("server", "").rstrip("/")
48
- api_key = cfg.get("api_key", "")
48
+ api_key = cfg.get("agent_token", "")
49
49
  workspace_id = cfg.get("workspace_id") or cfg.get("workspace", "")
50
50
  if not server or not workspace_id:
51
51
  return False
@@ -96,7 +96,7 @@ def post_session_to_api(session_id: str, transcript_path: str | None, repo: str
96
96
  data=payload,
97
97
  headers={
98
98
  "Content-Type": "application/json",
99
- "X-API-Key": api_key,
99
+ "Authorization": f"Bearer {api_key}",
100
100
  "X-Workspace-ID": workspace_id,
101
101
  },
102
102
  method="POST",
@@ -116,7 +116,7 @@ def search_team_memory(query: str, repo: str | None = None, limit: int = 5) -> l
116
116
  if not cfg:
117
117
  return []
118
118
  server = cfg.get("server", "").rstrip("/")
119
- api_key = cfg.get("api_key", "")
119
+ api_key = cfg.get("agent_token", "")
120
120
  workspace_id = cfg.get("workspace_id") or cfg.get("workspace", "")
121
121
  if not server or not workspace_id:
122
122
  return []
@@ -130,7 +130,7 @@ def search_team_memory(query: str, repo: str | None = None, limit: int = 5) -> l
130
130
  req = urllib.request.Request(
131
131
  url,
132
132
  headers={
133
- "X-API-Key": api_key,
133
+ "Authorization": f"Bearer {api_key}",
134
134
  "X-Workspace-ID": workspace_id,
135
135
  },
136
136
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.7.48
3
+ Version: 0.8.1
4
4
  Summary: CLI for Conduct AI — secure, govern, install agents, manage projects, run tests
5
5
  Author-email: Conduct AI <hello@conductai.ai>
6
6
  License: MIT
File without changes
File without changes
File without changes