neoagent 1.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.
Files changed (54) hide show
  1. package/.env.example +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +42 -0
  4. package/bin/neoagent.js +8 -0
  5. package/com.neoagent.plist +45 -0
  6. package/docs/configuration.md +45 -0
  7. package/docs/skills.md +45 -0
  8. package/lib/manager.js +459 -0
  9. package/package.json +61 -0
  10. package/server/db/database.js +239 -0
  11. package/server/index.js +442 -0
  12. package/server/middleware/auth.js +35 -0
  13. package/server/public/app.html +559 -0
  14. package/server/public/css/app.css +608 -0
  15. package/server/public/css/styles.css +472 -0
  16. package/server/public/favicon.svg +17 -0
  17. package/server/public/js/app.js +3283 -0
  18. package/server/public/login.html +313 -0
  19. package/server/routes/agents.js +125 -0
  20. package/server/routes/auth.js +105 -0
  21. package/server/routes/browser.js +116 -0
  22. package/server/routes/mcp.js +164 -0
  23. package/server/routes/memory.js +193 -0
  24. package/server/routes/messaging.js +153 -0
  25. package/server/routes/protocols.js +87 -0
  26. package/server/routes/scheduler.js +63 -0
  27. package/server/routes/settings.js +98 -0
  28. package/server/routes/skills.js +107 -0
  29. package/server/routes/store.js +1192 -0
  30. package/server/services/ai/compaction.js +82 -0
  31. package/server/services/ai/engine.js +1690 -0
  32. package/server/services/ai/models.js +46 -0
  33. package/server/services/ai/multiStep.js +112 -0
  34. package/server/services/ai/providers/anthropic.js +181 -0
  35. package/server/services/ai/providers/base.js +40 -0
  36. package/server/services/ai/providers/google.js +187 -0
  37. package/server/services/ai/providers/grok.js +121 -0
  38. package/server/services/ai/providers/ollama.js +162 -0
  39. package/server/services/ai/providers/openai.js +167 -0
  40. package/server/services/ai/toolRunner.js +218 -0
  41. package/server/services/browser/controller.js +320 -0
  42. package/server/services/cli/executor.js +204 -0
  43. package/server/services/mcp/client.js +260 -0
  44. package/server/services/memory/embeddings.js +126 -0
  45. package/server/services/memory/manager.js +431 -0
  46. package/server/services/messaging/base.js +23 -0
  47. package/server/services/messaging/discord.js +238 -0
  48. package/server/services/messaging/manager.js +328 -0
  49. package/server/services/messaging/telegram.js +243 -0
  50. package/server/services/messaging/telnyx.js +693 -0
  51. package/server/services/messaging/whatsapp.js +304 -0
  52. package/server/services/scheduler/cron.js +312 -0
  53. package/server/services/websocket.js +191 -0
  54. package/server/utils/security.js +71 -0
@@ -0,0 +1,98 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const db = require('../db/database');
4
+ const { requireAuth } = require('../middleware/auth');
5
+
6
+ router.use(requireAuth);
7
+
8
+ // Get supported models metadata
9
+ router.get('/meta/models', (req, res) => {
10
+ const { SUPPORTED_MODELS } = require('../services/ai/models');
11
+ res.json({ models: SUPPORTED_MODELS });
12
+ });
13
+
14
+ // Get all settings
15
+ router.get('/', (req, res) => {
16
+ const rows = db.prepare('SELECT key, value FROM user_settings WHERE user_id = ?').all(req.session.userId);
17
+ const settings = {};
18
+ for (const row of rows) {
19
+ try {
20
+ settings[row.key] = JSON.parse(row.value);
21
+ } catch (e) {
22
+ if (typeof row.value === 'string' && (row.value.trim().startsWith('{') || row.value.trim().startsWith('['))) {
23
+ console.warn(`[Settings] Failed to parse '${row.key}' as JSON, treating as raw string. Error:`, e.message);
24
+ }
25
+ settings[row.key] = row.value;
26
+ }
27
+ }
28
+ res.json(settings);
29
+ });
30
+
31
+ // Update settings (batch)
32
+ router.put('/', (req, res) => {
33
+ const userId = req.session.userId;
34
+ const upsert = db.prepare('INSERT INTO user_settings (user_id, key, value) VALUES (?, ?, ?) ON CONFLICT(user_id, key) DO UPDATE SET value = excluded.value');
35
+
36
+ const tx = db.transaction((entries) => {
37
+ for (const [key, value] of entries) {
38
+ const v = typeof value === 'string' ? value : JSON.stringify(value);
39
+ upsert.run(userId, key, v);
40
+ }
41
+ });
42
+
43
+ tx(Object.entries(req.body));
44
+
45
+ // Apply headless toggle immediately without restarting
46
+ if ('headless_browser' in req.body) {
47
+ const bc = req.app.locals.browserController;
48
+ if (bc) bc.setHeadless(req.body.headless_browser).catch(() => { });
49
+ }
50
+
51
+ res.json({ success: true });
52
+ });
53
+
54
+ // Get single setting
55
+ router.get('/:key', (req, res) => {
56
+ const row = db.prepare('SELECT value FROM user_settings WHERE user_id = ? AND key = ?').get(req.session.userId, req.params.key);
57
+ if (!row) return res.json({ value: null });
58
+ try {
59
+ res.json({ value: JSON.parse(row.value) });
60
+ } catch (e) {
61
+ if (typeof row.value === 'string' && (row.value.trim().startsWith('{') || row.value.trim().startsWith('['))) {
62
+ console.warn(`[Settings] Failed to parse '${req.params.key}' as JSON, returning as raw string. Error:`, e.message);
63
+ }
64
+ res.json({ value: row.value });
65
+ }
66
+ });
67
+
68
+ // Set single setting
69
+ router.put('/:key', (req, res) => {
70
+ const v = typeof req.body.value === 'string' ? req.body.value : JSON.stringify(req.body.value);
71
+ db.prepare('INSERT INTO user_settings (user_id, key, value) VALUES (?, ?, ?) ON CONFLICT(user_id, key) DO UPDATE SET value = excluded.value')
72
+ .run(req.session.userId, req.params.key, v);
73
+ res.json({ success: true });
74
+ });
75
+
76
+ // Delete setting
77
+ router.delete('/:key', (req, res) => {
78
+ db.prepare('DELETE FROM user_settings WHERE user_id = ? AND key = ?').run(req.session.userId, req.params.key);
79
+ res.json({ success: true });
80
+ });
81
+
82
+ // Trigger auto-update script
83
+ router.post('/update', (req, res) => {
84
+ const { spawn } = require('child_process');
85
+ console.log('[Settings] Triggering neoagent update...');
86
+
87
+ // Spawn the update command in detached mode so it survives the node process exiting
88
+ const child = spawn(process.execPath, ['bin/neoagent.js', 'update'], {
89
+ detached: true,
90
+ stdio: 'ignore',
91
+ cwd: process.cwd()
92
+ });
93
+
94
+ child.unref();
95
+ res.json({ success: true, message: 'Update triggered' });
96
+ });
97
+
98
+ module.exports = router;
@@ -0,0 +1,107 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { requireAuth } = require('../middleware/auth');
6
+
7
+ const SKILLS_DIR = path.join(__dirname, '../../agent-data/skills');
8
+
9
+ /**
10
+ * Resolve a client-supplied filename to an absolute path inside SKILLS_DIR.
11
+ * Returns null if the resolved path escapes the directory (path traversal attempt).
12
+ */
13
+ function safeSkillPath(filename) {
14
+ if (!filename || typeof filename !== 'string') return null;
15
+ // Strip any directory components – only the basename is allowed
16
+ const base = path.basename(filename);
17
+ if (!base || base === '.' || base === '..') return null;
18
+ const resolved = path.resolve(SKILLS_DIR, base);
19
+ // Ensure the resolved path stays inside SKILLS_DIR
20
+ if (!resolved.startsWith(SKILLS_DIR + path.sep) && resolved !== SKILLS_DIR) return null;
21
+ return resolved;
22
+ }
23
+
24
+ router.use(requireAuth);
25
+
26
+ // List all skills
27
+ router.get('/', (req, res) => {
28
+ if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
29
+
30
+ const files = fs.readdirSync(SKILLS_DIR).filter(f => f.endsWith('.md'));
31
+ const skills = files.map(f => {
32
+ const content = fs.readFileSync(path.join(SKILLS_DIR, f), 'utf-8');
33
+ const meta = parseSkillMeta(content);
34
+ return {
35
+ filename: f,
36
+ name: meta.name || f.replace('.md', ''),
37
+ description: meta.description || '',
38
+ trigger: meta.trigger || '',
39
+ enabled: meta.enabled !== false,
40
+ category: meta.category || 'general'
41
+ };
42
+ });
43
+
44
+ res.json(skills);
45
+ });
46
+
47
+ // Get a specific skill
48
+ router.get('/:filename', (req, res) => {
49
+ const fp = safeSkillPath(req.params.filename);
50
+ if (!fp || !fs.existsSync(fp)) return res.status(404).json({ error: 'Skill not found' });
51
+
52
+ const content = fs.readFileSync(fp, 'utf-8');
53
+ res.json({ filename: req.params.filename, content, meta: parseSkillMeta(content) });
54
+ });
55
+
56
+ // Create a new skill
57
+ router.post('/', (req, res) => {
58
+ const { filename, content } = req.body;
59
+ if (!filename || !content) return res.status(400).json({ error: 'filename and content required' });
60
+
61
+ const baseName = filename.replace(/\.md$/i, '').replace(/[^a-zA-Z0-9_.-]/g, '');
62
+ const safeName = baseName + '.md';
63
+ const fp = path.join(SKILLS_DIR, safeName);
64
+
65
+ if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
66
+
67
+ fs.writeFileSync(fp, content, 'utf-8');
68
+ res.status(201).json({ filename: safeName, meta: parseSkillMeta(content) });
69
+ });
70
+
71
+ // Update a skill
72
+ router.put('/:filename', (req, res) => {
73
+ const fp = safeSkillPath(req.params.filename);
74
+ if (!fp || !fs.existsSync(fp)) return res.status(404).json({ error: 'Skill not found' });
75
+
76
+ fs.writeFileSync(fp, req.body.content, 'utf-8');
77
+ res.json({ filename: path.basename(fp), meta: parseSkillMeta(req.body.content) });
78
+ });
79
+
80
+ // Delete a skill
81
+ router.delete('/:filename', (req, res) => {
82
+ const fp = safeSkillPath(req.params.filename);
83
+ if (!fp || !fs.existsSync(fp)) return res.status(404).json({ error: 'Skill not found' });
84
+
85
+ fs.unlinkSync(fp);
86
+ res.json({ success: true });
87
+ });
88
+
89
+ function parseSkillMeta(content) {
90
+ const meta = {};
91
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
92
+ if (!match) return meta;
93
+
94
+ const lines = match[1].split('\n');
95
+ for (const line of lines) {
96
+ const colon = line.indexOf(':');
97
+ if (colon === -1) continue;
98
+ const key = line.slice(0, colon).trim();
99
+ let val = line.slice(colon + 1).trim();
100
+ if (val === 'true') val = true;
101
+ else if (val === 'false') val = false;
102
+ meta[key] = val;
103
+ }
104
+ return meta;
105
+ }
106
+
107
+ module.exports = router;