lvtn-mobile 1.0.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.
@@ -0,0 +1 @@
1
+ node_modules
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.4
2
+ Name: lvtn-mobile
3
+ Version: 1.0.0
4
+ Summary: Leviathan Shel HaShem — Mobile Swarm Client for Termux/Android. Backend-heavy thin client with offline task scheduling, headless remote control, and full 708-agent swarm access from your phone.
5
+ Project-URL: Homepage, https://lvtn.metanoiaunlimited.com
6
+ Author-email: "HaChazal (Chaz Leland Hamm)" <hachazal418@metanoiaunlimited.com>
7
+ License: Proprietary - Metanoia Unlimited LLC
8
+ Keywords: agent,android,background,headless,mobile,orchestration,scheduler,swarm,termux
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: Android
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Communications :: Chat
20
+ Classifier: Topic :: System :: Distributed Computing
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: aiohttp>=3.9
23
+ Provides-Extra: full
24
+ Requires-Dist: lvtn>=3.14; extra == 'full'
25
+ Provides-Extra: notify
26
+ Requires-Dist: plyer>=2.1; extra == 'notify'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # lvtn-mobile — Termux/Android Swarm Client
30
+
31
+ The full 708-agent Leviathan swarm, on your phone. Backend-heavy — all LLM compute runs server-side. Your phone is the remote control.
32
+
33
+ ```bash
34
+ # Termux one-time setup
35
+ pkg update && pkg upgrade
36
+ pkg install python termux-api termux-boot
37
+ pip install lvtn-mobile
38
+ lvtnm login
39
+ ```
40
+
41
+ ## Commands
42
+
43
+ | Command | What it does |
44
+ |---------|-------------|
45
+ | `lvtnm login` | Authenticate with your Leviathan key |
46
+ | `lvtnm chat "prompt"` | Send a prompt to HaChazal |
47
+ | `lvtnm fleet "task"` | Deploy all 7 Angels at once |
48
+ | `lvtnm council "task"` | All Angels answer in parallel |
49
+ | `lvtnm swarm zadkiel "task"` | Deploy one Angel's swarm |
50
+ | `lvtnm schedule "task"` | Queue a task for offline execution |
51
+ | `lvtnm schedule-list` | List scheduled tasks |
52
+ | `lvtnm schedule-run` | Execute all queued tasks now |
53
+ | `lvtnm headless` | Start HTTP server for remote control |
54
+ | `lvtnm status` | Show swarm health + scheduled tasks |
55
+ | `lvtnm balance` | Check KTRS balance |
56
+ | `lvtnm angels` | List all 7 Angels |
57
+ | `lvtnm remember "text"` | Write to HiveMind memory |
58
+ | `lvtnm recall "query"` | Search HiveMind memory |
59
+ | `lvtnm notify on\|off` | Toggle Android notifications |
60
+ | `lvtnm wake on\|off` | Toggle wake lock (keep phone awake) |
61
+ | `lvtnm boot on\|off` | Toggle auto-start on device boot |
62
+
63
+ ## Offline Mode
64
+
65
+ Tasks queued while offline execute automatically when connectivity returns. Uses a background watcher thread that checks every 30 seconds.
66
+
67
+ ```bash
68
+ lvtnm schedule "research Solana DeFi trends" --type fleet
69
+ # Queued. Will execute when online.
70
+ ```
71
+
72
+ ## Headless Mode (Remote Control)
73
+
74
+ Start an HTTP server on your phone, control the swarm from any device on the same network:
75
+
76
+ ```bash
77
+ lvtnm headless --port 9877
78
+ ```
79
+
80
+ Then from another device:
81
+ ```bash
82
+ curl http://192.168.1.100:9877/fleet -d '{"task":"audit the lead gen pipeline"}'
83
+ curl http://192.168.1.100:9877/status
84
+ ```
85
+
86
+ ## Auto-Start on Boot
87
+
88
+ ```bash
89
+ lvtnm boot on
90
+ # Headless server starts automatically when your phone boots
91
+ ```
92
+
93
+ ## Notifications
94
+
95
+ ```bash
96
+ lvtnm notify on
97
+ # Get Android notifications when scheduled tasks complete
98
+ ```
99
+
100
+ ## Architecture
101
+
102
+ ```
103
+ Phone (Termux) Server (api.leviathansi.xyz)
104
+ ┌─────────────────┐ ┌──────────────────────────┐
105
+ │ lvtn-mobile │ HTTPS │ Leviathan Backend │
106
+ │ │ ◄──────────► │ │
107
+ │ • chat │ │ • 708 LLM agents │
108
+ │ • fleet │ aiohttp │ • 2,124 binary claws │
109
+ │ • council │ │ • HiveMind memory │
110
+ │ • schedule │ │ • KTRS metering │
111
+ │ • headless │ │ • All compute │
112
+ │ │ │ │
113
+ │ Background │ │ Your phone does nothing │
114
+ │ watcher thread │ │ heavy — just HTTP calls │
115
+ └─────────────────┘ └──────────────────────────┘
116
+ ```
117
+
118
+ ## Dependencies
119
+
120
+ Only one: `aiohttp` for async HTTP. No `rich`, no `prompt_toolkit`, no heavy TUI libs. Designed for Termux's limited environment.
121
+
122
+ ## Install from npm
123
+
124
+ ```bash
125
+ npm install -g lvtn-mobile
126
+ lvtnm login
127
+ ```
@@ -0,0 +1,99 @@
1
+ # lvtn-mobile — Termux/Android Swarm Client
2
+
3
+ The full 708-agent Leviathan swarm, on your phone. Backend-heavy — all LLM compute runs server-side. Your phone is the remote control.
4
+
5
+ ```bash
6
+ # Termux one-time setup
7
+ pkg update && pkg upgrade
8
+ pkg install python termux-api termux-boot
9
+ pip install lvtn-mobile
10
+ lvtnm login
11
+ ```
12
+
13
+ ## Commands
14
+
15
+ | Command | What it does |
16
+ |---------|-------------|
17
+ | `lvtnm login` | Authenticate with your Leviathan key |
18
+ | `lvtnm chat "prompt"` | Send a prompt to HaChazal |
19
+ | `lvtnm fleet "task"` | Deploy all 7 Angels at once |
20
+ | `lvtnm council "task"` | All Angels answer in parallel |
21
+ | `lvtnm swarm zadkiel "task"` | Deploy one Angel's swarm |
22
+ | `lvtnm schedule "task"` | Queue a task for offline execution |
23
+ | `lvtnm schedule-list` | List scheduled tasks |
24
+ | `lvtnm schedule-run` | Execute all queued tasks now |
25
+ | `lvtnm headless` | Start HTTP server for remote control |
26
+ | `lvtnm status` | Show swarm health + scheduled tasks |
27
+ | `lvtnm balance` | Check KTRS balance |
28
+ | `lvtnm angels` | List all 7 Angels |
29
+ | `lvtnm remember "text"` | Write to HiveMind memory |
30
+ | `lvtnm recall "query"` | Search HiveMind memory |
31
+ | `lvtnm notify on\|off` | Toggle Android notifications |
32
+ | `lvtnm wake on\|off` | Toggle wake lock (keep phone awake) |
33
+ | `lvtnm boot on\|off` | Toggle auto-start on device boot |
34
+
35
+ ## Offline Mode
36
+
37
+ Tasks queued while offline execute automatically when connectivity returns. Uses a background watcher thread that checks every 30 seconds.
38
+
39
+ ```bash
40
+ lvtnm schedule "research Solana DeFi trends" --type fleet
41
+ # Queued. Will execute when online.
42
+ ```
43
+
44
+ ## Headless Mode (Remote Control)
45
+
46
+ Start an HTTP server on your phone, control the swarm from any device on the same network:
47
+
48
+ ```bash
49
+ lvtnm headless --port 9877
50
+ ```
51
+
52
+ Then from another device:
53
+ ```bash
54
+ curl http://192.168.1.100:9877/fleet -d '{"task":"audit the lead gen pipeline"}'
55
+ curl http://192.168.1.100:9877/status
56
+ ```
57
+
58
+ ## Auto-Start on Boot
59
+
60
+ ```bash
61
+ lvtnm boot on
62
+ # Headless server starts automatically when your phone boots
63
+ ```
64
+
65
+ ## Notifications
66
+
67
+ ```bash
68
+ lvtnm notify on
69
+ # Get Android notifications when scheduled tasks complete
70
+ ```
71
+
72
+ ## Architecture
73
+
74
+ ```
75
+ Phone (Termux) Server (api.leviathansi.xyz)
76
+ ┌─────────────────┐ ┌──────────────────────────┐
77
+ │ lvtn-mobile │ HTTPS │ Leviathan Backend │
78
+ │ │ ◄──────────► │ │
79
+ │ • chat │ │ • 708 LLM agents │
80
+ │ • fleet │ aiohttp │ • 2,124 binary claws │
81
+ │ • council │ │ • HiveMind memory │
82
+ │ • schedule │ │ • KTRS metering │
83
+ │ • headless │ │ • All compute │
84
+ │ │ │ │
85
+ │ Background │ │ Your phone does nothing │
86
+ │ watcher thread │ │ heavy — just HTTP calls │
87
+ └─────────────────┘ └──────────────────────────┘
88
+ ```
89
+
90
+ ## Dependencies
91
+
92
+ Only one: `aiohttp` for async HTTP. No `rich`, no `prompt_toolkit`, no heavy TUI libs. Designed for Termux's limited environment.
93
+
94
+ ## Install from npm
95
+
96
+ ```bash
97
+ npm install -g lvtn-mobile
98
+ lvtnm login
99
+ ```
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const { spawn } = require("child_process");
4
+ const path = require("path");
5
+
6
+ const pkgRoot = path.resolve(__dirname, "..");
7
+ const mainPy = path.join(pkgRoot, "src", "lvtn_mobile", "main.py");
8
+
9
+ const args = process.argv.slice(2);
10
+ const child = spawn("python3", [mainPy, ...args], {
11
+ cwd: pkgRoot,
12
+ stdio: "inherit",
13
+ });
14
+
15
+ child.on("error", () => {
16
+ const child2 = spawn("python", [mainPy, ...args], {
17
+ cwd: pkgRoot,
18
+ stdio: "inherit",
19
+ });
20
+ child2.on("error", () => {
21
+ console.error("lvtn-mobile: Python not found. Install Python 3.9+ in Termux: pkg install python");
22
+ process.exit(1);
23
+ });
24
+ child2.on("exit", (code) => process.exit(code || 0));
25
+ });
26
+
27
+ child.on("exit", (code) => process.exit(code || 0));
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "lvtn-mobile",
3
+ "version": "1.0.0",
4
+ "description": "Leviathan Shel HaShem — Mobile Swarm Client for Termux/Android. Backend-heavy thin client with offline task scheduling, headless remote control, and full 708-agent swarm access from your phone.",
5
+ "license": "Proprietary",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/chazam41892-gif/leviathan-of-hashem.git"
9
+ },
10
+ "author": "HaChazal (Chaz Leland Hamm) <hachazal418@metanoiaunlimited.com>",
11
+ "bin": {
12
+ "lvtn-mobile": "bin/lvtn-mobile.js",
13
+ "lvtnm": "bin/lvtn-mobile.js"
14
+ },
15
+ "files": [
16
+ "bin/",
17
+ "src/",
18
+ "pyproject.toml",
19
+ "README.md"
20
+ ],
21
+ "engines": {
22
+ "node": ">=14.0.0"
23
+ },
24
+ "keywords": [
25
+ "agent",
26
+ "swarm",
27
+ "orchestration",
28
+ "mobile",
29
+ "termux",
30
+ "android",
31
+ "headless",
32
+ "background",
33
+ "scheduler",
34
+ "kabbalah",
35
+ "metanoia",
36
+ "leviathan"
37
+ ]
38
+ }
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "lvtn-mobile"
7
+ version = "1.0.0"
8
+ description = "Leviathan Shel HaShem — Mobile Swarm Client for Termux/Android. Backend-heavy thin client with offline task scheduling, headless remote control, and full 708-agent swarm access from your phone."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "Proprietary - Metanoia Unlimited LLC" }
12
+ authors = [
13
+ { name = "HaChazal (Chaz Leland Hamm)", email = "hachazal418@metanoiaunlimited.com" },
14
+ ]
15
+ keywords = ["agent", "swarm", "orchestration", "mobile", "termux", "android", "headless", "background", "scheduler"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "Operating System :: Android",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Topic :: System :: Distributed Computing",
28
+ "Topic :: Communications :: Chat",
29
+ ]
30
+
31
+ dependencies = [
32
+ "aiohttp>=3.9",
33
+ ]
34
+
35
+ [project.optional-dependencies]
36
+ notify = ["plyer>=2.1"] # Android notifications for task completion
37
+ full = ["lvtn>=3.14"] # Full desktop lvtn as fallback
38
+
39
+ [project.urls]
40
+ Homepage = "https://lvtn.metanoiaunlimited.com"
41
+
42
+ [project.scripts]
43
+ lvtn-mobile = "lvtn_mobile.main:main"
44
+ lvtnm = "lvtn_mobile.main:main"
45
+
46
+ [tool.hatch.build.targets.wheel]
47
+ packages = ["src/lvtn_mobile"]
@@ -0,0 +1,2 @@
1
+ """lvtn-mobile — Termux/Android swarm client. Backend-heavy, offline-capable, headless-ready."""
2
+ __version__ = "1.0.0"
@@ -0,0 +1,701 @@
1
+ #!/usr/bin/env python3
2
+ """lvtn-mobile — Termux/Android Swarm Client
3
+
4
+ Backend-heavy thin client for the 708-agent Leviathan swarm.
5
+ All LLM compute runs server-side. Your phone is just the remote control.
6
+
7
+ Commands:
8
+ lvtnm login Authenticate with your Leviathan key
9
+ lvtnm chat "prompt" Send a prompt to HaChazal
10
+ lvtnm fleet "task" Deploy all 7 Angels at once
11
+ lvtnm council "task" All Angels answer in parallel
12
+ lvtnm swarm <angel> "t" Deploy one Angel's swarm
13
+ lvtnm schedule "task" Queue a task for offline execution
14
+ lvtnm headless Start headless HTTP server (remote control)
15
+ lvtnm status Show swarm health + scheduled tasks
16
+ lvtnm balance Check KTRS balance
17
+ lvtnm angels List all 7 Angels
18
+ lvtnm keys Manage API keys
19
+ lvtnm remember "text" Write to HiveMind memory
20
+ lvtnm recall "query" Search HiveMind memory
21
+ lvtnm notify on|off Toggle Android notifications
22
+ lvtnm wake on|off Toggle wake lock (keep phone awake)
23
+ lvtnm boot on|off Toggle auto-start on device boot
24
+
25
+ Termux Setup (one-time):
26
+ pkg update && pkg upgrade
27
+ pkg install python termux-api termux-boot
28
+ pip install lvtn-mobile
29
+ lvtnm login
30
+
31
+ Offline Mode:
32
+ Tasks queued while offline execute automatically when
33
+ connectivity returns. Uses Termux:JobScheduler on Android 8+
34
+ or a background watcher thread.
35
+
36
+ Headless Mode:
37
+ lvtnm headless --port 9877
38
+ Then control from any device: curl http://<phone-ip>:9877/fleet -d '{"task":"..."}'
39
+ """
40
+ from __future__ import annotations
41
+
42
+ import argparse
43
+ import asyncio
44
+ import json
45
+ import os
46
+ import signal
47
+ import sys
48
+ from pathlib import Path
49
+ from typing import Optional
50
+
51
+ CONF_DIR = Path.home() / ".lvtn"
52
+ CONF_FILE = CONF_DIR / "credentials.json"
53
+ SCHEDULE_FILE = CONF_DIR / "mobile_schedule.jsonl"
54
+ CONFIG_FILE = CONF_DIR / "mobile_config.json"
55
+
56
+ DEFAULT_BASE = "https://api.leviathansi.xyz"
57
+ USER_AGENT = "lvtn-mobile/1.0"
58
+
59
+ # ── Config ──────────────────────────────────────────────────────────
60
+
61
+ def _load_config() -> dict:
62
+ if CONFIG_FILE.exists():
63
+ try:
64
+ return json.loads(CONFIG_FILE.read_text())
65
+ except Exception:
66
+ pass
67
+ return {"notify": True, "wake_lock": False, "boot_start": False}
68
+
69
+ def _save_config(cfg: dict):
70
+ CONF_DIR.mkdir(parents=True, exist_ok=True)
71
+ CONFIG_FILE.write_text(json.dumps(cfg, indent=2))
72
+
73
+ def _load_key() -> Optional[str]:
74
+ if CONF_FILE.exists():
75
+ try:
76
+ return json.loads(CONF_FILE.read_text()).get("api_key")
77
+ except Exception:
78
+ pass
79
+ return None
80
+
81
+ def _save_key(key: str):
82
+ CONF_DIR.mkdir(parents=True, exist_ok=True)
83
+ CONF_FILE.write_text(json.dumps({"api_key": key, "base": DEFAULT_BASE}))
84
+
85
+ # ── HTTP Client (aiohttp, async) ───────────────────────────────────
86
+
87
+ async def _api(method: str, path: str, key: Optional[str] = None,
88
+ body: Optional[dict] = None, timeout: float = 900.0) -> tuple[int, dict]:
89
+ import aiohttp
90
+ url = f"{DEFAULT_BASE}{path}"
91
+ headers = {"Content-Type": "application/json", "User-Agent": USER_AGENT}
92
+ if key:
93
+ headers["X-Leviathan-Key"] = key
94
+ try:
95
+ async with aiohttp.ClientSession() as session:
96
+ async with session.request(method, url, json=body, headers=headers,
97
+ timeout=aiohttp.ClientTimeout(total=timeout)) as resp:
98
+ data = await resp.json()
99
+ return resp.status, data
100
+ except aiohttp.ClientError as e:
101
+ return 0, {"detail": str(e)}
102
+ except asyncio.TimeoutError:
103
+ return 0, {"detail": "Request timed out"}
104
+
105
+ # ── Commands ─────────────────────────────────────────────────────────
106
+
107
+ async def cmd_login(args):
108
+ key = input("Leviathan API key: ").strip()
109
+ if not key:
110
+ print("No key provided.")
111
+ return 1
112
+ status, data = await _api("GET", "/api/llm/me", key=key)
113
+ if status == 200:
114
+ _save_key(key)
115
+ print(f" Logged in as {data.get('email', 'unknown')}")
116
+ print(f" Balance: ${data.get('balance_usd', 0)} KTRS")
117
+ return 0
118
+ print(f" Login failed (HTTP {status}): {data.get('detail', 'unknown')}")
119
+ return 1
120
+
121
+ async def cmd_balance(args):
122
+ key = _load_key()
123
+ if not key:
124
+ print("Not logged in. Run: lvtnm login")
125
+ return 1
126
+ status, data = await _api("GET", "/api/llm/me", key=key)
127
+ if status == 200:
128
+ print(f" ${data.get('balance_usd', 0)} KTRS")
129
+ return 0
130
+ print(f" Failed: {data.get('detail')}")
131
+ return 1
132
+
133
+ async def cmd_angels(args):
134
+ status, data = await _api("GET", "/api/llm/angels")
135
+ if status != 200:
136
+ print(f" Failed: {data.get('detail')}")
137
+ return 1
138
+ for a in data.get("angels", []):
139
+ print(f" {a['name']:<16} {a['sefirah']:<10} {a['title']}")
140
+ return 0
141
+
142
+ async def cmd_chat(args):
143
+ key = _load_key()
144
+ if not key:
145
+ print("Not logged in. Run: lvtnm login")
146
+ return 1
147
+ prompt = " ".join(args.prompt)
148
+ print(f" HaChazal is thinking...")
149
+ status, data = await _api("POST", "/api/llm/chat", key=key,
150
+ body={"messages": [{"role": "user", "content": prompt}]})
151
+ if status == 200:
152
+ print(f"\n{data.get('text', '')}\n")
153
+ print(f" [${data.get('charged_usd', 0)} charged · ${data.get('balance_usd', 0)} KTRS]")
154
+ return 0
155
+ print(f" Failed (HTTP {status}): {data.get('detail')}")
156
+ return 1
157
+
158
+ async def cmd_fleet(args):
159
+ key = _load_key()
160
+ if not key:
161
+ print("Not logged in.")
162
+ return 1
163
+ prompt = " ".join(args.prompt)
164
+ per_angel = getattr(args, 'per_angel', 2)
165
+ print(f" Fleet deploying — 7 Angels x {per_angel} agents...")
166
+ status, data = await _api("POST", "/api/llm/fleet", key=key,
167
+ body={"prompt": prompt, "per_angel": per_angel})
168
+ if status == 200:
169
+ for a in data.get("council", []):
170
+ print(f"\n --- {a.get('angel')} ({a.get('sefirah')}) ---")
171
+ for o in a.get("outputs", []):
172
+ print(f" {o.get('text', '').strip()[:500]}")
173
+ print(f"\n [${data.get('charged_usd', 0)} charged · ${data.get('balance_usd', 0)} KTRS]")
174
+ return 0
175
+ print(f" Failed (HTTP {status}): {data.get('detail')}")
176
+ return 1
177
+
178
+ async def cmd_council(args):
179
+ key = _load_key()
180
+ if not key:
181
+ print("Not logged in.")
182
+ return 1
183
+ prompt = " ".join(args.prompt)
184
+ per_angel = getattr(args, 'per_angel', 2)
185
+ print(f" Council convening — {per_angel} agents per Angel...")
186
+ status, data = await _api("POST", "/api/llm/council", key=key,
187
+ body={"prompt": prompt, "agents_per_angel": per_angel})
188
+ if status == 200:
189
+ for a in data.get("council", []):
190
+ print(f"\n --- {a.get('angel')} ({a.get('sefirah')}) ---")
191
+ for o in a.get("outputs", []):
192
+ print(f" {o.get('text', '').strip()[:500]}")
193
+ print(f"\n [${data.get('charged_usd', 0)} charged · ${data.get('balance_usd', 0)} KTRS]")
194
+ return 0
195
+ print(f" Failed (HTTP {status}): {data.get('detail')}")
196
+ return 1
197
+
198
+ async def cmd_swarm(args):
199
+ key = _load_key()
200
+ if not key:
201
+ print("Not logged in.")
202
+ return 1
203
+ prompt = " ".join(args.prompt)
204
+ agents = getattr(args, 'agents', 2)
205
+ print(f" {args.angel}'s swarm — {agents} agents...")
206
+ status, data = await _api("POST", "/api/llm/swarm", key=key,
207
+ body={"angel": args.angel, "prompt": prompt, "agents": agents, "tools": True})
208
+ if status == 200:
209
+ for i, o in enumerate(data.get("outputs", []), 1):
210
+ print(f"\n --- agent {i} ---")
211
+ print(f" {o.get('text', '').strip()[:500]}")
212
+ print(f"\n [${data.get('charged_usd', 0)} charged · ${data.get('balance_usd', 0)} KTRS]")
213
+ return 0
214
+ print(f" Failed (HTTP {status}): {data.get('detail')}")
215
+ return 1
216
+
217
+ async def cmd_remember(args):
218
+ key = _load_key()
219
+ if not key:
220
+ print("Not logged in.")
221
+ return 1
222
+ body = " ".join(args.body)
223
+ status, data = await _api("POST", "/api/llm/memory/remember", key=key,
224
+ body={"body": body, "agent": "mobile", "kind": "note"})
225
+ if status == 200:
226
+ print(f" Remembered.")
227
+ return 0
228
+ print(f" Failed: {data.get('detail')}")
229
+ return 1
230
+
231
+ async def cmd_recall(args):
232
+ key = _load_key()
233
+ if not key:
234
+ print("Not logged in.")
235
+ return 1
236
+ query = " ".join(args.query)
237
+ from urllib.parse import quote
238
+ status, data = await _api("GET", f"/api/llm/memory/recall?query={quote(query)}&agent=mobile", key=key)
239
+ if status == 200:
240
+ for m in data.get("memories", [])[:5]:
241
+ print(f" [{m.get('ts', '?')}] {m.get('body', '')[:200]}")
242
+ return 0
243
+ print(f" Failed: {data.get('detail')}")
244
+ return 1
245
+
246
+ # ── Offline Scheduler ────────────────────────────────────────────────
247
+
248
+ async def cmd_schedule(args):
249
+ """Queue a task for execution. Runs immediately if online, queues if offline."""
250
+ task = " ".join(args.task)
251
+ cmd_type = getattr(args, 'type', 'chat')
252
+ entry = {
253
+ "ts": __import__("time").strftime("%Y-%m-%d %H:%M:%S"),
254
+ "type": cmd_type,
255
+ "task": task,
256
+ "status": "queued",
257
+ }
258
+ CONF_DIR.mkdir(parents=True, exist_ok=True)
259
+ with open(SCHEDULE_FILE, "a") as f:
260
+ f.write(json.dumps(entry) + "\n")
261
+ print(f" Queued: [{cmd_type}] {task[:80]}")
262
+
263
+ # Try executing immediately
264
+ key = _load_key()
265
+ if key:
266
+ try:
267
+ if cmd_type == "chat":
268
+ status, data = await _api("POST", "/api/llm/chat", key=key,
269
+ body={"messages": [{"role": "user", "content": task}]})
270
+ elif cmd_type == "fleet":
271
+ status, data = await _api("POST", "/api/llm/fleet", key=key,
272
+ body={"prompt": task, "per_angel": 2})
273
+ else:
274
+ status, data = await _api("POST", "/api/llm/chat", key=key,
275
+ body={"messages": [{"role": "user", "content": task}]})
276
+ if status == 200:
277
+ entry["status"] = "done"
278
+ entry["result"] = data.get("text", "")[:500]
279
+ print(f" Executed immediately.")
280
+ _notify(f"Task complete: {task[:50]}...")
281
+ else:
282
+ entry["status"] = "failed"
283
+ entry["error"] = data.get("detail", f"HTTP {status}")
284
+ print(f" Queued for retry (offline).")
285
+ except Exception as e:
286
+ entry["status"] = "queued"
287
+ print(f" Queued for retry ({e}).")
288
+ return 0
289
+
290
+ async def cmd_schedule_list(args):
291
+ if not SCHEDULE_FILE.exists():
292
+ print(" No scheduled tasks.")
293
+ return 0
294
+ tasks = []
295
+ with open(SCHEDULE_FILE) as f:
296
+ for line in f:
297
+ if line.strip():
298
+ tasks.append(json.loads(line))
299
+ for t in tasks[-20:]:
300
+ status = t.get("status", "?")
301
+ icon = {"done": "+", "failed": "!", "queued": "~"}.get(status, "?")
302
+ print(f" [{icon}] {t.get('ts')} [{t.get('type')}] {t.get('task', '')[:60]}")
303
+ return 0
304
+
305
+ async def cmd_schedule_run(args):
306
+ """Execute all queued tasks."""
307
+ if not SCHEDULE_FILE.exists():
308
+ print(" No queued tasks.")
309
+ return 0
310
+ key = _load_key()
311
+ if not key:
312
+ print(" Not logged in — cannot execute queued tasks.")
313
+ return 1
314
+ tasks = []
315
+ with open(SCHEDULE_FILE) as f:
316
+ for line in f:
317
+ if line.strip():
318
+ tasks.append(json.loads(line))
319
+ executed = 0
320
+ for t in tasks:
321
+ if t.get("status") != "queued":
322
+ continue
323
+ print(f" Executing: {t.get('task', '')[:60]}...")
324
+ try:
325
+ status, data = await _api("POST", "/api/llm/chat", key=key,
326
+ body={"messages": [{"role": "user", "content": t["task"]}]})
327
+ if status == 200:
328
+ t["status"] = "done"
329
+ t["result"] = data.get("text", "")[:500]
330
+ executed += 1
331
+ else:
332
+ t["status"] = "failed"
333
+ t["error"] = data.get("detail", f"HTTP {status}")
334
+ except Exception as e:
335
+ t["status"] = "failed"
336
+ t["error"] = str(e)
337
+ # Rewrite file
338
+ with open(SCHEDULE_FILE, "w") as f:
339
+ for t in tasks:
340
+ f.write(json.dumps(t) + "\n")
341
+ print(f" Executed {executed} tasks.")
342
+ if executed > 0:
343
+ _notify(f"{executed} scheduled tasks completed")
344
+ return 0
345
+
346
+ # ── Headless HTTP Server ─────────────────────────────────────────────
347
+
348
+ async def cmd_headless(args):
349
+ """Start a lightweight HTTP server for remote control.
350
+
351
+ Other devices on the same network can control the swarm:
352
+ curl http://<phone-ip>:9877/chat -d '{"prompt":"hello"}'
353
+ curl http://<phone-ip>:9877/fleet -d '{"task":"research X"}'
354
+ curl http://<phone-ip>:9877/status
355
+ """
356
+ from aiohttp import web
357
+
358
+ key = _load_key()
359
+ if not key:
360
+ print("Not logged in. Run: lvtnm login")
361
+ return 1
362
+
363
+ port = getattr(args, 'port', 9877)
364
+
365
+ async def handle_chat(request):
366
+ try:
367
+ body = await request.json()
368
+ prompt = body.get("prompt", "")
369
+ status, data = await _api("POST", "/api/llm/chat", key=key,
370
+ body={"messages": [{"role": "user", "content": prompt}]})
371
+ return web.json_response(data)
372
+ except Exception as e:
373
+ return web.json_response({"error": str(e)}, status=500)
374
+
375
+ async def handle_fleet(request):
376
+ try:
377
+ body = await request.json()
378
+ task = body.get("task", "")
379
+ per_angel = body.get("per_angel", 2)
380
+ status, data = await _api("POST", "/api/llm/fleet", key=key,
381
+ body={"prompt": task, "per_angel": per_angel})
382
+ return web.json_response(data)
383
+ except Exception as e:
384
+ return web.json_response({"error": str(e)}, status=500)
385
+
386
+ async def handle_council(request):
387
+ try:
388
+ body = await request.json()
389
+ task = body.get("task", "")
390
+ per_angel = body.get("per_angel", 2)
391
+ status, data = await _api("POST", "/api/llm/council", key=key,
392
+ body={"prompt": task, "agents_per_angel": per_angel})
393
+ return web.json_response(data)
394
+ except Exception as e:
395
+ return web.json_response({"error": str(e)}, status=500)
396
+
397
+ async def handle_status(request):
398
+ s, d = await _api("GET", "/api/llm/me", key=key)
399
+ scheduled = 0
400
+ if SCHEDULE_FILE.exists():
401
+ scheduled = sum(1 for _ in open(SCHEDULE_FILE))
402
+ return web.json_response({
403
+ "balance": d.get("balance_usd", 0),
404
+ "email": d.get("email", ""),
405
+ "scheduled_tasks": scheduled,
406
+ "mode": "headless",
407
+ })
408
+
409
+ async def handle_schedule(request):
410
+ try:
411
+ body = await request.json()
412
+ task = body.get("task", "")
413
+ cmd_type = body.get("type", "chat")
414
+ entry = {
415
+ "ts": __import__("time").strftime("%Y-%m-%d %H:%M:%S"),
416
+ "type": cmd_type,
417
+ "task": task,
418
+ "status": "queued",
419
+ }
420
+ CONF_DIR.mkdir(parents=True, exist_ok=True)
421
+ with open(SCHEDULE_FILE, "a") as f:
422
+ f.write(json.dumps(entry) + "\n")
423
+ return web.json_response({"ok": True, "queued": task[:80]})
424
+ except Exception as e:
425
+ return web.json_response({"error": str(e)}, status=500)
426
+
427
+ app = web.Application()
428
+ app.router.add_post("/chat", handle_chat)
429
+ app.router.add_post("/fleet", handle_fleet)
430
+ app.router.add_post("/council", handle_council)
431
+ app.router.add_post("/schedule", handle_schedule)
432
+ app.router.add_get("/status", handle_status)
433
+
434
+ # Get local IP
435
+ import socket
436
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
437
+ try:
438
+ s.connect(("8.8.8.8", 80))
439
+ local_ip = s.getsockname()[0]
440
+ except Exception:
441
+ local_ip = "127.0.0.1"
442
+ finally:
443
+ s.close()
444
+
445
+ print(f"\n Headless server on http://{local_ip}:{port}")
446
+ print(f" Endpoints:")
447
+ print(f" POST /chat {{\"prompt\": \"...\"}}")
448
+ print(f" POST /fleet {{\"task\": \"...\", \"per_angel\": 2}}")
449
+ print(f" POST /council {{\"task\": \"...\", \"per_angel\": 2}}")
450
+ print(f" POST /schedule {{\"task\": \"...\", \"type\": \"chat\"}}")
451
+ print(f" GET /status")
452
+ print(f"\n Press Ctrl+C to stop.")
453
+
454
+ runner = web.AppRunner(app)
455
+ await runner.setup()
456
+ site = web.TCPSite(runner, "0.0.0.0", port)
457
+ await site.start()
458
+
459
+ # Keep running
460
+ stop = asyncio.Event()
461
+ try:
462
+ await stop.wait()
463
+ except asyncio.CancelledError:
464
+ pass
465
+
466
+ # ── Notifications ────────────────────────────────────────────────────
467
+
468
+ def _notify(msg: str):
469
+ """Send Android notification via Termux:API."""
470
+ cfg = _load_config()
471
+ if not cfg.get("notify", True):
472
+ return
473
+ try:
474
+ import subprocess
475
+ subprocess.run(["termux-notification", "--title", "lvtn-mobile",
476
+ "--content", msg, "--priority", "default"],
477
+ capture_output=True, timeout=5)
478
+ except Exception:
479
+ pass # termux-api not installed — silent fail
480
+
481
+ async def cmd_notify(args):
482
+ cfg = _load_config()
483
+ state = args.state.lower()
484
+ if state == "on":
485
+ cfg["notify"] = True
486
+ print(" Notifications: ON")
487
+ elif state == "off":
488
+ cfg["notify"] = False
489
+ print(" Notifications: OFF")
490
+ else:
491
+ print(f" Notifications: {'ON' if cfg.get('notify', True) else 'OFF'}")
492
+ _save_config(cfg)
493
+ return 0
494
+
495
+ async def cmd_wake(args):
496
+ """Toggle wake lock via Termux:API."""
497
+ cfg = _load_config()
498
+ state = args.state.lower()
499
+ if state == "on":
500
+ cfg["wake_lock"] = True
501
+ try:
502
+ import subprocess
503
+ subprocess.run(["termux-wake-lock", "acquire"], capture_output=True, timeout=5)
504
+ except Exception:
505
+ pass
506
+ print(" Wake lock: ON (phone won't sleep)")
507
+ elif state == "off":
508
+ cfg["wake_lock"] = False
509
+ try:
510
+ import subprocess
511
+ subprocess.run(["termux-wake-lock", "release"], capture_output=True, timeout=5)
512
+ except Exception:
513
+ pass
514
+ print(" Wake lock: OFF")
515
+ else:
516
+ print(f" Wake lock: {'ON' if cfg.get('wake_lock', False) else 'OFF'}")
517
+ _save_config(cfg)
518
+ return 0
519
+
520
+ async def cmd_boot(args):
521
+ """Toggle auto-start on device boot via Termux:Boot."""
522
+ cfg = _load_config()
523
+ state = args.state.lower()
524
+ boot_script = Path.home() / ".termux" / "boot" / "lvtn-mobile"
525
+ if state == "on":
526
+ cfg["boot_start"] = True
527
+ boot_script.parent.mkdir(parents=True, exist_ok=True)
528
+ boot_script.write_text("#!/data/data/com.termux/files/usr/bin/bash\nlvtnm headless --port 9877 &\n")
529
+ boot_script.chmod(0o755)
530
+ print(" Boot start: ON (headless server starts on device boot)")
531
+ elif state == "off":
532
+ cfg["boot_start"] = False
533
+ if boot_script.exists():
534
+ boot_script.unlink()
535
+ print(" Boot start: OFF")
536
+ else:
537
+ print(f" Boot start: {'ON' if cfg.get('boot_start', False) else 'OFF'}")
538
+ _save_config(cfg)
539
+ return 0
540
+
541
+ async def cmd_status(args):
542
+ """Show swarm health + local state."""
543
+ key = _load_key()
544
+ print(f" lvtn-mobile v1.0.0")
545
+ if key:
546
+ status, data = await _api("GET", "/api/llm/me", key=key)
547
+ if status == 200:
548
+ print(f" Balance: ${data.get('balance_usd', 0)} KTRS")
549
+ print(f" Email: {data.get('email', 'unknown')}")
550
+ else:
551
+ print(f" API: unreachable")
552
+ else:
553
+ print(f" Status: not logged in")
554
+
555
+ cfg = _load_config()
556
+ print(f" Notify: {'ON' if cfg.get('notify', True) else 'OFF'}")
557
+ print(f" Wake: {'ON' if cfg.get('wake_lock', False) else 'OFF'}")
558
+ print(f" Boot: {'ON' if cfg.get('boot_start', False) else 'OFF'}")
559
+
560
+ if SCHEDULE_FILE.exists():
561
+ tasks = [json.loads(l) for l in open(SCHEDULE_FILE) if l.strip()]
562
+ queued = sum(1 for t in tasks if t.get("status") == "queued")
563
+ done = sum(1 for t in tasks if t.get("status") == "done")
564
+ failed = sum(1 for t in tasks if t.get("status") == "failed")
565
+ print(f" Tasks: {len(tasks)} total ({queued} queued, {done} done, {failed} failed)")
566
+ else:
567
+ print(f" Tasks: none scheduled")
568
+ return 0
569
+
570
+ # ── Background Watcher (offline task executor) ──────────────────────
571
+
572
+ async def _background_watcher():
573
+ """Watch for connectivity and execute queued tasks."""
574
+ import time as _time
575
+ while True:
576
+ await asyncio.sleep(30)
577
+ if not SCHEDULE_FILE.exists():
578
+ continue
579
+ key = _load_key()
580
+ if not key:
581
+ continue
582
+ # Check connectivity
583
+ try:
584
+ status, _ = await _api("GET", "/api/llm/angels", timeout=10)
585
+ if status != 200:
586
+ continue
587
+ except Exception:
588
+ continue
589
+ # Execute queued tasks
590
+ tasks = []
591
+ with open(SCHEDULE_FILE) as f:
592
+ for line in f:
593
+ if line.strip():
594
+ tasks.append(json.loads(line))
595
+ changed = False
596
+ for t in tasks:
597
+ if t.get("status") != "queued":
598
+ continue
599
+ try:
600
+ status, data = await _api("POST", "/api/llm/chat", key=key,
601
+ body={"messages": [{"role": "user", "content": t["task"]}]})
602
+ if status == 200:
603
+ t["status"] = "done"
604
+ t["result"] = data.get("text", "")[:500]
605
+ changed = True
606
+ _notify(f"Task done: {t['task'][:50]}...")
607
+ else:
608
+ t["status"] = "failed"
609
+ t["error"] = data.get("detail", f"HTTP {status}")
610
+ changed = True
611
+ except Exception:
612
+ break # stop on first failure, retry next cycle
613
+ if changed:
614
+ with open(SCHEDULE_FILE, "w") as f:
615
+ for t in tasks:
616
+ f.write(json.dumps(t) + "\n")
617
+
618
+ # ── Main ─────────────────────────────────────────────────────────────
619
+
620
+ def main():
621
+ parser = argparse.ArgumentParser(description="lvtn-mobile — Termux/Android Swarm Client")
622
+ sub = parser.add_subparsers(dest="command")
623
+
624
+ sub.add_parser("login", help="Authenticate with Leviathan key")
625
+ sub.add_parser("balance", help="Check KTRS balance")
626
+ sub.add_parser("angels", help="List all 7 Angels")
627
+ sub.add_parser("status", help="Show swarm health + local state")
628
+
629
+ p = sub.add_parser("chat", help="Send prompt to HaChazal")
630
+ p.add_argument("prompt", nargs="+", help="Your prompt")
631
+
632
+ p = sub.add_parser("fleet", help="Deploy all 7 Angels")
633
+ p.add_argument("prompt", nargs="+", help="Task for the fleet")
634
+ p.add_argument("--per-angel", type=int, default=2, help="Agents per Angel")
635
+
636
+ p = sub.add_parser("council", help="All Angels answer in parallel")
637
+ p.add_argument("prompt", nargs="+", help="Task for the council")
638
+ p.add_argument("--per-angel", type=int, default=2, help="Agents per Angel")
639
+
640
+ p = sub.add_parser("swarm", help="Deploy one Angel's swarm")
641
+ p.add_argument("angel", help="Angel ID (zadkiel, gavriel, etc.)")
642
+ p.add_argument("prompt", nargs="+", help="Task for the swarm")
643
+ p.add_argument("--agents", type=int, default=2, help="Number of agents")
644
+
645
+ p = sub.add_parser("schedule", help="Queue task for offline execution")
646
+ p.add_argument("task", nargs="+", help="Task to queue")
647
+ p.add_argument("--type", default="chat", choices=["chat", "fleet", "council"])
648
+
649
+ sub.add_parser("schedule-list", help="List scheduled tasks")
650
+ sub.add_parser("schedule-run", help="Execute all queued tasks now")
651
+
652
+ p = sub.add_parser("headless", help="Start headless HTTP server")
653
+ p.add_argument("--port", type=int, default=9877, help="Port to listen on")
654
+
655
+ p = sub.add_parser("remember", help="Write to HiveMind memory")
656
+ p.add_argument("body", nargs="+", help="Memory content")
657
+
658
+ p = sub.add_parser("recall", help="Search HiveMind memory")
659
+ p.add_argument("query", nargs="+", help="Search query")
660
+
661
+ p = sub.add_parser("notify", help="Toggle Android notifications")
662
+ p.add_argument("state", nargs="?", default="", help="on|off")
663
+
664
+ p = sub.add_parser("wake", help="Toggle wake lock")
665
+ p.add_argument("state", nargs="?", default="", help="on|off")
666
+
667
+ p = sub.add_parser("boot", help="Toggle auto-start on boot")
668
+ p.add_argument("state", nargs="?", default="", help="on|off")
669
+
670
+ args = parser.parse_args()
671
+ if not args.command:
672
+ parser.print_help()
673
+ return 1
674
+
675
+ cmds = {
676
+ "login": cmd_login,
677
+ "balance": cmd_balance,
678
+ "angels": cmd_angels,
679
+ "status": cmd_status,
680
+ "chat": cmd_chat,
681
+ "fleet": cmd_fleet,
682
+ "council": cmd_council,
683
+ "swarm": cmd_swarm,
684
+ "schedule": cmd_schedule,
685
+ "schedule-list": cmd_schedule_list,
686
+ "schedule-run": cmd_schedule_run,
687
+ "headless": cmd_headless,
688
+ "remember": cmd_remember,
689
+ "recall": cmd_recall,
690
+ "notify": cmd_notify,
691
+ "wake": cmd_wake,
692
+ "boot": cmd_boot,
693
+ }
694
+
695
+ fn = cmds.get(args.command)
696
+ if fn:
697
+ return asyncio.run(fn(args))
698
+ return 1
699
+
700
+ if __name__ == "__main__":
701
+ sys.exit(main())