neoagent 2.4.1-beta.9 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/.env.example +33 -3
  2. package/LICENSE +111 -56
  3. package/README.md +8 -3
  4. package/docs/configuration.md +8 -0
  5. package/docs/getting-started.md +9 -3
  6. package/docs/index.md +4 -0
  7. package/extensions/chrome-browser/background.mjs +36 -4
  8. package/extensions/chrome-browser/icons/icon128.png +0 -0
  9. package/extensions/chrome-browser/icons/icon16.png +0 -0
  10. package/extensions/chrome-browser/icons/icon48.png +0 -0
  11. package/extensions/chrome-browser/icons/logo.svg +39 -8
  12. package/extensions/chrome-browser/manifest.json +3 -3
  13. package/extensions/chrome-browser/popup.html +5 -1
  14. package/extensions/chrome-browser/popup.js +13 -0
  15. package/flutter_app/android/app/src/main/AndroidManifest.xml +2 -1
  16. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  17. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  18. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  19. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  20. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  21. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  22. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  23. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  24. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  25. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  26. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  27. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  28. package/flutter_app/assets/branding/app_icon_light_1024.png +0 -0
  29. package/flutter_app/assets/branding/app_icon_light_128.png +0 -0
  30. package/flutter_app/assets/branding/app_icon_light_192.png +0 -0
  31. package/flutter_app/assets/branding/app_icon_light_256.png +0 -0
  32. package/flutter_app/assets/branding/app_icon_light_32.png +0 -0
  33. package/flutter_app/assets/branding/app_icon_light_512.png +0 -0
  34. package/flutter_app/assets/branding/app_icon_light_64.png +0 -0
  35. package/flutter_app/assets/branding/onboarding_intro.mp4 +0 -0
  36. package/flutter_app/assets/branding/tray_icon_light_template.png +0 -0
  37. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  38. package/flutter_app/lib/features/location/location_service.dart +3 -0
  39. package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +391 -382
  40. package/flutter_app/lib/features/onboarding/onboarding_companion_step.dart +743 -0
  41. package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +18 -16
  42. package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +19 -18
  43. package/flutter_app/lib/features/onboarding/onboarding_shell.dart +8 -1
  44. package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +16 -13
  45. package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +17 -13
  46. package/flutter_app/lib/main.dart +2 -0
  47. package/flutter_app/lib/main_account_settings.dart +10 -34
  48. package/flutter_app/lib/main_admin.dart +14 -1
  49. package/flutter_app/lib/main_app_shell.dart +377 -340
  50. package/flutter_app/lib/main_chat.dart +707 -227
  51. package/flutter_app/lib/main_controller.dart +197 -42
  52. package/flutter_app/lib/main_devices.dart +436 -4
  53. package/flutter_app/lib/main_integrations.dart +255 -6
  54. package/flutter_app/lib/main_launcher.dart +1 -1
  55. package/flutter_app/lib/main_model_picker.dart +685 -0
  56. package/flutter_app/lib/main_models.dart +212 -0
  57. package/flutter_app/lib/main_navigation.dart +1 -9
  58. package/flutter_app/lib/main_operations.dart +1417 -614
  59. package/flutter_app/lib/main_settings.dart +673 -871
  60. package/flutter_app/lib/main_shared.dart +971 -443
  61. package/flutter_app/lib/main_spacing.dart +4 -4
  62. package/flutter_app/lib/main_theme.dart +22 -14
  63. package/flutter_app/lib/main_unified.dart +5 -72
  64. package/flutter_app/lib/src/android_apk_drop_zone.dart +24 -0
  65. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +13 -0
  66. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +219 -0
  67. package/flutter_app/lib/src/backend_client.dart +46 -0
  68. package/flutter_app/lib/src/desktop_companion_actions.dart +30 -7
  69. package/flutter_app/lib/src/desktop_companion_io.dart +71 -1
  70. package/flutter_app/lib/src/security/password_strength.dart +84 -0
  71. package/flutter_app/lib/src/stream_renderer.dart +42 -3
  72. package/flutter_app/lib/src/theme/palette.dart +76 -34
  73. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  74. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  75. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  76. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  77. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  78. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  79. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  80. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  81. package/flutter_app/pubspec.lock +2 -2
  82. package/flutter_app/pubspec.yaml +7 -1
  83. package/flutter_app/tool/branding_source/neoagent-icon-1024.png +0 -0
  84. package/flutter_app/tool/branding_source/neoagent-icon-128.png +0 -0
  85. package/flutter_app/tool/branding_source/neoagent-icon-16.png +0 -0
  86. package/flutter_app/tool/branding_source/neoagent-icon-180.png +0 -0
  87. package/flutter_app/tool/branding_source/neoagent-icon-192.png +0 -0
  88. package/flutter_app/tool/branding_source/neoagent-icon-256.png +0 -0
  89. package/flutter_app/tool/branding_source/neoagent-icon-32.png +0 -0
  90. package/flutter_app/tool/branding_source/neoagent-icon-48.png +0 -0
  91. package/flutter_app/tool/branding_source/neoagent-icon-512.png +0 -0
  92. package/flutter_app/tool/branding_source/neoagent-icon-64.png +0 -0
  93. package/flutter_app/tool/branding_source/neoagent-icon.svg +43 -0
  94. package/flutter_app/tool/generate_desktop_branding.py +154 -152
  95. package/flutter_app/web/favicon.png +0 -0
  96. package/flutter_app/web/favicon.svg +40 -9
  97. package/flutter_app/web/favicon_light.svg +43 -0
  98. package/flutter_app/web/icons/Icon-192-light.png +0 -0
  99. package/flutter_app/web/icons/Icon-192.png +0 -0
  100. package/flutter_app/web/icons/Icon-512-light.png +0 -0
  101. package/flutter_app/web/icons/Icon-512.png +0 -0
  102. package/flutter_app/web/icons/Icon-maskable-192-light.png +0 -0
  103. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  104. package/flutter_app/web/icons/Icon-maskable-512-light.png +0 -0
  105. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  106. package/flutter_app/windows/runner/main.cpp +7 -1
  107. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  108. package/lib/manager.js +445 -83
  109. package/package.json +17 -3
  110. package/runtime/paths.js +3 -1
  111. package/server/admin/access.js +198 -0
  112. package/server/admin/admin.css +268 -0
  113. package/server/admin/admin.js +348 -0
  114. package/server/admin/analytics.js +128 -0
  115. package/server/admin/index.html +1015 -0
  116. package/server/admin/login.html +290 -0
  117. package/server/admin/logo.svg +43 -0
  118. package/server/admin/sql.js +134 -0
  119. package/server/admin/users.js +147 -0
  120. package/server/config/origins.js +3 -1
  121. package/server/db/database.js +92 -0
  122. package/server/db/ftsQuery.js +27 -0
  123. package/server/http/routes.js +1 -0
  124. package/server/http/static.js +23 -6
  125. package/server/index.js +1 -1
  126. package/server/middleware/adminAuth.js +48 -0
  127. package/server/middleware/auth.js +1 -40
  128. package/server/public/.last_build_id +1 -1
  129. package/server/public/app_icon.png +0 -0
  130. package/server/public/assets/AssetManifest.bin +1 -1
  131. package/server/public/assets/AssetManifest.bin.json +1 -1
  132. package/server/public/assets/assets/branding/app_icon_1024.png +0 -0
  133. package/server/public/assets/assets/branding/app_icon_256.png +0 -0
  134. package/server/public/assets/assets/branding/app_icon_512.png +0 -0
  135. package/server/public/assets/assets/branding/app_icon_light_1024.png +0 -0
  136. package/server/public/assets/assets/branding/app_icon_light_256.png +0 -0
  137. package/server/public/assets/assets/branding/app_icon_light_512.png +0 -0
  138. package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
  139. package/server/public/assets/assets/branding/tray_icon_light_template.png +0 -0
  140. package/server/public/assets/assets/branding/tray_icon_template.png +0 -0
  141. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  142. package/server/public/assets/web/icons/Icon-192.png +0 -0
  143. package/server/public/favicon.png +0 -0
  144. package/server/public/favicon.svg +40 -9
  145. package/server/public/favicon_light.svg +43 -0
  146. package/server/public/flutter_bootstrap.js +2 -2
  147. package/server/public/icons/Icon-192-light.png +0 -0
  148. package/server/public/icons/Icon-192.png +0 -0
  149. package/server/public/icons/Icon-512-light.png +0 -0
  150. package/server/public/icons/Icon-512.png +0 -0
  151. package/server/public/icons/Icon-maskable-192-light.png +0 -0
  152. package/server/public/icons/Icon-maskable-192.png +0 -0
  153. package/server/public/icons/Icon-maskable-512-light.png +0 -0
  154. package/server/public/icons/Icon-maskable-512.png +0 -0
  155. package/server/public/main.dart.js +88537 -85936
  156. package/server/routes/admin.js +632 -0
  157. package/server/routes/agent_profiles.js +3 -0
  158. package/server/routes/agents.js +1 -1
  159. package/server/routes/auth.js +3 -1
  160. package/server/routes/mcp.js +29 -13
  161. package/server/routes/memory.js +23 -1
  162. package/server/routes/messaging.js +2 -0
  163. package/server/routes/screenHistory.js +14 -8
  164. package/server/routes/settings.js +10 -2
  165. package/server/routes/stream.js +1 -1
  166. package/server/routes/triggers.js +4 -4
  167. package/server/routes/voice_assistant.js +36 -1
  168. package/server/routes/workspace.js +86 -0
  169. package/server/services/account/admin_two_factor.js +132 -0
  170. package/server/services/account/password_policy.js +6 -1
  171. package/server/services/ai/completion.js +44 -0
  172. package/server/services/ai/engine.js +321 -500
  173. package/server/services/ai/imageAnalysis.js +9 -5
  174. package/server/services/ai/logFormat.js +46 -0
  175. package/server/services/ai/loopPolicy.js +11 -0
  176. package/server/services/ai/messagingFallback.js +228 -0
  177. package/server/services/ai/models.js +194 -55
  178. package/server/services/ai/providerRetry.js +169 -0
  179. package/server/services/ai/providers/anthropic.js +15 -4
  180. package/server/services/ai/providers/google.js +38 -11
  181. package/server/services/ai/providers/grok.js +19 -68
  182. package/server/services/ai/providers/grokOauth.js +141 -0
  183. package/server/services/ai/providers/nvidia.js +154 -0
  184. package/server/services/ai/providers/ollama.js +76 -39
  185. package/server/services/ai/providers/openai.js +20 -31
  186. package/server/services/ai/providers/openaiCodex.js +6 -2
  187. package/server/services/ai/providers/openaiCompatible.js +70 -0
  188. package/server/services/ai/providers/openrouter.js +162 -0
  189. package/server/services/ai/settings.js +30 -0
  190. package/server/services/ai/systemPrompt.js +51 -29
  191. package/server/services/ai/taskAnalysis.js +60 -1
  192. package/server/services/ai/toolEvidence.js +207 -0
  193. package/server/services/ai/toolSelector.js +8 -1
  194. package/server/services/ai/tools.js +78 -13
  195. package/server/services/browser/extension/registry.js +94 -9
  196. package/server/services/desktop/registry.js +104 -1
  197. package/server/services/desktop/screenRecorder.js +208 -98
  198. package/server/services/desktop/screen_recorder_support.js +46 -0
  199. package/server/services/integrations/google/provider.js +13 -0
  200. package/server/services/integrations/home_assistant/constants.js +88 -0
  201. package/server/services/integrations/home_assistant/network.js +207 -0
  202. package/server/services/integrations/home_assistant/provider.js +249 -0
  203. package/server/services/integrations/home_assistant/snapshot.js +98 -0
  204. package/server/services/integrations/home_assistant/tools.js +101 -0
  205. package/server/services/integrations/manager.js +5 -0
  206. package/server/services/integrations/registry.js +2 -0
  207. package/server/services/integrations/trello/provider.js +8 -2
  208. package/server/services/manager.js +51 -53
  209. package/server/services/mcp/client.js +208 -268
  210. package/server/services/mcp/client_support.js +172 -0
  211. package/server/services/mcp/recovery.js +116 -0
  212. package/server/services/mcp/tool_operations.js +123 -0
  213. package/server/services/memory/ingestion.js +185 -370
  214. package/server/services/memory/ingestion_coverage.js +129 -0
  215. package/server/services/memory/ingestion_documents.js +123 -0
  216. package/server/services/memory/ingestion_support.js +171 -0
  217. package/server/services/memory/intelligence.js +181 -0
  218. package/server/services/memory/manager.js +476 -32
  219. package/server/services/messaging/automation.js +71 -134
  220. package/server/services/messaging/formatting_guides.js +26 -1
  221. package/server/services/messaging/inbound_queue.js +111 -0
  222. package/server/services/messaging/manager.js +25 -8
  223. package/server/services/messaging/typing_keepalive.js +110 -0
  224. package/server/services/streaming/android-stream.js +293 -40
  225. package/server/services/tasks/integration_runtime.js +4 -1
  226. package/server/services/tasks/runtime.js +415 -56
  227. package/server/services/tasks/task_repository.js +48 -6
  228. package/server/services/websocket.js +8 -2
  229. package/server/services/widgets/service.js +17 -1
  230. package/server/services/workspace/manager.js +116 -0
  231. package/server/utils/logger.js +44 -6
  232. package/server/utils/security.js +3 -0
  233. package/server/utils/version.js +29 -19
  234. package/server/services/memory/openhuman_uplift.test.js +0 -98
@@ -1,7 +1,16 @@
1
- import 'dart:ui' show ImageFilter;
2
-
3
1
  import 'package:flutter/material.dart';
4
-
2
+ import 'package:google_fonts/google_fonts.dart';
3
+
4
+ import '../../src/theme/palette.dart';
5
+
6
+ /// Shared chrome for the onboarding flow.
7
+ ///
8
+ /// On wide viewports this renders the "Control Surface" two-pane onboarding:
9
+ /// a brand / narrative pane on the left (logo, step counter, eyebrow, title,
10
+ /// supporting copy and progress rail) over the olive [bgPrimary] surface, and
11
+ /// an interaction pane on the right (the step's content + nav) over the deeper
12
+ /// [bgSecondary]. It is fully theme-aware — light or dark follows the system
13
+ /// brightness via [paletteOf].
5
14
  class OnboardingScaffold extends StatelessWidget {
6
15
  const OnboardingScaffold({
7
16
  super.key,
@@ -28,59 +37,297 @@ class OnboardingScaffold extends StatelessWidget {
28
37
 
29
38
  @override
30
39
  Widget build(BuildContext context) {
31
- final width = MediaQuery.sizeOf(context).width;
32
- final compact = width < 980;
33
- return Stack(
34
- children: <Widget>[
35
- const Positioned.fill(child: _OnboardingBackdrop()),
36
- SafeArea(
37
- child: Padding(
38
- padding: EdgeInsets.fromLTRB(
39
- compact ? 20 : 32,
40
- compact ? 16 : 22,
41
- compact ? 20 : 32,
42
- compact ? 20 : 28,
40
+ final p = paletteOf(context);
41
+ final wide = MediaQuery.sizeOf(context).width >= 900;
42
+
43
+ if (!wide) {
44
+ return ColoredBox(
45
+ color: p.bgPrimary,
46
+ child: SafeArea(child: _CompactBody(scaffold: this)),
47
+ );
48
+ }
49
+
50
+ return ColoredBox(
51
+ color: p.bgPrimary,
52
+ child: SafeArea(
53
+ child: Row(
54
+ crossAxisAlignment: CrossAxisAlignment.stretch,
55
+ children: <Widget>[
56
+ Expanded(flex: 43, child: _NarrativePane(scaffold: this)),
57
+ Expanded(flex: 57, child: _InteractionPane(scaffold: this)),
58
+ ],
59
+ ),
60
+ ),
61
+ );
62
+ }
63
+ }
64
+
65
+ class _NarrativePane extends StatelessWidget {
66
+ const _NarrativePane({required this.scaffold});
67
+
68
+ final OnboardingScaffold scaffold;
69
+
70
+ @override
71
+ Widget build(BuildContext context) {
72
+ final p = paletteOf(context);
73
+ return DecoratedBox(
74
+ decoration: BoxDecoration(color: p.bgPrimary),
75
+ child: Stack(
76
+ children: <Widget>[
77
+ // Sage glow, top-left — the brand "agent OS" atmosphere.
78
+ Positioned.fill(
79
+ child: DecoratedBox(
80
+ decoration: BoxDecoration(
81
+ gradient: RadialGradient(
82
+ center: const Alignment(-0.8, -0.9),
83
+ radius: 1.2,
84
+ colors: <Color>[
85
+ p.accentAlt.withValues(alpha: 0.20),
86
+ p.accentAlt.withValues(alpha: 0),
87
+ ],
88
+ ),
89
+ ),
43
90
  ),
91
+ ),
92
+ Padding(
93
+ padding: const EdgeInsets.fromLTRB(52, 44, 44, 44),
44
94
  child: Column(
95
+ crossAxisAlignment: CrossAxisAlignment.start,
45
96
  children: <Widget>[
46
- _OnboardingTopBar(step: step, totalSteps: totalSteps),
47
- const SizedBox(height: 18),
97
+ _Brand(step: scaffold.step, totalSteps: scaffold.totalSteps),
48
98
  Expanded(
49
- child: compact
50
- ? _OnboardingPanel(
51
- padding: EdgeInsets.all(dense ? 22 : 26),
52
- child: _OnboardingContentColumn(
53
- eyebrow: eyebrow,
54
- title: title,
55
- description: description,
56
- footer: footer,
57
- sidePanel: sidePanel,
58
- compact: true,
59
- child: child,
99
+ child: Center(
100
+ child: SingleChildScrollView(
101
+ child: Column(
102
+ crossAxisAlignment: CrossAxisAlignment.start,
103
+ mainAxisSize: MainAxisSize.min,
104
+ children: <Widget>[
105
+ const SizedBox(height: 24),
106
+ OnboardingEyebrow(scaffold.eyebrow),
107
+ const SizedBox(height: 18),
108
+ Text(
109
+ scaffold.title,
110
+ style: GoogleFonts.geist(
111
+ color: p.textPrimary,
112
+ fontSize: 42,
113
+ height: 1.04,
114
+ fontWeight: FontWeight.w800,
115
+ letterSpacing: -1.2,
116
+ ),
60
117
  ),
61
- )
62
- : _OnboardingPanel(
63
- padding: const EdgeInsets.all(34),
64
- child: _OnboardingContentColumn(
65
- eyebrow: eyebrow,
66
- title: title,
67
- description: description,
68
- footer: footer,
69
- sidePanel: sidePanel,
70
- compact: false,
71
- child: child,
118
+ const SizedBox(height: 18),
119
+ ConstrainedBox(
120
+ constraints: const BoxConstraints(maxWidth: 460),
121
+ child: Text(
122
+ scaffold.description,
123
+ style: GoogleFonts.geist(
124
+ color: p.textSecondary,
125
+ fontSize: 16,
126
+ height: 1.6,
127
+ ),
128
+ ),
72
129
  ),
73
- ),
130
+ if (scaffold.sidePanel != null) ...<Widget>[
131
+ const SizedBox(height: 28),
132
+ scaffold.sidePanel!,
133
+ ],
134
+ const SizedBox(height: 24),
135
+ ],
136
+ ),
137
+ ),
138
+ ),
139
+ ),
140
+ _ProgressRail(
141
+ step: scaffold.step,
142
+ totalSteps: scaffold.totalSteps,
74
143
  ),
75
144
  ],
76
145
  ),
77
146
  ),
147
+ ],
148
+ ),
149
+ );
150
+ }
151
+ }
152
+
153
+ class _InteractionPane extends StatelessWidget {
154
+ const _InteractionPane({required this.scaffold});
155
+
156
+ final OnboardingScaffold scaffold;
157
+
158
+ @override
159
+ Widget build(BuildContext context) {
160
+ final p = paletteOf(context);
161
+ return DecoratedBox(
162
+ decoration: BoxDecoration(
163
+ color: p.bgSecondary,
164
+ border: Border(left: BorderSide(color: p.border)),
165
+ ),
166
+ child: Padding(
167
+ padding: EdgeInsets.all(scaffold.dense ? 36 : 44),
168
+ child: Column(
169
+ crossAxisAlignment: CrossAxisAlignment.stretch,
170
+ children: <Widget>[
171
+ Expanded(child: scaffold.child),
172
+ const SizedBox(height: 26),
173
+ scaffold.footer,
174
+ ],
175
+ ),
176
+ ),
177
+ );
178
+ }
179
+ }
180
+
181
+ class _CompactBody extends StatelessWidget {
182
+ const _CompactBody({required this.scaffold});
183
+
184
+ final OnboardingScaffold scaffold;
185
+
186
+ @override
187
+ Widget build(BuildContext context) {
188
+ final p = paletteOf(context);
189
+ return Padding(
190
+ padding: const EdgeInsets.fromLTRB(22, 18, 22, 22),
191
+ child: Column(
192
+ crossAxisAlignment: CrossAxisAlignment.start,
193
+ children: <Widget>[
194
+ _Brand(step: scaffold.step, totalSteps: scaffold.totalSteps),
195
+ const SizedBox(height: 16),
196
+ _ProgressRail(step: scaffold.step, totalSteps: scaffold.totalSteps),
197
+ const SizedBox(height: 24),
198
+ OnboardingEyebrow(scaffold.eyebrow),
199
+ const SizedBox(height: 12),
200
+ Text(
201
+ scaffold.title,
202
+ style: GoogleFonts.geist(
203
+ color: p.textPrimary,
204
+ fontSize: 28,
205
+ height: 1.06,
206
+ fontWeight: FontWeight.w800,
207
+ letterSpacing: -0.8,
208
+ ),
209
+ ),
210
+ const SizedBox(height: 10),
211
+ Text(
212
+ scaffold.description,
213
+ style: GoogleFonts.geist(
214
+ color: p.textSecondary,
215
+ fontSize: 14.5,
216
+ height: 1.55,
217
+ ),
218
+ ),
219
+ if (scaffold.sidePanel != null) ...<Widget>[
220
+ const SizedBox(height: 18),
221
+ scaffold.sidePanel!,
222
+ ],
223
+ const SizedBox(height: 22),
224
+ Expanded(child: scaffold.child),
225
+ const SizedBox(height: 18),
226
+ scaffold.footer,
227
+ ],
228
+ ),
229
+ );
230
+ }
231
+ }
232
+
233
+ class _Brand extends StatelessWidget {
234
+ const _Brand({required this.step, required this.totalSteps});
235
+
236
+ final int step;
237
+ final int totalSteps;
238
+
239
+ @override
240
+ Widget build(BuildContext context) {
241
+ final p = paletteOf(context);
242
+ final dark = MediaQuery.platformBrightnessOf(context) == Brightness.dark;
243
+ return Row(
244
+ mainAxisSize: MainAxisSize.min,
245
+ children: <Widget>[
246
+ Container(
247
+ width: 36,
248
+ height: 36,
249
+ decoration: BoxDecoration(
250
+ borderRadius: BorderRadius.circular(10),
251
+ boxShadow: <BoxShadow>[
252
+ BoxShadow(
253
+ color: p.accent.withValues(alpha: 0.16),
254
+ blurRadius: 14,
255
+ ),
256
+ ],
257
+ ),
258
+ child: ClipRRect(
259
+ borderRadius: BorderRadius.circular(10),
260
+ child: Image.asset(
261
+ dark
262
+ ? 'assets/branding/app_icon_1024.png'
263
+ : 'assets/branding/app_icon_light_1024.png',
264
+ width: 36,
265
+ height: 36,
266
+ filterQuality: FilterQuality.high,
267
+ ),
268
+ ),
269
+ ),
270
+ const SizedBox(width: 12),
271
+ Column(
272
+ mainAxisSize: MainAxisSize.min,
273
+ crossAxisAlignment: CrossAxisAlignment.start,
274
+ children: <Widget>[
275
+ Text(
276
+ 'NeoAgent',
277
+ style: GoogleFonts.geist(
278
+ color: p.textPrimary,
279
+ fontSize: 17,
280
+ fontWeight: FontWeight.w700,
281
+ letterSpacing: -0.3,
282
+ ),
283
+ ),
284
+ const SizedBox(height: 2),
285
+ Text(
286
+ 'STEP ${step + 1} OF $totalSteps',
287
+ style: GoogleFonts.geistMono(
288
+ color: p.textMuted,
289
+ fontSize: 10.5,
290
+ fontWeight: FontWeight.w600,
291
+ letterSpacing: 1.6,
292
+ ),
293
+ ),
294
+ ],
78
295
  ),
79
296
  ],
80
297
  );
81
298
  }
82
299
  }
83
300
 
301
+ class _ProgressRail extends StatelessWidget {
302
+ const _ProgressRail({required this.step, required this.totalSteps});
303
+
304
+ final int step;
305
+ final int totalSteps;
306
+
307
+ @override
308
+ Widget build(BuildContext context) {
309
+ final p = paletteOf(context);
310
+ return Row(
311
+ mainAxisSize: MainAxisSize.min,
312
+ children: List<Widget>.generate(totalSteps, (index) {
313
+ final active = index == step;
314
+ final done = index < step;
315
+ return AnimatedContainer(
316
+ duration: const Duration(milliseconds: 320),
317
+ curve: Curves.easeOutCubic,
318
+ margin: const EdgeInsets.only(right: 8),
319
+ width: active ? 30 : 16,
320
+ height: 5,
321
+ decoration: BoxDecoration(
322
+ color: active || done ? p.accent : p.borderLight,
323
+ borderRadius: BorderRadius.circular(999),
324
+ ),
325
+ );
326
+ }),
327
+ );
328
+ }
329
+ }
330
+
84
331
  class OnboardingPanel extends StatelessWidget {
85
332
  const OnboardingPanel({
86
333
  super.key,
@@ -93,7 +340,16 @@ class OnboardingPanel extends StatelessWidget {
93
340
 
94
341
  @override
95
342
  Widget build(BuildContext context) {
96
- return _OnboardingPanel(padding: padding, child: child);
343
+ final p = paletteOf(context);
344
+ return Container(
345
+ padding: padding,
346
+ decoration: BoxDecoration(
347
+ color: p.bgCard,
348
+ borderRadius: BorderRadius.circular(20),
349
+ border: Border.all(color: p.border),
350
+ ),
351
+ child: child,
352
+ );
97
353
  }
98
354
  }
99
355
 
@@ -115,33 +371,31 @@ class OnboardingOptionCard extends StatelessWidget {
115
371
 
116
372
  @override
117
373
  Widget build(BuildContext context) {
118
- final highlight = accent ?? Theme.of(context).colorScheme.primary;
374
+ final p = paletteOf(context);
375
+ final highlight = accent ?? p.accent;
376
+ final radius = compact ? 16.0 : 20.0;
119
377
  return Material(
120
378
  color: Colors.transparent,
121
379
  child: InkWell(
122
380
  onTap: onTap,
123
- borderRadius: BorderRadius.circular(compact ? 22 : 28),
381
+ borderRadius: BorderRadius.circular(radius),
124
382
  child: AnimatedContainer(
125
- duration: const Duration(milliseconds: 260),
383
+ duration: const Duration(milliseconds: 200),
126
384
  curve: Curves.easeOutCubic,
127
- padding: EdgeInsets.all(compact ? 16 : 22),
385
+ padding: EdgeInsets.all(compact ? 16 : 18),
128
386
  decoration: BoxDecoration(
129
- color: selected
130
- ? highlight.withValues(alpha: 0.16)
131
- : Colors.white.withValues(alpha: 0.055),
132
- borderRadius: BorderRadius.circular(compact ? 22 : 28),
387
+ color: selected ? highlight.withValues(alpha: 0.08) : p.bgCard,
388
+ borderRadius: BorderRadius.circular(radius),
133
389
  border: Border.all(
134
- color: selected
135
- ? highlight.withValues(alpha: 0.92)
136
- : Colors.white.withValues(alpha: 0.1),
137
- width: selected ? 1.8 : 1,
390
+ color: selected ? highlight : p.borderLight,
391
+ width: selected ? 1.6 : 1,
138
392
  ),
139
393
  boxShadow: selected
140
394
  ? <BoxShadow>[
141
395
  BoxShadow(
142
- color: highlight.withValues(alpha: 0.18),
143
- blurRadius: 28,
144
- offset: const Offset(0, 14),
396
+ color: highlight.withValues(alpha: 0.22),
397
+ blurRadius: 0,
398
+ spreadRadius: 3,
145
399
  ),
146
400
  ]
147
401
  : const <BoxShadow>[],
@@ -167,14 +421,19 @@ class OnboardingGhostButton extends StatelessWidget {
167
421
 
168
422
  @override
169
423
  Widget build(BuildContext context) {
424
+ final p = paletteOf(context);
170
425
  return TextButton.icon(
171
426
  onPressed: onPressed,
172
427
  style: TextButton.styleFrom(
173
- foregroundColor: Colors.white.withValues(alpha: 0.72),
174
- padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
175
- textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
428
+ foregroundColor: p.textSecondary,
429
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
430
+ textStyle: GoogleFonts.geist(fontSize: 14, fontWeight: FontWeight.w600),
431
+ shape: RoundedRectangleBorder(
432
+ borderRadius: BorderRadius.circular(12),
433
+ side: BorderSide(color: p.border),
434
+ ),
176
435
  ),
177
- icon: icon == null ? const SizedBox.shrink() : Icon(icon, size: 18),
436
+ icon: icon == null ? const SizedBox.shrink() : Icon(icon, size: 17),
178
437
  label: Text(label),
179
438
  );
180
439
  }
@@ -194,31 +453,58 @@ class OnboardingPrimaryButton extends StatelessWidget {
194
453
 
195
454
  @override
196
455
  Widget build(BuildContext context) {
197
- final accent = Theme.of(context).colorScheme.primary;
198
- return Container(
199
- decoration: BoxDecoration(
200
- borderRadius: BorderRadius.circular(22),
201
- boxShadow: <BoxShadow>[
202
- BoxShadow(
203
- color: accent.withValues(alpha: 0.26),
204
- blurRadius: 26,
205
- offset: const Offset(0, 14),
456
+ final p = paletteOf(context);
457
+ final enabled = onPressed != null;
458
+ final gold = p.accent;
459
+ return Opacity(
460
+ opacity: enabled ? 1 : 0.5,
461
+ child: DecoratedBox(
462
+ decoration: BoxDecoration(
463
+ gradient: LinearGradient(
464
+ begin: Alignment.topLeft,
465
+ end: Alignment.bottomRight,
466
+ colors: <Color>[
467
+ Color.lerp(gold, Colors.white, 0.16)!,
468
+ gold,
469
+ ],
206
470
  ),
207
- ],
208
- ),
209
- child: FilledButton.icon(
210
- onPressed: onPressed,
211
- style: FilledButton.styleFrom(
212
- backgroundColor: accent,
213
- foregroundColor: Colors.white,
214
- padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 20),
215
- shape: RoundedRectangleBorder(
216
- borderRadius: BorderRadius.circular(22),
471
+ borderRadius: BorderRadius.circular(14),
472
+ boxShadow: <BoxShadow>[
473
+ BoxShadow(
474
+ color: gold.withValues(alpha: 0.34),
475
+ blurRadius: 22,
476
+ offset: const Offset(0, 10),
477
+ ),
478
+ ],
479
+ ),
480
+ child: Material(
481
+ color: Colors.transparent,
482
+ child: InkWell(
483
+ onTap: onPressed,
484
+ borderRadius: BorderRadius.circular(14),
485
+ child: Padding(
486
+ padding: const EdgeInsets.symmetric(horizontal: 26, vertical: 17),
487
+ child: Row(
488
+ mainAxisSize: MainAxisSize.min,
489
+ children: <Widget>[
490
+ Text(
491
+ label,
492
+ style: GoogleFonts.geist(
493
+ color: Colors.white,
494
+ fontSize: 15,
495
+ fontWeight: FontWeight.w700,
496
+ letterSpacing: -0.1,
497
+ ),
498
+ ),
499
+ if (icon != null) ...<Widget>[
500
+ const SizedBox(width: 9),
501
+ Icon(icon, size: 18, color: Colors.white),
502
+ ],
503
+ ],
504
+ ),
505
+ ),
217
506
  ),
218
- textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
219
507
  ),
220
- icon: icon == null ? const SizedBox.shrink() : Icon(icon, size: 18),
221
- label: Text(label),
222
508
  ),
223
509
  );
224
510
  }
@@ -236,12 +522,13 @@ class OnboardingMetricPill extends StatelessWidget {
236
522
 
237
523
  @override
238
524
  Widget build(BuildContext context) {
525
+ final p = paletteOf(context);
239
526
  return Container(
240
527
  padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
241
528
  decoration: BoxDecoration(
242
- color: Colors.white.withValues(alpha: 0.06),
243
- borderRadius: BorderRadius.circular(18),
244
- border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
529
+ color: p.bgCard,
530
+ borderRadius: BorderRadius.circular(14),
531
+ border: Border.all(color: p.border),
245
532
  ),
246
533
  child: Column(
247
534
  crossAxisAlignment: CrossAxisAlignment.start,
@@ -249,18 +536,18 @@ class OnboardingMetricPill extends StatelessWidget {
249
536
  children: <Widget>[
250
537
  Text(
251
538
  label.toUpperCase(),
252
- style: TextStyle(
253
- color: Colors.white.withValues(alpha: 0.56),
254
- fontSize: 11,
255
- fontWeight: FontWeight.w700,
256
- letterSpacing: 0.9,
539
+ style: GoogleFonts.geistMono(
540
+ color: p.textMuted,
541
+ fontSize: 10.5,
542
+ fontWeight: FontWeight.w600,
543
+ letterSpacing: 1.4,
257
544
  ),
258
545
  ),
259
546
  const SizedBox(height: 6),
260
547
  Text(
261
548
  value,
262
- style: const TextStyle(
263
- color: Colors.white,
549
+ style: GoogleFonts.geist(
550
+ color: p.textPrimary,
264
551
  fontSize: 15,
265
552
  fontWeight: FontWeight.w700,
266
553
  ),
@@ -271,300 +558,22 @@ class OnboardingMetricPill extends StatelessWidget {
271
558
  }
272
559
  }
273
560
 
274
- class _OnboardingTopBar extends StatelessWidget {
275
- const _OnboardingTopBar({required this.step, required this.totalSteps});
276
-
277
- final int step;
278
- final int totalSteps;
279
-
280
- @override
281
- Widget build(BuildContext context) {
282
- final progress = totalSteps <= 1 ? 1.0 : (step + 1) / totalSteps;
283
- return Row(
284
- children: <Widget>[
285
- Container(
286
- padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
287
- decoration: BoxDecoration(
288
- color: Colors.white.withValues(alpha: 0.06),
289
- borderRadius: BorderRadius.circular(999),
290
- border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
291
- ),
292
- child: Row(
293
- mainAxisSize: MainAxisSize.min,
294
- children: <Widget>[
295
- Container(
296
- width: 10,
297
- height: 10,
298
- decoration: BoxDecoration(
299
- color: Theme.of(context).colorScheme.primary,
300
- shape: BoxShape.circle,
301
- ),
302
- ),
303
- const SizedBox(width: 10),
304
- const Text(
305
- 'NeoOS Setup',
306
- style: TextStyle(
307
- color: Colors.white,
308
- fontSize: 14,
309
- fontWeight: FontWeight.w700,
310
- ),
311
- ),
312
- ],
313
- ),
314
- ),
315
- const SizedBox(width: 16),
316
- Expanded(
317
- child: ClipRRect(
318
- borderRadius: BorderRadius.circular(999),
319
- child: TweenAnimationBuilder<double>(
320
- tween: Tween<double>(begin: 0, end: progress),
321
- duration: const Duration(milliseconds: 700),
322
- curve: Curves.easeOutCubic,
323
- builder: (context, animatedValue, _) {
324
- return LinearProgressIndicator(
325
- value: animatedValue,
326
- minHeight: 8,
327
- backgroundColor: Colors.white.withValues(alpha: 0.08),
328
- valueColor: AlwaysStoppedAnimation<Color>(
329
- Theme.of(context).colorScheme.primary,
330
- ),
331
- );
332
- },
333
- ),
334
- ),
335
- ),
336
- const SizedBox(width: 16),
337
- Text(
338
- '${step + 1} / $totalSteps',
339
- style: TextStyle(
340
- color: Colors.white.withValues(alpha: 0.7),
341
- fontSize: 13,
342
- fontWeight: FontWeight.w700,
343
- ),
344
- ),
345
- ],
346
- );
347
- }
348
- }
349
-
350
- class _OnboardingContentColumn extends StatelessWidget {
351
- const _OnboardingContentColumn({
352
- required this.eyebrow,
353
- required this.title,
354
- required this.description,
355
- required this.child,
356
- required this.footer,
357
- required this.sidePanel,
358
- required this.compact,
359
- });
561
+ /// Mono gold-ink eyebrow label, matching the design's `.eyebrow` treatment.
562
+ class OnboardingEyebrow extends StatelessWidget {
563
+ const OnboardingEyebrow(this.text, {super.key});
360
564
 
361
- final String eyebrow;
362
- final String title;
363
- final String description;
364
- final Widget child;
365
- final Widget footer;
366
- final Widget? sidePanel;
367
- final bool compact;
565
+ final String text;
368
566
 
369
567
  @override
370
568
  Widget build(BuildContext context) {
371
- final intro = Column(
372
- crossAxisAlignment: CrossAxisAlignment.start,
373
- children: <Widget>[
374
- Text(
375
- eyebrow,
376
- style: TextStyle(
377
- color: Theme.of(
378
- context,
379
- ).colorScheme.primary.withValues(alpha: 0.92),
380
- fontSize: 12,
381
- fontWeight: FontWeight.w800,
382
- letterSpacing: 1.1,
383
- ),
384
- ),
385
- const SizedBox(height: 14),
386
- Text(
387
- title,
388
- style: TextStyle(
389
- color: Colors.white,
390
- fontSize: compact ? 40 : 56,
391
- height: compact ? 1.04 : 1.0,
392
- fontWeight: FontWeight.w800,
393
- letterSpacing: compact ? -1.6 : -2.3,
394
- ),
395
- ),
396
- const SizedBox(height: 18),
397
- ConstrainedBox(
398
- constraints: const BoxConstraints(maxWidth: 680),
399
- child: Text(
400
- description,
401
- style: TextStyle(
402
- color: Colors.white.withValues(alpha: 0.74),
403
- fontSize: compact ? 17 : 19,
404
- height: 1.55,
405
- fontWeight: FontWeight.w500,
406
- ),
407
- ),
408
- ),
409
- ],
410
- );
411
-
412
- if (compact) {
413
- return Column(
414
- crossAxisAlignment: CrossAxisAlignment.start,
415
- children: <Widget>[
416
- intro,
417
- const SizedBox(height: 26),
418
- if (sidePanel != null) ...<Widget>[
419
- sidePanel!,
420
- const SizedBox(height: 18),
421
- ],
422
- Expanded(child: child),
423
- const SizedBox(height: 20),
424
- footer,
425
- ],
426
- );
427
- }
428
-
429
- return Column(
430
- crossAxisAlignment: CrossAxisAlignment.start,
431
- children: <Widget>[
432
- Row(
433
- crossAxisAlignment: CrossAxisAlignment.start,
434
- children: <Widget>[
435
- Expanded(flex: 12, child: intro),
436
- if (sidePanel != null) ...<Widget>[
437
- const SizedBox(width: 28),
438
- Expanded(flex: 7, child: sidePanel!),
439
- ],
440
- ],
441
- ),
442
- const SizedBox(height: 28),
443
- Expanded(child: child),
444
- const SizedBox(height: 22),
445
- footer,
446
- ],
447
- );
448
- }
449
- }
450
-
451
- class _OnboardingPanel extends StatelessWidget {
452
- const _OnboardingPanel({required this.child, required this.padding});
453
-
454
- final Widget child;
455
- final EdgeInsetsGeometry padding;
456
-
457
- @override
458
- Widget build(BuildContext context) {
459
- return ClipRRect(
460
- borderRadius: BorderRadius.circular(36),
461
- child: BackdropFilter(
462
- filter: ImageFilter.blur(sigmaX: 30, sigmaY: 30),
463
- child: Container(
464
- padding: padding,
465
- decoration: BoxDecoration(
466
- gradient: LinearGradient(
467
- colors: <Color>[
468
- Colors.white.withValues(alpha: 0.16),
469
- Colors.white.withValues(alpha: 0.08),
470
- const Color(0xFF111317).withValues(alpha: 0.72),
471
- ],
472
- begin: Alignment.topLeft,
473
- end: Alignment.bottomRight,
474
- ),
475
- borderRadius: BorderRadius.circular(36),
476
- border: Border.all(color: Colors.white.withValues(alpha: 0.14)),
477
- boxShadow: <BoxShadow>[
478
- BoxShadow(
479
- color: Colors.black.withValues(alpha: 0.34),
480
- blurRadius: 46,
481
- offset: const Offset(0, 24),
482
- ),
483
- ],
484
- ),
485
- child: child,
486
- ),
487
- ),
488
- );
489
- }
490
- }
491
-
492
- class _OnboardingBackdrop extends StatelessWidget {
493
- const _OnboardingBackdrop();
494
-
495
- @override
496
- Widget build(BuildContext context) {
497
- final accent = Theme.of(context).colorScheme.primary;
498
- return DecoratedBox(
499
- decoration: const BoxDecoration(
500
- gradient: RadialGradient(
501
- center: Alignment(-0.8, -0.95),
502
- radius: 1.8,
503
- colors: <Color>[
504
- Color(0xFF20242C),
505
- Color(0xFF0C0F13),
506
- Color(0xFF040506),
507
- ],
508
- ),
509
- ),
510
- child: Stack(
511
- children: <Widget>[
512
- Positioned(
513
- top: -120,
514
- left: -80,
515
- child: _GlowOrb(size: 340, color: accent.withValues(alpha: 0.24)),
516
- ),
517
- const Positioned(
518
- top: 120,
519
- right: -60,
520
- child: _GlowOrb(size: 300, color: Color(0x226EDBFF)),
521
- ),
522
- const Positioned(
523
- bottom: -120,
524
- left: 160,
525
- child: _GlowOrb(size: 420, color: Color(0x18D7B27C)),
526
- ),
527
- Positioned.fill(
528
- child: IgnorePointer(
529
- child: DecoratedBox(
530
- decoration: BoxDecoration(
531
- gradient: LinearGradient(
532
- colors: <Color>[
533
- Colors.white.withValues(alpha: 0.04),
534
- Colors.transparent,
535
- Colors.black.withValues(alpha: 0.26),
536
- ],
537
- begin: Alignment.topCenter,
538
- end: Alignment.bottomCenter,
539
- ),
540
- ),
541
- ),
542
- ),
543
- ),
544
- ],
545
- ),
546
- );
547
- }
548
- }
549
-
550
- class _GlowOrb extends StatelessWidget {
551
- const _GlowOrb({required this.size, required this.color});
552
-
553
- final double size;
554
- final Color color;
555
-
556
- @override
557
- Widget build(BuildContext context) {
558
- return IgnorePointer(
559
- child: Container(
560
- width: size,
561
- height: size,
562
- decoration: BoxDecoration(
563
- shape: BoxShape.circle,
564
- boxShadow: <BoxShadow>[
565
- BoxShadow(color: color, blurRadius: 160, spreadRadius: 28),
566
- ],
567
- ),
569
+ final p = paletteOf(context);
570
+ return Text(
571
+ text.toUpperCase(),
572
+ style: GoogleFonts.geistMono(
573
+ color: p.accentHover,
574
+ fontSize: 11.5,
575
+ fontWeight: FontWeight.w600,
576
+ letterSpacing: 1.8,
568
577
  ),
569
578
  );
570
579
  }