@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.
@@ -14,7 +14,7 @@ import {
14
14
  searchMemories,
15
15
  updateMemory,
16
16
  upsertMemory
17
- } from "./chunk-M7NKSXFS.js";
17
+ } from "./chunk-GIPKVQSA.js";
18
18
  import {
19
19
  buildModuleDependencyEdges,
20
20
  collectResolvedImports,
@@ -208,7 +208,17 @@ function buildArchRules(arch) {
208
208
  { id: "no-circular", name: "No circular dependencies", fromLayer: "*", toLayer: "*", allowed: false, severity: "critical", enforcement: "block" },
209
209
  { id: "components-no-routes", name: "Components must not import route modules", fromLayer: "components", toLayer: "routes", allowed: false, severity: "critical", enforcement: "block" },
210
210
  { id: "stores-no-components", name: "Stores must not import components", fromLayer: "stores", toLayer: "components", allowed: false, severity: "critical", enforcement: "block" },
211
- { id: "utils-no-stores", name: "Utils must not import stores", fromLayer: "utils", toLayer: "stores", allowed: false, severity: "medium", enforcement: "warn" }
211
+ { id: "lib-no-routes", name: "Lib must not import routes", fromLayer: "lib", toLayer: "routes", allowed: false, severity: "critical", enforcement: "block" },
212
+ { id: "lib-no-stores", name: "Lib must not import client stores", fromLayer: "lib", toLayer: "stores", allowed: false, severity: "critical", enforcement: "block" },
213
+ { id: "lib-no-components", name: "Lib must not import components", fromLayer: "lib", toLayer: "components", allowed: false, severity: "critical", enforcement: "block" },
214
+ { id: "utils-no-stores", name: "Utils must not import stores", fromLayer: "utils", toLayer: "stores", allowed: false, severity: "medium", enforcement: "warn" },
215
+ { id: "utils-no-lib", name: "Utils must not import lib \u2014 keep utils pure", fromLayer: "utils", toLayer: "lib", allowed: false, severity: "medium", enforcement: "warn" },
216
+ { id: "routes-use-components", name: "Routes use components", fromLayer: "routes", toLayer: "components", allowed: true, severity: "low", enforcement: "suggest" },
217
+ { id: "routes-use-stores", name: "Routes use stores", fromLayer: "routes", toLayer: "stores", allowed: true, severity: "low", enforcement: "suggest" },
218
+ { id: "routes-use-lib", name: "Routes use lib for server actions and API", fromLayer: "routes", toLayer: "lib", allowed: true, severity: "low", enforcement: "suggest" },
219
+ { id: "components-use-stores", name: "Components use stores for reactive state", fromLayer: "components", toLayer: "stores", allowed: true, severity: "low", enforcement: "suggest" },
220
+ { id: "components-use-lib", name: "Components use lib for API clients", fromLayer: "components", toLayer: "lib", allowed: true, severity: "low", enforcement: "suggest" },
221
+ { id: "stores-use-lib", name: "Stores use lib for API and utilities", fromLayer: "stores", toLayer: "lib", allowed: true, severity: "low", enforcement: "suggest" }
212
222
  ] };
213
223
  case "angular":
214
224
  return { rules: [
@@ -71,6 +71,34 @@ async function runMigrations() {
71
71
  WHERE content_hash IS NULL`
72
72
  );
73
73
  await client.query(`CREATE INDEX IF NOT EXISTS memories_content_hash_idx ON memories (content_hash)`);
74
+ await client.query(`
75
+ CREATE TABLE IF NOT EXISTS arch_profiles (
76
+ id SERIAL PRIMARY KEY,
77
+ name TEXT NOT NULL,
78
+ arch_type TEXT NOT NULL DEFAULT 'custom',
79
+ layers JSONB NOT NULL DEFAULT '[]',
80
+ rules JSONB NOT NULL DEFAULT '[]',
81
+ project_name TEXT,
82
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
83
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
84
+ )
85
+ `);
86
+ await client.query(`
87
+ CREATE UNIQUE INDEX IF NOT EXISTS arch_profiles_name_project_idx
88
+ ON arch_profiles (name, COALESCE(project_name, ''))
89
+ `);
90
+ await client.query(`
91
+ CREATE TABLE IF NOT EXISTS users (
92
+ id SERIAL PRIMARY KEY,
93
+ email TEXT NOT NULL UNIQUE,
94
+ username TEXT NOT NULL UNIQUE,
95
+ password_hash TEXT NOT NULL,
96
+ role TEXT NOT NULL DEFAULT 'viewer',
97
+ team_name TEXT,
98
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
99
+ last_login TIMESTAMPTZ
100
+ )
101
+ `);
74
102
  await client.query("COMMIT");
75
103
  migrationsRun = true;
76
104
  } catch (err) {
@@ -80,6 +108,38 @@ async function runMigrations() {
80
108
  client.release();
81
109
  }
82
110
  }
111
+ async function saveArchProfile(input) {
112
+ await runMigrations();
113
+ const result = await getPool().query(
114
+ `INSERT INTO arch_profiles (name, arch_type, layers, rules, project_name)
115
+ VALUES ($1, $2, $3::jsonb, $4::jsonb, $5)
116
+ ON CONFLICT (name, COALESCE(project_name, ''))
117
+ DO UPDATE SET arch_type = EXCLUDED.arch_type, layers = EXCLUDED.layers, rules = EXCLUDED.rules, updated_at = NOW()
118
+ RETURNING *`,
119
+ [input.name, input.archType, JSON.stringify(input.layers), JSON.stringify(input.rules), input.projectName ?? null]
120
+ );
121
+ return result.rows[0];
122
+ }
123
+ async function listArchProfiles(projectName) {
124
+ await runMigrations();
125
+ const result = await getPool().query(
126
+ `SELECT * FROM arch_profiles
127
+ WHERE project_name IS NULL OR project_name = $1
128
+ ORDER BY updated_at DESC`,
129
+ [projectName ?? null]
130
+ );
131
+ return result.rows;
132
+ }
133
+ async function getArchProfile(id) {
134
+ await runMigrations();
135
+ const result = await getPool().query(`SELECT * FROM arch_profiles WHERE id = $1`, [id]);
136
+ return result.rows[0] ?? null;
137
+ }
138
+ async function deleteArchProfile(id) {
139
+ await runMigrations();
140
+ const result = await getPool().query(`DELETE FROM arch_profiles WHERE id = $1`, [id]);
141
+ return (result.rowCount ?? 0) > 0;
142
+ }
83
143
  async function saveMemory(memory) {
84
144
  await runMigrations();
85
145
  const { type, scope, architecture, projectName, title, content, reason, context, tags, embedding } = memory;
@@ -283,12 +343,57 @@ async function closePool() {
283
343
  migrationsRun = false;
284
344
  }
285
345
  }
346
+ async function createUser(input) {
347
+ await runMigrations();
348
+ const result = await getPool().query(
349
+ `INSERT INTO users (email, username, password_hash, role, team_name)
350
+ VALUES ($1, $2, $3, $4, $5)
351
+ RETURNING *`,
352
+ [input.email, input.username, input.passwordHash, input.role ?? "viewer", input.teamName ?? null]
353
+ );
354
+ return result.rows[0];
355
+ }
356
+ async function getUserByEmail(email) {
357
+ await runMigrations();
358
+ const result = await getPool().query(`SELECT * FROM users WHERE email = $1`, [email]);
359
+ return result.rows[0] ?? null;
360
+ }
361
+ async function getUserById(id) {
362
+ await runMigrations();
363
+ const result = await getPool().query(`SELECT * FROM users WHERE id = $1`, [id]);
364
+ return result.rows[0] ?? null;
365
+ }
366
+ async function listUsers() {
367
+ await runMigrations();
368
+ const result = await getPool().query(
369
+ `SELECT id, email, username, role, team_name, created_at, last_login FROM users ORDER BY created_at ASC`
370
+ );
371
+ return result.rows;
372
+ }
373
+ async function updateLastLogin(id) {
374
+ await runMigrations();
375
+ await getPool().query(`UPDATE users SET last_login = NOW() WHERE id = $1`, [id]);
376
+ }
377
+ async function countUsers() {
378
+ await runMigrations();
379
+ const result = await getPool().query(`SELECT COUNT(*) FROM users`);
380
+ return parseInt(result.rows[0].count, 10);
381
+ }
382
+ async function deleteUser(id) {
383
+ await runMigrations();
384
+ const result = await getPool().query(`DELETE FROM users WHERE id = $1`, [id]);
385
+ return (result.rowCount ?? 0) > 0;
386
+ }
286
387
 
287
388
  export {
288
389
  Config,
289
390
  hashMemoryContent,
290
391
  getPool,
291
392
  runMigrations,
393
+ saveArchProfile,
394
+ listArchProfiles,
395
+ getArchProfile,
396
+ deleteArchProfile,
292
397
  saveMemory,
293
398
  upsertMemory,
294
399
  listMemories,
@@ -297,5 +402,12 @@ export {
297
402
  deleteMemories,
298
403
  updateMemory,
299
404
  searchMemories,
300
- closePool
405
+ closePool,
406
+ createUser,
407
+ getUserByEmail,
408
+ getUserById,
409
+ listUsers,
410
+ updateLastLogin,
411
+ countUsers,
412
+ deleteUser
301
413
  };
package/dist/cli.js CHANGED
@@ -29,13 +29,13 @@ import {
29
29
  toPortableMemory,
30
30
  uninstallHook,
31
31
  writeMemoryFile
32
- } from "./chunk-OHVCPFEY.js";
32
+ } from "./chunk-23GUWJ6F.js";
33
33
  import "./chunk-PQBWHAZN.js";
34
34
  import {
35
35
  closePool,
36
36
  getPool,
37
37
  runMigrations
38
- } from "./chunk-M7NKSXFS.js";
38
+ } from "./chunk-GIPKVQSA.js";
39
39
  import "./chunk-ZZBQEXEO.js";
40
40
 
41
41
  // src/cli.ts
@@ -1738,7 +1738,7 @@ program.command("tune").description("Review and disable noisy rules with high fa
1738
1738
  console.log(chalk.dim("\n No changes made.\n"));
1739
1739
  }
1740
1740
  });
1741
- program.command("dashboard").description("Start the live Svelte dashboard with WebSocket watch events").option("-p, --port <port>", "Dashboard port", "5178").option("--path <dir>", "Directory to watch (default: current directory)").option("--no-watch", "Serve the dashboard without starting file watch").action(async (opts) => {
1741
+ program.command("dashboard").description("Start the live Svelte dashboard with WebSocket watch events").option("-p, --port <port>", "Dashboard port", "5178").option("--path <dir>", "Directory to watch (default: current directory)").option("--no-watch", "Serve the dashboard without starting file watch").option("--auth", "Enable JWT authentication (requires MC_JWT_SECRET in .memory-core.env)").action(async (opts) => {
1742
1742
  const resolveDashboardPath = () => {
1743
1743
  if (typeof opts.path === "string" && opts.path.trim().length > 0) return opts.path;
1744
1744
  const withEquals = process.argv.find((arg) => arg.startsWith("--path="));
@@ -1755,11 +1755,12 @@ program.command("dashboard").description("Start the live Svelte dashboard with W
1755
1755
  }
1756
1756
  return void 0;
1757
1757
  };
1758
- const { startDashboard } = await import("./dashboard-server-OHVL64F2.js");
1758
+ const { startDashboard } = await import("./dashboard-server-MD6NVL2F.js");
1759
1759
  await startDashboard({
1760
1760
  port: parseInt(opts.port, 10),
1761
1761
  path: resolveDashboardPath(),
1762
- watch: opts.watch
1762
+ watch: opts.watch,
1763
+ auth: opts.auth ?? false
1763
1764
  });
1764
1765
  });
1765
1766
  program.command("seed").description("Load all predefined memories into the database").option("--arch <architecture>", "Only seed a specific architecture (e.g. clean-architecture)").option("--force", "Re-seed even if memories already exist", false).action(async (opts) => {
@@ -2388,7 +2389,7 @@ program.command("check").description("Check staged changes against architecture
2388
2389
  }
2389
2390
  let storedMemories = [];
2390
2391
  try {
2391
- const { listMemories } = await import("./db-PRDHI2CN.js");
2392
+ const { listMemories } = await import("./db-FLFZZXG3.js");
2392
2393
  const allMemories = await listMemories({ limit: 1e4 });
2393
2394
  const ranked = allMemories.filter((m) => ["rule", "pattern", "decision"].includes(m.type));
2394
2395
  const changedLayers = packet.layersAffected;
@@ -2660,4 +2661,92 @@ arch.command("incident").description("Log a production incident and capture it a
2660
2661
  console.log(chalk.dim(" Discarded.\n"));
2661
2662
  }
2662
2663
  });
2664
+ var CREDENTIALS_PATH = join(homedir(), ".memory-core", "credentials.json");
2665
+ function readCredentials() {
2666
+ try {
2667
+ return JSON.parse(readFileSync(CREDENTIALS_PATH, "utf-8"));
2668
+ } catch {
2669
+ return null;
2670
+ }
2671
+ }
2672
+ function writeCredentials(data) {
2673
+ mkdirSync(dirname(CREDENTIALS_PATH), { recursive: true });
2674
+ writeFileSync(CREDENTIALS_PATH, JSON.stringify(data, null, 2) + "\n", "utf-8");
2675
+ }
2676
+ function clearCredentials() {
2677
+ try {
2678
+ writeFileSync(CREDENTIALS_PATH, "{}", "utf-8");
2679
+ } catch {
2680
+ }
2681
+ }
2682
+ var authCmd = program.command("auth").description("Manage dashboard authentication");
2683
+ authCmd.command("setup").description("Create the first admin account (dashboard must be running with --auth)").option("--url <url>", "Dashboard URL", "http://localhost:5178").action(async (opts) => {
2684
+ const email = await input({ message: "Admin email:" });
2685
+ const username = await input({ message: "Admin username:" });
2686
+ const password = await input({ message: "Password (min 8 chars):", transformer: () => "****" });
2687
+ const teamName = await input({ message: "Team name (optional):" });
2688
+ const res = await fetch(`${opts.url}/api/auth/setup`, {
2689
+ method: "POST",
2690
+ headers: { "content-type": "application/json" },
2691
+ body: JSON.stringify({ email, username, password, teamName: teamName || void 0 })
2692
+ });
2693
+ const data = await res.json();
2694
+ if (!res.ok) {
2695
+ console.error(chalk.red(` \u2717 ${data.error ?? "Setup failed"}`));
2696
+ process.exit(1);
2697
+ }
2698
+ writeCredentials({ token: data.token, email, username, dashboardUrl: opts.url });
2699
+ console.log(chalk.green(`
2700
+ \u2713 Admin created and logged in as ${username}
2701
+ `));
2702
+ console.log(chalk.gray(` Credentials saved to ${CREDENTIALS_PATH}
2703
+ `));
2704
+ });
2705
+ authCmd.command("login").description("Log in to a running dashboard (stores JWT locally)").option("--url <url>", "Dashboard URL", "http://localhost:5178").action(async (opts) => {
2706
+ const email = await input({ message: "Email:" });
2707
+ const password = await input({ message: "Password:", transformer: () => "****" });
2708
+ const res = await fetch(`${opts.url}/api/auth/login`, {
2709
+ method: "POST",
2710
+ headers: { "content-type": "application/json" },
2711
+ body: JSON.stringify({ email, password })
2712
+ });
2713
+ const data = await res.json();
2714
+ if (!res.ok) {
2715
+ console.error(chalk.red(` \u2717 ${data.error ?? "Login failed"}`));
2716
+ process.exit(1);
2717
+ }
2718
+ const user = data.user;
2719
+ writeCredentials({ token: data.token, email, username: user.username, dashboardUrl: opts.url });
2720
+ console.log(chalk.green(`
2721
+ \u2713 Logged in as ${user.username}
2722
+ `));
2723
+ });
2724
+ authCmd.command("logout").description("Clear locally stored credentials").action(() => {
2725
+ clearCredentials();
2726
+ console.log(chalk.green("\n \u2713 Logged out\n"));
2727
+ });
2728
+ authCmd.command("status").description("Show current auth status").action(async () => {
2729
+ const creds = readCredentials();
2730
+ if (!creds?.token) {
2731
+ console.log(chalk.yellow("\n Not logged in\n"));
2732
+ return;
2733
+ }
2734
+ try {
2735
+ const res = await fetch(`${creds.dashboardUrl}/api/auth/me`, {
2736
+ headers: { authorization: `Bearer ${creds.token}` }
2737
+ });
2738
+ const data = await res.json();
2739
+ if (!res.ok) {
2740
+ console.log(chalk.yellow("\n Token invalid or expired \u2014 run `memory-core auth login`\n"));
2741
+ return;
2742
+ }
2743
+ const user = data.user;
2744
+ console.log(chalk.green(`
2745
+ Logged in as ${user.username} (${user.email}) [${user.role}]`));
2746
+ console.log(chalk.gray(` Dashboard: ${creds.dashboardUrl}
2747
+ `));
2748
+ } catch {
2749
+ console.log(chalk.yellow("\n Dashboard not reachable\n"));
2750
+ }
2751
+ });
2663
2752
  program.parseAsync(process.argv);