neoagent 3.0.1-beta.8 → 3.1.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/.env.example +2 -10
- package/README.md +12 -3
- package/docs/automation.md +37 -0
- package/docs/benchmarking.md +102 -0
- package/docs/billing.md +34 -8
- package/docs/configuration.md +11 -2
- package/docs/getting-started.md +10 -8
- package/docs/operations.md +1 -1
- package/flutter_app/lib/main.dart +3 -0
- package/flutter_app/lib/main_account_settings.dart +138 -0
- package/flutter_app/lib/main_app_shell.dart +38 -13
- package/flutter_app/lib/main_billing.dart +1465 -0
- package/flutter_app/lib/main_chat.dart +1612 -214
- package/flutter_app/lib/main_controller.dart +398 -26
- package/flutter_app/lib/main_devices.dart +293 -207
- package/flutter_app/lib/main_models.dart +142 -0
- package/flutter_app/lib/main_navigation.dart +19 -1
- package/flutter_app/lib/main_operations.dart +288 -9
- package/flutter_app/lib/main_settings.dart +510 -300
- package/flutter_app/lib/main_shared.dart +2 -0
- package/flutter_app/lib/main_timeline.dart +1378 -0
- package/flutter_app/lib/src/backend_client.dart +149 -19
- package/flutter_app/lib/src/desktop_companion_actions.dart +132 -21
- package/flutter_app/lib/src/desktop_companion_io.dart +65 -1
- package/flutter_app/lib/src/desktop_companion_stub.dart +12 -0
- package/flutter_app/lib/src/desktop_ocr_bridge.dart +2 -0
- package/flutter_app/lib/src/desktop_ocr_bridge_io.dart +125 -0
- package/flutter_app/lib/src/desktop_ocr_bridge_stub.dart +30 -0
- package/flutter_app/lib/src/desktop_passive_history.dart +332 -0
- package/flutter_app/lib/src/recording_bridge_io.dart +80 -72
- package/flutter_app/lib/src/recording_bridge_web.dart +127 -114
- package/flutter_app/lib/src/recording_chunk_queue.dart +149 -0
- package/flutter_app/lib/src/recording_chunk_queue_io.dart +182 -0
- package/flutter_app/lib/src/recording_payloads.dart +9 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +25 -0
- package/flutter_app/windows/runner/flutter_window.cpp +75 -0
- package/landing/index.html +3 -3
- package/lib/manager.js +184 -66
- package/package.json +4 -1
- package/server/admin/access.js +12 -7
- package/server/admin/admin.css +78 -0
- package/server/admin/admin.js +436 -10
- package/server/admin/billing.js +165 -42
- package/server/admin/index.html +80 -5
- package/server/admin/users.js +15 -15
- package/server/db/database.js +125 -20
- package/server/http/routes.js +1 -0
- package/server/http/static.js +4 -2
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/canvaskit/wimp.js.symbols +8475 -8467
- package/server/public/canvaskit/wimp.wasm +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +91077 -87037
- package/server/routes/account.js +53 -0
- package/server/routes/admin.js +345 -64
- package/server/routes/agents.js +203 -21
- package/server/routes/billing.js +5 -0
- package/server/routes/browser.js +8 -1
- package/server/routes/recordings.js +96 -6
- package/server/routes/screenHistory.js +140 -2
- package/server/routes/timeline.js +43 -0
- package/server/services/account/erasure.js +263 -0
- package/server/services/ai/hooks.js +4 -1
- package/server/services/ai/loop/agent_engine_core.js +8 -1
- package/server/services/ai/loop/blank_recovery.js +36 -0
- package/server/services/ai/loop/conversation_loop.js +166 -33
- package/server/services/ai/messagingFallback.js +22 -0
- package/server/services/ai/rate_limits.js +28 -5
- package/server/services/ai/systemPrompt.js +6 -5
- package/server/services/ai/taskAnalysis.js +2 -0
- package/server/services/ai/toolEvidence.js +15 -0
- package/server/services/ai/toolResult.js +40 -0
- package/server/services/ai/tools.js +163 -4
- package/server/services/android/controller.js +6 -2
- package/server/services/billing/plans.js +2 -1
- package/server/services/browser/anti_detection.js +192 -0
- package/server/services/browser/controller.js +180 -54
- package/server/services/desktop/auth.js +3 -0
- package/server/services/desktop/registry.js +50 -2
- package/server/services/integrations/google/calendar.js +22 -14
- package/server/services/manager.js +12 -42
- package/server/services/memory/ingestion_chunking.js +268 -0
- package/server/services/messaging/telnyx.js +9 -8
- package/server/services/recordings/manager.js +60 -27
- package/server/services/runtime/backends/local-vm.js +40 -22
- package/server/services/runtime/docker-vm-manager.js +157 -266
- package/server/services/runtime/guest_bootstrap.js +17 -5
- package/server/services/runtime/guest_image.js +188 -0
- package/server/services/runtime/manager.js +0 -1
- package/server/services/runtime/validation.js +3 -8
- package/server/services/social_video/service.js +60 -10
- package/server/services/tasks/runtime.js +234 -9
- package/server/services/tasks/schedule_utils.js +5 -5
- package/server/services/tasks/task_repository.js +13 -0
- package/server/services/timeline/service.js +558 -0
- package/server/services/wearable/gateway.js +1 -1
- package/server/services/websocket.js +21 -3
- package/server/services/desktop/screenRecorder.js +0 -292
- package/server/services/desktop/screen_recorder_support.js +0 -46
package/lib/manager.js
CHANGED
|
@@ -1432,81 +1432,43 @@ function startFallback() {
|
|
|
1432
1432
|
logOk(`Started detached process (pid ${child.pid})`);
|
|
1433
1433
|
}
|
|
1434
1434
|
|
|
1435
|
-
|
|
1436
|
-
|
|
1435
|
+
// The isolated browser/CLI runtime runs each user's guest agent in a Docker
|
|
1436
|
+
// container built from a baked image (browser + dependencies installed at build
|
|
1437
|
+
// time). Docker cannot be auto-installed safely across platforms, so when it is
|
|
1438
|
+
// missing we surface clear guidance; when it is present we pre-build the guest
|
|
1439
|
+
// image so the first browser/CLI action is instant rather than paying a one-time
|
|
1440
|
+
// multi-minute build on demand.
|
|
1441
|
+
async function ensureContainerRuntime({ required = false } = {}) {
|
|
1442
|
+
heading('Ensure Container Runtime');
|
|
1437
1443
|
const platform = detectPlatform();
|
|
1438
|
-
|
|
1439
|
-
const hasSystem = commandExists('qemu-system-x86_64') || commandExists('qemu-system-aarch64');
|
|
1440
|
-
const hasImg = commandExists('qemu-img');
|
|
1441
1444
|
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
logInfo('QEMU components not found. Attempting to install...');
|
|
1445
|
+
const dockerReady = commandExists('docker')
|
|
1446
|
+
&& spawnSync('docker', ['info'], { stdio: 'ignore', timeout: 10000 }).status === 0;
|
|
1448
1447
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
}
|
|
1458
|
-
logInfo('Running "brew install qemu"...');
|
|
1459
|
-
runOrThrow('brew', ['install', 'qemu']);
|
|
1460
|
-
} else if (platform === 'linux') {
|
|
1461
|
-
const isArm = process.arch === 'arm64' || process.arch === 'aarch64';
|
|
1462
|
-
if (commandExists('apt-get')) {
|
|
1463
|
-
const pkgs = ['qemu-utils'];
|
|
1464
|
-
if (isArm) {
|
|
1465
|
-
pkgs.push('qemu-system-arm');
|
|
1466
|
-
} else {
|
|
1467
|
-
pkgs.push('qemu-system-x86');
|
|
1468
|
-
}
|
|
1469
|
-
logInfo(`Running "sudo apt-get update && sudo apt-get install -y ${pkgs.join(' ')}"...`);
|
|
1470
|
-
runOrThrow('sudo', ['apt-get', 'update']);
|
|
1471
|
-
runOrThrow('sudo', ['apt-get', 'install', '-y', ...pkgs]);
|
|
1472
|
-
} else if (commandExists('dnf')) {
|
|
1473
|
-
logInfo('Running "sudo dnf install -y qemu-kvm qemu-img"...');
|
|
1474
|
-
runOrThrow('sudo', ['dnf', 'install', '-y', 'qemu-kvm', 'qemu-img']);
|
|
1475
|
-
} else if (commandExists('yum')) {
|
|
1476
|
-
logInfo('Running "sudo yum install -y qemu-kvm qemu-img"...');
|
|
1477
|
-
runOrThrow('sudo', ['yum', 'install', '-y', 'qemu-kvm', 'qemu-img']);
|
|
1478
|
-
} else {
|
|
1479
|
-
const message = 'Unsupported Linux package manager for automatic QEMU install. Install qemu-system and qemu-utils, then run `neoagent install` again.';
|
|
1480
|
-
if (required) throw new Error(message);
|
|
1481
|
-
logWarn('Could not find apt-get, dnf, or yum for QEMU installation.');
|
|
1482
|
-
rememberInstallAction(message);
|
|
1483
|
-
return false;
|
|
1484
|
-
}
|
|
1485
|
-
} else {
|
|
1486
|
-
const message = 'Unsupported platform for automatic QEMU installation. Install QEMU manually if you need VM-isolated runtime features.';
|
|
1487
|
-
if (required) throw new Error(message);
|
|
1488
|
-
logWarn('Skipping QEMU auto-install on this platform.');
|
|
1489
|
-
rememberInstallAction(message);
|
|
1490
|
-
return false;
|
|
1491
|
-
}
|
|
1492
|
-
} catch (err) {
|
|
1493
|
-
if (required) throw err;
|
|
1494
|
-
logWarn(`QEMU install did not complete: ${err.message}`);
|
|
1495
|
-
rememberInstallAction('Install QEMU manually or rerun `neoagent install` after your package manager is ready.');
|
|
1448
|
+
if (!dockerReady) {
|
|
1449
|
+
const installHint = platform === 'macos'
|
|
1450
|
+
? 'Install Docker Desktop from https://www.docker.com/products/docker-desktop/ and start it.'
|
|
1451
|
+
: 'Install Docker Engine (https://docs.docker.com/engine/install/) and ensure the daemon is running.';
|
|
1452
|
+
const message = `Docker is required for the isolated browser/CLI runtime. ${installHint} Then run \`neoagent install\` again.`;
|
|
1453
|
+
if (required) throw new Error(message);
|
|
1454
|
+
logWarn('Docker not detected; the cloud browser and CLI will be unavailable until Docker is installed and running.');
|
|
1455
|
+
rememberInstallAction(message);
|
|
1496
1456
|
return false;
|
|
1497
1457
|
}
|
|
1498
1458
|
|
|
1499
|
-
|
|
1500
|
-
const verifiedImg = commandExists('qemu-img');
|
|
1459
|
+
logOk('Docker is installed and the daemon is running');
|
|
1501
1460
|
|
|
1502
|
-
|
|
1503
|
-
|
|
1461
|
+
try {
|
|
1462
|
+
logInfo('Building the guest runtime image (one-time; downloads the browser and dependencies)…');
|
|
1463
|
+
const { GuestImageBuilder } = require('../server/services/runtime/guest_image');
|
|
1464
|
+
await new GuestImageBuilder({ runtimeProfile: 'browser_cli' }).ensure();
|
|
1465
|
+
logOk('Guest runtime image is ready');
|
|
1504
1466
|
return true;
|
|
1505
|
-
}
|
|
1506
|
-
const message =
|
|
1467
|
+
} catch (err) {
|
|
1468
|
+
const message = `Guest runtime image build did not complete: ${err.message}`;
|
|
1507
1469
|
if (required) throw new Error(message);
|
|
1508
1470
|
logWarn(message);
|
|
1509
|
-
rememberInstallAction('
|
|
1471
|
+
rememberInstallAction('Rerun `neoagent install` to retry building the guest runtime image, or it will build automatically on first use.');
|
|
1510
1472
|
return false;
|
|
1511
1473
|
}
|
|
1512
1474
|
}
|
|
@@ -1585,7 +1547,7 @@ async function cmdInstall() {
|
|
|
1585
1547
|
}
|
|
1586
1548
|
|
|
1587
1549
|
installDependencies();
|
|
1588
|
-
await
|
|
1550
|
+
await ensureContainerRuntime({ required: false });
|
|
1589
1551
|
ensureYtDlpInstalled();
|
|
1590
1552
|
buildBundledWebClientIfPossible({ required: true });
|
|
1591
1553
|
|
|
@@ -2000,6 +1962,152 @@ function cmdVersion() {
|
|
|
2000
1962
|
console.log(currentInstalledVersionLabel());
|
|
2001
1963
|
}
|
|
2002
1964
|
|
|
1965
|
+
async function cmdBilling(args = []) {
|
|
1966
|
+
const sub = (args[0] || 'status').toLowerCase();
|
|
1967
|
+
|
|
1968
|
+
if (sub === '--help' || sub === '-h' || sub === 'help') {
|
|
1969
|
+
console.log('\nNeoAgent Billing');
|
|
1970
|
+
console.log('Usage: neoagent billing [subcommand]');
|
|
1971
|
+
console.log('');
|
|
1972
|
+
console.log('Subcommands:');
|
|
1973
|
+
console.log(' neoagent billing Show billing configuration status');
|
|
1974
|
+
console.log(' neoagent billing setup Interactive Stripe setup wizard');
|
|
1975
|
+
console.log(' neoagent billing enable Enable billing and restart');
|
|
1976
|
+
console.log(' neoagent billing disable Disable billing and restart');
|
|
1977
|
+
console.log('');
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
if (sub === 'status') {
|
|
1982
|
+
cliBanner('Billing', 'configuration status');
|
|
1983
|
+
heading('Billing Status');
|
|
1984
|
+
const env = Object.fromEntries(parseEnv(readEnvFileRaw()).entries());
|
|
1985
|
+
const enabled = ['1', 'true', 'yes'].includes(String(env.NEOAGENT_BILLING_ENABLED || '').toLowerCase());
|
|
1986
|
+
const hasSecret = Boolean(env.STRIPE_SECRET_KEY);
|
|
1987
|
+
const hasPub = Boolean(env.STRIPE_PUBLISHABLE_KEY);
|
|
1988
|
+
const hasWebhook = Boolean(env.STRIPE_WEBHOOK_SECRET);
|
|
1989
|
+
const trialDays = env.BILLING_TRIAL_DAYS || '14';
|
|
1990
|
+
|
|
1991
|
+
cliSection('Billing config');
|
|
1992
|
+
statusLine(enabled, 'enabled', enabled ? 'yes' : 'no', enabled ? '' : 'run: neoagent billing enable');
|
|
1993
|
+
statusLine(hasSecret, 'secret', hasSecret ? maskEnvValue('SECRET', env.STRIPE_SECRET_KEY) : 'not set', hasSecret ? '' : 'run: neoagent billing setup');
|
|
1994
|
+
statusLine(hasPub, 'public', hasPub ? maskEnvValue('KEY', env.STRIPE_PUBLISHABLE_KEY) : 'not set');
|
|
1995
|
+
statusLine(hasWebhook, 'webhook', hasWebhook ? 'configured' : 'not set');
|
|
1996
|
+
console.log('');
|
|
1997
|
+
cliSection('Trial');
|
|
1998
|
+
console.log(` trial days ${trialDays}`);
|
|
1999
|
+
|
|
2000
|
+
const port = loadEnvPort();
|
|
2001
|
+
const publicUrl = env.PUBLIC_URL || `http://localhost:${port}`;
|
|
2002
|
+
console.log('');
|
|
2003
|
+
cliSection('Webhook endpoint');
|
|
2004
|
+
console.log(` ${publicUrl}/api/billing/webhook`);
|
|
2005
|
+
console.log('');
|
|
2006
|
+
|
|
2007
|
+
if (!hasSecret || !hasPub) {
|
|
2008
|
+
logWarn('Stripe keys not configured. Run `neoagent billing setup` to get started.');
|
|
2009
|
+
} else if (!enabled) {
|
|
2010
|
+
logInfo('Billing is configured but not enabled. Run `neoagent billing enable` to activate.');
|
|
2011
|
+
} else {
|
|
2012
|
+
logOk('Billing is active. Manage plans in Admin › Billing.');
|
|
2013
|
+
}
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
if (sub === 'enable') {
|
|
2018
|
+
heading('Enable Billing');
|
|
2019
|
+
upsertEnvValue('NEOAGENT_BILLING_ENABLED', 'true');
|
|
2020
|
+
logOk('Set NEOAGENT_BILLING_ENABLED=true');
|
|
2021
|
+
logInfo('Restarting NeoAgent to apply changes...');
|
|
2022
|
+
cmdRestart();
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
if (sub === 'disable') {
|
|
2027
|
+
heading('Disable Billing');
|
|
2028
|
+
upsertEnvValue('NEOAGENT_BILLING_ENABLED', 'false');
|
|
2029
|
+
logOk('Set NEOAGENT_BILLING_ENABLED=false');
|
|
2030
|
+
logInfo('Restarting NeoAgent to apply changes...');
|
|
2031
|
+
cmdRestart();
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
if (sub === 'setup') {
|
|
2036
|
+
cliBanner('Billing Setup', 'Stripe configuration');
|
|
2037
|
+
heading('Billing Setup');
|
|
2038
|
+
ensureRuntimeDirs();
|
|
2039
|
+
|
|
2040
|
+
const current = Object.fromEntries(parseEnv(readEnvFileRaw()).entries());
|
|
2041
|
+
const port = loadEnvPort();
|
|
2042
|
+
const publicUrl = current.PUBLIC_URL || `http://localhost:${port}`;
|
|
2043
|
+
|
|
2044
|
+
logInfo('Press Enter to keep the current value shown in brackets.');
|
|
2045
|
+
logInfo(`Webhook endpoint: ${publicUrl}/api/billing/webhook`);
|
|
2046
|
+
console.log('');
|
|
2047
|
+
|
|
2048
|
+
heading('Stripe API keys');
|
|
2049
|
+
logInfo('Find these in your Stripe dashboard: https://dashboard.stripe.com/apikeys');
|
|
2050
|
+
const secretKey = await askSecret('Stripe secret key (sk_live_... or sk_test_...)', current.STRIPE_SECRET_KEY || '');
|
|
2051
|
+
const publishableKey = await askSecret('Stripe publishable key (pk_live_... or pk_test_...)', current.STRIPE_PUBLISHABLE_KEY || '');
|
|
2052
|
+
|
|
2053
|
+
if (secretKey && !secretKey.startsWith('sk_')) {
|
|
2054
|
+
logWarn('Secret key does not start with "sk_" — double-check the value.');
|
|
2055
|
+
}
|
|
2056
|
+
if (publishableKey && !publishableKey.startsWith('pk_')) {
|
|
2057
|
+
logWarn('Publishable key does not start with "pk_" — double-check the value.');
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
heading('Webhook');
|
|
2061
|
+
logInfo(`Register this endpoint in Stripe: ${publicUrl}/api/billing/webhook`);
|
|
2062
|
+
logInfo('Required events: customer.subscription.*, invoice.payment_succeeded, invoice.payment_failed');
|
|
2063
|
+
const webhookSecret = await askSecret('Stripe webhook signing secret (whsec_...)', current.STRIPE_WEBHOOK_SECRET || '');
|
|
2064
|
+
|
|
2065
|
+
if (webhookSecret && !webhookSecret.startsWith('whsec_')) {
|
|
2066
|
+
logWarn('Webhook secret does not start with "whsec_" — double-check the value.');
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
heading('Trial');
|
|
2070
|
+
const trialDaysRaw = await ask('Free trial length in days (0 to disable)', current.BILLING_TRIAL_DAYS || '14');
|
|
2071
|
+
const trialDays = Number(trialDaysRaw);
|
|
2072
|
+
if (!Number.isInteger(trialDays) || trialDays < 0) {
|
|
2073
|
+
throw new Error(`Invalid trial length "${trialDaysRaw}". Must be a non-negative integer.`);
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
if (secretKey) upsertEnvValue('STRIPE_SECRET_KEY', secretKey);
|
|
2077
|
+
if (publishableKey) upsertEnvValue('STRIPE_PUBLISHABLE_KEY', publishableKey);
|
|
2078
|
+
if (webhookSecret) upsertEnvValue('STRIPE_WEBHOOK_SECRET', webhookSecret);
|
|
2079
|
+
upsertEnvValue('BILLING_TRIAL_DAYS', String(trialDays));
|
|
2080
|
+
|
|
2081
|
+
logOk('Stripe credentials saved to .env');
|
|
2082
|
+
|
|
2083
|
+
const isAlreadyEnabled = ['1', 'true', 'yes'].includes(String(current.NEOAGENT_BILLING_ENABLED || '').toLowerCase());
|
|
2084
|
+
let enableNow = isAlreadyEnabled;
|
|
2085
|
+
if (!isAlreadyEnabled) {
|
|
2086
|
+
const enableAnswer = await ask('Enable billing now? (y/N)', 'N');
|
|
2087
|
+
enableNow = enableAnswer.toLowerCase() === 'y' || enableAnswer.toLowerCase() === 'yes';
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
if (enableNow) {
|
|
2091
|
+
upsertEnvValue('NEOAGENT_BILLING_ENABLED', 'true');
|
|
2092
|
+
logOk('Billing enabled');
|
|
2093
|
+
logInfo('Restarting NeoAgent to apply credentials...');
|
|
2094
|
+
cmdRestart();
|
|
2095
|
+
} else {
|
|
2096
|
+
logInfo('Billing is not yet enabled. Run `neoagent billing enable` when ready.');
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
console.log('');
|
|
2100
|
+
heading('Next steps');
|
|
2101
|
+
logInfo('1. Verify the webhook endpoint is reachable from Stripe');
|
|
2102
|
+
logInfo('2. Create subscription plans in Admin › Billing › Plans');
|
|
2103
|
+
logInfo('3. Add a free plan (price = 0) for users without a paid subscription');
|
|
2104
|
+
logInfo(' See: neoagent billing status');
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
throw new Error(`Unknown billing subcommand: ${sub}. Run "neoagent billing --help" for usage.`);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2003
2111
|
function printHelp() {
|
|
2004
2112
|
const c = COLORS;
|
|
2005
2113
|
const W = 38;
|
|
@@ -2047,6 +2155,13 @@ function printHelp() {
|
|
|
2047
2155
|
row('admin', 'Show admin dashboard URL and credentials');
|
|
2048
2156
|
console.log('');
|
|
2049
2157
|
|
|
2158
|
+
cliSection('Billing');
|
|
2159
|
+
row('billing', 'Show billing configuration status');
|
|
2160
|
+
row('billing setup', 'Interactive Stripe setup wizard');
|
|
2161
|
+
row('billing enable', 'Enable billing and restart');
|
|
2162
|
+
row('billing disable', 'Disable billing and restart');
|
|
2163
|
+
console.log('');
|
|
2164
|
+
|
|
2050
2165
|
cliSection('Maintenance');
|
|
2051
2166
|
row('migrate', 'Migrate from another agent installation');
|
|
2052
2167
|
row('migrate dry-run', 'Preview what would be migrated');
|
|
@@ -2106,6 +2221,9 @@ async function runCLI(argv) {
|
|
|
2106
2221
|
case 'login':
|
|
2107
2222
|
await cmdLogin(argv.slice(1));
|
|
2108
2223
|
break;
|
|
2224
|
+
case 'billing':
|
|
2225
|
+
await cmdBilling(argv.slice(1));
|
|
2226
|
+
break;
|
|
2109
2227
|
case 'admin': {
|
|
2110
2228
|
cliBanner('Admin Dashboard', 'credentials');
|
|
2111
2229
|
const adminCreds = readAdminCredentials();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neoagent",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Self-hosted AI agent for long-running tasks, automation, messaging, device control, and local memory",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"main": "server/index.js",
|
|
@@ -51,6 +51,9 @@
|
|
|
51
51
|
"flutter:test": "cd flutter_app && flutter test ../test/flutter/unit ../test/flutter/widget",
|
|
52
52
|
"flutter:test:unit": "cd flutter_app && flutter test ../test/flutter/unit",
|
|
53
53
|
"flutter:test:widget": "cd flutter_app && flutter test ../test/flutter/widget",
|
|
54
|
+
"benchmark:setup": "node benchmark/cli.js setup",
|
|
55
|
+
"benchmark:run": "node benchmark/cli.js run",
|
|
56
|
+
"benchmark:report": "node benchmark/cli.js report",
|
|
54
57
|
"benchmark:tokens": "node scripts/benchmark-token-cost.js",
|
|
55
58
|
"benchmark:memory": "node scripts/benchmark-memory.js",
|
|
56
59
|
"benchmark:memorybench:install": "node scripts/install-memorybench-provider.js",
|
package/server/admin/access.js
CHANGED
|
@@ -113,12 +113,12 @@ async function setSignup(enabled) {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
async function rotateApiKey(btn) {
|
|
116
|
-
if (!
|
|
117
|
-
'Rotate admin API key
|
|
118
|
-
'The current key will stop working immediately
|
|
119
|
-
'
|
|
120
|
-
|
|
121
|
-
)) return;
|
|
116
|
+
if (!await showConfirmModal({
|
|
117
|
+
title: 'Rotate admin API key?',
|
|
118
|
+
body: 'The current key will stop working immediately. Any scripts using it must be updated.<br><br>The new key will be shown once — copy it before leaving.',
|
|
119
|
+
confirmLabel: 'Rotate Key',
|
|
120
|
+
confirmClass: 'btn-danger',
|
|
121
|
+
})) return;
|
|
122
122
|
btn.disabled = true;
|
|
123
123
|
btn.textContent = 'Rotating…';
|
|
124
124
|
try {
|
|
@@ -140,7 +140,12 @@ async function rotateApiKey(btn) {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
async function revokeApiKey(btn) {
|
|
143
|
-
if (!
|
|
143
|
+
if (!await showConfirmModal({
|
|
144
|
+
title: 'Revoke admin API key?',
|
|
145
|
+
body: 'All programmatic access using this key will stop immediately.',
|
|
146
|
+
confirmLabel: 'Revoke Key',
|
|
147
|
+
confirmClass: 'btn-danger',
|
|
148
|
+
})) return;
|
|
144
149
|
btn.disabled = true;
|
|
145
150
|
btn.textContent = 'Revoking…';
|
|
146
151
|
_revealedKey = null;
|
package/server/admin/admin.css
CHANGED
|
@@ -318,3 +318,81 @@ button:disabled,
|
|
|
318
318
|
border: 1px solid rgba(116, 192, 124, 0.25);
|
|
319
319
|
color: var(--success);
|
|
320
320
|
}
|
|
321
|
+
|
|
322
|
+
/* ── Form helpers (used in dynamic modals) ───────────────────────────────── */
|
|
323
|
+
.form-label {
|
|
324
|
+
display: block;
|
|
325
|
+
font-size: 11px;
|
|
326
|
+
font-weight: 600;
|
|
327
|
+
letter-spacing: 0.04em;
|
|
328
|
+
color: var(--text-muted);
|
|
329
|
+
text-transform: uppercase;
|
|
330
|
+
margin-bottom: 6px;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.form-label small {
|
|
334
|
+
font-size: 10px;
|
|
335
|
+
font-weight: 400;
|
|
336
|
+
text-transform: none;
|
|
337
|
+
letter-spacing: 0;
|
|
338
|
+
opacity: 0.75;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.form-input {
|
|
342
|
+
width: 100%;
|
|
343
|
+
background: var(--bg-input);
|
|
344
|
+
border: 1px solid var(--border-light);
|
|
345
|
+
border-radius: var(--radius-sm);
|
|
346
|
+
padding: 9px 12px;
|
|
347
|
+
color: var(--text);
|
|
348
|
+
font-family: var(--font-sans);
|
|
349
|
+
font-size: 13px;
|
|
350
|
+
outline: none;
|
|
351
|
+
transition: border-color 0.15s;
|
|
352
|
+
-webkit-appearance: none;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.form-input:focus {
|
|
356
|
+
border-color: var(--accent);
|
|
357
|
+
box-shadow: 0 0 0 3px var(--accent-muted);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.form-input:disabled {
|
|
361
|
+
opacity: 0.5;
|
|
362
|
+
cursor: default;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* ── Small button variant ────────────────────────────────────────────────── */
|
|
366
|
+
.btn-sm {
|
|
367
|
+
padding: 5px 10px;
|
|
368
|
+
font-size: 12px;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/* ── Generic data table (billing, etc.) ──────────────────────────────────── */
|
|
372
|
+
.data-table {
|
|
373
|
+
width: 100%;
|
|
374
|
+
border-collapse: collapse;
|
|
375
|
+
font-size: 13px;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.data-table th {
|
|
379
|
+
text-align: left;
|
|
380
|
+
font-size: 10px;
|
|
381
|
+
font-weight: 600;
|
|
382
|
+
text-transform: uppercase;
|
|
383
|
+
letter-spacing: 0.07em;
|
|
384
|
+
color: var(--text-muted);
|
|
385
|
+
padding: 6px 10px;
|
|
386
|
+
border-bottom: 1px solid var(--border);
|
|
387
|
+
white-space: nowrap;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.data-table td {
|
|
391
|
+
padding: 10px 10px;
|
|
392
|
+
border-bottom: 1px solid var(--border);
|
|
393
|
+
color: var(--text-secondary);
|
|
394
|
+
vertical-align: middle;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.data-table tr:last-child td { border-bottom: none; }
|
|
398
|
+
.data-table tr:hover td { background: var(--row-hover); }
|