awnode 0.3.2__tar.gz → 0.3.4__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.
- awnode-0.3.4/CLAUDE.md +2 -0
- {awnode-0.3.2 → awnode-0.3.4}/PKG-INFO +40 -7
- {awnode-0.3.2 → awnode-0.3.4}/README.md +38 -5
- {awnode-0.3.2 → awnode-0.3.4}/awnode/__init__.py +1 -1
- {awnode-0.3.2 → awnode-0.3.4}/awnode/_doctor.py +2 -2
- {awnode-0.3.2 → awnode-0.3.4}/awnode/cli.py +51 -5
- {awnode-0.3.2 → awnode-0.3.4}/awnode/hub.py +20 -1
- {awnode-0.3.2 → awnode-0.3.4}/awnode/mcp_server.py +248 -88
- {awnode-0.3.2 → awnode-0.3.4}/awnode/proxy.py +14 -2
- {awnode-0.3.2 → awnode-0.3.4}/awnode/server.py +483 -70
- {awnode-0.3.2 → awnode-0.3.4}/awnode/sync.py +79 -6
- awnode-0.3.4/awnode/update_check.py +245 -0
- {awnode-0.3.2 → awnode-0.3.4}/docs/aither-manifest.json +4 -4
- awnode-0.3.4/docs/index.html +1360 -0
- awnode-0.3.4/docs/llms.txt +33 -0
- {awnode-0.3.2 → awnode-0.3.4}/pyproject.toml +9 -12
- awnode-0.3.4/tests/test_extension_contract.py +357 -0
- awnode-0.3.4/tests/test_hub_error_cooldown.py +29 -0
- awnode-0.3.4/tests/test_mcp_api_compat.py +60 -0
- awnode-0.3.4/tests/test_mcp_mode_reprobe.py +31 -0
- awnode-0.3.4/tests/test_mcp_sse_transport.py +142 -0
- awnode-0.3.4/tests/test_pair_mode_route.py +23 -0
- {awnode-0.3.2 → awnode-0.3.4}/tests/test_proxy_plane.py +18 -4
- awnode-0.3.4/tests/test_sync_enrollment_lifecycle.py +132 -0
- awnode-0.3.4/tests/test_update_check.py +209 -0
- awnode-0.3.2/docs/index.html +0 -1378
- {awnode-0.3.2 → awnode-0.3.4}/.github/workflows/publish.yml +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/.gitignore +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/Dockerfile +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/LICENSE +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/_smoke_check.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/aither-constellation.js +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/aither-manifest.json +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/__main__.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/comfyui_setup.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/components/__init__.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/components/autorig.yaml +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/components/canvas.yaml +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/components/comfyui-3d.yaml +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/components/comfyui.yaml +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/components/design.yaml +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/components/iris.yaml +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/components/muse.yaml +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/components/ollama.yaml +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/components/sd-webui.yaml +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/extensions.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/license.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/service_install.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/__init__.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/_http.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/_scopes.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/commands.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/context.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/documents.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/embeddings.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/extensions.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/filesystem.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/fleet_host.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/fleet_verbs.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/generation.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/git_tools.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/http_client.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/knowledge.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/memory.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/mermaid.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/notebooks.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/ollama.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/orchestrator.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/awnode/tools/search.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/dist_validate/aithernode-0.1.0-py3-none-any.whl +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/docker-entrypoint.sh +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/docs/aither-constellation.js +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/packaging/aithernode-stub/README.md +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/packaging/aithernode-stub/pyproject.toml +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/tests/test_awnix_host_install.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/tests/test_chat_endpoint_auth.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/tests/test_component_image_env.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/tests/test_custom_backend.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/tests/test_fleet_host_tools.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/tests/test_fleet_verbs_tools.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/tests/test_mcp_server_auth.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/tests/test_server_route_hygiene.py +0 -0
- {awnode-0.3.2 → awnode-0.3.4}/tests/test_server_security_hardening.py +0 -0
awnode-0.3.4/CLAUDE.md
ADDED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: awnode
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: awnode — lightweight local gateway for AitherOS
|
|
5
5
|
Project-URL: Homepage, https://aitherium.com
|
|
6
6
|
Author-email: Aitherium <hello@aitherium.com>
|
|
@@ -11,7 +11,7 @@ Requires-Python: >=3.10
|
|
|
11
11
|
Requires-Dist: awdk>=1.3.0
|
|
12
12
|
Requires-Dist: fastapi>=0.109.0
|
|
13
13
|
Requires-Dist: httpx>=0.26.0
|
|
14
|
-
Requires-Dist: mcp<
|
|
14
|
+
Requires-Dist: mcp<3.0.0,>=1.0.0
|
|
15
15
|
Requires-Dist: pyyaml>=6.0
|
|
16
16
|
Requires-Dist: uvicorn[standard]>=0.27.0
|
|
17
17
|
Provides-Extra: all
|
|
@@ -32,7 +32,7 @@ Description-Content-Type: text/markdown
|
|
|
32
32
|
|
|
33
33
|
**[Docs](https://aitherium.github.io/awnode/)** · [Source](https://github.com/Aitherium/awnode) · `pip install awnode` · [The Aither World](https://aitherium.github.io/)
|
|
34
34
|
|
|
35
|
-
> **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awnode** is one of its
|
|
35
|
+
> **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awnode** is one of its 67 bricks — each installs on its own, runs offline, and needs no account.
|
|
36
36
|
>
|
|
37
37
|
> **Start here:** Run it once and point one app at it instead of a vendor URL.
|
|
38
38
|
|
|
@@ -54,7 +54,7 @@ curl -fsSL https://launch.aitherium.com | bash
|
|
|
54
54
|
## Quick Start
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
# Start with auto-detection (finds Genesis, vLLM, Ollama, or cloud)
|
|
57
|
+
# Start with auto-detection (finds Genesis, vLLM, llama.cpp, LM Studio, Ollama, or cloud)
|
|
58
58
|
awnode start
|
|
59
59
|
|
|
60
60
|
# Run the published container
|
|
@@ -77,11 +77,18 @@ awnode deploy my-service --target docker --strategy rolling
|
|
|
77
77
|
|
|
78
78
|
## Backend Priority (auto mode)
|
|
79
79
|
|
|
80
|
+
0. **Custom** (`AITHERNODE_CUSTOM_BACKEND_URL`) — your own OpenAI-compatible backend, when it answers
|
|
80
81
|
1. **Genesis** (localhost:8001) — full AitherOS pipeline (context, memory, agents)
|
|
81
82
|
2. **vLLM** (localhost:8120) — direct GPU inference (OpenAI-compatible)
|
|
82
|
-
3. **
|
|
83
|
-
4. **
|
|
84
|
-
5. **
|
|
83
|
+
3. **llama.cpp** (localhost:8200) / **Bonsai** (localhost:8092) — OpenAI-compatible llama.cpp servers
|
|
84
|
+
4. **LM Studio** (127.0.0.1:1234) — LM Studio's local server (OpenAI-compatible; start it from LM Studio's Developer tab)
|
|
85
|
+
5. **Elysium** (cloud) — hosted AitherOS (requires API key)
|
|
86
|
+
6. **Ollama** (localhost:11434) — local CPU/GPU inference
|
|
87
|
+
7. **Standalone** — no LLM, tools-only mode
|
|
88
|
+
|
|
89
|
+
OpenAI-compatible backends (vLLM, llama.cpp, Bonsai, LM Studio, custom) are detected by a
|
|
90
|
+
200 on `/v1/models`, never by `/health`, so an unrelated service on the same port is not
|
|
91
|
+
mistaken for one.
|
|
85
92
|
|
|
86
93
|
## API
|
|
87
94
|
|
|
@@ -91,8 +98,25 @@ GET /status — Backend status (what's connected)
|
|
|
91
98
|
POST /chat — Chat (proxied to best backend)
|
|
92
99
|
POST /deploy — Deploy via AitherComet
|
|
93
100
|
POST /connect — Register with Elysium
|
|
101
|
+
GET /v1/models — Models across the local OpenAI-compatible backends
|
|
102
|
+
POST /v1/chat/completions — OpenAI-compatible chat, routed to the best backend
|
|
103
|
+
GET /mcp/tools — Tool discovery
|
|
104
|
+
POST /mcp — MCP JSON-RPC 2.0: tools/list, tools/call
|
|
94
105
|
```
|
|
95
106
|
|
|
107
|
+
The awconnect browser extension (its published ids, plus any in `AWNODE_EXTENSION_IDS`)
|
|
108
|
+
may call `/health`, `/v1/models`, `/v1/chat/completions` and `POST /mcp`; every other
|
|
109
|
+
route refuses an extension origin. The node stays loopback-bound and the
|
|
110
|
+
loopback-or-enrolled-bearer gate still applies.
|
|
111
|
+
|
|
112
|
+
**Tool scope.** By default `POST /mcp` serves only `read_file`, `list_directory` (both
|
|
113
|
+
jailed to `AITHERNODE_FS_ROOT`) and `web_search`. `write_file`, `run_command` and every
|
|
114
|
+
other tool need a direct loopback caller (no proxy/tunnel headers) that is either a local
|
|
115
|
+
non-browser process sending `Authorization: Bearer <enrolled token or AITHER_MCP_KEY>`, or
|
|
116
|
+
any loopback caller when the node owner sets `AWNODE_BROWSER_FULL_TOOLS=1`. A browser or
|
|
117
|
+
extension origin, a tunnelled/remote request and an unauthenticated local process stay on
|
|
118
|
+
the read-only set; for full tool access from an IDE use `awnode mcp`.
|
|
119
|
+
|
|
96
120
|
## Environment Variables
|
|
97
121
|
|
|
98
122
|
| Variable | Default | Description |
|
|
@@ -100,6 +124,9 @@ POST /connect — Register with Elysium
|
|
|
100
124
|
| `AITHER_URL` | `http://localhost:8001` | Genesis URL |
|
|
101
125
|
| `AITHER_VLLM_URL` | `http://localhost:8120` | vLLM URL |
|
|
102
126
|
| `OLLAMA_HOST` | `http://localhost:11434` | Ollama URL |
|
|
127
|
+
| `AITHERNODE_LMSTUDIO_URL` | `http://127.0.0.1:1234` | LM Studio server URL (`LMSTUDIO_URL` also read) |
|
|
128
|
+
| `AWNODE_EXTENSION_IDS` | | Extra extension ids admitted on the extension routes (comma-separated) |
|
|
129
|
+
| `AWNODE_BROWSER_FULL_TOOLS` | | `1` lets direct loopback callers (browser/extension included) run every tool without a credential; tunnelled/remote callers stay read-only |
|
|
103
130
|
| `AITHER_API_KEY` | | API key for cloud/auth |
|
|
104
131
|
| `AITHERNODE_MODE` | `auto` | Force mode: auto/local/cloud/standalone |
|
|
105
132
|
| `AITHERNODE_PORT` | `8090` | Server port |
|
|
@@ -132,9 +159,11 @@ Each installs on its own, works offline, and needs no account.
|
|
|
132
159
|
| [awgit](https://github.com/Aitherium/awgit) | that no one else is editing this file | a lease, refused at commit time if you do not hold it |
|
|
133
160
|
| [awdelphi](https://github.com/Aitherium/awdelphi) | one agent's confident take on a decision | the round trace, the anonymity, and who dissents |
|
|
134
161
|
| [awclassify](https://github.com/Aitherium/awclassify) | a filename, a folder, or whoever last touched it | doc_type, visibility, audience and topics, with the evidence lines that decided each |
|
|
162
|
+
| [awdecide](https://github.com/Aitherium/awdecide) | a hosted classifier's probability that never learns whether it was right | the decision, its probability, and the calibration curve from your own resolved outcomes |
|
|
135
163
|
| [awtoll](https://github.com/Aitherium/awtoll) | that your tooling is saving you context | the measured token cost of each tool call, and what the alternative cost |
|
|
136
164
|
| [awseal](https://github.com/Aitherium/awseal) | that the artifact came from who you think | an Ed25519 seal — the key that verifies is not the key that forges |
|
|
137
165
|
| [awshare](https://github.com/Aitherium/awshare) | that the download is intact | content-addressed bundles, verified on fetch |
|
|
166
|
+
| [awsuite](https://github.com/Aitherium/awsuite) | that an agent holding your mailbox will not send on its own | every send, draft, upload and create returns a dry-run until confirm is true |
|
|
138
167
|
| [awnest](https://github.com/Aitherium/awnest) | that there is a person on the other end | a verdict with evidence, where "we could not tell" is not "yes" |
|
|
139
168
|
| [awrena](https://github.com/Aitherium/awrena) | a leaderboard someone can edit, and votes nobody counted | a scored duel with both answers kept, and a result bound to them |
|
|
140
169
|
| [awnboard](https://github.com/Aitherium/awnboard) | a share link anyone who sees it can use | an invitation addressed to one person, for one gate, revocable |
|
|
@@ -196,9 +225,11 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
196
225
|
| [awgit](https://github.com/Aitherium/awgit) | Semantic version control on top of git — edit-ops and leases | [docs](https://aitherium.github.io/awgit/) |
|
|
197
226
|
| [awdelphi](https://github.com/Aitherium/awdelphi) | Anonymous multi-round expert panels — a converged answer with a trace | [docs](https://aitherium.github.io/awdelphi/) |
|
|
198
227
|
| [awclassify](https://github.com/Aitherium/awclassify) | Classify any document -- what it is, who may read it, who it is for, what it is about | — |
|
|
228
|
+
| [awdecide](https://github.com/Aitherium/awdecide) | One typed-decision contract -- choice / score / bool with a probability -- over a ladder of backends you already run (rules, tiny local models, an LLM's logprobs), fail-closed, with a Brier ledger that resolves every decision against its outcome | — |
|
|
199
229
|
| [awtoll](https://github.com/Aitherium/awtoll) | What every tool call costs you in context, measured from your own transcripts | [docs](https://aitherium.github.io/awtoll/) |
|
|
200
230
|
| [awseal](https://github.com/Aitherium/awseal) | Sign an artifact so a stranger can verify it | [docs](https://aitherium.github.io/awseal/) |
|
|
201
231
|
| [awshare](https://github.com/Aitherium/awshare) | Publish an artifact and fetch it back verified | [docs](https://aitherium.github.io/awshare/) |
|
|
232
|
+
| [awsuite](https://github.com/Aitherium/awsuite) | Your Google Workspace as agent tools, and no write happens without a yes | — |
|
|
202
233
|
| [awdit](https://github.com/Aitherium/awdit) | An append-only audit trail whose gaps are DETECTABLE | [docs](https://aitherium.github.io/awdit/) |
|
|
203
234
|
| [awbac](https://github.com/Aitherium/awbac) | Role-based access control that fails closed and explains itself | [docs](https://aitherium.github.io/awbac/) |
|
|
204
235
|
| [awiam](https://github.com/Aitherium/awiam) | Who is this caller? A directory and session store that fails honestly | [docs](https://aitherium.github.io/awiam/) |
|
|
@@ -251,6 +282,8 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
251
282
|
| [awsettings](https://github.com/Aitherium/awsettings) | Your agent's permissions and config, following you to the next machine | [docs](https://aitherium.github.io/awsettings/) |
|
|
252
283
|
| [awavatar](https://github.com/Aitherium/awavatar) | One character spec in, a rigged, animated, multi-style avatar pack out | [docs](https://aitherium.github.io/awavatar/) |
|
|
253
284
|
|
|
285
|
+
**Built on** [REA (Reverse Engineer Anything)](https://github.com/morluto/rea) · [llama.cpp](https://github.com/ggml-org/llama.cpp) · [vLLM](https://github.com/vllm-project/vllm) · [ComfyUI](https://github.com/comfyanonymous/ComfyUI) · [CentOS Stream](https://www.centos.org/centos-stream/) · [Podman](https://github.com/containers/podman) · [Docker](https://github.com/moby/moby) · [LanceDB](https://github.com/lancedb/lancedb) · [WireGuard](https://www.wireguard.com/) · [FFmpeg](https://ffmpeg.org/) · [Blender + Rigify](https://www.blender.org/) · [headroom](https://github.com/headroomlabs-ai/headroom) · [SANA](https://github.com/NVlabs/Sana) · [Hunyuan3D](https://github.com/Tencent-Hunyuan/Hunyuan3D-2.1) · [repowise](https://github.com/repowise-dev/repowise) · [Playwright](https://github.com/microsoft/playwright) · [Chromium](https://www.chromium.org/) · [Next.js](https://github.com/vercel/next.js) · [React](https://github.com/facebook/react).
|
|
286
|
+
|
|
254
287
|
<div id="aither-constellation" data-self="awnode"></div>
|
|
255
288
|
<script src="aither-constellation.js"></script>
|
|
256
289
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**[Docs](https://aitherium.github.io/awnode/)** · [Source](https://github.com/Aitherium/awnode) · `pip install awnode` · [The Aither World](https://aitherium.github.io/)
|
|
6
6
|
|
|
7
|
-
> **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awnode** is one of its
|
|
7
|
+
> **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awnode** is one of its 67 bricks — each installs on its own, runs offline, and needs no account.
|
|
8
8
|
>
|
|
9
9
|
> **Start here:** Run it once and point one app at it instead of a vendor URL.
|
|
10
10
|
|
|
@@ -26,7 +26,7 @@ curl -fsSL https://launch.aitherium.com | bash
|
|
|
26
26
|
## Quick Start
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
# Start with auto-detection (finds Genesis, vLLM, Ollama, or cloud)
|
|
29
|
+
# Start with auto-detection (finds Genesis, vLLM, llama.cpp, LM Studio, Ollama, or cloud)
|
|
30
30
|
awnode start
|
|
31
31
|
|
|
32
32
|
# Run the published container
|
|
@@ -49,11 +49,18 @@ awnode deploy my-service --target docker --strategy rolling
|
|
|
49
49
|
|
|
50
50
|
## Backend Priority (auto mode)
|
|
51
51
|
|
|
52
|
+
0. **Custom** (`AITHERNODE_CUSTOM_BACKEND_URL`) — your own OpenAI-compatible backend, when it answers
|
|
52
53
|
1. **Genesis** (localhost:8001) — full AitherOS pipeline (context, memory, agents)
|
|
53
54
|
2. **vLLM** (localhost:8120) — direct GPU inference (OpenAI-compatible)
|
|
54
|
-
3. **
|
|
55
|
-
4. **
|
|
56
|
-
5. **
|
|
55
|
+
3. **llama.cpp** (localhost:8200) / **Bonsai** (localhost:8092) — OpenAI-compatible llama.cpp servers
|
|
56
|
+
4. **LM Studio** (127.0.0.1:1234) — LM Studio's local server (OpenAI-compatible; start it from LM Studio's Developer tab)
|
|
57
|
+
5. **Elysium** (cloud) — hosted AitherOS (requires API key)
|
|
58
|
+
6. **Ollama** (localhost:11434) — local CPU/GPU inference
|
|
59
|
+
7. **Standalone** — no LLM, tools-only mode
|
|
60
|
+
|
|
61
|
+
OpenAI-compatible backends (vLLM, llama.cpp, Bonsai, LM Studio, custom) are detected by a
|
|
62
|
+
200 on `/v1/models`, never by `/health`, so an unrelated service on the same port is not
|
|
63
|
+
mistaken for one.
|
|
57
64
|
|
|
58
65
|
## API
|
|
59
66
|
|
|
@@ -63,8 +70,25 @@ GET /status — Backend status (what's connected)
|
|
|
63
70
|
POST /chat — Chat (proxied to best backend)
|
|
64
71
|
POST /deploy — Deploy via AitherComet
|
|
65
72
|
POST /connect — Register with Elysium
|
|
73
|
+
GET /v1/models — Models across the local OpenAI-compatible backends
|
|
74
|
+
POST /v1/chat/completions — OpenAI-compatible chat, routed to the best backend
|
|
75
|
+
GET /mcp/tools — Tool discovery
|
|
76
|
+
POST /mcp — MCP JSON-RPC 2.0: tools/list, tools/call
|
|
66
77
|
```
|
|
67
78
|
|
|
79
|
+
The awconnect browser extension (its published ids, plus any in `AWNODE_EXTENSION_IDS`)
|
|
80
|
+
may call `/health`, `/v1/models`, `/v1/chat/completions` and `POST /mcp`; every other
|
|
81
|
+
route refuses an extension origin. The node stays loopback-bound and the
|
|
82
|
+
loopback-or-enrolled-bearer gate still applies.
|
|
83
|
+
|
|
84
|
+
**Tool scope.** By default `POST /mcp` serves only `read_file`, `list_directory` (both
|
|
85
|
+
jailed to `AITHERNODE_FS_ROOT`) and `web_search`. `write_file`, `run_command` and every
|
|
86
|
+
other tool need a direct loopback caller (no proxy/tunnel headers) that is either a local
|
|
87
|
+
non-browser process sending `Authorization: Bearer <enrolled token or AITHER_MCP_KEY>`, or
|
|
88
|
+
any loopback caller when the node owner sets `AWNODE_BROWSER_FULL_TOOLS=1`. A browser or
|
|
89
|
+
extension origin, a tunnelled/remote request and an unauthenticated local process stay on
|
|
90
|
+
the read-only set; for full tool access from an IDE use `awnode mcp`.
|
|
91
|
+
|
|
68
92
|
## Environment Variables
|
|
69
93
|
|
|
70
94
|
| Variable | Default | Description |
|
|
@@ -72,6 +96,9 @@ POST /connect — Register with Elysium
|
|
|
72
96
|
| `AITHER_URL` | `http://localhost:8001` | Genesis URL |
|
|
73
97
|
| `AITHER_VLLM_URL` | `http://localhost:8120` | vLLM URL |
|
|
74
98
|
| `OLLAMA_HOST` | `http://localhost:11434` | Ollama URL |
|
|
99
|
+
| `AITHERNODE_LMSTUDIO_URL` | `http://127.0.0.1:1234` | LM Studio server URL (`LMSTUDIO_URL` also read) |
|
|
100
|
+
| `AWNODE_EXTENSION_IDS` | | Extra extension ids admitted on the extension routes (comma-separated) |
|
|
101
|
+
| `AWNODE_BROWSER_FULL_TOOLS` | | `1` lets direct loopback callers (browser/extension included) run every tool without a credential; tunnelled/remote callers stay read-only |
|
|
75
102
|
| `AITHER_API_KEY` | | API key for cloud/auth |
|
|
76
103
|
| `AITHERNODE_MODE` | `auto` | Force mode: auto/local/cloud/standalone |
|
|
77
104
|
| `AITHERNODE_PORT` | `8090` | Server port |
|
|
@@ -104,9 +131,11 @@ Each installs on its own, works offline, and needs no account.
|
|
|
104
131
|
| [awgit](https://github.com/Aitherium/awgit) | that no one else is editing this file | a lease, refused at commit time if you do not hold it |
|
|
105
132
|
| [awdelphi](https://github.com/Aitherium/awdelphi) | one agent's confident take on a decision | the round trace, the anonymity, and who dissents |
|
|
106
133
|
| [awclassify](https://github.com/Aitherium/awclassify) | a filename, a folder, or whoever last touched it | doc_type, visibility, audience and topics, with the evidence lines that decided each |
|
|
134
|
+
| [awdecide](https://github.com/Aitherium/awdecide) | a hosted classifier's probability that never learns whether it was right | the decision, its probability, and the calibration curve from your own resolved outcomes |
|
|
107
135
|
| [awtoll](https://github.com/Aitherium/awtoll) | that your tooling is saving you context | the measured token cost of each tool call, and what the alternative cost |
|
|
108
136
|
| [awseal](https://github.com/Aitherium/awseal) | that the artifact came from who you think | an Ed25519 seal — the key that verifies is not the key that forges |
|
|
109
137
|
| [awshare](https://github.com/Aitherium/awshare) | that the download is intact | content-addressed bundles, verified on fetch |
|
|
138
|
+
| [awsuite](https://github.com/Aitherium/awsuite) | that an agent holding your mailbox will not send on its own | every send, draft, upload and create returns a dry-run until confirm is true |
|
|
110
139
|
| [awnest](https://github.com/Aitherium/awnest) | that there is a person on the other end | a verdict with evidence, where "we could not tell" is not "yes" |
|
|
111
140
|
| [awrena](https://github.com/Aitherium/awrena) | a leaderboard someone can edit, and votes nobody counted | a scored duel with both answers kept, and a result bound to them |
|
|
112
141
|
| [awnboard](https://github.com/Aitherium/awnboard) | a share link anyone who sees it can use | an invitation addressed to one person, for one gate, revocable |
|
|
@@ -168,9 +197,11 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
168
197
|
| [awgit](https://github.com/Aitherium/awgit) | Semantic version control on top of git — edit-ops and leases | [docs](https://aitherium.github.io/awgit/) |
|
|
169
198
|
| [awdelphi](https://github.com/Aitherium/awdelphi) | Anonymous multi-round expert panels — a converged answer with a trace | [docs](https://aitherium.github.io/awdelphi/) |
|
|
170
199
|
| [awclassify](https://github.com/Aitherium/awclassify) | Classify any document -- what it is, who may read it, who it is for, what it is about | — |
|
|
200
|
+
| [awdecide](https://github.com/Aitherium/awdecide) | One typed-decision contract -- choice / score / bool with a probability -- over a ladder of backends you already run (rules, tiny local models, an LLM's logprobs), fail-closed, with a Brier ledger that resolves every decision against its outcome | — |
|
|
171
201
|
| [awtoll](https://github.com/Aitherium/awtoll) | What every tool call costs you in context, measured from your own transcripts | [docs](https://aitherium.github.io/awtoll/) |
|
|
172
202
|
| [awseal](https://github.com/Aitherium/awseal) | Sign an artifact so a stranger can verify it | [docs](https://aitherium.github.io/awseal/) |
|
|
173
203
|
| [awshare](https://github.com/Aitherium/awshare) | Publish an artifact and fetch it back verified | [docs](https://aitherium.github.io/awshare/) |
|
|
204
|
+
| [awsuite](https://github.com/Aitherium/awsuite) | Your Google Workspace as agent tools, and no write happens without a yes | — |
|
|
174
205
|
| [awdit](https://github.com/Aitherium/awdit) | An append-only audit trail whose gaps are DETECTABLE | [docs](https://aitherium.github.io/awdit/) |
|
|
175
206
|
| [awbac](https://github.com/Aitherium/awbac) | Role-based access control that fails closed and explains itself | [docs](https://aitherium.github.io/awbac/) |
|
|
176
207
|
| [awiam](https://github.com/Aitherium/awiam) | Who is this caller? A directory and session store that fails honestly | [docs](https://aitherium.github.io/awiam/) |
|
|
@@ -223,6 +254,8 @@ Every repository here is public. Each publishes an `aither-manifest.json` beside
|
|
|
223
254
|
| [awsettings](https://github.com/Aitherium/awsettings) | Your agent's permissions and config, following you to the next machine | [docs](https://aitherium.github.io/awsettings/) |
|
|
224
255
|
| [awavatar](https://github.com/Aitherium/awavatar) | One character spec in, a rigged, animated, multi-style avatar pack out | [docs](https://aitherium.github.io/awavatar/) |
|
|
225
256
|
|
|
257
|
+
**Built on** [REA (Reverse Engineer Anything)](https://github.com/morluto/rea) · [llama.cpp](https://github.com/ggml-org/llama.cpp) · [vLLM](https://github.com/vllm-project/vllm) · [ComfyUI](https://github.com/comfyanonymous/ComfyUI) · [CentOS Stream](https://www.centos.org/centos-stream/) · [Podman](https://github.com/containers/podman) · [Docker](https://github.com/moby/moby) · [LanceDB](https://github.com/lancedb/lancedb) · [WireGuard](https://www.wireguard.com/) · [FFmpeg](https://ffmpeg.org/) · [Blender + Rigify](https://www.blender.org/) · [headroom](https://github.com/headroomlabs-ai/headroom) · [SANA](https://github.com/NVlabs/Sana) · [Hunyuan3D](https://github.com/Tencent-Hunyuan/Hunyuan3D-2.1) · [repowise](https://github.com/repowise-dev/repowise) · [Playwright](https://github.com/microsoft/playwright) · [Chromium](https://www.chromium.org/) · [Next.js](https://github.com/vercel/next.js) · [React](https://github.com/facebook/react).
|
|
258
|
+
|
|
226
259
|
<div id="aither-constellation" data-self="awnode"></div>
|
|
227
260
|
<script src="aither-constellation.js"></script>
|
|
228
261
|
|
|
@@ -23,7 +23,7 @@ import sys
|
|
|
23
23
|
#: package cannot read the registry, and a doctor that guessed at the family
|
|
24
24
|
#: would go stale in silence. Regenerate to update.
|
|
25
25
|
SELF = 'awnode'
|
|
26
|
-
FAMILY = ['awask', 'awavatar', 'awbac', 'awbrain', 'awbrowse', 'awclassify', 'awdecide', 'awdeck', 'awdelphi', 'awdit', 'awembed', 'awevolve', 'awfind', 'awflow', 'awfocus', 'awgit', 'awgraph', 'awgym', 'awiam', 'awkno', 'awm', 'awmail', 'awmine', 'awnboard', 'awnest', 'awnet', 'awpool', 'awpredict', 'awprism', 'awprove', 'awreason', 'awrecover', 'awrecurse', 'awrelay', 'awrena', 'awrepl', 'awreport', 'awresearch', 'awrise', 'awrouter', 'awrtifact', 'awrun', 'awscreen', 'awseal', 'awsettings', 'awshare', 'awsprite', 'awstorage', 'awswarm', 'awtax', 'awtoll', 'awtunnel', 'awvision', 'awvoice', 'awwall', 'gawbbonet']
|
|
26
|
+
FAMILY = ['awask', 'awavatar', 'awbac', 'awbrain', 'awbrowse', 'awcams', 'awclassify', 'awdecide', 'awdeck', 'awdelphi', 'awdit', 'awembed', 'awevolve', 'awfind', 'awflow', 'awfocus', 'awgit', 'awgraph', 'awgym', 'awiam', 'awkno', 'awm', 'awmail', 'awmine', 'awnboard', 'awnest', 'awnet', 'awpool', 'awpredict', 'awprism', 'awprove', 'awreason', 'awrecover', 'awrecurse', 'awrelay', 'awrena', 'awrepl', 'awreport', 'awresearch', 'awrise', 'awrouter', 'awrtifact', 'awrun', 'awscope', 'awscreen', 'awseal', 'awsettings', 'awshare', 'awsprite', 'awstorage', 'awsuite', 'awswarm', 'awtax', 'awtoll', 'awtunnel', 'awvision', 'awvoice', 'awwall', 'gawbbonet']
|
|
27
27
|
PAIRS_WITH = ['adk']
|
|
28
28
|
|
|
29
29
|
#: This brick's OWN config, read out of its source at generation time.
|
|
@@ -33,7 +33,7 @@ PAIRS_WITH = ['adk']
|
|
|
33
33
|
#: platform-wide vars it also touches would be noise, and a doctor that floods
|
|
34
34
|
#: gets ignored.
|
|
35
35
|
ENV_REQUIRED = []
|
|
36
|
-
ENV_OPTIONAL = ['AITHERNODE_ALLOWED_ORIGINS', 'AITHERNODE_BIND_HOST', 'AITHERNODE_CUSTOM_BACKEND_API_KEY', 'AITHERNODE_CUSTOM_BACKEND_MODEL', 'AITHERNODE_CUSTOM_BACKEND_URL', 'AITHERNODE_FS_ROOT', 'AITHERNODE_MODE', 'AITHERNODE_PORT', 'AITHERNODE_PROXY_MAP', 'AITHERNODE_PROXY_TOKEN', 'AITHERNODE_SERVICE_HOST', 'AITHERNODE_TRUSTED_PROXIES', 'AWNODE_EXTENSION_IDS']
|
|
36
|
+
ENV_OPTIONAL = ['AITHERNODE_ALLOWED_ORIGINS', 'AITHERNODE_BIND_HOST', 'AITHERNODE_CUSTOM_BACKEND_API_KEY', 'AITHERNODE_CUSTOM_BACKEND_MODEL', 'AITHERNODE_CUSTOM_BACKEND_URL', 'AITHERNODE_FS_ROOT', 'AITHERNODE_LMSTUDIO_URL', 'AITHERNODE_MODE', 'AITHERNODE_PORT', 'AITHERNODE_PROXY_MAP', 'AITHERNODE_PROXY_TOKEN', 'AITHERNODE_SERVICE_HOST', 'AITHERNODE_TRUSTED_PROXIES', 'AWNODE_EXTENSION_IDS', 'AWNODE_IN_CONTAINER']
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def _installed(mod: str) -> "str | None":
|
|
@@ -12,6 +12,7 @@ awnode CLI
|
|
|
12
12
|
awnode mcp --transport sse # Start MCP server (HTTP SSE)
|
|
13
13
|
awnode mcp-config # Output .mcp.json config snippet
|
|
14
14
|
awnode deploy <service> # Deploy via AitherComet
|
|
15
|
+
awnode pair-mode # Let a device you own find this one on the Wi-Fi (5 min)
|
|
15
16
|
"""
|
|
16
17
|
|
|
17
18
|
import argparse
|
|
@@ -41,6 +42,15 @@ def main():
|
|
|
41
42
|
if sys.argv[1:2] == ["hub"]:
|
|
42
43
|
from awnode.hub import main as hub_main
|
|
43
44
|
return hub_main(sys.argv[2:])
|
|
45
|
+
# `awnode pair-mode ...` -- advertise this box on the LAN for 5 minutes so a device that
|
|
46
|
+
# is already the owner's can approve it (adk/lan_pair.py; Phase 2 of device join).
|
|
47
|
+
if sys.argv[1:2] == ["pair-mode"]:
|
|
48
|
+
try:
|
|
49
|
+
from adk.lan_pair import main as pair_mode_main
|
|
50
|
+
except ImportError as exc:
|
|
51
|
+
print(f"pair-mode needs a newer awdk (pip install -U awdk): {exc}")
|
|
52
|
+
return 1
|
|
53
|
+
return pair_mode_main(sys.argv[2:])
|
|
44
54
|
# License entitlement gate (logs tier, wires metering quotas; never blocks unless
|
|
45
55
|
# AITHER_LICENSE_REQUIRE=1). adk must be importable for this to apply.
|
|
46
56
|
try:
|
|
@@ -166,6 +176,14 @@ def main():
|
|
|
166
176
|
|
|
167
177
|
args = parser.parse_args()
|
|
168
178
|
|
|
179
|
+
# Once-a-day PyPI check for a newer awnode (awnode/update_check.py): a cached
|
|
180
|
+
# notice prints now, a stale cache refreshes on a daemon thread. Skipped for
|
|
181
|
+
# the stdio MCP server, whose client owns the terminal. AWNODE_NO_UPDATE_CHECK=1
|
|
182
|
+
# turns it off.
|
|
183
|
+
if args.command and args.command != "mcp":
|
|
184
|
+
from awnode.update_check import cli_start_check
|
|
185
|
+
cli_start_check()
|
|
186
|
+
|
|
169
187
|
if args.command == "start":
|
|
170
188
|
if args.cloud:
|
|
171
189
|
os.environ["AITHERNODE_MODE"] = "cloud"
|
|
@@ -178,6 +196,7 @@ def main():
|
|
|
178
196
|
if args.genesis_url:
|
|
179
197
|
os.environ["AITHER_URL"] = args.genesis_url
|
|
180
198
|
os.environ["AITHERNODE_PORT"] = str(args.port)
|
|
199
|
+
os.environ["AITHERNODE_BIND_HOST"] = str(args.host) # the startup bind warning reads this env
|
|
181
200
|
|
|
182
201
|
import uvicorn
|
|
183
202
|
uvicorn.run(
|
|
@@ -329,11 +348,15 @@ async def _doctor(*, emit_json: bool = False, timeout: float = 3.0) -> int:
|
|
|
329
348
|
"genesis": os.environ.get("AITHER_URL", "http://localhost:8001"),
|
|
330
349
|
"vllm": os.environ.get("AITHER_VLLM_URL", "http://localhost:8120"),
|
|
331
350
|
"ollama": os.environ.get("OLLAMA_HOST", "http://localhost:11434"),
|
|
351
|
+
# LM Studio has no /health; its OpenAI-compatible /v1/models is the liveness surface.
|
|
352
|
+
"lmstudio": os.environ.get(
|
|
353
|
+
"AITHERNODE_LMSTUDIO_URL", os.environ.get("LMSTUDIO_URL", "http://127.0.0.1:1234")
|
|
354
|
+
),
|
|
332
355
|
"gateway": os.environ.get("AITHER_CLOUD_URL", "https://gateway.aitherium.com"),
|
|
333
356
|
}
|
|
334
357
|
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
335
358
|
for name, url in backends.items():
|
|
336
|
-
health_path = "/api/tags"
|
|
359
|
+
health_path = {"ollama": "/api/tags", "lmstudio": "/v1/models"}.get(name, "/health")
|
|
337
360
|
try:
|
|
338
361
|
r = await client.get(url.rstrip("/") + health_path)
|
|
339
362
|
if r.status_code < 500:
|
|
@@ -397,6 +420,22 @@ async def _status():
|
|
|
397
420
|
icon = "+" if status == "UP" else "-"
|
|
398
421
|
print(f" [{icon}] {name:12s} {url:40s} {status}")
|
|
399
422
|
|
|
423
|
+
# Enrollment: a dead or expiring token is the most common silent failure (sync,
|
|
424
|
+
# register and heartbeat all 401), so say it here in plain words.
|
|
425
|
+
try:
|
|
426
|
+
from awnode.sync import WorkspaceSyncClient
|
|
427
|
+
sc = WorkspaceSyncClient()
|
|
428
|
+
sc._load_local_state()
|
|
429
|
+
if sc._tenant_scoped_token:
|
|
430
|
+
exp = sc._token_expires_at or "unknown"
|
|
431
|
+
if sc.token_expired():
|
|
432
|
+
print(f" [-] Enrollment token EXPIRED ({exp}) -- re-enroll: "
|
|
433
|
+
"awnode connect --enroll-token=TOKEN")
|
|
434
|
+
else:
|
|
435
|
+
print(f" [+] Enrollment token valid until {exp}")
|
|
436
|
+
except Exception:
|
|
437
|
+
pass
|
|
438
|
+
|
|
400
439
|
# Check for running Node
|
|
401
440
|
try:
|
|
402
441
|
async with httpx.AsyncClient(timeout=2.0) as c:
|
|
@@ -578,9 +617,8 @@ async def _connect(
|
|
|
578
617
|
|
|
579
618
|
# Resolve portal URL
|
|
580
619
|
if not portal_url:
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
)
|
|
620
|
+
# api.aitherium.com is the live app host (Veil); portal.* is retired.
|
|
621
|
+
portal_url = os.environ.get("AITHER_CLOUD_URL", "https://api.aitherium.com")
|
|
584
622
|
|
|
585
623
|
aither_home = Path(os.environ.get("AITHER_HOME", str(Path.home() / ".aither")))
|
|
586
624
|
state_file = aither_home / "sync_state.json"
|
|
@@ -610,7 +648,15 @@ async def _connect(
|
|
|
610
648
|
pass
|
|
611
649
|
state["tenant_scoped_token"] = workspace_token
|
|
612
650
|
state["node_id"] = node_id
|
|
613
|
-
|
|
651
|
+
# Recorded so the node can say "re-enroll" before the 30-day
|
|
652
|
+
# token dies, instead of 401ing silently afterwards.
|
|
653
|
+
if data.get("expires_at"):
|
|
654
|
+
state["tenant_scoped_token_expires_at"] = data["expires_at"]
|
|
655
|
+
# 0600 from the first byte: this file holds a bearer token,
|
|
656
|
+
# and write_text() created it at the default umask.
|
|
657
|
+
fd = os.open(state_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
658
|
+
with os.fdopen(fd, "w") as fh:
|
|
659
|
+
fh.write(json.dumps(state, indent=2))
|
|
614
660
|
print(
|
|
615
661
|
f"✓ Token saved for node {node_id} "
|
|
616
662
|
f"(expires {data.get('expires_at', '?')})"
|
|
@@ -371,6 +371,9 @@ class Hub:
|
|
|
371
371
|
self.cache_path = state_dir / "tools-cache.json"
|
|
372
372
|
self.tool_cache: Dict[str, List[dict]] = self._load_cache()
|
|
373
373
|
self.errors: Dict[str, str] = {}
|
|
374
|
+
# When each error was recorded. An error used to be permanent: one transient
|
|
375
|
+
# start failure hid that brick's tools in every session until the hub restarted.
|
|
376
|
+
self.error_at: Dict[str, float] = {}
|
|
374
377
|
self.started_at = time.time()
|
|
375
378
|
self.stop_event = threading.Event()
|
|
376
379
|
|
|
@@ -446,8 +449,10 @@ class Hub:
|
|
|
446
449
|
c.start()
|
|
447
450
|
except ChildError as exc:
|
|
448
451
|
self.errors[brick] = str(exc)
|
|
452
|
+
self.error_at[brick] = time.monotonic()
|
|
449
453
|
raise
|
|
450
454
|
self.errors.pop(brick, None)
|
|
455
|
+
self.error_at.pop(brick, None)
|
|
451
456
|
self.children[key] = c
|
|
452
457
|
self._learn(brick, c.tools)
|
|
453
458
|
log("started %s for %s (pid %s)" % (brick, project_dir,
|
|
@@ -510,10 +515,24 @@ class Hub:
|
|
|
510
515
|
tools.append(t if exposed == orig else {**t, "name": exposed})
|
|
511
516
|
return tools, table
|
|
512
517
|
|
|
518
|
+
#: How long a brick's start failure suppresses retries.
|
|
519
|
+
ERROR_COOLDOWN_S = 300.0
|
|
520
|
+
|
|
521
|
+
def _error_fresh(self, brick: str) -> bool:
|
|
522
|
+
"""True while a recorded error is younger than the cooldown."""
|
|
523
|
+
if brick not in self.errors:
|
|
524
|
+
return False
|
|
525
|
+
at = self.error_at.get(brick)
|
|
526
|
+
if at is not None and time.monotonic() - at >= self.ERROR_COOLDOWN_S:
|
|
527
|
+
self.errors.pop(brick, None)
|
|
528
|
+
self.error_at.pop(brick, None)
|
|
529
|
+
return False
|
|
530
|
+
return True
|
|
531
|
+
|
|
513
532
|
def ensure_tool_cache(self, project_dir: str, timeout: float = 90.0) -> None:
|
|
514
533
|
"""Learn the tool list of every brick not yet in the cache (in parallel)."""
|
|
515
534
|
missing = [b for b in self.order if not self.tool_cache.get(b)
|
|
516
|
-
and
|
|
535
|
+
and not self._error_fresh(b)]
|
|
517
536
|
if not missing:
|
|
518
537
|
return
|
|
519
538
|
threads = []
|