@shahmilsaari/memory-core 1.0.30 → 1.0.32
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/dist/{chunk-OHVCPFEY.js → chunk-23GUWJ6F.js} +12 -2
- package/dist/{chunk-M7NKSXFS.js → chunk-GIPKVQSA.js} +113 -1
- package/dist/cli.js +95 -6
- package/dist/dashboard/assets/index-BFwqVRYO.js +2 -0
- package/dist/dashboard/assets/index-y7eHWJtq.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/{dashboard-server-OHVL64F2.js → dashboard-server-MD6NVL2F.js} +375 -3
- package/dist/{db-PRDHI2CN.js → db-FLFZZXG3.js} +23 -1
- package/package.json +11 -7
- package/dist/dashboard/assets/index-B4vD0HC8.css +0 -1
- package/dist/dashboard/assets/index-CesnieMi.js +0 -2
|
@@ -6,28 +6,79 @@ import {
|
|
|
6
6
|
embed,
|
|
7
7
|
inferProjectArchitectures,
|
|
8
8
|
startWatch
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-23GUWJ6F.js";
|
|
10
10
|
import {
|
|
11
11
|
getChatProviderLabel
|
|
12
12
|
} from "./chunk-PQBWHAZN.js";
|
|
13
13
|
import {
|
|
14
14
|
Config,
|
|
15
15
|
closePool,
|
|
16
|
+
countUsers,
|
|
17
|
+
createUser,
|
|
18
|
+
deleteArchProfile,
|
|
16
19
|
deleteMemory,
|
|
20
|
+
deleteUser,
|
|
21
|
+
getArchProfile,
|
|
17
22
|
getPool,
|
|
23
|
+
getUserByEmail,
|
|
24
|
+
listArchProfiles,
|
|
18
25
|
listMemories,
|
|
26
|
+
listUsers,
|
|
27
|
+
saveArchProfile,
|
|
19
28
|
saveMemory,
|
|
29
|
+
updateLastLogin,
|
|
20
30
|
updateMemory
|
|
21
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-GIPKVQSA.js";
|
|
22
32
|
import "./chunk-ZZBQEXEO.js";
|
|
23
33
|
|
|
24
34
|
// src/dashboard-server.ts
|
|
25
35
|
import { createHash } from "crypto";
|
|
36
|
+
import { execSync } from "child_process";
|
|
26
37
|
import { createReadStream, existsSync, mkdirSync, readFileSync, watch, writeFileSync } from "fs";
|
|
27
38
|
import { createServer } from "http";
|
|
28
39
|
import { basename, dirname, extname, join, normalize, relative, resolve } from "path";
|
|
29
40
|
import { fileURLToPath } from "url";
|
|
30
41
|
import chalk from "chalk";
|
|
42
|
+
|
|
43
|
+
// src/auth.ts
|
|
44
|
+
import jwt from "jsonwebtoken";
|
|
45
|
+
import bcrypt from "bcryptjs";
|
|
46
|
+
var BCRYPT_ROUNDS = 12;
|
|
47
|
+
var JWT_EXPIRY = "30d";
|
|
48
|
+
function getJwtSecret() {
|
|
49
|
+
const secret = process.env.MC_JWT_SECRET;
|
|
50
|
+
if (!secret) throw new Error("MC_JWT_SECRET not set. Add it to .memory-core.env.");
|
|
51
|
+
return secret;
|
|
52
|
+
}
|
|
53
|
+
function signToken(payload) {
|
|
54
|
+
return jwt.sign(payload, getJwtSecret(), { expiresIn: JWT_EXPIRY });
|
|
55
|
+
}
|
|
56
|
+
function verifyToken(token) {
|
|
57
|
+
return jwt.verify(token, getJwtSecret());
|
|
58
|
+
}
|
|
59
|
+
async function hashPassword(password) {
|
|
60
|
+
return bcrypt.hash(password, BCRYPT_ROUNDS);
|
|
61
|
+
}
|
|
62
|
+
async function comparePassword(password, hash) {
|
|
63
|
+
return bcrypt.compare(password, hash);
|
|
64
|
+
}
|
|
65
|
+
function extractBearerToken(authHeader) {
|
|
66
|
+
if (!authHeader?.startsWith("Bearer ")) return null;
|
|
67
|
+
return authHeader.slice(7).trim() || null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/dashboard-server.ts
|
|
71
|
+
var authEnabled = false;
|
|
72
|
+
function requireAuth(req) {
|
|
73
|
+
if (!authEnabled) return { userId: 0, email: "local", username: "local", role: "admin", teamName: null };
|
|
74
|
+
const token = extractBearerToken(req.headers["authorization"]);
|
|
75
|
+
if (!token) return null;
|
|
76
|
+
try {
|
|
77
|
+
return verifyToken(token);
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
31
82
|
var clients = /* @__PURE__ */ new Set();
|
|
32
83
|
var fileStatuses = /* @__PURE__ */ new Map();
|
|
33
84
|
var recentEvents = [];
|
|
@@ -74,12 +125,20 @@ function readViolationHistory() {
|
|
|
74
125
|
return [];
|
|
75
126
|
}
|
|
76
127
|
}
|
|
128
|
+
function getGitAuthor(file) {
|
|
129
|
+
try {
|
|
130
|
+
return execSync(`git log --format="%an" -1 -- "${file}"`, { cwd: projectRoot, stdio: ["ignore", "pipe", "ignore"], timeout: 2e3 }).toString().trim() || void 0;
|
|
131
|
+
} catch {
|
|
132
|
+
return void 0;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
77
135
|
function appendViolationHistory(file, violations, timestamp) {
|
|
78
136
|
try {
|
|
79
137
|
const archmindDir = join(projectRoot, ".archmind");
|
|
80
138
|
mkdirSync(archmindDir, { recursive: true });
|
|
81
139
|
const history = readViolationHistory();
|
|
82
|
-
|
|
140
|
+
const author = getGitAuthor(file);
|
|
141
|
+
history.unshift({ timestamp, file, violations, author });
|
|
83
142
|
if (history.length > HISTORY_MAX) history.length = HISTORY_MAX;
|
|
84
143
|
writeFileSync(historyPath(), JSON.stringify(history, null, 2), "utf-8");
|
|
85
144
|
} catch {
|
|
@@ -680,6 +739,117 @@ async function handleApi(req, res, url) {
|
|
|
680
739
|
sendJson(res, 200, { ok: true, message: "Tune started" });
|
|
681
740
|
return;
|
|
682
741
|
}
|
|
742
|
+
if (req.method === "GET" && url.pathname === "/api/analytics") {
|
|
743
|
+
const history = readViolationHistory();
|
|
744
|
+
const archRules = archState.rules;
|
|
745
|
+
const now = Date.now();
|
|
746
|
+
const DAY = 864e5;
|
|
747
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
748
|
+
for (let i = 29; i >= 0; i--) {
|
|
749
|
+
const d = new Date(now - i * DAY).toISOString().slice(0, 10);
|
|
750
|
+
buckets.set(d, { violations: 0, files: /* @__PURE__ */ new Set() });
|
|
751
|
+
}
|
|
752
|
+
for (const entry of history) {
|
|
753
|
+
const d = entry.timestamp.slice(0, 10);
|
|
754
|
+
if (buckets.has(d)) {
|
|
755
|
+
const b = buckets.get(d);
|
|
756
|
+
b.violations += entry.violations.length;
|
|
757
|
+
b.files.add(entry.file);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const trends = Array.from(buckets.entries()).map(([date, b]) => ({ date, violations: b.violations, files: b.files.size }));
|
|
761
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
762
|
+
for (const entry of history) {
|
|
763
|
+
const existing = fileMap.get(entry.file) ?? { count: 0, lastViolation: "", resolved: 0 };
|
|
764
|
+
existing.count += entry.violations.length;
|
|
765
|
+
if (!existing.lastViolation || entry.timestamp > existing.lastViolation) existing.lastViolation = entry.timestamp;
|
|
766
|
+
if (entry.resolved) existing.resolved++;
|
|
767
|
+
fileMap.set(entry.file, existing);
|
|
768
|
+
}
|
|
769
|
+
const fileHeat = Array.from(fileMap.entries()).map(([file, v]) => ({ file, ...v })).sort((a, b) => b.count - a.count).slice(0, 20);
|
|
770
|
+
const ruleHits = /* @__PURE__ */ new Map();
|
|
771
|
+
for (const entry of history) {
|
|
772
|
+
for (const v of entry.violations) {
|
|
773
|
+
const key = v.rule ?? "";
|
|
774
|
+
ruleHits.set(key, (ruleHits.get(key) ?? 0) + 1);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
const statsRules = (() => {
|
|
778
|
+
try {
|
|
779
|
+
return readJsonFile(join(projectRoot, ".memory-core-stats.json"), {}).rules ?? {};
|
|
780
|
+
} catch {
|
|
781
|
+
return {};
|
|
782
|
+
}
|
|
783
|
+
})();
|
|
784
|
+
const ruleEffectiveness = archRules.map((r) => ({
|
|
785
|
+
id: r.id,
|
|
786
|
+
name: r.name,
|
|
787
|
+
fromLayer: r.fromLayer,
|
|
788
|
+
toLayer: r.toLayer,
|
|
789
|
+
allowed: r.allowed,
|
|
790
|
+
enforcement: r.enforcement,
|
|
791
|
+
hitCount: (statsRules[r.id] ?? 0) + (ruleHits.get(r.name) ?? 0),
|
|
792
|
+
lastFired: history.find((e) => e.violations.some((v) => v.rule === r.name || v.rule === r.id))?.timestamp ?? null
|
|
793
|
+
})).sort((a, b) => b.hitCount - a.hitCount);
|
|
794
|
+
const authorMap = /* @__PURE__ */ new Map();
|
|
795
|
+
for (const entry of history) {
|
|
796
|
+
const author = entry.author ?? "Unknown";
|
|
797
|
+
const existing = authorMap.get(author) ?? { violations: 0, files: /* @__PURE__ */ new Set(), lastSeen: "" };
|
|
798
|
+
existing.violations += entry.violations.length;
|
|
799
|
+
existing.files.add(entry.file);
|
|
800
|
+
if (!existing.lastSeen || entry.timestamp > existing.lastSeen) existing.lastSeen = entry.timestamp;
|
|
801
|
+
authorMap.set(author, existing);
|
|
802
|
+
}
|
|
803
|
+
const teamActivity = Array.from(authorMap.entries()).map(([author, v]) => ({ author, violations: v.violations, files: v.files.size, lastSeen: v.lastSeen })).sort((a, b) => b.violations - a.violations);
|
|
804
|
+
sendJson(res, 200, {
|
|
805
|
+
trends,
|
|
806
|
+
fileHeat,
|
|
807
|
+
ruleEffectiveness,
|
|
808
|
+
teamActivity,
|
|
809
|
+
summary: {
|
|
810
|
+
total: history.reduce((s, e) => s + e.violations.length, 0),
|
|
811
|
+
resolved: history.filter((e) => e.resolved).length,
|
|
812
|
+
open: history.filter((e) => !e.resolved).length,
|
|
813
|
+
uniqueFiles: new Set(history.map((e) => e.file)).size
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (req.method === "DELETE" && url.pathname === "/api/history") {
|
|
819
|
+
try {
|
|
820
|
+
const path = historyPath();
|
|
821
|
+
if (existsSync(path)) writeFileSync(path, "[]", "utf-8");
|
|
822
|
+
scheduleSnapshotBroadcast();
|
|
823
|
+
sendJson(res, 200, { ok: true });
|
|
824
|
+
} catch (err) {
|
|
825
|
+
sendJson(res, 500, { error: err.message });
|
|
826
|
+
}
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (req.method === "POST" && url.pathname === "/api/history/resolve") {
|
|
830
|
+
const body = await readBody(req);
|
|
831
|
+
const timestamp = typeof body.timestamp === "string" ? body.timestamp : "";
|
|
832
|
+
if (!timestamp) {
|
|
833
|
+
sendJson(res, 400, { error: "timestamp required" });
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
try {
|
|
837
|
+
const history = readViolationHistory();
|
|
838
|
+
const entry = history.find((e) => e.timestamp === timestamp);
|
|
839
|
+
if (!entry) {
|
|
840
|
+
sendJson(res, 404, { error: "Entry not found" });
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
entry.resolved = !entry.resolved;
|
|
844
|
+
entry.resolvedAt = entry.resolved ? (/* @__PURE__ */ new Date()).toISOString() : void 0;
|
|
845
|
+
writeFileSync(historyPath(), JSON.stringify(history, null, 2), "utf-8");
|
|
846
|
+
scheduleSnapshotBroadcast();
|
|
847
|
+
sendJson(res, 200, { ok: true, resolved: entry.resolved });
|
|
848
|
+
} catch (err) {
|
|
849
|
+
sendJson(res, 500, { error: err.message });
|
|
850
|
+
}
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
683
853
|
if (req.method === "POST" && url.pathname === "/api/arch/layers") {
|
|
684
854
|
const body = await readBody(req);
|
|
685
855
|
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
@@ -774,6 +944,207 @@ async function handleApi(req, res, url) {
|
|
|
774
944
|
sendJson(res, 200, { ok: true, arch });
|
|
775
945
|
return;
|
|
776
946
|
}
|
|
947
|
+
if (req.method === "GET" && url.pathname === "/api/arch/profiles") {
|
|
948
|
+
try {
|
|
949
|
+
const projectConfig = readProjectConfig();
|
|
950
|
+
const profiles = await listArchProfiles(projectConfig?.projectName ?? void 0);
|
|
951
|
+
sendJson(res, 200, { profiles });
|
|
952
|
+
} catch {
|
|
953
|
+
sendJson(res, 200, { profiles: [] });
|
|
954
|
+
}
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (req.method === "POST" && url.pathname === "/api/arch/profiles") {
|
|
958
|
+
const body = await readBody(req);
|
|
959
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
960
|
+
if (!name) {
|
|
961
|
+
sendJson(res, 400, { error: "name is required" });
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const projectConfig = readProjectConfig();
|
|
965
|
+
const arch = projectConfig?.backendArchitecture ?? projectConfig?.frontendFramework ?? "custom";
|
|
966
|
+
const layers = archState.layers;
|
|
967
|
+
const rules = archState.rules;
|
|
968
|
+
try {
|
|
969
|
+
const profile = await saveArchProfile({
|
|
970
|
+
name,
|
|
971
|
+
archType: arch,
|
|
972
|
+
layers,
|
|
973
|
+
rules,
|
|
974
|
+
projectName: typeof body.projectScoped === "boolean" && body.projectScoped ? projectConfig?.projectName ?? void 0 : void 0
|
|
975
|
+
});
|
|
976
|
+
sendJson(res, 201, { ok: true, profile });
|
|
977
|
+
} catch (err) {
|
|
978
|
+
sendJson(res, 500, { error: err.message });
|
|
979
|
+
}
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
const profileMatch = url.pathname.match(/^\/api\/arch\/profiles\/(\d+)$/);
|
|
983
|
+
if (profileMatch) {
|
|
984
|
+
const profileId = parseInt(profileMatch[1], 10);
|
|
985
|
+
if (req.method === "DELETE") {
|
|
986
|
+
try {
|
|
987
|
+
const deleted = await deleteArchProfile(profileId);
|
|
988
|
+
sendJson(res, deleted ? 200 : 404, deleted ? { ok: true } : { error: "Profile not found" });
|
|
989
|
+
} catch (err) {
|
|
990
|
+
sendJson(res, 500, { error: err.message });
|
|
991
|
+
}
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (req.method === "POST" && url.pathname.endsWith("/apply")) {
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
const profileApplyMatch = url.pathname.match(/^\/api\/arch\/profiles\/(\d+)\/apply$/);
|
|
998
|
+
if (profileApplyMatch && req.method === "POST") {
|
|
999
|
+
const profileId = parseInt(profileApplyMatch[1], 10);
|
|
1000
|
+
try {
|
|
1001
|
+
const profile = await getArchProfile(profileId);
|
|
1002
|
+
if (!profile) {
|
|
1003
|
+
sendJson(res, 404, { error: "Profile not found" });
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
const archmindDir = join(projectRoot, ".archmind");
|
|
1007
|
+
mkdirSync(archmindDir, { recursive: true });
|
|
1008
|
+
writeFileSync(join(archmindDir, "layers.json"), JSON.stringify({ layers: profile.layers }, null, 2) + "\n", "utf-8");
|
|
1009
|
+
writeFileSync(join(archmindDir, "rules.json"), JSON.stringify({ rules: profile.rules }, null, 2) + "\n", "utf-8");
|
|
1010
|
+
readArchState();
|
|
1011
|
+
scheduleSnapshotBroadcast();
|
|
1012
|
+
sendJson(res, 200, { ok: true, profile });
|
|
1013
|
+
} catch (err) {
|
|
1014
|
+
sendJson(res, 500, { error: err.message });
|
|
1015
|
+
}
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
if (req.method === "POST" && url.pathname === "/api/auth/setup") {
|
|
1019
|
+
if (!authEnabled) {
|
|
1020
|
+
sendJson(res, 403, { error: "Auth not enabled. Start dashboard with --auth flag." });
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
const count = await countUsers();
|
|
1024
|
+
if (count > 0) {
|
|
1025
|
+
sendJson(res, 409, { error: "Admin already exists. Use /api/auth/login." });
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const body = await readBody(req);
|
|
1029
|
+
const email = typeof body.email === "string" ? body.email.trim() : "";
|
|
1030
|
+
const username = typeof body.username === "string" ? body.username.trim() : "";
|
|
1031
|
+
const password = typeof body.password === "string" ? body.password : "";
|
|
1032
|
+
const teamName = typeof body.teamName === "string" ? body.teamName.trim() : void 0;
|
|
1033
|
+
if (!email || !username || password.length < 8) {
|
|
1034
|
+
sendJson(res, 400, { error: "email, username, and password (min 8 chars) required" });
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
const passwordHash = await hashPassword(password);
|
|
1038
|
+
const user = await createUser({ email, username, passwordHash, role: "admin", teamName });
|
|
1039
|
+
await updateLastLogin(user.id);
|
|
1040
|
+
const token = signToken({ userId: user.id, email: user.email, username: user.username, role: user.role, teamName: user.team_name });
|
|
1041
|
+
sendJson(res, 201, { token, user: { id: user.id, email: user.email, username: user.username, role: user.role, teamName: user.team_name } });
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
if (req.method === "POST" && url.pathname === "/api/auth/login") {
|
|
1045
|
+
if (!authEnabled) {
|
|
1046
|
+
sendJson(res, 403, { error: "Auth not enabled." });
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
const body = await readBody(req);
|
|
1050
|
+
const email = typeof body.email === "string" ? body.email.trim() : "";
|
|
1051
|
+
const password = typeof body.password === "string" ? body.password : "";
|
|
1052
|
+
if (!email || !password) {
|
|
1053
|
+
sendJson(res, 400, { error: "email and password required" });
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
const user = await getUserByEmail(email);
|
|
1057
|
+
if (!user) {
|
|
1058
|
+
sendJson(res, 401, { error: "Invalid credentials" });
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
const ok = await comparePassword(password, user.password_hash);
|
|
1062
|
+
if (!ok) {
|
|
1063
|
+
sendJson(res, 401, { error: "Invalid credentials" });
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
await updateLastLogin(user.id);
|
|
1067
|
+
const token = signToken({ userId: user.id, email: user.email, username: user.username, role: user.role, teamName: user.team_name });
|
|
1068
|
+
sendJson(res, 200, { token, user: { id: user.id, email: user.email, username: user.username, role: user.role, teamName: user.team_name } });
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
if (req.method === "GET" && url.pathname === "/api/auth/me") {
|
|
1072
|
+
const payload = requireAuth(req);
|
|
1073
|
+
if (!payload) {
|
|
1074
|
+
sendJson(res, 401, { error: "Unauthorized" });
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
sendJson(res, 200, { user: { userId: payload.userId, email: payload.email, username: payload.username, role: payload.role, teamName: payload.teamName }, authEnabled });
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
if (req.method === "POST" && url.pathname === "/api/auth/logout") {
|
|
1081
|
+
sendJson(res, 200, { ok: true });
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
if (req.method === "GET" && url.pathname === "/api/users") {
|
|
1085
|
+
const payload = requireAuth(req);
|
|
1086
|
+
if (!payload) {
|
|
1087
|
+
sendJson(res, 401, { error: "Unauthorized" });
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
if (payload.role !== "admin") {
|
|
1091
|
+
sendJson(res, 403, { error: "Admin only" });
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const users = await listUsers();
|
|
1095
|
+
sendJson(res, 200, { users });
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
if (req.method === "POST" && url.pathname === "/api/users") {
|
|
1099
|
+
const payload = requireAuth(req);
|
|
1100
|
+
if (!payload) {
|
|
1101
|
+
sendJson(res, 401, { error: "Unauthorized" });
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
if (payload.role !== "admin") {
|
|
1105
|
+
sendJson(res, 403, { error: "Admin only" });
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
const body = await readBody(req);
|
|
1109
|
+
const email = typeof body.email === "string" ? body.email.trim() : "";
|
|
1110
|
+
const username = typeof body.username === "string" ? body.username.trim() : "";
|
|
1111
|
+
const password = typeof body.password === "string" ? body.password : "";
|
|
1112
|
+
const role = body.role === "admin" ? "admin" : "viewer";
|
|
1113
|
+
const teamName = typeof body.teamName === "string" ? body.teamName.trim() : void 0;
|
|
1114
|
+
if (!email || !username || password.length < 8) {
|
|
1115
|
+
sendJson(res, 400, { error: "email, username, and password (min 8 chars) required" });
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const passwordHash = await hashPassword(password);
|
|
1119
|
+
try {
|
|
1120
|
+
const user = await createUser({ email, username, passwordHash, role, teamName });
|
|
1121
|
+
sendJson(res, 201, { user: { id: user.id, email: user.email, username: user.username, role: user.role, teamName: user.team_name } });
|
|
1122
|
+
} catch (err) {
|
|
1123
|
+
const msg = err.message;
|
|
1124
|
+
sendJson(res, 409, { error: msg.includes("unique") ? "Email or username already exists" : msg });
|
|
1125
|
+
}
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
const userMatch = url.pathname.match(/^\/api\/users\/(\d+)$/);
|
|
1129
|
+
if (userMatch && req.method === "DELETE") {
|
|
1130
|
+
const payload = requireAuth(req);
|
|
1131
|
+
if (!payload) {
|
|
1132
|
+
sendJson(res, 401, { error: "Unauthorized" });
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
if (payload.role !== "admin") {
|
|
1136
|
+
sendJson(res, 403, { error: "Admin only" });
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const uid = parseInt(userMatch[1], 10);
|
|
1140
|
+
if (uid === payload.userId) {
|
|
1141
|
+
sendJson(res, 400, { error: "Cannot delete yourself" });
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const deleted = await deleteUser(uid);
|
|
1145
|
+
sendJson(res, deleted ? 200 : 404, deleted ? { ok: true } : { error: "User not found" });
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
777
1148
|
sendJson(res, 404, { error: "Not found" });
|
|
778
1149
|
} catch (err) {
|
|
779
1150
|
sendJson(res, 500, { error: err.message });
|
|
@@ -1036,6 +1407,7 @@ function startConfigWatch() {
|
|
|
1036
1407
|
}
|
|
1037
1408
|
async function startDashboard(options = {}) {
|
|
1038
1409
|
projectRoot = resolveDashboardProjectRoot(options.path);
|
|
1410
|
+
authEnabled = options.auth ?? false;
|
|
1039
1411
|
reloadRuntimeEnv();
|
|
1040
1412
|
const port = options.port ?? 5178;
|
|
1041
1413
|
const stopConfigWatch = startConfigWatch();
|
|
@@ -1,29 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
closePool,
|
|
4
|
+
countUsers,
|
|
5
|
+
createUser,
|
|
6
|
+
deleteArchProfile,
|
|
4
7
|
deleteMemories,
|
|
5
8
|
deleteMemory,
|
|
9
|
+
deleteUser,
|
|
10
|
+
getArchProfile,
|
|
6
11
|
getMemory,
|
|
7
12
|
getPool,
|
|
13
|
+
getUserByEmail,
|
|
14
|
+
getUserById,
|
|
8
15
|
hashMemoryContent,
|
|
16
|
+
listArchProfiles,
|
|
9
17
|
listMemories,
|
|
18
|
+
listUsers,
|
|
10
19
|
runMigrations,
|
|
20
|
+
saveArchProfile,
|
|
11
21
|
saveMemory,
|
|
12
22
|
searchMemories,
|
|
23
|
+
updateLastLogin,
|
|
13
24
|
updateMemory,
|
|
14
25
|
upsertMemory
|
|
15
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-GIPKVQSA.js";
|
|
16
27
|
export {
|
|
17
28
|
closePool,
|
|
29
|
+
countUsers,
|
|
30
|
+
createUser,
|
|
31
|
+
deleteArchProfile,
|
|
18
32
|
deleteMemories,
|
|
19
33
|
deleteMemory,
|
|
34
|
+
deleteUser,
|
|
35
|
+
getArchProfile,
|
|
20
36
|
getMemory,
|
|
21
37
|
getPool,
|
|
38
|
+
getUserByEmail,
|
|
39
|
+
getUserById,
|
|
22
40
|
hashMemoryContent,
|
|
41
|
+
listArchProfiles,
|
|
23
42
|
listMemories,
|
|
43
|
+
listUsers,
|
|
24
44
|
runMigrations,
|
|
45
|
+
saveArchProfile,
|
|
25
46
|
saveMemory,
|
|
26
47
|
searchMemories,
|
|
48
|
+
updateLastLogin,
|
|
27
49
|
updateMemory,
|
|
28
50
|
upsertMemory
|
|
29
51
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shahmilsaari/memory-core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"description": "Universal AI memory core — generate AI context files from architecture profiles with RAG support",
|
|
5
5
|
"homepage": "https://memory-core.shahmilsaari.my/",
|
|
6
6
|
"type": "module",
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
"release:patch": "npm version patch --no-git-tag-version && npm run build && npm publish --access public",
|
|
18
18
|
"release:minor": "npm version minor --no-git-tag-version && npm run build && npm publish --access public",
|
|
19
19
|
"release:major": "npm version major --no-git-tag-version && npm run build && npm publish --access public",
|
|
20
|
-
"docs:start": "npm run
|
|
21
|
-
"docs:build": "
|
|
22
|
-
"docs:serve": "npm run
|
|
23
|
-
"site:build": "npm run
|
|
24
|
-
"site:serve": "
|
|
25
|
-
"site:start": "npm
|
|
20
|
+
"docs:start": "npm --prefix website run start",
|
|
21
|
+
"docs:build": "npm --prefix website run build",
|
|
22
|
+
"docs:serve": "npm --prefix website run serve",
|
|
23
|
+
"site:build": "npm --prefix website run build",
|
|
24
|
+
"site:serve": "npm --prefix website run serve",
|
|
25
|
+
"site:start": "npm --prefix website run start",
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
27
27
|
"lint": "node scripts/lint.mjs",
|
|
28
28
|
"smoke:pack": "node scripts/pack-smoke.mjs",
|
|
@@ -33,18 +33,22 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@inquirer/prompts": "^5.0.0",
|
|
36
|
+
"bcryptjs": "^3.0.3",
|
|
36
37
|
"chalk": "^5.3.0",
|
|
37
38
|
"chokidar": "^5.0.0",
|
|
38
39
|
"commander": "^12.0.0",
|
|
39
40
|
"dotenv": "^16.4.0",
|
|
40
41
|
"handlebars": "^4.7.8",
|
|
41
42
|
"js-yaml": "^4.1.0",
|
|
43
|
+
"jsonwebtoken": "^9.0.3",
|
|
42
44
|
"ora": "^8.0.0",
|
|
43
45
|
"pg": "^8.11.0"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
46
48
|
"@sveltejs/vite-plugin-svelte": "^4.0.4",
|
|
49
|
+
"@types/bcryptjs": "^2.4.6",
|
|
47
50
|
"@types/js-yaml": "^4.0.9",
|
|
51
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
48
52
|
"@types/node": "^20.0.0",
|
|
49
53
|
"@types/pg": "^8.11.0",
|
|
50
54
|
"svelte": "^5.55.5",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{color-scheme:light dark;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}body{margin:0;min-width:320px;min-height:100vh}button,input,select,textarea{font:inherit}button{border:0}:root{--primary: #0040e0;--primary-dim: #2e5bff;--primary-container: #efefff;--on-primary: #ffffff;--surface: #f8f9fb;--surface-low: #f3f4f6;--surface-mid: #edeef0;--surface-high: #e7e8ea;--surface-highest: #e1e2e4;--on-surface: #191c1e;--on-surface-var: #434656;--outline: #747688;--outline-var: #c4c5d9;--error: #ba1a1a;--error-bg: #ffdad6;--tertiary: #993100;--green: #10b981;--green-bg: rgba(16,185,129,.1);--green-border: rgba(16,185,129,.3);--mono: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;--sans: "Hanken Grotesk", Inter, ui-sans-serif, system-ui, sans-serif}body{margin:0;background:var(--surface);color:var(--on-surface);font-family:var(--sans);font-size:14px;overflow:hidden;height:100vh}*{box-sizing:border-box}::-webkit-scrollbar{width:5px;height:5px}::-webkit-scrollbar-track{background:var(--surface-low)}::-webkit-scrollbar-thumb{background:var(--outline-var);border-radius:4px}.shell.svelte-d3ct2b{display:flex;flex-direction:column;height:100vh;overflow:hidden}.topbar.svelte-d3ct2b{display:flex;align-items:center;gap:16px;height:64px;padding:0 24px;background:var(--surface);border-bottom:1px solid var(--outline-var);flex-shrink:0;z-index:30}.topbar-brand.svelte-d3ct2b{display:flex;align-items:center;gap:24px;flex-shrink:0}.brand-name.svelte-d3ct2b{font-size:18px;font-weight:700;color:var(--on-surface);font-family:var(--sans);letter-spacing:-.02em}.topbar-nav.svelte-d3ct2b{display:flex;gap:2px}.tab-btn.svelte-d3ct2b{padding:6px 12px;background:transparent;border:none;border-bottom:2px solid transparent;color:var(--on-surface-var);font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.04em;cursor:pointer;text-transform:uppercase;transition:color .15s,border-color .15s}.tab-btn.svelte-d3ct2b:hover{color:var(--primary)}.tab-active.svelte-d3ct2b{color:var(--primary)!important;border-bottom-color:var(--primary);font-weight:700}.topbar-search.svelte-d3ct2b{flex:1;max-width:480px;margin:0 auto;position:relative}.search-icon.svelte-d3ct2b{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--on-surface-var);font-size:18px;pointer-events:none;line-height:1}.search-input.svelte-d3ct2b{width:100%;height:36px;padding:0 12px 0 36px;background:var(--surface-mid);border:1px solid var(--outline-var);border-radius:8px;color:var(--on-surface);font-family:var(--sans);font-size:13px;outline:none;transition:border-color .15s}.search-input.svelte-d3ct2b:focus{border-color:var(--primary-dim)}.topbar-actions.svelte-d3ct2b{display:flex;align-items:center;gap:4px;flex-shrink:0}.icon-btn.svelte-d3ct2b{width:34px;height:34px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;border-radius:8px;color:var(--on-surface-var);cursor:pointer;transition:background .15s,color .15s}.icon-btn.svelte-d3ct2b:hover{background:var(--surface-low);color:var(--primary)}.icon-btn-active.svelte-d3ct2b{color:var(--primary)!important;background:var(--primary-container)!important}.avatar.svelte-d3ct2b{width:32px;height:32px;border-radius:50%;background:var(--primary-dim);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;margin-left:8px;border:1px solid var(--outline-var);flex-shrink:0;cursor:default}.body.svelte-d3ct2b{display:flex;flex:1;min-height:0;overflow:hidden}.sidebar.svelte-d3ct2b{width:64px;flex-shrink:0;display:flex;flex-direction:column;align-items:center;padding:16px 0;background:#fff;border-right:1px solid var(--outline-var);z-index:20}.sidebar-logo.svelte-d3ct2b{width:40px;height:40px;background:var(--primary-dim);border-radius:4px;display:flex;align-items:center;justify-content:center;color:#fff;margin-bottom:24px}.sidebar-nav.svelte-d3ct2b{display:flex;flex-direction:column;gap:8px}.side-btn.svelte-d3ct2b{width:36px;height:36px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;border-right:2px solid transparent;color:var(--on-surface-var);cursor:pointer;transition:color .15s;margin-right:-1px}.side-btn.svelte-d3ct2b:hover{color:var(--primary)}.side-btn-active.svelte-d3ct2b{color:var(--primary)!important;background:#0040e014;border-right-color:var(--primary)}.sidebar-footer-dot.svelte-d3ct2b{margin-top:auto;width:8px;height:8px;border-radius:50%;background:var(--outline)}.dot-live.svelte-d3ct2b{background:var(--green)!important;box-shadow:0 0 6px var(--green)}.content.svelte-d3ct2b{flex:1;min-width:0;display:flex;flex-direction:column;overflow:hidden;background:var(--surface)}.health-bar.svelte-d3ct2b{display:flex;align-items:center;gap:8px;padding:6px 24px;border-bottom:1px solid var(--outline-var);background:#fff;flex-shrink:0;flex-wrap:wrap}.health-label.svelte-d3ct2b{font-family:var(--mono);font-size:10px;font-weight:700;color:var(--outline);text-transform:uppercase;letter-spacing:.06em;margin-right:4px}.health-badge.svelte-d3ct2b{display:flex;align-items:center;gap:4px;padding:2px 8px;border-radius:4px;font-family:var(--mono);font-size:10px;font-weight:700}.health-green.svelte-d3ct2b{background:var(--green-bg);color:var(--green);border:1px solid var(--green-border)}.health-blue.svelte-d3ct2b{background:#0040e014;color:var(--primary);border:1px solid rgba(0,64,224,.2)}.health-warn.svelte-d3ct2b{background:#ba1a1a14;color:var(--error);border:1px solid rgba(186,26,26,.25)}.health-error.svelte-d3ct2b{font-family:var(--mono);font-size:10px;color:var(--error);font-weight:700}.dot.svelte-d3ct2b{width:6px;height:6px;border-radius:50%;flex-shrink:0}.dot-green.svelte-d3ct2b{background:var(--green)}.dot-blue.svelte-d3ct2b{background:var(--primary)}.dot-warn.svelte-d3ct2b{background:var(--error)}.dot-pulse.svelte-d3ct2b{animation:svelte-d3ct2b-pulse 1.4s ease-in-out infinite}@keyframes svelte-d3ct2b-pulse{0%,to{opacity:1}50%{opacity:.35}}.filter-bar.svelte-d3ct2b{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 24px;background:var(--surface-low);border-bottom:1px solid var(--outline-var);flex-shrink:0}.filter-left.svelte-d3ct2b,.filter-right.svelte-d3ct2b{display:flex;align-items:center;gap:8px}.chip.svelte-d3ct2b{display:flex;align-items:center;gap:4px;padding:4px 10px;border-radius:8px;font-family:var(--mono);font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap;border:none}.chip-primary.svelte-d3ct2b{background:var(--primary-dim);color:#fff}.chip-close.svelte-d3ct2b{background:transparent;border:none;color:#fffc;cursor:pointer;padding:0;font-size:10px;line-height:1}.chip-outline.svelte-d3ct2b{background:var(--surface);color:var(--on-surface-var);border:1px solid var(--outline-var);transition:border-color .15s}.chip-outline.svelte-d3ct2b:hover{border-color:var(--primary-dim)}.chevron.svelte-d3ct2b{transition:transform .2s}.chevron-open.svelte-d3ct2b{transform:rotate(180deg)}.service-dropdown-wrap.svelte-d3ct2b{position:relative}.dropdown.svelte-d3ct2b{position:absolute;top:calc(100% + 4px);left:0;min-width:180px;background:#fff;border:1px solid var(--outline-var);border-radius:8px;box-shadow:0 8px 24px #0000001f;z-index:100;overflow:hidden;animation:svelte-d3ct2b-dropIn .12s ease}@keyframes svelte-d3ct2b-dropIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.dropdown-item.svelte-d3ct2b{display:flex;align-items:center;gap:8px;width:100%;padding:10px 14px;background:transparent;border:none;color:var(--on-surface-var);font-family:var(--sans);font-size:13px;font-weight:500;cursor:pointer;transition:background .12s;text-align:left}.dropdown-item.svelte-d3ct2b:hover{background:var(--surface-low)}.dropdown-item-active.svelte-d3ct2b{color:var(--primary);background:var(--primary-container)}.dropdown-item-active.svelte-d3ct2b:hover{background:var(--primary-container)}.dropdown-badge.svelte-d3ct2b{margin-left:auto;background:var(--error);color:#fff;border-radius:999px;padding:1px 6px;font-size:10px;font-weight:700}.divider-v.svelte-d3ct2b{width:1px;height:28px;background:var(--outline-var);margin:0 4px}.metrics-strip.svelte-d3ct2b{display:flex;align-items:center;gap:24px}.metric-item.svelte-d3ct2b{display:flex;flex-direction:column;align-items:center}.metric-label.svelte-d3ct2b{font-family:var(--mono);font-size:9px;font-weight:700;color:var(--outline);text-transform:uppercase;letter-spacing:.05em;line-height:1}.metric-value.svelte-d3ct2b{font-family:var(--mono);font-size:14px;font-weight:700;color:var(--on-surface);line-height:1.4}.metric-value-warn.svelte-d3ct2b{color:var(--error)}.live-badge.svelte-d3ct2b{display:flex;align-items:center;gap:6px;padding:4px 10px;border:1px solid var(--outline-var);border-radius:8px;background:#fff;font-family:var(--mono);font-size:11px;font-weight:600;color:var(--on-surface);white-space:nowrap}.feed-area.svelte-d3ct2b{flex:1;min-height:0;overflow-y:auto;padding:12px 24px;background:#fff;font-family:var(--mono);font-size:12px}.log-row.svelte-d3ct2b{display:flex;align-items:baseline;gap:12px;padding:3px 8px;border-radius:4px;transition:background .1s;min-height:22px}.log-row.svelte-d3ct2b:hover{background:var(--surface-low)}.log-row-warn.svelte-d3ct2b{background:#ba1a1a0a}.log-row-fail.svelte-d3ct2b{background:#9931000a}.log-time.svelte-d3ct2b{width:80px;flex-shrink:0;color:var(--outline)}.log-label.svelte-d3ct2b{width:56px;flex-shrink:0;font-weight:700}.label-sys.svelte-d3ct2b{color:var(--primary)}.label-ok.svelte-d3ct2b{color:var(--green)}.label-warn.svelte-d3ct2b{color:var(--tertiary)}.label-info.svelte-d3ct2b{color:var(--on-surface-var)}.label-hook.svelte-d3ct2b{color:var(--error)}.label-chck.svelte-d3ct2b{color:var(--outline)}.log-msg.svelte-d3ct2b{flex:1;min-width:0;color:var(--on-surface-var);word-break:break-all;line-height:1.5}.log-msg.svelte-d3ct2b strong:where(.svelte-d3ct2b){color:var(--on-surface);font-weight:600}.copy-btn.svelte-d3ct2b{opacity:0;background:transparent;border:none;color:var(--outline);cursor:pointer;font-size:14px;padding:0 4px;transition:opacity .15s,color .15s;flex-shrink:0}.log-row.svelte-d3ct2b:hover .copy-btn:where(.svelte-d3ct2b){opacity:1}.copy-btn.svelte-d3ct2b:hover{color:var(--primary)}.log-detail.svelte-d3ct2b{display:flex;gap:8px;padding:3px 8px 3px 148px;font-size:11px;border-left:2px solid rgba(153,49,0,.3);margin-left:8px;margin-bottom:2px}.detail-index.svelte-d3ct2b{color:var(--outline);flex-shrink:0;width:24px}.detail-body.svelte-d3ct2b{color:var(--on-surface-var);flex:1;min-width:0;line-height:1.5}.detail-fix.svelte-d3ct2b{color:var(--primary)}.cursor-blink.svelte-d3ct2b{animation:svelte-d3ct2b-blink 1s step-start infinite}@keyframes svelte-d3ct2b-blink{0%,to{opacity:1}50%{opacity:0}}.section-header.svelte-d3ct2b{display:flex;align-items:baseline;gap:12px;padding:8px 8px 10px;border-bottom:1px solid var(--outline-var);margin-bottom:8px}.section-title.svelte-d3ct2b{font-family:var(--sans);font-size:14px;font-weight:700;color:var(--on-surface)}.section-meta.svelte-d3ct2b{font-size:11px;color:var(--outline);font-family:var(--mono)}.rules-toolbar.svelte-d3ct2b{display:flex;gap:8px;padding:8px;margin-bottom:4px}.rules-select.svelte-d3ct2b,.rules-input.svelte-d3ct2b,.rules-textarea.svelte-d3ct2b{padding:6px 10px;background:var(--surface-low);border:1px solid var(--outline-var);border-radius:6px;color:var(--on-surface);font-family:var(--sans);font-size:13px;outline:none;transition:border-color .15s}.rules-select.svelte-d3ct2b:focus,.rules-input.svelte-d3ct2b:focus,.rules-textarea.svelte-d3ct2b:focus{border-color:var(--primary-dim)}.rules-textarea.svelte-d3ct2b{resize:vertical;flex:1;min-width:0}.rule-form-wrap.svelte-d3ct2b{padding:0 8px 8px;border-bottom:1px solid var(--outline-var);margin-bottom:8px}.rule-form.svelte-d3ct2b{display:flex;flex-direction:column;gap:8px}.rule-form-row.svelte-d3ct2b{display:flex;gap:8px;align-items:flex-start}.add-btn.svelte-d3ct2b{padding:8px 16px;background:var(--primary);color:var(--on-primary);border:none;border-radius:6px;font-family:var(--mono);font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;transition:background .15s;align-self:flex-end}.add-btn.svelte-d3ct2b:hover:not(:disabled){background:var(--primary-dim)}.add-btn.svelte-d3ct2b:disabled{opacity:.5;cursor:not-allowed}.rule-list.svelte-d3ct2b{display:flex;flex-direction:column;gap:4px;overflow-y:auto;max-height:400px;padding:0 8px}.rule-row.svelte-d3ct2b{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--outline-var);border-radius:6px;background:var(--surface-low);font-size:12px;min-width:0;transition:background .1s}.rule-row.svelte-d3ct2b:hover{background:var(--surface-mid)}.rule-badge.svelte-d3ct2b{padding:2px 7px;background:var(--primary-container);color:var(--primary);border-radius:4px;font-family:var(--mono);font-size:10px;font-weight:700;text-transform:uppercase;flex-shrink:0}.rule-scope.svelte-d3ct2b{color:var(--outline);font-family:var(--mono);font-size:10px;font-weight:600;flex-shrink:0}.rule-content.svelte-d3ct2b{flex:1;min-width:0;color:var(--on-surface);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rule-reason.svelte-d3ct2b{color:var(--on-surface-var);font-size:11px;flex-shrink:0;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.del-btn.svelte-d3ct2b{background:transparent;border:none;color:var(--outline);cursor:pointer;font-size:12px;padding:2px 6px;border-radius:4px;flex-shrink:0;transition:background .1s,color .1s}.del-btn.svelte-d3ct2b:hover{background:var(--error-bg);color:var(--error)}.metrics-footer.svelte-d3ct2b{display:flex;align-items:center;justify-content:space-between;height:48px;padding:0 24px;border-top:1px solid var(--outline-var);background:var(--surface);flex-shrink:0}.footer-left.svelte-d3ct2b,.footer-right.svelte-d3ct2b{display:flex;align-items:center;gap:24px}.footer-metric.svelte-d3ct2b{display:flex;align-items:center;gap:6px}.footer-label.svelte-d3ct2b{font-family:var(--mono);font-size:10px;font-weight:700;color:var(--outline);text-transform:uppercase;letter-spacing:.04em}.footer-val.svelte-d3ct2b{font-family:var(--mono);font-size:14px;font-weight:700;color:var(--on-surface)}.footer-val-error.svelte-d3ct2b{color:var(--error)}.footer-unit.svelte-d3ct2b{font-family:var(--mono);font-size:9px;color:var(--outline);text-transform:uppercase}.storage-bar-wrap.svelte-d3ct2b{display:flex;align-items:center;gap:8px}.storage-bar.svelte-d3ct2b{width:96px;height:6px;background:var(--surface-mid);border-radius:999px;overflow:hidden}.storage-fill.svelte-d3ct2b{height:100%;background:var(--primary-dim);border-radius:999px;transition:width .6s ease}.cli-btn.svelte-d3ct2b{display:flex;align-items:center;gap:6px;padding:5px 12px;background:var(--surface-high);border:none;border-radius:4px;color:var(--on-surface);font-family:var(--mono);font-size:11px;font-weight:700;cursor:pointer;transition:background .15s}.cli-btn.svelte-d3ct2b:hover{background:var(--outline-var)}:root[data-theme=dark]{--primary: #b8c3ff;--primary-dim: #4d6dff;--primary-container: #1a2340;--on-primary: #001b3e;--surface: #111318;--surface-low: #0c0e12;--surface-mid: #1e2024;--surface-high: #282a2e;--surface-highest: #37393e;--on-surface: #e2e2e8;--on-surface-var: #c4c5d9;--outline: #747688;--outline-var: #3a3c4a;--error: #ffb4ab;--error-bg: rgba(147,0,10,.22);--tertiary: #ffb59b;--green: #03f59b;--green-bg: rgba(3,245,155,.1);--green-border: rgba(3,245,155,.28)}html[data-theme=dark] .topbar.svelte-d3ct2b{background:#1a1c20;border-bottom-color:var(--outline-var)}html[data-theme=dark] .sidebar.svelte-d3ct2b{background:#0c0e12;border-right-color:var(--outline-var)}html[data-theme=dark] .health-bar.svelte-d3ct2b{background:#1a1c20;border-bottom-color:var(--outline-var)}html[data-theme=dark] .filter-bar.svelte-d3ct2b{background:#111318;border-bottom-color:var(--outline-var)}html[data-theme=dark] .feed-area.svelte-d3ct2b{background:#0c0e12}html[data-theme=dark] .metrics-footer.svelte-d3ct2b{background:#111318;border-top-color:var(--outline-var)}html[data-theme=dark] .search-input.svelte-d3ct2b{background:#1e2024;border-color:var(--outline-var);color:var(--on-surface)}html[data-theme=dark] .dropdown.svelte-d3ct2b{background:#1e2024;border-color:var(--outline-var);box-shadow:0 8px 24px #00000073}html[data-theme=dark] .dropdown-item.svelte-d3ct2b:hover{background:#282a2e}html[data-theme=dark] .dropdown-item-active.svelte-d3ct2b{background:var(--primary-container);color:var(--primary)}html[data-theme=dark] .chip-outline.svelte-d3ct2b,html[data-theme=dark] .live-badge.svelte-d3ct2b{background:#1e2024}html[data-theme=dark] .rule-row.svelte-d3ct2b{background:#1e2024;border-color:var(--outline-var)}html[data-theme=dark] .rule-row.svelte-d3ct2b:hover{background:#282a2e}html[data-theme=dark] .rules-select.svelte-d3ct2b,html[data-theme=dark] .rules-input.svelte-d3ct2b,html[data-theme=dark] .rules-textarea.svelte-d3ct2b{background:#1e2024;border-color:var(--outline-var);color:var(--on-surface)}html[data-theme=dark] .log-row.svelte-d3ct2b:hover{background:#1e2024}html[data-theme=dark] .log-row-warn.svelte-d3ct2b{background:#ffb4ab0d}html[data-theme=dark] .log-row-fail.svelte-d3ct2b{background:#ffba200a}html[data-theme=dark] .section-header.svelte-d3ct2b{border-bottom-color:var(--outline-var)}html[data-theme=dark] .storage-bar.svelte-d3ct2b{background:#282a2e}html[data-theme=dark] .badge-warn.svelte-d3ct2b{background:#422006;color:#fbbf24}html[data-theme=dark] .log-detail.svelte-d3ct2b{border-left-color:#ffba204d}html[data-theme=dark] .history-entry.svelte-d3ct2b{border-color:var(--outline-var);background:#1a1c20}html[data-theme=dark] .history-header.svelte-d3ct2b{background:#1e2024;border-bottom-color:var(--outline-var)}html[data-theme=dark] .history-violation.svelte-d3ct2b{border-bottom-color:var(--outline-var)}html[data-theme=dark] .arch-node-panel.svelte-d3ct2b{background:#1a1c20;border-color:var(--outline-var)}html[data-theme=dark] .arch-node-panel-path.svelte-d3ct2b{background:#1e2024}html[data-theme=dark] .arch-rule-row.svelte-d3ct2b{background:#1a1c20;border-color:var(--outline-var)}html[data-theme=dark] .arch-svg.svelte-d3ct2b{background:#0c0e12;border-color:var(--outline-var)}html[data-theme=dark] .footer-actions.svelte-d3ct2b .action-btn:where(.svelte-d3ct2b){background:#1e2024}html[data-theme=dark] .cli-btn.svelte-d3ct2b{background:#1e2024}html[data-theme=dark] .modal-status-row.svelte-d3ct2b{border-bottom-color:var(--outline-var)}html[data-theme=dark] .modal-input.svelte-d3ct2b{background:#1e2024;border-color:var(--outline-var);color:var(--on-surface)}html[data-theme=dark] .modal-btn.svelte-d3ct2b{background:#1e2024;border-color:var(--outline-var);color:var(--on-surface-var)}html[data-theme=dark] .modal-btn.svelte-d3ct2b:hover:not(:disabled){background:#282a2e}html[data-theme=dark] .rule-form-wrap.svelte-d3ct2b{border-bottom-color:var(--outline-var)}@media (max-width: 768px){.sidebar.svelte-d3ct2b,.topbar-nav.svelte-d3ct2b,.metrics-strip.svelte-d3ct2b{display:none}.filter-bar.svelte-d3ct2b,.feed-area.svelte-d3ct2b{padding:8px 12px}.metrics-footer.svelte-d3ct2b{padding:0 12px;gap:12px}.footer-metric.svelte-d3ct2b:nth-child(n+2){display:none}}.footer-actions.svelte-d3ct2b{display:flex;gap:6px;align-items:center;margin-right:12px}.action-btn.svelte-d3ct2b{font-size:11px;font-family:var(--mono);padding:4px 10px;border-radius:6px;border:1px solid var(--outline-var);background:var(--surface-low);color:var(--on-surface-var);cursor:pointer;transition:background .15s,border-color .15s}.action-btn.svelte-d3ct2b:hover:not(:disabled){background:var(--primary-container);border-color:var(--primary);color:var(--primary)}.action-btn.svelte-d3ct2b:disabled{opacity:.5;cursor:not-allowed}.action-btn-warn.svelte-d3ct2b:hover:not(:disabled){background:#fff3e0;border-color:#f59e0b;color:#b45309}.modal-backdrop.svelte-d3ct2b{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000073;display:flex;align-items:center;justify-content:center;z-index:200}.modal.svelte-d3ct2b{background:var(--surface);border:1px solid var(--outline-var);border-radius:12px;width:480px;max-width:calc(100vw - 32px);box-shadow:0 8px 32px #0000002e;overflow:hidden}.modal-header.svelte-d3ct2b{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 12px;border-bottom:1px solid var(--outline-var)}.modal-title.svelte-d3ct2b{font-size:14px;font-weight:600;color:var(--on-surface)}.modal-close.svelte-d3ct2b{background:none;border:none;cursor:pointer;color:var(--outline);font-size:16px;line-height:1}.modal-section.svelte-d3ct2b{padding:16px 20px;border-bottom:1px solid var(--outline-var)}.modal-section.svelte-d3ct2b:last-child{border-bottom:none}.modal-section-title.svelte-d3ct2b{font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--outline);text-transform:uppercase;margin-bottom:12px}.modal-row.svelte-d3ct2b{display:grid;grid-template-columns:100px 1fr;gap:8px;align-items:center;margin-bottom:10px}.modal-label.svelte-d3ct2b{font-size:12px;color:var(--on-surface-var)}.modal-input.svelte-d3ct2b{font-size:12px;font-family:var(--mono);padding:6px 10px;border:1px solid var(--outline-var);border-radius:6px;background:var(--surface-low);color:var(--on-surface);outline:none;width:100%}.modal-input.svelte-d3ct2b:focus{border-color:var(--primary)}.modal-row-actions.svelte-d3ct2b{display:flex;gap:8px}.modal-btn.svelte-d3ct2b{font-size:12px;padding:6px 14px;border:1px solid var(--outline-var);border-radius:6px;background:var(--surface-low);color:var(--on-surface-var);cursor:pointer}.modal-btn.svelte-d3ct2b:hover:not(:disabled){background:var(--surface-mid)}.modal-btn.svelte-d3ct2b:disabled{opacity:.5;cursor:not-allowed}.modal-btn-primary.svelte-d3ct2b{background:var(--primary);color:var(--on-primary);border-color:var(--primary)}.modal-btn-primary.svelte-d3ct2b:hover:not(:disabled){background:var(--primary-dim)}.modal-msg.svelte-d3ct2b{font-size:12px;margin-top:8px;padding:6px 10px;border-radius:6px}.modal-msg-ok.svelte-d3ct2b{background:var(--green-bg);color:var(--green);border:1px solid var(--green-border)}.modal-msg-err.svelte-d3ct2b{background:var(--error-bg);color:var(--error);border:1px solid var(--error)}.modal-status-row.svelte-d3ct2b{display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid var(--surface-mid)}.modal-status-row.svelte-d3ct2b:last-child{border-bottom:none}.modal-status-label.svelte-d3ct2b{font-size:11px;color:var(--outline)}.modal-status-val.svelte-d3ct2b{font-size:11px;font-family:var(--mono);color:var(--on-surface)}html[data-theme=dark] .modal.svelte-d3ct2b{background:var(--surface);border-color:var(--outline-var)}.arch-graph-wrap.svelte-d3ct2b{display:flex;flex-direction:column;gap:16px;padding:16px}.arch-svg.svelte-d3ct2b{display:block;border:1px solid var(--outline-var);border-radius:8px;background:var(--surface-low);width:100%;max-width:640px}.arch-node-rect.svelte-d3ct2b{fill:var(--surface-mid);stroke:var(--outline-var);stroke-width:1.5}.arch-node-domain.arch-node-rect.svelte-d3ct2b{fill:#eff6ff;stroke:#3b82f6}.arch-node-application.arch-node-rect.svelte-d3ct2b{fill:#f0fdf4;stroke:#22c55e}.arch-node-infrastructure.arch-node-rect.svelte-d3ct2b{fill:#fff7ed;stroke:#f97316}.arch-node-api.arch-node-rect.svelte-d3ct2b{fill:#faf5ff;stroke:#a855f7}.arch-node-shared.arch-node-rect.svelte-d3ct2b{fill:#f8fafc;stroke:#94a3b8}.arch-node-controllers.arch-node-rect.svelte-d3ct2b{fill:#eff6ff;stroke:#3b82f6}.arch-node-handlers.arch-node-rect.svelte-d3ct2b{fill:#faf5ff;stroke:#a855f7}.arch-node-services.arch-node-rect.svelte-d3ct2b{fill:#f0fdf4;stroke:#22c55e}.arch-node-repositories.arch-node-rect.svelte-d3ct2b{fill:#fff7ed;stroke:#f97316}.arch-node-modules.arch-node-rect.svelte-d3ct2b{fill:#eef2ff;stroke:#6366f1}.arch-node-common.arch-node-rect.svelte-d3ct2b{fill:#f8fafc;stroke:#94a3b8}.arch-node-models.arch-node-rect.svelte-d3ct2b{fill:#f0fdfa;stroke:#14b8a6}.arch-node-views.arch-node-rect.svelte-d3ct2b{fill:#fefce8;stroke:#eab308}.arch-node-pages.arch-node-rect.svelte-d3ct2b,.arch-node-screens.arch-node-rect.svelte-d3ct2b,.arch-node-routes.arch-node-rect.svelte-d3ct2b{fill:#eff6ff;stroke:#3b82f6}.arch-node-components.arch-node-rect.svelte-d3ct2b{fill:#fdf2f8;stroke:#ec4899}.arch-node-hooks.arch-node-rect.svelte-d3ct2b{fill:#f5f3ff;stroke:#8b5cf6}.arch-node-composables.arch-node-rect.svelte-d3ct2b{fill:#ecfeff;stroke:#06b6d4}.arch-node-store.arch-node-rect.svelte-d3ct2b,.arch-node-stores.arch-node-rect.svelte-d3ct2b{fill:#fffbeb;stroke:#f59e0b}.arch-node-lib.arch-node-rect.svelte-d3ct2b{fill:#f0fdfa;stroke:#14b8a6}.arch-node-utils.arch-node-rect.svelte-d3ct2b{fill:#f8fafc;stroke:#94a3b8}.arch-node-guards.arch-node-rect.svelte-d3ct2b{fill:#fff1f2;stroke:#f43f5e}.arch-node-server.arch-node-rect.svelte-d3ct2b{fill:#fff7ed;stroke:#f97316}.arch-node-ports.arch-node-rect.svelte-d3ct2b{fill:#ecfeff;stroke:#06b6d4}.arch-node-adapters.arch-node-rect.svelte-d3ct2b{fill:#f0fdfa;stroke:#14b8a6}html[data-theme=dark] .arch-node-domain.arch-node-rect.svelte-d3ct2b{fill:#1e3a5f;stroke:#3b82f6}html[data-theme=dark] .arch-node-application.arch-node-rect.svelte-d3ct2b{fill:#14352a;stroke:#22c55e}html[data-theme=dark] .arch-node-infrastructure.arch-node-rect.svelte-d3ct2b{fill:#3d2006;stroke:#f97316}html[data-theme=dark] .arch-node-api.arch-node-rect.svelte-d3ct2b{fill:#2e1a47;stroke:#a855f7}html[data-theme=dark] .arch-node-shared.arch-node-rect.svelte-d3ct2b{fill:#1e2535;stroke:#94a3b8}html[data-theme=dark] .arch-node-controllers.arch-node-rect.svelte-d3ct2b{fill:#1e3a5f;stroke:#3b82f6}html[data-theme=dark] .arch-node-handlers.arch-node-rect.svelte-d3ct2b{fill:#2e1a47;stroke:#a855f7}html[data-theme=dark] .arch-node-services.arch-node-rect.svelte-d3ct2b{fill:#14352a;stroke:#22c55e}html[data-theme=dark] .arch-node-repositories.arch-node-rect.svelte-d3ct2b{fill:#3d2006;stroke:#f97316}html[data-theme=dark] .arch-node-modules.arch-node-rect.svelte-d3ct2b{fill:#1e1b4b;stroke:#6366f1}html[data-theme=dark] .arch-node-common.arch-node-rect.svelte-d3ct2b{fill:#1e2535;stroke:#94a3b8}html[data-theme=dark] .arch-node-models.arch-node-rect.svelte-d3ct2b{fill:#0f2922;stroke:#14b8a6}html[data-theme=dark] .arch-node-views.arch-node-rect.svelte-d3ct2b{fill:#29240a;stroke:#eab308}html[data-theme=dark] .arch-node-pages.arch-node-rect.svelte-d3ct2b,html[data-theme=dark] .arch-node-screens.arch-node-rect.svelte-d3ct2b,html[data-theme=dark] .arch-node-routes.arch-node-rect.svelte-d3ct2b{fill:#1e3a5f;stroke:#3b82f6}html[data-theme=dark] .arch-node-components.arch-node-rect.svelte-d3ct2b{fill:#3d0e2e;stroke:#ec4899}html[data-theme=dark] .arch-node-hooks.arch-node-rect.svelte-d3ct2b{fill:#1e1547;stroke:#8b5cf6}html[data-theme=dark] .arch-node-composables.arch-node-rect.svelte-d3ct2b{fill:#042934;stroke:#06b6d4}html[data-theme=dark] .arch-node-store.arch-node-rect.svelte-d3ct2b,html[data-theme=dark] .arch-node-stores.arch-node-rect.svelte-d3ct2b{fill:#2d200a;stroke:#f59e0b}html[data-theme=dark] .arch-node-lib.arch-node-rect.svelte-d3ct2b{fill:#0f2922;stroke:#14b8a6}html[data-theme=dark] .arch-node-utils.arch-node-rect.svelte-d3ct2b{fill:#1e2535;stroke:#94a3b8}html[data-theme=dark] .arch-node-guards.arch-node-rect.svelte-d3ct2b{fill:#3d0a14;stroke:#f43f5e}html[data-theme=dark] .arch-node-server.arch-node-rect.svelte-d3ct2b{fill:#3d2006;stroke:#f97316}html[data-theme=dark] .arch-node-ports.arch-node-rect.svelte-d3ct2b{fill:#042934;stroke:#06b6d4}html[data-theme=dark] .arch-node-adapters.arch-node-rect.svelte-d3ct2b{fill:#0f2922;stroke:#14b8a6}.arch-node-label.svelte-d3ct2b{font-family:var(--mono);font-size:11px;font-weight:600;fill:var(--on-surface)}.arch-node-desc.svelte-d3ct2b{font-family:var(--sans);font-size:9px;fill:var(--outline)}.arch-node-clickable.svelte-d3ct2b{cursor:pointer}.arch-node-clickable.svelte-d3ct2b:hover .arch-node-rect:where(.svelte-d3ct2b){filter:brightness(.95)}html[data-theme=dark] .arch-node-clickable.svelte-d3ct2b:hover .arch-node-rect:where(.svelte-d3ct2b){filter:brightness(1.15)}.arch-node-panel.svelte-d3ct2b{border:1px solid var(--outline-var);border-radius:8px;background:var(--surface-low);padding:12px 16px;max-width:640px;display:flex;flex-direction:column;gap:10px}.arch-node-panel-header.svelte-d3ct2b{display:flex;align-items:center;gap:10px}.arch-node-panel-name.svelte-d3ct2b{font-family:var(--mono);font-weight:700;font-size:13px;color:var(--on-surface)}.arch-node-panel-meta.svelte-d3ct2b{font-size:12px;color:var(--outline);flex:1}.arch-node-panel-close.svelte-d3ct2b{background:none;border:none;cursor:pointer;color:var(--outline);font-size:14px;padding:0;line-height:1}.arch-node-panel-close.svelte-d3ct2b:hover{color:var(--on-surface)}.arch-node-panel-paths.svelte-d3ct2b{display:flex;flex-direction:column;gap:4px}.arch-node-panel-section.svelte-d3ct2b{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--outline);margin-bottom:2px}.arch-node-panel-path.svelte-d3ct2b{font-family:var(--mono);font-size:11px;color:var(--cyan, #22d3ee);background:var(--surface-mid);padding:3px 8px;border-radius:4px}.arch-node-panel-rule.svelte-d3ct2b{display:flex;align-items:center;gap:8px;font-size:12px}.arch-legend.svelte-d3ct2b{display:flex;gap:16px;align-items:center;font-size:11px;color:var(--outline)}.legend-item.svelte-d3ct2b{display:flex;align-items:center;gap:6px}.arch-rule-list.svelte-d3ct2b{display:flex;flex-direction:column;gap:4px;max-width:640px}.arch-rule-row.svelte-d3ct2b{display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--surface-low);border-radius:4px;border:1px solid var(--outline-var)}.tab-badge.svelte-d3ct2b{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:16px;padding:0 5px;border-radius:8px;background:var(--error);color:#fff;font-size:10px;font-weight:700;margin-left:4px;vertical-align:middle}.history-list.svelte-d3ct2b{display:flex;flex-direction:column;gap:8px;max-width:760px}.history-entry.svelte-d3ct2b{border:1px solid var(--outline-var);border-radius:6px;background:var(--surface-low);overflow:hidden}.history-header.svelte-d3ct2b{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--outline-var);background:var(--surface-mid)}.history-file.svelte-d3ct2b{font-family:var(--mono);font-size:12px;font-weight:600;color:var(--cyan, #22d3ee)}.history-time.svelte-d3ct2b{font-size:11px;color:var(--outline)}.history-violation.svelte-d3ct2b{display:flex;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--outline-var);flex-wrap:wrap}.history-violation.svelte-d3ct2b:last-child{border-bottom:none}.history-badge.svelte-d3ct2b{flex-shrink:0;font-family:var(--mono);font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;background:var(--error-bg);color:var(--error);white-space:nowrap}.history-rule.svelte-d3ct2b{font-size:12px;font-weight:600;color:var(--on-surface)}.history-issue.svelte-d3ct2b{font-size:12px;color:var(--outline)}.arch-rule-badge.svelte-d3ct2b{font-size:10px;font-family:var(--mono);font-weight:700;padding:2px 6px;border-radius:3px}.badge-block.svelte-d3ct2b{background:var(--error-bg);color:var(--error)}.badge-warn.svelte-d3ct2b{background:#fef3c7;color:#92400e}.badge-allow.svelte-d3ct2b{background:var(--green-bg);color:var(--green)}.arch-rule-layers.svelte-d3ct2b{font-family:var(--mono);font-size:11px;color:var(--on-surface);white-space:nowrap}.arch-rule-name.svelte-d3ct2b{font-size:11px;color:var(--outline);flex:1}.arch-edit-btn.svelte-d3ct2b{padding:4px 10px;font-family:var(--mono);font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;background:var(--surface-mid);border:1px solid var(--outline-var);border-radius:4px;color:var(--on-surface-var);cursor:pointer;transition:background .15s,color .15s}.arch-edit-btn.svelte-d3ct2b:hover{background:var(--primary-container);color:var(--primary);border-color:var(--primary-dim)}.arch-edit-btn-active.svelte-d3ct2b{background:var(--primary-container)!important;color:var(--primary)!important;border-color:var(--primary-dim)!important}.arch-reset-btn.svelte-d3ct2b{color:var(--tertiary)!important;border-color:var(--tertiary)!important}.arch-reset-btn.svelte-d3ct2b:hover{background:#fff3eb!important}html[data-theme=dark] .arch-reset-btn.svelte-d3ct2b:hover{background:#3d1a00!important}.arch-rule-del.svelte-d3ct2b{background:none;border:none;color:var(--outline);cursor:pointer;font-size:12px;padding:0 4px;line-height:1;transition:color .15s}.arch-rule-del.svelte-d3ct2b:hover{color:var(--error)}.arch-add-form.svelte-d3ct2b{display:flex;flex-direction:column;gap:8px;background:var(--surface-low);border:1px solid var(--outline-var);border-radius:6px;padding:12px;max-width:640px}.arch-add-form-title.svelte-d3ct2b{font-family:var(--mono);font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--outline)}.arch-add-row.svelte-d3ct2b{display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end}.arch-add-input.svelte-d3ct2b{height:30px;padding:0 8px;background:var(--surface);border:1px solid var(--outline-var);border-radius:4px;color:var(--on-surface);font-family:var(--mono);font-size:11px;outline:none;min-width:80px}.arch-add-input.svelte-d3ct2b:focus{border-color:var(--primary-dim)}.arch-add-select.svelte-d3ct2b{height:30px;padding:0 6px;background:var(--surface);border:1px solid var(--outline-var);border-radius:4px;color:var(--on-surface);font-family:var(--mono);font-size:11px;outline:none}.arch-add-submit.svelte-d3ct2b{height:30px;padding:0 12px;background:var(--primary);border:none;border-radius:4px;color:#fff;font-family:var(--mono);font-size:11px;font-weight:700;cursor:pointer;transition:background .15s}.arch-add-submit.svelte-d3ct2b:hover{background:var(--primary-dim)}.arch-add-submit.svelte-d3ct2b:disabled{opacity:.5;cursor:default}.arch-add-label.svelte-d3ct2b{font-size:10px;color:var(--outline);font-family:var(--mono)}.arch-add-field.svelte-d3ct2b{display:flex;flex-direction:column;gap:2px}
|