neoagent 2.4.1-beta.19 → 2.4.1-beta.21
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 +4 -1
- package/docs/getting-started.md +9 -3
- package/flutter_app/assets/branding/app_icon_light_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_64.png +0 -0
- package/flutter_app/assets/branding/tray_icon_light_template.png +0 -0
- package/flutter_app/lib/features/location/location_service.dart +3 -0
- package/flutter_app/lib/main.dart +1 -0
- package/flutter_app/lib/main_account_settings.dart +9 -33
- package/flutter_app/lib/main_app_shell.dart +237 -197
- package/flutter_app/lib/main_controller.dart +0 -25
- package/flutter_app/lib/main_devices.dart +2 -0
- package/flutter_app/lib/main_models.dart +144 -0
- package/flutter_app/lib/main_operations.dart +150 -19
- package/flutter_app/lib/main_shared.dart +642 -195
- package/flutter_app/lib/main_theme.dart +2 -0
- package/flutter_app/lib/src/android_apk_drop_zone_web.dart +3 -1
- package/flutter_app/lib/src/security/password_strength.dart +84 -0
- package/flutter_app/lib/src/theme/palette.dart +15 -15
- package/flutter_app/pubspec.yaml +3 -0
- package/flutter_app/web/favicon_light.svg +3 -0
- package/flutter_app/web/icons/Icon-192-light.png +0 -0
- package/flutter_app/web/icons/Icon-512-light.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192-light.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512-light.png +0 -0
- package/lib/manager.js +282 -81
- package/package.json +17 -3
- package/server/config/origins.js +3 -1
- package/server/db/database.js +73 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/AssetManifest.bin +1 -1
- package/server/public/assets/AssetManifest.bin.json +1 -1
- package/server/public/assets/assets/branding/app_icon_light_256.png +0 -0
- package/server/public/assets/assets/branding/app_icon_light_512.png +0 -0
- package/server/public/assets/assets/branding/tray_icon_light_template.png +0 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/favicon_light.svg +3 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/icons/Icon-192-light.png +0 -0
- package/server/public/icons/Icon-512-light.png +0 -0
- package/server/public/icons/Icon-maskable-192-light.png +0 -0
- package/server/public/icons/Icon-maskable-512-light.png +0 -0
- package/server/public/main.dart.js +68769 -68268
- package/server/routes/agent_profiles.js +3 -0
- package/server/routes/memory.js +22 -1
- package/server/services/account/password_policy.js +6 -1
- package/server/services/memory/intelligence.js +181 -0
- package/server/services/memory/manager.js +475 -25
- package/server/utils/security.js +3 -0
- package/server/services/memory/openhuman_uplift.test.js +0 -98
- package/server/utils/version.test.js +0 -39
package/lib/manager.js
CHANGED
|
@@ -62,29 +62,86 @@ const COLORS = process.stdout.isTTY
|
|
|
62
62
|
green: '\x1b[1;32m',
|
|
63
63
|
yellow: '\x1b[1;33m',
|
|
64
64
|
blue: '\x1b[1;34m',
|
|
65
|
+
magenta: '\x1b[1;35m',
|
|
65
66
|
cyan: '\x1b[1;36m',
|
|
66
67
|
dim: '\x1b[2m'
|
|
67
68
|
}
|
|
68
|
-
: { reset: '', bold: '', red: '', green: '', yellow: '', blue: '', cyan: '', dim: '' };
|
|
69
|
+
: { reset: '', bold: '', red: '', green: '', yellow: '', blue: '', magenta: '', cyan: '', dim: '' };
|
|
70
|
+
|
|
71
|
+
const CLI_INTERACTIVE = process.stdout.isTTY;
|
|
72
|
+
const installActionItems = [];
|
|
69
73
|
|
|
70
74
|
function logInfo(msg) {
|
|
71
|
-
|
|
75
|
+
const mark = CLI_INTERACTIVE ? `${COLORS.blue}◇${COLORS.reset}` : '->';
|
|
76
|
+
console.log(` ${mark} ${msg}`);
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
function logOk(msg) {
|
|
75
|
-
|
|
80
|
+
const mark = CLI_INTERACTIVE ? `${COLORS.green}◆${COLORS.reset}` : 'ok';
|
|
81
|
+
console.log(` ${mark} ${msg}`);
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
function logWarn(msg) {
|
|
79
|
-
|
|
85
|
+
const mark = CLI_INTERACTIVE ? `${COLORS.yellow}▲${COLORS.reset}` : 'warn';
|
|
86
|
+
console.warn(` ${mark} ${msg}`);
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
function logErr(msg) {
|
|
83
|
-
|
|
90
|
+
const mark = CLI_INTERACTIVE ? `${COLORS.red}✕${COLORS.reset}` : 'err';
|
|
91
|
+
console.error(` ${mark} ${msg}`);
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
function heading(text) {
|
|
87
|
-
|
|
95
|
+
if (!CLI_INTERACTIVE) {
|
|
96
|
+
console.log(`\n${text}`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
console.log(`\n${COLORS.bold}${COLORS.cyan}${text}${COLORS.reset}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function cliBanner(title = APP_NAME, subtitle = 'local agent control') {
|
|
103
|
+
if (!CLI_INTERACTIVE) return;
|
|
104
|
+
const c = COLORS;
|
|
105
|
+
const width = 38;
|
|
106
|
+
const stripAnsi = (text) => String(text).replace(/\x1b\[[0-9;]*m/g, '');
|
|
107
|
+
const boxLine = (content) => {
|
|
108
|
+
const padding = Math.max(0, width - stripAnsi(content).length);
|
|
109
|
+
console.log(` ${c.cyan}│${c.reset} ${content}${' '.repeat(padding)} ${c.cyan}│${c.reset}`);
|
|
110
|
+
};
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(` ${c.cyan}╭────────────────────────────────────────╮${c.reset}`);
|
|
113
|
+
boxLine(`${c.bold}${c.magenta}NeoAgent${c.reset} ${c.dim}arcade ops console${c.reset}`);
|
|
114
|
+
boxLine(`${c.bold}${title}${c.reset} ${c.dim}${subtitle}${c.reset}`);
|
|
115
|
+
console.log(` ${c.cyan}╰────────────────────────────────────────╯${c.reset}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function cliSection(text) {
|
|
119
|
+
if (CLI_INTERACTIVE) {
|
|
120
|
+
console.log(`${COLORS.dim} ──${COLORS.reset} ${COLORS.bold}${text}${COLORS.reset}`);
|
|
121
|
+
} else {
|
|
122
|
+
console.log(text);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function statusLine(ok, label, value, hint = '') {
|
|
127
|
+
const mark = ok ? (CLI_INTERACTIVE ? `${COLORS.green}●${COLORS.reset}` : 'ok') : (CLI_INTERACTIVE ? `${COLORS.yellow}●${COLORS.reset}` : 'warn');
|
|
128
|
+
const padded = String(label).padEnd(9);
|
|
129
|
+
const suffix = hint ? ` ${COLORS.dim}${hint}${COLORS.reset}` : '';
|
|
130
|
+
console.log(` ${mark} ${padded} ${value}${suffix}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function rememberInstallAction(message) {
|
|
134
|
+
if (!installActionItems.includes(message)) {
|
|
135
|
+
installActionItems.push(message);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function printInstallActionItems() {
|
|
140
|
+
if (installActionItems.length === 0) return;
|
|
141
|
+
heading('Post-install actions');
|
|
142
|
+
for (const item of installActionItems) {
|
|
143
|
+
logWarn(item);
|
|
144
|
+
}
|
|
88
145
|
}
|
|
89
146
|
|
|
90
147
|
function detectPlatform() {
|
|
@@ -477,6 +534,43 @@ async function askSecret(question, currentValue = '') {
|
|
|
477
534
|
});
|
|
478
535
|
}
|
|
479
536
|
|
|
537
|
+
function defaultEnvLines(current = {}) {
|
|
538
|
+
const defaultVmBaseImageUrl = getDefaultVmBaseImageUrl();
|
|
539
|
+
const port = current.PORT || '3333';
|
|
540
|
+
const publicUrl = current.PUBLIC_URL || '';
|
|
541
|
+
const secureCookies = current.SECURE_COOKIES ||
|
|
542
|
+
(String(publicUrl || '').trim().startsWith('https://') ? 'true' : 'false');
|
|
543
|
+
const trustProxy = current.TRUST_PROXY || secureCookies;
|
|
544
|
+
return [
|
|
545
|
+
'NODE_ENV=production',
|
|
546
|
+
`PORT=${port}`,
|
|
547
|
+
publicUrl ? `PUBLIC_URL=${publicUrl}` : '',
|
|
548
|
+
`SECURE_COOKIES=${String(secureCookies || '').trim().toLowerCase() === 'true' ? 'true' : 'false'}`,
|
|
549
|
+
`TRUST_PROXY=${String(trustProxy || '').trim().toLowerCase() === 'true' ? 'true' : 'false'}`,
|
|
550
|
+
`SESSION_SECRET=${current.SESSION_SECRET || randomSecret()}`,
|
|
551
|
+
`NEOAGENT_PROFILE=${current.NEOAGENT_PROFILE || 'prod'}`,
|
|
552
|
+
`NEOAGENT_DEPLOYMENT_MODE=${parseDeploymentMode(current.NEOAGENT_DEPLOYMENT_MODE || 'self_hosted')}`,
|
|
553
|
+
`NEOAGENT_RELEASE_CHANNEL=${parseReleaseChannel(current.NEOAGENT_RELEASE_CHANNEL || 'stable') || 'stable'}`,
|
|
554
|
+
`NEOAGENT_VM_BASE_IMAGE_URL=${current.NEOAGENT_VM_BASE_IMAGE_URL || defaultVmBaseImageUrl}`,
|
|
555
|
+
`NEOAGENT_VM_MEMORY_MB=${current.NEOAGENT_VM_MEMORY_MB || '4096'}`,
|
|
556
|
+
`NEOAGENT_VM_CPUS=${current.NEOAGENT_VM_CPUS || '2'}`,
|
|
557
|
+
`NEOAGENT_VM_GUEST_TOKEN=${current.NEOAGENT_VM_GUEST_TOKEN || randomSecret()}`,
|
|
558
|
+
current.XAI_BASE_URL ? `XAI_BASE_URL=${current.XAI_BASE_URL}` : 'XAI_BASE_URL=https://api.x.ai/v1',
|
|
559
|
+
current.OLLAMA_URL ? `OLLAMA_URL=${current.OLLAMA_URL}` : 'OLLAMA_URL=http://localhost:11434',
|
|
560
|
+
current.DEEPGRAM_BASE_URL ? `DEEPGRAM_BASE_URL=${current.DEEPGRAM_BASE_URL}` : 'DEEPGRAM_BASE_URL=https://api.deepgram.com',
|
|
561
|
+
current.DEEPGRAM_MODEL ? `DEEPGRAM_MODEL=${current.DEEPGRAM_MODEL}` : 'DEEPGRAM_MODEL=nova-3',
|
|
562
|
+
current.DEEPGRAM_LANGUAGE ? `DEEPGRAM_LANGUAGE=${current.DEEPGRAM_LANGUAGE}` : 'DEEPGRAM_LANGUAGE=multi',
|
|
563
|
+
].filter(Boolean);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function writeDefaultEnvFile() {
|
|
567
|
+
ensureRuntimeDirs();
|
|
568
|
+
const current = Object.fromEntries(parseEnv(readEnvFileRaw()).entries());
|
|
569
|
+
fs.writeFileSync(ENV_FILE, `${defaultEnvLines(current).join('\n')}\n`, { mode: 0o600 });
|
|
570
|
+
logOk(`Wrote default config to ${ENV_FILE}`);
|
|
571
|
+
rememberInstallAction('Add provider keys with `neoagent setup`, `neoagent env set KEY VALUE`, or the login commands when you are ready.');
|
|
572
|
+
}
|
|
573
|
+
|
|
480
574
|
async function cmdSetup() {
|
|
481
575
|
heading('Environment Setup');
|
|
482
576
|
ensureRuntimeDirs();
|
|
@@ -1193,6 +1287,50 @@ function installDependencies() {
|
|
|
1193
1287
|
logOk('Dependencies installed');
|
|
1194
1288
|
}
|
|
1195
1289
|
|
|
1290
|
+
function assertSupportedNodeRuntime() {
|
|
1291
|
+
const major = Number(String(process.versions.node || '').split('.')[0]);
|
|
1292
|
+
if (!Number.isInteger(major) || major < 20) {
|
|
1293
|
+
throw new Error(`NeoAgent requires Node.js 20 or newer. Current runtime is ${process.versions.node || 'unknown'}.`);
|
|
1294
|
+
}
|
|
1295
|
+
logOk(`Node.js ${process.versions.node}`);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function installPreflight() {
|
|
1299
|
+
heading('Installer preflight');
|
|
1300
|
+
ensureRuntimeDirs();
|
|
1301
|
+
assertSupportedNodeRuntime();
|
|
1302
|
+
|
|
1303
|
+
if (!commandExists('npm')) {
|
|
1304
|
+
throw new Error('npm was not found in PATH. Install Node.js 20+ with npm, then run `neoagent install` again.');
|
|
1305
|
+
}
|
|
1306
|
+
const npmVersion = runQuiet('npm', ['--version']);
|
|
1307
|
+
logOk(`npm ${npmVersion.status === 0 ? npmVersion.stdout.trim() : '(version unknown)'}`);
|
|
1308
|
+
|
|
1309
|
+
const platform = detectPlatform();
|
|
1310
|
+
if (platform === 'other') {
|
|
1311
|
+
rememberInstallAction('Automatic service installation is available on macOS and Linux. This machine will use a detached process fallback.');
|
|
1312
|
+
} else {
|
|
1313
|
+
logOk(`platform ${platform}`);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
if (platform === 'macos' && !commandExists('launchctl')) {
|
|
1317
|
+
rememberInstallAction('launchctl was not found, so the installer will use a detached process fallback instead of a login service.');
|
|
1318
|
+
}
|
|
1319
|
+
if (platform === 'linux' && !commandExists('systemctl')) {
|
|
1320
|
+
rememberInstallAction('systemctl was not found, so the installer will use a detached process fallback instead of a user service.');
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
const port = loadEnvPort();
|
|
1324
|
+
const portOwner = commandExists('lsof')
|
|
1325
|
+
? runQuiet('lsof', ['-nP', '-iTCP:' + port, '-sTCP:LISTEN'])
|
|
1326
|
+
: null;
|
|
1327
|
+
if (portOwner && portOwner.status === 0 && portOwner.stdout.trim()) {
|
|
1328
|
+
logInfo(`Port ${port} already has a listener; install will keep existing processes unless service start replaces them.`);
|
|
1329
|
+
} else {
|
|
1330
|
+
logOk(`port ${port} available`);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1196
1334
|
function buildBundledWebClientIfPossible({ required = false } = {}) {
|
|
1197
1335
|
heading('Web Client');
|
|
1198
1336
|
return buildWebClient({
|
|
@@ -1281,7 +1419,7 @@ function startFallback() {
|
|
|
1281
1419
|
logOk(`Started detached process (pid ${child.pid})`);
|
|
1282
1420
|
}
|
|
1283
1421
|
|
|
1284
|
-
async function ensureQemuInstalled() {
|
|
1422
|
+
async function ensureQemuInstalled({ required = false } = {}) {
|
|
1285
1423
|
heading('Ensure QEMU Installed');
|
|
1286
1424
|
const platform = detectPlatform();
|
|
1287
1425
|
|
|
@@ -1295,35 +1433,54 @@ async function ensureQemuInstalled() {
|
|
|
1295
1433
|
|
|
1296
1434
|
logInfo('QEMU components not found. Attempting to install...');
|
|
1297
1435
|
|
|
1298
|
-
|
|
1299
|
-
if (
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1436
|
+
try {
|
|
1437
|
+
if (platform === 'macos') {
|
|
1438
|
+
if (!commandExists('brew')) {
|
|
1439
|
+
const message = 'Homebrew is required for automatic QEMU install on macOS. Install Homebrew from https://brew.sh/ and run `neoagent install` again.';
|
|
1440
|
+
if (required) throw new Error(message);
|
|
1441
|
+
logWarn('Homebrew not found; VM features will stay unavailable until QEMU is installed.');
|
|
1442
|
+
rememberInstallAction(message);
|
|
1443
|
+
return false;
|
|
1444
|
+
}
|
|
1445
|
+
logInfo('Running "brew install qemu"...');
|
|
1446
|
+
runOrThrow('brew', ['install', 'qemu']);
|
|
1447
|
+
} else if (platform === 'linux') {
|
|
1448
|
+
const isArm = process.arch === 'arm64' || process.arch === 'aarch64';
|
|
1449
|
+
if (commandExists('apt-get')) {
|
|
1450
|
+
const pkgs = ['qemu-utils'];
|
|
1451
|
+
if (isArm) {
|
|
1452
|
+
pkgs.push('qemu-system-arm');
|
|
1453
|
+
} else {
|
|
1454
|
+
pkgs.push('qemu-system-x86');
|
|
1455
|
+
}
|
|
1456
|
+
logInfo(`Running "sudo apt-get update && sudo apt-get install -y ${pkgs.join(' ')}"...`);
|
|
1457
|
+
runOrThrow('sudo', ['apt-get', 'update']);
|
|
1458
|
+
runOrThrow('sudo', ['apt-get', 'install', '-y', ...pkgs]);
|
|
1459
|
+
} else if (commandExists('dnf')) {
|
|
1460
|
+
logInfo('Running "sudo dnf install -y qemu-kvm qemu-img"...');
|
|
1461
|
+
runOrThrow('sudo', ['dnf', 'install', '-y', 'qemu-kvm', 'qemu-img']);
|
|
1462
|
+
} else if (commandExists('yum')) {
|
|
1463
|
+
logInfo('Running "sudo yum install -y qemu-kvm qemu-img"...');
|
|
1464
|
+
runOrThrow('sudo', ['yum', 'install', '-y', 'qemu-kvm', 'qemu-img']);
|
|
1310
1465
|
} else {
|
|
1311
|
-
|
|
1466
|
+
const message = 'Unsupported Linux package manager for automatic QEMU install. Install qemu-system and qemu-utils, then run `neoagent install` again.';
|
|
1467
|
+
if (required) throw new Error(message);
|
|
1468
|
+
logWarn('Could not find apt-get, dnf, or yum for QEMU installation.');
|
|
1469
|
+
rememberInstallAction(message);
|
|
1470
|
+
return false;
|
|
1312
1471
|
}
|
|
1313
|
-
logInfo(`Running "sudo apt-get update && sudo apt-get install -y ${pkgs.join(' ')}"...`);
|
|
1314
|
-
runOrThrow('sudo', ['apt-get', 'update']);
|
|
1315
|
-
runOrThrow('sudo', ['apt-get', 'install', '-y', ...pkgs]);
|
|
1316
|
-
} else if (commandExists('dnf')) {
|
|
1317
|
-
logInfo('Running "sudo dnf install -y qemu-kvm qemu-img"...');
|
|
1318
|
-
runOrThrow('sudo', ['dnf', 'install', '-y', 'qemu-kvm', 'qemu-img']);
|
|
1319
|
-
} else if (commandExists('yum')) {
|
|
1320
|
-
logInfo('Running "sudo yum install -y qemu-kvm qemu-img"...');
|
|
1321
|
-
runOrThrow('sudo', ['yum', 'install', '-y', 'qemu-kvm', 'qemu-img']);
|
|
1322
1472
|
} else {
|
|
1323
|
-
|
|
1473
|
+
const message = 'Unsupported platform for automatic QEMU installation. Install QEMU manually if you need VM-isolated runtime features.';
|
|
1474
|
+
if (required) throw new Error(message);
|
|
1475
|
+
logWarn('Skipping QEMU auto-install on this platform.');
|
|
1476
|
+
rememberInstallAction(message);
|
|
1477
|
+
return false;
|
|
1324
1478
|
}
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1479
|
+
} catch (err) {
|
|
1480
|
+
if (required) throw err;
|
|
1481
|
+
logWarn(`QEMU install did not complete: ${err.message}`);
|
|
1482
|
+
rememberInstallAction('Install QEMU manually or rerun `neoagent install` after your package manager is ready.');
|
|
1483
|
+
return false;
|
|
1327
1484
|
}
|
|
1328
1485
|
|
|
1329
1486
|
const verifiedSystem = commandExists('qemu-system-x86_64') || commandExists('qemu-system-aarch64');
|
|
@@ -1331,8 +1488,13 @@ async function ensureQemuInstalled() {
|
|
|
1331
1488
|
|
|
1332
1489
|
if (verifiedSystem && verifiedImg) {
|
|
1333
1490
|
logOk('QEMU installed successfully');
|
|
1491
|
+
return true;
|
|
1334
1492
|
} else {
|
|
1335
|
-
|
|
1493
|
+
const message = 'QEMU installation finished but required binaries were not found in PATH.';
|
|
1494
|
+
if (required) throw new Error(message);
|
|
1495
|
+
logWarn(message);
|
|
1496
|
+
rememberInstallAction('Ensure qemu-system and qemu-img are available in PATH, then run `neoagent install` again.');
|
|
1497
|
+
return false;
|
|
1336
1498
|
}
|
|
1337
1499
|
}
|
|
1338
1500
|
|
|
@@ -1341,7 +1503,7 @@ function ensureYtDlpInstalled() {
|
|
|
1341
1503
|
if (commandExists('yt-dlp')) {
|
|
1342
1504
|
const ver = runQuiet('yt-dlp', ['--version']);
|
|
1343
1505
|
logOk(`yt-dlp ${ver.status === 0 ? ver.stdout.trim() : '(version unknown)'}`);
|
|
1344
|
-
return;
|
|
1506
|
+
return true;
|
|
1345
1507
|
}
|
|
1346
1508
|
|
|
1347
1509
|
logInfo('yt-dlp not found. Attempting to install...');
|
|
@@ -1349,16 +1511,19 @@ function ensureYtDlpInstalled() {
|
|
|
1349
1511
|
|
|
1350
1512
|
if (platform === 'macos') {
|
|
1351
1513
|
if (!commandExists('brew')) {
|
|
1352
|
-
logWarn('Homebrew not found
|
|
1353
|
-
|
|
1514
|
+
logWarn('Homebrew not found; skipping yt-dlp auto-install.');
|
|
1515
|
+
rememberInstallAction('Install yt-dlp with `brew install yt-dlp` if you need video/audio extraction.');
|
|
1516
|
+
return false;
|
|
1354
1517
|
}
|
|
1355
1518
|
try {
|
|
1356
1519
|
runOrThrow('brew', ['install', 'yt-dlp']);
|
|
1357
1520
|
logOk('yt-dlp installed via Homebrew');
|
|
1521
|
+
return true;
|
|
1358
1522
|
} catch {
|
|
1359
|
-
logWarn('yt-dlp install failed.
|
|
1523
|
+
logWarn('yt-dlp install failed.');
|
|
1524
|
+
rememberInstallAction('Install yt-dlp with `brew install yt-dlp` if you need video/audio extraction.');
|
|
1525
|
+
return false;
|
|
1360
1526
|
}
|
|
1361
|
-
return;
|
|
1362
1527
|
}
|
|
1363
1528
|
|
|
1364
1529
|
if (platform === 'linux') {
|
|
@@ -1366,7 +1531,7 @@ function ensureYtDlpInstalled() {
|
|
|
1366
1531
|
try {
|
|
1367
1532
|
runOrThrow('pipx', ['install', 'yt-dlp']);
|
|
1368
1533
|
logOk('yt-dlp installed via pipx');
|
|
1369
|
-
return;
|
|
1534
|
+
return true;
|
|
1370
1535
|
} catch {
|
|
1371
1536
|
// fall through to pip3
|
|
1372
1537
|
}
|
|
@@ -1375,24 +1540,39 @@ function ensureYtDlpInstalled() {
|
|
|
1375
1540
|
try {
|
|
1376
1541
|
runOrThrow('pip3', ['install', '--user', 'yt-dlp']);
|
|
1377
1542
|
logOk('yt-dlp installed via pip3');
|
|
1378
|
-
return;
|
|
1543
|
+
return true;
|
|
1379
1544
|
} catch {
|
|
1380
1545
|
// fall through to warn
|
|
1381
1546
|
}
|
|
1382
1547
|
}
|
|
1383
|
-
logWarn('Could not install yt-dlp automatically.
|
|
1548
|
+
logWarn('Could not install yt-dlp automatically.');
|
|
1549
|
+
rememberInstallAction('Install yt-dlp with `pipx install yt-dlp` or your OS package manager if you need video/audio extraction.');
|
|
1550
|
+
return false;
|
|
1384
1551
|
}
|
|
1552
|
+
rememberInstallAction('Install yt-dlp manually if you need video/audio extraction.');
|
|
1553
|
+
return false;
|
|
1385
1554
|
}
|
|
1386
1555
|
|
|
1387
1556
|
async function cmdInstall() {
|
|
1557
|
+
installActionItems.length = 0;
|
|
1558
|
+
cliBanner(`Install ${APP_NAME}`, 'guided bootstrap');
|
|
1388
1559
|
heading(`Install ${APP_NAME}`);
|
|
1560
|
+
installPreflight();
|
|
1561
|
+
|
|
1389
1562
|
if (!fs.existsSync(ENV_FILE)) {
|
|
1390
|
-
|
|
1391
|
-
|
|
1563
|
+
if (process.stdin.isTTY) {
|
|
1564
|
+
logWarn('.env not found; starting guided setup');
|
|
1565
|
+
await cmdSetup();
|
|
1566
|
+
} else {
|
|
1567
|
+
logWarn('.env not found and stdin is not interactive; writing a secure default config');
|
|
1568
|
+
writeDefaultEnvFile();
|
|
1569
|
+
}
|
|
1570
|
+
} else {
|
|
1571
|
+
logOk(`Using config ${ENV_FILE}`);
|
|
1392
1572
|
}
|
|
1393
1573
|
|
|
1394
1574
|
installDependencies();
|
|
1395
|
-
await ensureQemuInstalled();
|
|
1575
|
+
await ensureQemuInstalled({ required: false });
|
|
1396
1576
|
ensureYtDlpInstalled();
|
|
1397
1577
|
buildBundledWebClientIfPossible({ required: true });
|
|
1398
1578
|
|
|
@@ -1407,19 +1587,25 @@ async function cmdInstall() {
|
|
|
1407
1587
|
|
|
1408
1588
|
const port = loadEnvPort();
|
|
1409
1589
|
logOk(`Running on http://localhost:${port}`);
|
|
1590
|
+
printInstallActionItems();
|
|
1591
|
+
heading('Ready');
|
|
1592
|
+
logInfo(`Open http://localhost:${port} or run \`neoagent status\` for a health check.`);
|
|
1410
1593
|
}
|
|
1411
1594
|
|
|
1412
1595
|
function cmdStart() {
|
|
1596
|
+
cliBanner(`Start ${APP_NAME}`, 'boot sequence');
|
|
1413
1597
|
heading(`Start ${APP_NAME}`);
|
|
1414
1598
|
const platform = detectPlatform();
|
|
1415
1599
|
|
|
1416
1600
|
if (platform === 'macos' && fs.existsSync(PLIST_DST)) {
|
|
1601
|
+
logInfo('Handing launch to launchd');
|
|
1417
1602
|
installMacService();
|
|
1418
1603
|
logOk('launchd start requested');
|
|
1419
1604
|
return;
|
|
1420
1605
|
}
|
|
1421
1606
|
|
|
1422
1607
|
if (platform === 'linux' && fs.existsSync(SYSTEMD_UNIT)) {
|
|
1608
|
+
logInfo('Handing launch to systemd');
|
|
1423
1609
|
runOrThrow('systemctl', ['--user', 'start', 'neoagent']);
|
|
1424
1610
|
runOrThrow('systemctl', ['--user', 'is-active', '--quiet', 'neoagent']);
|
|
1425
1611
|
logOk('systemd start requested');
|
|
@@ -1512,54 +1698,68 @@ function cmdUninstall() {
|
|
|
1512
1698
|
}
|
|
1513
1699
|
|
|
1514
1700
|
async function cmdStatus() {
|
|
1701
|
+
cliBanner(`${APP_NAME} Status`, 'systems sweep');
|
|
1515
1702
|
heading(`${APP_NAME} Status`);
|
|
1516
1703
|
const port = loadEnvPort();
|
|
1517
1704
|
const running = await isPortOpen(port);
|
|
1518
1705
|
const releaseChannel = currentReleaseChannel();
|
|
1519
1706
|
const platform = detectPlatform();
|
|
1520
1707
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1708
|
+
cliSection('Runtime');
|
|
1709
|
+
statusLine(
|
|
1710
|
+
running,
|
|
1711
|
+
'server',
|
|
1712
|
+
running ? `http://localhost:${port}` : `not reachable on port ${port}`,
|
|
1713
|
+
);
|
|
1526
1714
|
|
|
1527
1715
|
if (platform === 'macos' && fs.existsSync(PLIST_DST)) {
|
|
1528
1716
|
const svcRes = runQuiet('launchctl', ['list', SERVICE_LABEL]);
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1717
|
+
statusLine(
|
|
1718
|
+
svcRes.status === 0 && Boolean(svcRes.stdout.trim()),
|
|
1719
|
+
'service',
|
|
1720
|
+
svcRes.status === 0 && svcRes.stdout.trim()
|
|
1721
|
+
? `launchd (${SERVICE_LABEL})`
|
|
1722
|
+
: 'launchd unit not loaded',
|
|
1723
|
+
svcRes.status === 0 && svcRes.stdout.trim() ? '' : 'run: neoagent install',
|
|
1724
|
+
);
|
|
1534
1725
|
} else if (platform === 'linux' && fs.existsSync(SYSTEMD_UNIT)) {
|
|
1535
1726
|
const svcRes = runQuiet('systemctl', ['--user', 'is-active', 'neoagent']);
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1727
|
+
statusLine(
|
|
1728
|
+
svcRes.status === 0 && svcRes.stdout.trim() === 'active',
|
|
1729
|
+
'service',
|
|
1730
|
+
svcRes.status === 0 && svcRes.stdout.trim() === 'active'
|
|
1731
|
+
? 'systemd (neoagent)'
|
|
1732
|
+
: 'systemd unit not active',
|
|
1733
|
+
svcRes.status === 0 && svcRes.stdout.trim() === 'active' ? '' : 'run: neoagent install',
|
|
1734
|
+
);
|
|
1541
1735
|
}
|
|
1542
1736
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1737
|
+
cliSection('Assets');
|
|
1738
|
+
statusLine(
|
|
1739
|
+
fs.existsSync(ENV_FILE),
|
|
1740
|
+
'config',
|
|
1741
|
+
fs.existsSync(ENV_FILE) ? ENV_FILE : '.env not found',
|
|
1742
|
+
fs.existsSync(ENV_FILE) ? '' : 'run: neoagent setup',
|
|
1743
|
+
);
|
|
1548
1744
|
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1745
|
+
statusLine(
|
|
1746
|
+
hasBundledWebClient(WEB_CLIENT_DIR),
|
|
1747
|
+
'web',
|
|
1748
|
+
hasBundledWebClient(WEB_CLIENT_DIR)
|
|
1749
|
+
? 'bundled Flutter client present'
|
|
1750
|
+
: 'no bundled client',
|
|
1751
|
+
hasBundledWebClient(WEB_CLIENT_DIR) ? '' : 'run: neoagent rebuild-web',
|
|
1752
|
+
);
|
|
1554
1753
|
|
|
1555
1754
|
console.log('');
|
|
1556
|
-
|
|
1557
|
-
console.log(`
|
|
1558
|
-
console.log(`
|
|
1755
|
+
cliSection('Build');
|
|
1756
|
+
console.log(` install ${APP_DIR}`);
|
|
1757
|
+
console.log(` version ${currentInstalledVersionLabel()}`);
|
|
1758
|
+
console.log(` channel ${releaseChannelSummary(releaseChannel)}`);
|
|
1559
1759
|
|
|
1560
1760
|
const processes = listNeoAgentServerProcesses();
|
|
1561
1761
|
if (processes.length > 0) {
|
|
1562
|
-
console.log(` pids
|
|
1762
|
+
console.log(` pids ${processes.map((proc) => proc.pid).join(', ')}`);
|
|
1563
1763
|
if (processes.length > 1) {
|
|
1564
1764
|
logWarn(`multiple NeoAgent processes detected (${processes.length})`);
|
|
1565
1765
|
}
|
|
@@ -1729,14 +1929,15 @@ function printHelp() {
|
|
|
1729
1929
|
|
|
1730
1930
|
function row(cmd, desc) {
|
|
1731
1931
|
const padded = ` neoagent ${cmd}`.padEnd(W);
|
|
1732
|
-
|
|
1932
|
+
const arrow = CLI_INTERACTIVE ? `${c.cyan}›${c.reset} ` : '';
|
|
1933
|
+
console.log(`${padded}${arrow}${c.dim}${desc}${c.reset}`);
|
|
1733
1934
|
}
|
|
1734
1935
|
|
|
1735
|
-
|
|
1736
|
-
console.log(
|
|
1936
|
+
cliBanner('neoagent', 'command deck');
|
|
1937
|
+
console.log(`\n${c.bold}Usage${c.reset} neoagent <command> [args]\n`);
|
|
1737
1938
|
|
|
1738
|
-
|
|
1739
|
-
row('install', '
|
|
1939
|
+
cliSection('Lifecycle');
|
|
1940
|
+
row('install', 'Guided bootstrap, dependencies, config, service');
|
|
1740
1941
|
row('start', 'Start the server');
|
|
1741
1942
|
row('stop', 'Stop the server');
|
|
1742
1943
|
row('restart', 'Stop, then start');
|
|
@@ -1745,7 +1946,7 @@ function printHelp() {
|
|
|
1745
1946
|
row('uninstall', 'Remove the system service');
|
|
1746
1947
|
console.log('');
|
|
1747
1948
|
|
|
1748
|
-
|
|
1949
|
+
cliSection('Configuration');
|
|
1749
1950
|
row('setup', 'Interactive configuration wizard');
|
|
1750
1951
|
row('env list', 'List all variables (secrets masked)');
|
|
1751
1952
|
row('env get KEY', 'Print a single variable');
|
|
@@ -1755,7 +1956,7 @@ function printHelp() {
|
|
|
1755
1956
|
row('channel stable|beta', 'Switch release channel');
|
|
1756
1957
|
console.log('');
|
|
1757
1958
|
|
|
1758
|
-
|
|
1959
|
+
cliSection('Updates & Auth');
|
|
1759
1960
|
row('update', 'Update to latest on current channel');
|
|
1760
1961
|
row('update stable|beta', 'Update and switch channel');
|
|
1761
1962
|
row('login github-copilot','Authenticate GitHub Copilot');
|
|
@@ -1764,7 +1965,7 @@ function printHelp() {
|
|
|
1764
1965
|
row('login grok-oauth', 'Authenticate Grok (xAI OAuth)');
|
|
1765
1966
|
console.log('');
|
|
1766
1967
|
|
|
1767
|
-
|
|
1968
|
+
cliSection('Maintenance');
|
|
1768
1969
|
row('migrate', 'Migrate from another agent installation');
|
|
1769
1970
|
row('migrate dry-run', 'Preview what would be migrated');
|
|
1770
1971
|
row('rebuild-web', 'Rebuild the bundled Flutter web client');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neoagent",
|
|
3
|
-
"version": "2.4.1-beta.
|
|
3
|
+
"version": "2.4.1-beta.21",
|
|
4
4
|
"description": "Proactive personal AI agent with no limits",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"main": "server/index.js",
|
|
@@ -38,7 +38,18 @@
|
|
|
38
38
|
"flutter:run:web": "cd flutter_app && flutter run -d chrome --dart-define=NEOAGENT_WEB_BUILD_ID=$(node ../scripts/web_build_id.js)",
|
|
39
39
|
"flutter:build:web": "cd flutter_app && flutter build web --output ../server/public --dart-define=NEOAGENT_BACKEND_URL=${NEOAGENT_BACKEND_URL:-} --dart-define=NEOAGENT_WEB_BUILD_ID=$(node ../scripts/web_build_id.js)",
|
|
40
40
|
"manage": "node bin/neoagent.js",
|
|
41
|
-
"test": "
|
|
41
|
+
"test": "npm run test:backend",
|
|
42
|
+
"test:unit": "node --test --test-reporter=spec test/backend/unit/*.test.js",
|
|
43
|
+
"test:integration": "node --test --test-reporter=spec test/integration/*.test.js",
|
|
44
|
+
"test:security": "node --test --test-reporter=spec test/security/*.test.js",
|
|
45
|
+
"test:contract": "node --test --test-reporter=spec test/contract/*.test.js",
|
|
46
|
+
"test:e2e": "node --test --test-reporter=spec test/e2e/*.test.js",
|
|
47
|
+
"test:ws": "node --test --test-reporter=spec test/websocket/*.test.js",
|
|
48
|
+
"test:backend": "npm run test:unit && npm run test:integration && npm run test:security && npm run test:contract && npm run test:e2e && npm run test:ws",
|
|
49
|
+
"test:load": "node test/load/auth_load.js",
|
|
50
|
+
"flutter:test": "cd flutter_app && flutter test ../test/flutter/unit ../test/flutter/widget",
|
|
51
|
+
"flutter:test:unit": "cd flutter_app && flutter test ../test/flutter/unit",
|
|
52
|
+
"flutter:test:widget": "cd flutter_app && flutter test ../test/flutter/widget",
|
|
42
53
|
"benchmark:tokens": "node scripts/benchmark-token-cost.js",
|
|
43
54
|
"release": "npx semantic-release"
|
|
44
55
|
},
|
|
@@ -104,7 +115,10 @@
|
|
|
104
115
|
"devDependencies": {
|
|
105
116
|
"@docusaurus/core": "3.10.0",
|
|
106
117
|
"@docusaurus/preset-classic": "3.10.0",
|
|
118
|
+
"autocannon": "^7.15.0",
|
|
107
119
|
"react": "18.3.1",
|
|
108
|
-
"react-dom": "18.3.1"
|
|
120
|
+
"react-dom": "18.3.1",
|
|
121
|
+
"socket.io-client": "^4.8.3",
|
|
122
|
+
"supertest": "^7.2.2"
|
|
109
123
|
}
|
|
110
124
|
}
|
package/server/config/origins.js
CHANGED
|
@@ -35,7 +35,9 @@ function isAllowedOrigin(origin, options = {}) {
|
|
|
35
35
|
|
|
36
36
|
function validateOrigin(origin, callback, options = {}) {
|
|
37
37
|
if (isAllowedOrigin(origin, options)) return callback(null, true);
|
|
38
|
-
|
|
38
|
+
const error = new Error(`Origin not allowed: ${origin || 'unknown'}`);
|
|
39
|
+
error.statusCode = 403;
|
|
40
|
+
return callback(error);
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
module.exports = {
|