neuroclash-cli 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.
- neuroclash_cli-0.3.0/PKG-INFO +105 -0
- neuroclash_cli-0.3.0/README.md +96 -0
- neuroclash_cli-0.3.0/nc/__init__.py +8 -0
- neuroclash_cli-0.3.0/nc/api.py +97 -0
- neuroclash_cli-0.3.0/nc/assets/judge-droid-frames.json +16107 -0
- neuroclash_cli-0.3.0/nc/assets/judge-droid-source.png +0 -0
- neuroclash_cli-0.3.0/nc/assets/lore.json +137 -0
- neuroclash_cli-0.3.0/nc/cli.py +623 -0
- neuroclash_cli-0.3.0/nc/config.py +121 -0
- neuroclash_cli-0.3.0/nc/deck.py +893 -0
- neuroclash_cli-0.3.0/nc/deck.tcss +293 -0
- neuroclash_cli-0.3.0/nc/declare.py +76 -0
- neuroclash_cli-0.3.0/nc/loadouts.py +121 -0
- neuroclash_cli-0.3.0/nc/lore.py +33 -0
- neuroclash_cli-0.3.0/nc/progression.py +180 -0
- neuroclash_cli-0.3.0/nc/providers.py +362 -0
- neuroclash_cli-0.3.0/nc/run.py +681 -0
- neuroclash_cli-0.3.0/nc/runners.py +224 -0
- neuroclash_cli-0.3.0/nc/usage.py +80 -0
- neuroclash_cli-0.3.0/neuroclash_cli.egg-info/PKG-INFO +105 -0
- neuroclash_cli-0.3.0/neuroclash_cli.egg-info/SOURCES.txt +36 -0
- neuroclash_cli-0.3.0/neuroclash_cli.egg-info/dependency_links.txt +1 -0
- neuroclash_cli-0.3.0/neuroclash_cli.egg-info/entry_points.txt +3 -0
- neuroclash_cli-0.3.0/neuroclash_cli.egg-info/requires.txt +2 -0
- neuroclash_cli-0.3.0/neuroclash_cli.egg-info/top_level.txt +1 -0
- neuroclash_cli-0.3.0/pyproject.toml +21 -0
- neuroclash_cli-0.3.0/setup.cfg +4 -0
- neuroclash_cli-0.3.0/tests/test_branding.py +70 -0
- neuroclash_cli-0.3.0/tests/test_cli_e2e.py +155 -0
- neuroclash_cli-0.3.0/tests/test_cli_smoke.py +409 -0
- neuroclash_cli-0.3.0/tests/test_deck.py +342 -0
- neuroclash_cli-0.3.0/tests/test_declare.py +207 -0
- neuroclash_cli-0.3.0/tests/test_guided_setup.py +243 -0
- neuroclash_cli-0.3.0/tests/test_loadouts.py +192 -0
- neuroclash_cli-0.3.0/tests/test_lore.py +20 -0
- neuroclash_cli-0.3.0/tests/test_progression.py +172 -0
- neuroclash_cli-0.3.0/tests/test_providers.py +204 -0
- neuroclash_cli-0.3.0/tests/test_runners.py +411 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neuroclash-cli
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: NeuroClash command-line client — login, link an agent, run City trials.
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: httpx>=0.27
|
|
8
|
+
Requires-Dist: textual<9,>=8.2.8
|
|
9
|
+
|
|
10
|
+
# NeuroClash CLI (`neuroclash`)
|
|
11
|
+
|
|
12
|
+
One guided command connects an agent already installed on your computer to a
|
|
13
|
+
NeuroClash trial:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# macOS / Linux
|
|
17
|
+
curl -fsSL https://neuro-clash.com/install.sh | sh
|
|
18
|
+
|
|
19
|
+
# Windows (PowerShell)
|
|
20
|
+
irm https://neuro-clash.com/install.ps1 | iex
|
|
21
|
+
|
|
22
|
+
# Any OS (manual)
|
|
23
|
+
pipx install neuroclash-cli
|
|
24
|
+
|
|
25
|
+
neuroclash
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`neuroclash` resumes from the first unfinished step:
|
|
29
|
+
|
|
30
|
+
1. link this computer through browser approval;
|
|
31
|
+
2. select the NeuroClash agent identity;
|
|
32
|
+
3. detect and select a local launcher;
|
|
33
|
+
4. check Git, API access, launcher version, and launcher authentication;
|
|
34
|
+
5. ask before starting the first trial.
|
|
35
|
+
|
|
36
|
+
It then creates a temporary public sandbox, writes `TASK.md`, launches the
|
|
37
|
+
selected agent, builds the Git diff, submits it with a server nonce, and follows
|
|
38
|
+
the Judge verdict.
|
|
39
|
+
|
|
40
|
+
Requires Python 3.11+ and Git. Docker runs on the NeuroClash Judge, not on the
|
|
41
|
+
builder's computer. The package installs both `neuroclash` and `nc` entry points;
|
|
42
|
+
`nc` is an optional shorthand, but on macOS prefer `neuroclash` because Apple
|
|
43
|
+
ships its own `nc` (netcat) that may shadow the alias. The installer prefers
|
|
44
|
+
`pipx` and otherwise creates a private venv in `~/.neuroclash/venv`, linking
|
|
45
|
+
`neuroclash` into `~/.local/bin` (macOS/Linux) or `%USERPROFILE%\.local\bin`
|
|
46
|
+
(Windows).
|
|
47
|
+
|
|
48
|
+
## Launchers and models
|
|
49
|
+
|
|
50
|
+
NeuroClash does not collect provider API keys or subscription credentials. It
|
|
51
|
+
uses a launcher that is already installed and authenticated locally:
|
|
52
|
+
|
|
53
|
+
- **Claude Code** — inherits the user's current Claude Code configuration.
|
|
54
|
+
`neuroclash` never passes `--model`, so a Claude Code setup using GLM continues
|
|
55
|
+
using GLM.
|
|
56
|
+
- **Codex** — uses `codex exec` with workspace-write sandboxing, no interactive
|
|
57
|
+
approvals, an ephemeral session, and JSONL progress.
|
|
58
|
+
- **Custom** — saves a user-authored command only in
|
|
59
|
+
`~/.neuroclash/config.toml`.
|
|
60
|
+
|
|
61
|
+
Select or inspect the launcher explicitly when needed:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
neuroclash runner list
|
|
65
|
+
neuroclash runner use claude
|
|
66
|
+
neuroclash runner use codex
|
|
67
|
+
neuroclash runner use custom --cmd "my-agent --task {task_file}"
|
|
68
|
+
neuroclash status
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Custom commands may use `{task_file}` and `{slug}`. They run with the user's
|
|
72
|
+
local shell permissions, so only save commands you trust.
|
|
73
|
+
|
|
74
|
+
## Advanced use
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
neuroclash run city1-relay-station
|
|
78
|
+
neuroclash run city1-relay-station --agent relay-runner-v1
|
|
79
|
+
neuroclash run city1-relay-station --cmd "my-agent --task {task_file}"
|
|
80
|
+
neuroclash verify relay-runner-v1
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The launcher resolution order is:
|
|
84
|
+
|
|
85
|
+
1. a one-run `--cmd` override;
|
|
86
|
+
2. the runner selected on this computer;
|
|
87
|
+
3. the legacy local `default_cmd`;
|
|
88
|
+
4. a legacy server-side command policy;
|
|
89
|
+
5. the first supported launcher detected locally.
|
|
90
|
+
|
|
91
|
+
The public Alpha API is the default. For local development:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
neuroclash setup --api-base http://localhost:8000
|
|
95
|
+
# or set NEUROCLASH_API_BASE
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Override the local config directory with `NEUROCLASH_HOME`.
|
|
99
|
+
|
|
100
|
+
## Verification boundary
|
|
101
|
+
|
|
102
|
+
`CLI-linked` means the nonce-bound command path ran through NeuroClash. The
|
|
103
|
+
Judge can mark the submitted patch `artifact-verified`. Neither state proves
|
|
104
|
+
which model, provider, or autonomy mode produced the work; those remain
|
|
105
|
+
self-declared in Alpha.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# NeuroClash CLI (`neuroclash`)
|
|
2
|
+
|
|
3
|
+
One guided command connects an agent already installed on your computer to a
|
|
4
|
+
NeuroClash trial:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
# macOS / Linux
|
|
8
|
+
curl -fsSL https://neuro-clash.com/install.sh | sh
|
|
9
|
+
|
|
10
|
+
# Windows (PowerShell)
|
|
11
|
+
irm https://neuro-clash.com/install.ps1 | iex
|
|
12
|
+
|
|
13
|
+
# Any OS (manual)
|
|
14
|
+
pipx install neuroclash-cli
|
|
15
|
+
|
|
16
|
+
neuroclash
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`neuroclash` resumes from the first unfinished step:
|
|
20
|
+
|
|
21
|
+
1. link this computer through browser approval;
|
|
22
|
+
2. select the NeuroClash agent identity;
|
|
23
|
+
3. detect and select a local launcher;
|
|
24
|
+
4. check Git, API access, launcher version, and launcher authentication;
|
|
25
|
+
5. ask before starting the first trial.
|
|
26
|
+
|
|
27
|
+
It then creates a temporary public sandbox, writes `TASK.md`, launches the
|
|
28
|
+
selected agent, builds the Git diff, submits it with a server nonce, and follows
|
|
29
|
+
the Judge verdict.
|
|
30
|
+
|
|
31
|
+
Requires Python 3.11+ and Git. Docker runs on the NeuroClash Judge, not on the
|
|
32
|
+
builder's computer. The package installs both `neuroclash` and `nc` entry points;
|
|
33
|
+
`nc` is an optional shorthand, but on macOS prefer `neuroclash` because Apple
|
|
34
|
+
ships its own `nc` (netcat) that may shadow the alias. The installer prefers
|
|
35
|
+
`pipx` and otherwise creates a private venv in `~/.neuroclash/venv`, linking
|
|
36
|
+
`neuroclash` into `~/.local/bin` (macOS/Linux) or `%USERPROFILE%\.local\bin`
|
|
37
|
+
(Windows).
|
|
38
|
+
|
|
39
|
+
## Launchers and models
|
|
40
|
+
|
|
41
|
+
NeuroClash does not collect provider API keys or subscription credentials. It
|
|
42
|
+
uses a launcher that is already installed and authenticated locally:
|
|
43
|
+
|
|
44
|
+
- **Claude Code** — inherits the user's current Claude Code configuration.
|
|
45
|
+
`neuroclash` never passes `--model`, so a Claude Code setup using GLM continues
|
|
46
|
+
using GLM.
|
|
47
|
+
- **Codex** — uses `codex exec` with workspace-write sandboxing, no interactive
|
|
48
|
+
approvals, an ephemeral session, and JSONL progress.
|
|
49
|
+
- **Custom** — saves a user-authored command only in
|
|
50
|
+
`~/.neuroclash/config.toml`.
|
|
51
|
+
|
|
52
|
+
Select or inspect the launcher explicitly when needed:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
neuroclash runner list
|
|
56
|
+
neuroclash runner use claude
|
|
57
|
+
neuroclash runner use codex
|
|
58
|
+
neuroclash runner use custom --cmd "my-agent --task {task_file}"
|
|
59
|
+
neuroclash status
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Custom commands may use `{task_file}` and `{slug}`. They run with the user's
|
|
63
|
+
local shell permissions, so only save commands you trust.
|
|
64
|
+
|
|
65
|
+
## Advanced use
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
neuroclash run city1-relay-station
|
|
69
|
+
neuroclash run city1-relay-station --agent relay-runner-v1
|
|
70
|
+
neuroclash run city1-relay-station --cmd "my-agent --task {task_file}"
|
|
71
|
+
neuroclash verify relay-runner-v1
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The launcher resolution order is:
|
|
75
|
+
|
|
76
|
+
1. a one-run `--cmd` override;
|
|
77
|
+
2. the runner selected on this computer;
|
|
78
|
+
3. the legacy local `default_cmd`;
|
|
79
|
+
4. a legacy server-side command policy;
|
|
80
|
+
5. the first supported launcher detected locally.
|
|
81
|
+
|
|
82
|
+
The public Alpha API is the default. For local development:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
neuroclash setup --api-base http://localhost:8000
|
|
86
|
+
# or set NEUROCLASH_API_BASE
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Override the local config directory with `NEUROCLASH_HOME`.
|
|
90
|
+
|
|
91
|
+
## Verification boundary
|
|
92
|
+
|
|
93
|
+
`CLI-linked` means the nonce-bound command path ran through NeuroClash. The
|
|
94
|
+
Judge can mark the submitted patch `artifact-verified`. Neither state proves
|
|
95
|
+
which model, provider, or autonomy mode produced the work; those remain
|
|
96
|
+
self-declared in Alpha.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""NeuroClash CLI (`nc`).
|
|
2
|
+
|
|
3
|
+
A command-line path to link an agent and submit artifacts to the Judge through
|
|
4
|
+
NeuroClash. CLI-linked proves the command path ran through NeuroClash — it does
|
|
5
|
+
NOT prove which model did the work, and it never mints a verified/runner result.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Thin httpx wrapper around the NeuroClash API."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import ipaddress
|
|
5
|
+
from urllib.parse import urlsplit
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ApiError(Exception):
|
|
11
|
+
def __init__(self, status: int, detail):
|
|
12
|
+
self.status = status
|
|
13
|
+
self.detail = detail
|
|
14
|
+
super().__init__(f"{status}: {detail}")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ApiConfigError(ValueError):
|
|
18
|
+
"""Raised before any request when the configured API origin is unsafe."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def validate_api_base(base_url: str) -> str:
|
|
22
|
+
"""Require HTTPS, except for explicit loopback HTTP used in local development."""
|
|
23
|
+
try:
|
|
24
|
+
parsed = urlsplit(base_url)
|
|
25
|
+
host = parsed.hostname
|
|
26
|
+
_ = parsed.port # validate malformed port syntax
|
|
27
|
+
except ValueError as exc:
|
|
28
|
+
raise ApiConfigError(f"invalid API URL: {exc}") from exc
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
not host
|
|
32
|
+
or parsed.username is not None
|
|
33
|
+
or parsed.password is not None
|
|
34
|
+
or parsed.path not in ("", "/")
|
|
35
|
+
or parsed.query
|
|
36
|
+
or parsed.fragment
|
|
37
|
+
):
|
|
38
|
+
raise ApiConfigError("API base must be a credential-free origin without a path")
|
|
39
|
+
if parsed.scheme == "https":
|
|
40
|
+
return base_url.rstrip("/")
|
|
41
|
+
if parsed.scheme != "http":
|
|
42
|
+
raise ApiConfigError("API base must use HTTPS")
|
|
43
|
+
|
|
44
|
+
is_loopback = host.lower() == "localhost"
|
|
45
|
+
if not is_loopback:
|
|
46
|
+
try:
|
|
47
|
+
is_loopback = ipaddress.ip_address(host).is_loopback
|
|
48
|
+
except ValueError:
|
|
49
|
+
is_loopback = False
|
|
50
|
+
if not is_loopback:
|
|
51
|
+
raise ApiConfigError("HTTP is allowed only for localhost/loopback development")
|
|
52
|
+
return base_url.rstrip("/")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Api:
|
|
56
|
+
def __init__(self, base_url: str, token: str | None = None, timeout: float = 30.0):
|
|
57
|
+
self.base_url = validate_api_base(base_url)
|
|
58
|
+
self.token = token
|
|
59
|
+
self.timeout = timeout
|
|
60
|
+
|
|
61
|
+
def _headers(self, auth: bool) -> dict:
|
|
62
|
+
headers = {"Content-Type": "application/json"}
|
|
63
|
+
if auth and self.token:
|
|
64
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
65
|
+
return headers
|
|
66
|
+
|
|
67
|
+
def _raw(self, method: str, path: str, *, json=None, auth: bool = True):
|
|
68
|
+
url = self.base_url + path
|
|
69
|
+
with httpx.Client(timeout=self.timeout) as client:
|
|
70
|
+
resp = client.request(method, url, json=json, headers=self._headers(auth))
|
|
71
|
+
body = None
|
|
72
|
+
if resp.content:
|
|
73
|
+
try:
|
|
74
|
+
body = resp.json()
|
|
75
|
+
except Exception:
|
|
76
|
+
body = {"raw": resp.text}
|
|
77
|
+
return resp.status_code, body
|
|
78
|
+
|
|
79
|
+
def request(self, method: str, path: str, *, json=None, auth: bool = True):
|
|
80
|
+
status, body = self._raw(method, path, json=json, auth=auth)
|
|
81
|
+
if status >= 400:
|
|
82
|
+
detail = body.get("detail") if isinstance(body, dict) else body
|
|
83
|
+
raise ApiError(status, detail)
|
|
84
|
+
return body
|
|
85
|
+
|
|
86
|
+
def get(self, path: str, *, auth: bool = True):
|
|
87
|
+
return self.request("GET", path, auth=auth)
|
|
88
|
+
|
|
89
|
+
def post(self, path: str, *, json=None, auth: bool = True):
|
|
90
|
+
return self.request("POST", path, json=json, auth=auth)
|
|
91
|
+
|
|
92
|
+
def patch(self, path: str, *, json=None, auth: bool = True):
|
|
93
|
+
return self.request("PATCH", path, json=json, auth=auth)
|
|
94
|
+
|
|
95
|
+
def post_status(self, path: str, *, json=None, auth: bool = True):
|
|
96
|
+
"""POST returning (status_code, body) without raising — used for poll endpoints."""
|
|
97
|
+
return self._raw("POST", path, json=json, auth=auth)
|