neoagent 2.3.1-beta.0 → 2.3.1-beta.100

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.
Files changed (406) hide show
  1. package/.env.example +63 -5
  2. package/README.md +16 -5
  3. package/com.neoagent.plist +1 -1
  4. package/docs/automation.md +82 -41
  5. package/docs/capabilities.md +85 -89
  6. package/docs/configuration.md +89 -87
  7. package/docs/getting-started.md +46 -27
  8. package/docs/hardware.md +1 -1
  9. package/docs/index.md +24 -30
  10. package/docs/integrations.md +46 -60
  11. package/docs/migration.md +41 -146
  12. package/docs/operations.md +23 -20
  13. package/docs/skills.md +20 -20
  14. package/docs/why-neoagent.md +15 -13
  15. package/extensions/chrome-browser/background.mjs +10 -2
  16. package/flutter_app/.metadata +42 -0
  17. package/flutter_app/DESIGN_SYSTEM.md +166 -0
  18. package/flutter_app/README.md +21 -0
  19. package/flutter_app/analysis_options.yaml +32 -0
  20. package/flutter_app/android/app/build.gradle.kts +111 -0
  21. package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
  22. package/flutter_app/android/app/src/main/AndroidManifest.xml +185 -0
  23. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +888 -0
  24. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/auto/NeoAgentCarAppService.kt +114 -0
  25. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
  26. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
  27. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
  28. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
  29. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
  30. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
  31. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
  32. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
  33. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
  34. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
  35. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnection.kt +118 -0
  36. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnectionService.kt +86 -0
  37. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
  38. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
  39. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
  40. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
  41. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
  42. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
  43. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
  44. package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  45. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
  46. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
  47. package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  48. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
  49. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
  50. package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
  51. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  52. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  53. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  54. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  55. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  56. package/flutter_app/android/app/src/main/res/values/arrays.xml +8 -0
  57. package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
  58. package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
  59. package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
  60. package/flutter_app/android/app/src/main/res/xml/automotive_app_desc.xml +4 -0
  61. package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
  62. package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
  63. package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
  64. package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
  65. package/flutter_app/android/build.gradle.kts +24 -0
  66. package/flutter_app/android/ci-release.keystore +0 -0
  67. package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  68. package/flutter_app/android/gradle.properties +3 -0
  69. package/flutter_app/android/key.properties +4 -0
  70. package/flutter_app/android/settings.gradle.kts +26 -0
  71. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  72. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  73. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  74. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  75. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  76. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  77. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  78. package/flutter_app/assets/branding/onboarding_intro.mp4 +0 -0
  79. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  80. package/flutter_app/lib/features/location/location_service.dart +117 -0
  81. package/flutter_app/lib/features/notifications/notification_interceptor.dart +73 -0
  82. package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +571 -0
  83. package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +275 -0
  84. package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +366 -0
  85. package/flutter_app/lib/features/onboarding/onboarding_shell.dart +57 -0
  86. package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +218 -0
  87. package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +46 -0
  88. package/flutter_app/lib/main.dart +107 -0
  89. package/flutter_app/lib/main_account_settings.dart +1340 -0
  90. package/flutter_app/lib/main_admin.dart +900 -0
  91. package/flutter_app/lib/main_app_shell.dart +2162 -0
  92. package/flutter_app/lib/main_chat.dart +3902 -0
  93. package/flutter_app/lib/main_controller.dart +7229 -0
  94. package/flutter_app/lib/main_devices.dart +1738 -0
  95. package/flutter_app/lib/main_integrations.dart +917 -0
  96. package/flutter_app/lib/main_launcher.dart +959 -0
  97. package/flutter_app/lib/main_launcher_entry.dart +5 -0
  98. package/flutter_app/lib/main_models.dart +3807 -0
  99. package/flutter_app/lib/main_navigation.dart +206 -0
  100. package/flutter_app/lib/main_operations.dart +5249 -0
  101. package/flutter_app/lib/main_recordings.dart +920 -0
  102. package/flutter_app/lib/main_runtime.dart +869 -0
  103. package/flutter_app/lib/main_settings.dart +2288 -0
  104. package/flutter_app/lib/main_shared.dart +3393 -0
  105. package/flutter_app/lib/main_spacing.dart +33 -0
  106. package/flutter_app/lib/main_theme.dart +276 -0
  107. package/flutter_app/lib/main_unified.dart +388 -0
  108. package/flutter_app/lib/main_voice_assistant.dart +957 -0
  109. package/flutter_app/lib/src/analytics_service.dart +294 -0
  110. package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
  111. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
  112. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
  113. package/flutter_app/lib/src/android_app_installer.dart +22 -0
  114. package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
  115. package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
  116. package/flutter_app/lib/src/android_auto_bridge.dart +59 -0
  117. package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
  118. package/flutter_app/lib/src/app_launch_bridge.dart +58 -0
  119. package/flutter_app/lib/src/app_release_updater.dart +511 -0
  120. package/flutter_app/lib/src/backend_client.dart +1870 -0
  121. package/flutter_app/lib/src/desktop_companion.dart +2 -0
  122. package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
  123. package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
  124. package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
  125. package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
  126. package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
  127. package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
  128. package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
  129. package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
  130. package/flutter_app/lib/src/health_bridge.dart +136 -0
  131. package/flutter_app/lib/src/live_voice_capture.dart +85 -0
  132. package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
  133. package/flutter_app/lib/src/network/app_http_client.dart +53 -0
  134. package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
  135. package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
  136. package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
  137. package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
  138. package/flutter_app/lib/src/oauth_launcher.dart +33 -0
  139. package/flutter_app/lib/src/oauth_launcher_io.dart +95 -0
  140. package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
  141. package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
  142. package/flutter_app/lib/src/recording_bridge.dart +232 -0
  143. package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
  144. package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
  145. package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
  146. package/flutter_app/lib/src/recording_payloads.dart +86 -0
  147. package/flutter_app/lib/src/theme/palette.dart +81 -0
  148. package/flutter_app/lib/src/web_app_update_monitor.dart +17 -0
  149. package/flutter_app/lib/src/web_app_update_monitor_stub.dart +24 -0
  150. package/flutter_app/lib/src/web_app_update_monitor_web.dart +123 -0
  151. package/flutter_app/lib/src/widget_bridge.dart +49 -0
  152. package/flutter_app/linux/CMakeLists.txt +128 -0
  153. package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
  154. package/flutter_app/linux/flutter/generated_plugin_registrant.cc +47 -0
  155. package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
  156. package/flutter_app/linux/flutter/generated_plugins.cmake +32 -0
  157. package/flutter_app/linux/runner/CMakeLists.txt +26 -0
  158. package/flutter_app/linux/runner/main.cc +6 -0
  159. package/flutter_app/linux/runner/my_application.cc +144 -0
  160. package/flutter_app/linux/runner/my_application.h +18 -0
  161. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  162. package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
  163. package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
  164. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +48 -0
  165. package/flutter_app/macos/Podfile +42 -0
  166. package/flutter_app/macos/Podfile.lock +87 -0
  167. package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
  168. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  169. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  170. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  171. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  172. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  173. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  174. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  175. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  176. package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
  177. package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  178. package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
  179. package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
  180. package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
  181. package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
  182. package/flutter_app/macos/Runner/Info.plist +36 -0
  183. package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
  184. package/flutter_app/macos/Runner/Release.entitlements +12 -0
  185. package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
  186. package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  187. package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  188. package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
  189. package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  190. package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
  191. package/flutter_app/patch_strings.py +12 -0
  192. package/flutter_app/pubspec.lock +1256 -0
  193. package/flutter_app/pubspec.yaml +59 -0
  194. package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
  195. package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
  196. package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
  197. package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
  198. package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
  199. package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
  200. package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
  201. package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
  202. package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
  203. package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
  204. package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
  205. package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
  206. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
  207. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
  208. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
  209. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
  210. package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
  211. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
  212. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
  213. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
  214. package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
  215. package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
  216. package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
  217. package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
  218. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
  219. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
  220. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
  221. package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
  222. package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
  223. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
  224. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
  225. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
  226. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
  227. package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
  228. package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
  229. package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
  230. package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
  231. package/flutter_app/tool/generate_desktop_branding.py +219 -0
  232. package/flutter_app/web/favicon.png +0 -0
  233. package/flutter_app/web/favicon.svg +12 -0
  234. package/flutter_app/web/icons/Icon-192.png +0 -0
  235. package/flutter_app/web/icons/Icon-512.png +0 -0
  236. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  237. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  238. package/flutter_app/web/index.html +39 -0
  239. package/flutter_app/web/manifest.json +35 -0
  240. package/flutter_app/windows/CMakeLists.txt +108 -0
  241. package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
  242. package/flutter_app/windows/flutter/generated_plugin_registrant.cc +50 -0
  243. package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
  244. package/flutter_app/windows/flutter/generated_plugins.cmake +36 -0
  245. package/flutter_app/windows/runner/CMakeLists.txt +41 -0
  246. package/flutter_app/windows/runner/Runner.rc +121 -0
  247. package/flutter_app/windows/runner/flutter_window.cpp +533 -0
  248. package/flutter_app/windows/runner/flutter_window.h +37 -0
  249. package/flutter_app/windows/runner/main.cpp +53 -0
  250. package/flutter_app/windows/runner/resource.h +16 -0
  251. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  252. package/flutter_app/windows/runner/runner.exe.manifest +14 -0
  253. package/flutter_app/windows/runner/utils.cpp +65 -0
  254. package/flutter_app/windows/runner/utils.h +19 -0
  255. package/flutter_app/windows/runner/win32_window.cpp +299 -0
  256. package/flutter_app/windows/runner/win32_window.h +102 -0
  257. package/lib/install_helpers.js +31 -0
  258. package/lib/manager.js +465 -43
  259. package/package.json +11 -3
  260. package/runtime/paths.js +137 -0
  261. package/server/config/analytics.js +30 -0
  262. package/server/db/database.js +363 -21
  263. package/server/guest-agent.android.package.json +13 -0
  264. package/server/guest-agent.browser.package.json +14 -0
  265. package/server/guest_agent.js +98 -52
  266. package/server/http/middleware.js +55 -2
  267. package/server/http/routes.js +6 -1
  268. package/server/http/static.js +41 -1
  269. package/server/index.js +16 -16
  270. package/server/public/.last_build_id +1 -1
  271. package/server/public/assets/AssetManifest.bin +1 -1
  272. package/server/public/assets/AssetManifest.bin.json +1 -1
  273. package/server/public/assets/AssetManifest.json +1 -1
  274. package/server/public/assets/NOTICES +679 -129
  275. package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
  276. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  277. package/server/public/assets/packages/mixpanel_flutter/assets/mixpanel.js +3 -0
  278. package/server/public/canvaskit/wimp.wasm +0 -0
  279. package/server/public/flutter_bootstrap.js +2 -2
  280. package/server/public/main.dart.js +95866 -87333
  281. package/server/routes/account.js +26 -1
  282. package/server/routes/agents.js +6 -2
  283. package/server/routes/android.js +22 -6
  284. package/server/routes/auth.js +25 -5
  285. package/server/routes/browser.js +47 -13
  286. package/server/routes/integrations.js +119 -9
  287. package/server/routes/memory.js +179 -8
  288. package/server/routes/runtime.js +14 -0
  289. package/server/routes/screenHistory.js +46 -0
  290. package/server/routes/settings.js +102 -24
  291. package/server/routes/skills.js +12 -0
  292. package/server/routes/social_video.js +66 -0
  293. package/server/routes/tasks.js +22 -2
  294. package/server/routes/triggers.js +90 -0
  295. package/server/routes/wearable.js +67 -0
  296. package/server/routes/widgets.js +4 -4
  297. package/server/services/ai/capabilityHealth.js +23 -51
  298. package/server/services/ai/compaction.js +1 -1
  299. package/server/services/ai/deliverables/artifact_helpers.js +190 -0
  300. package/server/services/ai/deliverables/contracts.js +113 -0
  301. package/server/services/ai/deliverables/index.js +20 -0
  302. package/server/services/ai/deliverables/selector.js +94 -0
  303. package/server/services/ai/deliverables/validator.js +63 -0
  304. package/server/services/ai/deliverables/workflows.js +207 -0
  305. package/server/services/ai/engine.js +372 -27
  306. package/server/services/ai/hooks.js +127 -0
  307. package/server/services/ai/integrated_tools/index.js +98 -0
  308. package/server/services/ai/integrated_tools/remotion.js +373 -0
  309. package/server/services/ai/integrated_tools/shared.js +204 -0
  310. package/server/services/ai/integrated_tools/slidev.js +207 -0
  311. package/server/services/ai/loopPolicy.js +146 -0
  312. package/server/services/ai/models.js +36 -0
  313. package/server/services/ai/providers/githubCopilot.js +97 -0
  314. package/server/services/ai/providers/openai.js +2 -1
  315. package/server/services/ai/providers/openaiCodex.js +384 -0
  316. package/server/services/ai/recordingInsights.js +11 -13
  317. package/server/services/ai/runEvents.js +100 -0
  318. package/server/services/ai/settings.js +20 -0
  319. package/server/services/ai/systemPrompt.js +16 -2
  320. package/server/services/ai/taskAnalysis.js +2 -0
  321. package/server/services/ai/toolResult.js +20 -0
  322. package/server/services/ai/toolRunner.js +46 -6
  323. package/server/services/ai/toolSelector.js +54 -1
  324. package/server/services/ai/tools.js +360 -127
  325. package/server/services/android/android_bootstrap_worker.js +63 -0
  326. package/server/services/android/controller.js +442 -1760
  327. package/server/services/bootstrap_helpers.js +0 -34
  328. package/server/services/browser/controller.js +326 -52
  329. package/server/services/cli/executor.js +40 -1
  330. package/server/services/desktop/screenRecorder.js +182 -0
  331. package/server/services/integrations/env.js +10 -0
  332. package/server/services/integrations/figma/provider.js +1 -0
  333. package/server/services/integrations/github/common.js +106 -0
  334. package/server/services/integrations/github/provider.js +499 -0
  335. package/server/services/integrations/github/repos.js +1124 -0
  336. package/server/services/integrations/google/provider.js +1 -0
  337. package/server/services/integrations/manager.js +88 -12
  338. package/server/services/integrations/microsoft/provider.js +1 -0
  339. package/server/services/integrations/oauth_provider.js +25 -8
  340. package/server/services/integrations/provider_config_store.js +85 -0
  341. package/server/services/integrations/registry.js +8 -2
  342. package/server/services/integrations/spotify/provider.js +488 -0
  343. package/server/services/integrations/trello/provider.js +842 -0
  344. package/server/services/integrations/weather/provider.js +559 -0
  345. package/server/services/integrations/whatsapp/provider.js +6 -2
  346. package/server/services/manager.js +109 -154
  347. package/server/services/mcp/client.js +120 -23
  348. package/server/services/memory/llm_transfer.js +218 -0
  349. package/server/services/memory/manager.js +299 -35
  350. package/server/services/messaging/access_policy.js +10 -0
  351. package/server/services/messaging/automation.js +1 -1
  352. package/server/services/messaging/manager.js +93 -8
  353. package/server/services/messaging/meshtastic.js +277 -0
  354. package/server/services/messaging/meshtastic_env.js +40 -0
  355. package/server/services/messaging/meshtastic_protocol.js +530 -0
  356. package/server/services/messaging/meshtastic_tcp_transport.js +25 -0
  357. package/server/services/runtime/backends/local-vm.js +221 -158
  358. package/server/services/runtime/docker-vm-manager.js +392 -0
  359. package/server/services/runtime/guest_bootstrap.js +654 -0
  360. package/server/services/runtime/manager.js +85 -29
  361. package/server/services/runtime/settings.js +22 -31
  362. package/server/services/runtime/validation.js +16 -31
  363. package/server/services/skills/base_catalog.js +33 -0
  364. package/server/services/social_video/adapters/base.js +26 -0
  365. package/server/services/social_video/adapters/index.js +27 -0
  366. package/server/services/social_video/adapters/instagram.js +17 -0
  367. package/server/services/social_video/adapters/tiktok.js +17 -0
  368. package/server/services/social_video/adapters/x.js +17 -0
  369. package/server/services/social_video/adapters/youtube.js +17 -0
  370. package/server/services/social_video/captions.js +187 -0
  371. package/server/services/social_video/frame.js +42 -0
  372. package/server/services/social_video/index.js +7 -0
  373. package/server/services/social_video/metadata.js +63 -0
  374. package/server/services/social_video/result.js +63 -0
  375. package/server/services/social_video/service.js +720 -0
  376. package/server/services/social_video/url.js +83 -0
  377. package/server/services/tasks/adapters/index.js +2 -0
  378. package/server/services/tasks/adapters/manual.js +12 -0
  379. package/server/services/tasks/adapters/schedule.js +33 -5
  380. package/server/services/tasks/adapters/weather_event.js +84 -0
  381. package/server/services/tasks/integration_runtime.js +86 -1
  382. package/server/services/tasks/runtime.js +3 -3
  383. package/server/services/tasks/task_repository.js +3 -6
  384. package/server/services/voice/agentBridge.js +20 -4
  385. package/server/services/voice/message.js +3 -0
  386. package/server/services/voice/openaiClient.js +4 -1
  387. package/server/services/voice/openaiSpeech.js +6 -1
  388. package/server/services/voice/providers.js +144 -15
  389. package/server/services/voice/runtime.js +8 -8
  390. package/server/services/voice/runtimeManager.js +292 -20
  391. package/server/services/voice/turnRunner.js +29 -9
  392. package/server/services/wearable/firmware_manifest.js +353 -0
  393. package/server/services/wearable/gateway.js +350 -0
  394. package/server/services/wearable/protocol.js +45 -0
  395. package/server/services/wearable/service.js +240 -0
  396. package/server/services/websocket.js +3 -1
  397. package/server/services/widgets/focus_widget.js +137 -0
  398. package/server/services/widgets/service.js +182 -6
  399. package/server/services/workspace/manager.js +301 -0
  400. package/server/utils/deployment.js +16 -9
  401. package/server/utils/local_secrets.js +56 -0
  402. package/server/utils/logger.js +37 -9
  403. package/server/services/integrations/home_assistant/provider.js +0 -350
  404. package/server/services/messaging/access_policy.test.js +0 -228
  405. package/server/services/runtime/backends/host.js +0 -37
  406. 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(`${key}=`)) {
128
- lines[i] = `${key}=${value}`;
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(`${key}=${value}`);
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(`${key}=`));
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 !== process.pid &&
352
- /(^|\s)node(\s|$)/.test(entry.command) &&
353
- (
354
- appIndexPattern.test(String(entry.command || '').replace(/\\/g, '/')) ||
355
- genericNeoAgentPattern.test(String(entry.command || '').replace(/\\/g, '/')) ||
356
- repoNamePattern.test(String(entry.command || ''))
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 port = await ask('Server port', current.PORT || '3333');
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 = await askSecret('Session secret', current.SESSION_SECRET || randomSecret());
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,6 +761,141 @@ 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 cmdLogin(args = []) {
790
+ const provider = args[0];
791
+ if (provider !== 'github-copilot' && provider !== 'openai-codex') {
792
+ throw new Error(`Unsupported login provider: ${provider || 'none'}. Available: github-copilot, openai-codex`);
793
+ }
794
+
795
+ if (provider === 'github-copilot') {
796
+ heading('GitHub Copilot Login');
797
+ const clientId = '01ab8ac9400c4e429b23';
798
+ logInfo('Requesting device code from GitHub...');
799
+
800
+ const reqRes = await fetch('https://github.com/login/device/code', {
801
+ method: 'POST',
802
+ headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
803
+ body: JSON.stringify({ client_id: clientId, scope: 'user:email' })
804
+ });
805
+ if (!reqRes.ok) throw new Error(`Failed to request device code: HTTP ${reqRes.status}`);
806
+
807
+ const { device_code, user_code, verification_uri, interval } = await reqRes.json();
808
+ console.log(`\n ${COLORS.cyan}Please visit:${COLORS.reset} ${verification_uri}`);
809
+ console.log(` ${COLORS.cyan}Enter code:${COLORS.reset} ${COLORS.bold}${user_code}${COLORS.reset}\n`);
810
+ logInfo('Waiting for authorization (timeout in 15m)...');
811
+
812
+ await pollDeviceCode({
813
+ pollUrl: 'https://github.com/login/oauth/access_token',
814
+ pollBody: () => ({ client_id: clientId, device_code, grant_type: 'urn:ietf:params:oauth:grant-type:device_code' }),
815
+ intervalMs: (interval || 5) * 1000,
816
+ timeoutMs: 15 * 60 * 1000,
817
+ onToken: async (data) => {
818
+ if (!data.access_token) return false;
819
+ upsertEnvValue('GITHUB_COPILOT_ACCESS_TOKEN', data.access_token);
820
+ logOk('Saved GitHub Copilot access token to .env');
821
+ logInfo('Restarting NeoAgent to apply credentials...');
822
+ cmdRestart();
823
+ return true;
824
+ },
825
+ });
826
+ return;
827
+ } else if (provider === 'openai-codex') {
828
+ heading('OpenAI Codex Login');
829
+ const clientId = 'app_EMoamEEZ73f0CkXaXp7hrann';
830
+ logInfo('Requesting device code from OpenAI...');
831
+
832
+ const reqRes = await fetch('https://auth.openai.com/api/accounts/deviceauth/usercode', {
833
+ method: 'POST',
834
+ headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
835
+ body: JSON.stringify({ client_id: clientId, scope: 'openid profile email offline_access model.request model.read model.create' })
836
+ });
837
+
838
+ if (!reqRes.ok) {
839
+ throw new Error(`Failed to request device code: HTTP ${reqRes.status}`);
840
+ }
841
+
842
+ const data = await reqRes.json();
843
+ const { device_auth_id, interval } = data;
844
+ const user_code = data.user_code || data.usercode;
845
+ const verification_uri = 'https://auth.openai.com/codex/device';
846
+
847
+ console.log(`\n ${COLORS.cyan}Please visit:${COLORS.reset} ${verification_uri}`);
848
+ console.log(` ${COLORS.cyan}Enter code:${COLORS.reset} ${COLORS.bold}${user_code}${COLORS.reset}\n`);
849
+ logInfo('Waiting for authorization (timeout in 15m)...');
850
+
851
+ let authorizationCode = null;
852
+ let codeVerifier = null;
853
+
854
+ await pollDeviceCode({
855
+ pollUrl: 'https://auth.openai.com/api/accounts/deviceauth/token',
856
+ pollBody: () => ({ device_auth_id, user_code }),
857
+ intervalMs: (interval || 5) * 1000,
858
+ timeoutMs: 15 * 60 * 1000,
859
+ onToken: async (data) => {
860
+ if (!data.authorization_code || !data.code_verifier) return false;
861
+ authorizationCode = data.authorization_code;
862
+ codeVerifier = data.code_verifier;
863
+ return true;
864
+ },
865
+ });
866
+
867
+ logInfo('Exchanging authorization code for access token...');
868
+ const exchangeRes = await fetch('https://auth.openai.com/oauth/token', {
869
+ method: 'POST',
870
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
871
+ body: new URLSearchParams({
872
+ grant_type: 'authorization_code',
873
+ code: authorizationCode,
874
+ redirect_uri: 'https://auth.openai.com/deviceauth/callback',
875
+ client_id: clientId,
876
+ code_verifier: codeVerifier,
877
+ }),
878
+ });
879
+
880
+ if (!exchangeRes.ok) {
881
+ const errorText = await exchangeRes.text().catch(() => 'Unknown error');
882
+ throw new Error(`OpenAI token exchange failed: HTTP ${exchangeRes.status} — ${errorText}`);
883
+ }
884
+
885
+ const exchangeData = await exchangeRes.json();
886
+ if (!exchangeData.access_token) {
887
+ throw new Error('OpenAI token exchange succeeded but did not return an access token.');
888
+ }
889
+ upsertEnvValue('OPENAI_CODEX_ACCESS_TOKEN', exchangeData.access_token);
890
+ if (exchangeData.refresh_token) {
891
+ upsertEnvValue('OPENAI_CODEX_REFRESH_TOKEN', exchangeData.refresh_token);
892
+ }
893
+ logOk('Saved OpenAI Codex tokens to .env');
894
+ logInfo('Restarting NeoAgent to apply credentials...');
895
+ cmdRestart();
896
+ }
897
+ }
898
+
677
899
  function installDependencies() {
678
900
  heading('Dependencies');
679
901
  runOrThrow('npm', ['install', '--omit=dev', '--no-audit', '--no-fund'], {
@@ -770,6 +992,109 @@ function startFallback() {
770
992
  logOk(`Started detached process (pid ${child.pid})`);
771
993
  }
772
994
 
995
+ async function ensureQemuInstalled() {
996
+ heading('Ensure QEMU Installed');
997
+ const platform = detectPlatform();
998
+
999
+ const hasSystem = commandExists('qemu-system-x86_64') || commandExists('qemu-system-aarch64');
1000
+ const hasImg = commandExists('qemu-img');
1001
+
1002
+ if (hasSystem && hasImg) {
1003
+ logOk('QEMU components are already installed');
1004
+ return;
1005
+ }
1006
+
1007
+ logInfo('QEMU components not found. Attempting to install...');
1008
+
1009
+ if (platform === 'macos') {
1010
+ if (!commandExists('brew')) {
1011
+ throw new Error('Homebrew is required to install QEMU on macOS. Please install it first: https://brew.sh/');
1012
+ }
1013
+ logInfo('Running "brew install qemu"...');
1014
+ runOrThrow('brew', ['install', 'qemu']);
1015
+ } else if (platform === 'linux') {
1016
+ const isArm = process.arch === 'arm64' || process.arch === 'aarch64';
1017
+ if (commandExists('apt-get')) {
1018
+ const pkgs = ['qemu-utils'];
1019
+ if (isArm) {
1020
+ pkgs.push('qemu-system-arm');
1021
+ } else {
1022
+ pkgs.push('qemu-system-x86');
1023
+ }
1024
+ logInfo(`Running "sudo apt-get update && sudo apt-get install -y ${pkgs.join(' ')}"...`);
1025
+ runOrThrow('sudo', ['apt-get', 'update']);
1026
+ runOrThrow('sudo', ['apt-get', 'install', '-y', ...pkgs]);
1027
+ } else if (commandExists('dnf')) {
1028
+ logInfo('Running "sudo dnf install -y qemu-kvm qemu-img"...');
1029
+ runOrThrow('sudo', ['dnf', 'install', '-y', 'qemu-kvm', 'qemu-img']);
1030
+ } else if (commandExists('yum')) {
1031
+ logInfo('Running "sudo yum install -y qemu-kvm qemu-img"...');
1032
+ runOrThrow('sudo', ['yum', 'install', '-y', 'qemu-kvm', 'qemu-img']);
1033
+ } else {
1034
+ throw new Error('Unsupported Linux distribution. Please install qemu-system and qemu-utils manually.');
1035
+ }
1036
+ } else {
1037
+ throw new Error('Unsupported platform for automatic QEMU installation. Please install QEMU manually.');
1038
+ }
1039
+
1040
+ const verifiedSystem = commandExists('qemu-system-x86_64') || commandExists('qemu-system-aarch64');
1041
+ const verifiedImg = commandExists('qemu-img');
1042
+
1043
+ if (verifiedSystem && verifiedImg) {
1044
+ logOk('QEMU installed successfully');
1045
+ } else {
1046
+ throw new Error('QEMU installation failed or components not found in PATH after install.');
1047
+ }
1048
+ }
1049
+
1050
+ function ensureYtDlpInstalled() {
1051
+ heading('Ensure yt-dlp Installed');
1052
+ if (commandExists('yt-dlp')) {
1053
+ const ver = runQuiet('yt-dlp', ['--version']);
1054
+ logOk(`yt-dlp ${ver.status === 0 ? ver.stdout.trim() : '(version unknown)'}`);
1055
+ return;
1056
+ }
1057
+
1058
+ logInfo('yt-dlp not found. Attempting to install...');
1059
+ const platform = detectPlatform();
1060
+
1061
+ if (platform === 'macos') {
1062
+ if (!commandExists('brew')) {
1063
+ logWarn('Homebrew not found — skipping yt-dlp install. Install manually: brew install yt-dlp');
1064
+ return;
1065
+ }
1066
+ try {
1067
+ runOrThrow('brew', ['install', 'yt-dlp']);
1068
+ logOk('yt-dlp installed via Homebrew');
1069
+ } catch {
1070
+ logWarn('yt-dlp install failed. Install manually: brew install yt-dlp');
1071
+ }
1072
+ return;
1073
+ }
1074
+
1075
+ if (platform === 'linux') {
1076
+ if (commandExists('pipx')) {
1077
+ try {
1078
+ runOrThrow('pipx', ['install', 'yt-dlp']);
1079
+ logOk('yt-dlp installed via pipx');
1080
+ return;
1081
+ } catch {
1082
+ // fall through to pip3
1083
+ }
1084
+ }
1085
+ if (commandExists('pip3')) {
1086
+ try {
1087
+ runOrThrow('pip3', ['install', '--user', 'yt-dlp']);
1088
+ logOk('yt-dlp installed via pip3');
1089
+ return;
1090
+ } catch {
1091
+ // fall through to warn
1092
+ }
1093
+ }
1094
+ logWarn('Could not install yt-dlp automatically. Install manually: pipx install yt-dlp');
1095
+ }
1096
+ }
1097
+
773
1098
  async function cmdInstall() {
774
1099
  heading(`Install ${APP_NAME}`);
775
1100
  if (!fs.existsSync(ENV_FILE)) {
@@ -778,6 +1103,8 @@ async function cmdInstall() {
778
1103
  }
779
1104
 
780
1105
  installDependencies();
1106
+ await ensureQemuInstalled();
1107
+ ensureYtDlpInstalled();
781
1108
  buildBundledWebClientIfPossible({ required: true });
782
1109
 
783
1110
  const platform = detectPlatform();
@@ -839,7 +1166,7 @@ function cmdStop() {
839
1166
  logOk(`Stopped pid ${pid}`);
840
1167
  stopped = true;
841
1168
  } catch {
842
- logWarn(`pid ${pid} not running`);
1169
+ logWarn(`pid ${pid} was not running (stale PID file)`);
843
1170
  }
844
1171
  }
845
1172
  fs.rmSync(pidPath, { force: true });
@@ -867,6 +1194,11 @@ function cmdRestart() {
867
1194
  cmdStart();
868
1195
  }
869
1196
 
1197
+ async function cmdRebuildWeb() {
1198
+ heading(`Rebuild Flutter Web Client`);
1199
+ buildBundledWebClientIfPossible();
1200
+ }
1201
+
870
1202
  function cmdUninstall() {
871
1203
  heading(`Uninstall ${APP_NAME}`);
872
1204
  const platform = detectPlatform();
@@ -895,22 +1227,52 @@ async function cmdStatus() {
895
1227
  const port = loadEnvPort();
896
1228
  const running = await isPortOpen(port);
897
1229
  const releaseChannel = currentReleaseChannel();
1230
+ const platform = detectPlatform();
898
1231
 
899
1232
  if (running) {
900
- logOk(`running on http://localhost:${port}`);
1233
+ logOk(`server http://localhost:${port}`);
1234
+ } else {
1235
+ logWarn(`server not reachable on port ${port}`);
1236
+ }
1237
+
1238
+ if (platform === 'macos' && fs.existsSync(PLIST_DST)) {
1239
+ const svcRes = runQuiet('launchctl', ['list', SERVICE_LABEL]);
1240
+ if (svcRes.status === 0 && svcRes.stdout.trim()) {
1241
+ logOk(`service launchd (${SERVICE_LABEL})`);
1242
+ } else {
1243
+ logWarn(`service launchd unit not loaded — run: neoagent install`);
1244
+ }
1245
+ } else if (platform === 'linux' && fs.existsSync(SYSTEMD_UNIT)) {
1246
+ const svcRes = runQuiet('systemctl', ['--user', 'is-active', 'neoagent']);
1247
+ if (svcRes.status === 0 && svcRes.stdout.trim() === 'active') {
1248
+ logOk('service systemd (neoagent)');
1249
+ } else {
1250
+ logWarn('service systemd unit not active — run: neoagent install');
1251
+ }
1252
+ }
1253
+
1254
+ if (fs.existsSync(ENV_FILE)) {
1255
+ logOk(`config ${ENV_FILE}`);
1256
+ } else {
1257
+ logWarn(`config .env not found — run: neoagent setup`);
1258
+ }
1259
+
1260
+ if (hasBundledWebClient(WEB_CLIENT_DIR)) {
1261
+ logOk('web bundled Flutter client present');
901
1262
  } else {
902
- logWarn(`not reachable on port ${port}`);
1263
+ logWarn('web no bundled client run: neoagent rebuild-web');
903
1264
  }
904
1265
 
905
- console.log(` install root ${APP_DIR}`);
906
- console.log(` version ${currentInstalledVersionLabel()}`);
907
- console.log(` release channel ${releaseChannelSummary(releaseChannel)}`);
1266
+ console.log('');
1267
+ console.log(` install ${APP_DIR}`);
1268
+ console.log(` version ${currentInstalledVersionLabel()}`);
1269
+ console.log(` channel ${releaseChannelSummary(releaseChannel)}`);
908
1270
 
909
1271
  const processes = listNeoAgentServerProcesses();
910
1272
  if (processes.length > 0) {
911
- console.log(` neoagent pids ${processes.map((proc) => proc.pid).join(', ')}`);
1273
+ console.log(` pids ${processes.map((proc) => proc.pid).join(', ')}`);
912
1274
  if (processes.length > 1) {
913
- logWarn(`multiple NeoAgent server processes detected (${processes.length})`);
1275
+ logWarn(`multiple NeoAgent processes detected (${processes.length})`);
914
1276
  }
915
1277
  }
916
1278
  }
@@ -962,6 +1324,8 @@ function cmdUpdate(args = []) {
962
1324
  }
963
1325
  const versionBefore = currentInstalledVersionLabel();
964
1326
  let versionAfter = versionBefore;
1327
+ const githubInstallRef = releaseChannel === 'beta' ? '#beta' : '';
1328
+ const githubInstallSpec = `git+https://github.com/NeoLabs-Systems/NeoAgent.git${githubInstallRef}`;
965
1329
 
966
1330
  if (fs.existsSync(path.join(APP_DIR, '.git')) && commandExists('git')) {
967
1331
  const current = runQuiet('git', ['rev-parse', '--short', 'HEAD']);
@@ -970,6 +1334,7 @@ function cmdUpdate(args = []) {
970
1334
  const targetBranch = resolvePreferredGitBranch(releaseChannel);
971
1335
  logInfo(`Using git branch ${targetBranch} for the ${releaseChannel} channel.`);
972
1336
  ensureGitBranchForReleaseChannel(targetBranch);
1337
+ backupRuntimeData();
973
1338
  runOrThrow('git', ['pull', '--rebase', '--autostash', 'origin', targetBranch]);
974
1339
 
975
1340
  const next = runQuiet('git', ['rev-parse', '--short', 'HEAD']);
@@ -982,17 +1347,16 @@ function cmdUpdate(args = []) {
982
1347
  buildBundledWebClientIfPossible();
983
1348
  }
984
1349
  } else {
985
- const npmTag = resolvePreferredNpmTag(releaseChannel);
986
- logWarn(`No git repo detected; attempting npm global update from ${npmTag}.`);
1350
+ logWarn(`No git repo detected; attempting npm global update from ${githubInstallSpec}.`);
987
1351
  if (commandExists('npm')) {
988
1352
  try {
989
1353
  backupRuntimeData();
990
- runOrThrow('npm', ['install', '-g', `neoagent@${npmTag}`, '--force'], {
1354
+ runOrThrow('npm', ['install', '-g', githubInstallSpec, '--force'], {
991
1355
  env: withInstallEnv()
992
1356
  });
993
- logOk('npm global update completed (forced reinstall)');
1357
+ logOk('npm global update completed (forced reinstall from GitHub)');
994
1358
  } catch {
995
- logWarn(`npm global update failed. Run: npm install -g neoagent@${npmTag} --force`);
1359
+ logWarn(`npm global update failed. Run: npm install -g ${githubInstallSpec} --force`);
996
1360
  }
997
1361
  } else {
998
1362
  logWarn('npm not found. Cannot perform global update.');
@@ -1000,6 +1364,7 @@ function cmdUpdate(args = []) {
1000
1364
  }
1001
1365
 
1002
1366
  versionAfter = currentInstalledVersionLabel();
1367
+ ensureYtDlpInstalled();
1003
1368
 
1004
1369
  if (!hasBundledWebClient(WEB_CLIENT_DIR)) {
1005
1370
  throw new Error('No bundled Flutter web client found after update.');
@@ -1011,11 +1376,16 @@ function cmdUpdate(args = []) {
1011
1376
 
1012
1377
  async function cmdEnv(args = []) {
1013
1378
  heading('Environment Variables');
1014
- let action = args[0];
1379
+ const action = (args[0] || '').trim().toLowerCase();
1015
1380
 
1016
1381
  if (!action) {
1017
- const picked = await ask('Action (list/get/set/unset)', 'list');
1018
- action = (picked || 'list').trim().toLowerCase();
1382
+ console.log('Usage: neoagent env <subcommand>');
1383
+ console.log('');
1384
+ console.log(' neoagent env list List all variables (secrets masked)');
1385
+ console.log(' neoagent env get KEY Print a single variable');
1386
+ console.log(' neoagent env set KEY VALUE Set a variable');
1387
+ console.log(' neoagent env unset KEY Remove a variable');
1388
+ return;
1019
1389
  }
1020
1390
 
1021
1391
  if (action === 'list') {
@@ -1040,16 +1410,17 @@ async function cmdEnv(args = []) {
1040
1410
  }
1041
1411
 
1042
1412
  if (action === 'set') {
1043
- const key = args[1] || await ask('Key', 'PORT');
1044
- const value = args.slice(2).join(' ') || await ask('Value', key === 'PORT' ? '3333' : '');
1413
+ const key = args[1];
1414
+ const value = args.slice(2).join(' ');
1045
1415
  if (!key || !value) throw new Error('Usage: neoagent env set <KEY> <VALUE>');
1416
+ validateEnvKey(key);
1046
1417
  upsertEnvValue(key, value);
1047
1418
  logOk(`Set ${key} in ${ENV_FILE}`);
1048
1419
  return;
1049
1420
  }
1050
1421
 
1051
1422
  if (action === 'unset') {
1052
- const key = args[1] || await ask('Key', 'PORT');
1423
+ const key = args[1];
1053
1424
  if (!key) throw new Error('Usage: neoagent env unset <KEY>');
1054
1425
  removeEnvValue(key);
1055
1426
  logOk(`Removed ${key} from ${ENV_FILE}`);
@@ -1059,15 +1430,55 @@ async function cmdEnv(args = []) {
1059
1430
  throw new Error('Usage: neoagent env [list|get|set|unset] ...');
1060
1431
  }
1061
1432
 
1433
+ function cmdVersion() {
1434
+ console.log(currentInstalledVersionLabel());
1435
+ }
1436
+
1062
1437
  function printHelp() {
1063
- console.log(`${APP_NAME} manager`);
1064
- console.log('Usage: neoagent <command>');
1065
- console.log('Commands: install | setup | env | channel | update | restart | start | stop | status | logs | uninstall | migrate');
1066
- console.log('Channel usage: neoagent channel | neoagent channel stable | neoagent channel beta');
1067
- console.log('Update usage: neoagent update | neoagent update stable | neoagent update beta');
1068
- console.log('Env usage: neoagent env list | neoagent env get PORT | neoagent env set PORT 3333 | neoagent env unset PORT');
1069
- console.log('Migrate usage: neoagent migrate | neoagent migrate dry-run | neoagent migrate status');
1070
- console.log(' neoagent migrate openclaw-only | neoagent migrate hermes-only');
1438
+ const c = COLORS;
1439
+ const W = 38;
1440
+
1441
+ function row(cmd, desc) {
1442
+ const padded = ` neoagent ${cmd}`.padEnd(W);
1443
+ console.log(`${padded}${c.dim}${desc}${c.reset}`);
1444
+ }
1445
+
1446
+ console.log(`\n${c.bold}neoagent${c.reset} — manage your NeoAgent server\n`);
1447
+ console.log(`${c.bold}Usage${c.reset} neoagent <command> [args]\n`);
1448
+
1449
+ console.log(`${c.bold}Lifecycle${c.reset}`);
1450
+ row('install', 'First-time install and service setup');
1451
+ row('start', 'Start the server');
1452
+ row('stop', 'Stop the server');
1453
+ row('restart', 'Stop, then start');
1454
+ row('status', 'Health overview (server, service, config)');
1455
+ row('logs', 'Tail server logs');
1456
+ row('uninstall', 'Remove the system service');
1457
+ console.log('');
1458
+
1459
+ console.log(`${c.bold}Configuration${c.reset}`);
1460
+ row('setup', 'Interactive configuration wizard');
1461
+ row('env list', 'List all variables (secrets masked)');
1462
+ row('env get KEY', 'Print a single variable');
1463
+ row('env set KEY VALUE', 'Set a variable');
1464
+ row('env unset KEY', 'Remove a variable');
1465
+ row('channel', 'Show current release channel');
1466
+ row('channel stable|beta', 'Switch release channel');
1467
+ console.log('');
1468
+
1469
+ console.log(`${c.bold}Updates & Auth${c.reset}`);
1470
+ row('update', 'Update to latest on current channel');
1471
+ row('update stable|beta', 'Update and switch channel');
1472
+ row('login github-copilot','Authenticate GitHub Copilot');
1473
+ row('login openai-codex', 'Authenticate OpenAI Codex');
1474
+ console.log('');
1475
+
1476
+ console.log(`${c.bold}Maintenance${c.reset}`);
1477
+ row('migrate', 'Migrate from another agent installation');
1478
+ row('migrate dry-run', 'Preview what would be migrated');
1479
+ row('rebuild-web', 'Rebuild the bundled Flutter web client');
1480
+ row('version', 'Print installed version');
1481
+ console.log('');
1071
1482
  }
1072
1483
 
1073
1484
  async function runCLI(argv) {
@@ -1094,6 +1505,9 @@ async function runCLI(argv) {
1094
1505
  case 'restart':
1095
1506
  cmdRestart();
1096
1507
  break;
1508
+ case 'rebuild-web':
1509
+ await cmdRebuildWeb();
1510
+ break;
1097
1511
  case 'start':
1098
1512
  cmdStart();
1099
1513
  break;
@@ -1112,13 +1526,21 @@ async function runCLI(argv) {
1112
1526
  case 'migrate':
1113
1527
  await cmdMigrate(argv.slice(1));
1114
1528
  break;
1529
+ case 'login':
1530
+ await cmdLogin(argv.slice(1));
1531
+ break;
1532
+ case 'version':
1533
+ case '--version':
1534
+ case '-V':
1535
+ cmdVersion();
1536
+ break;
1115
1537
  case 'help':
1116
1538
  case '--help':
1117
1539
  case '-h':
1118
1540
  printHelp();
1119
1541
  break;
1120
1542
  default:
1121
- throw new Error(`Unknown command: ${command}`);
1543
+ throw new Error(`Unknown command: ${command}. Run "neoagent --help" for usage.`);
1122
1544
  }
1123
1545
  }
1124
1546