neoagent 2.4.1-beta.8 → 2.4.1
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 +33 -3
- package/LICENSE +111 -56
- package/README.md +8 -3
- package/docs/configuration.md +8 -0
- package/docs/getting-started.md +9 -3
- package/docs/index.md +4 -0
- package/extensions/chrome-browser/background.mjs +45 -7
- package/extensions/chrome-browser/icons/icon128.png +0 -0
- package/extensions/chrome-browser/icons/icon16.png +0 -0
- package/extensions/chrome-browser/icons/icon48.png +0 -0
- package/extensions/chrome-browser/icons/logo.svg +39 -8
- package/extensions/chrome-browser/manifest.json +3 -2
- package/extensions/chrome-browser/popup.html +5 -1
- package/extensions/chrome-browser/popup.js +15 -2
- package/flutter_app/android/app/src/main/AndroidManifest.xml +2 -1
- package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/flutter_app/assets/branding/app_icon_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_64.png +0 -0
- 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/onboarding_intro.mp4 +0 -0
- package/flutter_app/assets/branding/tray_icon_light_template.png +0 -0
- package/flutter_app/assets/branding/tray_icon_template.png +0 -0
- package/flutter_app/lib/features/location/location_service.dart +3 -0
- package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +391 -382
- package/flutter_app/lib/features/onboarding/onboarding_companion_step.dart +743 -0
- package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +18 -16
- package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +19 -18
- package/flutter_app/lib/features/onboarding/onboarding_shell.dart +8 -1
- package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +16 -13
- package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +17 -13
- package/flutter_app/lib/main.dart +3 -0
- package/flutter_app/lib/main_account_settings.dart +10 -34
- package/flutter_app/lib/main_admin.dart +14 -1
- package/flutter_app/lib/main_app_shell.dart +377 -340
- package/flutter_app/lib/main_chat.dart +707 -227
- package/flutter_app/lib/main_controller.dart +338 -46
- package/flutter_app/lib/main_devices.dart +851 -122
- package/flutter_app/lib/main_integrations.dart +255 -6
- package/flutter_app/lib/main_launcher.dart +1 -1
- package/flutter_app/lib/main_model_picker.dart +685 -0
- package/flutter_app/lib/main_models.dart +212 -0
- package/flutter_app/lib/main_navigation.dart +1 -9
- package/flutter_app/lib/main_operations.dart +1417 -614
- package/flutter_app/lib/main_settings.dart +764 -887
- package/flutter_app/lib/main_shared.dart +971 -443
- package/flutter_app/lib/main_spacing.dart +4 -4
- package/flutter_app/lib/main_theme.dart +22 -14
- package/flutter_app/lib/main_unified.dart +5 -72
- package/flutter_app/lib/src/android_apk_drop_zone.dart +24 -0
- package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +13 -0
- package/flutter_app/lib/src/android_apk_drop_zone_web.dart +219 -0
- package/flutter_app/lib/src/backend_client.dart +79 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +56 -7
- package/flutter_app/lib/src/desktop_companion_io.dart +77 -1
- package/flutter_app/lib/src/desktop_native_bridge.dart +13 -0
- package/flutter_app/lib/src/security/password_strength.dart +84 -0
- package/flutter_app/lib/src/stream_renderer.dart +205 -35
- package/flutter_app/lib/src/theme/palette.dart +76 -34
- package/flutter_app/linux/runner/resources/app_icon.png +0 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +44 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
- package/flutter_app/pubspec.lock +2 -2
- package/flutter_app/pubspec.yaml +7 -1
- package/flutter_app/tool/branding_source/neoagent-icon-1024.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-128.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-16.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-180.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-192.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-256.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-32.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-48.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-512.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-64.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon.svg +43 -0
- package/flutter_app/tool/generate_desktop_branding.py +154 -152
- package/flutter_app/web/favicon.png +0 -0
- package/flutter_app/web/favicon.svg +40 -9
- package/flutter_app/web/favicon_light.svg +43 -0
- package/flutter_app/web/icons/Icon-192-light.png +0 -0
- package/flutter_app/web/icons/Icon-192.png +0 -0
- package/flutter_app/web/icons/Icon-512-light.png +0 -0
- package/flutter_app/web/icons/Icon-512.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192-light.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512-light.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
- package/flutter_app/windows/runner/main.cpp +7 -1
- package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
- package/lib/manager.js +445 -83
- package/package.json +17 -3
- package/runtime/paths.js +3 -1
- package/server/admin/access.js +198 -0
- package/server/admin/admin.css +268 -0
- package/server/admin/admin.js +348 -0
- package/server/admin/analytics.js +128 -0
- package/server/admin/index.html +1015 -0
- package/server/admin/login.html +290 -0
- package/server/admin/logo.svg +43 -0
- package/server/admin/sql.js +134 -0
- package/server/admin/users.js +147 -0
- package/server/config/origins.js +3 -1
- package/server/db/database.js +92 -0
- package/server/db/ftsQuery.js +27 -0
- package/server/http/routes.js +1 -0
- package/server/http/static.js +23 -6
- package/server/index.js +1 -1
- package/server/middleware/adminAuth.js +48 -0
- package/server/middleware/auth.js +1 -40
- package/server/public/.last_build_id +1 -1
- package/server/public/app_icon.png +0 -0
- 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_1024.png +0 -0
- package/server/public/assets/assets/branding/app_icon_256.png +0 -0
- package/server/public/assets/assets/branding/app_icon_512.png +0 -0
- package/server/public/assets/assets/branding/app_icon_light_1024.png +0 -0
- 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/onboarding_intro.mp4 +0 -0
- package/server/public/assets/assets/branding/tray_icon_light_template.png +0 -0
- package/server/public/assets/assets/branding/tray_icon_template.png +0 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/assets/web/icons/Icon-192.png +0 -0
- package/server/public/favicon.png +0 -0
- package/server/public/favicon.svg +40 -9
- package/server/public/favicon_light.svg +43 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/icons/Icon-192-light.png +0 -0
- package/server/public/icons/Icon-192.png +0 -0
- package/server/public/icons/Icon-512-light.png +0 -0
- package/server/public/icons/Icon-512.png +0 -0
- package/server/public/icons/Icon-maskable-192-light.png +0 -0
- package/server/public/icons/Icon-maskable-192.png +0 -0
- package/server/public/icons/Icon-maskable-512-light.png +0 -0
- package/server/public/icons/Icon-maskable-512.png +0 -0
- package/server/public/main.dart.js +88944 -85720
- package/server/routes/admin.js +632 -0
- package/server/routes/agent_profiles.js +3 -0
- package/server/routes/agents.js +1 -1
- package/server/routes/auth.js +3 -1
- package/server/routes/browser.js +14 -0
- package/server/routes/browser_extension.js +21 -4
- package/server/routes/desktop.js +10 -0
- package/server/routes/mcp.js +29 -13
- package/server/routes/memory.js +23 -1
- package/server/routes/messaging.js +2 -0
- package/server/routes/screenHistory.js +14 -8
- package/server/routes/settings.js +14 -2
- package/server/routes/stream.js +12 -3
- package/server/routes/triggers.js +4 -4
- package/server/routes/voice_assistant.js +36 -1
- package/server/routes/workspace.js +86 -0
- package/server/services/account/admin_two_factor.js +132 -0
- package/server/services/account/password_policy.js +6 -1
- package/server/services/ai/completion.js +44 -0
- package/server/services/ai/engine.js +321 -500
- package/server/services/ai/imageAnalysis.js +9 -5
- package/server/services/ai/logFormat.js +46 -0
- package/server/services/ai/loopPolicy.js +11 -0
- package/server/services/ai/messagingFallback.js +228 -0
- package/server/services/ai/models.js +194 -55
- package/server/services/ai/providerRetry.js +169 -0
- package/server/services/ai/providers/anthropic.js +15 -4
- package/server/services/ai/providers/google.js +38 -11
- package/server/services/ai/providers/grok.js +19 -68
- package/server/services/ai/providers/grokOauth.js +141 -0
- package/server/services/ai/providers/nvidia.js +154 -0
- package/server/services/ai/providers/ollama.js +76 -39
- package/server/services/ai/providers/openai.js +20 -31
- package/server/services/ai/providers/openaiCodex.js +6 -2
- package/server/services/ai/providers/openaiCompatible.js +70 -0
- package/server/services/ai/providers/openrouter.js +162 -0
- package/server/services/ai/settings.js +30 -0
- package/server/services/ai/systemPrompt.js +51 -29
- package/server/services/ai/taskAnalysis.js +60 -1
- package/server/services/ai/toolEvidence.js +207 -0
- package/server/services/ai/toolSelector.js +8 -1
- package/server/services/ai/tools.js +80 -15
- package/server/services/android/controller.js +36 -1
- package/server/services/browser/controller.js +18 -0
- package/server/services/browser/extension/manifest.js +33 -0
- package/server/services/browser/extension/provider.js +12 -6
- package/server/services/browser/extension/registry.js +229 -18
- package/server/services/desktop/protocol.js +1 -0
- package/server/services/desktop/provider.js +4 -0
- package/server/services/desktop/registry.js +104 -1
- package/server/services/desktop/screenRecorder.js +208 -98
- package/server/services/desktop/screen_recorder_support.js +46 -0
- package/server/services/integrations/google/provider.js +13 -0
- package/server/services/integrations/home_assistant/constants.js +88 -0
- package/server/services/integrations/home_assistant/network.js +207 -0
- package/server/services/integrations/home_assistant/provider.js +249 -0
- package/server/services/integrations/home_assistant/snapshot.js +98 -0
- package/server/services/integrations/home_assistant/tools.js +101 -0
- package/server/services/integrations/manager.js +5 -0
- package/server/services/integrations/registry.js +2 -0
- package/server/services/integrations/trello/provider.js +8 -2
- package/server/services/manager.js +51 -47
- package/server/services/mcp/client.js +208 -268
- package/server/services/mcp/client_support.js +172 -0
- package/server/services/mcp/recovery.js +116 -0
- package/server/services/mcp/tool_operations.js +123 -0
- package/server/services/memory/ingestion.js +185 -370
- package/server/services/memory/ingestion_coverage.js +129 -0
- package/server/services/memory/ingestion_documents.js +123 -0
- package/server/services/memory/ingestion_support.js +171 -0
- package/server/services/memory/intelligence.js +181 -0
- package/server/services/memory/manager.js +476 -32
- package/server/services/messaging/automation.js +71 -134
- package/server/services/messaging/formatting_guides.js +26 -1
- package/server/services/messaging/inbound_queue.js +111 -0
- package/server/services/messaging/manager.js +25 -8
- package/server/services/messaging/typing_keepalive.js +110 -0
- package/server/services/runtime/manager.js +7 -3
- package/server/services/runtime/settings.js +17 -0
- package/server/services/streaming/android-stream.js +296 -32
- package/server/services/streaming/stream-hub.js +1 -1
- package/server/services/tasks/integration_runtime.js +4 -1
- package/server/services/tasks/runtime.js +415 -56
- package/server/services/tasks/task_repository.js +48 -6
- package/server/services/websocket.js +8 -2
- package/server/services/widgets/service.js +17 -1
- package/server/services/workspace/manager.js +116 -0
- package/server/utils/logger.js +44 -6
- package/server/utils/security.js +3 -0
- package/server/utils/version.js +29 -19
- package/server/services/memory/openhuman_uplift.test.js +0 -98
package/lib/manager.js
CHANGED
|
@@ -6,6 +6,7 @@ const crypto = require('crypto');
|
|
|
6
6
|
const readline = require('readline');
|
|
7
7
|
const { spawn, spawnSync } = require('child_process');
|
|
8
8
|
const { CLAUDE_CODE_SCOPES } = require('../server/services/ai/providers/claudeCode');
|
|
9
|
+
const { GROK_OAUTH_SCOPES, GROK_OAUTH_CLIENT_ID } = require('../server/services/ai/providers/grokOauth');
|
|
9
10
|
const {
|
|
10
11
|
buildBundledWebClientIfPossible: buildWebClient,
|
|
11
12
|
commandExists: sharedCommandExists,
|
|
@@ -61,29 +62,86 @@ const COLORS = process.stdout.isTTY
|
|
|
61
62
|
green: '\x1b[1;32m',
|
|
62
63
|
yellow: '\x1b[1;33m',
|
|
63
64
|
blue: '\x1b[1;34m',
|
|
65
|
+
magenta: '\x1b[1;35m',
|
|
64
66
|
cyan: '\x1b[1;36m',
|
|
65
67
|
dim: '\x1b[2m'
|
|
66
68
|
}
|
|
67
|
-
: { 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 = [];
|
|
68
73
|
|
|
69
74
|
function logInfo(msg) {
|
|
70
|
-
|
|
75
|
+
const mark = CLI_INTERACTIVE ? `${COLORS.blue}◇${COLORS.reset}` : '->';
|
|
76
|
+
console.log(` ${mark} ${msg}`);
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
function logOk(msg) {
|
|
74
|
-
|
|
80
|
+
const mark = CLI_INTERACTIVE ? `${COLORS.green}◆${COLORS.reset}` : 'ok';
|
|
81
|
+
console.log(` ${mark} ${msg}`);
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
function logWarn(msg) {
|
|
78
|
-
|
|
85
|
+
const mark = CLI_INTERACTIVE ? `${COLORS.yellow}▲${COLORS.reset}` : 'warn';
|
|
86
|
+
console.warn(` ${mark} ${msg}`);
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
function logErr(msg) {
|
|
82
|
-
|
|
90
|
+
const mark = CLI_INTERACTIVE ? `${COLORS.red}✕${COLORS.reset}` : 'err';
|
|
91
|
+
console.error(` ${mark} ${msg}`);
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
function heading(text) {
|
|
86
|
-
|
|
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
|
+
}
|
|
87
145
|
}
|
|
88
146
|
|
|
89
147
|
function detectPlatform() {
|
|
@@ -164,6 +222,14 @@ function removeEnvValue(key) {
|
|
|
164
222
|
return true;
|
|
165
223
|
}
|
|
166
224
|
|
|
225
|
+
function readAdminCredentials() {
|
|
226
|
+
const env = Object.fromEntries(parseEnv(readEnvFileRaw()).entries());
|
|
227
|
+
return {
|
|
228
|
+
username: env.ADMIN_USERNAME || 'admin',
|
|
229
|
+
password: env.ADMIN_PASSWORD || '(not set — run `neoagent setup`)',
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
167
233
|
function maskEnvValue(key, value) {
|
|
168
234
|
if (!/(KEY|TOKEN|SECRET|PASSWORD)/i.test(key)) return value;
|
|
169
235
|
const text = String(value || '');
|
|
@@ -476,6 +542,43 @@ async function askSecret(question, currentValue = '') {
|
|
|
476
542
|
});
|
|
477
543
|
}
|
|
478
544
|
|
|
545
|
+
function defaultEnvLines(current = {}) {
|
|
546
|
+
const defaultVmBaseImageUrl = getDefaultVmBaseImageUrl();
|
|
547
|
+
const port = current.PORT || '3333';
|
|
548
|
+
const publicUrl = current.PUBLIC_URL || '';
|
|
549
|
+
const secureCookies = current.SECURE_COOKIES ||
|
|
550
|
+
(String(publicUrl || '').trim().startsWith('https://') ? 'true' : 'false');
|
|
551
|
+
const trustProxy = current.TRUST_PROXY || secureCookies;
|
|
552
|
+
return [
|
|
553
|
+
'NODE_ENV=production',
|
|
554
|
+
`PORT=${port}`,
|
|
555
|
+
publicUrl ? `PUBLIC_URL=${publicUrl}` : '',
|
|
556
|
+
`SECURE_COOKIES=${String(secureCookies || '').trim().toLowerCase() === 'true' ? 'true' : 'false'}`,
|
|
557
|
+
`TRUST_PROXY=${String(trustProxy || '').trim().toLowerCase() === 'true' ? 'true' : 'false'}`,
|
|
558
|
+
`SESSION_SECRET=${current.SESSION_SECRET || randomSecret()}`,
|
|
559
|
+
`NEOAGENT_PROFILE=${current.NEOAGENT_PROFILE || 'prod'}`,
|
|
560
|
+
`NEOAGENT_DEPLOYMENT_MODE=${parseDeploymentMode(current.NEOAGENT_DEPLOYMENT_MODE || 'self_hosted')}`,
|
|
561
|
+
`NEOAGENT_RELEASE_CHANNEL=${parseReleaseChannel(current.NEOAGENT_RELEASE_CHANNEL || 'stable') || 'stable'}`,
|
|
562
|
+
`NEOAGENT_VM_BASE_IMAGE_URL=${current.NEOAGENT_VM_BASE_IMAGE_URL || defaultVmBaseImageUrl}`,
|
|
563
|
+
`NEOAGENT_VM_MEMORY_MB=${current.NEOAGENT_VM_MEMORY_MB || '4096'}`,
|
|
564
|
+
`NEOAGENT_VM_CPUS=${current.NEOAGENT_VM_CPUS || '2'}`,
|
|
565
|
+
`NEOAGENT_VM_GUEST_TOKEN=${current.NEOAGENT_VM_GUEST_TOKEN || randomSecret()}`,
|
|
566
|
+
current.XAI_BASE_URL ? `XAI_BASE_URL=${current.XAI_BASE_URL}` : 'XAI_BASE_URL=https://api.x.ai/v1',
|
|
567
|
+
current.OLLAMA_URL ? `OLLAMA_URL=${current.OLLAMA_URL}` : 'OLLAMA_URL=http://localhost:11434',
|
|
568
|
+
current.DEEPGRAM_BASE_URL ? `DEEPGRAM_BASE_URL=${current.DEEPGRAM_BASE_URL}` : 'DEEPGRAM_BASE_URL=https://api.deepgram.com',
|
|
569
|
+
current.DEEPGRAM_MODEL ? `DEEPGRAM_MODEL=${current.DEEPGRAM_MODEL}` : 'DEEPGRAM_MODEL=nova-3',
|
|
570
|
+
current.DEEPGRAM_LANGUAGE ? `DEEPGRAM_LANGUAGE=${current.DEEPGRAM_LANGUAGE}` : 'DEEPGRAM_LANGUAGE=multi',
|
|
571
|
+
].filter(Boolean);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function writeDefaultEnvFile() {
|
|
575
|
+
ensureRuntimeDirs();
|
|
576
|
+
const current = Object.fromEntries(parseEnv(readEnvFileRaw()).entries());
|
|
577
|
+
fs.writeFileSync(ENV_FILE, `${defaultEnvLines(current).join('\n')}\n`, { mode: 0o600 });
|
|
578
|
+
logOk(`Wrote default config to ${ENV_FILE}`);
|
|
579
|
+
rememberInstallAction('Add provider keys with `neoagent setup`, `neoagent env set KEY VALUE`, or the login commands when you are ready.');
|
|
580
|
+
}
|
|
581
|
+
|
|
479
582
|
async function cmdSetup() {
|
|
480
583
|
heading('Environment Setup');
|
|
481
584
|
ensureRuntimeDirs();
|
|
@@ -673,6 +776,11 @@ async function cmdSetup() {
|
|
|
673
776
|
origins ? `ALLOWED_ORIGINS=${origins}` : ''
|
|
674
777
|
].filter(Boolean);
|
|
675
778
|
|
|
779
|
+
const adminUsername = current.ADMIN_USERNAME || 'admin';
|
|
780
|
+
const adminPassword = current.ADMIN_PASSWORD || crypto.randomBytes(16).toString('hex');
|
|
781
|
+
lines.push(`ADMIN_USERNAME=${adminUsername}`);
|
|
782
|
+
lines.push(`ADMIN_PASSWORD=${adminPassword}`);
|
|
783
|
+
|
|
676
784
|
fs.writeFileSync(ENV_FILE, `${lines.join('\n')}\n`, { mode: 0o600 });
|
|
677
785
|
logOk(`Wrote ${ENV_FILE}`);
|
|
678
786
|
}
|
|
@@ -945,10 +1053,135 @@ async function cmdLoginClaudeCode() {
|
|
|
945
1053
|
cmdRestart();
|
|
946
1054
|
}
|
|
947
1055
|
|
|
1056
|
+
async function cmdLoginGrokOAuth() {
|
|
1057
|
+
heading('Grok (xAI OAuth) Login');
|
|
1058
|
+
|
|
1059
|
+
const http = require('http');
|
|
1060
|
+
const { URL: NodeURL } = require('url');
|
|
1061
|
+
|
|
1062
|
+
const clientId = GROK_OAUTH_CLIENT_ID;
|
|
1063
|
+
const SCOPES = GROK_OAUTH_SCOPES;
|
|
1064
|
+
const redirectPort = 56121;
|
|
1065
|
+
const redirectUri = `http://127.0.0.1:${redirectPort}/callback`;
|
|
1066
|
+
|
|
1067
|
+
const codeVerifier = crypto.randomBytes(48).toString('base64url');
|
|
1068
|
+
const codeChallenge = crypto
|
|
1069
|
+
.createHash('sha256')
|
|
1070
|
+
.update(codeVerifier)
|
|
1071
|
+
.digest('base64url');
|
|
1072
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
1073
|
+
|
|
1074
|
+
const authUrl = new URL('https://auth.x.ai/oauth2/authorize');
|
|
1075
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
1076
|
+
authUrl.searchParams.set('client_id', clientId);
|
|
1077
|
+
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
1078
|
+
authUrl.searchParams.set('scope', SCOPES);
|
|
1079
|
+
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
1080
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
1081
|
+
authUrl.searchParams.set('state', state);
|
|
1082
|
+
|
|
1083
|
+
console.log(`\n ${COLORS.cyan}Opening browser for Grok (xAI) authorization...${COLORS.reset}`);
|
|
1084
|
+
console.log(` ${COLORS.dim}If the browser doesn't open, visit:${COLORS.reset}`);
|
|
1085
|
+
console.log(` ${authUrl.toString()}\n`);
|
|
1086
|
+
|
|
1087
|
+
const openCmd = process.platform === 'darwin' ? 'open'
|
|
1088
|
+
: process.platform === 'win32' ? 'start'
|
|
1089
|
+
: 'xdg-open';
|
|
1090
|
+
spawnSync(openCmd, [authUrl.toString()], { stdio: 'ignore' });
|
|
1091
|
+
|
|
1092
|
+
const authCode = await new Promise((resolve, reject) => {
|
|
1093
|
+
const timeout = setTimeout(() => {
|
|
1094
|
+
server.close();
|
|
1095
|
+
reject(new Error('Grok OAuth authorization timed out after 5 minutes.'));
|
|
1096
|
+
}, 5 * 60 * 1000);
|
|
1097
|
+
|
|
1098
|
+
const server = http.createServer((req, res) => {
|
|
1099
|
+
try {
|
|
1100
|
+
const reqUrl = new NodeURL(req.url, redirectUri);
|
|
1101
|
+
const code = reqUrl.searchParams.get('code');
|
|
1102
|
+
const returnedState = reqUrl.searchParams.get('state');
|
|
1103
|
+
const error = reqUrl.searchParams.get('error');
|
|
1104
|
+
|
|
1105
|
+
if (error) {
|
|
1106
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1107
|
+
res.end('<html><body><h2>Authorization failed.</h2><p>You can close this tab.</p></body></html>');
|
|
1108
|
+
clearTimeout(timeout);
|
|
1109
|
+
server.close();
|
|
1110
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (returnedState && returnedState !== state) {
|
|
1115
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1116
|
+
res.end('<html><body><h2>Authorization failed.</h2><p>State mismatch. You can close this tab.</p></body></html>');
|
|
1117
|
+
clearTimeout(timeout);
|
|
1118
|
+
server.close();
|
|
1119
|
+
reject(new Error('OAuth state mismatch — possible CSRF attempt.'));
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
if (code) {
|
|
1124
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1125
|
+
res.end('<html><body><h2>Authorization successful!</h2><p>You can close this tab and return to the terminal.</p></body></html>');
|
|
1126
|
+
clearTimeout(timeout);
|
|
1127
|
+
server.close();
|
|
1128
|
+
resolve(code);
|
|
1129
|
+
}
|
|
1130
|
+
} catch (err) {
|
|
1131
|
+
res.writeHead(500);
|
|
1132
|
+
res.end('Internal error');
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
server.listen(redirectPort, '127.0.0.1', () => {
|
|
1137
|
+
logInfo(`Waiting for OAuth callback on ${redirectUri} ...`);
|
|
1138
|
+
});
|
|
1139
|
+
server.on('error', (err) => {
|
|
1140
|
+
clearTimeout(timeout);
|
|
1141
|
+
reject(new Error(`Could not start OAuth callback server on port ${redirectPort}: ${err.message}`));
|
|
1142
|
+
});
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
logInfo('Exchanging authorization code for access token...');
|
|
1146
|
+
const tokenRes = await fetch('https://auth.x.ai/oauth2/token', {
|
|
1147
|
+
method: 'POST',
|
|
1148
|
+
headers: {
|
|
1149
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
1150
|
+
'Accept': 'application/json',
|
|
1151
|
+
},
|
|
1152
|
+
body: new URLSearchParams({
|
|
1153
|
+
grant_type: 'authorization_code',
|
|
1154
|
+
code: authCode,
|
|
1155
|
+
redirect_uri: redirectUri,
|
|
1156
|
+
client_id: clientId,
|
|
1157
|
+
code_verifier: codeVerifier,
|
|
1158
|
+
}),
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
if (!tokenRes.ok) {
|
|
1162
|
+
const text = await tokenRes.text().catch(() => 'Unknown error');
|
|
1163
|
+
throw new Error(`Token exchange failed: HTTP ${tokenRes.status} — ${text}`);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const tokenData = await tokenRes.json();
|
|
1167
|
+
const accessToken = tokenData.access_token;
|
|
1168
|
+
if (!accessToken) {
|
|
1169
|
+
throw new Error('Token exchange succeeded but no access_token was returned.');
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
upsertEnvValue('GROK_OAUTH_ACCESS_TOKEN', accessToken);
|
|
1173
|
+
if (tokenData.refresh_token) {
|
|
1174
|
+
upsertEnvValue('GROK_OAUTH_REFRESH_TOKEN', tokenData.refresh_token);
|
|
1175
|
+
}
|
|
1176
|
+
logOk('Saved Grok OAuth tokens to .env');
|
|
1177
|
+
logInfo('Restarting NeoAgent to apply credentials...');
|
|
1178
|
+
cmdRestart();
|
|
1179
|
+
}
|
|
1180
|
+
|
|
948
1181
|
async function cmdLogin(args = []) {
|
|
949
1182
|
const provider = args[0];
|
|
950
|
-
if (provider !== 'github-copilot' && provider !== 'openai-codex' && provider !== 'claude-code') {
|
|
951
|
-
throw new Error(`Unsupported login provider: ${provider || 'none'}. Available: github-copilot, openai-codex, claude-code`);
|
|
1183
|
+
if (provider !== 'github-copilot' && provider !== 'openai-codex' && provider !== 'claude-code' && provider !== 'grok-oauth') {
|
|
1184
|
+
throw new Error(`Unsupported login provider: ${provider || 'none'}. Available: github-copilot, openai-codex, claude-code, grok-oauth`);
|
|
952
1185
|
}
|
|
953
1186
|
|
|
954
1187
|
if (provider === 'github-copilot') {
|
|
@@ -1054,6 +1287,8 @@ async function cmdLogin(args = []) {
|
|
|
1054
1287
|
cmdRestart();
|
|
1055
1288
|
} else if (provider === 'claude-code') {
|
|
1056
1289
|
await cmdLoginClaudeCode();
|
|
1290
|
+
} else if (provider === 'grok-oauth') {
|
|
1291
|
+
await cmdLoginGrokOAuth();
|
|
1057
1292
|
}
|
|
1058
1293
|
}
|
|
1059
1294
|
|
|
@@ -1065,6 +1300,50 @@ function installDependencies() {
|
|
|
1065
1300
|
logOk('Dependencies installed');
|
|
1066
1301
|
}
|
|
1067
1302
|
|
|
1303
|
+
function assertSupportedNodeRuntime() {
|
|
1304
|
+
const major = Number(String(process.versions.node || '').split('.')[0]);
|
|
1305
|
+
if (!Number.isInteger(major) || major < 20) {
|
|
1306
|
+
throw new Error(`NeoAgent requires Node.js 20 or newer. Current runtime is ${process.versions.node || 'unknown'}.`);
|
|
1307
|
+
}
|
|
1308
|
+
logOk(`Node.js ${process.versions.node}`);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function installPreflight() {
|
|
1312
|
+
heading('Installer preflight');
|
|
1313
|
+
ensureRuntimeDirs();
|
|
1314
|
+
assertSupportedNodeRuntime();
|
|
1315
|
+
|
|
1316
|
+
if (!commandExists('npm')) {
|
|
1317
|
+
throw new Error('npm was not found in PATH. Install Node.js 20+ with npm, then run `neoagent install` again.');
|
|
1318
|
+
}
|
|
1319
|
+
const npmVersion = runQuiet('npm', ['--version']);
|
|
1320
|
+
logOk(`npm ${npmVersion.status === 0 ? npmVersion.stdout.trim() : '(version unknown)'}`);
|
|
1321
|
+
|
|
1322
|
+
const platform = detectPlatform();
|
|
1323
|
+
if (platform === 'other') {
|
|
1324
|
+
rememberInstallAction('Automatic service installation is available on macOS and Linux. This machine will use a detached process fallback.');
|
|
1325
|
+
} else {
|
|
1326
|
+
logOk(`platform ${platform}`);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
if (platform === 'macos' && !commandExists('launchctl')) {
|
|
1330
|
+
rememberInstallAction('launchctl was not found, so the installer will use a detached process fallback instead of a login service.');
|
|
1331
|
+
}
|
|
1332
|
+
if (platform === 'linux' && !commandExists('systemctl')) {
|
|
1333
|
+
rememberInstallAction('systemctl was not found, so the installer will use a detached process fallback instead of a user service.');
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
const port = loadEnvPort();
|
|
1337
|
+
const portOwner = commandExists('lsof')
|
|
1338
|
+
? runQuiet('lsof', ['-nP', '-iTCP:' + port, '-sTCP:LISTEN'])
|
|
1339
|
+
: null;
|
|
1340
|
+
if (portOwner && portOwner.status === 0 && portOwner.stdout.trim()) {
|
|
1341
|
+
logInfo(`Port ${port} already has a listener; install will keep existing processes unless service start replaces them.`);
|
|
1342
|
+
} else {
|
|
1343
|
+
logOk(`port ${port} available`);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1068
1347
|
function buildBundledWebClientIfPossible({ required = false } = {}) {
|
|
1069
1348
|
heading('Web Client');
|
|
1070
1349
|
return buildWebClient({
|
|
@@ -1153,7 +1432,7 @@ function startFallback() {
|
|
|
1153
1432
|
logOk(`Started detached process (pid ${child.pid})`);
|
|
1154
1433
|
}
|
|
1155
1434
|
|
|
1156
|
-
async function ensureQemuInstalled() {
|
|
1435
|
+
async function ensureQemuInstalled({ required = false } = {}) {
|
|
1157
1436
|
heading('Ensure QEMU Installed');
|
|
1158
1437
|
const platform = detectPlatform();
|
|
1159
1438
|
|
|
@@ -1167,35 +1446,54 @@ async function ensureQemuInstalled() {
|
|
|
1167
1446
|
|
|
1168
1447
|
logInfo('QEMU components not found. Attempting to install...');
|
|
1169
1448
|
|
|
1170
|
-
|
|
1171
|
-
if (
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1449
|
+
try {
|
|
1450
|
+
if (platform === 'macos') {
|
|
1451
|
+
if (!commandExists('brew')) {
|
|
1452
|
+
const message = 'Homebrew is required for automatic QEMU install on macOS. Install Homebrew from https://brew.sh/ and run `neoagent install` again.';
|
|
1453
|
+
if (required) throw new Error(message);
|
|
1454
|
+
logWarn('Homebrew not found; VM features will stay unavailable until QEMU is installed.');
|
|
1455
|
+
rememberInstallAction(message);
|
|
1456
|
+
return false;
|
|
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']);
|
|
1182
1478
|
} else {
|
|
1183
|
-
|
|
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;
|
|
1184
1484
|
}
|
|
1185
|
-
logInfo(`Running "sudo apt-get update && sudo apt-get install -y ${pkgs.join(' ')}"...`);
|
|
1186
|
-
runOrThrow('sudo', ['apt-get', 'update']);
|
|
1187
|
-
runOrThrow('sudo', ['apt-get', 'install', '-y', ...pkgs]);
|
|
1188
|
-
} else if (commandExists('dnf')) {
|
|
1189
|
-
logInfo('Running "sudo dnf install -y qemu-kvm qemu-img"...');
|
|
1190
|
-
runOrThrow('sudo', ['dnf', 'install', '-y', 'qemu-kvm', 'qemu-img']);
|
|
1191
|
-
} else if (commandExists('yum')) {
|
|
1192
|
-
logInfo('Running "sudo yum install -y qemu-kvm qemu-img"...');
|
|
1193
|
-
runOrThrow('sudo', ['yum', 'install', '-y', 'qemu-kvm', 'qemu-img']);
|
|
1194
1485
|
} else {
|
|
1195
|
-
|
|
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;
|
|
1196
1491
|
}
|
|
1197
|
-
}
|
|
1198
|
-
|
|
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.');
|
|
1496
|
+
return false;
|
|
1199
1497
|
}
|
|
1200
1498
|
|
|
1201
1499
|
const verifiedSystem = commandExists('qemu-system-x86_64') || commandExists('qemu-system-aarch64');
|
|
@@ -1203,8 +1501,13 @@ async function ensureQemuInstalled() {
|
|
|
1203
1501
|
|
|
1204
1502
|
if (verifiedSystem && verifiedImg) {
|
|
1205
1503
|
logOk('QEMU installed successfully');
|
|
1504
|
+
return true;
|
|
1206
1505
|
} else {
|
|
1207
|
-
|
|
1506
|
+
const message = 'QEMU installation finished but required binaries were not found in PATH.';
|
|
1507
|
+
if (required) throw new Error(message);
|
|
1508
|
+
logWarn(message);
|
|
1509
|
+
rememberInstallAction('Ensure qemu-system and qemu-img are available in PATH, then run `neoagent install` again.');
|
|
1510
|
+
return false;
|
|
1208
1511
|
}
|
|
1209
1512
|
}
|
|
1210
1513
|
|
|
@@ -1213,7 +1516,7 @@ function ensureYtDlpInstalled() {
|
|
|
1213
1516
|
if (commandExists('yt-dlp')) {
|
|
1214
1517
|
const ver = runQuiet('yt-dlp', ['--version']);
|
|
1215
1518
|
logOk(`yt-dlp ${ver.status === 0 ? ver.stdout.trim() : '(version unknown)'}`);
|
|
1216
|
-
return;
|
|
1519
|
+
return true;
|
|
1217
1520
|
}
|
|
1218
1521
|
|
|
1219
1522
|
logInfo('yt-dlp not found. Attempting to install...');
|
|
@@ -1221,16 +1524,19 @@ function ensureYtDlpInstalled() {
|
|
|
1221
1524
|
|
|
1222
1525
|
if (platform === 'macos') {
|
|
1223
1526
|
if (!commandExists('brew')) {
|
|
1224
|
-
logWarn('Homebrew not found
|
|
1225
|
-
|
|
1527
|
+
logWarn('Homebrew not found; skipping yt-dlp auto-install.');
|
|
1528
|
+
rememberInstallAction('Install yt-dlp with `brew install yt-dlp` if you need video/audio extraction.');
|
|
1529
|
+
return false;
|
|
1226
1530
|
}
|
|
1227
1531
|
try {
|
|
1228
1532
|
runOrThrow('brew', ['install', 'yt-dlp']);
|
|
1229
1533
|
logOk('yt-dlp installed via Homebrew');
|
|
1534
|
+
return true;
|
|
1230
1535
|
} catch {
|
|
1231
|
-
logWarn('yt-dlp install failed.
|
|
1536
|
+
logWarn('yt-dlp install failed.');
|
|
1537
|
+
rememberInstallAction('Install yt-dlp with `brew install yt-dlp` if you need video/audio extraction.');
|
|
1538
|
+
return false;
|
|
1232
1539
|
}
|
|
1233
|
-
return;
|
|
1234
1540
|
}
|
|
1235
1541
|
|
|
1236
1542
|
if (platform === 'linux') {
|
|
@@ -1238,7 +1544,7 @@ function ensureYtDlpInstalled() {
|
|
|
1238
1544
|
try {
|
|
1239
1545
|
runOrThrow('pipx', ['install', 'yt-dlp']);
|
|
1240
1546
|
logOk('yt-dlp installed via pipx');
|
|
1241
|
-
return;
|
|
1547
|
+
return true;
|
|
1242
1548
|
} catch {
|
|
1243
1549
|
// fall through to pip3
|
|
1244
1550
|
}
|
|
@@ -1247,24 +1553,39 @@ function ensureYtDlpInstalled() {
|
|
|
1247
1553
|
try {
|
|
1248
1554
|
runOrThrow('pip3', ['install', '--user', 'yt-dlp']);
|
|
1249
1555
|
logOk('yt-dlp installed via pip3');
|
|
1250
|
-
return;
|
|
1556
|
+
return true;
|
|
1251
1557
|
} catch {
|
|
1252
1558
|
// fall through to warn
|
|
1253
1559
|
}
|
|
1254
1560
|
}
|
|
1255
|
-
logWarn('Could not install yt-dlp automatically.
|
|
1561
|
+
logWarn('Could not install yt-dlp automatically.');
|
|
1562
|
+
rememberInstallAction('Install yt-dlp with `pipx install yt-dlp` or your OS package manager if you need video/audio extraction.');
|
|
1563
|
+
return false;
|
|
1256
1564
|
}
|
|
1565
|
+
rememberInstallAction('Install yt-dlp manually if you need video/audio extraction.');
|
|
1566
|
+
return false;
|
|
1257
1567
|
}
|
|
1258
1568
|
|
|
1259
1569
|
async function cmdInstall() {
|
|
1570
|
+
installActionItems.length = 0;
|
|
1571
|
+
cliBanner(`Install ${APP_NAME}`, 'guided bootstrap');
|
|
1260
1572
|
heading(`Install ${APP_NAME}`);
|
|
1573
|
+
installPreflight();
|
|
1574
|
+
|
|
1261
1575
|
if (!fs.existsSync(ENV_FILE)) {
|
|
1262
|
-
|
|
1263
|
-
|
|
1576
|
+
if (process.stdin.isTTY) {
|
|
1577
|
+
logWarn('.env not found; starting guided setup');
|
|
1578
|
+
await cmdSetup();
|
|
1579
|
+
} else {
|
|
1580
|
+
logWarn('.env not found and stdin is not interactive; writing a secure default config');
|
|
1581
|
+
writeDefaultEnvFile();
|
|
1582
|
+
}
|
|
1583
|
+
} else {
|
|
1584
|
+
logOk(`Using config ${ENV_FILE}`);
|
|
1264
1585
|
}
|
|
1265
1586
|
|
|
1266
1587
|
installDependencies();
|
|
1267
|
-
await ensureQemuInstalled();
|
|
1588
|
+
await ensureQemuInstalled({ required: false });
|
|
1268
1589
|
ensureYtDlpInstalled();
|
|
1269
1590
|
buildBundledWebClientIfPossible({ required: true });
|
|
1270
1591
|
|
|
@@ -1279,19 +1600,31 @@ async function cmdInstall() {
|
|
|
1279
1600
|
|
|
1280
1601
|
const port = loadEnvPort();
|
|
1281
1602
|
logOk(`Running on http://localhost:${port}`);
|
|
1603
|
+
printInstallActionItems();
|
|
1604
|
+
heading('Ready');
|
|
1605
|
+
logInfo(`Open http://localhost:${port} or run \`neoagent status\` for a health check.`);
|
|
1606
|
+
const adminCreds = readAdminCredentials();
|
|
1607
|
+
heading('Admin Dashboard');
|
|
1608
|
+
logOk(`URL: http://localhost:${port}/admin`);
|
|
1609
|
+
logInfo(`Username: ${adminCreds.username}`);
|
|
1610
|
+
logInfo(`Password: ${adminCreds.password}`);
|
|
1611
|
+
logWarn('Save these credentials — stored in .env and shown again with `neoagent admin`');
|
|
1282
1612
|
}
|
|
1283
1613
|
|
|
1284
1614
|
function cmdStart() {
|
|
1615
|
+
cliBanner(`Start ${APP_NAME}`, 'boot sequence');
|
|
1285
1616
|
heading(`Start ${APP_NAME}`);
|
|
1286
1617
|
const platform = detectPlatform();
|
|
1287
1618
|
|
|
1288
1619
|
if (platform === 'macos' && fs.existsSync(PLIST_DST)) {
|
|
1620
|
+
logInfo('Handing launch to launchd');
|
|
1289
1621
|
installMacService();
|
|
1290
1622
|
logOk('launchd start requested');
|
|
1291
1623
|
return;
|
|
1292
1624
|
}
|
|
1293
1625
|
|
|
1294
1626
|
if (platform === 'linux' && fs.existsSync(SYSTEMD_UNIT)) {
|
|
1627
|
+
logInfo('Handing launch to systemd');
|
|
1295
1628
|
runOrThrow('systemctl', ['--user', 'start', 'neoagent']);
|
|
1296
1629
|
runOrThrow('systemctl', ['--user', 'is-active', '--quiet', 'neoagent']);
|
|
1297
1630
|
logOk('systemd start requested');
|
|
@@ -1384,54 +1717,68 @@ function cmdUninstall() {
|
|
|
1384
1717
|
}
|
|
1385
1718
|
|
|
1386
1719
|
async function cmdStatus() {
|
|
1720
|
+
cliBanner(`${APP_NAME} Status`, 'systems sweep');
|
|
1387
1721
|
heading(`${APP_NAME} Status`);
|
|
1388
1722
|
const port = loadEnvPort();
|
|
1389
1723
|
const running = await isPortOpen(port);
|
|
1390
1724
|
const releaseChannel = currentReleaseChannel();
|
|
1391
1725
|
const platform = detectPlatform();
|
|
1392
1726
|
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1727
|
+
cliSection('Runtime');
|
|
1728
|
+
statusLine(
|
|
1729
|
+
running,
|
|
1730
|
+
'server',
|
|
1731
|
+
running ? `http://localhost:${port}` : `not reachable on port ${port}`,
|
|
1732
|
+
);
|
|
1398
1733
|
|
|
1399
1734
|
if (platform === 'macos' && fs.existsSync(PLIST_DST)) {
|
|
1400
1735
|
const svcRes = runQuiet('launchctl', ['list', SERVICE_LABEL]);
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1736
|
+
statusLine(
|
|
1737
|
+
svcRes.status === 0 && Boolean(svcRes.stdout.trim()),
|
|
1738
|
+
'service',
|
|
1739
|
+
svcRes.status === 0 && svcRes.stdout.trim()
|
|
1740
|
+
? `launchd (${SERVICE_LABEL})`
|
|
1741
|
+
: 'launchd unit not loaded',
|
|
1742
|
+
svcRes.status === 0 && svcRes.stdout.trim() ? '' : 'run: neoagent install',
|
|
1743
|
+
);
|
|
1406
1744
|
} else if (platform === 'linux' && fs.existsSync(SYSTEMD_UNIT)) {
|
|
1407
1745
|
const svcRes = runQuiet('systemctl', ['--user', 'is-active', 'neoagent']);
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1746
|
+
statusLine(
|
|
1747
|
+
svcRes.status === 0 && svcRes.stdout.trim() === 'active',
|
|
1748
|
+
'service',
|
|
1749
|
+
svcRes.status === 0 && svcRes.stdout.trim() === 'active'
|
|
1750
|
+
? 'systemd (neoagent)'
|
|
1751
|
+
: 'systemd unit not active',
|
|
1752
|
+
svcRes.status === 0 && svcRes.stdout.trim() === 'active' ? '' : 'run: neoagent install',
|
|
1753
|
+
);
|
|
1413
1754
|
}
|
|
1414
1755
|
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1756
|
+
cliSection('Assets');
|
|
1757
|
+
statusLine(
|
|
1758
|
+
fs.existsSync(ENV_FILE),
|
|
1759
|
+
'config',
|
|
1760
|
+
fs.existsSync(ENV_FILE) ? ENV_FILE : '.env not found',
|
|
1761
|
+
fs.existsSync(ENV_FILE) ? '' : 'run: neoagent setup',
|
|
1762
|
+
);
|
|
1420
1763
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1764
|
+
statusLine(
|
|
1765
|
+
hasBundledWebClient(WEB_CLIENT_DIR),
|
|
1766
|
+
'web',
|
|
1767
|
+
hasBundledWebClient(WEB_CLIENT_DIR)
|
|
1768
|
+
? 'bundled Flutter client present'
|
|
1769
|
+
: 'no bundled client',
|
|
1770
|
+
hasBundledWebClient(WEB_CLIENT_DIR) ? '' : 'run: neoagent rebuild-web',
|
|
1771
|
+
);
|
|
1426
1772
|
|
|
1427
1773
|
console.log('');
|
|
1428
|
-
|
|
1429
|
-
console.log(`
|
|
1430
|
-
console.log(`
|
|
1774
|
+
cliSection('Build');
|
|
1775
|
+
console.log(` install ${APP_DIR}`);
|
|
1776
|
+
console.log(` version ${currentInstalledVersionLabel()}`);
|
|
1777
|
+
console.log(` channel ${releaseChannelSummary(releaseChannel)}`);
|
|
1431
1778
|
|
|
1432
1779
|
const processes = listNeoAgentServerProcesses();
|
|
1433
1780
|
if (processes.length > 0) {
|
|
1434
|
-
console.log(` pids
|
|
1781
|
+
console.log(` pids ${processes.map((proc) => proc.pid).join(', ')}`);
|
|
1435
1782
|
if (processes.length > 1) {
|
|
1436
1783
|
logWarn(`multiple NeoAgent processes detected (${processes.length})`);
|
|
1437
1784
|
}
|
|
@@ -1601,14 +1948,15 @@ function printHelp() {
|
|
|
1601
1948
|
|
|
1602
1949
|
function row(cmd, desc) {
|
|
1603
1950
|
const padded = ` neoagent ${cmd}`.padEnd(W);
|
|
1604
|
-
|
|
1951
|
+
const arrow = CLI_INTERACTIVE ? `${c.cyan}›${c.reset} ` : '';
|
|
1952
|
+
console.log(`${padded}${arrow}${c.dim}${desc}${c.reset}`);
|
|
1605
1953
|
}
|
|
1606
1954
|
|
|
1607
|
-
|
|
1608
|
-
console.log(
|
|
1955
|
+
cliBanner('neoagent', 'command deck');
|
|
1956
|
+
console.log(`\n${c.bold}Usage${c.reset} neoagent <command> [args]\n`);
|
|
1609
1957
|
|
|
1610
|
-
|
|
1611
|
-
row('install', '
|
|
1958
|
+
cliSection('Lifecycle');
|
|
1959
|
+
row('install', 'Guided bootstrap, dependencies, config, service');
|
|
1612
1960
|
row('start', 'Start the server');
|
|
1613
1961
|
row('stop', 'Stop the server');
|
|
1614
1962
|
row('restart', 'Stop, then start');
|
|
@@ -1617,7 +1965,7 @@ function printHelp() {
|
|
|
1617
1965
|
row('uninstall', 'Remove the system service');
|
|
1618
1966
|
console.log('');
|
|
1619
1967
|
|
|
1620
|
-
|
|
1968
|
+
cliSection('Configuration');
|
|
1621
1969
|
row('setup', 'Interactive configuration wizard');
|
|
1622
1970
|
row('env list', 'List all variables (secrets masked)');
|
|
1623
1971
|
row('env get KEY', 'Print a single variable');
|
|
@@ -1627,15 +1975,20 @@ function printHelp() {
|
|
|
1627
1975
|
row('channel stable|beta', 'Switch release channel');
|
|
1628
1976
|
console.log('');
|
|
1629
1977
|
|
|
1630
|
-
|
|
1978
|
+
cliSection('Updates & Auth');
|
|
1631
1979
|
row('update', 'Update to latest on current channel');
|
|
1632
1980
|
row('update stable|beta', 'Update and switch channel');
|
|
1633
1981
|
row('login github-copilot','Authenticate GitHub Copilot');
|
|
1634
1982
|
row('login openai-codex', 'Authenticate OpenAI Codex');
|
|
1635
1983
|
row('login claude-code', 'Authenticate Claude Code');
|
|
1984
|
+
row('login grok-oauth', 'Authenticate Grok (xAI OAuth)');
|
|
1985
|
+
console.log('');
|
|
1986
|
+
|
|
1987
|
+
cliSection('Admin');
|
|
1988
|
+
row('admin', 'Show admin dashboard URL and credentials');
|
|
1636
1989
|
console.log('');
|
|
1637
1990
|
|
|
1638
|
-
|
|
1991
|
+
cliSection('Maintenance');
|
|
1639
1992
|
row('migrate', 'Migrate from another agent installation');
|
|
1640
1993
|
row('migrate dry-run', 'Preview what would be migrated');
|
|
1641
1994
|
row('rebuild-web', 'Rebuild the bundled Flutter web client');
|
|
@@ -1691,6 +2044,15 @@ async function runCLI(argv) {
|
|
|
1691
2044
|
case 'login':
|
|
1692
2045
|
await cmdLogin(argv.slice(1));
|
|
1693
2046
|
break;
|
|
2047
|
+
case 'admin': {
|
|
2048
|
+
cliBanner('Admin Dashboard', 'credentials');
|
|
2049
|
+
const adminCreds = readAdminCredentials();
|
|
2050
|
+
const port = loadEnvPort();
|
|
2051
|
+
logOk(`URL: http://localhost:${port}/admin`);
|
|
2052
|
+
logInfo(`Username: ${adminCreds.username}`);
|
|
2053
|
+
logInfo(`Password: ${adminCreds.password}`);
|
|
2054
|
+
break;
|
|
2055
|
+
}
|
|
1694
2056
|
case 'version':
|
|
1695
2057
|
case '--version':
|
|
1696
2058
|
case '-V':
|