conduct-cli 0.4.5__tar.gz → 0.4.7__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.5 → conduct_cli-0.4.7}/PKG-INFO +1 -1
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/pyproject.toml +1 -1
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/src/conduct_cli/guard.py +45 -18
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/src/conduct_cli.egg-info/PKG-INFO +1 -1
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/README.md +0 -0
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/setup.cfg +0 -0
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/setup.py +0 -0
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/src/conduct_cli/guardmcp.py +0 -0
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/src/conduct_cli/main.py +0 -0
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/src/conduct_cli.egg-info/requires.txt +0 -0
- {conduct_cli-0.4.5 → conduct_cli-0.4.7}/src/conduct_cli.egg-info/top_level.txt +0 -0
|
@@ -125,7 +125,7 @@ def _detect_ai_tool():
|
|
|
125
125
|
return "unknown"
|
|
126
126
|
|
|
127
127
|
|
|
128
|
-
def _post_event(tool_name, tool_input, decision, rule_id=None, message=None,
|
|
128
|
+
def _post_event(tool_name, tool_input, decision, rule_id=None, message=None, session_id=None):
|
|
129
129
|
try:
|
|
130
130
|
cfg = json.loads(CONFIG_PATH.read_text()) if CONFIG_PATH.exists() else {}
|
|
131
131
|
except Exception:
|
|
@@ -144,7 +144,7 @@ def _post_event(tool_name, tool_input, decision, rule_id=None, message=None, too
|
|
|
144
144
|
"decision": decision,
|
|
145
145
|
"rule_id": rule_id,
|
|
146
146
|
"rule_message": message,
|
|
147
|
-
"
|
|
147
|
+
"session_id": session_id,
|
|
148
148
|
})
|
|
149
149
|
api_url = cfg.get("api_url", "https://api.conductai.ai").rstrip("/")
|
|
150
150
|
script = (
|
|
@@ -163,18 +163,19 @@ def _post_event(tool_name, tool_input, decision, rule_id=None, message=None, too
|
|
|
163
163
|
)
|
|
164
164
|
|
|
165
165
|
|
|
166
|
-
def _post_usage(
|
|
166
|
+
def _post_usage(session_id, tool_name, tokens_input, tokens_output, duration_ms):
|
|
167
167
|
"""Fire-and-forget POST to /guard/events/usage"""
|
|
168
168
|
try:
|
|
169
169
|
cfg = json.loads(CONFIG_PATH.read_text()) if CONFIG_PATH.exists() else {}
|
|
170
170
|
except Exception:
|
|
171
171
|
return
|
|
172
172
|
workspace_id = cfg.get("workspace_id")
|
|
173
|
-
if not workspace_id or not
|
|
173
|
+
if not workspace_id or not session_id:
|
|
174
174
|
return
|
|
175
175
|
payload = json.dumps({
|
|
176
176
|
"workspace_id": workspace_id,
|
|
177
|
-
"
|
|
177
|
+
"session_id": session_id,
|
|
178
|
+
"tool_name": tool_name,
|
|
178
179
|
"tokens_input": tokens_input,
|
|
179
180
|
"tokens_output": tokens_output,
|
|
180
181
|
"duration_ms": duration_ms,
|
|
@@ -203,12 +204,13 @@ def post_usage_main():
|
|
|
203
204
|
data = json.load(sys.stdin)
|
|
204
205
|
except Exception:
|
|
205
206
|
sys.exit(0)
|
|
206
|
-
|
|
207
|
+
session_id = data.get("session_id")
|
|
208
|
+
tool_name = (data.get("tool_name") or "").lower()
|
|
207
209
|
usage = data.get("usage") or {}
|
|
208
210
|
tokens_input = usage.get("input_tokens", 0)
|
|
209
211
|
tokens_output = usage.get("output_tokens", 0)
|
|
210
212
|
duration_ms = data.get("duration_ms")
|
|
211
|
-
_post_usage(
|
|
213
|
+
_post_usage(session_id, tool_name, tokens_input, tokens_output, duration_ms)
|
|
212
214
|
sys.exit(0)
|
|
213
215
|
|
|
214
216
|
|
|
@@ -226,15 +228,15 @@ def main():
|
|
|
226
228
|
print(f"[ConductGuard] {reason or 'Budget hard cap reached. Contact your manager.'}")
|
|
227
229
|
sys.exit(2)
|
|
228
230
|
|
|
231
|
+
session_id = data.get("session_id")
|
|
229
232
|
tool_name = (data.get("tool_name") or "").lower()
|
|
230
233
|
tool_input = data.get("tool_input") or {}
|
|
231
|
-
tool_use_id = data.get("tool_use_id")
|
|
232
234
|
|
|
233
235
|
_, action, rule_id, message = _check_policy(tool_name, tool_input)
|
|
234
236
|
|
|
235
237
|
# Always post an event — "allowed" for normal calls, "blocked"/"warned" for violations
|
|
236
238
|
decision = {"block": "blocked", "warn": "warned", "approval": "blocked"}.get(action, "allowed")
|
|
237
|
-
_post_event(tool_name, tool_input, decision, rule_id, message,
|
|
239
|
+
_post_event(tool_name, tool_input, decision, rule_id, message, session_id=session_id)
|
|
238
240
|
|
|
239
241
|
if action == "block":
|
|
240
242
|
print(f"[ConductGuard] {message}")
|
|
@@ -246,7 +248,10 @@ def main():
|
|
|
246
248
|
|
|
247
249
|
|
|
248
250
|
if __name__ == "__main__":
|
|
249
|
-
|
|
251
|
+
if len(sys.argv) > 1 and sys.argv[1] == "post":
|
|
252
|
+
post_usage_main()
|
|
253
|
+
else:
|
|
254
|
+
main()
|
|
250
255
|
'''
|
|
251
256
|
|
|
252
257
|
# ── Guard config helpers ──────────────────────────────────────────────────────
|
|
@@ -343,16 +348,27 @@ def _install_codex_hook(hook_path: Path) -> None:
|
|
|
343
348
|
pre.append({"matcher": ".*", "hooks": [{"type": "command", "command": pre_cmd}]})
|
|
344
349
|
changed = True
|
|
345
350
|
|
|
346
|
-
# PostToolUse
|
|
347
|
-
post_cmd = "
|
|
351
|
+
# PostToolUse — self-contained: python3 /path/hook.py post (no PATH dependency)
|
|
352
|
+
post_cmd = f"python3 {hook_path} post"
|
|
348
353
|
post = hook_section.setdefault("PostToolUse", [])
|
|
354
|
+
# Remove stale conductguard-post entries registered by older CLI versions
|
|
355
|
+
stale = "conductguard-post"
|
|
356
|
+
cleaned = False
|
|
357
|
+
for h in post:
|
|
358
|
+
before = len(h.get("hooks", []))
|
|
359
|
+
h["hooks"] = [e for e in h.get("hooks", []) if e.get("command") != stale]
|
|
360
|
+
if len(h["hooks"]) < before:
|
|
361
|
+
cleaned = True
|
|
362
|
+
post[:] = [h for h in post if h.get("hooks")]
|
|
349
363
|
post_already = any(
|
|
350
364
|
e.get("command") == post_cmd
|
|
351
365
|
for h in post
|
|
352
366
|
for e in h.get("hooks", [])
|
|
353
367
|
)
|
|
354
368
|
if not post_already:
|
|
355
|
-
post.append({"matcher": "
|
|
369
|
+
post.append({"matcher": ".*", "hooks": [{"type": "command", "command": post_cmd}]})
|
|
370
|
+
changed = True
|
|
371
|
+
if cleaned:
|
|
356
372
|
changed = True
|
|
357
373
|
|
|
358
374
|
if changed:
|
|
@@ -360,7 +376,7 @@ def _install_codex_hook(hook_path: Path) -> None:
|
|
|
360
376
|
codex_hooks.write_text(json.dumps(hooks, indent=2))
|
|
361
377
|
if not pre_already:
|
|
362
378
|
print(f" {GREEN}Codex PreToolUse hook registered{RESET}")
|
|
363
|
-
if not post_already:
|
|
379
|
+
if not post_already or cleaned:
|
|
364
380
|
print(f" {GREEN}Codex PostToolUse hook registered{RESET}")
|
|
365
381
|
else:
|
|
366
382
|
print(f" {GRAY}Codex hooks already registered{RESET}")
|
|
@@ -439,16 +455,27 @@ def _install_claude_hook(hook_path: Path) -> None:
|
|
|
439
455
|
pre.append({"matcher": ".*", "hooks": [{"type": "command", "command": pre_cmd}]})
|
|
440
456
|
changed = True
|
|
441
457
|
|
|
442
|
-
# PostToolUse —
|
|
458
|
+
# PostToolUse — self-contained: python3 /path/hook.py post (no PATH dependency)
|
|
443
459
|
post = hooks.setdefault("PostToolUse", [])
|
|
444
|
-
post_cmd = "
|
|
460
|
+
post_cmd = f"python3 {hook_path} post"
|
|
461
|
+
# Remove stale conductguard-post entries registered by older CLI versions
|
|
462
|
+
stale = "conductguard-post"
|
|
463
|
+
cleaned = False
|
|
464
|
+
for h in post:
|
|
465
|
+
before = len(h.get("hooks", []))
|
|
466
|
+
h["hooks"] = [e for e in h.get("hooks", []) if e.get("command") != stale]
|
|
467
|
+
if len(h["hooks"]) < before:
|
|
468
|
+
cleaned = True
|
|
469
|
+
post[:] = [h for h in post if h.get("hooks")]
|
|
445
470
|
post_already = any(
|
|
446
471
|
e.get("command") == post_cmd
|
|
447
472
|
for h in post
|
|
448
473
|
for e in h.get("hooks", [])
|
|
449
474
|
)
|
|
450
475
|
if not post_already:
|
|
451
|
-
post.append({"matcher": "
|
|
476
|
+
post.append({"matcher": ".*", "hooks": [{"type": "command", "command": post_cmd}]})
|
|
477
|
+
changed = True
|
|
478
|
+
if cleaned:
|
|
452
479
|
changed = True
|
|
453
480
|
|
|
454
481
|
if changed:
|
|
@@ -456,7 +483,7 @@ def _install_claude_hook(hook_path: Path) -> None:
|
|
|
456
483
|
claude_settings.write_text(json.dumps(settings, indent=2))
|
|
457
484
|
if not pre_already:
|
|
458
485
|
print(f" {GREEN}Claude Code PreToolUse hook registered{RESET}")
|
|
459
|
-
if not post_already:
|
|
486
|
+
if not post_already or cleaned:
|
|
460
487
|
print(f" {GREEN}Claude Code PostToolUse hook registered{RESET}")
|
|
461
488
|
else:
|
|
462
489
|
print(f" {GRAY}Claude Code hooks already registered{RESET}")
|
|
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
|