lulabell-engine 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.
- lulabell_engine-0.3.0/LICENSE +21 -0
- lulabell_engine-0.3.0/MANIFEST.in +6 -0
- lulabell_engine-0.3.0/PKG-INFO +121 -0
- lulabell_engine-0.3.0/README.md +85 -0
- lulabell_engine-0.3.0/config.template.yaml +51 -0
- lulabell_engine-0.3.0/lulabell_engine/__init__.py +2 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/__init__.py +2 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/api.py +269 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/cli.py +376 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/config.py +42 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/core.py +166 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/executor.py +307 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/listeners/__init__.py +0 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/listeners/email_listener.py +132 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/planner.py +315 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/safety.py +180 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tasks/__init__.py +17 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tasks/email_task.py +61 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tasks/file_task.py +60 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tasks/llm_task.py +117 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tasks/plan_task.py +215 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tasks/system_task.py +113 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tools/__init__.py +42 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tools/bash.py +200 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tools/config.py +166 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tools/diagnose.py +268 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tools/docker.py +145 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tools/files.py +193 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tools/git.py +167 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tools/logs.py +139 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/tools/systemd.py +146 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/uap_handler.py +243 -0
- lulabell_engine-0.3.0/lulabell_engine/actioncore/validator.py +173 -0
- lulabell_engine-0.3.0/lulabell_engine/agent_manager.py +216 -0
- lulabell_engine-0.3.0/lulabell_engine/alerts.py +235 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/__init__.py +0 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/actions.py +0 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/bar.py +1213 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/chat.py +279 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/cli.py +320 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/config.py +67 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/core.py +242 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/crypto.py +115 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/homelab.py +0 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/identity.py +0 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/incubation.py +187 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/memory.py +151 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/notifications.py +155 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/personality.py +154 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/relevance.py +124 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/router.py +127 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/sanitizer.py +88 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/surrogates.py +0 -0
- lulabell_engine-0.3.0/lulabell_engine/anchor/tiers.py +100 -0
- lulabell_engine-0.3.0/lulabell_engine/api_keys.py +86 -0
- lulabell_engine-0.3.0/lulabell_engine/audit.py +404 -0
- lulabell_engine-0.3.0/lulabell_engine/auth.py +180 -0
- lulabell_engine-0.3.0/lulabell_engine/b2_routes.py +445 -0
- lulabell_engine-0.3.0/lulabell_engine/blackwell/__init__.py +3 -0
- lulabell_engine-0.3.0/lulabell_engine/blackwell/baseline.py +290 -0
- lulabell_engine-0.3.0/lulabell_engine/blackwell/drift.py +309 -0
- lulabell_engine-0.3.0/lulabell_engine/blackwell/embeddings.py +71 -0
- lulabell_engine-0.3.0/lulabell_engine/blackwell/routes.py +109 -0
- lulabell_engine-0.3.0/lulabell_engine/blackwell/summary.py +186 -0
- lulabell_engine-0.3.0/lulabell_engine/capabilities.py +248 -0
- lulabell_engine-0.3.0/lulabell_engine/cli.py +981 -0
- lulabell_engine-0.3.0/lulabell_engine/cli_summary.py +202 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/__init__.py +93 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/app_usage.py +68 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/audio_context.py +61 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/browser_history.py +103 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/clipboard.py +60 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/debug.py +117 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/keystrokes.py +80 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/launcher.py +223 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/network.py +78 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/presence.py +186 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/process_list.py +56 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/screen_context.py +68 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/system_stat.py +51 -0
- lulabell_engine-0.3.0/lulabell_engine/collectors/video.py +346 -0
- lulabell_engine-0.3.0/lulabell_engine/compression.py +495 -0
- lulabell_engine-0.3.0/lulabell_engine/config.py +533 -0
- lulabell_engine-0.3.0/lulabell_engine/config_loader.py +261 -0
- lulabell_engine-0.3.0/lulabell_engine/dashboard.py +556 -0
- lulabell_engine-0.3.0/lulabell_engine/database.py +790 -0
- lulabell_engine-0.3.0/lulabell_engine/distillation.py +416 -0
- lulabell_engine-0.3.0/lulabell_engine/extractor.py +1488 -0
- lulabell_engine-0.3.0/lulabell_engine/feedback.py +236 -0
- lulabell_engine-0.3.0/lulabell_engine/identity.py +1286 -0
- lulabell_engine-0.3.0/lulabell_engine/intelligence/__init__.py +2 -0
- lulabell_engine-0.3.0/lulabell_engine/intelligence/activity_score.py +403 -0
- lulabell_engine-0.3.0/lulabell_engine/intelligence/goal_drift.py +316 -0
- lulabell_engine-0.3.0/lulabell_engine/intelligence/routes.py +117 -0
- lulabell_engine-0.3.0/lulabell_engine/launcher.py +286 -0
- lulabell_engine-0.3.0/lulabell_engine/mcp_server.py +244 -0
- lulabell_engine-0.3.0/lulabell_engine/memory.py +341 -0
- lulabell_engine-0.3.0/lulabell_engine/monitors.py +187 -0
- lulabell_engine-0.3.0/lulabell_engine/observations.py +281 -0
- lulabell_engine-0.3.0/lulabell_engine/onboarding.py +276 -0
- lulabell_engine-0.3.0/lulabell_engine/patterns.py +1904 -0
- lulabell_engine-0.3.0/lulabell_engine/personality.py +303 -0
- lulabell_engine-0.3.0/lulabell_engine/pots.py +640 -0
- lulabell_engine-0.3.0/lulabell_engine/pots_linux.py +991 -0
- lulabell_engine-0.3.0/lulabell_engine/pots_manager.py +166 -0
- lulabell_engine-0.3.0/lulabell_engine/prune_conversations.py +133 -0
- lulabell_engine-0.3.0/lulabell_engine/relevance/__init__.py +10 -0
- lulabell_engine-0.3.0/lulabell_engine/relevance/baselines.py +356 -0
- lulabell_engine-0.3.0/lulabell_engine/relevance/correlator.py +348 -0
- lulabell_engine-0.3.0/lulabell_engine/relevance/routes.py +263 -0
- lulabell_engine-0.3.0/lulabell_engine/relevance/scorer.py +388 -0
- lulabell_engine-0.3.0/lulabell_engine/reminders.py +392 -0
- lulabell_engine-0.3.0/lulabell_engine/reminders_routes.py +126 -0
- lulabell_engine-0.3.0/lulabell_engine/scheduler.py +96 -0
- lulabell_engine-0.3.0/lulabell_engine/server.py +838 -0
- lulabell_engine-0.3.0/lulabell_engine/setup.py +337 -0
- lulabell_engine-0.3.0/lulabell_engine/static/app.js +611 -0
- lulabell_engine-0.3.0/lulabell_engine/static/index.html +131 -0
- lulabell_engine-0.3.0/lulabell_engine/static/style.css +824 -0
- lulabell_engine-0.3.0/lulabell_engine/subjects/__init__.py +1 -0
- lulabell_engine-0.3.0/lulabell_engine/subjects/camera.yaml +45 -0
- lulabell_engine-0.3.0/lulabell_engine/subjects/human.yaml +139 -0
- lulabell_engine-0.3.0/lulabell_engine/subjects/loader.py +85 -0
- lulabell_engine-0.3.0/lulabell_engine/subjects/vehicle.yaml +40 -0
- lulabell_engine-0.3.0/lulabell_engine/summary.py +221 -0
- lulabell_engine-0.3.0/lulabell_engine/uap/__init__.py +119 -0
- lulabell_engine-0.3.0/lulabell_engine/uap/backup.py +288 -0
- lulabell_engine-0.3.0/lulabell_engine/uap/config.py +37 -0
- lulabell_engine-0.3.0/lulabell_engine/uap/integrity.py +356 -0
- lulabell_engine-0.3.0/lulabell_engine/uap/routes.py +109 -0
- lulabell_engine-0.3.0/lulabell_engine/utils.py +189 -0
- lulabell_engine-0.3.0/lulabell_engine.egg-info/PKG-INFO +121 -0
- lulabell_engine-0.3.0/lulabell_engine.egg-info/SOURCES.txt +138 -0
- lulabell_engine-0.3.0/lulabell_engine.egg-info/dependency_links.txt +1 -0
- lulabell_engine-0.3.0/lulabell_engine.egg-info/entry_points.txt +2 -0
- lulabell_engine-0.3.0/lulabell_engine.egg-info/requires.txt +29 -0
- lulabell_engine-0.3.0/lulabell_engine.egg-info/top_level.txt +1 -0
- lulabell_engine-0.3.0/pyproject.toml +64 -0
- lulabell_engine-0.3.0/setup.cfg +4 -0
- lulabell_engine-0.3.0/tests/test_smoke.py +256 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jeff Draper
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lulabell-engine
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Local-first behavioral intelligence engine with collectors, AI agent, and task executor
|
|
5
|
+
Author: Jeff Draper
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: fastapi>=0.100.0
|
|
11
|
+
Requires-Dist: uvicorn[standard]>=0.20.0
|
|
12
|
+
Requires-Dist: pydantic>=2.0.0
|
|
13
|
+
Requires-Dist: python-jose[cryptography]>=3.3.0
|
|
14
|
+
Requires-Dist: passlib[bcrypt]>=1.7.4
|
|
15
|
+
Requires-Dist: bcrypt>=4.0.0
|
|
16
|
+
Requires-Dist: pyyaml>=6.0
|
|
17
|
+
Requires-Dist: httpx>=0.24.0
|
|
18
|
+
Requires-Dist: requests>=2.28.0
|
|
19
|
+
Requires-Dist: psutil>=5.9.0
|
|
20
|
+
Requires-Dist: pillow>=9.0.0
|
|
21
|
+
Requires-Dist: sentence-transformers>=2.2.0
|
|
22
|
+
Requires-Dist: rich>=13.0.0
|
|
23
|
+
Requires-Dist: cryptography>=41.0.0
|
|
24
|
+
Requires-Dist: groq
|
|
25
|
+
Requires-Dist: anthropic
|
|
26
|
+
Requires-Dist: watchdog
|
|
27
|
+
Requires-Dist: mcp
|
|
28
|
+
Requires-Dist: pywin32>=305; sys_platform == "win32"
|
|
29
|
+
Provides-Extra: ml
|
|
30
|
+
Requires-Dist: scikit-learn; extra == "ml"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest; extra == "dev"
|
|
33
|
+
Requires-Dist: black; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# tethr-engine
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/tethr-engine)
|
|
40
|
+
[](https://pypi.org/project/tethr-engine)
|
|
41
|
+
[](LICENSE)
|
|
42
|
+
|
|
43
|
+
TETHR is a local-first behavioral intelligence engine.
|
|
44
|
+
It passively observes your devices, detects behavioral
|
|
45
|
+
patterns without cloud processing, and serves real-time
|
|
46
|
+
presence context to AI agents in under 300 tokens.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install tethr-engine
|
|
50
|
+
tethr start
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## What it does
|
|
56
|
+
|
|
57
|
+
- **Passive observation** — watches active windows, typing cadence, system state via background POTS process
|
|
58
|
+
- **Pattern detection** — identifies focus areas, app clusters, work rhythms from local data only
|
|
59
|
+
- **Agent-ready API** — `/identity/context` returns a compact behavioral summary, ready to inject into any system prompt
|
|
60
|
+
- **MCP server** — native Claude Desktop integration via `python -m tethr_engine.mcp_server`
|
|
61
|
+
- **Zero cloud** — everything stays on your device; Groq API used only for LLM inference calls
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Quick start
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install tethr-engine
|
|
69
|
+
tethr start # first run prompts for Groq API key, then serves on :8001
|
|
70
|
+
tethr status # confirm health
|
|
71
|
+
curl http://127.0.0.1:8001/identity/context
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Full walkthrough → [docs/quickstart.md](docs/quickstart.md)
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Agent integration
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import requests
|
|
82
|
+
|
|
83
|
+
context = requests.get("http://127.0.0.1:8001/identity/context", timeout=3).json()["context"]
|
|
84
|
+
system_prompt = f"{your_base_prompt}\n\n[User context]\n{context}"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Query once per session, inject into system prompt. ~200–300 tokens.
|
|
88
|
+
|
|
89
|
+
Full guide → [docs/integration.md](docs/integration.md)
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## CLI
|
|
94
|
+
|
|
95
|
+
| Command | Description |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `tethr start` | Start server on `localhost:8001` |
|
|
98
|
+
| `tethr start --host 0.0.0.0` | Network accessible |
|
|
99
|
+
| `tethr start --reload` | Dev mode |
|
|
100
|
+
| `tethr status` | Health check |
|
|
101
|
+
| `tethr version` | Print version |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Configuring data storage
|
|
106
|
+
|
|
107
|
+
By default TETHR stores data in the package directory. To use an external drive set `data_path` in `config.yaml`:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
data_path: D:\tethr-data
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Or set it during first-run setup when prompted. TETHR creates the directory if it does not exist.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Links
|
|
118
|
+
|
|
119
|
+
- [PyPI](https://pypi.org/project/tethr-engine)
|
|
120
|
+
- [Quickstart](docs/quickstart.md)
|
|
121
|
+
- [Integration guide](docs/integration.md)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# tethr-engine
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/tethr-engine)
|
|
4
|
+
[](https://pypi.org/project/tethr-engine)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
TETHR is a local-first behavioral intelligence engine.
|
|
8
|
+
It passively observes your devices, detects behavioral
|
|
9
|
+
patterns without cloud processing, and serves real-time
|
|
10
|
+
presence context to AI agents in under 300 tokens.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install tethr-engine
|
|
14
|
+
tethr start
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What it does
|
|
20
|
+
|
|
21
|
+
- **Passive observation** — watches active windows, typing cadence, system state via background POTS process
|
|
22
|
+
- **Pattern detection** — identifies focus areas, app clusters, work rhythms from local data only
|
|
23
|
+
- **Agent-ready API** — `/identity/context` returns a compact behavioral summary, ready to inject into any system prompt
|
|
24
|
+
- **MCP server** — native Claude Desktop integration via `python -m tethr_engine.mcp_server`
|
|
25
|
+
- **Zero cloud** — everything stays on your device; Groq API used only for LLM inference calls
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install tethr-engine
|
|
33
|
+
tethr start # first run prompts for Groq API key, then serves on :8001
|
|
34
|
+
tethr status # confirm health
|
|
35
|
+
curl http://127.0.0.1:8001/identity/context
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Full walkthrough → [docs/quickstart.md](docs/quickstart.md)
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Agent integration
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import requests
|
|
46
|
+
|
|
47
|
+
context = requests.get("http://127.0.0.1:8001/identity/context", timeout=3).json()["context"]
|
|
48
|
+
system_prompt = f"{your_base_prompt}\n\n[User context]\n{context}"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Query once per session, inject into system prompt. ~200–300 tokens.
|
|
52
|
+
|
|
53
|
+
Full guide → [docs/integration.md](docs/integration.md)
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## CLI
|
|
58
|
+
|
|
59
|
+
| Command | Description |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `tethr start` | Start server on `localhost:8001` |
|
|
62
|
+
| `tethr start --host 0.0.0.0` | Network accessible |
|
|
63
|
+
| `tethr start --reload` | Dev mode |
|
|
64
|
+
| `tethr status` | Health check |
|
|
65
|
+
| `tethr version` | Print version |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Configuring data storage
|
|
70
|
+
|
|
71
|
+
By default TETHR stores data in the package directory. To use an external drive set `data_path` in `config.yaml`:
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
data_path: D:\tethr-data
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or set it during first-run setup when prompted. TETHR creates the directory if it does not exist.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Links
|
|
82
|
+
|
|
83
|
+
- [PyPI](https://pypi.org/project/tethr-engine)
|
|
84
|
+
- [Quickstart](docs/quickstart.md)
|
|
85
|
+
- [Integration guide](docs/integration.md)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# TETHR Engine Configuration Template
|
|
2
|
+
# Copy this to data/config.yaml and fill in your values.
|
|
3
|
+
# config.yaml is gitignored — never commit your actual secrets.
|
|
4
|
+
|
|
5
|
+
# === REQUIRED ===
|
|
6
|
+
data_path: "D:\\TETHR Data" # Where to store database + files
|
|
7
|
+
user_id: "" # Populated automatically during setup
|
|
8
|
+
|
|
9
|
+
# === AUTHENTICATION ===
|
|
10
|
+
username: "" # Your login username
|
|
11
|
+
port: 8001 # Server port
|
|
12
|
+
|
|
13
|
+
# === API KEYS (Optional - enables features) ===
|
|
14
|
+
groq_api_key: "" # For LLM - get from console.groq.com
|
|
15
|
+
tavily_api_key: "" # For web search - get from app.tavily.com
|
|
16
|
+
telegram_token: "" # For Telegram - get from @BotFather
|
|
17
|
+
telegram_chat_id: "" # Your Telegram chat ID (message @userinfobot)
|
|
18
|
+
|
|
19
|
+
# === AGENT CONFIG ===
|
|
20
|
+
agent_name: "" # Your agent's name (set during onboarding)
|
|
21
|
+
agent_hatched: false # Set to true after first conversation
|
|
22
|
+
|
|
23
|
+
# === DEVICE ===
|
|
24
|
+
device_id: "" # This device's hostname (auto-set)
|
|
25
|
+
|
|
26
|
+
# === OPTIONAL INTEGRATIONS ===
|
|
27
|
+
# zepp_token: "" # Amazfit/Zepp API
|
|
28
|
+
# spotify_token: "" # Spotify API
|
|
29
|
+
|
|
30
|
+
# === PROACTIVE ===
|
|
31
|
+
proactive:
|
|
32
|
+
enabled: true
|
|
33
|
+
schedule:
|
|
34
|
+
work_days: [mon, tue, wed, thu, fri]
|
|
35
|
+
work_start: "08:00"
|
|
36
|
+
work_end: "17:00"
|
|
37
|
+
wake_time_work: "07:00"
|
|
38
|
+
wake_time_off: "09:00"
|
|
39
|
+
quiet_hours:
|
|
40
|
+
start: 22
|
|
41
|
+
end: 7
|
|
42
|
+
|
|
43
|
+
# === SEARCH ===
|
|
44
|
+
search:
|
|
45
|
+
provider: "tavily"
|
|
46
|
+
tavily_api_key: "" # Can also go in the top-level tavily_api_key
|
|
47
|
+
|
|
48
|
+
# === STORAGE ===
|
|
49
|
+
storage:
|
|
50
|
+
photos_path: "D:\\TETHR Photos"
|
|
51
|
+
inbox_dir: "inbox"
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
api.py — FastAPI server for ActionCore (port 8002).
|
|
3
|
+
|
|
4
|
+
Endpoints:
|
|
5
|
+
GET /health — liveness check
|
|
6
|
+
GET /status — queue status + recent task history
|
|
7
|
+
GET /tools — list available tools
|
|
8
|
+
POST /task — submit a task {type, params}
|
|
9
|
+
POST /task/plan — plan a task (returns steps, no execution)
|
|
10
|
+
POST /task/execute — execute a pre-built plan
|
|
11
|
+
POST /task/run — plan + execute in one call
|
|
12
|
+
GET /task/{id}/status — status of a specific task by id
|
|
13
|
+
POST /diagnose — run full system diagnosis
|
|
14
|
+
"""
|
|
15
|
+
import logging
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
19
|
+
from pydantic import BaseModel
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
app = FastAPI(title="ActionCore", version="0.2.0")
|
|
24
|
+
# app.state.core is set by cli.py before the server starts
|
|
25
|
+
|
|
26
|
+
# ── Valid task types ──────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
_VALID_TYPES = {
|
|
29
|
+
"email_send",
|
|
30
|
+
"file_store",
|
|
31
|
+
"file_retrieve",
|
|
32
|
+
"system_status",
|
|
33
|
+
"llm_query",
|
|
34
|
+
"run_plan",
|
|
35
|
+
"diagnose_tethr",
|
|
36
|
+
"fix_collector",
|
|
37
|
+
"update_tethr",
|
|
38
|
+
"self_repair",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ── Request models ────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
class TaskRequest(BaseModel):
|
|
45
|
+
type: str
|
|
46
|
+
params: dict[str, Any] = {}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PlanRequest(BaseModel):
|
|
50
|
+
description: str
|
|
51
|
+
stop_on_failure: bool = True
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class StepDict(BaseModel):
|
|
55
|
+
action: str
|
|
56
|
+
tool: str
|
|
57
|
+
params: dict[str, Any] = {}
|
|
58
|
+
expected_result: str = ""
|
|
59
|
+
stop_on_failure: bool = True
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ExecuteRequest(BaseModel):
|
|
63
|
+
description: str = ""
|
|
64
|
+
steps: list[StepDict]
|
|
65
|
+
stop_on_failure: bool = True
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class RunRequest(BaseModel):
|
|
69
|
+
description: str
|
|
70
|
+
stop_on_failure: bool = True
|
|
71
|
+
plan_only: bool = False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
def _get_core(request: Request):
|
|
77
|
+
core = request.app.state.core
|
|
78
|
+
if core is None:
|
|
79
|
+
raise HTTPException(503, "Core not initialised")
|
|
80
|
+
return core
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ── Endpoints ─────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
@app.get("/health")
|
|
86
|
+
def health():
|
|
87
|
+
cfg = getattr(app.state, "config", {})
|
|
88
|
+
name = cfg.get("agent_name", "ActionCore")
|
|
89
|
+
return {"status": "ok", "agent": name, "version": "0.2.0"}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.get("/status")
|
|
93
|
+
def get_status(request: Request):
|
|
94
|
+
return _get_core(request).queue.get_status()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.get("/tools")
|
|
98
|
+
def list_tools():
|
|
99
|
+
"""Return the list of available tools and their capabilities."""
|
|
100
|
+
return {
|
|
101
|
+
"tools": [
|
|
102
|
+
{
|
|
103
|
+
"name": "bash",
|
|
104
|
+
"desc": "Execute shell commands",
|
|
105
|
+
"params": ["command", "timeout", "allow_dangerous"],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "files",
|
|
109
|
+
"desc": "Read, write, and edit files (within ALLOWED_PATHS)",
|
|
110
|
+
"params": ["action", "path", "content", "old_text", "new_text"],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "docker",
|
|
114
|
+
"desc": "Manage Docker containers and compose stacks",
|
|
115
|
+
"params": ["action", "container", "image", "compose_file"],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"name": "systemd",
|
|
119
|
+
"desc": "Control system services (Linux systemctl / Windows sc)",
|
|
120
|
+
"params": ["action", "name"],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "git",
|
|
124
|
+
"desc": "Git repository operations",
|
|
125
|
+
"params": ["action", "repo_path", "branch"],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "config",
|
|
129
|
+
"desc": "Read/write YAML and JSON config files",
|
|
130
|
+
"params": ["action", "path", "key", "value"],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "logs",
|
|
134
|
+
"desc": "Inspect log files",
|
|
135
|
+
"params": ["action", "path", "lines", "search"],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "diagnose",
|
|
139
|
+
"desc": "System health diagnostics",
|
|
140
|
+
"params": ["check"],
|
|
141
|
+
"checks": ["tethr_health", "collectors", "disk", "memory", "ports", "processes", "full"],
|
|
142
|
+
},
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@app.post("/task")
|
|
148
|
+
def submit_task(req: TaskRequest, request: Request):
|
|
149
|
+
"""Submit a background task to the ActionCore queue."""
|
|
150
|
+
core = _get_core(request)
|
|
151
|
+
|
|
152
|
+
if req.type not in _VALID_TYPES:
|
|
153
|
+
raise HTTPException(
|
|
154
|
+
400,
|
|
155
|
+
f"Unknown task type {req.type!r}. Valid: {sorted(_VALID_TYPES)}",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
task = core.queue.submit(req.type, req.params)
|
|
159
|
+
logger.info("[api] task submitted id=%s type=%s", task.id, task.type)
|
|
160
|
+
return {"task_id": task.id, "status": task.status, "type": task.type}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@app.post("/task/plan")
|
|
164
|
+
def plan_task(req: PlanRequest):
|
|
165
|
+
"""
|
|
166
|
+
Plan a task and return the ordered steps — does NOT execute.
|
|
167
|
+
Use POST /task/run to plan + execute.
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
from lulabell_engine.actioncore.planner import plan_task as _plan
|
|
171
|
+
steps = _plan(req.description)
|
|
172
|
+
return {
|
|
173
|
+
"description": req.description,
|
|
174
|
+
"steps": [s.to_dict() for s in steps],
|
|
175
|
+
"step_count": len(steps),
|
|
176
|
+
}
|
|
177
|
+
except Exception as exc:
|
|
178
|
+
raise HTTPException(500, str(exc))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@app.post("/task/execute")
|
|
182
|
+
def execute_plan(req: ExecuteRequest):
|
|
183
|
+
"""Execute a pre-built list of steps (from /task/plan or hand-crafted)."""
|
|
184
|
+
try:
|
|
185
|
+
from lulabell_engine.actioncore.planner import Step
|
|
186
|
+
from lulabell_engine.actioncore.executor import execute_plan as _exec
|
|
187
|
+
from lulabell_engine.actioncore.validator import validate_task
|
|
188
|
+
|
|
189
|
+
steps = [Step.from_dict(s.dict()) for s in req.steps]
|
|
190
|
+
result = _exec(steps, task_description=req.description,
|
|
191
|
+
stop_on_failure=req.stop_on_failure)
|
|
192
|
+
val = validate_task(req.description, result)
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
**result.to_dict(),
|
|
196
|
+
"validation": val.to_dict(),
|
|
197
|
+
}
|
|
198
|
+
except Exception as exc:
|
|
199
|
+
raise HTTPException(500, str(exc))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@app.post("/task/run")
|
|
203
|
+
def run_task(req: RunRequest):
|
|
204
|
+
"""Plan and execute a task description in one call."""
|
|
205
|
+
try:
|
|
206
|
+
from lulabell_engine.actioncore.planner import plan_task as _plan
|
|
207
|
+
from lulabell_engine.actioncore.executor import execute_plan as _exec
|
|
208
|
+
from lulabell_engine.actioncore.validator import validate_task
|
|
209
|
+
|
|
210
|
+
steps = _plan(req.description)
|
|
211
|
+
|
|
212
|
+
if req.plan_only:
|
|
213
|
+
return {
|
|
214
|
+
"description": req.description,
|
|
215
|
+
"steps": [s.to_dict() for s in steps],
|
|
216
|
+
"step_count": len(steps),
|
|
217
|
+
"executed": False,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
result = _exec(steps, task_description=req.description,
|
|
221
|
+
stop_on_failure=req.stop_on_failure)
|
|
222
|
+
val = validate_task(req.description, result)
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
**result.to_dict(),
|
|
226
|
+
"validation": val.to_dict(),
|
|
227
|
+
"executed": True,
|
|
228
|
+
}
|
|
229
|
+
except Exception as exc:
|
|
230
|
+
raise HTTPException(500, str(exc))
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@app.get("/task/{task_id}/status")
|
|
234
|
+
def task_status(task_id: str, request: Request):
|
|
235
|
+
"""Get the current status of a queued/completed task by id."""
|
|
236
|
+
core = _get_core(request)
|
|
237
|
+
for task in core.queue._history:
|
|
238
|
+
if task.id == task_id:
|
|
239
|
+
return task.to_dict()
|
|
240
|
+
# Check if it's still in the queue (pending)
|
|
241
|
+
raise HTTPException(404, f"Task {task_id!r} not found")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@app.post("/diagnose")
|
|
245
|
+
def diagnose():
|
|
246
|
+
"""Run a full system diagnosis and return the report."""
|
|
247
|
+
try:
|
|
248
|
+
from lulabell_engine.actioncore.tools.diagnose import full_diagnosis
|
|
249
|
+
report = full_diagnosis()
|
|
250
|
+
|
|
251
|
+
# High-level summary
|
|
252
|
+
issues = []
|
|
253
|
+
if not report["tethr"].get("server_up"):
|
|
254
|
+
issues.append("LulaBell server not responding")
|
|
255
|
+
silent = report["collectors"].get("silent", [])
|
|
256
|
+
if silent:
|
|
257
|
+
issues.append(f"Silent collectors: {', '.join(silent)}")
|
|
258
|
+
for part in report["disk"].get("low_space", []):
|
|
259
|
+
issues.append(f"Low disk space: {part}")
|
|
260
|
+
if report["memory"].get("low_memory"):
|
|
261
|
+
issues.append(f"High RAM usage: {report['memory']['ram_pct_used']:.0f}%")
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
"healthy": len(issues) == 0,
|
|
265
|
+
"issues": issues,
|
|
266
|
+
"report": report,
|
|
267
|
+
}
|
|
268
|
+
except Exception as exc:
|
|
269
|
+
raise HTTPException(500, str(exc))
|