neoagent 2.3.1-beta.11 → 2.3.1-beta.111
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 +53 -5
- package/README.md +16 -7
- package/com.neoagent.plist +1 -1
- package/docs/automation.md +82 -41
- package/docs/capabilities.md +85 -89
- package/docs/configuration.md +87 -94
- package/docs/getting-started.md +46 -27
- package/docs/hardware.md +1 -1
- package/docs/index.md +24 -30
- package/docs/integrations.md +46 -64
- package/docs/migration.md +41 -146
- package/docs/operations.md +23 -20
- package/docs/skills.md +20 -20
- package/docs/why-neoagent.md +15 -13
- package/extensions/chrome-browser/background.mjs +10 -2
- package/flutter_app/.metadata +42 -0
- package/flutter_app/DESIGN_SYSTEM.md +166 -0
- package/flutter_app/README.md +21 -0
- package/flutter_app/analysis_options.yaml +32 -0
- package/flutter_app/android/app/build.gradle.kts +111 -0
- package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
- package/flutter_app/android/app/src/main/AndroidManifest.xml +185 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +888 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/auto/NeoAgentCarAppService.kt +114 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnection.kt +118 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnectionService.kt +86 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
- package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
- package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
- package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
- package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
- 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/android/app/src/main/res/values/arrays.xml +8 -0
- package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
- package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
- package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
- package/flutter_app/android/app/src/main/res/xml/automotive_app_desc.xml +4 -0
- package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
- package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
- package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
- package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
- package/flutter_app/android/build.gradle.kts +24 -0
- package/flutter_app/android/ci-release.keystore +0 -0
- package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/flutter_app/android/gradle.properties +3 -0
- package/flutter_app/android/key.properties +4 -0
- package/flutter_app/android/settings.gradle.kts +26 -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/onboarding_intro.mp4 +0 -0
- package/flutter_app/assets/branding/tray_icon_template.png +0 -0
- package/flutter_app/lib/features/location/location_service.dart +117 -0
- package/flutter_app/lib/features/notifications/notification_interceptor.dart +73 -0
- package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +571 -0
- package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +275 -0
- package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +366 -0
- package/flutter_app/lib/features/onboarding/onboarding_shell.dart +57 -0
- package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +218 -0
- package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +46 -0
- package/flutter_app/lib/main.dart +107 -0
- package/flutter_app/lib/main_account_settings.dart +1340 -0
- package/flutter_app/lib/main_admin.dart +900 -0
- package/flutter_app/lib/main_app_shell.dart +2162 -0
- package/flutter_app/lib/main_chat.dart +3902 -0
- package/flutter_app/lib/main_controller.dart +7229 -0
- package/flutter_app/lib/main_devices.dart +1738 -0
- package/flutter_app/lib/main_integrations.dart +917 -0
- package/flutter_app/lib/main_launcher.dart +959 -0
- package/flutter_app/lib/main_launcher_entry.dart +5 -0
- package/flutter_app/lib/main_models.dart +3807 -0
- package/flutter_app/lib/main_navigation.dart +206 -0
- package/flutter_app/lib/main_operations.dart +5249 -0
- package/flutter_app/lib/main_recordings.dart +920 -0
- package/flutter_app/lib/main_runtime.dart +869 -0
- package/flutter_app/lib/main_settings.dart +2288 -0
- package/flutter_app/lib/main_shared.dart +3393 -0
- package/flutter_app/lib/main_spacing.dart +33 -0
- package/flutter_app/lib/main_theme.dart +276 -0
- package/flutter_app/lib/main_unified.dart +388 -0
- package/flutter_app/lib/main_voice_assistant.dart +957 -0
- package/flutter_app/lib/src/analytics_service.dart +294 -0
- package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
- package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
- package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
- package/flutter_app/lib/src/android_app_installer.dart +22 -0
- package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
- package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
- package/flutter_app/lib/src/android_auto_bridge.dart +59 -0
- package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
- package/flutter_app/lib/src/app_launch_bridge.dart +58 -0
- package/flutter_app/lib/src/app_release_updater.dart +511 -0
- package/flutter_app/lib/src/backend_client.dart +1870 -0
- package/flutter_app/lib/src/desktop_companion.dart +2 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
- package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
- package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
- package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
- package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
- package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
- package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
- package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
- package/flutter_app/lib/src/health_bridge.dart +136 -0
- package/flutter_app/lib/src/live_voice_capture.dart +85 -0
- package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
- package/flutter_app/lib/src/network/app_http_client.dart +53 -0
- package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
- package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
- package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
- package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
- package/flutter_app/lib/src/oauth_launcher.dart +33 -0
- package/flutter_app/lib/src/oauth_launcher_io.dart +95 -0
- package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
- package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
- package/flutter_app/lib/src/recording_bridge.dart +232 -0
- package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
- package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
- package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
- package/flutter_app/lib/src/recording_payloads.dart +86 -0
- package/flutter_app/lib/src/theme/palette.dart +81 -0
- package/flutter_app/lib/src/web_app_update_monitor.dart +17 -0
- package/flutter_app/lib/src/web_app_update_monitor_stub.dart +24 -0
- package/flutter_app/lib/src/web_app_update_monitor_web.dart +123 -0
- package/flutter_app/lib/src/widget_bridge.dart +49 -0
- package/flutter_app/linux/CMakeLists.txt +128 -0
- package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.cc +47 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/linux/flutter/generated_plugins.cmake +32 -0
- package/flutter_app/linux/runner/CMakeLists.txt +26 -0
- package/flutter_app/linux/runner/main.cc +6 -0
- package/flutter_app/linux/runner/my_application.cc +144 -0
- package/flutter_app/linux/runner/my_application.h +18 -0
- package/flutter_app/linux/runner/resources/app_icon.png +0 -0
- package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
- package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +48 -0
- package/flutter_app/macos/Podfile +42 -0
- package/flutter_app/macos/Podfile.lock +87 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -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/macos/Runner/Base.lproj/MainMenu.xib +342 -0
- package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
- package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
- package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
- package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
- package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
- package/flutter_app/macos/Runner/Info.plist +36 -0
- package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
- package/flutter_app/macos/Runner/Release.entitlements +12 -0
- package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
- package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
- package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
- package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
- package/flutter_app/patch_strings.py +12 -0
- package/flutter_app/pubspec.lock +1256 -0
- package/flutter_app/pubspec.yaml +59 -0
- package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
- package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
- package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
- package/flutter_app/tool/generate_desktop_branding.py +219 -0
- package/flutter_app/web/favicon.png +0 -0
- package/flutter_app/web/favicon.svg +12 -0
- package/flutter_app/web/icons/Icon-192.png +0 -0
- package/flutter_app/web/icons/Icon-512.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
- package/flutter_app/web/index.html +39 -0
- package/flutter_app/web/manifest.json +35 -0
- package/flutter_app/windows/CMakeLists.txt +108 -0
- package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.cc +50 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/windows/flutter/generated_plugins.cmake +36 -0
- package/flutter_app/windows/runner/CMakeLists.txt +41 -0
- package/flutter_app/windows/runner/Runner.rc +121 -0
- package/flutter_app/windows/runner/flutter_window.cpp +533 -0
- package/flutter_app/windows/runner/flutter_window.h +37 -0
- package/flutter_app/windows/runner/main.cpp +53 -0
- package/flutter_app/windows/runner/resource.h +16 -0
- package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
- package/flutter_app/windows/runner/runner.exe.manifest +14 -0
- package/flutter_app/windows/runner/utils.cpp +65 -0
- package/flutter_app/windows/runner/utils.h +19 -0
- package/flutter_app/windows/runner/win32_window.cpp +299 -0
- package/flutter_app/windows/runner/win32_window.h +102 -0
- package/lib/install_helpers.js +31 -0
- package/lib/manager.js +579 -122
- package/package.json +10 -3
- package/runtime/paths.js +137 -0
- package/server/config/analytics.js +30 -0
- package/server/db/database.js +296 -22
- package/server/guest-agent.android.package.json +13 -0
- package/server/guest-agent.browser.package.json +14 -0
- package/server/guest_agent.js +98 -52
- package/server/http/middleware.js +5 -2
- package/server/http/routes.js +3 -0
- package/server/http/static.js +41 -1
- package/server/index.js +15 -16
- 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/AssetManifest.json +1 -1
- package/server/public/assets/NOTICES +644 -155
- package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/assets/packages/mixpanel_flutter/assets/mixpanel.js +3 -0
- package/server/public/canvaskit/wimp.wasm +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +96067 -88139
- package/server/routes/account.js +26 -1
- package/server/routes/agents.js +6 -2
- package/server/routes/android.js +22 -6
- package/server/routes/auth.js +25 -5
- package/server/routes/browser.js +47 -13
- package/server/routes/integrations.js +22 -1
- package/server/routes/memory.js +168 -6
- package/server/routes/runtime.js +14 -0
- package/server/routes/settings.js +102 -24
- package/server/routes/skills.js +12 -0
- package/server/routes/social_video.js +66 -0
- package/server/routes/tasks.js +22 -2
- package/server/routes/triggers.js +44 -35
- package/server/routes/wearable.js +67 -0
- package/server/routes/widgets.js +4 -4
- package/server/services/ai/capabilityHealth.js +23 -51
- package/server/services/ai/compaction.js +1 -1
- package/server/services/ai/deliverables/artifact_helpers.js +190 -0
- package/server/services/ai/deliverables/contracts.js +113 -0
- package/server/services/ai/deliverables/index.js +20 -0
- package/server/services/ai/deliverables/selector.js +94 -0
- package/server/services/ai/deliverables/validator.js +63 -0
- package/server/services/ai/deliverables/workflows.js +207 -0
- package/server/services/ai/engine.js +363 -27
- package/server/services/ai/hooks.js +127 -0
- package/server/services/ai/integrated_tools/index.js +98 -0
- package/server/services/ai/integrated_tools/remotion.js +373 -0
- package/server/services/ai/integrated_tools/shared.js +204 -0
- package/server/services/ai/integrated_tools/slidev.js +207 -0
- package/server/services/ai/loopPolicy.js +146 -0
- package/server/services/ai/models.js +31 -4
- package/server/services/ai/providers/claudeCode.js +49 -0
- package/server/services/ai/providers/openai.js +2 -1
- package/server/services/ai/providers/openaiCodex.js +422 -15
- package/server/services/ai/recordingInsights.js +11 -13
- package/server/services/ai/runEvents.js +100 -0
- package/server/services/ai/settings.js +12 -2
- package/server/services/ai/systemPrompt.js +15 -1
- package/server/services/ai/taskAnalysis.js +2 -0
- package/server/services/ai/toolResult.js +20 -0
- package/server/services/ai/toolRunner.js +46 -6
- package/server/services/ai/toolSelector.js +54 -1
- package/server/services/ai/tools.js +210 -116
- package/server/services/android/android_bootstrap_worker.js +63 -0
- package/server/services/android/controller.js +442 -1760
- package/server/services/bootstrap_helpers.js +0 -34
- package/server/services/browser/controller.js +279 -49
- package/server/services/cli/executor.js +40 -1
- package/server/services/desktop/screenRecorder.js +65 -9
- package/server/services/integrations/figma/provider.js +1 -0
- package/server/services/integrations/github/provider.js +3 -3
- package/server/services/integrations/google/provider.js +1 -0
- package/server/services/integrations/manager.js +25 -5
- package/server/services/integrations/microsoft/provider.js +1 -0
- package/server/services/integrations/oauth_provider.js +15 -5
- package/server/services/integrations/provider_config_store.js +22 -13
- package/server/services/integrations/registry.js +2 -4
- package/server/services/integrations/spotify/provider.js +1 -0
- package/server/services/integrations/trello/provider.js +842 -0
- package/server/services/manager.js +88 -155
- package/server/services/mcp/client.js +120 -23
- package/server/services/memory/llm_transfer.js +218 -0
- package/server/services/memory/manager.js +260 -33
- package/server/services/messaging/access_policy.js +10 -0
- package/server/services/messaging/automation.js +1 -1
- package/server/services/messaging/manager.js +64 -1
- package/server/services/messaging/meshtastic.js +277 -0
- package/server/services/messaging/meshtastic_env.js +40 -0
- package/server/services/messaging/meshtastic_protocol.js +530 -0
- package/server/services/messaging/meshtastic_tcp_transport.js +25 -0
- package/server/services/runtime/backends/local-vm.js +221 -158
- package/server/services/runtime/docker-vm-manager.js +392 -0
- package/server/services/runtime/guest_bootstrap.js +654 -0
- package/server/services/runtime/manager.js +85 -29
- package/server/services/runtime/settings.js +22 -31
- package/server/services/runtime/validation.js +16 -31
- package/server/services/social_video/adapters/base.js +26 -0
- package/server/services/social_video/adapters/index.js +27 -0
- package/server/services/social_video/adapters/instagram.js +17 -0
- package/server/services/social_video/adapters/tiktok.js +17 -0
- package/server/services/social_video/adapters/x.js +17 -0
- package/server/services/social_video/adapters/youtube.js +17 -0
- package/server/services/social_video/captions.js +187 -0
- package/server/services/social_video/frame.js +42 -0
- package/server/services/social_video/index.js +7 -0
- package/server/services/social_video/metadata.js +63 -0
- package/server/services/social_video/result.js +63 -0
- package/server/services/social_video/service.js +720 -0
- package/server/services/social_video/url.js +83 -0
- package/server/services/tasks/integration_runtime.js +1 -1
- package/server/services/tasks/runtime.js +1 -1
- package/server/services/tasks/task_repository.js +3 -6
- package/server/services/voice/openaiSpeech.js +6 -1
- package/server/services/voice/providers.js +142 -14
- package/server/services/voice/runtime.js +8 -8
- package/server/services/voice/runtimeManager.js +156 -19
- package/server/services/voice/turnRunner.js +29 -9
- package/server/services/wearable/firmware_manifest.js +353 -0
- package/server/services/wearable/gateway.js +350 -0
- package/server/services/wearable/protocol.js +45 -0
- package/server/services/wearable/service.js +240 -0
- package/server/services/websocket.js +3 -1
- package/server/services/widgets/focus_widget.js +137 -0
- package/server/services/widgets/service.js +133 -2
- package/server/services/workspace/manager.js +301 -0
- package/server/utils/deployment.js +16 -9
- package/server/services/integrations/home_assistant/provider.js +0 -630
- package/server/services/messaging/access_policy.test.js +0 -228
- package/server/services/runtime/backends/host.js +0 -37
- package/server/services/runtime/qemu.js +0 -391
package/lib/manager.js
CHANGED
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
LOG_DIR,
|
|
19
19
|
ENV_FILE,
|
|
20
20
|
PID_FILE,
|
|
21
|
+
getDefaultVmBaseImageUrl,
|
|
21
22
|
ensureRuntimeDirs,
|
|
22
23
|
migrateLegacyRuntime
|
|
23
24
|
} = require('../runtime/paths');
|
|
@@ -118,28 +119,45 @@ function readEnvFileRaw() {
|
|
|
118
119
|
return fs.readFileSync(ENV_FILE, 'utf8');
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
function sanitizeEnvKey(key) {
|
|
123
|
+
return String(key).replace(/[\r\n]/g, '');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function sanitizeEnvValue(value) {
|
|
127
|
+
return String(value).replace(/[\r\n]/g, '');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function validateEnvKey(key) {
|
|
131
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
|
|
132
|
+
throw new Error(`Invalid env key "${key}". Keys must be uppercase letters, digits, and underscores (e.g. PORT, ANTHROPIC_API_KEY).`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
121
136
|
function upsertEnvValue(key, value) {
|
|
137
|
+
const safeKey = sanitizeEnvKey(key);
|
|
138
|
+
const safeValue = sanitizeEnvValue(value);
|
|
122
139
|
const raw = readEnvFileRaw();
|
|
123
140
|
const lines = raw ? raw.split('\n') : [];
|
|
124
141
|
let replaced = false;
|
|
125
142
|
|
|
126
143
|
for (let i = 0; i < lines.length; i++) {
|
|
127
|
-
if (lines[i].startsWith(`${
|
|
128
|
-
lines[i] = `${
|
|
144
|
+
if (lines[i].startsWith(`${safeKey}=`)) {
|
|
145
|
+
lines[i] = `${safeKey}=${safeValue}`;
|
|
129
146
|
replaced = true;
|
|
130
147
|
break;
|
|
131
148
|
}
|
|
132
149
|
}
|
|
133
150
|
|
|
134
|
-
if (!replaced) lines.push(`${
|
|
151
|
+
if (!replaced) lines.push(`${safeKey}=${safeValue}`);
|
|
135
152
|
const output = lines.filter((_, idx, arr) => idx !== arr.length - 1 || arr[idx] !== '').join('\n') + '\n';
|
|
136
153
|
fs.writeFileSync(ENV_FILE, output, { mode: 0o600 });
|
|
137
154
|
}
|
|
138
155
|
|
|
139
156
|
function removeEnvValue(key) {
|
|
157
|
+
const safeKey = sanitizeEnvKey(key);
|
|
140
158
|
const raw = readEnvFileRaw();
|
|
141
159
|
if (!raw) return false;
|
|
142
|
-
const lines = raw.split('\n').filter((line) => !line.startsWith(`${
|
|
160
|
+
const lines = raw.split('\n').filter((line) => !line.startsWith(`${safeKey}=`));
|
|
143
161
|
const output = lines.filter((_, idx, arr) => idx !== arr.length - 1 || arr[idx] !== '').join('\n') + '\n';
|
|
144
162
|
fs.writeFileSync(ENV_FILE, output, { mode: 0o600 });
|
|
145
163
|
return true;
|
|
@@ -287,6 +305,38 @@ function ensureLogDir() {
|
|
|
287
305
|
ensureRuntimeDirs();
|
|
288
306
|
}
|
|
289
307
|
|
|
308
|
+
function pruneOldRuntimeBackups(backupsDir, keepLatest = 3) {
|
|
309
|
+
if (!fs.existsSync(backupsDir) || keepLatest < 0) return;
|
|
310
|
+
|
|
311
|
+
const backupDirs = fs
|
|
312
|
+
.readdirSync(backupsDir, { withFileTypes: true })
|
|
313
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith('pre-update-'))
|
|
314
|
+
.map((entry) => {
|
|
315
|
+
const fullPath = path.join(backupsDir, entry.name);
|
|
316
|
+
let mtimeMs = 0;
|
|
317
|
+
try {
|
|
318
|
+
mtimeMs = fs.statSync(fullPath).mtimeMs;
|
|
319
|
+
} catch {
|
|
320
|
+
// Skip entries that disappear or cannot be statted.
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
return { name: entry.name, fullPath, mtimeMs };
|
|
324
|
+
})
|
|
325
|
+
.filter(Boolean)
|
|
326
|
+
.sort((a, b) => {
|
|
327
|
+
if (b.mtimeMs !== a.mtimeMs) return b.mtimeMs - a.mtimeMs;
|
|
328
|
+
return b.name.localeCompare(a.name);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
for (const backup of backupDirs.slice(keepLatest)) {
|
|
332
|
+
try {
|
|
333
|
+
fs.rmSync(backup.fullPath, { recursive: true, force: true });
|
|
334
|
+
} catch {
|
|
335
|
+
// Best-effort cleanup only.
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
290
340
|
function backupRuntimeData() {
|
|
291
341
|
const backupsDir = path.join(RUNTIME_HOME, 'backups');
|
|
292
342
|
fs.mkdirSync(backupsDir, { recursive: true });
|
|
@@ -296,6 +346,7 @@ function backupRuntimeData() {
|
|
|
296
346
|
|
|
297
347
|
if (fs.existsSync(ENV_FILE)) fs.copyFileSync(ENV_FILE, path.join(target, '.env'));
|
|
298
348
|
if (fs.existsSync(DATA_DIR)) fs.cpSync(DATA_DIR, path.join(target, 'data'), { recursive: true, force: false, errorOnExist: false });
|
|
349
|
+
pruneOldRuntimeBackups(backupsDir, 3);
|
|
299
350
|
}
|
|
300
351
|
|
|
301
352
|
function killByPort(port) {
|
|
@@ -347,15 +398,19 @@ function listNeoAgentServerProcesses() {
|
|
|
347
398
|
};
|
|
348
399
|
})
|
|
349
400
|
.filter(Boolean)
|
|
350
|
-
.filter((entry) =>
|
|
351
|
-
entry.pid
|
|
352
|
-
|
|
353
|
-
(
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
401
|
+
.filter((entry) => {
|
|
402
|
+
if (entry.pid === process.pid) return false;
|
|
403
|
+
const cmd = String(entry.command || '');
|
|
404
|
+
const cmdNormalized = cmd.replace(/\\/g, '/');
|
|
405
|
+
const executablePart = cmd.split(/\s+/)[0] || '';
|
|
406
|
+
const executableBase = path.basename(executablePart);
|
|
407
|
+
const isNode = /^node\d*$/.test(executableBase) || /(^|\s)node\d*(\s|$)/.test(cmd);
|
|
408
|
+
return isNode && (
|
|
409
|
+
appIndexPattern.test(cmdNormalized) ||
|
|
410
|
+
genericNeoAgentPattern.test(cmdNormalized) ||
|
|
411
|
+
repoNamePattern.test(cmd)
|
|
412
|
+
);
|
|
413
|
+
});
|
|
359
414
|
}
|
|
360
415
|
|
|
361
416
|
function killNeoAgentServerProcesses() {
|
|
@@ -425,18 +480,24 @@ async function cmdSetup() {
|
|
|
425
480
|
ensureRuntimeDirs();
|
|
426
481
|
|
|
427
482
|
const current = Object.fromEntries(parseEnv(readEnvFileRaw()).entries());
|
|
483
|
+
const defaultVmBaseImageUrl = getDefaultVmBaseImageUrl();
|
|
428
484
|
|
|
429
485
|
logInfo('Press Enter to keep the current value shown in brackets.');
|
|
430
486
|
|
|
431
487
|
heading('Core');
|
|
432
|
-
const
|
|
488
|
+
const portRaw = await ask('Server port', current.PORT || '3333');
|
|
489
|
+
const portNum = Number(portRaw);
|
|
490
|
+
if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) {
|
|
491
|
+
throw new Error(`Invalid port "${portRaw}". Must be an integer between 1 and 65535.`);
|
|
492
|
+
}
|
|
493
|
+
const port = String(portNum);
|
|
433
494
|
const publicUrl = await ask('Public base URL', current.PUBLIC_URL || '');
|
|
434
495
|
const secureCookiesDefault = current.SECURE_COOKIES ||
|
|
435
496
|
(String(publicUrl || '').trim().startsWith('https://') ? 'true' : 'false');
|
|
436
497
|
const secureCookies = await ask('Secure cookies (true/false)', secureCookiesDefault);
|
|
437
498
|
const trustProxyDefault = current.TRUST_PROXY || secureCookiesDefault;
|
|
438
499
|
const trustProxy = await ask('Trust reverse proxy headers (true/false)', trustProxyDefault);
|
|
439
|
-
const sessionSecret =
|
|
500
|
+
const sessionSecret = current.SESSION_SECRET || randomSecret();
|
|
440
501
|
const deploymentMode = await ask(
|
|
441
502
|
'Deployment mode (self_hosted/managed)',
|
|
442
503
|
current.NEOAGENT_DEPLOYMENT_MODE || 'self_hosted'
|
|
@@ -446,6 +507,11 @@ async function cmdSetup() {
|
|
|
446
507
|
current.NEOAGENT_RELEASE_CHANNEL || 'stable'
|
|
447
508
|
);
|
|
448
509
|
const origins = await ask('Allowed CORS origins', current.ALLOWED_ORIGINS || '');
|
|
510
|
+
const deploymentProfile = current.NEOAGENT_PROFILE || 'prod';
|
|
511
|
+
const vmBaseImageUrl = current.NEOAGENT_VM_BASE_IMAGE_URL || defaultVmBaseImageUrl;
|
|
512
|
+
const vmMemoryMb = current.NEOAGENT_VM_MEMORY_MB || '4096';
|
|
513
|
+
const vmCpus = current.NEOAGENT_VM_CPUS || '2';
|
|
514
|
+
const vmGuestToken = current.NEOAGENT_VM_GUEST_TOKEN || randomSecret();
|
|
449
515
|
|
|
450
516
|
heading('AI Providers');
|
|
451
517
|
const anthropic = await askSecret('Anthropic API key', current.ANTHROPIC_API_KEY || '');
|
|
@@ -542,6 +608,19 @@ async function cmdSetup() {
|
|
|
542
608
|
const normalizedDeploymentMode = parseDeploymentMode(deploymentMode);
|
|
543
609
|
const normalizedReleaseChannel = parseReleaseChannel(releaseChannel) || 'stable';
|
|
544
610
|
|
|
611
|
+
const githubOauthClientId = await askSecret(
|
|
612
|
+
'GitHub OAuth client ID',
|
|
613
|
+
current.GITHUB_OAUTH_CLIENT_ID || ''
|
|
614
|
+
);
|
|
615
|
+
const githubOauthClientSecret = await askSecret(
|
|
616
|
+
'GitHub OAuth client secret',
|
|
617
|
+
current.GITHUB_OAUTH_CLIENT_SECRET || ''
|
|
618
|
+
);
|
|
619
|
+
const githubOauthRedirectUri = await ask(
|
|
620
|
+
'GitHub OAuth redirect URI',
|
|
621
|
+
current.GITHUB_OAUTH_REDIRECT_URI || ''
|
|
622
|
+
);
|
|
623
|
+
|
|
545
624
|
const lines = [
|
|
546
625
|
`NODE_ENV=production`,
|
|
547
626
|
`PORT=${port}`,
|
|
@@ -549,8 +628,13 @@ async function cmdSetup() {
|
|
|
549
628
|
`SECURE_COOKIES=${normalizedSecureCookies}`,
|
|
550
629
|
`TRUST_PROXY=${normalizedTrustProxy}`,
|
|
551
630
|
`SESSION_SECRET=${sessionSecret}`,
|
|
631
|
+
`NEOAGENT_PROFILE=${deploymentProfile}`,
|
|
552
632
|
`NEOAGENT_DEPLOYMENT_MODE=${normalizedDeploymentMode}`,
|
|
553
633
|
`NEOAGENT_RELEASE_CHANNEL=${normalizedReleaseChannel}`,
|
|
634
|
+
`NEOAGENT_VM_BASE_IMAGE_URL=${vmBaseImageUrl}`,
|
|
635
|
+
`NEOAGENT_VM_MEMORY_MB=${vmMemoryMb}`,
|
|
636
|
+
`NEOAGENT_VM_CPUS=${vmCpus}`,
|
|
637
|
+
`NEOAGENT_VM_GUEST_TOKEN=${vmGuestToken}`,
|
|
554
638
|
anthropic ? `ANTHROPIC_API_KEY=${anthropic}` : '',
|
|
555
639
|
anthropicBaseUrl ? `ANTHROPIC_BASE_URL=${anthropicBaseUrl}` : '',
|
|
556
640
|
openai ? `OPENAI_API_KEY=${openai}` : '',
|
|
@@ -576,6 +660,9 @@ async function cmdSetup() {
|
|
|
576
660
|
figmaOauthClientId ? `FIGMA_OAUTH_CLIENT_ID=${figmaOauthClientId}` : '',
|
|
577
661
|
figmaOauthClientSecret ? `FIGMA_OAUTH_CLIENT_SECRET=${figmaOauthClientSecret}` : '',
|
|
578
662
|
figmaOauthRedirectUri ? `FIGMA_OAUTH_REDIRECT_URI=${figmaOauthRedirectUri}` : '',
|
|
663
|
+
githubOauthClientId ? `GITHUB_OAUTH_CLIENT_ID=${githubOauthClientId}` : '',
|
|
664
|
+
githubOauthClientSecret ? `GITHUB_OAUTH_CLIENT_SECRET=${githubOauthClientSecret}` : '',
|
|
665
|
+
githubOauthRedirectUri ? `GITHUB_OAUTH_REDIRECT_URI=${githubOauthRedirectUri}` : '',
|
|
579
666
|
deepgramApiKey ? `DEEPGRAM_API_KEY=${deepgramApiKey}` : '',
|
|
580
667
|
deepgramBaseUrl ? `DEEPGRAM_BASE_URL=${deepgramBaseUrl}` : '',
|
|
581
668
|
deepgramModel ? `DEEPGRAM_MODEL=${deepgramModel}` : '',
|
|
@@ -674,10 +761,192 @@ async function cmdMigrate(args = []) {
|
|
|
674
761
|
});
|
|
675
762
|
}
|
|
676
763
|
|
|
764
|
+
async function pollDeviceCode({ pollUrl, pollBody, pollHeaders = {}, intervalMs, timeoutMs, onToken }) {
|
|
765
|
+
const start = Date.now();
|
|
766
|
+
let currentInterval = intervalMs;
|
|
767
|
+
while (Date.now() - start < timeoutMs) {
|
|
768
|
+
await new Promise((r) => setTimeout(r, currentInterval));
|
|
769
|
+
const res = await fetch(pollUrl, {
|
|
770
|
+
method: 'POST',
|
|
771
|
+
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', ...pollHeaders },
|
|
772
|
+
body: JSON.stringify(pollBody()),
|
|
773
|
+
});
|
|
774
|
+
if (res.status === 403 || res.status === 404) continue;
|
|
775
|
+
if (!res.ok) {
|
|
776
|
+
const text = await res.text().catch(() => 'Unknown error');
|
|
777
|
+
throw new Error(`Token poll failed: HTTP ${res.status} — ${text}`);
|
|
778
|
+
}
|
|
779
|
+
const data = await res.json();
|
|
780
|
+
const done = await onToken(data);
|
|
781
|
+
if (done) return;
|
|
782
|
+
if (data.error === 'authorization_pending') continue;
|
|
783
|
+
if (data.error === 'slow_down') { currentInterval += 5000; continue; }
|
|
784
|
+
if (data.error) throw new Error(`Authentication failed: ${data.error_description || data.error}`);
|
|
785
|
+
}
|
|
786
|
+
throw new Error('Authentication timed out after 15 minutes.');
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
async function cmdLoginClaudeCode() {
|
|
790
|
+
heading('Claude Code Login');
|
|
791
|
+
|
|
792
|
+
// Check for Claude CLI credential file first (set by `claude login`)
|
|
793
|
+
const cliCredsPath = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
794
|
+
if (fs.existsSync(cliCredsPath)) {
|
|
795
|
+
try {
|
|
796
|
+
const raw = fs.readFileSync(cliCredsPath, 'utf8');
|
|
797
|
+
const data = JSON.parse(raw);
|
|
798
|
+
const token = data?.claudeAiOauthTokens?.accessToken;
|
|
799
|
+
if (token) {
|
|
800
|
+
upsertEnvValue('CLAUDE_CODE_OAUTH_TOKEN', token);
|
|
801
|
+
logOk('Imported access token from Claude CLI credentials store');
|
|
802
|
+
logInfo('Restarting NeoAgent to apply credentials...');
|
|
803
|
+
cmdRestart();
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
} catch { }
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Browser-based PKCE OAuth flow.
|
|
810
|
+
// client_id is the metadata URL per claude.ai's dynamic client registration.
|
|
811
|
+
// Redirect URIs registered: http://localhost/callback and http://127.0.0.1/callback (port 80).
|
|
812
|
+
// Per RFC 8252 §7.3, servers SHOULD allow any loopback port — we try high ports first
|
|
813
|
+
// and fall back to 80 if everything else is occupied.
|
|
814
|
+
const http = require('http');
|
|
815
|
+
const { URL: NodeURL } = require('url');
|
|
816
|
+
|
|
817
|
+
const clientId = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
|
|
818
|
+
const SCOPES = 'user:inference user:profile org:create_api_key user:sessions:claude_code user:mcp_servers';
|
|
819
|
+
|
|
820
|
+
// The registered redirect URIs are http://localhost/callback and http://127.0.0.1/callback
|
|
821
|
+
// (port 80). The OAuth server validates the URI exactly, so we must use port 80.
|
|
822
|
+
// Dynamic high port — the server accepts http://localhost:{any-port}/callback per RFC 8252.
|
|
823
|
+
const redirectPort = Math.floor(Math.random() * 10000) + 49152;
|
|
824
|
+
const redirectUri = `http://localhost:${redirectPort}/callback`;
|
|
825
|
+
|
|
826
|
+
// Generate PKCE verifier and challenge
|
|
827
|
+
const codeVerifier = crypto.randomBytes(48).toString('base64url');
|
|
828
|
+
const codeChallenge = crypto
|
|
829
|
+
.createHash('sha256')
|
|
830
|
+
.update(codeVerifier)
|
|
831
|
+
.digest('base64url');
|
|
832
|
+
|
|
833
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
834
|
+
|
|
835
|
+
const authUrl = new URL('https://platform.claude.com/oauth/authorize');
|
|
836
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
837
|
+
authUrl.searchParams.set('client_id', clientId);
|
|
838
|
+
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
839
|
+
authUrl.searchParams.set('scope', SCOPES);
|
|
840
|
+
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
841
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
842
|
+
authUrl.searchParams.set('state', state);
|
|
843
|
+
|
|
844
|
+
console.log(`\n ${COLORS.cyan}Opening browser for Claude Code authorization...${COLORS.reset}`);
|
|
845
|
+
console.log(` ${COLORS.dim}If the browser doesn't open, visit:${COLORS.reset}`);
|
|
846
|
+
console.log(` ${authUrl.toString()}\n`);
|
|
847
|
+
|
|
848
|
+
// Open browser
|
|
849
|
+
const openCmd = process.platform === 'darwin' ? 'open'
|
|
850
|
+
: process.platform === 'win32' ? 'start'
|
|
851
|
+
: 'xdg-open';
|
|
852
|
+
spawnSync(openCmd, [authUrl.toString()], { stdio: 'ignore' });
|
|
853
|
+
|
|
854
|
+
// Start local redirect server to capture authorization code
|
|
855
|
+
const authCode = await new Promise((resolve, reject) => {
|
|
856
|
+
const timeout = setTimeout(() => {
|
|
857
|
+
server.close();
|
|
858
|
+
reject(new Error('Claude Code authorization timed out after 5 minutes.'));
|
|
859
|
+
}, 5 * 60 * 1000);
|
|
860
|
+
|
|
861
|
+
const server = http.createServer((req, res) => {
|
|
862
|
+
try {
|
|
863
|
+
const reqUrl = new NodeURL(req.url, redirectUri);
|
|
864
|
+
const code = reqUrl.searchParams.get('code');
|
|
865
|
+
const returnedState = reqUrl.searchParams.get('state');
|
|
866
|
+
const error = reqUrl.searchParams.get('error');
|
|
867
|
+
|
|
868
|
+
if (error) {
|
|
869
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
870
|
+
res.end('<html><body><h2>Authorization failed.</h2><p>You can close this tab.</p></body></html>');
|
|
871
|
+
clearTimeout(timeout);
|
|
872
|
+
server.close();
|
|
873
|
+
reject(new Error(`OAuth error: ${error}`));
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (returnedState && returnedState !== state) {
|
|
878
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
879
|
+
res.end('<html><body><h2>Authorization failed.</h2><p>State mismatch. You can close this tab.</p></body></html>');
|
|
880
|
+
clearTimeout(timeout);
|
|
881
|
+
server.close();
|
|
882
|
+
reject(new Error('OAuth state mismatch — possible CSRF attempt.'));
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (code) {
|
|
887
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
888
|
+
res.end('<html><body><h2>Authorization successful!</h2><p>You can close this tab and return to the terminal.</p></body></html>');
|
|
889
|
+
clearTimeout(timeout);
|
|
890
|
+
server.close();
|
|
891
|
+
resolve(code);
|
|
892
|
+
}
|
|
893
|
+
} catch (err) {
|
|
894
|
+
res.writeHead(500);
|
|
895
|
+
res.end('Internal error');
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
server.listen(redirectPort, 'localhost', () => {
|
|
900
|
+
logInfo(`Waiting for OAuth callback on ${redirectUri} ...`);
|
|
901
|
+
});
|
|
902
|
+
server.on('error', (err) => {
|
|
903
|
+
clearTimeout(timeout);
|
|
904
|
+
reject(new Error(`Could not start OAuth callback server: ${err.message}`));
|
|
905
|
+
});
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
logInfo('Exchanging authorization code for access token...');
|
|
909
|
+
const tokenRes = await fetch('https://platform.claude.com/v1/oauth/token', {
|
|
910
|
+
method: 'POST',
|
|
911
|
+
headers: {
|
|
912
|
+
'Content-Type': 'application/json',
|
|
913
|
+
'Accept': 'application/json',
|
|
914
|
+
'anthropic-version': '2023-06-01',
|
|
915
|
+
},
|
|
916
|
+
body: JSON.stringify({
|
|
917
|
+
grant_type: 'authorization_code',
|
|
918
|
+
code: authCode,
|
|
919
|
+
redirect_uri: redirectUri,
|
|
920
|
+
client_id: clientId,
|
|
921
|
+
code_verifier: codeVerifier,
|
|
922
|
+
state,
|
|
923
|
+
}),
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
if (!tokenRes.ok) {
|
|
927
|
+
const text = await tokenRes.text().catch(() => 'Unknown error');
|
|
928
|
+
throw new Error(`Token exchange failed: HTTP ${tokenRes.status} — ${text}`);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const tokenData = await tokenRes.json();
|
|
932
|
+
const accessToken = tokenData.access_token;
|
|
933
|
+
if (!accessToken) {
|
|
934
|
+
throw new Error('Token exchange succeeded but no access_token was returned.');
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
upsertEnvValue('CLAUDE_CODE_OAUTH_TOKEN', accessToken);
|
|
938
|
+
if (tokenData.refresh_token) {
|
|
939
|
+
upsertEnvValue('CLAUDE_CODE_REFRESH_TOKEN', tokenData.refresh_token);
|
|
940
|
+
}
|
|
941
|
+
logOk('Saved Claude Code OAuth token to .env');
|
|
942
|
+
logInfo('Restarting NeoAgent to apply credentials...');
|
|
943
|
+
cmdRestart();
|
|
944
|
+
}
|
|
945
|
+
|
|
677
946
|
async function cmdLogin(args = []) {
|
|
678
947
|
const provider = args[0];
|
|
679
|
-
if (provider !== 'github-copilot' && provider !== 'openai-codex') {
|
|
680
|
-
throw new Error(`Unsupported login provider: ${provider || 'none'}. Available: github-copilot, openai-codex`);
|
|
948
|
+
if (provider !== 'github-copilot' && provider !== 'openai-codex' && provider !== 'claude-code') {
|
|
949
|
+
throw new Error(`Unsupported login provider: ${provider || 'none'}. Available: github-copilot, openai-codex, claude-code`);
|
|
681
950
|
}
|
|
682
951
|
|
|
683
952
|
if (provider === 'github-copilot') {
|
|
@@ -690,52 +959,28 @@ async function cmdLogin(args = []) {
|
|
|
690
959
|
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
|
|
691
960
|
body: JSON.stringify({ client_id: clientId, scope: 'user:email' })
|
|
692
961
|
});
|
|
693
|
-
|
|
694
|
-
if (!reqRes.ok) {
|
|
695
|
-
throw new Error(`Failed to request device code: HTTP ${reqRes.status}`);
|
|
696
|
-
}
|
|
962
|
+
if (!reqRes.ok) throw new Error(`Failed to request device code: HTTP ${reqRes.status}`);
|
|
697
963
|
|
|
698
964
|
const { device_code, user_code, verification_uri, interval } = await reqRes.json();
|
|
699
|
-
|
|
700
965
|
console.log(`\n ${COLORS.cyan}Please visit:${COLORS.reset} ${verification_uri}`);
|
|
701
|
-
console.log(` ${COLORS.cyan}
|
|
702
|
-
|
|
966
|
+
console.log(` ${COLORS.cyan}Enter code:${COLORS.reset} ${COLORS.bold}${user_code}${COLORS.reset}\n`);
|
|
703
967
|
logInfo('Waiting for authorization (timeout in 15m)...');
|
|
704
|
-
const startTime = Date.now();
|
|
705
|
-
const timeoutMs = 15 * 60 * 1000;
|
|
706
|
-
let currentPollInterval = (interval || 5) * 1000;
|
|
707
|
-
|
|
708
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
709
|
-
await new Promise((r) => setTimeout(r, currentPollInterval));
|
|
710
|
-
const tokenRes = await fetch('https://github.com/login/oauth/access_token', {
|
|
711
|
-
method: 'POST',
|
|
712
|
-
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
|
|
713
|
-
body: JSON.stringify({
|
|
714
|
-
client_id: clientId,
|
|
715
|
-
device_code,
|
|
716
|
-
grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
|
|
717
|
-
})
|
|
718
|
-
});
|
|
719
|
-
|
|
720
|
-
if (!tokenRes.ok) {
|
|
721
|
-
const errorText = await tokenRes.text().catch(() => 'Unknown error');
|
|
722
|
-
throw new Error(`GitHub token request failed: HTTP ${tokenRes.status} - ${errorText}`);
|
|
723
|
-
}
|
|
724
968
|
|
|
725
|
-
|
|
726
|
-
|
|
969
|
+
await pollDeviceCode({
|
|
970
|
+
pollUrl: 'https://github.com/login/oauth/access_token',
|
|
971
|
+
pollBody: () => ({ client_id: clientId, device_code, grant_type: 'urn:ietf:params:oauth:grant-type:device_code' }),
|
|
972
|
+
intervalMs: (interval || 5) * 1000,
|
|
973
|
+
timeoutMs: 15 * 60 * 1000,
|
|
974
|
+
onToken: async (data) => {
|
|
975
|
+
if (!data.access_token) return false;
|
|
727
976
|
upsertEnvValue('GITHUB_COPILOT_ACCESS_TOKEN', data.access_token);
|
|
728
|
-
logOk('
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
throw new Error(`Authentication failed: ${data.error_description || data.error}`);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
throw new Error('GitHub authentication timed out after 15 minutes.');
|
|
977
|
+
logOk('Saved GitHub Copilot access token to .env');
|
|
978
|
+
logInfo('Restarting NeoAgent to apply credentials...');
|
|
979
|
+
cmdRestart();
|
|
980
|
+
return true;
|
|
981
|
+
},
|
|
982
|
+
});
|
|
983
|
+
return;
|
|
739
984
|
} else if (provider === 'openai-codex') {
|
|
740
985
|
heading('OpenAI Codex Login');
|
|
741
986
|
const clientId = 'app_EMoamEEZ73f0CkXaXp7hrann';
|
|
@@ -751,47 +996,62 @@ async function cmdLogin(args = []) {
|
|
|
751
996
|
throw new Error(`Failed to request device code: HTTP ${reqRes.status}`);
|
|
752
997
|
}
|
|
753
998
|
|
|
754
|
-
const
|
|
999
|
+
const data = await reqRes.json();
|
|
1000
|
+
const { device_auth_id, interval } = data;
|
|
1001
|
+
const user_code = data.user_code || data.usercode;
|
|
1002
|
+
const verification_uri = 'https://auth.openai.com/codex/device';
|
|
755
1003
|
|
|
756
1004
|
console.log(`\n ${COLORS.cyan}Please visit:${COLORS.reset} ${verification_uri}`);
|
|
757
|
-
console.log(` ${COLORS.cyan}
|
|
758
|
-
|
|
1005
|
+
console.log(` ${COLORS.cyan}Enter code:${COLORS.reset} ${COLORS.bold}${user_code}${COLORS.reset}\n`);
|
|
759
1006
|
logInfo('Waiting for authorization (timeout in 15m)...');
|
|
760
|
-
const startTime = Date.now();
|
|
761
|
-
const timeoutMs = 15 * 60 * 1000;
|
|
762
|
-
let currentPollInterval = (interval || 5) * 1000;
|
|
763
|
-
|
|
764
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
765
|
-
await new Promise((r) => setTimeout(r, currentPollInterval));
|
|
766
|
-
const tokenRes = await fetch('https://auth.openai.com/api/accounts/deviceauth/token', {
|
|
767
|
-
method: 'POST',
|
|
768
|
-
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
|
|
769
|
-
body: JSON.stringify({
|
|
770
|
-
client_id: clientId,
|
|
771
|
-
device_code,
|
|
772
|
-
grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
|
|
773
|
-
})
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
if (!tokenRes.ok) {
|
|
777
|
-
const errorText = await tokenRes.text().catch(() => 'Unknown error');
|
|
778
|
-
throw new Error(`OpenAI token request failed: HTTP ${tokenRes.status} - ${errorText}`);
|
|
779
|
-
}
|
|
780
1007
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
1008
|
+
let authorizationCode = null;
|
|
1009
|
+
let codeVerifier = null;
|
|
1010
|
+
|
|
1011
|
+
await pollDeviceCode({
|
|
1012
|
+
pollUrl: 'https://auth.openai.com/api/accounts/deviceauth/token',
|
|
1013
|
+
pollBody: () => ({ device_auth_id, user_code }),
|
|
1014
|
+
intervalMs: (interval || 5) * 1000,
|
|
1015
|
+
timeoutMs: 15 * 60 * 1000,
|
|
1016
|
+
onToken: async (data) => {
|
|
1017
|
+
if (!data.authorization_code || !data.code_verifier) return false;
|
|
1018
|
+
authorizationCode = data.authorization_code;
|
|
1019
|
+
codeVerifier = data.code_verifier;
|
|
1020
|
+
return true;
|
|
1021
|
+
},
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
logInfo('Exchanging authorization code for access token...');
|
|
1025
|
+
const exchangeRes = await fetch('https://auth.openai.com/oauth/token', {
|
|
1026
|
+
method: 'POST',
|
|
1027
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
1028
|
+
body: new URLSearchParams({
|
|
1029
|
+
grant_type: 'authorization_code',
|
|
1030
|
+
code: authorizationCode,
|
|
1031
|
+
redirect_uri: 'https://auth.openai.com/deviceauth/callback',
|
|
1032
|
+
client_id: clientId,
|
|
1033
|
+
code_verifier: codeVerifier,
|
|
1034
|
+
}),
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
if (!exchangeRes.ok) {
|
|
1038
|
+
const errorText = await exchangeRes.text().catch(() => 'Unknown error');
|
|
1039
|
+
throw new Error(`OpenAI token exchange failed: HTTP ${exchangeRes.status} — ${errorText}`);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const exchangeData = await exchangeRes.json();
|
|
1043
|
+
if (!exchangeData.access_token) {
|
|
1044
|
+
throw new Error('OpenAI token exchange succeeded but did not return an access token.');
|
|
1045
|
+
}
|
|
1046
|
+
upsertEnvValue('OPENAI_CODEX_ACCESS_TOKEN', exchangeData.access_token);
|
|
1047
|
+
if (exchangeData.refresh_token) {
|
|
1048
|
+
upsertEnvValue('OPENAI_CODEX_REFRESH_TOKEN', exchangeData.refresh_token);
|
|
793
1049
|
}
|
|
794
|
-
|
|
1050
|
+
logOk('Saved OpenAI Codex tokens to .env');
|
|
1051
|
+
logInfo('Restarting NeoAgent to apply credentials...');
|
|
1052
|
+
cmdRestart();
|
|
1053
|
+
} else if (provider === 'claude-code') {
|
|
1054
|
+
await cmdLoginClaudeCode();
|
|
795
1055
|
}
|
|
796
1056
|
}
|
|
797
1057
|
|
|
@@ -891,6 +1151,109 @@ function startFallback() {
|
|
|
891
1151
|
logOk(`Started detached process (pid ${child.pid})`);
|
|
892
1152
|
}
|
|
893
1153
|
|
|
1154
|
+
async function ensureQemuInstalled() {
|
|
1155
|
+
heading('Ensure QEMU Installed');
|
|
1156
|
+
const platform = detectPlatform();
|
|
1157
|
+
|
|
1158
|
+
const hasSystem = commandExists('qemu-system-x86_64') || commandExists('qemu-system-aarch64');
|
|
1159
|
+
const hasImg = commandExists('qemu-img');
|
|
1160
|
+
|
|
1161
|
+
if (hasSystem && hasImg) {
|
|
1162
|
+
logOk('QEMU components are already installed');
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
logInfo('QEMU components not found. Attempting to install...');
|
|
1167
|
+
|
|
1168
|
+
if (platform === 'macos') {
|
|
1169
|
+
if (!commandExists('brew')) {
|
|
1170
|
+
throw new Error('Homebrew is required to install QEMU on macOS. Please install it first: https://brew.sh/');
|
|
1171
|
+
}
|
|
1172
|
+
logInfo('Running "brew install qemu"...');
|
|
1173
|
+
runOrThrow('brew', ['install', 'qemu']);
|
|
1174
|
+
} else if (platform === 'linux') {
|
|
1175
|
+
const isArm = process.arch === 'arm64' || process.arch === 'aarch64';
|
|
1176
|
+
if (commandExists('apt-get')) {
|
|
1177
|
+
const pkgs = ['qemu-utils'];
|
|
1178
|
+
if (isArm) {
|
|
1179
|
+
pkgs.push('qemu-system-arm');
|
|
1180
|
+
} else {
|
|
1181
|
+
pkgs.push('qemu-system-x86');
|
|
1182
|
+
}
|
|
1183
|
+
logInfo(`Running "sudo apt-get update && sudo apt-get install -y ${pkgs.join(' ')}"...`);
|
|
1184
|
+
runOrThrow('sudo', ['apt-get', 'update']);
|
|
1185
|
+
runOrThrow('sudo', ['apt-get', 'install', '-y', ...pkgs]);
|
|
1186
|
+
} else if (commandExists('dnf')) {
|
|
1187
|
+
logInfo('Running "sudo dnf install -y qemu-kvm qemu-img"...');
|
|
1188
|
+
runOrThrow('sudo', ['dnf', 'install', '-y', 'qemu-kvm', 'qemu-img']);
|
|
1189
|
+
} else if (commandExists('yum')) {
|
|
1190
|
+
logInfo('Running "sudo yum install -y qemu-kvm qemu-img"...');
|
|
1191
|
+
runOrThrow('sudo', ['yum', 'install', '-y', 'qemu-kvm', 'qemu-img']);
|
|
1192
|
+
} else {
|
|
1193
|
+
throw new Error('Unsupported Linux distribution. Please install qemu-system and qemu-utils manually.');
|
|
1194
|
+
}
|
|
1195
|
+
} else {
|
|
1196
|
+
throw new Error('Unsupported platform for automatic QEMU installation. Please install QEMU manually.');
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
const verifiedSystem = commandExists('qemu-system-x86_64') || commandExists('qemu-system-aarch64');
|
|
1200
|
+
const verifiedImg = commandExists('qemu-img');
|
|
1201
|
+
|
|
1202
|
+
if (verifiedSystem && verifiedImg) {
|
|
1203
|
+
logOk('QEMU installed successfully');
|
|
1204
|
+
} else {
|
|
1205
|
+
throw new Error('QEMU installation failed or components not found in PATH after install.');
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
function ensureYtDlpInstalled() {
|
|
1210
|
+
heading('Ensure yt-dlp Installed');
|
|
1211
|
+
if (commandExists('yt-dlp')) {
|
|
1212
|
+
const ver = runQuiet('yt-dlp', ['--version']);
|
|
1213
|
+
logOk(`yt-dlp ${ver.status === 0 ? ver.stdout.trim() : '(version unknown)'}`);
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
logInfo('yt-dlp not found. Attempting to install...');
|
|
1218
|
+
const platform = detectPlatform();
|
|
1219
|
+
|
|
1220
|
+
if (platform === 'macos') {
|
|
1221
|
+
if (!commandExists('brew')) {
|
|
1222
|
+
logWarn('Homebrew not found — skipping yt-dlp install. Install manually: brew install yt-dlp');
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
try {
|
|
1226
|
+
runOrThrow('brew', ['install', 'yt-dlp']);
|
|
1227
|
+
logOk('yt-dlp installed via Homebrew');
|
|
1228
|
+
} catch {
|
|
1229
|
+
logWarn('yt-dlp install failed. Install manually: brew install yt-dlp');
|
|
1230
|
+
}
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
if (platform === 'linux') {
|
|
1235
|
+
if (commandExists('pipx')) {
|
|
1236
|
+
try {
|
|
1237
|
+
runOrThrow('pipx', ['install', 'yt-dlp']);
|
|
1238
|
+
logOk('yt-dlp installed via pipx');
|
|
1239
|
+
return;
|
|
1240
|
+
} catch {
|
|
1241
|
+
// fall through to pip3
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (commandExists('pip3')) {
|
|
1245
|
+
try {
|
|
1246
|
+
runOrThrow('pip3', ['install', '--user', 'yt-dlp']);
|
|
1247
|
+
logOk('yt-dlp installed via pip3');
|
|
1248
|
+
return;
|
|
1249
|
+
} catch {
|
|
1250
|
+
// fall through to warn
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
logWarn('Could not install yt-dlp automatically. Install manually: pipx install yt-dlp');
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
894
1257
|
async function cmdInstall() {
|
|
895
1258
|
heading(`Install ${APP_NAME}`);
|
|
896
1259
|
if (!fs.existsSync(ENV_FILE)) {
|
|
@@ -899,6 +1262,8 @@ async function cmdInstall() {
|
|
|
899
1262
|
}
|
|
900
1263
|
|
|
901
1264
|
installDependencies();
|
|
1265
|
+
await ensureQemuInstalled();
|
|
1266
|
+
ensureYtDlpInstalled();
|
|
902
1267
|
buildBundledWebClientIfPossible({ required: true });
|
|
903
1268
|
|
|
904
1269
|
const platform = detectPlatform();
|
|
@@ -960,7 +1325,7 @@ function cmdStop() {
|
|
|
960
1325
|
logOk(`Stopped pid ${pid}`);
|
|
961
1326
|
stopped = true;
|
|
962
1327
|
} catch {
|
|
963
|
-
logWarn(`pid ${pid} not running`);
|
|
1328
|
+
logWarn(`pid ${pid} was not running (stale PID file)`);
|
|
964
1329
|
}
|
|
965
1330
|
}
|
|
966
1331
|
fs.rmSync(pidPath, { force: true });
|
|
@@ -988,6 +1353,11 @@ function cmdRestart() {
|
|
|
988
1353
|
cmdStart();
|
|
989
1354
|
}
|
|
990
1355
|
|
|
1356
|
+
async function cmdRebuildWeb() {
|
|
1357
|
+
heading(`Rebuild Flutter Web Client`);
|
|
1358
|
+
buildBundledWebClientIfPossible();
|
|
1359
|
+
}
|
|
1360
|
+
|
|
991
1361
|
function cmdUninstall() {
|
|
992
1362
|
heading(`Uninstall ${APP_NAME}`);
|
|
993
1363
|
const platform = detectPlatform();
|
|
@@ -1016,22 +1386,52 @@ async function cmdStatus() {
|
|
|
1016
1386
|
const port = loadEnvPort();
|
|
1017
1387
|
const running = await isPortOpen(port);
|
|
1018
1388
|
const releaseChannel = currentReleaseChannel();
|
|
1389
|
+
const platform = detectPlatform();
|
|
1019
1390
|
|
|
1020
1391
|
if (running) {
|
|
1021
|
-
logOk(`
|
|
1392
|
+
logOk(`server http://localhost:${port}`);
|
|
1022
1393
|
} else {
|
|
1023
|
-
logWarn(`not reachable on port ${port}`);
|
|
1394
|
+
logWarn(`server not reachable on port ${port}`);
|
|
1024
1395
|
}
|
|
1025
1396
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1397
|
+
if (platform === 'macos' && fs.existsSync(PLIST_DST)) {
|
|
1398
|
+
const svcRes = runQuiet('launchctl', ['list', SERVICE_LABEL]);
|
|
1399
|
+
if (svcRes.status === 0 && svcRes.stdout.trim()) {
|
|
1400
|
+
logOk(`service launchd (${SERVICE_LABEL})`);
|
|
1401
|
+
} else {
|
|
1402
|
+
logWarn(`service launchd unit not loaded — run: neoagent install`);
|
|
1403
|
+
}
|
|
1404
|
+
} else if (platform === 'linux' && fs.existsSync(SYSTEMD_UNIT)) {
|
|
1405
|
+
const svcRes = runQuiet('systemctl', ['--user', 'is-active', 'neoagent']);
|
|
1406
|
+
if (svcRes.status === 0 && svcRes.stdout.trim() === 'active') {
|
|
1407
|
+
logOk('service systemd (neoagent)');
|
|
1408
|
+
} else {
|
|
1409
|
+
logWarn('service systemd unit not active — run: neoagent install');
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
if (fs.existsSync(ENV_FILE)) {
|
|
1414
|
+
logOk(`config ${ENV_FILE}`);
|
|
1415
|
+
} else {
|
|
1416
|
+
logWarn(`config .env not found — run: neoagent setup`);
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
if (hasBundledWebClient(WEB_CLIENT_DIR)) {
|
|
1420
|
+
logOk('web bundled Flutter client present');
|
|
1421
|
+
} else {
|
|
1422
|
+
logWarn('web no bundled client — run: neoagent rebuild-web');
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
console.log('');
|
|
1426
|
+
console.log(` install ${APP_DIR}`);
|
|
1427
|
+
console.log(` version ${currentInstalledVersionLabel()}`);
|
|
1428
|
+
console.log(` channel ${releaseChannelSummary(releaseChannel)}`);
|
|
1029
1429
|
|
|
1030
1430
|
const processes = listNeoAgentServerProcesses();
|
|
1031
1431
|
if (processes.length > 0) {
|
|
1032
|
-
console.log(`
|
|
1432
|
+
console.log(` pids ${processes.map((proc) => proc.pid).join(', ')}`);
|
|
1033
1433
|
if (processes.length > 1) {
|
|
1034
|
-
logWarn(`multiple NeoAgent
|
|
1434
|
+
logWarn(`multiple NeoAgent processes detected (${processes.length})`);
|
|
1035
1435
|
}
|
|
1036
1436
|
}
|
|
1037
1437
|
}
|
|
@@ -1083,6 +1483,8 @@ function cmdUpdate(args = []) {
|
|
|
1083
1483
|
}
|
|
1084
1484
|
const versionBefore = currentInstalledVersionLabel();
|
|
1085
1485
|
let versionAfter = versionBefore;
|
|
1486
|
+
const githubInstallRef = releaseChannel === 'beta' ? '#beta' : '';
|
|
1487
|
+
const githubInstallSpec = `git+https://github.com/NeoLabs-Systems/NeoAgent.git${githubInstallRef}`;
|
|
1086
1488
|
|
|
1087
1489
|
if (fs.existsSync(path.join(APP_DIR, '.git')) && commandExists('git')) {
|
|
1088
1490
|
const current = runQuiet('git', ['rev-parse', '--short', 'HEAD']);
|
|
@@ -1091,6 +1493,7 @@ function cmdUpdate(args = []) {
|
|
|
1091
1493
|
const targetBranch = resolvePreferredGitBranch(releaseChannel);
|
|
1092
1494
|
logInfo(`Using git branch ${targetBranch} for the ${releaseChannel} channel.`);
|
|
1093
1495
|
ensureGitBranchForReleaseChannel(targetBranch);
|
|
1496
|
+
backupRuntimeData();
|
|
1094
1497
|
runOrThrow('git', ['pull', '--rebase', '--autostash', 'origin', targetBranch]);
|
|
1095
1498
|
|
|
1096
1499
|
const next = runQuiet('git', ['rev-parse', '--short', 'HEAD']);
|
|
@@ -1103,17 +1506,16 @@ function cmdUpdate(args = []) {
|
|
|
1103
1506
|
buildBundledWebClientIfPossible();
|
|
1104
1507
|
}
|
|
1105
1508
|
} else {
|
|
1106
|
-
|
|
1107
|
-
logWarn(`No git repo detected; attempting npm global update from ${npmTag}.`);
|
|
1509
|
+
logWarn(`No git repo detected; attempting npm global update from ${githubInstallSpec}.`);
|
|
1108
1510
|
if (commandExists('npm')) {
|
|
1109
1511
|
try {
|
|
1110
1512
|
backupRuntimeData();
|
|
1111
|
-
runOrThrow('npm', ['install', '-g',
|
|
1513
|
+
runOrThrow('npm', ['install', '-g', githubInstallSpec, '--force'], {
|
|
1112
1514
|
env: withInstallEnv()
|
|
1113
1515
|
});
|
|
1114
|
-
logOk('npm global update completed (forced reinstall)');
|
|
1516
|
+
logOk('npm global update completed (forced reinstall from GitHub)');
|
|
1115
1517
|
} catch {
|
|
1116
|
-
logWarn(`npm global update failed. Run: npm install -g
|
|
1518
|
+
logWarn(`npm global update failed. Run: npm install -g ${githubInstallSpec} --force`);
|
|
1117
1519
|
}
|
|
1118
1520
|
} else {
|
|
1119
1521
|
logWarn('npm not found. Cannot perform global update.');
|
|
@@ -1121,6 +1523,7 @@ function cmdUpdate(args = []) {
|
|
|
1121
1523
|
}
|
|
1122
1524
|
|
|
1123
1525
|
versionAfter = currentInstalledVersionLabel();
|
|
1526
|
+
ensureYtDlpInstalled();
|
|
1124
1527
|
|
|
1125
1528
|
if (!hasBundledWebClient(WEB_CLIENT_DIR)) {
|
|
1126
1529
|
throw new Error('No bundled Flutter web client found after update.');
|
|
@@ -1132,11 +1535,16 @@ function cmdUpdate(args = []) {
|
|
|
1132
1535
|
|
|
1133
1536
|
async function cmdEnv(args = []) {
|
|
1134
1537
|
heading('Environment Variables');
|
|
1135
|
-
|
|
1538
|
+
const action = (args[0] || '').trim().toLowerCase();
|
|
1136
1539
|
|
|
1137
1540
|
if (!action) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1541
|
+
console.log('Usage: neoagent env <subcommand>');
|
|
1542
|
+
console.log('');
|
|
1543
|
+
console.log(' neoagent env list List all variables (secrets masked)');
|
|
1544
|
+
console.log(' neoagent env get KEY Print a single variable');
|
|
1545
|
+
console.log(' neoagent env set KEY VALUE Set a variable');
|
|
1546
|
+
console.log(' neoagent env unset KEY Remove a variable');
|
|
1547
|
+
return;
|
|
1140
1548
|
}
|
|
1141
1549
|
|
|
1142
1550
|
if (action === 'list') {
|
|
@@ -1161,16 +1569,17 @@ async function cmdEnv(args = []) {
|
|
|
1161
1569
|
}
|
|
1162
1570
|
|
|
1163
1571
|
if (action === 'set') {
|
|
1164
|
-
const key = args[1]
|
|
1165
|
-
const value = args.slice(2).join(' ')
|
|
1572
|
+
const key = args[1];
|
|
1573
|
+
const value = args.slice(2).join(' ');
|
|
1166
1574
|
if (!key || !value) throw new Error('Usage: neoagent env set <KEY> <VALUE>');
|
|
1575
|
+
validateEnvKey(key);
|
|
1167
1576
|
upsertEnvValue(key, value);
|
|
1168
1577
|
logOk(`Set ${key} in ${ENV_FILE}`);
|
|
1169
1578
|
return;
|
|
1170
1579
|
}
|
|
1171
1580
|
|
|
1172
1581
|
if (action === 'unset') {
|
|
1173
|
-
const key = args[1]
|
|
1582
|
+
const key = args[1];
|
|
1174
1583
|
if (!key) throw new Error('Usage: neoagent env unset <KEY>');
|
|
1175
1584
|
removeEnvValue(key);
|
|
1176
1585
|
logOk(`Removed ${key} from ${ENV_FILE}`);
|
|
@@ -1180,16 +1589,56 @@ async function cmdEnv(args = []) {
|
|
|
1180
1589
|
throw new Error('Usage: neoagent env [list|get|set|unset] ...');
|
|
1181
1590
|
}
|
|
1182
1591
|
|
|
1592
|
+
function cmdVersion() {
|
|
1593
|
+
console.log(currentInstalledVersionLabel());
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1183
1596
|
function printHelp() {
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
console.log(
|
|
1597
|
+
const c = COLORS;
|
|
1598
|
+
const W = 38;
|
|
1599
|
+
|
|
1600
|
+
function row(cmd, desc) {
|
|
1601
|
+
const padded = ` neoagent ${cmd}`.padEnd(W);
|
|
1602
|
+
console.log(`${padded}${c.dim}${desc}${c.reset}`);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
console.log(`\n${c.bold}neoagent${c.reset} — manage your NeoAgent server\n`);
|
|
1606
|
+
console.log(`${c.bold}Usage${c.reset} neoagent <command> [args]\n`);
|
|
1607
|
+
|
|
1608
|
+
console.log(`${c.bold}Lifecycle${c.reset}`);
|
|
1609
|
+
row('install', 'First-time install and service setup');
|
|
1610
|
+
row('start', 'Start the server');
|
|
1611
|
+
row('stop', 'Stop the server');
|
|
1612
|
+
row('restart', 'Stop, then start');
|
|
1613
|
+
row('status', 'Health overview (server, service, config)');
|
|
1614
|
+
row('logs', 'Tail server logs');
|
|
1615
|
+
row('uninstall', 'Remove the system service');
|
|
1616
|
+
console.log('');
|
|
1617
|
+
|
|
1618
|
+
console.log(`${c.bold}Configuration${c.reset}`);
|
|
1619
|
+
row('setup', 'Interactive configuration wizard');
|
|
1620
|
+
row('env list', 'List all variables (secrets masked)');
|
|
1621
|
+
row('env get KEY', 'Print a single variable');
|
|
1622
|
+
row('env set KEY VALUE', 'Set a variable');
|
|
1623
|
+
row('env unset KEY', 'Remove a variable');
|
|
1624
|
+
row('channel', 'Show current release channel');
|
|
1625
|
+
row('channel stable|beta', 'Switch release channel');
|
|
1626
|
+
console.log('');
|
|
1627
|
+
|
|
1628
|
+
console.log(`${c.bold}Updates & Auth${c.reset}`);
|
|
1629
|
+
row('update', 'Update to latest on current channel');
|
|
1630
|
+
row('update stable|beta', 'Update and switch channel');
|
|
1631
|
+
row('login github-copilot','Authenticate GitHub Copilot');
|
|
1632
|
+
row('login openai-codex', 'Authenticate OpenAI Codex');
|
|
1633
|
+
row('login claude-code', 'Authenticate Claude Code');
|
|
1634
|
+
console.log('');
|
|
1635
|
+
|
|
1636
|
+
console.log(`${c.bold}Maintenance${c.reset}`);
|
|
1637
|
+
row('migrate', 'Migrate from another agent installation');
|
|
1638
|
+
row('migrate dry-run', 'Preview what would be migrated');
|
|
1639
|
+
row('rebuild-web', 'Rebuild the bundled Flutter web client');
|
|
1640
|
+
row('version', 'Print installed version');
|
|
1641
|
+
console.log('');
|
|
1193
1642
|
}
|
|
1194
1643
|
|
|
1195
1644
|
async function runCLI(argv) {
|
|
@@ -1216,6 +1665,9 @@ async function runCLI(argv) {
|
|
|
1216
1665
|
case 'restart':
|
|
1217
1666
|
cmdRestart();
|
|
1218
1667
|
break;
|
|
1668
|
+
case 'rebuild-web':
|
|
1669
|
+
await cmdRebuildWeb();
|
|
1670
|
+
break;
|
|
1219
1671
|
case 'start':
|
|
1220
1672
|
cmdStart();
|
|
1221
1673
|
break;
|
|
@@ -1237,13 +1689,18 @@ async function runCLI(argv) {
|
|
|
1237
1689
|
case 'login':
|
|
1238
1690
|
await cmdLogin(argv.slice(1));
|
|
1239
1691
|
break;
|
|
1692
|
+
case 'version':
|
|
1693
|
+
case '--version':
|
|
1694
|
+
case '-V':
|
|
1695
|
+
cmdVersion();
|
|
1696
|
+
break;
|
|
1240
1697
|
case 'help':
|
|
1241
1698
|
case '--help':
|
|
1242
1699
|
case '-h':
|
|
1243
1700
|
printHelp();
|
|
1244
1701
|
break;
|
|
1245
1702
|
default:
|
|
1246
|
-
throw new Error(`Unknown command: ${command}
|
|
1703
|
+
throw new Error(`Unknown command: ${command}. Run "neoagent --help" for usage.`);
|
|
1247
1704
|
}
|
|
1248
1705
|
}
|
|
1249
1706
|
|