neoagent 1.6.0 → 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 +18 -4
- package/docs/configuration.md +2 -2
- package/docs/skills.md +1 -1
- package/lib/manager.js +64 -2
- package/package.json +9 -2
- package/server/config/origins.js +34 -0
- package/server/db/database.js +0 -13
- package/server/http/errors.js +17 -0
- package/server/http/middleware.js +81 -0
- package/server/http/routes.js +45 -0
- package/server/http/socket.js +23 -0
- package/server/http/static.js +50 -0
- package/server/index.js +50 -188
- package/server/public/.last_build_id +1 -0
- package/server/public/assets/AssetManifest.bin +1 -0
- package/server/public/assets/AssetManifest.bin.json +1 -0
- package/server/public/assets/AssetManifest.json +1 -0
- package/server/public/assets/FontManifest.json +1 -0
- package/server/public/assets/NOTICES +33454 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/assets/packages/cupertino_icons/assets/CupertinoIcons.ttf +0 -0
- package/server/public/assets/shaders/ink_sparkle.frag +126 -0
- package/server/public/assets/web/icons/Icon-192.png +0 -0
- package/server/public/canvaskit/canvaskit.js +192 -0
- package/server/public/canvaskit/canvaskit.js.symbols +12142 -0
- package/server/public/canvaskit/canvaskit.wasm +0 -0
- package/server/public/canvaskit/chromium/canvaskit.js +192 -0
- package/server/public/canvaskit/chromium/canvaskit.js.symbols +11106 -0
- package/server/public/canvaskit/chromium/canvaskit.wasm +0 -0
- package/server/public/canvaskit/skwasm.js +140 -0
- package/server/public/canvaskit/skwasm.js.symbols +12164 -0
- package/server/public/canvaskit/skwasm.wasm +0 -0
- package/server/public/canvaskit/skwasm_heavy.js +140 -0
- package/server/public/canvaskit/skwasm_heavy.js.symbols +13766 -0
- package/server/public/canvaskit/skwasm_heavy.wasm +0 -0
- package/server/public/favicon.png +0 -0
- package/server/public/flutter.js +32 -0
- package/server/public/flutter_bootstrap.js +43 -0
- package/server/public/flutter_service_worker.js +208 -0
- package/server/public/icons/Icon-192.png +0 -0
- package/server/public/icons/Icon-512.png +0 -0
- package/server/public/icons/Icon-maskable-192.png +0 -0
- package/server/public/icons/Icon-maskable-512.png +0 -0
- package/server/public/index.html +38 -0
- package/server/public/main.dart.js +103124 -0
- package/server/public/manifest.json +35 -0
- package/server/public/version.json +1 -0
- package/server/services/ai/models.js +2 -8
- package/server/services/ai/tools.js +0 -47
- package/server/services/browser/controller.js +34 -0
- package/server/services/manager.js +49 -118
- package/server/services/messaging/automation.js +210 -0
- package/server/utils/version.js +37 -0
- package/server/public/app.html +0 -682
- package/server/public/assets/world-office-dark.png +0 -0
- package/server/public/assets/world-office-light.png +0 -0
- package/server/public/css/app.css +0 -941
- package/server/public/css/styles.css +0 -963
- package/server/public/favicon.svg +0 -17
- package/server/public/js/app.js +0 -4105
- package/server/public/login.html +0 -313
- package/server/routes/protocols.js +0 -87
package/README.md
CHANGED
|
@@ -6,12 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://nodejs.org)
|
|
8
8
|
[](https://sqlite.org)
|
|
9
|
-
[](https://flutter.dev)
|
|
10
10
|
[](LICENSE)
|
|
11
|
-
[](https://github.com/NeoLabs-Systems/NeoAgent/releases/latest/download/app-debug.apk)
|
|
12
11
|
|
|
13
|
-
A self-hosted, proactive AI agent with a
|
|
14
|
-
Connects to
|
|
12
|
+
A self-hosted, proactive AI agent with a Flutter client for web and Android.
|
|
13
|
+
Connects to OpenAI, xAI, Google, and local Ollama with `qwen3.5:4b`.
|
|
15
14
|
Runs tasks on a schedule, controls a browser, manages files, and talks to you over Telegram, Discord, or WhatsApp.
|
|
16
15
|
|
|
17
16
|
```bash
|
|
@@ -31,6 +30,21 @@ neoagent update
|
|
|
31
30
|
neoagent logs
|
|
32
31
|
```
|
|
33
32
|
|
|
33
|
+
Build the Flutter web client:
|
|
34
|
+
```bash
|
|
35
|
+
npm run flutter:build:web
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The installer and npm package ship the bundled web client from `server/public`, so Flutter is only needed when you want to rebuild the frontend locally.
|
|
39
|
+
|
|
40
|
+
Local development helpers live in `dev/`:
|
|
41
|
+
```bash
|
|
42
|
+
./dev/backend.sh
|
|
43
|
+
./dev/web.sh
|
|
44
|
+
./dev/stack.sh
|
|
45
|
+
./dev/test.sh
|
|
46
|
+
```
|
|
47
|
+
|
|
34
48
|
---
|
|
35
49
|
|
|
36
50
|
[⚙️ Configuration](docs/configuration.md) · [🧰 Skills](docs/skills.md) · [🐛 Issues](https://github.com/NeoLabs-Systems/NeoAgent/issues)
|
package/docs/configuration.md
CHANGED
|
@@ -17,7 +17,7 @@ You can override the runtime root with `NEOAGENT_HOME`.
|
|
|
17
17
|
|
|
18
18
|
### AI Providers
|
|
19
19
|
|
|
20
|
-
At least one API key is required. The active provider and model are configured in the
|
|
20
|
+
At least one API key is required. The active provider and model are configured in the Flutter app.
|
|
21
21
|
|
|
22
22
|
| Variable | Provider |
|
|
23
23
|
|---|---|
|
|
@@ -34,7 +34,7 @@ At least one API key is required. The active provider and model are configured i
|
|
|
34
34
|
|---|---|
|
|
35
35
|
| `TELNYX_WEBHOOK_TOKEN` | Telnyx webhook signature verification |
|
|
36
36
|
|
|
37
|
-
Telegram, Discord, and WhatsApp tokens are stored in the database via the
|
|
37
|
+
Telegram, Discord, and WhatsApp tokens are stored in the database via the Flutter app settings page — not in `.env`.
|
|
38
38
|
|
|
39
39
|
## Runtime data paths
|
|
40
40
|
|
package/docs/skills.md
CHANGED
|
@@ -46,4 +46,4 @@ The agent reads all `.md` files in the skills directory on each conversation tur
|
|
|
46
46
|
|
|
47
47
|
## MCP tools
|
|
48
48
|
|
|
49
|
-
External tools are connected via the [Model Context Protocol](https://modelcontextprotocol.io). Configure MCP servers in the
|
|
49
|
+
External tools are connected via the [Model Context Protocol](https://modelcontextprotocol.io). Configure MCP servers in the Flutter app under **Models / Settings → MCP**. Connected tools appear alongside built-in skills automatically.
|
package/lib/manager.js
CHANGED
|
@@ -21,6 +21,8 @@ const SERVICE_LABEL = 'com.neoagent';
|
|
|
21
21
|
const PLIST_SRC = path.join(APP_DIR, 'com.neoagent.plist');
|
|
22
22
|
const PLIST_DST = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.neoagent.plist');
|
|
23
23
|
const SYSTEMD_UNIT = path.join(os.homedir(), '.config', 'systemd', 'user', 'neoagent.service');
|
|
24
|
+
const FLUTTER_APP_DIR = path.join(APP_DIR, 'flutter_app');
|
|
25
|
+
const WEB_CLIENT_DIR = path.join(APP_DIR, 'server', 'public');
|
|
24
26
|
|
|
25
27
|
const COLORS = process.stdout.isTTY
|
|
26
28
|
? {
|
|
@@ -137,6 +139,14 @@ function runQuiet(cmd, args, options = {}) {
|
|
|
137
139
|
return spawnSync(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8', cwd: APP_DIR, ...options });
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
function withInstallEnv(extraEnv = {}) {
|
|
143
|
+
return {
|
|
144
|
+
...process.env,
|
|
145
|
+
PUPPETEER_SKIP_DOWNLOAD: process.env.PUPPETEER_SKIP_DOWNLOAD || 'true',
|
|
146
|
+
...extraEnv
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
140
150
|
function commandExists(cmd) {
|
|
141
151
|
const res = runQuiet('bash', ['-lc', `command -v ${cmd}`]);
|
|
142
152
|
return res.status === 0;
|
|
@@ -259,10 +269,53 @@ async function cmdSetup() {
|
|
|
259
269
|
|
|
260
270
|
function installDependencies() {
|
|
261
271
|
heading('Dependencies');
|
|
262
|
-
runOrThrow('npm', ['install', '--omit=dev', '--no-audit', '--no-fund']
|
|
272
|
+
runOrThrow('npm', ['install', '--omit=dev', '--no-audit', '--no-fund'], {
|
|
273
|
+
env: withInstallEnv()
|
|
274
|
+
});
|
|
263
275
|
logOk('Dependencies installed');
|
|
264
276
|
}
|
|
265
277
|
|
|
278
|
+
function hasBundledWebClient() {
|
|
279
|
+
return fs.existsSync(path.join(WEB_CLIENT_DIR, 'index.html'));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function buildBundledWebClientIfPossible({ required = false } = {}) {
|
|
283
|
+
heading('Web Client');
|
|
284
|
+
|
|
285
|
+
if (!fs.existsSync(FLUTTER_APP_DIR)) {
|
|
286
|
+
if (hasBundledWebClient()) {
|
|
287
|
+
logOk('Using bundled Flutter web client');
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
if (required) {
|
|
291
|
+
throw new Error(`Missing Flutter app sources at ${FLUTTER_APP_DIR}`);
|
|
292
|
+
}
|
|
293
|
+
logWarn('Flutter app sources not found; keeping existing bundled web client');
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!commandExists('flutter')) {
|
|
298
|
+
if (hasBundledWebClient()) {
|
|
299
|
+
logWarn('Flutter SDK not found; using bundled web client');
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
throw new Error('Flutter SDK is required to build the web client because no bundled client was found.');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
runOrThrow('flutter', [
|
|
306
|
+
'build',
|
|
307
|
+
'web',
|
|
308
|
+
'--output',
|
|
309
|
+
'../server/public',
|
|
310
|
+
`--dart-define=NEOAGENT_BACKEND_URL=${process.env.NEOAGENT_BACKEND_URL || ''}`
|
|
311
|
+
], {
|
|
312
|
+
cwd: FLUTTER_APP_DIR,
|
|
313
|
+
env: process.env
|
|
314
|
+
});
|
|
315
|
+
logOk('Bundled Flutter web client updated');
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
266
319
|
function installMacService() {
|
|
267
320
|
ensureLogDir();
|
|
268
321
|
fs.mkdirSync(path.dirname(PLIST_DST), { recursive: true });
|
|
@@ -325,6 +378,7 @@ async function cmdInstall() {
|
|
|
325
378
|
}
|
|
326
379
|
|
|
327
380
|
installDependencies();
|
|
381
|
+
buildBundledWebClientIfPossible({ required: true });
|
|
328
382
|
|
|
329
383
|
const platform = detectPlatform();
|
|
330
384
|
if (platform === 'macos' && commandExists('launchctl')) {
|
|
@@ -481,15 +535,19 @@ function cmdUpdate() {
|
|
|
481
535
|
if (current.status === 0 && next.status === 0 && current.stdout.trim() !== next.stdout.trim()) {
|
|
482
536
|
logOk(`Updated ${current.stdout.trim()} -> ${next.stdout.trim()}`);
|
|
483
537
|
installDependencies();
|
|
538
|
+
buildBundledWebClientIfPossible();
|
|
484
539
|
} else {
|
|
485
540
|
logOk('Already up to date');
|
|
541
|
+
buildBundledWebClientIfPossible();
|
|
486
542
|
}
|
|
487
543
|
} else {
|
|
488
544
|
logWarn('No git repo detected; attempting npm global update.');
|
|
489
545
|
if (commandExists('npm')) {
|
|
490
546
|
try {
|
|
491
547
|
backupRuntimeData();
|
|
492
|
-
runOrThrow('npm', ['install', '-g', 'neoagent@latest']
|
|
548
|
+
runOrThrow('npm', ['install', '-g', 'neoagent@latest'], {
|
|
549
|
+
env: withInstallEnv()
|
|
550
|
+
});
|
|
493
551
|
logOk('npm global update completed');
|
|
494
552
|
} catch {
|
|
495
553
|
logWarn('npm global update failed. Run: npm install -g neoagent@latest');
|
|
@@ -499,6 +557,10 @@ function cmdUpdate() {
|
|
|
499
557
|
}
|
|
500
558
|
}
|
|
501
559
|
|
|
560
|
+
if (!hasBundledWebClient()) {
|
|
561
|
+
throw new Error('No bundled Flutter web client found after update.');
|
|
562
|
+
}
|
|
563
|
+
|
|
502
564
|
cmdRestart();
|
|
503
565
|
}
|
|
504
566
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neoagent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Proactive personal AI agent with no limits",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "server/index.js",
|
|
@@ -21,6 +21,13 @@
|
|
|
21
21
|
"scripts": {
|
|
22
22
|
"start": "node server/index.js",
|
|
23
23
|
"dev": "node --watch server/index.js",
|
|
24
|
+
"dev:backend": "./dev/backend.sh",
|
|
25
|
+
"dev:web": "./dev/web.sh",
|
|
26
|
+
"dev:stack": "./dev/stack.sh",
|
|
27
|
+
"dev:build": "./dev/build.sh",
|
|
28
|
+
"dev:test": "./dev/test.sh",
|
|
29
|
+
"flutter:run:web": "cd flutter_app && flutter run -d chrome",
|
|
30
|
+
"flutter:build:web": "cd flutter_app && flutter build web --output ../server/public --dart-define=NEOAGENT_BACKEND_URL=${NEOAGENT_BACKEND_URL:-}",
|
|
24
31
|
"manage": "node bin/neoagent.js",
|
|
25
32
|
"test": "node --test",
|
|
26
33
|
"benchmark:tokens": "node scripts/benchmark-token-cost.js",
|
|
@@ -54,7 +61,7 @@
|
|
|
54
61
|
"node-pty": "^1.0.0",
|
|
55
62
|
"node-telegram-bot-api": "^0.67.0",
|
|
56
63
|
"openai": "^4.85.4",
|
|
57
|
-
"puppeteer": "^24.
|
|
64
|
+
"puppeteer-core": "^24.40.0",
|
|
58
65
|
"puppeteer-extra": "^3.3.6",
|
|
59
66
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
60
67
|
"socket.io": "^4.8.1",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const configuredOrigins = (process.env.ALLOWED_ORIGINS || '')
|
|
4
|
+
.split(',')
|
|
5
|
+
.map((origin) => origin.trim())
|
|
6
|
+
.filter(Boolean);
|
|
7
|
+
|
|
8
|
+
function isLoopbackOrigin(origin) {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = new URL(origin);
|
|
11
|
+
return ['localhost', '127.0.0.1', '[::1]'].includes(parsed.hostname);
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isAllowedOrigin(origin) {
|
|
18
|
+
if (!origin) return true;
|
|
19
|
+
if (configuredOrigins.includes(origin)) return true;
|
|
20
|
+
if (isLoopbackOrigin(origin)) return true;
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validateOrigin(origin, callback) {
|
|
25
|
+
if (isAllowedOrigin(origin)) return callback(null, true);
|
|
26
|
+
return callback(new Error(`Origin not allowed: ${origin || 'unknown'}`));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
configuredOrigins,
|
|
31
|
+
isAllowedOrigin,
|
|
32
|
+
isLoopbackOrigin,
|
|
33
|
+
validateOrigin
|
|
34
|
+
};
|
package/server/db/database.js
CHANGED
|
@@ -214,19 +214,6 @@ db.exec(`
|
|
|
214
214
|
|
|
215
215
|
CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id, archived, updated_at DESC);
|
|
216
216
|
CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(user_id, category, archived);
|
|
217
|
-
CREATE TABLE IF NOT EXISTS protocols (
|
|
218
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
219
|
-
user_id INTEGER NOT NULL,
|
|
220
|
-
name TEXT UNIQUE NOT NULL,
|
|
221
|
-
description TEXT,
|
|
222
|
-
content TEXT NOT NULL,
|
|
223
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
224
|
-
updated_at TEXT DEFAULT (datetime('now')),
|
|
225
|
-
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
CREATE INDEX IF NOT EXISTS idx_protocols_user ON protocols(user_id);
|
|
229
|
-
|
|
230
217
|
CREATE INDEX IF NOT EXISTS idx_core_memory_user ON core_memory(user_id, key);
|
|
231
218
|
|
|
232
219
|
CREATE TABLE IF NOT EXISTS health_sync_runs (
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { sanitizeError } = require('../utils/security');
|
|
4
|
+
|
|
5
|
+
function registerErrorHandler(app) {
|
|
6
|
+
app.use((err, req, res, next) => {
|
|
7
|
+
console.error('[Unhandled error]', err);
|
|
8
|
+
const status = err.status || err.statusCode || 500;
|
|
9
|
+
const message = sanitizeError(err);
|
|
10
|
+
if (req.path.startsWith('/api/')) {
|
|
11
|
+
return res.status(status).json({ error: message });
|
|
12
|
+
}
|
|
13
|
+
return res.status(status).send('Something went wrong.');
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { registerErrorHandler };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const session = require('express-session');
|
|
4
|
+
const SQLiteStore = require('connect-sqlite3')(session);
|
|
5
|
+
const helmet = require('helmet');
|
|
6
|
+
const cors = require('cors');
|
|
7
|
+
const { DATA_DIR } = require('../../runtime/paths');
|
|
8
|
+
|
|
9
|
+
function buildHelmetOptions({ secureCookies }) {
|
|
10
|
+
const wsConnectSrc = secureCookies ? ['wss:'] : ['ws:', 'wss:'];
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
strictTransportSecurity: false,
|
|
14
|
+
crossOriginOpenerPolicy: false,
|
|
15
|
+
originAgentCluster: false,
|
|
16
|
+
contentSecurityPolicy: {
|
|
17
|
+
directives: {
|
|
18
|
+
defaultSrc: ["'self'"],
|
|
19
|
+
scriptSrc: [
|
|
20
|
+
"'self'",
|
|
21
|
+
"'unsafe-inline'",
|
|
22
|
+
"'unsafe-eval'",
|
|
23
|
+
'blob:',
|
|
24
|
+
'https://cdn.jsdelivr.net'
|
|
25
|
+
],
|
|
26
|
+
scriptSrcAttr: ["'unsafe-inline'"],
|
|
27
|
+
styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
|
|
28
|
+
imgSrc: ["'self'", 'data:', 'blob:', 'https://api.qrserver.com'],
|
|
29
|
+
connectSrc: [
|
|
30
|
+
"'self'",
|
|
31
|
+
'https://fonts.googleapis.com',
|
|
32
|
+
'https://fonts.gstatic.com',
|
|
33
|
+
...wsConnectSrc
|
|
34
|
+
],
|
|
35
|
+
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'],
|
|
36
|
+
workerSrc: ["'self'", 'blob:'],
|
|
37
|
+
formAction: ["'self'"],
|
|
38
|
+
frameAncestors: ["'self'"],
|
|
39
|
+
upgradeInsecureRequests: null
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createSessionMiddleware({ secureCookies }) {
|
|
46
|
+
return session({
|
|
47
|
+
store: new SQLiteStore({ db: 'sessions.db', dir: DATA_DIR }),
|
|
48
|
+
secret: process.env.SESSION_SECRET || 'neoagent-dev-secret-change-me',
|
|
49
|
+
name: 'neoagent.sid',
|
|
50
|
+
resave: false,
|
|
51
|
+
saveUninitialized: false,
|
|
52
|
+
cookie: {
|
|
53
|
+
maxAge: 7 * 24 * 60 * 60 * 1000,
|
|
54
|
+
httpOnly: true,
|
|
55
|
+
sameSite: 'lax',
|
|
56
|
+
secure: secureCookies
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function applyHttpMiddleware(app, { secureCookies, sessionMiddleware, validateOrigin }) {
|
|
62
|
+
if (secureCookies) {
|
|
63
|
+
app.set('trust proxy', 1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
app.use(helmet(buildHelmetOptions({ secureCookies })));
|
|
67
|
+
app.use(
|
|
68
|
+
cors({
|
|
69
|
+
origin: validateOrigin,
|
|
70
|
+
credentials: true
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
app.use(require('express').json({ limit: '10mb' }));
|
|
74
|
+
app.use(require('express').urlencoded({ extended: true }));
|
|
75
|
+
app.use(sessionMiddleware);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
applyHttpMiddleware,
|
|
80
|
+
createSessionMiddleware
|
|
81
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { requireAuth } = require('../middleware/auth');
|
|
4
|
+
const { setupTelnyxWebhook } = require('../routes/telnyx');
|
|
5
|
+
const { getVersionInfo } = require('../utils/version');
|
|
6
|
+
|
|
7
|
+
const routeRegistry = [
|
|
8
|
+
{ basePath: null, modulePath: '../routes/auth' },
|
|
9
|
+
{ basePath: '/api/settings', modulePath: '../routes/settings' },
|
|
10
|
+
{ basePath: '/api/agents', modulePath: '../routes/agents' },
|
|
11
|
+
{ basePath: '/api/messaging', modulePath: '../routes/messaging' },
|
|
12
|
+
{ basePath: '/api/mcp', modulePath: '../routes/mcp' },
|
|
13
|
+
{ basePath: '/api/skills', modulePath: '../routes/skills' },
|
|
14
|
+
{ basePath: '/api/store', modulePath: '../routes/store' },
|
|
15
|
+
{ basePath: '/api/memory', modulePath: '../routes/memory' },
|
|
16
|
+
{ basePath: '/api/scheduler', modulePath: '../routes/scheduler' },
|
|
17
|
+
{ basePath: '/api/browser', modulePath: '../routes/browser' },
|
|
18
|
+
{ basePath: '/api/mobile/health', modulePath: '../routes/mobile-health' }
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function registerApiRoutes(app) {
|
|
22
|
+
for (const route of routeRegistry) {
|
|
23
|
+
const handler = require(route.modulePath);
|
|
24
|
+
if (route.basePath) {
|
|
25
|
+
app.use(route.basePath, handler);
|
|
26
|
+
} else {
|
|
27
|
+
app.use(handler);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setupTelnyxWebhook(app);
|
|
32
|
+
|
|
33
|
+
app.get('/api/health', requireAuth, (req, res) => {
|
|
34
|
+
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
app.get('/api/version', requireAuth, (req, res) => {
|
|
38
|
+
res.json(getVersionInfo());
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
registerApiRoutes,
|
|
44
|
+
routeRegistry
|
|
45
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Server: SocketIO } = require('socket.io');
|
|
4
|
+
|
|
5
|
+
function createSocketServer(httpServer, { validateOrigin }) {
|
|
6
|
+
return new SocketIO(httpServer, {
|
|
7
|
+
cors: {
|
|
8
|
+
origin: validateOrigin,
|
|
9
|
+
credentials: true
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function bindSocketSessions(io, sessionMiddleware) {
|
|
15
|
+
io.use((socket, next) => {
|
|
16
|
+
sessionMiddleware(socket.request, {}, next);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
bindSocketSessions,
|
|
22
|
+
createSocketServer
|
|
23
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const express = require('express');
|
|
6
|
+
const { DATA_DIR } = require('../../runtime/paths');
|
|
7
|
+
const { requireAuth } = require('../middleware/auth');
|
|
8
|
+
|
|
9
|
+
const FLUTTER_WEB_DIR = path.join(__dirname, '..', 'public');
|
|
10
|
+
|
|
11
|
+
function registerStaticRoutes(app) {
|
|
12
|
+
app.use(
|
|
13
|
+
'/telnyx-audio',
|
|
14
|
+
express.static(path.join(DATA_DIR, 'telnyx-audio'), {
|
|
15
|
+
index: false,
|
|
16
|
+
setHeaders: (res, filePath) => {
|
|
17
|
+
if (!filePath.match(/\.(mp3|wav|ogg|aac|m4a)$/i)) {
|
|
18
|
+
res.status(403).end();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
app.use(
|
|
25
|
+
'/screenshots',
|
|
26
|
+
requireAuth,
|
|
27
|
+
express.static(path.join(DATA_DIR, 'screenshots'))
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
app.use(express.static(FLUTTER_WEB_DIR, { index: false }));
|
|
31
|
+
app.get(/^\/(?!api|screenshots|telnyx-audio).*/, serveFlutterApp);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function serveFlutterApp(req, res) {
|
|
35
|
+
const entry = path.join(FLUTTER_WEB_DIR, 'index.html');
|
|
36
|
+
if (!fs.existsSync(entry)) {
|
|
37
|
+
return res
|
|
38
|
+
.status(503)
|
|
39
|
+
.send(
|
|
40
|
+
'Flutter web build not found. Run "npm run flutter:build:web" to generate the bundled client.'
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return res.sendFile(entry);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
FLUTTER_WEB_DIR,
|
|
48
|
+
registerStaticRoutes,
|
|
49
|
+
serveFlutterApp
|
|
50
|
+
};
|