neoagent 2.4.1-beta.46 → 2.4.1-beta.48

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.4.1-beta.46",
3
+ "version": "2.4.1-beta.48",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",
@@ -37,7 +37,7 @@ async function api(path, opts = {}) {
37
37
  }
38
38
 
39
39
  function esc(str) {
40
- return String(str ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
40
+ return String(str ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
41
41
  }
42
42
 
43
43
  function fmtTime(iso) {
@@ -4,6 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>NeoAgent Admin</title>
7
+ <link rel="icon" href="/admin/logo.svg" type="image/svg+xml">
7
8
  <link rel="stylesheet" href="/admin/admin.css">
8
9
  <style>
9
10
  /* ── Layout ────────────────────────────────────────────────────── */
@@ -82,6 +83,22 @@
82
83
  border-top: 1px solid var(--border);
83
84
  }
84
85
 
86
+ .sidebar-legal {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 6px;
90
+ padding: 4px 8px 8px;
91
+ font-size: 11px;
92
+ color: var(--text-muted, #666);
93
+ }
94
+ .sidebar-legal a {
95
+ color: var(--text-muted, #666);
96
+ text-decoration: none;
97
+ transition: color 0.15s;
98
+ }
99
+ .sidebar-legal a:hover { color: var(--text, #ccc); }
100
+ .sidebar-legal span { opacity: 0.4; }
101
+
85
102
  .main {
86
103
  margin-left: var(--sidebar-w);
87
104
  flex: 1;
@@ -811,6 +828,11 @@
811
828
  </nav>
812
829
 
813
830
  <div class="sidebar-footer">
831
+ <div class="sidebar-legal">
832
+ <a href="/legal.html#overview" target="_blank" rel="noopener">Privacy Policy</a>
833
+ <span>·</span>
834
+ <a href="/legal.html#legal-notice" target="_blank" rel="noopener">Legal Notice</a>
835
+ </div>
814
836
  <button class="nav-item btn-danger" onclick="signOut()">
815
837
  <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
816
838
  <path fill-rule="evenodd" d="M3 3a1 1 0 00-1 1v12a1 1 0 102 0V4a1 1 0 00-1-1zm10.293 9.293a1 1 0 001.414 1.414l3-3a1 1 0 000-1.414l-3-3a1 1 0 10-1.414 1.414L14.586 9H7a1 1 0 100 2h7.586l-1.293 1.293z" clip-rule="evenodd"/>
@@ -4,6 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>NeoAgent Admin</title>
7
+ <link rel="icon" href="/admin/logo.svg" type="image/svg+xml">
7
8
  <link rel="stylesheet" href="/admin/admin.css">
8
9
  <style>
9
10
  body {
@@ -70,18 +71,56 @@
70
71
  font-size: 14px;
71
72
  margin-top: 4px;
72
73
  }
74
+
75
+ /* ── Admin hero mark ────────────────────────────── */
76
+ .admin-hero {
77
+ display: flex;
78
+ flex-direction: column;
79
+ align-items: center;
80
+ margin-bottom: 28px;
81
+ gap: 14px;
82
+ }
83
+
84
+ .admin-a-mark {
85
+ width: 80px;
86
+ height: 80px;
87
+ border-radius: 22px;
88
+ background: var(--bg-secondary);
89
+ border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
90
+ box-shadow:
91
+ 0 0 0 4px color-mix(in srgb, var(--accent) 8%, transparent),
92
+ 0 12px 32px rgba(0,0,0,0.45);
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ font-size: 42px;
97
+ font-weight: 800;
98
+ color: var(--accent);
99
+ letter-spacing: -2px;
100
+ font-family: var(--font-sans);
101
+ line-height: 1;
102
+ user-select: none;
103
+ }
104
+
105
+ .admin-hero .brand {
106
+ margin-bottom: 0;
107
+ justify-content: center;
108
+ }
73
109
  </style>
74
110
  </head>
75
111
  <body>
76
112
  <div class="login-wrap">
77
113
  <div class="login-card">
78
- <div class="brand">
79
- <div class="brand-mark">
80
- <img src="/admin/logo.svg" width="32" height="32" alt="">
81
- </div>
82
- <div>
83
- <div class="brand-name">NeoAgent</div>
84
- <div class="brand-sub">Admin</div>
114
+ <div class="admin-hero">
115
+ <div class="admin-a-mark" aria-hidden="true">A</div>
116
+ <div class="brand">
117
+ <div class="brand-mark">
118
+ <img src="/admin/logo.svg" width="32" height="32" alt="">
119
+ </div>
120
+ <div>
121
+ <div class="brand-name">NeoAgent</div>
122
+ <div class="brand-sub">Admin</div>
123
+ </div>
85
124
  </div>
86
125
  </div>
87
126
 
@@ -7,6 +7,7 @@ const { DATA_DIR } = require('../../runtime/paths');
7
7
  const { requireAuth } = require('../middleware/auth');
8
8
 
9
9
  const FLUTTER_WEB_DIR = path.join(__dirname, '..', 'public');
10
+ const LANDING_DIR = path.join(__dirname, '..', '..', 'landing');
10
11
  const BUILD_ID_PATH = path.join(FLUTTER_WEB_DIR, '.last_build_id');
11
12
 
12
13
  function setNoStoreHeaders(res) {
@@ -66,11 +67,20 @@ function registerStaticRoutes(app) {
66
67
  const adminRouter = require('../routes/admin');
67
68
  app.use('/admin', adminRouter);
68
69
 
69
- app.use(express.static(FLUTTER_WEB_DIR, {
70
- index: false,
71
- setHeaders: setFlutterStaticHeaders,
72
- }));
73
- app.get(/^\/(?!api|admin|screenshots|telnyx-audio).*/, serveFlutterApp);
70
+ // Flutter app at /app
71
+ app.use(
72
+ '/app',
73
+ express.static(FLUTTER_WEB_DIR, {
74
+ index: false,
75
+ setHeaders: setFlutterStaticHeaders,
76
+ })
77
+ );
78
+ app.get(/^\/app(\/.*)?$/, serveFlutterApp);
79
+
80
+ // Landing page at /
81
+ app.use(express.static(LANDING_DIR));
82
+ app.get('/', (req, res) => res.sendFile(path.join(LANDING_DIR, 'index.html')));
83
+ app.get('/legal.html', (req, res) => res.sendFile(path.join(LANDING_DIR, 'legal.html')));
74
84
  }
75
85
 
76
86
  function serveFlutterApp(req, res) {
@@ -83,11 +93,15 @@ function serveFlutterApp(req, res) {
83
93
  );
84
94
  }
85
95
  setNoStoreHeaders(res);
86
- return res.sendFile(entry);
96
+ // Rewrite <base href="/"> to <base href="/app/"> so Flutter asset paths resolve correctly
97
+ const html = fs.readFileSync(entry, 'utf8').replace('<base href="/">', '<base href="/app/">');
98
+ res.set('Content-Type', 'text/html');
99
+ return res.send(html);
87
100
  }
88
101
 
89
102
  module.exports = {
90
103
  FLUTTER_WEB_DIR,
104
+ LANDING_DIR,
91
105
  registerStaticRoutes,
92
106
  serveFlutterApp
93
107
  };
@@ -1 +1 @@
1
- 8038ef582d1a04389ee868993a7f5f02
1
+ ce60a1618c141f32f9c6c152f08d1f52
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"c416acfeb8126e097f758c664aaa3da929e27d
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "1312157190" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "2964141920" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -133824,7 +133824,7 @@ r===$&&A.b()
133824
133824
  o.push(A.j4(p,A.j8(!1,new A.a3(B.up,A.dl(new A.cv(B.hr,new A.a7p(r,p),p),p,p),p),!1,B.I,!0),p,p,0,0,0,p))}r=!1
133825
133825
  if(!s.ay)if(!s.ch){r=s.e
133826
133826
  r===$&&A.b()
133827
- r=B.b.t("mq6pvw31-1a6d75b").length!==0&&r.b}if(r){r=s.d
133827
+ r=B.b.t("mq74d0iw-7a6b040").length!==0&&r.b}if(r){r=s.d
133828
133828
  r===$&&A.b()
133829
133829
  r=r.au&&!r.aQ?84:0
133830
133830
  q=s.e
@@ -139266,7 +139266,7 @@ $S:0}
139266
139266
  A.ZM.prototype={}
139267
139267
  A.Sw.prototype={
139268
139268
  n9(a){var s=this
139269
- if(B.b.t("mq6pvw31-1a6d75b").length===0||s.a!=null)return
139269
+ if(B.b.t("mq74d0iw-7a6b040").length===0||s.a!=null)return
139270
139270
  s.AN()
139271
139271
  s.a=A.qh(B.Rb,new A.bas(s))},
139272
139272
  AN(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
@@ -139284,7 +139284,7 @@ if(!t.f.b(k)){s=1
139284
139284
  break}i=J.a_(k,"buildId")
139285
139285
  h=i==null?null:B.b.t(J.p(i))
139286
139286
  j=h==null?"":h
139287
- if(J.bf(j)===0||J.d(j,"mq6pvw31-1a6d75b")){s=1
139287
+ if(J.bf(j)===0||J.d(j,"mq74d0iw-7a6b040")){s=1
139288
139288
  break}n.b=!0
139289
139289
  n.E()
139290
139290
  p=2
@@ -139301,7 +139301,7 @@ case 2:return A.i(o.at(-1),r)}})
139301
139301
  return A.k($async$AN,r)},
139302
139302
  vw(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
139303
139303
  var $async$vw=A.h(function(a2,a3){if(a2===1){o.push(a3)
139304
- s=p}for(;;)switch(s){case 0:if(B.b.t("mq6pvw31-1a6d75b").length===0||n.c){s=1
139304
+ s=p}for(;;)switch(s){case 0:if(B.b.t("mq74d0iw-7a6b040").length===0||n.c){s=1
139305
139305
  break}n.c=!0
139306
139306
  n.E()
139307
139307
  p=4
@@ -435,13 +435,21 @@ router.get('/api/analytics', requireAdminAuth, (req, res) => {
435
435
  };
436
436
  const topUsers = db.prepare(`
437
437
  SELECT u.id, u.username, u.display_name,
438
- COUNT(DISTINCT r.id) AS runs,
439
- COALESCE(SUM(r.total_tokens),0) AS tokens,
440
- COALESCE(SUM(a.byte_size),0) AS storage
438
+ COALESCE(r.runs, 0) AS runs,
439
+ COALESCE(r.tokens, 0) AS tokens,
440
+ COALESCE(a.storage, 0) AS storage
441
441
  FROM users u
442
- LEFT JOIN agent_runs r ON r.user_id = u.id
443
- LEFT JOIN artifacts a ON a.user_id = u.id
444
- GROUP BY u.id ORDER BY runs DESC LIMIT 8
442
+ LEFT JOIN (
443
+ SELECT user_id,
444
+ COUNT(*) AS runs,
445
+ COALESCE(SUM(total_tokens),0) AS tokens
446
+ FROM agent_runs GROUP BY user_id
447
+ ) r ON r.user_id = u.id
448
+ LEFT JOIN (
449
+ SELECT user_id, COALESCE(SUM(byte_size),0) AS storage
450
+ FROM artifacts GROUP BY user_id
451
+ ) a ON a.user_id = u.id
452
+ ORDER BY runs DESC LIMIT 8
445
453
  `).all();
446
454
  const recentRuns = db.prepare(`
447
455
  SELECT r.id, u.username, r.title, r.status, r.total_tokens,
@@ -465,13 +473,19 @@ router.get('/api/users', requireAdminAuth, (req, res) => {
465
473
  const sql = `
466
474
  SELECT u.id, u.username, u.display_name, u.email, u.email_verified_at,
467
475
  u.created_at, u.last_login,
468
- COUNT(DISTINCT r.id) AS run_count,
469
- COALESCE(SUM(a.byte_size),0) AS storage_bytes
476
+ COALESCE(r.run_count, 0) AS run_count,
477
+ COALESCE(a.storage_bytes,0) AS storage_bytes
470
478
  FROM users u
471
- LEFT JOIN agent_runs r ON r.user_id = u.id
472
- LEFT JOIN artifacts a ON a.user_id = u.id
479
+ LEFT JOIN (
480
+ SELECT user_id, COUNT(*) AS run_count
481
+ FROM agent_runs GROUP BY user_id
482
+ ) r ON r.user_id = u.id
483
+ LEFT JOIN (
484
+ SELECT user_id, COALESCE(SUM(byte_size),0) AS storage_bytes
485
+ FROM artifacts GROUP BY user_id
486
+ ) a ON a.user_id = u.id
473
487
  ${q ? 'WHERE u.username LIKE ? OR u.email LIKE ?' : ''}
474
- GROUP BY u.id ORDER BY u.created_at DESC LIMIT 200`;
488
+ ORDER BY u.created_at DESC LIMIT 200`;
475
489
  const users = q ? db.prepare(sql).all(q, q) : db.prepare(sql).all();
476
490
  res.json({ users });
477
491
  } catch (err) {
@@ -597,7 +611,7 @@ router.put('/api/providers', requireAdminAuth, express.json(), (req, res) => {
597
611
  if (!ALLOWED_PROVIDER_KEYS.has(key)) {
598
612
  return res.status(400).json({ error: 'Unknown provider key' });
599
613
  }
600
- const trimmed = String(value || '').trim();
614
+ const trimmed = String(value || '').trim().replace(/[\r\n]/g, '');
601
615
  upsertEnvValue(ENV_FILE, key, trimmed);
602
616
  if (trimmed) {
603
617
  process.env[key] = trimmed;