mop-agent 0.1.12 → 0.1.13

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
@@ -5,8 +5,9 @@ through MOP-FLOW. It stores project memory, performs semantic recall and
5
5
  consolidation, serves grounded chat, and can request approved actions from a
6
6
  linked FLOW node.
7
7
 
8
- > **Release status:** npm package `mop-agent@0.1.10` contains the corrected VPS
9
- > installer, one-time Admin setup/login flow, and shared retro application shell.
8
+ > **Release status:** release candidate `mop-agent@0.1.13` contains the corrected VPS
9
+ > installer, one-time Admin setup/login flow, and simplified shared application shell
10
+ > with centered page titles and ChatGPT-inspired navigation.
10
11
  > The canonical installation command is exactly `npx mop-agent`.
11
12
 
12
13
  ## Current status
@@ -7,7 +7,7 @@ import { useMemoryCore } from "@/components/AppShell";
7
7
  type Turn = { role: "user" | "assistant"; content: string };
8
8
 
9
9
  export default function AssistantPage() {
10
- const { selectedProject, setSelectedProject, projects, provider } = useMemoryCore();
10
+ const { projects } = useMemoryCore();
11
11
  const [turns, setTurns] = useState<Turn[]>([]);
12
12
  const [name, setName] = useState("Admin");
13
13
  const [input, setInput] = useState("");
@@ -34,8 +34,7 @@ export default function AssistantPage() {
34
34
  headers: { "content-type": "application/json" },
35
35
  body: JSON.stringify({
36
36
  message,
37
- projectId: selectedProject || undefined,
38
- allowCrossProject: !selectedProject,
37
+ allowCrossProject: true,
39
38
  }),
40
39
  });
41
40
 
@@ -120,7 +119,7 @@ export default function AssistantPage() {
120
119
  <button onClick={() => send()} disabled={busy || !input.trim()} style={{ ...sendButton, opacity: busy || !input.trim() ? .45 : 1 }}>↑</button>
121
120
  </div>
122
121
  <div style={{ textAlign: "center", color: "rgba(45,74,62,.62)", fontSize: 11, marginTop: 8 }}>
123
- {providerUsed ? `Answered by ${providerUsed} · ` : ""}{selectedProject ? "Selected project memory" : "Cross-project memory"}
122
+ {providerUsed ? `Answered by ${providerUsed} · ` : ""}Cross-project memory
124
123
  </div>
125
124
  </div>
126
125
  </section>
@@ -97,7 +97,7 @@ button {
97
97
  .mop-app-frame {
98
98
  min-height: 100vh;
99
99
  display: grid;
100
- grid-template-columns: 238px minmax(0, 1fr);
100
+ grid-template-columns: 260px minmax(0, 1fr);
101
101
  grid-template-rows: 70px minmax(0, 1fr);
102
102
  grid-template-areas:
103
103
  "topbar topbar"
@@ -111,7 +111,7 @@ button {
111
111
  top: 0;
112
112
  z-index: 50;
113
113
  display: grid;
114
- grid-template-columns: 238px minmax(0, 1fr);
114
+ grid-template-columns: 260px minmax(0, 1fr);
115
115
  min-width: 0;
116
116
  color: var(--mop-cream);
117
117
  background:
@@ -153,6 +153,7 @@ button {
153
153
  min-width: 0;
154
154
  display: flex;
155
155
  align-items: center;
156
+ justify-content: center;
156
157
  gap: 14px;
157
158
  padding: 0 18px;
158
159
  }
@@ -167,18 +168,8 @@ button {
167
168
  cursor: pointer;
168
169
  }
169
170
 
170
- .mop-topbar-title {
171
- display: flex;
172
- align-items: center;
173
- gap: 8px;
174
- min-width: 130px;
175
- font-family: "SFMono-Regular", Consolas, monospace;
176
- font-size: 13px;
177
- letter-spacing: .08em;
178
- text-transform: uppercase;
179
- }
180
-
181
171
  .mop-live-dot {
172
+ flex: 0 0 auto;
182
173
  width: 8px;
183
174
  height: 8px;
184
175
  background: #78e19b;
@@ -186,37 +177,21 @@ button {
186
177
  }
187
178
 
188
179
  .mop-topbar-center {
189
- position: absolute;
190
- left: 50%;
191
- top: 50%;
192
- transform: translate(-50%, -50%);
193
- min-width: 255px;
180
+ min-width: 240px;
181
+ display: flex;
182
+ align-items: center;
183
+ justify-content: center;
184
+ gap: 10px;
194
185
  padding: 9px 28px;
195
186
  text-align: center;
196
187
  color: rgba(254, 249, 225, .86);
197
188
  border: 1px solid rgba(254, 249, 225, .12);
198
189
  background: rgba(254, 249, 225, .07);
199
190
  font-family: "SFMono-Regular", Consolas, monospace;
200
- font-size: 11px;
201
- font-weight: 800;
202
- letter-spacing: .18em;
203
- }
204
-
205
- .mop-topbar-meta {
206
- display: flex;
207
- align-items: center;
208
- gap: 9px;
209
- margin-left: auto;
210
- font-family: "SFMono-Regular", Consolas, monospace;
211
- font-size: 10px;
191
+ font-size: 12px;
212
192
  font-weight: 800;
213
- letter-spacing: .12em;
214
- }
215
-
216
- .mop-version {
217
- padding: 4px 6px;
218
- border: 1px solid rgba(254, 249, 225, .22);
219
- background: rgba(254, 249, 225, .07);
193
+ letter-spacing: .14em;
194
+ text-transform: uppercase;
220
195
  }
221
196
 
222
197
  .mop-app-sidebar {
@@ -228,60 +203,68 @@ button {
228
203
  display: flex;
229
204
  flex-direction: column;
230
205
  overflow-y: auto;
231
- padding: 15px 9px 12px;
206
+ padding: 10px 9px 9px;
232
207
  color: var(--mop-cream);
233
208
  background:
234
- linear-gradient(rgba(255,255,255,.018), rgba(0,0,0,.05)),
209
+ linear-gradient(rgba(255,255,255,.028), rgba(0,0,0,.035)),
235
210
  var(--mop-green);
236
211
  border-right: 2px solid #20362e;
237
212
  }
238
213
 
239
- .mop-nav-section { margin-bottom: 17px; }
240
- .mop-nav-section > p {
241
- margin: 0 8px 9px;
242
- color: rgba(254, 249, 225, .46);
243
- font-family: "SFMono-Regular", Consolas, monospace;
244
- font-size: 9px;
245
- font-weight: 900;
246
- letter-spacing: .22em;
214
+ .mop-sidebar-primary {
215
+ display: grid;
216
+ gap: 3px;
217
+ margin-bottom: 22px;
247
218
  }
248
219
 
249
- .mop-nav-section nav { display: grid; gap: 4px; }
220
+ .mop-sidebar-primary a,
250
221
  .mop-nav-section a,
251
222
  .mop-nav-section button {
252
223
  display: flex;
253
224
  align-items: center;
254
225
  gap: 11px;
255
226
  width: 100%;
256
- min-height: 43px;
257
- padding: 8px 12px;
258
- color: rgba(254, 249, 225, .78);
227
+ min-height: 40px;
228
+ padding: 8px 11px;
229
+ color: rgba(254, 249, 225, .82);
259
230
  border: 1px solid transparent;
260
231
  background: transparent;
261
232
  text-align: left;
262
233
  text-decoration: none;
263
- font-family: "SFMono-Regular", Consolas, monospace;
264
- font-size: 12px;
265
- font-weight: 800;
266
- letter-spacing: .055em;
267
- text-transform: uppercase;
234
+ font-size: 14px;
235
+ font-weight: 540;
236
+ letter-spacing: 0;
268
237
  cursor: pointer;
269
238
  }
270
239
 
240
+ .mop-sidebar-primary a:hover,
271
241
  .mop-nav-section a:hover,
272
242
  .mop-nav-section button:hover {
273
243
  color: var(--mop-cream);
274
- background: rgba(254, 249, 225, .07);
244
+ background: rgba(254, 249, 225, .075);
275
245
  }
276
246
 
247
+ .mop-sidebar-primary a.is-active,
277
248
  .mop-nav-section a.is-active,
278
249
  .mop-nav-section button.is-active {
279
- color: #ff8a3d;
280
- border-color: rgba(254, 249, 225, .18);
281
- background: rgba(254, 249, 225, .09);
282
- box-shadow: inset 3px 0 #ff6f2c, 2px 2px 0 rgba(18, 38, 30, .25);
250
+ color: var(--mop-cream);
251
+ border-color: rgba(254, 249, 225, .12);
252
+ background: rgba(254, 249, 225, .1);
253
+ box-shadow: 2px 2px 0 rgba(18, 38, 30, .22);
254
+ }
255
+
256
+ .mop-nav-section { margin-bottom: 18px; }
257
+ .mop-nav-section > p {
258
+ margin: 0 11px 7px;
259
+ color: rgba(254, 249, 225, .46);
260
+ font-family: "SFMono-Regular", Consolas, monospace;
261
+ font-size: 10px;
262
+ font-weight: 750;
263
+ letter-spacing: .11em;
283
264
  }
284
265
 
266
+ .mop-nav-section nav { display: grid; gap: 2px; }
267
+
285
268
  .mop-nav-icon {
286
269
  width: 21px;
287
270
  text-align: center;
@@ -289,6 +272,32 @@ button {
289
272
  font-size: 16px;
290
273
  }
291
274
 
275
+ .mop-project-memory {
276
+ min-height: 0;
277
+ overflow: hidden;
278
+ }
279
+
280
+ .mop-project-memory nav {
281
+ max-height: min(34vh, 280px);
282
+ overflow-y: auto;
283
+ scrollbar-width: thin;
284
+ }
285
+
286
+ .mop-project-memory a { min-height: 34px; padding-block: 5px; font-size: 13px; }
287
+ .mop-project-dot {
288
+ flex: 0 0 auto;
289
+ width: 7px;
290
+ height: 7px;
291
+ background: rgba(254, 249, 225, .34);
292
+ }
293
+ .mop-project-dot[data-online="true"] { background: #78e19b; box-shadow: 0 0 0 2px rgba(120, 225, 155, .12); }
294
+ .mop-nav-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
295
+ .mop-sidebar-empty { display: block; padding: 7px 11px; color: rgba(254, 249, 225, .42); font-size: 12px; }
296
+ .mop-admin-nav { margin-top: 2px; }
297
+ .mop-settings-subnav { display: grid; gap: 1px; margin: 1px 0 2px 31px; border-left: 1px solid rgba(254, 249, 225, .18); }
298
+ .mop-settings-subnav button { min-height: 32px; padding: 5px 12px; color: rgba(254, 249, 225, .6); font-size: 12px; }
299
+ .mop-settings-subnav button.is-active { color: #ff9a56; border-color: transparent; background: transparent; box-shadow: none; }
300
+
292
301
  .mop-sidebar-spacer { flex: 1; }
293
302
  .mop-account-card {
294
303
  width: 100%;
@@ -297,11 +306,12 @@ button {
297
306
  gap: 9px;
298
307
  padding: 10px 8px;
299
308
  color: var(--mop-cream);
300
- border: 1px solid rgba(254, 249, 225, .13);
301
- background: rgba(0, 0, 0, .07);
309
+ border: 1px solid transparent;
310
+ background: transparent;
302
311
  text-align: left;
303
312
  cursor: pointer;
304
313
  }
314
+ .mop-account-card:hover { border-color: rgba(254, 249, 225, .1); background: rgba(254, 249, 225, .07); }
305
315
 
306
316
  .mop-account-avatar {
307
317
  flex: 0 0 auto;
@@ -385,63 +395,16 @@ button {
385
395
  flex-direction: column;
386
396
  }
387
397
 
388
- /* Toolbar lives inside the dark maroon topbar, so its text is light. */
389
- .mop-assistant-toolbar {
390
- width: 100%;
391
- display: flex;
392
- align-items: center;
393
- justify-content: space-between;
394
- gap: 16px;
395
- }
396
- .mop-assistant-status {
397
- display: flex;
398
- align-items: center;
399
- gap: 9px;
400
- min-width: 0;
401
- }
402
- .mop-assistant-status strong {
403
- font-family: "SFMono-Regular", Consolas, monospace;
404
- font-size: 13px;
405
- letter-spacing: .08em;
406
- text-transform: uppercase;
407
- color: var(--mop-cream);
408
- }
409
- .mop-assistant-provider {
410
- overflow: hidden;
411
- text-overflow: ellipsis;
412
- white-space: nowrap;
413
- color: rgba(254, 249, 225, .66);
414
- font-size: 12px;
415
- }
416
- .mop-assistant-scope {
417
- display: flex;
418
- align-items: center;
419
- gap: 8px;
420
- flex: 0 0 auto;
421
- color: rgba(254, 249, 225, .82);
422
- font-family: "SFMono-Regular", Consolas, monospace;
423
- font-size: 10px;
424
- font-weight: 800;
425
- letter-spacing: .12em;
426
- text-transform: uppercase;
427
- }
428
- .mop-assistant-scope select {
429
- color: var(--mop-green);
430
- background: var(--mop-paper);
431
- border: 1px solid rgba(254, 249, 225, .32);
432
- padding: 6px 8px;
433
- font-family: "SFMono-Regular", Consolas, monospace;
434
- font-size: 12px;
435
- }
436
-
437
- .mop-assistant-conversation { flex: 1; overflow-y: auto; padding: 0 28px; }
398
+ .mop-assistant-conversation { flex: 1; overflow-y: auto; padding: 0 clamp(18px, 5vw, 64px); }
438
399
  .mop-assistant-welcome {
439
- min-height: calc(100vh - 285px);
400
+ width: min(100%, 760px);
401
+ min-height: calc(100vh - 190px);
402
+ margin: 0 auto;
440
403
  display: flex;
441
404
  flex-direction: column;
442
405
  align-items: center;
443
406
  justify-content: center;
444
- padding: 32px 0 90px;
407
+ padding: 38px 0 110px;
445
408
  text-align: center;
446
409
  }
447
410
 
@@ -497,11 +460,10 @@ button {
497
460
  .mop-app-topbar { grid-template-columns: 66px minmax(0, 1fr); }
498
461
  .mop-app-brand { justify-content: center; padding: 5px; }
499
462
  .mop-app-brand img { width: 49px; height: 49px; }
500
- .mop-app-brand span, .mop-topbar-center { display: none; }
463
+ .mop-app-brand span { display: none; }
501
464
  .mop-app-topbar-main { padding: 0 10px; gap: 9px; }
502
465
  .mop-menu-toggle { display: block; }
503
- .mop-topbar-title { min-width: 0; font-size: 11px; }
504
- .mop-topbar-meta > span:first-child { display: none; }
466
+ .mop-topbar-center { min-width: 0; flex: 1; padding: 8px 12px; font-size: 10px; }
505
467
  .mop-app-sidebar {
506
468
  position: fixed;
507
469
  top: 62px;
@@ -523,43 +485,8 @@ button {
523
485
  }
524
486
  .mop-app-main { min-height: calc(100vh - 62px); }
525
487
  .mop-assistant-page { min-height: calc(100vh - 62px); }
526
- .mop-assistant-toolbar { gap: 9px; }
527
- .mop-assistant-status { display: none; }
528
488
  .mop-assistant-conversation { padding: 0 16px; }
529
489
  .mop-assistant-composer-wrap { padding: 26px 12px 12px; }
530
490
  .mop-settings-grid { grid-template-columns: 1fr; }
531
491
  .mop-user-invite-form { grid-template-columns: 1fr !important; }
532
492
  }
533
-
534
- .mop-back-workspace-btn {
535
- display: flex;
536
- align-items: center;
537
- justify-content: center;
538
- gap: 8px;
539
- width: 100%;
540
- min-height: 40px;
541
- margin-bottom: 9px;
542
- padding: 9px 12px;
543
- border: 1px solid var(--mop-red);
544
- background: var(--mop-red);
545
- color: var(--mop-cream);
546
- font-family: "SFMono-Regular", Consolas, monospace;
547
- font-size: 11px;
548
- font-weight: 900;
549
- text-decoration: none;
550
- cursor: pointer;
551
- transition: transform 80ms steps(2, end), box-shadow 80ms steps(2, end);
552
- box-shadow: 2px 2px 0 rgba(45, 74, 62, .17);
553
- }
554
-
555
- .mop-back-workspace-btn:hover {
556
- transform: translate(-1px, -1px);
557
- box-shadow: 3px 3px 0 rgba(45, 74, 62, .24);
558
- color: var(--mop-cream);
559
- }
560
-
561
- .mop-back-workspace-btn:active {
562
- transform: translate(1px, 1px);
563
- box-shadow: 0 0 0 rgba(45, 74, 62, 0);
564
- }
565
-
@@ -12,13 +12,9 @@ export type AppViewer = {
12
12
  };
13
13
 
14
14
  export type Project = { id: string; name: string; status: string };
15
- export type ProviderState = { configured: boolean; provider?: string; model?: string | null };
16
15
 
17
16
  interface MemoryCoreContextType {
18
- selectedProject: string;
19
- setSelectedProject: (id: string) => void;
20
17
  projects: Project[];
21
- provider: ProviderState;
22
18
  settingsSection: "providers" | "users";
23
19
  setSettingsSection: (section: "providers" | "users") => void;
24
20
  }
@@ -27,9 +23,7 @@ const MemoryCoreContext = createContext<MemoryCoreContextType | undefined>(undef
27
23
 
28
24
  export function useMemoryCore() {
29
25
  const context = useContext(MemoryCoreContext);
30
- if (!context) {
31
- throw new Error("useMemoryCore must be used within a MemoryCoreProvider");
32
- }
26
+ if (!context) throw new Error("useMemoryCore must be used within a MemoryCoreProvider");
33
27
  return context;
34
28
  }
35
29
 
@@ -46,22 +40,17 @@ function pageTitle(pathname: string): string {
46
40
  export function AppShell({ viewer, children }: { viewer: AppViewer; children: ReactNode }) {
47
41
  const pathname = usePathname();
48
42
  const [menuOpen, setMenuOpen] = useState(false);
49
- const isAdmin = viewer.role === "owner";
50
- const title = pageTitle(pathname);
51
-
52
43
  const [projects, setProjects] = useState<Project[]>([]);
53
- const [provider, setProvider] = useState<ProviderState>({ configured: false });
54
- const [selectedProject, setSelectedProject] = useState("");
55
44
  const [settingsSection, setSettingsSection] = useState<"providers" | "users">("providers");
45
+ const isAdmin = viewer.role === "owner";
46
+ const isSettings = pathname.startsWith("/settings");
47
+ const title = pageTitle(pathname);
56
48
 
57
49
  useEffect(() => {
58
- Promise.all([
59
- fetch("/api/projects").then((r) => r.json()),
60
- fetch("/api/providers").then((r) => r.json()),
61
- ]).then(([projectData, providerData]) => {
62
- setProjects(projectData.projects ?? []);
63
- setProvider(providerData.config ?? { configured: false });
64
- }).catch(() => {});
50
+ fetch("/api/projects")
51
+ .then((response) => response.json())
52
+ .then((data) => setProjects(data.projects ?? []))
53
+ .catch(() => {});
65
54
 
66
55
  const requested = new URLSearchParams(window.location.search).get("section");
67
56
  if (requested === "users") setSettingsSection("users");
@@ -72,21 +61,13 @@ export function AppShell({ viewer, children }: { viewer: AppViewer; children: Re
72
61
  window.location.replace("/login");
73
62
  }
74
63
 
75
- const selectSection = (sec: "providers" | "users") => {
76
- setSettingsSection(sec);
77
- const url = sec === "providers" ? "/settings" : "/settings?section=users";
78
- window.history.replaceState(null, "", url);
79
- };
80
-
81
- const isSettings = pathname.startsWith("/settings");
82
-
83
- const nav = [
84
- { href: "/assistant", label: "Assistant", icon: "✦", active: pathname.startsWith("/assistant") || pathname.startsWith("/chat/") },
85
- { href: "/brain", label: "Brain", icon: "◉", active: pathname.startsWith("/brain") },
86
- ];
64
+ function selectSection(section: "providers" | "users") {
65
+ setSettingsSection(section);
66
+ window.history.replaceState(null, "", section === "providers" ? "/settings" : "/settings?section=users");
67
+ }
87
68
 
88
69
  return (
89
- <MemoryCoreContext.Provider value={{ selectedProject, setSelectedProject, projects, provider, settingsSection, setSettingsSection }}>
70
+ <MemoryCoreContext.Provider value={{ projects, settingsSection, setSettingsSection }}>
90
71
  <div className="mop-app-frame">
91
72
  <header className="mop-app-topbar">
92
73
  <a className="mop-app-brand" href="/assistant" aria-label="MOP-AGENT home">
@@ -103,99 +84,70 @@ export function AppShell({ viewer, children }: { viewer: AppViewer; children: Re
103
84
  >
104
85
 
105
86
  </button>
106
- {pathname === "/assistant" ? (
107
- <div className="mop-assistant-toolbar">
108
- <div className="mop-assistant-status">
109
- <span className="mop-live-dot" />
110
- <strong>LIVE ASSISTANT</strong>
111
- <span className="mop-assistant-provider">
112
- {provider.configured ? `${provider.provider}${provider.model ? ` · ${provider.model}` : ""}` : "offline demo"}
113
- </span>
114
- </div>
115
- <label className="mop-assistant-scope">
116
- MEMORY SCOPE
117
- <select value={selectedProject} onChange={(e) => setSelectedProject(e.target.value)}>
118
- <option value="">All memory</option>
119
- {projects.map((project) => <option key={project.id} value={project.id}>{project.name}</option>)}
120
- </select>
121
- </label>
122
- </div>
123
- ) : (
124
- <>
125
- <div className="mop-topbar-title">
126
- <span className="mop-live-dot" />
127
- <strong>{title}</strong>
128
- </div>
129
- <div className="mop-topbar-center">MOP MEMORYCORE</div>
130
- <div className="mop-topbar-meta">
131
- <span>{isAdmin ? "ADMIN" : "MEMBER"}</span>
132
- <span className="mop-version">v0.1.12</span>
133
- </div>
134
- </>
135
- )}
87
+ <div className="mop-topbar-center">
88
+ <span className="mop-live-dot" />
89
+ <strong>{title}</strong>
90
+ </div>
136
91
  </div>
137
92
  </header>
138
93
 
139
94
  {menuOpen && <button className="mop-sidebar-scrim" aria-label="Close navigation" onClick={() => setMenuOpen(false)} />}
140
95
 
141
96
  <aside className={`mop-app-sidebar${menuOpen ? " is-open" : ""}`}>
142
- {isSettings ? (
143
- <div className="mop-nav-section">
144
- <p>SETTINGS</p>
97
+ <nav className="mop-sidebar-primary" aria-label="Workspace">
98
+ <a href="/assistant" className={pathname.startsWith("/assistant") || pathname.startsWith("/chat/") ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
99
+ <span className="mop-nav-icon">✎</span>
100
+ <span>New chat</span>
101
+ </a>
102
+ <a href="/brain" className={pathname.startsWith("/brain") ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
103
+ <span className="mop-nav-icon">◉</span>
104
+ <span>Brain</span>
105
+ </a>
106
+ </nav>
107
+
108
+ <div className="mop-nav-section mop-project-memory">
109
+ <p>PROJECT MEMORY</p>
110
+ <nav>
111
+ {projects.slice(0, 8).map((project) => (
112
+ <a key={project.id} href={`/brain/${project.id}`} className={pathname === `/brain/${project.id}` ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
113
+ <span className="mop-project-dot" data-online={project.status === "online"} />
114
+ <span className="mop-nav-label">{project.name}</span>
115
+ </a>
116
+ ))}
117
+ {projects.length === 0 && <span className="mop-sidebar-empty">No linked projects yet</span>}
118
+ </nav>
119
+ </div>
120
+
121
+ {isAdmin && (
122
+ <div className="mop-nav-section mop-admin-nav">
123
+ <p>ADMIN</p>
145
124
  <nav>
146
- <button className={settingsSection === "providers" ? "is-active" : ""} onClick={() => { selectSection("providers"); setMenuOpen(false); }}>
147
- <span className="mop-nav-icon">◇</span>
148
- <span>Providers</span>
149
- </button>
150
- <button className={settingsSection === "users" ? "is-active" : ""} onClick={() => { selectSection("users"); setMenuOpen(false); }}>
151
- <span className="mop-nav-icon">♙</span>
152
- <span>Users</span>
153
- </button>
125
+ <a href="/settings" className={isSettings ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
126
+ <span className="mop-nav-icon">⚙</span>
127
+ <span>Settings</span>
128
+ </a>
129
+ {isSettings && (
130
+ <div className="mop-settings-subnav">
131
+ <button className={settingsSection === "providers" ? "is-active" : ""} onClick={() => { selectSection("providers"); setMenuOpen(false); }}>
132
+ <span>Providers</span>
133
+ </button>
134
+ <button className={settingsSection === "users" ? "is-active" : ""} onClick={() => { selectSection("users"); setMenuOpen(false); }}>
135
+ <span>Users</span>
136
+ </button>
137
+ </div>
138
+ )}
154
139
  </nav>
155
140
  </div>
156
- ) : (
157
- <>
158
- <div className="mop-nav-section">
159
- <p>WORKSPACE</p>
160
- <nav>
161
- {nav.map((item) => (
162
- <a key={item.href} href={item.href} className={item.active ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
163
- <span className="mop-nav-icon">{item.icon}</span>
164
- <span>{item.label}</span>
165
- </a>
166
- ))}
167
- </nav>
168
- </div>
169
-
170
- {isAdmin && (
171
- <div className="mop-nav-section">
172
- <p>ADMIN</p>
173
- <nav>
174
- <a href="/settings" className={pathname.startsWith("/settings") ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
175
- <span className="mop-nav-icon">⚙</span>
176
- <span>Settings</span>
177
- </a>
178
- </nav>
179
- </div>
180
- )}
181
- </>
182
141
  )}
183
142
 
184
143
  <div className="mop-sidebar-spacer" />
185
-
186
- {isSettings && (
187
- <a href="/assistant" className="mop-back-workspace-btn">
188
- <span>← BACK TO WORKSPACE</span>
189
- </a>
190
- )}
191
-
192
144
  <button className="mop-account-card" type="button" onClick={logout} title="Sign out">
193
145
  <span className="mop-account-avatar">{viewer.name.slice(0, 1).toUpperCase()}</span>
194
146
  <span className="mop-account-copy">
195
147
  <strong>{viewer.name}</strong>
196
148
  <small>{isAdmin ? "Administrator" : "Member"}</small>
197
149
  </span>
198
- <span aria-hidden="true">↪</span>
150
+ <span aria-hidden="true">•••</span>
199
151
  </button>
200
152
  </aside>
201
153
 
@@ -204,4 +156,3 @@ export function AppShell({ viewer, children }: { viewer: AppViewer; children: Re
204
156
  </MemoryCoreContext.Provider>
205
157
  );
206
158
  }
207
-
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "mop-agent",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "mop-agent",
9
- "version": "0.1.12",
9
+ "version": "0.1.13",
10
10
  "license": "UNLICENSED",
11
11
  "workspaces": [
12
12
  "packages/*",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mop-agent",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Self-hosted AI assistant with persistent cross-project memory, installed with npx mop-agent.",
5
5
  "author": "BURHANDEV ENTERPRISE",
6
6
  "license": "UNLICENSED",