@aliwey/bmo 2.0.0
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.
- package/README.md +90 -0
- package/bin/bmo.js +188 -0
- package/cli.py +1129 -0
- package/config/__init__.py +0 -0
- package/config/__pycache__/__init__.cpython-313.pyc +0 -0
- package/config/__pycache__/settings.cpython-313.pyc +0 -0
- package/config/__pycache__/system-prompt.cpython-313.pyc +0 -0
- package/config/settings.py +104 -0
- package/config/system-prompt.json +18 -0
- package/core/__init__.py +0 -0
- package/core/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/__pycache__/bfp_a2a_bridge.cpython-313.pyc +0 -0
- package/core/__pycache__/bfp_agent.cpython-313.pyc +0 -0
- package/core/__pycache__/bfp_agent_card.cpython-313.pyc +0 -0
- package/core/__pycache__/bfp_connector.cpython-313.pyc +0 -0
- package/core/__pycache__/bfp_discovery.cpython-313.pyc +0 -0
- package/core/__pycache__/bfp_identity.cpython-313.pyc +0 -0
- package/core/__pycache__/bfp_tasks.cpython-313.pyc +0 -0
- package/core/__pycache__/bfp_transport.cpython-313.pyc +0 -0
- package/core/__pycache__/bmo_engine.cpython-313.pyc +0 -0
- package/core/__pycache__/bot_client.cpython-313.pyc +0 -0
- package/core/__pycache__/budget_tracker.cpython-313.pyc +0 -0
- package/core/__pycache__/cli_renderer.cpython-313.pyc +0 -0
- package/core/__pycache__/goal_runner.cpython-313.pyc +0 -0
- package/core/__pycache__/request_worker.cpython-313.pyc +0 -0
- package/core/__pycache__/security.cpython-313.pyc +0 -0
- package/core/__pycache__/shared_state.cpython-313.pyc +0 -0
- package/core/__pycache__/worker_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/worker_multiproc.cpython-313.pyc +0 -0
- package/core/__pycache__/worker_protocol.cpython-313.pyc +0 -0
- package/core/__pycache__/worker_subprocess.cpython-313.pyc +0 -0
- package/core/bfp_a2a_bridge.py +399 -0
- package/core/bfp_agent.py +98 -0
- package/core/bfp_agent_card.py +161 -0
- package/core/bfp_connector.py +177 -0
- package/core/bfp_discovery.py +105 -0
- package/core/bfp_identity.py +83 -0
- package/core/bfp_tasks.py +70 -0
- package/core/bfp_transport.py +368 -0
- package/core/bmo_engine.py +405 -0
- package/core/bot_client.py +838 -0
- package/core/budget_tracker.py +62 -0
- package/core/cli_renderer.py +177 -0
- package/core/goal_runner.py +129 -0
- package/core/request_worker.py +242 -0
- package/core/security.py +42 -0
- package/core/shared_state.py +4 -0
- package/core/worker_manager.py +71 -0
- package/core/worker_multiproc.py +155 -0
- package/core/worker_protocol.py +30 -0
- package/core/worker_subprocess.py +222 -0
- package/handlers/__init__.py +0 -0
- package/handlers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/handlers/__pycache__/messages.cpython-313.pyc +0 -0
- package/handlers/messages.py +2761 -0
- package/main.py +125 -0
- package/memory.md +43 -0
- package/models/__init__.py +0 -0
- package/models/__pycache__/__init__.cpython-313.pyc +0 -0
- package/models/__pycache__/chat_models.cpython-313.pyc +0 -0
- package/models/chat_models.py +143 -0
- package/package.json +50 -0
- package/registry/worker.js +108 -0
- package/registry/wrangler.toml +11 -0
- package/requirements.txt +13 -0
- package/scripts/bmo_init.js +115 -0
- package/scripts/postinstall.js +265 -0
- package/scripts/relay_cmd.js +276 -0
- package/scripts/web_cmd.js +136 -0
- package/setup.py +26 -0
- package/storage/__init__.py +0 -0
- package/storage/__pycache__/__init__.cpython-313.pyc +0 -0
- package/storage/__pycache__/sqlite_storage.cpython-313.pyc +0 -0
- package/storage/__pycache__/storage.cpython-313.pyc +0 -0
- package/storage/sqlite_storage.py +658 -0
- package/storage/storage.py +265 -0
- package/tools/__pycache__/bfp_relay.cpython-313.pyc +0 -0
- package/tools/__pycache__/get_session_summaries.cpython-313.pyc +0 -0
- package/tools/__pycache__/mcp_bridge.cpython-313.pyc +0 -0
- package/tools/__pycache__/mcp_server.cpython-313.pyc +0 -0
- package/tools/__pycache__/run_mcp_standalone.cpython-313.pyc +0 -0
- package/tools/__pycache__/task_registry.cpython-313.pyc +0 -0
- package/tools/__pycache__/test_mcp_connection.cpython-313.pyc +0 -0
- package/tools/bfp_relay.py +359 -0
- package/tools/bot.db +0 -0
- package/tools/get_session_summaries.py +45 -0
- package/tools/mcp_bridge.py +109 -0
- package/tools/mcp_server.py +531 -0
- package/tools/register_mcp_task.py +20 -0
- package/tools/run_detached.bat +32 -0
- package/tools/run_mcp_standalone.py +16 -0
- package/tools/task_registry.py +184 -0
- package/tools/test_mcp_connection.py +80 -0
- package/webchat/package-lock.json +1528 -0
- package/webchat/package.json +12 -0
- package/webchat/public/app.js +1293 -0
- package/webchat/public/index.html +226 -0
- package/webchat/public/index.html.bak +416 -0
- package/webchat/public/styles.css +2435 -0
- package/webchat/server.js +645 -0
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# BMO — OpenCode AI Coding Assistant
|
|
2
|
+
|
|
3
|
+
BMO is a unified, self-contained AI coding assistant with Telegram, CLI, and Webchat interfaces. It connects to a local [OpenCode](https://github.com/opencode-ai/opencode) instance and extends it with persistent memory, tool access, custom agents, and a decentralized peer-to-peer Agent Discovery Protocol (BFP).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🚀 Installation & Setup
|
|
8
|
+
|
|
9
|
+
BMO is published as a global Node/npm command-line tool. The installer automatically downloads a minimal embedded Python environment, installs python dependencies, and sets up everything required.
|
|
10
|
+
|
|
11
|
+
### 1. Install Globally via npm
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @aliwey/bmo
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### 2. Run the Configuration Wizard
|
|
17
|
+
Configure your Telegram Bot Father token, admin Telegram user ID, model settings, and peer registry:
|
|
18
|
+
```bash
|
|
19
|
+
bmo init
|
|
20
|
+
```
|
|
21
|
+
This generates your configuration file at `~/.bmo/.env`.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## ⚡ Quick Start
|
|
26
|
+
|
|
27
|
+
### 🤖 Start the Bot & CLI
|
|
28
|
+
To launch the Telegram bot polling and the interactive terminal TUI:
|
|
29
|
+
```bash
|
|
30
|
+
bmo
|
|
31
|
+
```
|
|
32
|
+
*Note: If your local OpenCode instance is not running, BMO will automatically attempt to launch it.*
|
|
33
|
+
|
|
34
|
+
### 🌐 Launch the Web Chat Interface
|
|
35
|
+
Starts the unified Node.js/Express Web Chat and opens a secure public Cloudflare Tunnel so you can chat from any web browser:
|
|
36
|
+
```bash
|
|
37
|
+
bmo web
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 📡 Go Online (Peer Relay)
|
|
41
|
+
Start a BFP (BMO Friendship Protocol) peer relay node to make your agent discoverable by other BMO peers on the registry:
|
|
42
|
+
```bash
|
|
43
|
+
bmo relay
|
|
44
|
+
```
|
|
45
|
+
* To run privately without listing on the public registry: `bmo relay --private`
|
|
46
|
+
* To stop the relay background services: `bmo relay --stop`
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 🛠️ CLI Commands & Subcommands
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
bmo [command] [options]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Command | Option | Description |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `bmo` | — | Starts the Telegram bot & CLI TUI |
|
|
59
|
+
| `bmo init` | — | Runs the interactive setup wizard |
|
|
60
|
+
| `bmo web` | — | Starts Express Webchat + Cloudflare tunnel |
|
|
61
|
+
| `bmo relay` | `[--private] [--stop]` | Starts/stops the BFP capability relay |
|
|
62
|
+
| `bmo --version` | — | Prints the package version |
|
|
63
|
+
| `bmo --update` | — | Automatically pulls and applies the latest updates |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## ⚙️ Configuration & Storage
|
|
68
|
+
|
|
69
|
+
All user configurations, chat databases, and logs are kept isolated in the user's home directory under `~/.bmo/`:
|
|
70
|
+
|
|
71
|
+
* **`~/.bmo/.env`** — Bot tokens, model configurations, and ports.
|
|
72
|
+
* **`~/.bmo/data/bot.db`** — Shared SQLite database (WAL mode) containing all sessions, messages, memory, and custom agents.
|
|
73
|
+
* **`~/.bmo/logs/`** — Log files for the bot, webchat, and tunnel.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 👥 Telegram interface
|
|
78
|
+
|
|
79
|
+
Use the persistent keyboard controls at the bottom of the chat, or the inline control menu (`/menu` command) to:
|
|
80
|
+
- **🆕 New / 💾 Save / 📂 Load** sessions
|
|
81
|
+
- **🤖 Choose Model** dynamically (e.g. OpenAI, Anthropic, DeepSeek, or local)
|
|
82
|
+
- **⚡ Operation Modes**: `plan` (outline only), `ask` (clarifying questions first), or `execute` (write code and run commands)
|
|
83
|
+
- **🎭 Agents & 🛠️ Skills**: Enable custom or generated personas and load specialized capabilities
|
|
84
|
+
- **🌐 Chat on Web**: Dynamically launch a cloudflared-tunneled web interface directly from Telegram
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 📄 License
|
|
89
|
+
|
|
90
|
+
MIT License. Developed by Aliwey.
|
package/bin/bmo.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* BMO CLI entry point — @aliwey/bmo
|
|
4
|
+
* Routes subcommands and manages OpenCode lifecycle.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { spawn, execSync } = require('child_process');
|
|
10
|
+
const net = require('net');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
|
|
15
|
+
const PKG_DIR = path.join(__dirname, '..');
|
|
16
|
+
const BMO_HOME = process.env.BMO_HOME || path.join(os.homedir(), '.bmo');
|
|
17
|
+
const VERSION = require('../package.json').version;
|
|
18
|
+
|
|
19
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function sleep(ms) {
|
|
22
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function isPortOpen(port, host = '127.0.0.1') {
|
|
26
|
+
return new Promise(resolve => {
|
|
27
|
+
const s = net.createConnection({ port, host });
|
|
28
|
+
s.setTimeout(500);
|
|
29
|
+
s.on('connect', () => { s.destroy(); resolve(true); });
|
|
30
|
+
s.on('timeout', () => { s.destroy(); resolve(false); });
|
|
31
|
+
s.on('error', () => resolve(false));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Resolve the embedded or system Python executable */
|
|
36
|
+
function getPython() {
|
|
37
|
+
const embeddedWin = path.join(BMO_HOME, 'python', 'python.exe');
|
|
38
|
+
const embeddedUnix = path.join(BMO_HOME, 'python', 'bin', 'python3');
|
|
39
|
+
if (fs.existsSync(embeddedWin)) return embeddedWin;
|
|
40
|
+
if (fs.existsSync(embeddedUnix)) return embeddedUnix;
|
|
41
|
+
// Fall back to system Python
|
|
42
|
+
for (const cmd of ['python3', 'python']) {
|
|
43
|
+
try { execSync(`${cmd} --version`, { stdio: 'ignore' }); return cmd; } catch {}
|
|
44
|
+
}
|
|
45
|
+
console.error('❌ Python not found. Run: bmo init');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Start OpenCode if not already running. Returns the child if we own it. */
|
|
50
|
+
async function ensureOpenCode() {
|
|
51
|
+
const port = parseInt(process.env.OPENCODE_PORT || '4800');
|
|
52
|
+
if (await isPortOpen(port)) {
|
|
53
|
+
process.stdout.write(`✓ OpenCode already running on port ${port}\n`);
|
|
54
|
+
return null; // don't own it
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check opencode is installed
|
|
58
|
+
try { execSync('opencode --version', { stdio: 'ignore' }); }
|
|
59
|
+
catch {
|
|
60
|
+
console.error('❌ opencode not found. Installing...');
|
|
61
|
+
try { execSync('npm install -g opencode-ai', { stdio: 'inherit' }); }
|
|
62
|
+
catch { console.error('❌ Could not install opencode-ai. Please run: npm install -g opencode-ai'); process.exit(1); }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
process.stdout.write(`⏳ Starting OpenCode server on port ${port}...\n`);
|
|
66
|
+
const child = spawn('opencode', ['serve', '--port', String(port)], {
|
|
67
|
+
detached: false,
|
|
68
|
+
stdio: 'ignore',
|
|
69
|
+
cwd: PKG_DIR,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
child.on('error', err => {
|
|
73
|
+
console.error(`❌ Failed to start OpenCode: ${err.message}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Wait up to 15s for port to open
|
|
78
|
+
for (let i = 0; i < 30; i++) {
|
|
79
|
+
await sleep(500);
|
|
80
|
+
if (await isPortOpen(port)) {
|
|
81
|
+
process.stdout.write(`✓ OpenCode server ready\n`);
|
|
82
|
+
return child;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
console.error('❌ OpenCode server did not start in time.');
|
|
86
|
+
child.kill();
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Command routing ──────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
const [,, cmd, ...args] = process.argv;
|
|
93
|
+
|
|
94
|
+
(async () => {
|
|
95
|
+
// ── bmo --version ─────────────────────────────────────────────────────
|
|
96
|
+
if (cmd === '--version' || cmd === '-v') {
|
|
97
|
+
console.log(`@aliwey/bmo v${VERSION}`);
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── bmo --help ───────────────────────────────────────────────────────
|
|
102
|
+
if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
|
|
103
|
+
const PKG_JSON = require('../package.json');
|
|
104
|
+
const BMO_HOME_DISPLAY = process.env.BMO_HOME || path.join(os.homedir(), '.bmo');
|
|
105
|
+
console.log(`
|
|
106
|
+
@aliwey/bmo v${PKG_JSON.version} — AI coding assistant with Telegram, CLI & Web sync
|
|
107
|
+
|
|
108
|
+
Usage:
|
|
109
|
+
bmo Start the BMO CLI (auto-starts OpenCode if needed)
|
|
110
|
+
bmo init Run the setup wizard (creates ~/.bmo/.env)
|
|
111
|
+
bmo web Start webchat + Cloudflare tunnel, print public URL
|
|
112
|
+
bmo relay Start BFP relay + register with discovery registry
|
|
113
|
+
bmo relay --stop Stop relay + deregister
|
|
114
|
+
bmo relay --private Start relay without registering (private mode)
|
|
115
|
+
bmo --version Show version
|
|
116
|
+
bmo --update Update to latest version
|
|
117
|
+
bmo --update <ver> Update to a specific version
|
|
118
|
+
bmo --help Show this help
|
|
119
|
+
|
|
120
|
+
Data lives in: ${BMO_HOME_DISPLAY}
|
|
121
|
+
`);
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── bmo --update [version] ────────────────────────────────────────────────
|
|
126
|
+
if (cmd === '--update') {
|
|
127
|
+
const ver = args[0] ? `@${args[0]}` : '@latest';
|
|
128
|
+
console.log(`🔄 Updating to @aliwey/bmo${ver}...`);
|
|
129
|
+
try {
|
|
130
|
+
execSync(`npm install -g @aliwey/bmo${ver}`, { stdio: 'inherit' });
|
|
131
|
+
} catch {
|
|
132
|
+
console.error('❌ Update failed. Try manually: npm install -g @aliwey/bmo');
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── bmo init ──────────────────────────────────────────────────────────────
|
|
139
|
+
if (cmd === 'init') {
|
|
140
|
+
require('../scripts/bmo_init.js');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── bmo web ───────────────────────────────────────────────────────────────
|
|
145
|
+
if (cmd === 'web') {
|
|
146
|
+
require('../scripts/web_cmd.js');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── bmo relay [--private|--stop] ──────────────────────────────────────────
|
|
151
|
+
if (cmd === 'relay') {
|
|
152
|
+
process.argv = [process.argv[0], process.argv[1], ...args]; // pass flags
|
|
153
|
+
require('../scripts/relay_cmd.js');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── bmo (default: CLI) ────────────────────────────────────────────────────
|
|
158
|
+
const openCodeChild = await ensureOpenCode();
|
|
159
|
+
|
|
160
|
+
// Clean up owned OpenCode on exit
|
|
161
|
+
if (openCodeChild) {
|
|
162
|
+
const cleanup = () => { try { openCodeChild.kill(); } catch {} };
|
|
163
|
+
process.on('exit', cleanup);
|
|
164
|
+
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
165
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Launch BMO Python CLI — exactly like running `python cli.py`
|
|
169
|
+
const python = getPython();
|
|
170
|
+
const cliScript = path.join(PKG_DIR, 'cli.py');
|
|
171
|
+
|
|
172
|
+
const bmo = spawn(python, [cliScript], {
|
|
173
|
+
stdio: 'inherit',
|
|
174
|
+
cwd: PKG_DIR,
|
|
175
|
+
env: {
|
|
176
|
+
...process.env,
|
|
177
|
+
BMO_HOME,
|
|
178
|
+
PYTHONUNBUFFERED: '1',
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
bmo.on('error', err => {
|
|
183
|
+
console.error(`❌ Failed to start BMO: ${err.message}`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
bmo.on('exit', code => process.exit(code ?? 0));
|
|
188
|
+
})();
|