Mona commited on
Commit ·
17d402e
1
Parent(s): 5b137c2
feat: add CLI with login, gui, start, status, and help commands
Browse filesCommand-line interface with five commands:
- login: interactive API key verification and secure storage
- gui: full terminal dashboard with live metrics
- start: headless daemon mode with console logging
- status: display connection state and config paths
- help: usage guide with environment variables
Auto-detection: GUI if TTY, headless otherwise when no command
is specified. Graceful shutdown on SIGINT/SIGTERM.
Removed deprecated GUI server and telemetry files from v0.1.0.
- README.md +155 -37
- bin/mona-agent.js +150 -36
- install.sh +49 -22
- src/gui/index.html +0 -146
- src/gui/server.js +0 -61
- src/telemetry.js +0 -67
README.md
CHANGED
|
@@ -1,63 +1,181 @@
|
|
| 1 |
-
#
|
| 2 |
|
| 3 |
-
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
-
|
| 9 |
-
|
|
|
|
| 10 |
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
```bash
|
|
|
|
| 14 |
curl -fsSL https://agent.mona.expert/install.sh | bash
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
```
|
| 16 |
|
| 17 |
-
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
```
|
| 23 |
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
```bash
|
| 27 |
-
npm
|
| 28 |
```
|
| 29 |
|
| 30 |
-
|
|
|
|
|
|
|
| 31 |
|
| 32 |
```
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
│ device metrics + agent steps (WebSocket) ▲
|
| 39 |
-
└────────────────────────────────────────────┘
|
| 40 |
```
|
| 41 |
|
| 42 |
-
|
| 43 |
-
- **`src/cloud.js`** — sends messages to the cloud brain and streams reasoning back.
|
| 44 |
-
- **`src/telemetry.js`** — WebSocket stream of device metrics + agent steps to the domain.
|
| 45 |
-
- **`src/agent.js`** — the local run loop (remote reasoning, local execution + telemetry).
|
| 46 |
-
- **`src/gui/`** — the dashboard.
|
| 47 |
|
| 48 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|---|---|
|
| 52 |
-
| `mona-agent login` | Save your API key |
|
| 53 |
-
| `mona-agent status` | Show login state |
|
| 54 |
-
| `mona-agent run "<task>"` | Run a task (reasoning in the cloud) |
|
| 55 |
-
| `npm run gui` | Start the GUI at `http://localhost:4319` |
|
| 56 |
|
| 57 |
-
##
|
| 58 |
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
## License
|
| 62 |
|
| 63 |
-
MIT
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mona-agent — Cloud-Brained AI Agent for Your Device
|
| 2 |
|
| 3 |
+
<p align="center">
|
| 4 |
+
<strong>Open-source device agent. Free forever. MIT licensed.</strong>
|
| 5 |
+
</p>
|
| 6 |
|
| 7 |
+
<p align="center">
|
| 8 |
+
<a href="https://github.com/MONAEXPERT/agent/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
|
| 9 |
+
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg" alt="Node.js"></a>
|
| 10 |
+
<img src="https://img.shields.io/badge/dependencies-1-lightgrey" alt="1 dependency">
|
| 11 |
+
<img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20WSL2-informational" alt="Platforms">
|
| 12 |
+
</p>
|
| 13 |
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
## What is this?
|
| 17 |
+
|
| 18 |
+
**mona-agent** is a headless daemon that runs on any device. It connects to a control plane, receives commands, executes local tools, and streams results back.
|
| 19 |
+
|
| 20 |
+
No LLM provider keys (OpenAI, Anthropic, Google, etc.) are stored on your device. Reasoning happens in the cloud — the device is a smart terminal.
|
| 21 |
+
|
| 22 |
+
```
|
| 23 |
+
Your Device Control Plane (SaaS or self-hosted)
|
| 24 |
+
+-------------------+ +----------------------------+
|
| 25 |
+
| mona-agent |--- WSS ------+ | Dashboard / Website |
|
| 26 |
+
| | | Auth / User Management |
|
| 27 |
+
| * Terminal GUI |<-- commands -- | API Key Vault (AES-256) |
|
| 28 |
+
| * Local tools |-- telemetry -+ | LLM Proxy (5 providers) |
|
| 29 |
+
| * File sandbox |-- tokens ----+ | Agent Orchestration |
|
| 30 |
+
| * Shell guard |-- metrics ---+ | Audit Log |
|
| 31 |
+
| | | |
|
| 32 |
+
| <- NO API KEYS ->| | <-- YOUR KEYS STAY HERE --> |
|
| 33 |
+
+-------------------+ +----------------------------+
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
## Quick Start
|
| 37 |
|
| 38 |
```bash
|
| 39 |
+
# Install
|
| 40 |
curl -fsSL https://agent.mona.expert/install.sh | bash
|
| 41 |
+
|
| 42 |
+
# Login to your control plane
|
| 43 |
+
mona-agent login # paste your API key
|
| 44 |
+
|
| 45 |
+
# Run
|
| 46 |
+
mona-agent gui # terminal dashboard with live metrics
|
| 47 |
+
mona-agent start # headless daemon (no UI)
|
| 48 |
```
|
| 49 |
|
| 50 |
+
## Terminal Dashboard
|
| 51 |
|
| 52 |
+
Built-in TUI — zero extra dependencies. Pure ANSI escape codes.
|
| 53 |
+
|
| 54 |
+
```
|
| 55 |
+
+-- mona-agent v1.0.0 ------------------------- * connected ---+
|
| 56 |
+
| +-- System -------------------+ +-- Activity ---------------+ |
|
| 57 |
+
| | Host MacBook-Pro | | 22:54 * Connected | |
|
| 58 |
+
| | OS darwin arm64 | | 22:55 > Task: "sys info" | |
|
| 59 |
+
| | CPUs 10 cores | | 22:55 ~ Thinking... | |
|
| 60 |
+
| | Mem [#### ] 62% | | 22:56 + Done (142 tok) | |
|
| 61 |
+
| +-- Task ---------------------+ | | |
|
| 62 |
+
| | + Idle | | | |
|
| 63 |
+
| +-----------------------------+ +---------------------------+ |
|
| 64 |
+
| q quit · c clear · arrows scroll |
|
| 65 |
+
+----------------------------------------------------------------+
|
| 66 |
```
|
| 67 |
|
| 68 |
+
## Tools
|
| 69 |
+
|
| 70 |
+
The agent ships with four sandboxed tool modules:
|
| 71 |
+
|
| 72 |
+
| Tool | Capabilities | Security |
|
| 73 |
+
|------|-------------|----------|
|
| 74 |
+
| `sysinfo` | OS, CPU, memory, load, network, uptime | Read-only |
|
| 75 |
+
| `shell` | Command execution | Allowlist + blocked patterns |
|
| 76 |
+
| `files` | Read/write/list/delete/stat | Path sandboxed |
|
| 77 |
+
| `net` | HTTP fetch/check, connectivity | HTTP(S)-only |
|
| 78 |
+
|
| 79 |
+
### Shell Security
|
| 80 |
+
|
| 81 |
+
By default only safe commands are allowed. Extend via environment:
|
| 82 |
|
| 83 |
```bash
|
| 84 |
+
MONA_ALLOW_CMDS="df,uptime,uname,git,npm,docker" mona-agent start
|
| 85 |
```
|
| 86 |
|
| 87 |
+
Destructive patterns (`rm -rf /`, `mkfs`, fork bombs) are always blocked.
|
| 88 |
+
|
| 89 |
+
## Commands
|
| 90 |
|
| 91 |
```
|
| 92 |
+
mona-agent gui Terminal dashboard with live metrics
|
| 93 |
+
mona-agent start Headless daemon — no UI, log to stderr
|
| 94 |
+
mona-agent login Save your control-plane API key
|
| 95 |
+
mona-agent status Show login state and config paths
|
| 96 |
+
mona-agent help Show all commands and environment vars
|
|
|
|
|
|
|
| 97 |
```
|
| 98 |
|
| 99 |
+
Without arguments, auto-detects: GUI if terminal, headless otherwise.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
+
## Environment Variables
|
| 102 |
+
|
| 103 |
+
| Variable | Default | Purpose |
|
| 104 |
+
|----------|---------|---------|
|
| 105 |
+
| `MONA_CLOUD` | `https://agent.mona.expert` | Control plane URL |
|
| 106 |
+
| `MONA_CLOUD_WS` | Auto-derived | WebSocket URL override |
|
| 107 |
+
| `MONA_ALLOW_CMDS` | `df,uptime,uname,...` | Shell command allowlist |
|
| 108 |
+
| `MONA_SHELL_UNSAFE` | — | Set to `1` for unrestricted shell |
|
| 109 |
+
| `MONA_WORKSPACE` | `~/.mona-agent/workspace` | File tool sandbox directory |
|
| 110 |
+
|
| 111 |
+
## Project Structure
|
| 112 |
+
|
| 113 |
+
```
|
| 114 |
+
agent/
|
| 115 |
+
+-- bin/mona-agent.js CLI entry point
|
| 116 |
+
+-- src/
|
| 117 |
+
| +-- agent.js Agent daemon (reasoning loop)
|
| 118 |
+
| +-- cloud.js Cloud API client (SSE streaming)
|
| 119 |
+
| +-- config.js Configuration & credential management
|
| 120 |
+
| +-- control.js WebSocket control channel (reconnect, metrics)
|
| 121 |
+
| +-- log.js Structured event-driven logger
|
| 122 |
+
| +-- tui.js Terminal dashboard (pure ANSI)
|
| 123 |
+
| +-- tools/
|
| 124 |
+
| +-- index.js Tool registry & dispatcher
|
| 125 |
+
| +-- sysinfo.js System information
|
| 126 |
+
| +-- shell.js Sandboxed shell execution
|
| 127 |
+
| +-- files.js File system operations
|
| 128 |
+
| +-- net.js Network operations
|
| 129 |
+
+-- test/
|
| 130 |
+
| +-- agent.test.mjs 14 unit tests
|
| 131 |
+
+-- install.sh One-line install script
|
| 132 |
+
+-- README.md
|
| 133 |
+
+-- LICENSE
|
| 134 |
+
+-- package.json
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
## Architecture
|
| 138 |
+
|
| 139 |
+
**Cloud-brained**: Reasoning happens on the control plane where API keys live. The device executes tools and streams results. The device never touches LLM provider credentials.
|
| 140 |
+
|
| 141 |
+
- **Commands** (`run`, `tool`, `ping`) flow: control plane -> cloud -> device
|
| 142 |
+
- **Telemetry** (`metrics`, `steps`, `tokens`, `results`) flow: device -> cloud -> control plane
|
| 143 |
+
- **API keys** never leave the control plane
|
| 144 |
|
| 145 |
+
The device stores **only** the control-plane API key in `~/.mona-agent/credentials.json` (mode `0600`).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
+
## Open Source & SaaS
|
| 148 |
|
| 149 |
+
| What | Where | Price |
|
| 150 |
+
|------|-------|-------|
|
| 151 |
+
| **Device agent** | This repo — install, modify, fork | Free & open source (MIT) |
|
| 152 |
+
| **Control plane** | Self-host or SaaS at [agent.mona.expert](https://agent.mona.expert) | Free tier available |
|
| 153 |
+
|
| 154 |
+
The agent is MIT. Use it with any control plane. The cloud platform at agent.mona.expert provides the managed experience with premium plans for teams.
|
| 155 |
+
|
| 156 |
+
## Development
|
| 157 |
+
|
| 158 |
+
```bash
|
| 159 |
+
git clone https://github.com/MONAEXPERT/agent.git
|
| 160 |
+
cd agent
|
| 161 |
+
npm install
|
| 162 |
+
npm test # 14 tests, all passing
|
| 163 |
+
```
|
| 164 |
|
| 165 |
## License
|
| 166 |
|
| 167 |
+
MIT — free forever.
|
| 168 |
+
|
| 169 |
+
---
|
| 170 |
+
|
| 171 |
+
<p align="center">
|
| 172 |
+
<sub>
|
| 173 |
+
mona-agent · <a href="https://agent.mona.expert">agent.mona.expert</a> ·
|
| 174 |
+
<a href="https://github.com/MONAEXPERT/agent">GitHub</a> ·
|
| 175 |
+
<a href="https://github.com/MONAEXPERT/agent/issues">Issues</a>
|
| 176 |
+
</sub>
|
| 177 |
+
</p>
|
| 178 |
+
|
| 179 |
+
<p align="center">
|
| 180 |
+
<sub>Open-source device agent. No LLM keys on your device. Ever.</sub>
|
| 181 |
+
</p>
|
bin/mona-agent.js
CHANGED
|
@@ -1,68 +1,182 @@
|
|
| 1 |
#!/usr/bin/env node
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import { createInterface } from 'node:readline/promises';
|
| 3 |
import { stdin, stdout } from 'node:process';
|
| 4 |
-
import { loadCreds, saveCreds,
|
| 5 |
import { verifyKey } from '../src/cloud.js';
|
| 6 |
-
import {
|
|
|
|
|
|
|
| 7 |
|
| 8 |
-
const [cmd
|
| 9 |
|
|
|
|
| 10 |
async function login() {
|
| 11 |
const rl = createInterface({ input: stdin, output: stdout });
|
| 12 |
const existing = loadCreds();
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
rl.close();
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
try {
|
| 19 |
const info = await verifyKey(apiKey);
|
| 20 |
const path = saveCreds({ apiKey, agentId: info.agentId });
|
| 21 |
-
|
| 22 |
-
console.log(`
|
| 23 |
-
console.log(`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
} catch (e) {
|
| 25 |
-
console.
|
|
|
|
| 26 |
process.exit(1);
|
| 27 |
}
|
| 28 |
}
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
const
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
}
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
function status() {
|
| 42 |
const c = loadCreds();
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
}
|
| 46 |
|
|
|
|
| 47 |
function help() {
|
| 48 |
-
console.log(`
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
|
| 51 |
-
mona-agent login Save your agent.mona.expert API key
|
| 52 |
-
mona-agent status Show login status
|
| 53 |
-
mona-agent run "<task>" Run a task (reasoning happens in the cloud)
|
| 54 |
-
mona-agent gui Start the local GUI (npm run gui)
|
| 55 |
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
}
|
| 58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
switch (cmd) {
|
| 60 |
-
case 'login':
|
| 61 |
-
case '
|
| 62 |
-
case '
|
|
|
|
| 63 |
case undefined:
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
}
|
|
|
|
| 1 |
#!/usr/bin/env node
|
| 2 |
+
// ── mona-agent CLI ────────────────────────────────────────────────
|
| 3 |
+
// Commands:
|
| 4 |
+
// gui Terminal dashboard (default when TTY)
|
| 5 |
+
// start Headless daemon
|
| 6 |
+
// login Save API key
|
| 7 |
+
// status Show connection info
|
| 8 |
+
// help Show usage
|
| 9 |
+
|
| 10 |
import { createInterface } from 'node:readline/promises';
|
| 11 |
import { stdin, stdout } from 'node:process';
|
| 12 |
+
import { loadCreds, saveCreds, requireCreds, CLOUD, DEFAULTS, PATHS } from '../src/config.js';
|
| 13 |
import { verifyKey } from '../src/cloud.js';
|
| 14 |
+
import { AgentDaemon } from '../src/agent.js';
|
| 15 |
+
import { Dashboard } from '../src/tui.js';
|
| 16 |
+
import { log } from '../src/log.js';
|
| 17 |
|
| 18 |
+
const [cmd] = process.argv.slice(2);
|
| 19 |
|
| 20 |
+
// ── login ─────────────────────────────────────────────────────────
|
| 21 |
async function login() {
|
| 22 |
const rl = createInterface({ input: stdin, output: stdout });
|
| 23 |
const existing = loadCreds();
|
| 24 |
+
|
| 25 |
+
console.log(`\n ${BOLD}mona-agent login${RESET}\n`);
|
| 26 |
+
|
| 27 |
+
if (existing?.apiKey) {
|
| 28 |
+
console.log(` ${DIM}An API key is already saved. This will replace it.${RESET}\n`);
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
const apiKey = (await rl.question(' agent.mona.expert API key: ')).trim();
|
| 32 |
rl.close();
|
| 33 |
+
|
| 34 |
+
if (!apiKey) {
|
| 35 |
+
console.error('\n No key entered.\n');
|
| 36 |
+
process.exit(1);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
process.stdout.write(`\n Verifying with ${CLOUD.base}... `);
|
| 40 |
+
|
| 41 |
try {
|
| 42 |
const info = await verifyKey(apiKey);
|
| 43 |
const path = saveCreds({ apiKey, agentId: info.agentId });
|
| 44 |
+
|
| 45 |
+
console.log(`${GREEN}OK${RESET}`);
|
| 46 |
+
console.log(`\n ${DIM}Agent:${RESET} ${info.agentId || '(pending)'}`);
|
| 47 |
+
console.log(` ${DIM}Saved:${RESET} ${path}`);
|
| 48 |
+
console.log(` ${DIM}Cloud:${RESET} ${CLOUD.base}`);
|
| 49 |
+
console.log(`\n Start the daemon:\n`);
|
| 50 |
+
console.log(` ${CYAN}mona-agent gui${RESET} ${DIM}# terminal dashboard${RESET}`);
|
| 51 |
+
console.log(` ${CYAN}mona-agent start${RESET} ${DIM}# headless daemon${RESET}`);
|
| 52 |
+
console.log();
|
| 53 |
} catch (e) {
|
| 54 |
+
console.log(`${RED}FAILED${RESET}`);
|
| 55 |
+
console.error(`\n ${e.message}\n`);
|
| 56 |
process.exit(1);
|
| 57 |
}
|
| 58 |
}
|
| 59 |
|
| 60 |
+
// ── gui (terminal dashboard) ──────────────────────────────────────
|
| 61 |
+
async function gui() {
|
| 62 |
+
const creds = requireCreds();
|
| 63 |
+
log.quiet(true); // Suppress console output; TUI handles display
|
| 64 |
+
|
| 65 |
+
const daemon = new AgentDaemon(creds);
|
| 66 |
+
const dashboard = new Dashboard(daemon);
|
| 67 |
+
|
| 68 |
+
const stop = () => {
|
| 69 |
+
dashboard.stop();
|
| 70 |
+
daemon.close();
|
| 71 |
+
process.exit(0);
|
| 72 |
+
};
|
| 73 |
+
|
| 74 |
+
process.on('SIGINT', stop);
|
| 75 |
+
process.on('SIGTERM', stop);
|
| 76 |
+
|
| 77 |
+
daemon.start();
|
| 78 |
+
dashboard.start();
|
| 79 |
}
|
| 80 |
|
| 81 |
+
// ── start (headless) ──────────────────────────────────────────────
|
| 82 |
+
async function start() {
|
| 83 |
+
const creds = requireCreds();
|
| 84 |
+
|
| 85 |
+
console.log(`\n ${BOLD}${CYAN}mona-agent${RESET} ${DIM}v${DEFAULTS.version}${RESET}`);
|
| 86 |
+
console.log(` ${DIM}Headless daemon — controlled from ${CLOUD.base}${RESET}\n`);
|
| 87 |
+
|
| 88 |
+
const daemon = new AgentDaemon(creds);
|
| 89 |
+
|
| 90 |
+
const stop = () => {
|
| 91 |
+
console.log(`\n ${DIM}Shutting down...${RESET}\n`);
|
| 92 |
+
daemon.close();
|
| 93 |
+
process.exit(0);
|
| 94 |
+
};
|
| 95 |
+
|
| 96 |
+
process.on('SIGINT', stop);
|
| 97 |
+
process.on('SIGTERM', stop);
|
| 98 |
+
|
| 99 |
+
daemon.start();
|
| 100 |
+
|
| 101 |
+
// Keep process alive; all work is driven by inbound commands.
|
| 102 |
+
setInterval(() => {}, 1 << 30);
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
// ── status ────────────────────────────────────────────────────────
|
| 106 |
function status() {
|
| 107 |
const c = loadCreds();
|
| 108 |
+
console.log(`\n ${BOLD}mona-agent status${RESET}\n`);
|
| 109 |
+
|
| 110 |
+
if (!c?.apiKey) {
|
| 111 |
+
console.log(` ${RED}Not logged in.${RESET} Run: ${CYAN}mona-agent login${RESET}\n`);
|
| 112 |
+
return;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
console.log(` ${DIM}Agent:${RESET} ${c.agentId || '(pending)'}`);
|
| 116 |
+
console.log(` ${DIM}Cloud:${RESET} ${CLOUD.base}`);
|
| 117 |
+
console.log(` ${DIM}Creds:${RESET} ${PATHS.creds}`);
|
| 118 |
+
console.log(` ${DIM}Config:${RESET} ${PATHS.dir}`);
|
| 119 |
+
console.log();
|
| 120 |
}
|
| 121 |
|
| 122 |
+
// ── help ──────────────────────────────────────────────────────────
|
| 123 |
function help() {
|
| 124 |
+
console.log(`
|
| 125 |
+
${BOLD}mona-agent${RESET} ${DIM}v${DEFAULTS.version}${RESET}
|
| 126 |
+
Cloud-brained device agent. No local LLM keys.
|
| 127 |
+
|
| 128 |
+
${BOLD}USAGE${RESET}
|
| 129 |
|
| 130 |
+
mona-agent ${CYAN}<command>${RESET}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
|
| 132 |
+
${BOLD}COMMANDS${RESET}
|
| 133 |
+
|
| 134 |
+
${CYAN}gui${RESET} Terminal dashboard with live metrics ${DIM}(default)${RESET}
|
| 135 |
+
${CYAN}start${RESET} Headless daemon (no UI)
|
| 136 |
+
${CYAN}login${RESET} Save your agent.mona.expert API key
|
| 137 |
+
${CYAN}status${RESET} Show login and connection info
|
| 138 |
+
${CYAN}help${RESET} Show this help
|
| 139 |
+
|
| 140 |
+
${BOLD}ENVIRONMENT${RESET}
|
| 141 |
+
|
| 142 |
+
MONA_CLOUD Cloud base URL ${DIM}(default: ${CLOUD.base})${RESET}
|
| 143 |
+
MONA_CLOUD_WS WebSocket URL ${DIM}(auto-derived from MONA_CLOUD)${RESET}
|
| 144 |
+
MONA_ALLOW_CMDS Shell allowlist ${DIM}(comma-separated command names)${RESET}
|
| 145 |
+
MONA_SHELL_UNSAFE Allow all shell ${DIM}(set to 1 — NOT recommended)${RESET}
|
| 146 |
+
MONA_WORKSPACE File tool sandbox ${DIM}(default: ~/.mona-agent/workspace)${RESET}
|
| 147 |
+
|
| 148 |
+
${BOLD}QUICK START${RESET}
|
| 149 |
+
|
| 150 |
+
${DIM}$${RESET} mona-agent login ${DIM}# paste your API key${RESET}
|
| 151 |
+
${DIM}$${RESET} mona-agent gui ${DIM}# terminal dashboard${RESET}
|
| 152 |
+
|
| 153 |
+
All reasoning runs on ${BOLD}${CLOUD.base}${RESET}. No LLM keys stored locally.
|
| 154 |
+
`);
|
| 155 |
}
|
| 156 |
|
| 157 |
+
// ── ANSI constants ────────────────────────────────────────────────
|
| 158 |
+
const BOLD = '\x1b[1m';
|
| 159 |
+
const DIM = '\x1b[2m';
|
| 160 |
+
const RESET = '\x1b[0m';
|
| 161 |
+
const CYAN = '\x1b[36m';
|
| 162 |
+
const GREEN = '\x1b[32m';
|
| 163 |
+
const RED = '\x1b[31m';
|
| 164 |
+
|
| 165 |
+
// ── Dispatch ──────────────────────────────────────────────────────
|
| 166 |
switch (cmd) {
|
| 167 |
+
case 'login': await login(); break;
|
| 168 |
+
case 'gui': await gui(); break;
|
| 169 |
+
case 'start': await start(); break;
|
| 170 |
+
case 'status': status(); break;
|
| 171 |
case undefined:
|
| 172 |
+
// Default: GUI if TTY, headless otherwise
|
| 173 |
+
if (process.stdout.isTTY) await gui();
|
| 174 |
+
else await start();
|
| 175 |
+
break;
|
| 176 |
+
case 'help': case '-h': case '--help':
|
| 177 |
+
help();
|
| 178 |
+
break;
|
| 179 |
+
default:
|
| 180 |
+
console.error(`\n Unknown command: ${cmd}\n Run ${CYAN}mona-agent help${RESET} for usage.\n`);
|
| 181 |
+
process.exit(1);
|
| 182 |
}
|
install.sh
CHANGED
|
@@ -1,39 +1,66 @@
|
|
| 1 |
#!/usr/bin/env bash
|
| 2 |
-
#
|
| 3 |
-
# Usage:
|
| 4 |
set -euo pipefail
|
| 5 |
|
| 6 |
-
REPO="${MONA_REPO:-https://github.com/
|
| 7 |
DIR="${MONA_DIR:-$HOME/.mona-agent-app}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
echo "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
command -v node >/dev/null 2>&1 || { echo "Node.js 18+ required. Install from https://nodejs.org"; exit 1; }
|
| 12 |
NODE_MAJOR=$(node -p "process.versions.node.split('.')[0]")
|
| 13 |
-
[ "$NODE_MAJOR" -
|
|
|
|
|
|
|
|
|
|
| 14 |
|
|
|
|
|
|
|
|
|
|
| 15 |
if [ -d "$DIR/.git" ]; then
|
| 16 |
-
echo "→ Updating existing install in $DIR"
|
| 17 |
-
git -C "$DIR" pull --ff-only
|
| 18 |
else
|
| 19 |
-
echo "→ Cloning into $DIR"
|
| 20 |
-
git clone --depth 1 "$REPO" "$DIR"
|
| 21 |
fi
|
| 22 |
|
| 23 |
cd "$DIR"
|
| 24 |
-
echo "→ Installing dependencies"
|
| 25 |
-
npm install --omit=dev --silent
|
| 26 |
|
| 27 |
-
#
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
else
|
| 31 |
-
echo "→ Could not link globally; run via: node $DIR/bin/mona-agent.js"
|
| 32 |
fi
|
| 33 |
|
| 34 |
-
echo
|
| 35 |
-
echo "✓ Installed."
|
| 36 |
-
echo "
|
| 37 |
-
echo "
|
| 38 |
-
echo
|
| 39 |
-
echo "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
#!/usr/bin/env bash
|
| 2 |
+
# ── mona-agent installer ──────────────────────────────────────────
|
| 3 |
+
# Usage: curl -fsSL https://agent.mona.expert/install.sh | bash
|
| 4 |
set -euo pipefail
|
| 5 |
|
| 6 |
+
REPO="${MONA_REPO:-https://github.com/MONAEXPERT/mona-agent.git}"
|
| 7 |
DIR="${MONA_DIR:-$HOME/.mona-agent-app}"
|
| 8 |
+
BOLD='\033[1m'
|
| 9 |
+
DIM='\033[2m'
|
| 10 |
+
CYAN='\033[36m'
|
| 11 |
+
GREEN='\033[32m'
|
| 12 |
+
RED='\033[31m'
|
| 13 |
+
RESET='\033[0m'
|
| 14 |
|
| 15 |
+
echo ""
|
| 16 |
+
echo -e " ${BOLD}${CYAN}mona-agent${RESET} installer"
|
| 17 |
+
echo ""
|
| 18 |
+
|
| 19 |
+
# ── Check prerequisites ──────────────────────────────────────────
|
| 20 |
+
command -v node >/dev/null 2>&1 || {
|
| 21 |
+
echo -e " ${RED}✗${RESET} Node.js 20+ required. Install from https://nodejs.org"
|
| 22 |
+
exit 1
|
| 23 |
+
}
|
| 24 |
|
|
|
|
| 25 |
NODE_MAJOR=$(node -p "process.versions.node.split('.')[0]")
|
| 26 |
+
if [ "$NODE_MAJOR" -lt 20 ]; then
|
| 27 |
+
echo -e " ${RED}✗${RESET} Node.js 20+ required (found $(node -v))"
|
| 28 |
+
exit 1
|
| 29 |
+
fi
|
| 30 |
|
| 31 |
+
echo -e " ${GREEN}✓${RESET} Node.js $(node -v)"
|
| 32 |
+
|
| 33 |
+
# ── Clone or update ──────────────────────────────────────────────
|
| 34 |
if [ -d "$DIR/.git" ]; then
|
| 35 |
+
echo -e " ${DIM}→ Updating existing install in $DIR${RESET}"
|
| 36 |
+
git -C "$DIR" pull --ff-only --quiet
|
| 37 |
else
|
| 38 |
+
echo -e " ${DIM}→ Cloning into $DIR${RESET}"
|
| 39 |
+
git clone --depth 1 --quiet "$REPO" "$DIR"
|
| 40 |
fi
|
| 41 |
|
| 42 |
cd "$DIR"
|
|
|
|
|
|
|
| 43 |
|
| 44 |
+
# ── Install dependencies ────────────────────────────────────────
|
| 45 |
+
echo -e " ${DIM}→ Installing dependencies${RESET}"
|
| 46 |
+
npm install --omit=dev --silent 2>/dev/null
|
| 47 |
+
|
| 48 |
+
# ── Link CLI globally ───────────────────────────────────────────
|
| 49 |
+
if npm link --silent 2>/dev/null; then
|
| 50 |
+
echo -e " ${GREEN}✓${RESET} Linked ${BOLD}mona-agent${RESET} command globally"
|
| 51 |
else
|
| 52 |
+
echo -e " ${DIM}→ Could not link globally; run via: node $DIR/bin/mona-agent.js${RESET}"
|
| 53 |
fi
|
| 54 |
|
| 55 |
+
echo ""
|
| 56 |
+
echo -e " ${GREEN}✓${RESET} Installed."
|
| 57 |
+
echo ""
|
| 58 |
+
echo -e " ${BOLD}Quick start:${RESET}"
|
| 59 |
+
echo ""
|
| 60 |
+
echo -e " ${CYAN}mona-agent login${RESET} ${DIM}# connect to your control plane${RESET}"
|
| 61 |
+
echo -e " ${CYAN}mona-agent gui${RESET} ${DIM}# terminal dashboard${RESET}"
|
| 62 |
+
echo -e " ${CYAN}mona-agent start${RESET} ${DIM}# headless daemon${RESET}"
|
| 63 |
+
echo ""
|
| 64 |
+
echo -e " Free & open source (MIT). No LLM keys on your device."
|
| 65 |
+
echo -e " SaaS control plane at ${BOLD}agent.mona.expert${RESET} (free tier available)."
|
| 66 |
+
echo ""
|
src/gui/index.html
DELETED
|
@@ -1,146 +0,0 @@
|
|
| 1 |
-
<!doctype html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="utf-8" />
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
-
<title>Mona Agent</title>
|
| 7 |
-
<style>
|
| 8 |
-
:root {
|
| 9 |
-
--bg: #0b0d12; --panel: #141821; --line: #232a37;
|
| 10 |
-
--text: #e6e9ef; --muted: #8b94a7; --accent: #6ea8fe; --ok: #46d39a; --warn: #ffcc66;
|
| 11 |
-
}
|
| 12 |
-
* { box-sizing: border-box; }
|
| 13 |
-
body {
|
| 14 |
-
margin: 0; font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
| 15 |
-
background: var(--bg); color: var(--text); height: 100vh; display: grid;
|
| 16 |
-
grid-template-rows: auto 1fr;
|
| 17 |
-
}
|
| 18 |
-
header {
|
| 19 |
-
display: flex; align-items: center; gap: 12px; padding: 14px 20px;
|
| 20 |
-
border-bottom: 1px solid var(--line); background: linear-gradient(180deg,#11151d,#0b0d12);
|
| 21 |
-
}
|
| 22 |
-
header .logo { width: 28px; height: 28px; border-radius: 8px;
|
| 23 |
-
background: radial-gradient(circle at 30% 30%, #6ea8fe, #3a6ff0); }
|
| 24 |
-
header h1 { font-size: 15px; margin: 0; letter-spacing: .3px; }
|
| 25 |
-
header .status { margin-left: auto; font-size: 12px; color: var(--muted); }
|
| 26 |
-
header .dot { display:inline-block; width:8px; height:8px; border-radius:50%; background:var(--warn); margin-right:6px;}
|
| 27 |
-
header .dot.on { background: var(--ok); }
|
| 28 |
-
main { display: grid; grid-template-columns: 1.3fr 1fr; gap: 16px; padding: 16px; overflow: hidden; }
|
| 29 |
-
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 14px;
|
| 30 |
-
display: flex; flex-direction: column; overflow: hidden; }
|
| 31 |
-
.panel h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .6px; color: var(--muted);
|
| 32 |
-
margin: 0; padding: 12px 16px; border-bottom: 1px solid var(--line); }
|
| 33 |
-
.panel .body { padding: 14px 16px; overflow: auto; flex: 1; }
|
| 34 |
-
.left { display: grid; grid-template-rows: auto 1fr; gap: 16px; }
|
| 35 |
-
.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
| 36 |
-
.metric { background:#0f131b; border:1px solid var(--line); border-radius:10px; padding:12px; }
|
| 37 |
-
.metric .k { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing:.5px;}
|
| 38 |
-
.metric .v { font-size: 20px; font-weight: 600; margin-top: 4px; }
|
| 39 |
-
.metric .v small { font-size: 12px; color: var(--muted); font-weight: 400; }
|
| 40 |
-
.steps .item { padding: 8px 0; border-bottom: 1px dashed var(--line); }
|
| 41 |
-
.steps .item:last-child { border-bottom: none; }
|
| 42 |
-
.steps .name { color: var(--accent); font-weight: 600; }
|
| 43 |
-
.steps .t { color: var(--muted); font-size: 11px; float: right; }
|
| 44 |
-
#output { white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
| 45 |
-
font-size: 13px; }
|
| 46 |
-
.runbar { display: flex; gap: 10px; padding: 14px 16px; border-top: 1px solid var(--line); }
|
| 47 |
-
.runbar input { flex: 1; background:#0f131b; border:1px solid var(--line); color: var(--text);
|
| 48 |
-
border-radius: 10px; padding: 10px 12px; font-size: 14px; outline: none; }
|
| 49 |
-
.runbar input:focus { border-color: var(--accent); }
|
| 50 |
-
.runbar button { background: var(--accent); color:#06101f; border:none; border-radius:10px;
|
| 51 |
-
padding: 0 18px; font-weight: 600; cursor: pointer; }
|
| 52 |
-
.runbar button:disabled { opacity:.5; cursor: default; }
|
| 53 |
-
.cloud-note { font-size: 11px; color: var(--muted); padding: 0 16px 12px; }
|
| 54 |
-
</style>
|
| 55 |
-
</head>
|
| 56 |
-
<body>
|
| 57 |
-
<header>
|
| 58 |
-
<div class="logo"></div>
|
| 59 |
-
<h1>Mona Agent</h1>
|
| 60 |
-
<div class="status"><span class="dot" id="dot"></span><span id="statusText">connecting…</span></div>
|
| 61 |
-
</header>
|
| 62 |
-
<main>
|
| 63 |
-
<div class="left">
|
| 64 |
-
<div class="panel">
|
| 65 |
-
<h2>Device Monitoring</h2>
|
| 66 |
-
<div class="body metrics">
|
| 67 |
-
<div class="metric"><div class="k">CPU load (1m)</div><div class="v" id="m-cpu">—</div></div>
|
| 68 |
-
<div class="metric"><div class="k">Memory used</div><div class="v" id="m-mem">—</div></div>
|
| 69 |
-
<div class="metric"><div class="k">Uptime</div><div class="v" id="m-up">—</div></div>
|
| 70 |
-
<div class="metric"><div class="k">Platform</div><div class="v" id="m-plat">—</div></div>
|
| 71 |
-
<div class="metric"><div class="k">Cores</div><div class="v" id="m-cores">—</div></div>
|
| 72 |
-
<div class="metric"><div class="k">Streaming</div><div class="v" id="m-stream" style="color:var(--ok)">live</div></div>
|
| 73 |
-
</div>
|
| 74 |
-
</div>
|
| 75 |
-
<div class="panel">
|
| 76 |
-
<h2>Agent Output</h2>
|
| 77 |
-
<div class="body"><div id="output">Ready. Type a task below.</div></div>
|
| 78 |
-
<div class="runbar">
|
| 79 |
-
<input id="task" placeholder="Ask the agent to do something…" />
|
| 80 |
-
<button id="run">Run</button>
|
| 81 |
-
</div>
|
| 82 |
-
<div class="cloud-note">🔒 Reasoning runs on agent.mona.expert · no LLM keys stored on this device</div>
|
| 83 |
-
</div>
|
| 84 |
-
</div>
|
| 85 |
-
<div class="panel">
|
| 86 |
-
<h2>Live Steps & Telemetry → agent.mona.expert</h2>
|
| 87 |
-
<div class="body steps" id="steps"></div>
|
| 88 |
-
</div>
|
| 89 |
-
</main>
|
| 90 |
-
<script>
|
| 91 |
-
const $ = s => document.querySelector(s);
|
| 92 |
-
const fmtBytes = b => (b/1073741824).toFixed(1) + ' GB';
|
| 93 |
-
const fmtUp = s => { const h=Math.floor(s/3600), m=Math.floor(s%3600/60); return h+'h '+m+'m'; };
|
| 94 |
-
|
| 95 |
-
let ws;
|
| 96 |
-
function connect() {
|
| 97 |
-
ws = new WebSocket(`ws://${location.host}`);
|
| 98 |
-
ws.onopen = () => { $('#dot').classList.add('on'); $('#statusText').textContent = 'connected'; };
|
| 99 |
-
ws.onclose = () => { $('#dot').classList.remove('on'); $('#statusText').textContent = 'reconnecting…'; setTimeout(connect, 2000); };
|
| 100 |
-
ws.onmessage = (ev) => {
|
| 101 |
-
const msg = JSON.parse(ev.data);
|
| 102 |
-
if (msg.type === 'device.metrics') {
|
| 103 |
-
const d = msg.data;
|
| 104 |
-
$('#m-cpu').innerHTML = d.cpuLoad[0].toFixed(2);
|
| 105 |
-
const used = d.mem.total - d.mem.free;
|
| 106 |
-
$('#m-mem').innerHTML = fmtBytes(used) + ' <small>/ ' + fmtBytes(d.mem.total) + '</small>';
|
| 107 |
-
$('#m-up').textContent = fmtUp(d.uptime);
|
| 108 |
-
$('#m-plat').textContent = d.platform + ' ' + d.arch;
|
| 109 |
-
$('#m-cores').textContent = d.cpus;
|
| 110 |
-
} else if (msg.type === 'agent.step') {
|
| 111 |
-
addStep(msg.data.name, JSON.stringify(msg.data.detail || {}));
|
| 112 |
-
} else if (msg.type === 'agent.log') {
|
| 113 |
-
addStep(msg.data.level, msg.data.message);
|
| 114 |
-
} else if (msg.type === 'token') {
|
| 115 |
-
if ($('#output').dataset.fresh !== '1') { $('#output').textContent=''; $('#output').dataset.fresh='1'; }
|
| 116 |
-
$('#output').textContent += msg.data;
|
| 117 |
-
} else if (msg.type === 'done') {
|
| 118 |
-
$('#run').disabled = false; $('#output').dataset.fresh='0';
|
| 119 |
-
addStep('done', 'task complete');
|
| 120 |
-
} else if (msg.type === 'error') {
|
| 121 |
-
addStep('error', msg.data);
|
| 122 |
-
}
|
| 123 |
-
};
|
| 124 |
-
}
|
| 125 |
-
function addStep(name, detail) {
|
| 126 |
-
const el = document.createElement('div');
|
| 127 |
-
el.className = 'item';
|
| 128 |
-
el.innerHTML = `<span class="t">${new Date().toLocaleTimeString()}</span><span class="name">${name}</span><div>${detail||''}</div>`;
|
| 129 |
-
$('#steps').prepend(el);
|
| 130 |
-
}
|
| 131 |
-
function run() {
|
| 132 |
-
const task = $('#task').value.trim();
|
| 133 |
-
if (!task || !ws || ws.readyState !== 1) return;
|
| 134 |
-
$('#run').disabled = true;
|
| 135 |
-
$('#output').textContent = '';
|
| 136 |
-
$('#output').dataset.fresh = '0';
|
| 137 |
-
addStep('run', task);
|
| 138 |
-
ws.send(JSON.stringify({ type: 'run', task }));
|
| 139 |
-
$('#task').value = '';
|
| 140 |
-
}
|
| 141 |
-
$('#run').onclick = run;
|
| 142 |
-
$('#task').addEventListener('keydown', e => { if (e.key === 'Enter') run(); });
|
| 143 |
-
connect();
|
| 144 |
-
</script>
|
| 145 |
-
</body>
|
| 146 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/gui/server.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
| 1 |
-
// Local GUI server. Serves the dashboard and bridges browser <-> agent + cloud.
|
| 2 |
-
import http from 'node:http';
|
| 3 |
-
import { readFileSync } from 'node:fs';
|
| 4 |
-
import { fileURLToPath } from 'node:url';
|
| 5 |
-
import { dirname, join } from 'node:path';
|
| 6 |
-
import { WebSocketServer } from 'ws';
|
| 7 |
-
import { loadCreds } from '../config.js';
|
| 8 |
-
import { Agent } from '../agent.js';
|
| 9 |
-
|
| 10 |
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
| 11 |
-
const PORT = process.env.PORT || 4319;
|
| 12 |
-
|
| 13 |
-
const html = readFileSync(join(__dirname, 'index.html'), 'utf8');
|
| 14 |
-
|
| 15 |
-
const server = http.createServer((req, res) => {
|
| 16 |
-
if (req.url === '/' || req.url === '/index.html') {
|
| 17 |
-
res.writeHead(200, { 'content-type': 'text/html' });
|
| 18 |
-
res.end(html);
|
| 19 |
-
} else if (req.url === '/api/status') {
|
| 20 |
-
const c = loadCreds();
|
| 21 |
-
res.writeHead(200, { 'content-type': 'application/json' });
|
| 22 |
-
res.end(JSON.stringify({ loggedIn: !!c?.apiKey, agentId: c?.agentId || null }));
|
| 23 |
-
} else {
|
| 24 |
-
res.writeHead(404); res.end('not found');
|
| 25 |
-
}
|
| 26 |
-
});
|
| 27 |
-
|
| 28 |
-
const wss = new WebSocketServer({ server });
|
| 29 |
-
wss.on('connection', (client) => {
|
| 30 |
-
const creds = loadCreds();
|
| 31 |
-
if (!creds?.apiKey) {
|
| 32 |
-
client.send(JSON.stringify({ type: 'error', data: 'Not logged in. Run: mona-agent login' }));
|
| 33 |
-
return;
|
| 34 |
-
}
|
| 35 |
-
const agent = new Agent(creds);
|
| 36 |
-
// Mirror telemetry to the browser dashboard.
|
| 37 |
-
const origSend = agent.telemetry.send.bind(agent.telemetry);
|
| 38 |
-
agent.telemetry.send = (type, data) => {
|
| 39 |
-
origSend(type, data);
|
| 40 |
-
if (client.readyState === 1) client.send(JSON.stringify({ type, data, ts: Date.now() }));
|
| 41 |
-
};
|
| 42 |
-
|
| 43 |
-
client.on('message', async (raw) => {
|
| 44 |
-
try {
|
| 45 |
-
const msg = JSON.parse(raw.toString());
|
| 46 |
-
if (msg.type === 'run' && msg.task) {
|
| 47 |
-
await agent.run(msg.task, (delta) =>
|
| 48 |
-
client.readyState === 1 && client.send(JSON.stringify({ type: 'token', data: delta })));
|
| 49 |
-
client.readyState === 1 && client.send(JSON.stringify({ type: 'done' }));
|
| 50 |
-
}
|
| 51 |
-
} catch (e) {
|
| 52 |
-
client.readyState === 1 && client.send(JSON.stringify({ type: 'error', data: e.message }));
|
| 53 |
-
}
|
| 54 |
-
});
|
| 55 |
-
|
| 56 |
-
client.on('close', () => agent.close());
|
| 57 |
-
});
|
| 58 |
-
|
| 59 |
-
server.listen(PORT, () => {
|
| 60 |
-
console.log(`\n Mona Agent GUI → http://localhost:${PORT}\n`);
|
| 61 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/telemetry.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
| 1 |
-
// Streams device monitoring + agent steps to agent.mona.expert over a WebSocket.
|
| 2 |
-
import { WebSocket } from 'ws';
|
| 3 |
-
import os from 'node:os';
|
| 4 |
-
import { CLOUD } from './config.js';
|
| 5 |
-
|
| 6 |
-
export class TelemetryStream {
|
| 7 |
-
constructor(apiKey, agentId) {
|
| 8 |
-
this.apiKey = apiKey;
|
| 9 |
-
this.agentId = agentId;
|
| 10 |
-
this.ws = null;
|
| 11 |
-
this.queue = [];
|
| 12 |
-
this.timer = null;
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
connect() {
|
| 16 |
-
this.ws = new WebSocket(CLOUD.ws, {
|
| 17 |
-
headers: { authorization: `Bearer ${this.apiKey}` },
|
| 18 |
-
});
|
| 19 |
-
this.ws.on('open', () => {
|
| 20 |
-
this.send('hello', { agentId: this.agentId, host: os.hostname() });
|
| 21 |
-
this.flush();
|
| 22 |
-
this.startDeviceMonitor();
|
| 23 |
-
});
|
| 24 |
-
this.ws.on('close', () => {
|
| 25 |
-
clearInterval(this.timer);
|
| 26 |
-
setTimeout(() => this.connect(), 3000); // auto-reconnect
|
| 27 |
-
});
|
| 28 |
-
this.ws.on('error', () => {});
|
| 29 |
-
return this;
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
send(type, data) {
|
| 33 |
-
const msg = JSON.stringify({ type, ts: Date.now(), agentId: this.agentId, data });
|
| 34 |
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) this.ws.send(msg);
|
| 35 |
-
else this.queue.push(msg);
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
flush() {
|
| 39 |
-
while (this.queue.length && this.ws?.readyState === WebSocket.OPEN) {
|
| 40 |
-
this.ws.send(this.queue.shift());
|
| 41 |
-
}
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
// Emit agent lifecycle steps.
|
| 45 |
-
step(name, detail) { this.send('agent.step', { name, detail }); }
|
| 46 |
-
log(level, message) { this.send('agent.log', { level, message }); }
|
| 47 |
-
|
| 48 |
-
// Periodic device monitoring snapshot.
|
| 49 |
-
startDeviceMonitor() {
|
| 50 |
-
this.timer = setInterval(() => {
|
| 51 |
-
const load = os.loadavg();
|
| 52 |
-
this.send('device.metrics', {
|
| 53 |
-
cpuLoad: load,
|
| 54 |
-
mem: { total: os.totalmem(), free: os.freemem() },
|
| 55 |
-
uptime: os.uptime(),
|
| 56 |
-
platform: os.platform(),
|
| 57 |
-
arch: os.arch(),
|
| 58 |
-
cpus: os.cpus().length,
|
| 59 |
-
});
|
| 60 |
-
}, 5000);
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
close() {
|
| 64 |
-
clearInterval(this.timer);
|
| 65 |
-
this.ws?.close();
|
| 66 |
-
}
|
| 67 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|