sernixa-cli 0.2.0__tar.gz → 0.3.0__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.
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/PKG-INFO +10 -4
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/README.md +7 -2
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/pyproject.toml +3 -2
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli/__init__.py +1 -1
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli/discovery.py +3 -3
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli/formatting.py +21 -38
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli/local_agent_targets.py +41 -6
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli/main.py +192 -112
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli.egg-info/PKG-INFO +10 -4
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli.egg-info/requires.txt +1 -1
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/tests/test_cli.py +311 -6
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/tests/test_discovery.py +6 -5
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/tests/test_local_agent_targets.py +34 -8
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli/__main__.py +0 -0
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli/adapters.py +0 -0
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli/config.py +0 -0
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli/hooks.py +0 -0
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli/mcp_server.py +0 -0
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli/native_launcher.py +0 -0
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli.egg-info/SOURCES.txt +0 -0
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli.egg-info/dependency_links.txt +0 -0
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli.egg-info/entry_points.txt +0 -0
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/sernixa_cli.egg-info/top_level.txt +0 -0
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/setup.cfg +0 -0
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/tests/test_mcp_server.py +0 -0
- {sernixa_cli-0.2.0 → sernixa_cli-0.3.0}/tests/test_native_launcher.py +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sernixa-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Official Sernixa CLI for policy-governed local AI agent actions
|
|
5
5
|
Author: Sernixa Team
|
|
6
6
|
License-Expression: MIT
|
|
7
|
+
Project-URL: Changelog, https://github.com/abhishekdhull63/Sernixa.ai-Web/tree/main/apps/cli/CHANGELOG.md
|
|
7
8
|
Project-URL: Homepage, https://sernixa.com
|
|
8
9
|
Project-URL: Repository, https://github.com/abhishekdhull63/Sernixa.ai-Web
|
|
9
10
|
Project-URL: Documentation, https://github.com/abhishekdhull63/Sernixa.ai-Web/tree/main/docs/cli.md
|
|
@@ -20,7 +21,7 @@ Requires-Python: >=3.10
|
|
|
20
21
|
Description-Content-Type: text/markdown
|
|
21
22
|
Requires-Dist: platformdirs>=4.0.0
|
|
22
23
|
Requires-Dist: sernixa>=0.4.0
|
|
23
|
-
Requires-Dist: sernixa-agent>=1.6.
|
|
24
|
+
Requires-Dist: sernixa-agent>=1.6.1
|
|
24
25
|
Requires-Dist: typer>=0.12.0
|
|
25
26
|
|
|
26
27
|
# Sernixa CLI
|
|
@@ -33,7 +34,7 @@ The official terminal and local-agent integration for Sernixa governance. The CL
|
|
|
33
34
|
pip install --upgrade sernixa-cli
|
|
34
35
|
sernixa auth login
|
|
35
36
|
sernixa setup
|
|
36
|
-
sernixa doctor
|
|
37
|
+
sernixa doctor --strict
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
`sernixa setup` configures Claude Code, Claude Desktop, Codex, and Cursor, preserves unrelated entries, backs up changed files, registers the bounded `sernixa://` dashboard launcher, and reports host restart or hook-trust steps. Preview without changing files:
|
|
@@ -85,11 +86,13 @@ sernixa hook install codex
|
|
|
85
86
|
sernixa hook install cursor
|
|
86
87
|
|
|
87
88
|
sernixa hook repair codex
|
|
88
|
-
sernixa hook status
|
|
89
|
+
sernixa hook status
|
|
89
90
|
```
|
|
90
91
|
|
|
91
92
|
Claude Code and Codex hooks call `sernixa hook run <provider>` before tool execution. `allow` continues through normal host permissions and `deny` blocks. Claude Code maps `review` to its supported human prompt. Current Codex `PreToolUse` does not support an `ask` result, so Sernixa maps `review` to a fail-closed deny with an explicit human-review reason instead of returning a hook shape that Codex would ignore. An API/configuration failure exits with code `2`, which both hosts treat as blocking; there is no local allow fallback.
|
|
92
93
|
|
|
94
|
+
Hook handlers allow 35 seconds for the live policy request; the CLI request budget is 30 seconds. This prevents slow semantic evaluations from being killed by a shorter host-hook timeout.
|
|
95
|
+
|
|
93
96
|
Claude Desktop and Cursor run `sernixa mcp serve`. The stdio MCP server exposes three non-mutating tools:
|
|
94
97
|
|
|
95
98
|
- `sernixa_policy_check`: evaluate one proposed action.
|
|
@@ -117,6 +120,8 @@ Exit codes are stable:
|
|
|
117
120
|
- `1`: deny, review required, invalid evidence, or non-success terminal result
|
|
118
121
|
- `2`: configuration, parsing, auth, network, or API failure
|
|
119
122
|
|
|
123
|
+
Human output is intentionally concise. Use `--json` only when a script needs structured details; JSON decisions omit the duplicated raw backend payload.
|
|
124
|
+
|
|
120
125
|
`validate-plan` calls the canonical plan endpoint. `exec` and `codex-exec` run the child command only after every planned step is allowed and bound to its proof.
|
|
121
126
|
|
|
122
127
|
## Audit, approvals, and Flight Recorder
|
|
@@ -139,6 +144,7 @@ These commands call the authenticated organization endpoints and inherit backend
|
|
|
139
144
|
## Stable configuration overrides
|
|
140
145
|
|
|
141
146
|
- `SERNIXA_CONFIG_FILE`
|
|
147
|
+
- `SERNIXA_CLI_TIMEOUT_SECONDS`
|
|
142
148
|
- `SERNIXA_PLAN_SESSION_FILE`
|
|
143
149
|
- `SERNIXA_CLAUDE_CONFIG_FILE`
|
|
144
150
|
- `SERNIXA_CLAUDE_DESKTOP_CONFIG_FILE`
|
|
@@ -8,7 +8,7 @@ The official terminal and local-agent integration for Sernixa governance. The CL
|
|
|
8
8
|
pip install --upgrade sernixa-cli
|
|
9
9
|
sernixa auth login
|
|
10
10
|
sernixa setup
|
|
11
|
-
sernixa doctor
|
|
11
|
+
sernixa doctor --strict
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
`sernixa setup` configures Claude Code, Claude Desktop, Codex, and Cursor, preserves unrelated entries, backs up changed files, registers the bounded `sernixa://` dashboard launcher, and reports host restart or hook-trust steps. Preview without changing files:
|
|
@@ -60,11 +60,13 @@ sernixa hook install codex
|
|
|
60
60
|
sernixa hook install cursor
|
|
61
61
|
|
|
62
62
|
sernixa hook repair codex
|
|
63
|
-
sernixa hook status
|
|
63
|
+
sernixa hook status
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
Claude Code and Codex hooks call `sernixa hook run <provider>` before tool execution. `allow` continues through normal host permissions and `deny` blocks. Claude Code maps `review` to its supported human prompt. Current Codex `PreToolUse` does not support an `ask` result, so Sernixa maps `review` to a fail-closed deny with an explicit human-review reason instead of returning a hook shape that Codex would ignore. An API/configuration failure exits with code `2`, which both hosts treat as blocking; there is no local allow fallback.
|
|
67
67
|
|
|
68
|
+
Hook handlers allow 35 seconds for the live policy request; the CLI request budget is 30 seconds. This prevents slow semantic evaluations from being killed by a shorter host-hook timeout.
|
|
69
|
+
|
|
68
70
|
Claude Desktop and Cursor run `sernixa mcp serve`. The stdio MCP server exposes three non-mutating tools:
|
|
69
71
|
|
|
70
72
|
- `sernixa_policy_check`: evaluate one proposed action.
|
|
@@ -92,6 +94,8 @@ Exit codes are stable:
|
|
|
92
94
|
- `1`: deny, review required, invalid evidence, or non-success terminal result
|
|
93
95
|
- `2`: configuration, parsing, auth, network, or API failure
|
|
94
96
|
|
|
97
|
+
Human output is intentionally concise. Use `--json` only when a script needs structured details; JSON decisions omit the duplicated raw backend payload.
|
|
98
|
+
|
|
95
99
|
`validate-plan` calls the canonical plan endpoint. `exec` and `codex-exec` run the child command only after every planned step is allowed and bound to its proof.
|
|
96
100
|
|
|
97
101
|
## Audit, approvals, and Flight Recorder
|
|
@@ -114,6 +118,7 @@ These commands call the authenticated organization endpoints and inherit backend
|
|
|
114
118
|
## Stable configuration overrides
|
|
115
119
|
|
|
116
120
|
- `SERNIXA_CONFIG_FILE`
|
|
121
|
+
- `SERNIXA_CLI_TIMEOUT_SECONDS`
|
|
117
122
|
- `SERNIXA_PLAN_SESSION_FILE`
|
|
118
123
|
- `SERNIXA_CLAUDE_CONFIG_FILE`
|
|
119
124
|
- `SERNIXA_CLAUDE_DESKTOP_CONFIG_FILE`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sernixa-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Official Sernixa CLI for policy-governed local AI agent actions"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -20,11 +20,12 @@ classifiers = [
|
|
|
20
20
|
dependencies = [
|
|
21
21
|
"platformdirs>=4.0.0",
|
|
22
22
|
"sernixa>=0.4.0",
|
|
23
|
-
"sernixa-agent>=1.6.
|
|
23
|
+
"sernixa-agent>=1.6.1",
|
|
24
24
|
"typer>=0.12.0",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
[project.urls]
|
|
28
|
+
Changelog = "https://github.com/abhishekdhull63/Sernixa.ai-Web/tree/main/apps/cli/CHANGELOG.md"
|
|
28
29
|
Homepage = "https://sernixa.com"
|
|
29
30
|
Repository = "https://github.com/abhishekdhull63/Sernixa.ai-Web"
|
|
30
31
|
Documentation = "https://github.com/abhishekdhull63/Sernixa.ai-Web/tree/main/docs/cli.md"
|
|
@@ -44,7 +44,7 @@ def _agent_executable() -> str:
|
|
|
44
44
|
executable = shutil.which("sernixa-agent")
|
|
45
45
|
if not executable:
|
|
46
46
|
raise DiscoveryError(
|
|
47
|
-
"sernixa-agent is not installed. Upgrade sernixa-cli or install sernixa-agent>=1.6.
|
|
47
|
+
"sernixa-agent is not installed. Upgrade sernixa-cli or install sernixa-agent>=1.6.1."
|
|
48
48
|
)
|
|
49
49
|
return executable
|
|
50
50
|
|
|
@@ -155,14 +155,14 @@ def run_discovery(
|
|
|
155
155
|
agent_run = _run_agent(
|
|
156
156
|
[
|
|
157
157
|
executable,
|
|
158
|
-
"
|
|
158
|
+
"tasks-once",
|
|
159
159
|
"--config",
|
|
160
160
|
str(config_path),
|
|
161
161
|
"--api-base-url",
|
|
162
162
|
config.base_url,
|
|
163
163
|
"--token-env",
|
|
164
164
|
"SERNIXA_DISCOVERY_TOKEN",
|
|
165
|
-
"--
|
|
165
|
+
"--json",
|
|
166
166
|
],
|
|
167
167
|
extra_env=agent_env,
|
|
168
168
|
)
|
|
@@ -20,7 +20,7 @@ def exit_code_for_decision(decision: dict[str, Any]) -> int:
|
|
|
20
20
|
def decision_summary(
|
|
21
21
|
backend_decision: dict[str, Any],
|
|
22
22
|
*,
|
|
23
|
-
include_raw: bool =
|
|
23
|
+
include_raw: bool = False,
|
|
24
24
|
request: dict[str, Any] | None = None,
|
|
25
25
|
) -> dict[str, Any]:
|
|
26
26
|
allowed = is_allowed(backend_decision)
|
|
@@ -60,51 +60,34 @@ def render_decision(
|
|
|
60
60
|
)
|
|
61
61
|
return
|
|
62
62
|
if allowed:
|
|
63
|
-
typer.secho("
|
|
64
|
-
matched = backend_decision.get("matched_policies") or []
|
|
65
|
-
if matched:
|
|
66
|
-
typer.echo(f"Matched policies: {', '.join(str(item) for item in matched)}")
|
|
63
|
+
typer.secho("ALLOW", fg=typer.colors.GREEN)
|
|
67
64
|
return
|
|
68
65
|
review_required = (
|
|
69
66
|
str(backend_decision.get("final_decision") or "").casefold() == "review"
|
|
70
67
|
)
|
|
68
|
+
violations = _violations(backend_decision, request=request)
|
|
69
|
+
primary = violations[0] if violations else {}
|
|
70
|
+
category = primary.get("risk_category") or primary.get("category")
|
|
71
|
+
severity = primary.get("severity")
|
|
72
|
+
heading = "REVIEW" if review_required else "DENY"
|
|
73
|
+
details = [str(item) for item in (category, severity) if item]
|
|
74
|
+
if details:
|
|
75
|
+
heading = f"{heading} · {' · '.join(details)}"
|
|
71
76
|
typer.secho(
|
|
72
|
-
|
|
73
|
-
if review_required
|
|
74
|
-
else "Denied by Sernixa policy.",
|
|
77
|
+
heading,
|
|
75
78
|
fg=typer.colors.YELLOW if review_required else typer.colors.RED,
|
|
76
79
|
err=True,
|
|
77
80
|
)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
violation.get("message") or violation.get("reason") or "Denied by policy."
|
|
89
|
-
)
|
|
90
|
-
prefix = f"{label}"
|
|
91
|
-
if category:
|
|
92
|
-
prefix = f"{prefix} [{category}]"
|
|
93
|
-
if severity:
|
|
94
|
-
prefix = f"{prefix} severity={severity}"
|
|
95
|
-
typer.echo(f"- {prefix}: {message}", err=True)
|
|
96
|
-
if violation.get("principle"):
|
|
97
|
-
typer.echo(f" principle: {violation['principle']}", err=True)
|
|
98
|
-
if category:
|
|
99
|
-
typer.echo(f" risk category: {category}", err=True)
|
|
100
|
-
if violation.get("severity"):
|
|
101
|
-
typer.echo(f" severity: {violation['severity']}", err=True)
|
|
102
|
-
if violation.get("resource"):
|
|
103
|
-
typer.echo(f" resource: {violation['resource']}", err=True)
|
|
104
|
-
if violation.get("action"):
|
|
105
|
-
typer.echo(f" action: {violation['action']}", err=True)
|
|
106
|
-
if violation.get("remediation"):
|
|
107
|
-
typer.echo(f" remediation: {violation['remediation']}", err=True)
|
|
81
|
+
message = primary.get("message") or primary.get("reason")
|
|
82
|
+
if not message:
|
|
83
|
+
reasons = backend_decision.get("reasons") or []
|
|
84
|
+
message = reasons[0] if reasons else "Blocked by policy."
|
|
85
|
+
typer.echo(f"Reason: {message}", err=True)
|
|
86
|
+
remediation = primary.get("remediation") or _first_remediation(
|
|
87
|
+
backend_decision.get("remediation")
|
|
88
|
+
)
|
|
89
|
+
if remediation:
|
|
90
|
+
typer.echo(f"Next: {remediation}", err=True)
|
|
108
91
|
|
|
109
92
|
|
|
110
93
|
def hook_response(
|
|
@@ -17,6 +17,7 @@ ConfigFormat = Literal["json", "toml"]
|
|
|
17
17
|
|
|
18
18
|
SERNIXA_TOML_BEGIN = "# BEGIN SERNIXA MANAGED CODEX HOOK"
|
|
19
19
|
SERNIXA_TOML_END = "# END SERNIXA MANAGED CODEX HOOK"
|
|
20
|
+
HOOK_TIMEOUT_SECONDS = 35
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class TargetConfigError(RuntimeError):
|
|
@@ -132,6 +133,14 @@ class TargetStatus:
|
|
|
132
133
|
_status_path(candidate.path) for candidate in self.candidates
|
|
133
134
|
]
|
|
134
135
|
primary_write_target = _status_path(self.primary_write_target)
|
|
136
|
+
enforcement_mode = (
|
|
137
|
+
"mcp_server"
|
|
138
|
+
if self.target_id in {"claude-desktop", "cursor"}
|
|
139
|
+
else "pre_tool_hook"
|
|
140
|
+
)
|
|
141
|
+
integration_effective = self.wrapper_valid and (
|
|
142
|
+
enforcement_mode == "mcp_server" or self.hooks_effective
|
|
143
|
+
)
|
|
135
144
|
return {
|
|
136
145
|
"target_id": self.target_id,
|
|
137
146
|
"target": self.target_id,
|
|
@@ -157,6 +166,8 @@ class TargetStatus:
|
|
|
157
166
|
"installed": self.sernixa_installed,
|
|
158
167
|
"detected": self.sernixa_installed,
|
|
159
168
|
"wrapper_valid": self.wrapper_valid,
|
|
169
|
+
"enforcement_mode": enforcement_mode,
|
|
170
|
+
"integration_effective": integration_effective,
|
|
160
171
|
"malformed": self.malformed,
|
|
161
172
|
"parse_errors": list(self.parse_errors),
|
|
162
173
|
"parse_error": self.parse_errors[0] if self.parse_errors else None,
|
|
@@ -353,14 +364,14 @@ def hook_snippet(target_name: str) -> dict[str, Any]:
|
|
|
353
364
|
"type": "command",
|
|
354
365
|
"command": command[0],
|
|
355
366
|
"args": command[1:],
|
|
356
|
-
"timeout":
|
|
367
|
+
"timeout": HOOK_TIMEOUT_SECONDS,
|
|
357
368
|
"statusMessage": "Checking Sernixa policy",
|
|
358
369
|
}
|
|
359
370
|
else:
|
|
360
371
|
handler = {
|
|
361
372
|
"type": "command",
|
|
362
373
|
"command": " ".join(command),
|
|
363
|
-
"timeout":
|
|
374
|
+
"timeout": HOOK_TIMEOUT_SECONDS,
|
|
364
375
|
"statusMessage": "Checking Sernixa policy",
|
|
365
376
|
}
|
|
366
377
|
return {"hooks": {"PreToolUse": [{"matcher": "*", "hooks": [handler]}]}}
|
|
@@ -385,7 +396,7 @@ def install_target(target_name: str) -> tuple[TargetResolution, str, Path | None
|
|
|
385
396
|
merged, changed = _merge_hooks(existing, snippet, resolution.provider)
|
|
386
397
|
backup_path = _backup(path) if path.exists() and changed else None
|
|
387
398
|
_write_private_text(path, json.dumps(merged, indent=2, sort_keys=True) + "\n")
|
|
388
|
-
return resolution, "installed" if changed else "
|
|
399
|
+
return resolution, "installed" if changed else "already_valid", backup_path
|
|
389
400
|
|
|
390
401
|
|
|
391
402
|
def target_status(target_name: str) -> TargetStatus:
|
|
@@ -440,6 +451,9 @@ def target_status(target_name: str) -> TargetStatus:
|
|
|
440
451
|
selected_reason=resolution.selected_reason,
|
|
441
452
|
uncertainty=resolution.uncertainty,
|
|
442
453
|
detected_hook_sources=tuple(detected_sources),
|
|
454
|
+
hooks_declared=wrapper_valid if resolution.target_id == "claude" else False,
|
|
455
|
+
hooks_enabled=wrapper_valid if resolution.target_id == "claude" else False,
|
|
456
|
+
hooks_effective=wrapper_valid if resolution.target_id == "claude" else False,
|
|
443
457
|
)
|
|
444
458
|
|
|
445
459
|
|
|
@@ -971,7 +985,7 @@ def _codex_toml_block() -> str:
|
|
|
971
985
|
"[[hooks.PreToolUse.hooks]]\n"
|
|
972
986
|
'type = "command"\n'
|
|
973
987
|
'command = "sernixa hook run codex"\n'
|
|
974
|
-
"timeout =
|
|
988
|
+
f"timeout = {HOOK_TIMEOUT_SECONDS}\n"
|
|
975
989
|
'statusMessage = "Checking Sernixa policy"\n'
|
|
976
990
|
f"{SERNIXA_TOML_END}\n"
|
|
977
991
|
)
|
|
@@ -1074,7 +1088,24 @@ def _mcp_server_state(
|
|
|
1074
1088
|
|
|
1075
1089
|
|
|
1076
1090
|
def _group_has_expected_wrapper(group: Any, provider: Provider) -> bool:
|
|
1077
|
-
|
|
1091
|
+
if not isinstance(group, dict):
|
|
1092
|
+
return False
|
|
1093
|
+
for hook in group.get("hooks") or []:
|
|
1094
|
+
if not isinstance(hook, dict):
|
|
1095
|
+
continue
|
|
1096
|
+
command = hook.get("command")
|
|
1097
|
+
args = hook.get("args")
|
|
1098
|
+
full = (
|
|
1099
|
+
" ".join([command, *[str(item) for item in args]])
|
|
1100
|
+
if isinstance(command, str) and isinstance(args, list)
|
|
1101
|
+
else command
|
|
1102
|
+
)
|
|
1103
|
+
if (
|
|
1104
|
+
full == _expected_command(provider)
|
|
1105
|
+
and hook.get("timeout") == HOOK_TIMEOUT_SECONDS
|
|
1106
|
+
):
|
|
1107
|
+
return True
|
|
1108
|
+
return False
|
|
1078
1109
|
|
|
1079
1110
|
|
|
1080
1111
|
def _is_sernixa_group_for_provider(group: Any, provider: Provider) -> bool:
|
|
@@ -1122,7 +1153,11 @@ def _toml_find_sernixa_wrapper(value: Any, *, expect_valid: bool) -> bool:
|
|
|
1122
1153
|
if isinstance(args, list)
|
|
1123
1154
|
else command
|
|
1124
1155
|
)
|
|
1125
|
-
if
|
|
1156
|
+
if (
|
|
1157
|
+
expect_valid
|
|
1158
|
+
and full == "sernixa hook run codex"
|
|
1159
|
+
and value.get("timeout") == HOOK_TIMEOUT_SECONDS
|
|
1160
|
+
):
|
|
1126
1161
|
return True
|
|
1127
1162
|
if not expect_valid and "sernixa" in full and "codex" in full:
|
|
1128
1163
|
return True
|