neoagent 3.3.1-beta.1 → 3.3.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,9 +49,14 @@ is available.
49
49
 
50
50
  ```bash
51
51
  npm install -g neoagent
52
- neoagent install
52
+ neoagent
53
53
  ```
54
54
 
55
+ On its first run, `neoagent` scans the computer for an existing instance. If
56
+ none is found, it installs the application, prepares the service and isolated
57
+ runtime, then opens a focused terminal wizard for your AI provider API keys.
58
+ Running `neoagent` again shows the status of the existing installation.
59
+
55
60
  <p align="center">
56
61
  <a href="https://github.com/NeoLabs-Systems/NeoAgent/releases/latest"><img alt="Download macOS app" src="https://img.shields.io/badge/macOS_app-download-black?style=flat-square&logo=apple&logoColor=white"></a>
57
62
  </p>
@@ -29,12 +29,15 @@ the cloud browser/CLI become available once Docker is running.
29
29
 
30
30
  ```bash
31
31
  npm install -g neoagent
32
- neoagent install
32
+ neoagent
33
33
  ```
34
34
 
35
- `neoagent install` checks the host, creates the runtime configuration, installs
36
- application dependencies and supported system tools, prepares the isolated
37
- runtime, and registers a launchd or systemd user service when available.
35
+ The first `neoagent` run checks the host for an existing installation. On a new
36
+ machine it creates the runtime configuration, installs application dependencies
37
+ and supported system tools, prepares the isolated runtime, guides you through
38
+ the API keys for the hosted providers you choose, and registers a launchd or
39
+ systemd user service when available. Later runs show the existing
40
+ installation's status.
38
41
 
39
42
  Read the post-install actions printed by the command. They describe anything
40
43
  the installer could not complete automatically.
@@ -45,10 +48,10 @@ and `PUBLIC_URL` before connecting integrations or mobile clients.
45
48
  ## First run
46
49
 
47
50
  1. Create the first user account.
48
- 2. Open **Settings > AI Providers**.
49
- 3. Configure a hosted provider or connect local Ollama.
50
- 4. Select a model and send a message in **Chat**.
51
- 5. Open **Settings > Tool Permissions** and review the approval policy before
51
+ 2. Open **Settings > AI Providers** and verify the provider configured by the
52
+ terminal wizard, or connect local Ollama.
53
+ 3. Select a model and send a message in **Chat**.
54
+ 4. Open **Settings > Tool Permissions** and review the approval policy before
52
55
  enabling browser, shell, file-write, desktop, or Android actions.
53
56
 
54
57
  The first isolated-runtime boot downloads and prepares an Ubuntu image. It can
package/docs/index.md CHANGED
@@ -18,11 +18,13 @@ sensitive accounts.
18
18
 
19
19
  ```bash
20
20
  npm install -g neoagent
21
- neoagent install
21
+ neoagent
22
22
  ```
23
23
 
24
- The installer starts the service and prints any host-specific action items.
25
- Open `http://localhost:3333` when it finishes.
24
+ On a new machine, the bare command detects that NeoAgent is not installed,
25
+ runs the installer, and guides you through AI provider API keys. It starts the
26
+ service and prints any host-specific action items. Open
27
+ `http://localhost:3333` when it finishes.
26
28
 
27
29
  Continue with:
28
30
 
@@ -79,116 +79,143 @@ const Map<String, List<String>> _voiceLiveVoicesByProvider =
79
79
  class _SettingsSection {
80
80
  const _SettingsSection(
81
81
  this.title,
82
+ this.label,
83
+ this.icon,
82
84
  this.keywords, {
83
85
  this.requiresDesktop = false,
84
86
  });
85
87
 
86
88
  final String title;
89
+ final String label;
90
+ final IconData icon;
87
91
  final List<String> keywords;
88
92
  final bool requiresDesktop;
89
93
  }
90
94
 
91
- const _overviewSettingsSection = _SettingsSection('overview', <String>[
95
+ const _overviewSettingsSection = _SettingsSection(
92
96
  'overview',
93
- 'summary',
94
- 'onboarding',
95
- 'platform',
96
- 'providers',
97
- ]);
97
+ 'Overview',
98
+ Icons.dashboard_outlined,
99
+ <String>['overview', 'summary', 'onboarding', 'platform', 'providers'],
100
+ );
98
101
 
99
- const _workspaceSettingsSection = _SettingsSection('workspace', <String>[
102
+ const _workspaceSettingsSection = _SettingsSection(
100
103
  'workspace',
101
- 'browser',
102
- 'extension',
103
- 'cli',
104
- 'claude code',
105
- 'desktop',
106
- 'routing',
107
- ]);
108
-
109
- const _behaviorSettingsSection = _SettingsSection('behavior', <String>[
104
+ 'Workspace',
105
+ Icons.workspaces_outline,
106
+ <String>[
107
+ 'workspace',
108
+ 'browser',
109
+ 'extension',
110
+ 'cli',
111
+ 'claude code',
112
+ 'desktop',
113
+ 'routing',
114
+ ],
115
+ );
116
+
117
+ const _behaviorSettingsSection = _SettingsSection(
110
118
  'behavior',
111
- 'persona',
112
- 'social intelligence',
113
- 'turn taking',
114
- 'groups',
115
- 'memory',
116
- 'norms',
117
- 'theory of mind',
118
- 'delivery',
119
- ]);
120
-
121
- const _modelsSettingsSection = _SettingsSection('models', <String>[
122
- 'models',
123
- 'providers',
124
- 'routing',
125
- 'fallback',
126
- 'chat',
127
- 'sub-agent',
128
- 'subagent',
129
- 'smart selector',
130
- ]);
131
-
132
- const _socialReachSettingsSection = _SettingsSection('social reach', <String>[
133
- 'social',
134
- 'reach',
135
- 'web',
136
- 'rss',
137
- 'github',
138
- 'youtube',
139
- 'linkedin',
140
- 'xueqiu',
141
- 'twitter',
142
- 'reddit',
143
- 'instagram',
144
- 'facebook',
145
- 'cookies',
146
- ]);
147
-
148
- const _voiceSettingsSection = _SettingsSection('voice', <String>[
119
+ 'Behavior',
120
+ Icons.psychology_outlined,
121
+ <String>[
122
+ 'behavior',
123
+ 'persona',
124
+ 'social intelligence',
125
+ 'turn taking',
126
+ 'groups',
127
+ 'memory',
128
+ 'norms',
129
+ 'theory of mind',
130
+ 'delivery',
131
+ ],
132
+ );
133
+
134
+ const _modelsSettingsSection =
135
+ _SettingsSection('models', 'Models & routing', Icons.hub_outlined, <String>[
136
+ 'models',
137
+ 'providers',
138
+ 'routing',
139
+ 'fallback',
140
+ 'chat',
141
+ 'sub-agent',
142
+ 'subagent',
143
+ 'smart selector',
144
+ ]);
145
+
146
+ const _socialReachSettingsSection = _SettingsSection(
147
+ 'social reach',
148
+ 'Social reach',
149
+ Icons.public_outlined,
150
+ <String>[
151
+ 'social',
152
+ 'reach',
153
+ 'web',
154
+ 'rss',
155
+ 'github',
156
+ 'youtube',
157
+ 'linkedin',
158
+ 'xueqiu',
159
+ 'twitter',
160
+ 'reddit',
161
+ 'instagram',
162
+ 'facebook',
163
+ 'cookies',
164
+ ],
165
+ );
166
+
167
+ const _voiceSettingsSection = _SettingsSection(
149
168
  'voice',
150
- 'speech',
151
- 'tts',
152
- 'stt',
153
- 'live',
154
- ]);
169
+ 'Voice',
170
+ Icons.mic_none_outlined,
171
+ <String>['voice', 'speech', 'tts', 'stt', 'live'],
172
+ );
155
173
 
156
- const _desktopSettingsSection = _SettingsSection('desktop', <String>[
174
+ const _desktopSettingsSection = _SettingsSection(
157
175
  'desktop',
158
- 'permissions',
159
- 'capture',
160
- 'companion',
161
- 'screen recording',
162
- 'accessibility',
163
- 'input',
164
- ], requiresDesktop: true);
165
-
166
- const _diagnosticsSettingsSection = _SettingsSection('diagnostics', <String>[
176
+ 'Desktop',
177
+ Icons.desktop_windows_outlined,
178
+ <String>[
179
+ 'desktop',
180
+ 'permissions',
181
+ 'capture',
182
+ 'companion',
183
+ 'screen recording',
184
+ 'accessibility',
185
+ 'input',
186
+ ],
187
+ requiresDesktop: true,
188
+ );
189
+
190
+ const _diagnosticsSettingsSection = _SettingsSection(
167
191
  'diagnostics',
168
- 'logs',
169
- 'token',
170
- 'usage',
171
- 'debug',
172
- 'health',
173
- ]);
174
-
175
- const _securitySettingsSection = _SettingsSection('security', <String>[
192
+ 'Diagnostics',
193
+ Icons.monitor_heart_outlined,
194
+ <String>['diagnostics', 'logs', 'token', 'usage', 'debug', 'health'],
195
+ );
196
+
197
+ const _securitySettingsSection = _SettingsSection(
176
198
  'security',
177
- 'tool',
178
- 'permission',
179
- 'allowlist',
180
- 'shell',
181
- 'android',
182
- 'approval',
183
- 'policy',
184
- ]);
199
+ 'Permissions',
200
+ Icons.admin_panel_settings_outlined,
201
+ <String>[
202
+ 'security',
203
+ 'tool',
204
+ 'permission',
205
+ 'allowlist',
206
+ 'shell',
207
+ 'android',
208
+ 'approval',
209
+ 'policy',
210
+ ],
211
+ );
185
212
 
186
213
  const List<_SettingsSection> _settingsSearchSections = <_SettingsSection>[
187
214
  _overviewSettingsSection,
188
- _behaviorSettingsSection,
215
+ _modelsSettingsSection,
189
216
  _workspaceSettingsSection,
217
+ _behaviorSettingsSection,
190
218
  _socialReachSettingsSection,
191
- _modelsSettingsSection,
192
219
  _voiceSettingsSection,
193
220
  _desktopSettingsSection,
194
221
  _securitySettingsSection,
@@ -197,6 +224,7 @@ const List<_SettingsSection> _settingsSearchSections = <_SettingsSection>[
197
224
 
198
225
  class _SettingsPanelState extends State<SettingsPanel> {
199
226
  late final TextEditingController _searchController;
227
+ _SettingsSection _selectedSettingsSection = _overviewSettingsSection;
200
228
  late String _browserBackend;
201
229
  String? _browserExtensionTokenId;
202
230
  late String _cliBackend;
@@ -433,11 +461,30 @@ class _SettingsPanelState extends State<SettingsPanel> {
433
461
  trailing: _settingsSaveButton(controller),
434
462
  )
435
463
  else
436
- Align(
437
- alignment: Alignment.centerRight,
438
- child: Padding(
439
- padding: const EdgeInsets.only(bottom: 12),
440
- child: _settingsSaveButton(controller),
464
+ Padding(
465
+ padding: const EdgeInsets.only(bottom: 12),
466
+ child: Row(
467
+ children: <Widget>[
468
+ Expanded(
469
+ child: Column(
470
+ crossAxisAlignment: CrossAxisAlignment.start,
471
+ children: <Widget>[
472
+ Text(
473
+ 'General settings',
474
+ style: Theme.of(context).textTheme.titleLarge
475
+ ?.copyWith(fontWeight: FontWeight.w800),
476
+ ),
477
+ const SizedBox(height: 2),
478
+ Text(
479
+ 'Choose a category or search across all settings.',
480
+ style: TextStyle(color: _textSecondary),
481
+ ),
482
+ ],
483
+ ),
484
+ ),
485
+ const SizedBox(width: 12),
486
+ _settingsSaveButton(controller),
487
+ ],
441
488
  ),
442
489
  ),
443
490
  if (controller.errorMessage != null) ...<Widget>[
@@ -462,36 +509,38 @@ class _SettingsPanelState extends State<SettingsPanel> {
462
509
  ),
463
510
  ),
464
511
  ),
512
+ const SizedBox(height: 12),
513
+ _buildSettingsCategoryPicker(visibleSearchSections),
465
514
  const SizedBox(height: 16),
466
- if (_matchesSettingsSection(
515
+ if (_showsSettingsSection(
467
516
  searchQuery,
468
517
  _overviewSettingsSection,
469
518
  )) ...<Widget>[
470
519
  _buildSettingsOverview(controller, availableModels.length),
471
520
  const SizedBox(height: 16),
472
521
  ],
473
- if (_matchesSettingsSection(
522
+ if (_showsSettingsSection(
474
523
  searchQuery,
475
524
  _behaviorSettingsSection,
476
525
  )) ...<Widget>[
477
526
  _buildBehaviorSection(controller, routingModels),
478
527
  const SizedBox(height: 16),
479
528
  ],
480
- if (_matchesSettingsSection(
529
+ if (_showsSettingsSection(
481
530
  searchQuery,
482
531
  _workspaceSettingsSection,
483
532
  )) ...<Widget>[
484
533
  _buildWorkspaceSection(controller),
485
534
  const SizedBox(height: 16),
486
535
  ],
487
- if (_matchesSettingsSection(
536
+ if (_showsSettingsSection(
488
537
  searchQuery,
489
538
  _socialReachSettingsSection,
490
539
  )) ...<Widget>[
491
540
  _buildSocialReachSection(controller),
492
541
  const SizedBox(height: 16),
493
542
  ],
494
- if (_matchesSettingsSection(
543
+ if (_showsSettingsSection(
495
544
  searchQuery,
496
545
  _modelsSettingsSection,
497
546
  )) ...<Widget>[
@@ -505,7 +554,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
505
554
  ),
506
555
  const SizedBox(height: 16),
507
556
  ],
508
- if (_matchesSettingsSection(
557
+ if (_showsSettingsSection(
509
558
  searchQuery,
510
559
  _voiceSettingsSection,
511
560
  )) ...<Widget>[
@@ -517,21 +566,21 @@ class _SettingsPanelState extends State<SettingsPanel> {
517
566
  const SizedBox(height: 16),
518
567
  ],
519
568
  if (visibleSearchSections.contains(_desktopSettingsSection) &&
520
- _matchesSettingsSection(
569
+ _showsSettingsSection(
521
570
  searchQuery,
522
571
  _desktopSettingsSection,
523
572
  )) ...<Widget>[
524
573
  _buildDesktopSection(controller),
525
574
  const SizedBox(height: 16),
526
575
  ],
527
- if (_matchesSettingsSection(
576
+ if (_showsSettingsSection(
528
577
  searchQuery,
529
578
  _securitySettingsSection,
530
579
  )) ...<Widget>[
531
580
  _buildSecuritySection(context, controller),
532
581
  const SizedBox(height: 16),
533
582
  ],
534
- if (_matchesSettingsSection(
583
+ if (_showsSettingsSection(
535
584
  searchQuery,
536
585
  _diagnosticsSettingsSection,
537
586
  )) ...<Widget>[_buildDiagnosticsSection(controller)],
@@ -560,6 +609,70 @@ class _SettingsPanelState extends State<SettingsPanel> {
560
609
  return haystack.contains(query);
561
610
  }
562
611
 
612
+ bool _showsSettingsSection(String query, _SettingsSection section) {
613
+ if (query.isNotEmpty) {
614
+ return _matchesSettingsSection(query, section);
615
+ }
616
+ return _selectedSettingsSection == section;
617
+ }
618
+
619
+ Widget _buildSettingsCategoryPicker(Set<_SettingsSection> visibleSections) {
620
+ final sections = _settingsSearchSections
621
+ .where(visibleSections.contains)
622
+ .toList();
623
+ return LayoutBuilder(
624
+ builder: (context, constraints) {
625
+ if (constraints.maxWidth < 620) {
626
+ return DropdownButtonFormField<_SettingsSection>(
627
+ key: ValueKey<_SettingsSection>(_selectedSettingsSection),
628
+ initialValue: _selectedSettingsSection,
629
+ isExpanded: true,
630
+ decoration: const InputDecoration(
631
+ labelText: 'Category',
632
+ prefixIcon: Icon(Icons.category_outlined),
633
+ ),
634
+ items: sections
635
+ .map(
636
+ (section) => DropdownMenuItem<_SettingsSection>(
637
+ value: section,
638
+ child: Row(
639
+ children: <Widget>[
640
+ Icon(section.icon, size: 18),
641
+ const SizedBox(width: 10),
642
+ Text(section.label),
643
+ ],
644
+ ),
645
+ ),
646
+ )
647
+ .toList(),
648
+ onChanged: (section) {
649
+ if (section == null) return;
650
+ _searchController.clear();
651
+ setState(() => _selectedSettingsSection = section);
652
+ },
653
+ );
654
+ }
655
+ return Wrap(
656
+ spacing: 8,
657
+ runSpacing: 8,
658
+ children: sections
659
+ .map(
660
+ (section) => ChoiceChip(
661
+ avatar: Icon(section.icon, size: 17),
662
+ label: Text(section.label),
663
+ selected: _selectedSettingsSection == section,
664
+ onSelected: (_) {
665
+ _searchController.clear();
666
+ setState(() => _selectedSettingsSection = section);
667
+ },
668
+ ),
669
+ )
670
+ .toList(),
671
+ );
672
+ },
673
+ );
674
+ }
675
+
563
676
  bool _noSettingsMatches(
564
677
  String query,
565
678
  Iterable<_SettingsSection> visibleSections,