mop-agent 0.1.3 → 0.1.5
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 +1 -1
- package/apps/web/app/api/setup/status/route.ts +5 -4
- package/apps/web/app/assistant/page.tsx +33 -33
- package/apps/web/app/brain/[projectId]/page.tsx +4 -3
- package/apps/web/app/brain/graph/page.tsx +4 -4
- package/apps/web/app/brain/page.tsx +15 -15
- package/apps/web/app/chat/[projectId]/page.tsx +6 -6
- package/apps/web/app/globals.css +21 -0
- package/apps/web/app/icon.svg +489 -0
- package/apps/web/app/layout.tsx +4 -2
- package/apps/web/app/settings/page.tsx +4 -4
- package/apps/web/app/setup/page.tsx +19 -32
- package/apps/web/app/team/page.tsx +5 -5
- package/apps/web/server.ts +12 -2
- package/installer/mop-agent.mjs +9 -2
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/apps/web/app/layout.tsx
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import "./globals.css";
|
|
2
3
|
|
|
3
4
|
export const metadata = {
|
|
4
5
|
title: "MOP-AGENT",
|
|
5
6
|
description: "Self-hosted AI assistant with persistent, cross-project memory.",
|
|
7
|
+
icons: { icon: "/icon.svg" },
|
|
6
8
|
};
|
|
7
9
|
|
|
8
10
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
@@ -13,8 +15,8 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
13
15
|
margin: 0,
|
|
14
16
|
fontFamily:
|
|
15
17
|
"ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
|
|
16
|
-
background: "#
|
|
17
|
-
color: "#
|
|
18
|
+
background: "#fef9e1",
|
|
19
|
+
color: "#2d4a3e",
|
|
18
20
|
}}
|
|
19
21
|
>
|
|
20
22
|
{children}
|
|
@@ -33,10 +33,10 @@ export default function SettingsPage() {
|
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
35
|
<main style={{ maxWidth: 520, margin: "0 auto", padding: "48px 24px" }}>
|
|
36
|
-
<a href="/assistant" style={{ color: "#
|
|
36
|
+
<a href="/assistant" style={{ color: "#742220" }}>← Assistant</a>
|
|
37
37
|
<h1 style={{ fontSize: 24 }}>⚙️ Provider settings</h1>
|
|
38
38
|
|
|
39
|
-
<div style={{ border: "1px solid
|
|
39
|
+
<div style={{ border: "1px solid rgba(45,74,62,.28)", borderRadius: 8, padding: 14, margin: "12px 0", opacity: 0.9, background: "#fffdf2" }}>
|
|
40
40
|
{config.configured
|
|
41
41
|
? <>Active: <strong>{config.provider}</strong>{config.model ? ` · ${config.model}` : ""} · key {config.keyHint}</>
|
|
42
42
|
: "No provider key saved yet — chat uses the offline echo provider."}
|
|
@@ -59,5 +59,5 @@ export default function SettingsPage() {
|
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const inp: React.CSSProperties = { padding: "10px 12px", borderRadius: 8, border: "1px solid
|
|
63
|
-
const btn: React.CSSProperties = { padding: "10px 12px", borderRadius: 8, border: "1px solid #
|
|
62
|
+
const inp: React.CSSProperties = { padding: "10px 12px", borderRadius: 8, border: "1px solid rgba(45,74,62,.32)", background: "#fffdf2", color: "#2d4a3e" };
|
|
63
|
+
const btn: React.CSSProperties = { padding: "10px 12px", borderRadius: 8, border: "1px solid #742220", background: "#742220", color: "#fef9e1", cursor: "pointer" };
|
|
@@ -13,12 +13,10 @@ export default function SetupPage() {
|
|
|
13
13
|
const [mode, setMode] = useState<"signup" | "signin">("signup");
|
|
14
14
|
|
|
15
15
|
useEffect(() => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
.then(([status, me]) => {
|
|
21
|
-
if (me.ok) {
|
|
16
|
+
fetch("/api/setup/status")
|
|
17
|
+
.then((r) => r.json() as Promise<{ ownerExists: boolean; authenticated: boolean }>)
|
|
18
|
+
.then((status) => {
|
|
19
|
+
if (status.authenticated) {
|
|
22
20
|
window.location.replace("/assistant");
|
|
23
21
|
return;
|
|
24
22
|
}
|
|
@@ -57,22 +55,12 @@ export default function SetupPage() {
|
|
|
57
55
|
const loading = ownerExists === null && !msg;
|
|
58
56
|
|
|
59
57
|
return (
|
|
60
|
-
<main style={shell}>
|
|
61
|
-
<section style={brandPanel}>
|
|
62
|
-
<
|
|
63
|
-
<p style={eyebrow}>SELF-HOSTED AI ASSISTANT</p>
|
|
64
|
-
<h1 style={{ fontSize: "clamp(32px, 5vw, 52px)", lineHeight: 1.05, margin: "12px 0 18px" }}>
|
|
65
|
-
Your assistant.<br />Your server. Your memory.
|
|
66
|
-
</h1>
|
|
67
|
-
<p style={{ color: "#9aa8bd", fontSize: 17, lineHeight: 1.7, maxWidth: 520 }}>
|
|
68
|
-
MOP-AGENT gives you one private assistant across your projects. Brain is the memory layer behind it—not the first hurdle.
|
|
69
|
-
</p>
|
|
70
|
-
<div style={featureRow}>
|
|
71
|
-
<span>◆ Private</span><span>◆ Persistent memory</span><span>◆ Cross-project</span>
|
|
72
|
-
</div>
|
|
58
|
+
<main className="mop-setup-shell" style={shell}>
|
|
59
|
+
<section className="mop-setup-brand" style={brandPanel}>
|
|
60
|
+
<img src="/icon.svg" alt="MOP-AGENT" style={heroLogo} />
|
|
73
61
|
</section>
|
|
74
62
|
|
|
75
|
-
<section style={formWrap}>
|
|
63
|
+
<section className="mop-setup-form" style={formWrap}>
|
|
76
64
|
<div style={formCard}>
|
|
77
65
|
<p style={eyebrow}>{mode === "signup" ? "FIRST-RUN SETUP" : "WELCOME BACK"}</p>
|
|
78
66
|
<h2 style={{ fontSize: 27, margin: "8px 0" }}>
|
|
@@ -103,7 +91,7 @@ export default function SetupPage() {
|
|
|
103
91
|
</form>
|
|
104
92
|
)}
|
|
105
93
|
|
|
106
|
-
{msg && <p role="alert" style={{ marginTop: 16, color: "#
|
|
94
|
+
{msg && <p role="alert" style={{ marginTop: 16, color: "#742220" }}>{msg}</p>}
|
|
107
95
|
|
|
108
96
|
{!loading && (
|
|
109
97
|
<p style={{ marginTop: 22, fontSize: 13, color: "#7f8da2" }}>
|
|
@@ -119,14 +107,13 @@ export default function SetupPage() {
|
|
|
119
107
|
);
|
|
120
108
|
}
|
|
121
109
|
|
|
122
|
-
const shell: React.CSSProperties = { minHeight: "100vh", display: "grid", gridTemplateColumns: "minmax(0, 1.25fr) minmax(380px, .75fr)", background: "
|
|
123
|
-
const brandPanel: React.CSSProperties = { padding: "clamp(48px, 8vw, 110px)", display: "flex",
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
const eyebrow: React.CSSProperties = { margin: "20px 0 0", color: "#
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
const textButton: React.CSSProperties = { padding: 0, border: 0, background: "none", color: "#85a1ff", cursor: "pointer" };
|
|
110
|
+
const shell: React.CSSProperties = { minHeight: "100vh", display: "grid", gridTemplateColumns: "minmax(0, 1.25fr) minmax(380px, .75fr)", background: "#fef9e1" };
|
|
111
|
+
const brandPanel: React.CSSProperties = { padding: "clamp(48px, 8vw, 110px)", display: "flex", alignItems: "center", justifyContent: "center", overflow: "hidden", background: "radial-gradient(circle at 50% 45%, #49685c 0, #2d4a3e 58%, #21382f 100%)" };
|
|
112
|
+
const heroLogo: React.CSSProperties = { display: "block", width: "min(72%, 560px)", height: "auto", maxHeight: "72vh", objectFit: "contain", filter: "drop-shadow(0 24px 34px rgba(0,0,0,.28))" };
|
|
113
|
+
const formWrap: React.CSSProperties = { display: "flex", alignItems: "center", justifyContent: "center", padding: 28, background: "#fef9e1", borderLeft: "1px solid #2d4a3e" };
|
|
114
|
+
const formCard: React.CSSProperties = { width: "min(100%, 430px)", padding: "38px 34px", border: "1px solid rgba(45,74,62,.45)", borderRadius: 18, background: "#fffdf2", boxShadow: "0 24px 70px rgba(45,74,62,.14)" };
|
|
115
|
+
const eyebrow: React.CSSProperties = { margin: "20px 0 0", color: "#742220", fontSize: 12, fontWeight: 800, letterSpacing: ".16em" };
|
|
116
|
+
const label: React.CSSProperties = { display: "grid", gap: 7, color: "#2d4a3e", fontSize: 13, fontWeight: 650 };
|
|
117
|
+
const inputStyle: React.CSSProperties = { padding: "12px 13px", borderRadius: 9, border: "1px solid rgba(45,74,62,.42)", outline: "none", background: "#fef9e1", color: "#2d4a3e", fontSize: 15 };
|
|
118
|
+
const buttonStyle: React.CSSProperties = { marginTop: 4, padding: "12px 14px", borderRadius: 9, border: "1px solid #742220", background: "#742220", color: "#fef9e1", fontWeight: 750, fontSize: 15, cursor: "pointer" };
|
|
119
|
+
const textButton: React.CSSProperties = { padding: 0, border: 0, background: "none", color: "#742220", cursor: "pointer" };
|
|
@@ -39,7 +39,7 @@ export default function TeamPage() {
|
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
41
|
<main style={{ maxWidth: 640, margin: "0 auto", padding: "40px 24px" }}>
|
|
42
|
-
<a href="/brain" style={{ color: "#
|
|
42
|
+
<a href="/brain" style={{ color: "#742220" }}>← Brain</a>
|
|
43
43
|
<h1 style={{ fontSize: 24 }}>👥 Team</h1>
|
|
44
44
|
<p style={{ opacity: 0.7 }}>You are <strong>{me?.user.email}</strong> · role <strong>{me?.role}</strong></p>
|
|
45
45
|
|
|
@@ -69,7 +69,7 @@ export default function TeamPage() {
|
|
|
69
69
|
{invites.filter((i) => !i.usedAt).map((i) => (
|
|
70
70
|
<li key={i.email} style={row}>
|
|
71
71
|
✉️ {i.email} <span style={{ opacity: 0.5 }}>· {i.role}</span>
|
|
72
|
-
<button onClick={() => revoke(i.email)} style={{ float: "right", ...btn, background: "#
|
|
72
|
+
<button onClick={() => revoke(i.email)} style={{ float: "right", ...btn, background: "#742220", borderColor: "#742220", padding: "2px 10px" }}>revoke</button>
|
|
73
73
|
</li>
|
|
74
74
|
))}
|
|
75
75
|
{invites.filter((i) => !i.usedAt).length === 0 && <p style={{ opacity: 0.5 }}>None.</p>}
|
|
@@ -81,6 +81,6 @@ export default function TeamPage() {
|
|
|
81
81
|
);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const row: React.CSSProperties = { border: "1px solid
|
|
85
|
-
const inp: React.CSSProperties = { padding: "8px 10px", borderRadius: 8, border: "1px solid
|
|
86
|
-
const btn: React.CSSProperties = { padding: "8px 14px", borderRadius: 8, border: "1px solid #
|
|
84
|
+
const row: React.CSSProperties = { border: "1px solid rgba(45,74,62,.28)", borderRadius: 8, padding: "8px 12px", marginBottom: 6, background: "#fffdf2" };
|
|
85
|
+
const inp: React.CSSProperties = { padding: "8px 10px", borderRadius: 8, border: "1px solid rgba(45,74,62,.32)", background: "#fffdf2", color: "#2d4a3e" };
|
|
86
|
+
const btn: React.CSSProperties = { padding: "8px 14px", borderRadius: 8, border: "1px solid #742220", background: "#742220", color: "#fef9e1", cursor: "pointer" };
|
package/apps/web/server.ts
CHANGED
|
@@ -7,14 +7,19 @@ import next from "next";
|
|
|
7
7
|
import { attachGateway } from "./lib/ws/gateway.js";
|
|
8
8
|
import { startChannels } from "./lib/channels/index.js";
|
|
9
9
|
import { startScheduler } from "./lib/brain/scheduler.js";
|
|
10
|
+
import { runAllMigrations } from "./lib/db/migrate.js";
|
|
10
11
|
|
|
11
12
|
const dev = process.env.NODE_ENV !== "production";
|
|
12
13
|
const port = Number(process.env.PORT ?? 3000);
|
|
13
14
|
|
|
14
15
|
const app = next({ dev });
|
|
15
|
-
const handle = app.getRequestHandler();
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
async function start() {
|
|
18
|
+
// Never accept auth/API traffic before both application and Better Auth
|
|
19
|
+
// tables exist. This also makes service restarts repair missed migrations.
|
|
20
|
+
await runAllMigrations();
|
|
21
|
+
await app.prepare();
|
|
22
|
+
const handle = app.getRequestHandler();
|
|
18
23
|
const server = createServer((req, res) => handle(req, res));
|
|
19
24
|
attachGateway(server);
|
|
20
25
|
server.listen(port, async () => {
|
|
@@ -24,4 +29,9 @@ app.prepare().then(() => {
|
|
|
24
29
|
const jobs = startScheduler();
|
|
25
30
|
if (jobs.length) console.log(`scheduler: ${jobs.join(", ")}`);
|
|
26
31
|
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
start().catch((error) => {
|
|
35
|
+
console.error("MOP-AGENT failed to start:", error);
|
|
36
|
+
process.exit(1);
|
|
27
37
|
});
|
package/installer/mop-agent.mjs
CHANGED
|
@@ -229,13 +229,20 @@ function cmdUpdate() {
|
|
|
229
229
|
if (!printInstallLocations()) return;
|
|
230
230
|
console.log(c("bold", "Updating MOP-AGENT…\n"));
|
|
231
231
|
runSteps([
|
|
232
|
+
// Close SQLite cleanly before migration/rebuild. In particular, this
|
|
233
|
+
// prevents a root-run update leaving live WAL handles behind.
|
|
234
|
+
{ label: "Stop old service", cmd: "systemctl stop mop-agent", privileged: true, allowFailure: true },
|
|
232
235
|
...(!managedByNpx ? [{ label: "Pull latest", cmd: `cd ${q(APP_DIR)} && git pull --ff-only` }] : []),
|
|
233
236
|
{ label: "Install deps", cmd: `cd ${q(APP_DIR)} && npm ci` },
|
|
234
237
|
{ label: "Migrate SQLite", cmd: `cd ${q(`${APP_DIR}/apps/web`)} && npm run db:migrate` },
|
|
238
|
+
...(isRoot ? [{ label: "Repair runtime ownership", cmd: `chown -R mop-agent:mop-agent ${q(`${APP_DIR}/data`)}` }] : []),
|
|
239
|
+
{ label: "Remove stale Next.js build", cmd: `rm -rf ${q(`${APP_DIR}/apps/web/.next`)}` },
|
|
235
240
|
{ label: "Build", cmd: `cd ${q(`${APP_DIR}/apps/web`)} && npm run build` },
|
|
236
|
-
{ label: "
|
|
241
|
+
{ label: "Reload systemd", cmd: "systemctl daemon-reload", privileged: true },
|
|
242
|
+
{ label: "Start new service", cmd: "systemctl start mop-agent", privileged: true },
|
|
243
|
+
{ label: "Verify service", cmd: "sleep 2 && systemctl is-active --quiet mop-agent", privileged: true },
|
|
237
244
|
]);
|
|
238
|
-
console.log(c("green", "\n✓ updated\n"));
|
|
245
|
+
console.log(c("green", "\n✓ updated, rebuilt, and service verified\n"));
|
|
239
246
|
}
|
|
240
247
|
|
|
241
248
|
function cmdStatus() {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mop-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "mop-agent",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.5",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"packages/*",
|
package/package.json
CHANGED