sernixa-cli 0.1.1__tar.gz → 0.2.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/PKG-INFO +149 -0
- sernixa_cli-0.2.0/README.md +124 -0
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/pyproject.toml +3 -2
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/sernixa_cli/__init__.py +1 -1
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/sernixa_cli/adapters.py +19 -7
- sernixa_cli-0.2.0/sernixa_cli/discovery.py +198 -0
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/sernixa_cli/formatting.py +53 -18
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/sernixa_cli/local_agent_targets.py +217 -60
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/sernixa_cli/main.py +460 -26
- sernixa_cli-0.2.0/sernixa_cli/mcp_server.py +233 -0
- sernixa_cli-0.2.0/sernixa_cli/native_launcher.py +275 -0
- sernixa_cli-0.2.0/sernixa_cli.egg-info/PKG-INFO +149 -0
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/sernixa_cli.egg-info/SOURCES.txt +7 -1
- sernixa_cli-0.2.0/sernixa_cli.egg-info/requires.txt +4 -0
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/tests/test_cli.py +180 -1
- sernixa_cli-0.2.0/tests/test_discovery.py +88 -0
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/tests/test_local_agent_targets.py +124 -23
- sernixa_cli-0.2.0/tests/test_mcp_server.py +110 -0
- sernixa_cli-0.2.0/tests/test_native_launcher.py +83 -0
- sernixa_cli-0.1.1/PKG-INFO +0 -179
- sernixa_cli-0.1.1/README.md +0 -155
- sernixa_cli-0.1.1/sernixa_cli.egg-info/PKG-INFO +0 -179
- sernixa_cli-0.1.1/sernixa_cli.egg-info/requires.txt +0 -3
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/sernixa_cli/__main__.py +0 -0
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/sernixa_cli/config.py +0 -0
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/sernixa_cli/hooks.py +0 -0
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/sernixa_cli.egg-info/dependency_links.txt +0 -0
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/sernixa_cli.egg-info/entry_points.txt +0 -0
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/sernixa_cli.egg-info/top_level.txt +0 -0
- {sernixa_cli-0.1.1 → sernixa_cli-0.2.0}/setup.cfg +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sernixa-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official Sernixa CLI for policy-governed local AI agent actions
|
|
5
|
+
Author: Sernixa Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://sernixa.com
|
|
8
|
+
Project-URL: Repository, https://github.com/abhishekdhull63/Sernixa.ai-Web
|
|
9
|
+
Project-URL: Documentation, https://github.com/abhishekdhull63/Sernixa.ai-Web/tree/main/docs/cli.md
|
|
10
|
+
Keywords: sernixa,cli,governance,agents,policy
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: platformdirs>=4.0.0
|
|
22
|
+
Requires-Dist: sernixa>=0.4.0
|
|
23
|
+
Requires-Dist: sernixa-agent>=1.6.0
|
|
24
|
+
Requires-Dist: typer>=0.12.0
|
|
25
|
+
|
|
26
|
+
# Sernixa CLI
|
|
27
|
+
|
|
28
|
+
The official terminal and local-agent integration for Sernixa governance. The CLI evaluates actions against the real Sernixa control plane, installs supported agent hooks and MCP configuration, launches safe local discovery, and exposes operational evidence without a dashboard-only workflow.
|
|
29
|
+
|
|
30
|
+
## Install and set up
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install --upgrade sernixa-cli
|
|
34
|
+
sernixa auth login
|
|
35
|
+
sernixa setup
|
|
36
|
+
sernixa doctor
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`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:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
sernixa setup --dry-run
|
|
43
|
+
sernixa setup --target codex --target cursor --json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For local repository development:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
python3 -m venv .venv-cli
|
|
50
|
+
source .venv-cli/bin/activate
|
|
51
|
+
pip install -e packages/sernixa -e packages/sernixa-agent -e apps/cli
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The CLI defaults to `https://api.sernixa.com`. Stored credentials use the platform user-config directory and mode `0600` on Unix. `SERNIXA_API_KEY` and `SERNIXA_BASE_URL` override the stored configuration for non-interactive use.
|
|
55
|
+
|
|
56
|
+
## One-command Auto Discover
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
sernixa discover
|
|
60
|
+
sernixa discover --json
|
|
61
|
+
sernixa discover --job discjob_0123456789abcdef0123456789abcdef
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The command validates auth, initializes the supported local agent when needed, passes the stored credential only through the one-shot child-process environment, enrolls the stable machine ID, creates or accepts a policy-scoped job for that machine, runs one sync/task cycle, and waits for the terminal job result. The credential is not printed or duplicated into a second token file.
|
|
65
|
+
|
|
66
|
+
The Command Center opens the same flow through `sernixa://discover` after a direct user click and an OS/browser confirmation. The native URL accepts only the fixed discovery action and an optional validated job ID; it cannot carry commands, credentials, paths, or environment values. If the launcher is unavailable, the dashboard shows the exact fallback command.
|
|
67
|
+
|
|
68
|
+
Discovery candidates are observations for review. Enrollment is inventory. An accepted agent sync is proof. No discovery result silently becomes a managed MCP server.
|
|
69
|
+
|
|
70
|
+
## Codex, Claude, and Cursor
|
|
71
|
+
|
|
72
|
+
Supported global targets:
|
|
73
|
+
|
|
74
|
+
- `claude`: Claude Code `PreToolUse` exec-form hook in `~/.claude/settings.json`.
|
|
75
|
+
- `claude-desktop`: real stdio MCP server in the OS-specific Claude Desktop configuration.
|
|
76
|
+
- `codex`: current `PreToolUse` JSON or array-of-tables TOML hook in `~/.codex`. Hooks are effective by default unless `[features].hooks = false`; users still review/trust new non-managed hooks in Codex.
|
|
77
|
+
- `cursor`: global MCP entry in `~/.cursor/mcp.json`, shared by Cursor IDE and CLI.
|
|
78
|
+
|
|
79
|
+
Install or repair an individual integration:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
sernixa hook install claude
|
|
83
|
+
sernixa hook install claude-desktop
|
|
84
|
+
sernixa hook install codex
|
|
85
|
+
sernixa hook install cursor
|
|
86
|
+
|
|
87
|
+
sernixa hook repair codex
|
|
88
|
+
sernixa hook status --json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
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
|
+
Claude Desktop and Cursor run `sernixa mcp serve`. The stdio MCP server exposes three non-mutating tools:
|
|
94
|
+
|
|
95
|
+
- `sernixa_policy_check`: evaluate one proposed action.
|
|
96
|
+
- `sernixa_plan_check`: evaluate a declared multi-step plan.
|
|
97
|
+
- `sernixa_identity`: validate auth and return non-secret principal metadata.
|
|
98
|
+
|
|
99
|
+
MCP tools provide explicit model capabilities; the installed hooks are the local tool-call enforcement points.
|
|
100
|
+
|
|
101
|
+
## Governance and execution
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
sernixa validate payload.json
|
|
105
|
+
cat payload.json | sernixa validate --json
|
|
106
|
+
sernixa validate --from-claude < claude-hook.json
|
|
107
|
+
sernixa validate --from-codex < codex-hook.jsonl
|
|
108
|
+
sernixa validate-plan --agent-type codex plan.json
|
|
109
|
+
sernixa validate-many --output json policies/*.json
|
|
110
|
+
sernixa exec --plan plan.json -- npm test
|
|
111
|
+
sernixa codex-exec --plan codex-plan.json -- codex exec "fix lint"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Exit codes are stable:
|
|
115
|
+
|
|
116
|
+
- `0`: success or policy allow
|
|
117
|
+
- `1`: deny, review required, invalid evidence, or non-success terminal result
|
|
118
|
+
- `2`: configuration, parsing, auth, network, or API failure
|
|
119
|
+
|
|
120
|
+
`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
|
+
|
|
122
|
+
## Audit, approvals, and Flight Recorder
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
sernixa events --limit 25
|
|
126
|
+
sernixa events --type governance_agent_plan_evaluated --json
|
|
127
|
+
|
|
128
|
+
sernixa approvals list
|
|
129
|
+
sernixa approvals decide approval_123 approved --reason "Reviewed bounded deployment."
|
|
130
|
+
sernixa approvals decide approval_456 rejected --reason "Production scope is too broad."
|
|
131
|
+
|
|
132
|
+
sernixa flight verify
|
|
133
|
+
sernixa flight verify --stream stream_123 --json
|
|
134
|
+
sernixa flight collectors
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
These commands call the authenticated organization endpoints and inherit backend permissions and feature entitlements. `sernixa decisions` remains a backward-compatible alias for the pending approval queue. Replay is not claimed because the backend does not expose an authorized decision-replay endpoint.
|
|
138
|
+
|
|
139
|
+
## Stable configuration overrides
|
|
140
|
+
|
|
141
|
+
- `SERNIXA_CONFIG_FILE`
|
|
142
|
+
- `SERNIXA_PLAN_SESSION_FILE`
|
|
143
|
+
- `SERNIXA_CLAUDE_CONFIG_FILE`
|
|
144
|
+
- `SERNIXA_CLAUDE_DESKTOP_CONFIG_FILE`
|
|
145
|
+
- `SERNIXA_CODEX_HOOKS_FILE`
|
|
146
|
+
- `SERNIXA_CODEX_CONFIG_FILE`
|
|
147
|
+
- `SERNIXA_CURSOR_CONFIG_FILE`
|
|
148
|
+
|
|
149
|
+
Project-scoped Codex files are intentionally not modified by global setup. Invalid JSON or TOML fails closed with the exact path and a repair command. Existing files are backed up before material changes.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Sernixa CLI
|
|
2
|
+
|
|
3
|
+
The official terminal and local-agent integration for Sernixa governance. The CLI evaluates actions against the real Sernixa control plane, installs supported agent hooks and MCP configuration, launches safe local discovery, and exposes operational evidence without a dashboard-only workflow.
|
|
4
|
+
|
|
5
|
+
## Install and set up
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install --upgrade sernixa-cli
|
|
9
|
+
sernixa auth login
|
|
10
|
+
sernixa setup
|
|
11
|
+
sernixa doctor
|
|
12
|
+
```
|
|
13
|
+
|
|
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:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
sernixa setup --dry-run
|
|
18
|
+
sernixa setup --target codex --target cursor --json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For local repository development:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
python3 -m venv .venv-cli
|
|
25
|
+
source .venv-cli/bin/activate
|
|
26
|
+
pip install -e packages/sernixa -e packages/sernixa-agent -e apps/cli
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The CLI defaults to `https://api.sernixa.com`. Stored credentials use the platform user-config directory and mode `0600` on Unix. `SERNIXA_API_KEY` and `SERNIXA_BASE_URL` override the stored configuration for non-interactive use.
|
|
30
|
+
|
|
31
|
+
## One-command Auto Discover
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
sernixa discover
|
|
35
|
+
sernixa discover --json
|
|
36
|
+
sernixa discover --job discjob_0123456789abcdef0123456789abcdef
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The command validates auth, initializes the supported local agent when needed, passes the stored credential only through the one-shot child-process environment, enrolls the stable machine ID, creates or accepts a policy-scoped job for that machine, runs one sync/task cycle, and waits for the terminal job result. The credential is not printed or duplicated into a second token file.
|
|
40
|
+
|
|
41
|
+
The Command Center opens the same flow through `sernixa://discover` after a direct user click and an OS/browser confirmation. The native URL accepts only the fixed discovery action and an optional validated job ID; it cannot carry commands, credentials, paths, or environment values. If the launcher is unavailable, the dashboard shows the exact fallback command.
|
|
42
|
+
|
|
43
|
+
Discovery candidates are observations for review. Enrollment is inventory. An accepted agent sync is proof. No discovery result silently becomes a managed MCP server.
|
|
44
|
+
|
|
45
|
+
## Codex, Claude, and Cursor
|
|
46
|
+
|
|
47
|
+
Supported global targets:
|
|
48
|
+
|
|
49
|
+
- `claude`: Claude Code `PreToolUse` exec-form hook in `~/.claude/settings.json`.
|
|
50
|
+
- `claude-desktop`: real stdio MCP server in the OS-specific Claude Desktop configuration.
|
|
51
|
+
- `codex`: current `PreToolUse` JSON or array-of-tables TOML hook in `~/.codex`. Hooks are effective by default unless `[features].hooks = false`; users still review/trust new non-managed hooks in Codex.
|
|
52
|
+
- `cursor`: global MCP entry in `~/.cursor/mcp.json`, shared by Cursor IDE and CLI.
|
|
53
|
+
|
|
54
|
+
Install or repair an individual integration:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
sernixa hook install claude
|
|
58
|
+
sernixa hook install claude-desktop
|
|
59
|
+
sernixa hook install codex
|
|
60
|
+
sernixa hook install cursor
|
|
61
|
+
|
|
62
|
+
sernixa hook repair codex
|
|
63
|
+
sernixa hook status --json
|
|
64
|
+
```
|
|
65
|
+
|
|
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
|
+
|
|
68
|
+
Claude Desktop and Cursor run `sernixa mcp serve`. The stdio MCP server exposes three non-mutating tools:
|
|
69
|
+
|
|
70
|
+
- `sernixa_policy_check`: evaluate one proposed action.
|
|
71
|
+
- `sernixa_plan_check`: evaluate a declared multi-step plan.
|
|
72
|
+
- `sernixa_identity`: validate auth and return non-secret principal metadata.
|
|
73
|
+
|
|
74
|
+
MCP tools provide explicit model capabilities; the installed hooks are the local tool-call enforcement points.
|
|
75
|
+
|
|
76
|
+
## Governance and execution
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
sernixa validate payload.json
|
|
80
|
+
cat payload.json | sernixa validate --json
|
|
81
|
+
sernixa validate --from-claude < claude-hook.json
|
|
82
|
+
sernixa validate --from-codex < codex-hook.jsonl
|
|
83
|
+
sernixa validate-plan --agent-type codex plan.json
|
|
84
|
+
sernixa validate-many --output json policies/*.json
|
|
85
|
+
sernixa exec --plan plan.json -- npm test
|
|
86
|
+
sernixa codex-exec --plan codex-plan.json -- codex exec "fix lint"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Exit codes are stable:
|
|
90
|
+
|
|
91
|
+
- `0`: success or policy allow
|
|
92
|
+
- `1`: deny, review required, invalid evidence, or non-success terminal result
|
|
93
|
+
- `2`: configuration, parsing, auth, network, or API failure
|
|
94
|
+
|
|
95
|
+
`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
|
+
|
|
97
|
+
## Audit, approvals, and Flight Recorder
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
sernixa events --limit 25
|
|
101
|
+
sernixa events --type governance_agent_plan_evaluated --json
|
|
102
|
+
|
|
103
|
+
sernixa approvals list
|
|
104
|
+
sernixa approvals decide approval_123 approved --reason "Reviewed bounded deployment."
|
|
105
|
+
sernixa approvals decide approval_456 rejected --reason "Production scope is too broad."
|
|
106
|
+
|
|
107
|
+
sernixa flight verify
|
|
108
|
+
sernixa flight verify --stream stream_123 --json
|
|
109
|
+
sernixa flight collectors
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
These commands call the authenticated organization endpoints and inherit backend permissions and feature entitlements. `sernixa decisions` remains a backward-compatible alias for the pending approval queue. Replay is not claimed because the backend does not expose an authorized decision-replay endpoint.
|
|
113
|
+
|
|
114
|
+
## Stable configuration overrides
|
|
115
|
+
|
|
116
|
+
- `SERNIXA_CONFIG_FILE`
|
|
117
|
+
- `SERNIXA_PLAN_SESSION_FILE`
|
|
118
|
+
- `SERNIXA_CLAUDE_CONFIG_FILE`
|
|
119
|
+
- `SERNIXA_CLAUDE_DESKTOP_CONFIG_FILE`
|
|
120
|
+
- `SERNIXA_CODEX_HOOKS_FILE`
|
|
121
|
+
- `SERNIXA_CODEX_CONFIG_FILE`
|
|
122
|
+
- `SERNIXA_CURSOR_CONFIG_FILE`
|
|
123
|
+
|
|
124
|
+
Project-scoped Codex files are intentionally not modified by global setup. Invalid JSON or TOML fails closed with the exact path and a repair command. Existing files are backed up before material changes.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sernixa-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Official Sernixa CLI for policy-governed local AI agent actions"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -19,7 +19,8 @@ classifiers = [
|
|
|
19
19
|
]
|
|
20
20
|
dependencies = [
|
|
21
21
|
"platformdirs>=4.0.0",
|
|
22
|
-
"sernixa>=0.
|
|
22
|
+
"sernixa>=0.4.0",
|
|
23
|
+
"sernixa-agent>=1.6.0",
|
|
23
24
|
"typer>=0.12.0",
|
|
24
25
|
]
|
|
25
26
|
|
|
@@ -85,13 +85,19 @@ def canonicalize_payload(raw: Any, *, mode: ProviderMode) -> CanonicalGovernance
|
|
|
85
85
|
raise AdapterError("Governance payload must be a JSON object.")
|
|
86
86
|
return CanonicalGovernanceRequest(input=raw, source="plain")
|
|
87
87
|
if mode == "claude":
|
|
88
|
-
return CanonicalGovernanceRequest(
|
|
88
|
+
return CanonicalGovernanceRequest(
|
|
89
|
+
input=_claude_to_governance(raw), source="claude"
|
|
90
|
+
)
|
|
89
91
|
if mode == "codex":
|
|
90
|
-
return CanonicalGovernanceRequest(
|
|
92
|
+
return CanonicalGovernanceRequest(
|
|
93
|
+
input=_codex_to_governance(raw), source="codex"
|
|
94
|
+
)
|
|
91
95
|
raise AdapterError(f"Unsupported payload adapter: {mode}")
|
|
92
96
|
|
|
93
97
|
|
|
94
|
-
def payload_from_content(
|
|
98
|
+
def payload_from_content(
|
|
99
|
+
content: str, *, mode: ProviderMode
|
|
100
|
+
) -> CanonicalGovernanceRequest:
|
|
95
101
|
raw = load_json_payload(content)
|
|
96
102
|
return canonicalize_payload(raw, mode=mode)
|
|
97
103
|
|
|
@@ -128,7 +134,9 @@ def _codex_to_governance(raw: Any) -> dict[str, Any]:
|
|
|
128
134
|
event = _select_codex_event(raw)
|
|
129
135
|
if not isinstance(event, dict):
|
|
130
136
|
raise AdapterError("Codex payload must be a JSON object or JSON-lines stream.")
|
|
131
|
-
tool_name = _string(
|
|
137
|
+
tool_name = _string(
|
|
138
|
+
event.get("tool_name") or event.get("name") or event.get("tool")
|
|
139
|
+
)
|
|
132
140
|
tool_input = event.get("tool_input")
|
|
133
141
|
if tool_input is None:
|
|
134
142
|
if isinstance(event.get("input"), dict):
|
|
@@ -178,7 +186,9 @@ def _select_codex_event(raw: Any) -> Any:
|
|
|
178
186
|
)
|
|
179
187
|
]
|
|
180
188
|
if not candidates:
|
|
181
|
-
raise AdapterError(
|
|
189
|
+
raise AdapterError(
|
|
190
|
+
"No recognizable Codex tool event found in JSON-lines stream."
|
|
191
|
+
)
|
|
182
192
|
return candidates[-1]
|
|
183
193
|
|
|
184
194
|
|
|
@@ -243,7 +253,8 @@ def _action_for_tool(tool_name: str, tool_input: dict[str, Any]) -> dict[str, An
|
|
|
243
253
|
return {
|
|
244
254
|
"action": "network.request",
|
|
245
255
|
"operation_class": "network",
|
|
246
|
-
"resource": _string(tool_input.get("url"))
|
|
256
|
+
"resource": _string(tool_input.get("url"))
|
|
257
|
+
or _string(tool_input.get("query")),
|
|
247
258
|
"intent": _string(tool_input.get("prompt")),
|
|
248
259
|
}
|
|
249
260
|
if normalized.startswith("mcp__"):
|
|
@@ -258,7 +269,8 @@ def _action_for_tool(tool_name: str, tool_input: dict[str, Any]) -> dict[str, An
|
|
|
258
269
|
"action": "agent.delegate",
|
|
259
270
|
"operation_class": "delegate",
|
|
260
271
|
"resource": _string(tool_input.get("subagent_type")),
|
|
261
|
-
"intent": _string(tool_input.get("prompt"))
|
|
272
|
+
"intent": _string(tool_input.get("prompt"))
|
|
273
|
+
or _string(tool_input.get("description")),
|
|
262
274
|
}
|
|
263
275
|
return {
|
|
264
276
|
"action": f"tool.{normalized}",
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from sernixa import Client
|
|
12
|
+
|
|
13
|
+
from .config import CliConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DiscoveryError(RuntimeError):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
TERMINAL_JOB_STATES = {"completed", "failed", "expired", "cancelled"}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _run_agent(
|
|
24
|
+
command: list[str],
|
|
25
|
+
*,
|
|
26
|
+
input_text: str | None = None,
|
|
27
|
+
extra_env: dict[str, str] | None = None,
|
|
28
|
+
) -> subprocess.CompletedProcess[str]:
|
|
29
|
+
completed = subprocess.run(
|
|
30
|
+
command,
|
|
31
|
+
input=input_text,
|
|
32
|
+
text=True,
|
|
33
|
+
capture_output=True,
|
|
34
|
+
check=False,
|
|
35
|
+
env={**os.environ, **(extra_env or {})},
|
|
36
|
+
)
|
|
37
|
+
if completed.returncode != 0:
|
|
38
|
+
detail = completed.stderr.strip() or completed.stdout.strip()
|
|
39
|
+
raise DiscoveryError(detail or f"Command failed: {' '.join(command)}")
|
|
40
|
+
return completed
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _agent_executable() -> str:
|
|
44
|
+
executable = shutil.which("sernixa-agent")
|
|
45
|
+
if not executable:
|
|
46
|
+
raise DiscoveryError(
|
|
47
|
+
"sernixa-agent is not installed. Upgrade sernixa-cli or install sernixa-agent>=1.6.0."
|
|
48
|
+
)
|
|
49
|
+
return executable
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _ensure_agent_ready(
|
|
53
|
+
config: CliConfig,
|
|
54
|
+
*,
|
|
55
|
+
agent_config_path: Path,
|
|
56
|
+
) -> tuple[str, dict[str, Any], bool, dict[str, str]]:
|
|
57
|
+
executable = _agent_executable()
|
|
58
|
+
agent_env = {"SERNIXA_DISCOVERY_TOKEN": config.api_key}
|
|
59
|
+
initialized = False
|
|
60
|
+
if not agent_config_path.exists():
|
|
61
|
+
_run_agent(
|
|
62
|
+
[
|
|
63
|
+
executable,
|
|
64
|
+
"init",
|
|
65
|
+
"--config",
|
|
66
|
+
str(agent_config_path),
|
|
67
|
+
"--api-base-url",
|
|
68
|
+
config.base_url,
|
|
69
|
+
]
|
|
70
|
+
)
|
|
71
|
+
initialized = True
|
|
72
|
+
status = _run_agent(
|
|
73
|
+
[
|
|
74
|
+
executable,
|
|
75
|
+
"status",
|
|
76
|
+
"--config",
|
|
77
|
+
str(agent_config_path),
|
|
78
|
+
"--token-env",
|
|
79
|
+
"SERNIXA_DISCOVERY_TOKEN",
|
|
80
|
+
"--json",
|
|
81
|
+
],
|
|
82
|
+
extra_env=agent_env,
|
|
83
|
+
)
|
|
84
|
+
try:
|
|
85
|
+
status_payload = json.loads(status.stdout)
|
|
86
|
+
except json.JSONDecodeError as exc:
|
|
87
|
+
raise DiscoveryError("sernixa-agent returned invalid status JSON.") from exc
|
|
88
|
+
auth = status_payload.get("auth") if isinstance(status_payload, dict) else None
|
|
89
|
+
if not isinstance(auth, dict) or not auth.get("ok"):
|
|
90
|
+
raise DiscoveryError(
|
|
91
|
+
"Unable to pass the stored CLI credential to the local discovery agent."
|
|
92
|
+
)
|
|
93
|
+
return executable, status_payload, initialized, agent_env
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _discovery_job_payload(
|
|
97
|
+
policy: dict[str, Any], *, machine_id: str
|
|
98
|
+
) -> dict[str, Any]:
|
|
99
|
+
process_enabled = bool(policy.get("process_inspection_enabled", False))
|
|
100
|
+
config_enabled = bool(policy.get("mcp_config_inspection_enabled", False))
|
|
101
|
+
return {
|
|
102
|
+
"scope": {"mode": "local_agent", "machine_ids": [machine_id]},
|
|
103
|
+
"requested_scan": {
|
|
104
|
+
"network_ports": "policy_default",
|
|
105
|
+
"stdio_detection": process_enabled or config_enabled,
|
|
106
|
+
"process_inspection": process_enabled,
|
|
107
|
+
"mcp_config_inspection": config_enabled,
|
|
108
|
+
"controlled_stdio_validation": False,
|
|
109
|
+
"max_candidates": min(int(policy.get("max_candidates") or 50), 100),
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def run_discovery(
|
|
115
|
+
client: Client,
|
|
116
|
+
config: CliConfig,
|
|
117
|
+
*,
|
|
118
|
+
job_id: str | None = None,
|
|
119
|
+
agent_config_path: Path | None = None,
|
|
120
|
+
timeout_seconds: float = 120.0,
|
|
121
|
+
poll_interval: float = 1.0,
|
|
122
|
+
) -> dict[str, Any]:
|
|
123
|
+
"""Enroll, dispatch, execute, and verify one bounded Auto Discover run."""
|
|
124
|
+
identity = client.whoami()
|
|
125
|
+
config_path = agent_config_path or Path.home() / ".sernixa" / "agent.json"
|
|
126
|
+
executable, agent_status, initialized, agent_env = _ensure_agent_ready(
|
|
127
|
+
config, agent_config_path=config_path
|
|
128
|
+
)
|
|
129
|
+
machine_id = str(agent_status.get("machine_id") or "")
|
|
130
|
+
if not machine_id:
|
|
131
|
+
raise DiscoveryError("sernixa-agent did not return a stable machine ID.")
|
|
132
|
+
_run_agent(
|
|
133
|
+
[
|
|
134
|
+
executable,
|
|
135
|
+
"enroll",
|
|
136
|
+
"--config",
|
|
137
|
+
str(config_path),
|
|
138
|
+
"--api-base-url",
|
|
139
|
+
config.base_url,
|
|
140
|
+
"--token-env",
|
|
141
|
+
"SERNIXA_DISCOVERY_TOKEN",
|
|
142
|
+
],
|
|
143
|
+
extra_env=agent_env,
|
|
144
|
+
)
|
|
145
|
+
if job_id:
|
|
146
|
+
job = client.auto_discovery_job(job_id)
|
|
147
|
+
else:
|
|
148
|
+
policy = client.auto_discovery_policy()
|
|
149
|
+
job = client.create_auto_discovery_job(
|
|
150
|
+
_discovery_job_payload(policy, machine_id=machine_id)
|
|
151
|
+
)
|
|
152
|
+
job_id = str(job.get("job_id") or "")
|
|
153
|
+
if not job_id:
|
|
154
|
+
raise DiscoveryError("Sernixa did not return an Auto Discover job ID.")
|
|
155
|
+
agent_run = _run_agent(
|
|
156
|
+
[
|
|
157
|
+
executable,
|
|
158
|
+
"daemon",
|
|
159
|
+
"--config",
|
|
160
|
+
str(config_path),
|
|
161
|
+
"--api-base-url",
|
|
162
|
+
config.base_url,
|
|
163
|
+
"--token-env",
|
|
164
|
+
"SERNIXA_DISCOVERY_TOKEN",
|
|
165
|
+
"--once",
|
|
166
|
+
],
|
|
167
|
+
extra_env=agent_env,
|
|
168
|
+
)
|
|
169
|
+
try:
|
|
170
|
+
agent_summary = json.loads(agent_run.stdout) if agent_run.stdout.strip() else {}
|
|
171
|
+
except json.JSONDecodeError:
|
|
172
|
+
agent_summary = {
|
|
173
|
+
"status": "completed",
|
|
174
|
+
"detail": agent_run.stdout.strip()[:500],
|
|
175
|
+
}
|
|
176
|
+
deadline = time.monotonic() + timeout_seconds
|
|
177
|
+
while str(job.get("status")) not in TERMINAL_JOB_STATES:
|
|
178
|
+
if time.monotonic() >= deadline:
|
|
179
|
+
raise DiscoveryError(
|
|
180
|
+
f"Timed out waiting for Auto Discover job {job_id}. The job may still complete in Sernixa."
|
|
181
|
+
)
|
|
182
|
+
time.sleep(max(0.05, poll_interval))
|
|
183
|
+
job = client.auto_discovery_job(job_id)
|
|
184
|
+
return {
|
|
185
|
+
"organization_id": identity.get("organization_id"),
|
|
186
|
+
"job": job,
|
|
187
|
+
"agent": {
|
|
188
|
+
"config_path": str(config_path.resolve(strict=False)),
|
|
189
|
+
"initialized": initialized,
|
|
190
|
+
"machine_id": agent_status.get("machine_id"),
|
|
191
|
+
"version": agent_status.get("agent_version"),
|
|
192
|
+
"cycle": agent_summary,
|
|
193
|
+
},
|
|
194
|
+
"trust_boundary": (
|
|
195
|
+
"Discovery candidates are observations for review. Enrollment is inventory; "
|
|
196
|
+
"the accepted agent sync is the proof recorded by Sernixa."
|
|
197
|
+
),
|
|
198
|
+
}
|