conduct-cli 0.4.93__tar.gz → 0.4.95__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.4.93 → conduct_cli-0.4.95}/PKG-INFO +1 -1
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/pyproject.toml +1 -1
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli/main.py +115 -14
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli.egg-info/PKG-INFO +1 -1
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/README.md +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/setup.cfg +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/setup.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli/guard.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli/guardmcp.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli/hook_precompact_template.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli/hook_session_start_template.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli/hook_stop_template.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli/hook_template.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli/mcp_server.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli/memory.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli/paxel.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli.egg-info/requires.txt +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/src/conduct_cli.egg-info/top_level.txt +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/tests/test_guard_policy.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/tests/test_guard_savings.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/tests/test_hook_syntax.py +0 -0
- {conduct_cli-0.4.93 → conduct_cli-0.4.95}/tests/test_switch.py +0 -0
|
@@ -2334,18 +2334,75 @@ def cmd_emit_finding(args):
|
|
|
2334
2334
|
print(finding_id)
|
|
2335
2335
|
|
|
2336
2336
|
|
|
2337
|
+
def _gh_api_get(url: str, token: str):
|
|
2338
|
+
import urllib.request, urllib.error
|
|
2339
|
+
req = urllib.request.Request(url, headers={
|
|
2340
|
+
"Authorization": f"Bearer {token}",
|
|
2341
|
+
"Accept": "application/vnd.github+json",
|
|
2342
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
2343
|
+
})
|
|
2344
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
2345
|
+
return json.loads(resp.read())
|
|
2346
|
+
|
|
2347
|
+
|
|
2348
|
+
def _fetch_github_issue(repo: str, issue_number: int, token: str) -> dict:
|
|
2349
|
+
return _gh_api_get(f"https://api.github.com/repos/{repo}/issues/{issue_number}", token)
|
|
2350
|
+
|
|
2351
|
+
|
|
2352
|
+
def _fetch_issues_by_label(repo: str, label: str, token: str) -> list:
|
|
2353
|
+
return _gh_api_get(
|
|
2354
|
+
f"https://api.github.com/repos/{repo}/issues?labels={label}&state=open&per_page=20",
|
|
2355
|
+
token,
|
|
2356
|
+
)
|
|
2357
|
+
|
|
2358
|
+
|
|
2359
|
+
def _build_issue_trigger_payload(issue: dict, repo: str) -> dict:
|
|
2360
|
+
owner, repo_name = (repo.split("/", 1) + [""])[:2]
|
|
2361
|
+
return {
|
|
2362
|
+
"action": "labeled",
|
|
2363
|
+
"issue": {
|
|
2364
|
+
"number": issue["number"],
|
|
2365
|
+
"title": issue["title"],
|
|
2366
|
+
"body": issue.get("body") or "",
|
|
2367
|
+
"html_url": issue.get("html_url", ""),
|
|
2368
|
+
"user": issue.get("user") or {},
|
|
2369
|
+
"labels": issue.get("labels") or [],
|
|
2370
|
+
},
|
|
2371
|
+
"repository": {
|
|
2372
|
+
"full_name": repo,
|
|
2373
|
+
"name": repo_name,
|
|
2374
|
+
"owner": {"login": owner},
|
|
2375
|
+
"clone_url": f"https://github.com/{repo}.git",
|
|
2376
|
+
"default_branch": "main",
|
|
2377
|
+
},
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
|
|
2381
|
+
def _prompt_issue_choice(issues: list) -> dict:
|
|
2382
|
+
print(f"\n{BOLD}Multiple open issues found — choose one:{RESET}\n")
|
|
2383
|
+
for i, iss in enumerate(issues):
|
|
2384
|
+
print(f" {BOLD}{i + 1}.{RESET} #{iss['number']} — {iss['title']}")
|
|
2385
|
+
print()
|
|
2386
|
+
while True:
|
|
2387
|
+
raw = input(f"Enter number [1–{len(issues)}]: ").strip()
|
|
2388
|
+
if raw.isdigit() and 1 <= int(raw) <= len(issues):
|
|
2389
|
+
return issues[int(raw) - 1]
|
|
2390
|
+
print(f"{RED}Invalid choice.{RESET}")
|
|
2391
|
+
|
|
2392
|
+
|
|
2337
2393
|
def cmd_run(args):
|
|
2338
2394
|
server, workspace_id, api_key, token = _require_auth(args)
|
|
2339
2395
|
json_h = api.headers(workspace_id, token, "application/json", api_key)
|
|
2340
2396
|
|
|
2341
|
-
#
|
|
2342
|
-
|
|
2397
|
+
# Fix 1: --input values go into state["inputs"], not top-level state.
|
|
2398
|
+
# {{inputs.key}} refs in YAML only resolve when the executor finds state["inputs"].
|
|
2399
|
+
run_inputs: dict = {}
|
|
2343
2400
|
for kv in (args.input or []):
|
|
2344
2401
|
if "=" not in kv:
|
|
2345
2402
|
print(f"{RED}Bad --input format '{kv}' — expected key=value{RESET}")
|
|
2346
2403
|
sys.exit(1)
|
|
2347
2404
|
k, v = kv.split("=", 1)
|
|
2348
|
-
|
|
2405
|
+
run_inputs[k] = v
|
|
2349
2406
|
|
|
2350
2407
|
# Resolve agent by name
|
|
2351
2408
|
target = args.agent
|
|
@@ -2366,17 +2423,22 @@ def cmd_run(args):
|
|
|
2366
2423
|
sys.exit(1)
|
|
2367
2424
|
|
|
2368
2425
|
workflow_id = wf["id"]
|
|
2426
|
+
|
|
2427
|
+
# Fix 4: determine trigger type from workflow metadata.
|
|
2428
|
+
is_github_webhook = bool(wf.get("github_webhook"))
|
|
2429
|
+
slug = wf.get("playbook_slug") or ""
|
|
2430
|
+
is_issue_trigger = is_github_webhook and "issue" in slug
|
|
2431
|
+
|
|
2369
2432
|
print(f"\n{BOLD}▶ conduct run — {wf['name']}{RESET}")
|
|
2370
|
-
if
|
|
2371
|
-
for k, v in
|
|
2433
|
+
if run_inputs:
|
|
2434
|
+
for k, v in run_inputs.items():
|
|
2372
2435
|
print(f" {GRAY}{k}={v}{RESET}")
|
|
2373
2436
|
print()
|
|
2374
2437
|
|
|
2375
|
-
#
|
|
2438
|
+
# Fix 2: preflight receives run_inputs so turn estimates use real input context.
|
|
2376
2439
|
try:
|
|
2377
2440
|
pf = api.req("POST", f"{server}/workflows/{workflow_id}/preflight", json_h, {
|
|
2378
|
-
"
|
|
2379
|
-
"issue_body": initial_state.get("body", ""),
|
|
2441
|
+
"run_inputs": run_inputs,
|
|
2380
2442
|
})
|
|
2381
2443
|
suggested = pf.get("suggested_max_turns", 20)
|
|
2382
2444
|
files = pf.get("total_files", [])
|
|
@@ -2387,17 +2449,56 @@ def cmd_run(args):
|
|
|
2387
2449
|
except Exception:
|
|
2388
2450
|
suggested = 20
|
|
2389
2451
|
|
|
2390
|
-
#
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
#
|
|
2452
|
+
# Fix 4: extract github_token before building body so it never lands in state["inputs"].
|
|
2453
|
+
import os as _os
|
|
2454
|
+
gh_token = run_inputs.pop("github_token", None) or _os.environ.get("GITHUB_TOKEN")
|
|
2455
|
+
|
|
2456
|
+
# Build the trigger body.
|
|
2457
|
+
# Fix 1: send inputs under "inputs" key so server puts them in state["inputs"].
|
|
2458
|
+
body: dict = {}
|
|
2459
|
+
if run_inputs:
|
|
2460
|
+
body["inputs"] = run_inputs
|
|
2461
|
+
|
|
2462
|
+
# Fix 4: non-webhook triggers (manual/schedule) — flag so server skips trigger validation.
|
|
2463
|
+
if not is_github_webhook:
|
|
2464
|
+
body["__manual"] = True
|
|
2465
|
+
|
|
2466
|
+
# Fix 4: github_issue_labeled — fire against a real issue when possible.
|
|
2467
|
+
if is_issue_trigger:
|
|
2468
|
+
repo = wf.get("github_hook_repo") or run_inputs.get("repo")
|
|
2469
|
+
label = wf.get("github_hook_label") or ""
|
|
2470
|
+
issue_number_raw = run_inputs.get("issue_number")
|
|
2471
|
+
if gh_token and repo:
|
|
2472
|
+
try:
|
|
2473
|
+
if issue_number_raw:
|
|
2474
|
+
issue = _fetch_github_issue(repo, int(issue_number_raw), gh_token)
|
|
2475
|
+
body.update(_build_issue_trigger_payload(issue, repo))
|
|
2476
|
+
print(f" {GRAY}issue: #{issue['number']} — {issue['title']}{RESET}\n")
|
|
2477
|
+
elif label:
|
|
2478
|
+
issues = _fetch_issues_by_label(repo, label, gh_token)
|
|
2479
|
+
if not issues:
|
|
2480
|
+
print(f"{YELLOW}⚠ No open issues with label '{label}' in {repo}. Using test payload.{RESET}\n")
|
|
2481
|
+
elif len(issues) == 1:
|
|
2482
|
+
body.update(_build_issue_trigger_payload(issues[0], repo))
|
|
2483
|
+
print(f" {GRAY}issue: #{issues[0]['number']} — {issues[0]['title']}{RESET}\n")
|
|
2484
|
+
else:
|
|
2485
|
+
chosen = _prompt_issue_choice(issues)
|
|
2486
|
+
body.update(_build_issue_trigger_payload(chosen, repo))
|
|
2487
|
+
print(f" {GRAY}issue: #{chosen['number']} — {chosen['title']}{RESET}\n")
|
|
2488
|
+
except Exception as _gh_err:
|
|
2489
|
+
print(f"{YELLOW}⚠ GitHub fetch failed ({_gh_err}). Using test payload.{RESET}\n")
|
|
2490
|
+
else:
|
|
2491
|
+
hint = "export GITHUB_TOKEN=<token>" if not gh_token else "workflow has no github_hook_repo"
|
|
2492
|
+
print(f" {GRAY}No GitHub token — using test payload. ({hint}){RESET}\n")
|
|
2493
|
+
|
|
2494
|
+
# Fix 3: /trigger returns run_id, not id.
|
|
2395
2495
|
if getattr(args, "max_turns", None):
|
|
2396
2496
|
body["__max_turns"] = args.max_turns
|
|
2397
2497
|
elif suggested > 20:
|
|
2398
2498
|
body["__max_turns"] = suggested
|
|
2399
2499
|
run = api.req("POST", f"{server}/workflows/{workflow_id}/trigger", json_h, body)
|
|
2400
|
-
|
|
2500
|
+
run_id = run.get("run_id") or run.get("id")
|
|
2501
|
+
_stream_run(server, workflow_id, run_id, workspace_id, token, api_key)
|
|
2401
2502
|
|
|
2402
2503
|
|
|
2403
2504
|
# ── conduct sync / test-guard / test-security ────────────────────────────────
|
|
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
|