@mem-weave/server 0.2.0

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.
Files changed (66) hide show
  1. package/README.md +74 -0
  2. package/dist/cli-entry.js +49 -0
  3. package/dist/cli.js +53 -0
  4. package/dist/commands/backup.js +28 -0
  5. package/dist/commands/doctor.js +108 -0
  6. package/dist/commands/help.js +29 -0
  7. package/dist/commands/index.js +27 -0
  8. package/dist/commands/init.js +58 -0
  9. package/dist/commands/migrate.js +25 -0
  10. package/dist/commands/start.js +29 -0
  11. package/dist/commands/status.js +19 -0
  12. package/dist/commands/stop.js +46 -0
  13. package/dist/commands/version.js +21 -0
  14. package/dist/core/config.js +161 -0
  15. package/dist/core/decay.js +50 -0
  16. package/dist/core/types.js +72 -0
  17. package/dist/db/database.js +58 -0
  18. package/dist/db/repositories/access-log-repo.js +59 -0
  19. package/dist/db/repositories/consolidation-run-repo.js +86 -0
  20. package/dist/db/repositories/device-repo.js +66 -0
  21. package/dist/db/repositories/edge-repo.js +104 -0
  22. package/dist/db/repositories/memory-repo.js +294 -0
  23. package/dist/db/repositories/observation-repo.js +65 -0
  24. package/dist/db/repositories/session-repo.js +81 -0
  25. package/dist/db/repositories/stats-repo.js +92 -0
  26. package/dist/db/repositories/vector-repo.js +55 -0
  27. package/dist/db/schema.js +185 -0
  28. package/dist/injection/bundler.js +39 -0
  29. package/dist/injection/formatter.js +23 -0
  30. package/dist/prompts/compression.js +43 -0
  31. package/dist/prompts/edge-extract.js +21 -0
  32. package/dist/prompts/value-gate.js +27 -0
  33. package/dist/providers/embedding/index.js +36 -0
  34. package/dist/providers/embedding/local-xenova.js +166 -0
  35. package/dist/providers/embedding/noop.js +40 -0
  36. package/dist/providers/embedding/openai-compatible.js +46 -0
  37. package/dist/providers/llm/index.js +12 -0
  38. package/dist/providers/llm/noop.js +5 -0
  39. package/dist/providers/llm/openai.js +45 -0
  40. package/dist/rest/routes/consolidation.js +62 -0
  41. package/dist/rest/routes/devices.js +47 -0
  42. package/dist/rest/routes/injection.js +76 -0
  43. package/dist/rest/routes/memories.js +349 -0
  44. package/dist/rest/routes/observations.js +29 -0
  45. package/dist/rest/routes/sessions.js +37 -0
  46. package/dist/rest/routes/settings.js +25 -0
  47. package/dist/rest/routes/stats.js +15 -0
  48. package/dist/retrieval/bm25-search.js +91 -0
  49. package/dist/retrieval/causal-chain.js +197 -0
  50. package/dist/retrieval/fusion.js +48 -0
  51. package/dist/retrieval/graph-traversal.js +144 -0
  52. package/dist/retrieval/search-engine.js +150 -0
  53. package/dist/retrieval/vector-search.js +91 -0
  54. package/dist/server/auth.js +80 -0
  55. package/dist/server/bootstrap.js +28 -0
  56. package/dist/server/http.js +77 -0
  57. package/dist/server/logger.js +36 -0
  58. package/dist/server/rate-limiter.js +81 -0
  59. package/dist/server/scheduler.js +99 -0
  60. package/dist/workers/association.js +41 -0
  61. package/dist/workers/compressor.js +14 -0
  62. package/dist/workers/consolidator.js +201 -0
  63. package/dist/workers/embedder.js +102 -0
  64. package/dist/workers/graph-worker.js +166 -0
  65. package/dist/workers/value-gate.js +38 -0
  66. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # @mem-weave/server
2
+
3
+ **MemWeave local-first memory infrastructure for AI agents.** Structured memory, 4-layer retrieval (BM25 + vector + graph + causal), token-budgeted injection, server-side write deduplication, and background consolidation. Fastify REST API + CLI.
4
+
5
+ This is the **server** half of the MemWeave stack. The complementary packages are:
6
+
7
+ - **[@mem-weave/mcp](https://www.npmjs.com/package/@mem-weave/mcp)** — stdio MCP server exposing 10 tools
8
+ - **[@mem-weave/opencode-plugin](https://www.npmjs.com/package/@mem-weave/opencode-plugin)** — OpenCode plugin that auto-injects memory + registers the MCP server
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install -g @mem-weave/server
14
+ ```
15
+
16
+ ## Quick start
17
+
18
+ ```bash
19
+ # 1. Initialize a config + data dir in the current directory
20
+ memweave init
21
+
22
+ # 2. Start the server (foreground)
23
+ memweave start
24
+
25
+ # Server listens on http://127.0.0.1:3131 by default.
26
+ # Health check:
27
+ curl http://127.0.0.1:3131/api/v1/health
28
+ # → {"ok":true,"service":"memweave-server"}
29
+ ```
30
+
31
+ In another terminal, point an MCP client (e.g. `@mem-weave/opencode-plugin`) at `http://127.0.0.1:3131`.
32
+
33
+ ## CLI
34
+
35
+ ```
36
+ memweave start Start the HTTP server + background workers (default)
37
+ memweave stop Stop a running memweave-server (via PID file)
38
+ memweave status Probe /api/v1/health
39
+ memweave init Create default config, DB, and device key
40
+ memweave doctor Check dependencies, port, DB, embedding/LLM config
41
+ memweave migrate Run schema migration (idempotent)
42
+ memweave backup [path] Copy the SQLite DB to a snapshot file
43
+ memweave help Show help
44
+ ```
45
+
46
+ > Note: the `mcp` subcommand was removed in v0.2. Install `@mem-weave/mcp` and run its `memweave-mcp` bin instead.
47
+
48
+ ## REST API
49
+
50
+ All routes are prefixed `/api/v1/`. Selected endpoints:
51
+
52
+ | Method | Path | Purpose |
53
+ |---|---|---|
54
+ | `GET` | `/health` | Liveness check |
55
+ | `POST` | `/memories` | Create a memory (rate-limited per API key) |
56
+ | `POST` | `/memories/search` | 4-layer hybrid search; `mode: 'compact' \| 'full'` |
57
+ | `GET` | `/memories/:id` | Fetch one memory (full record, used by `memory_expand`) |
58
+ | `PATCH` | `/memories/:id` | Edit title / content / summary / importance / confidence |
59
+ | `DELETE` | `/memories/:id` | Soft-delete (`deleted_at`) with reason |
60
+ | `POST` | `/inject` | Token-budgeted summary XML for a session/phase |
61
+ | `GET` | `/stats` | Dashboard aggregates |
62
+ | `GET` | `/sessions` | Recent sessions with observation counts |
63
+ | `GET` | `/consolidation/runs` | Background "sleep" cycle history |
64
+ | `POST` | `/consolidate` | Manually trigger a consolidation run |
65
+ | `GET/POST/DELETE` | `/devices` | Device API key management |
66
+ | `GET` | `/settings` | Server config (secrets masked) |
67
+
68
+ ## Programmatic use
69
+
70
+ The server is also a TypeScript library. Importing the HTTP bootstrap from `@mem-weave/server/dist/server/bootstrap.js` boots everything in-process. See `src/server/bootstrap.ts` for the entry point.
71
+
72
+ ## License
73
+
74
+ MIT
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MemWeave CLI entry point.
4
+ *
5
+ * This file is the executable for the `memweave` bin command. It parses
6
+ * argv, dispatches to the right command, and prints the result.
7
+ */
8
+ import { parseCli, runCli, CliError } from './cli.js';
9
+ async function main() {
10
+ let parsed;
11
+ try {
12
+ parsed = parseCli(process.argv);
13
+ }
14
+ catch (err) {
15
+ if (err instanceof CliError) {
16
+ // eslint-disable-next-line no-console
17
+ console.error(err.message);
18
+ process.exit(2);
19
+ }
20
+ throw err;
21
+ }
22
+ const result = await runCli({
23
+ command: parsed.command,
24
+ args: parsed.args,
25
+ env: process.env,
26
+ configPath: process.env.MEMWEAVE_CONFIG
27
+ });
28
+ // Print message + data, and exit non-zero on failure
29
+ if (result.message) {
30
+ if (result.ok) {
31
+ // eslint-disable-next-line no-console
32
+ console.log(result.message);
33
+ }
34
+ else {
35
+ // eslint-disable-next-line no-console
36
+ console.error(result.message);
37
+ }
38
+ }
39
+ if (result.data !== undefined) {
40
+ // eslint-disable-next-line no-console
41
+ console.log(JSON.stringify(result.data, null, 2));
42
+ }
43
+ process.exit(result.ok ? 0 : 1);
44
+ }
45
+ main().catch((err) => {
46
+ // eslint-disable-next-line no-console
47
+ console.error('memweave: unexpected error:', err);
48
+ process.exit(1);
49
+ });
package/dist/cli.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * MemWeave CLI 鈥?design spec 搂9.11.
3
+ *
4
+ * Subcommands (all optional; running without args shows help):
5
+ *
6
+ * memweave start Start the HTTP server + background workers (default)
7
+ * memweave stop Stop a running memweave-server (via PID file)
8
+ * memweave status Probe /api/v1/health
9
+ * memweave init Create default config, DB, and device key
10
+ * memweave doctor Check dependencies, port, DB, embedding/LLM config
11
+ * memweave mcp Start the stdio MCP shim
12
+ * memweave migrate Run schema migration (idempotent)
13
+ * memweave backup [path] Copy the SQLite DB to a snapshot file
14
+ * memweave help Show help
15
+ *
16
+ * Note: this file is the *parser / dispatcher*. The actual logic for each
17
+ * command lives in `./commands/`. The CLI never edits `bootstrap.ts` 鈥?the
18
+ * default `start` command simply invokes the existing server bootstrap.
19
+ */
20
+ import { runCommand } from './commands/index.js';
21
+ export function parseCli(argv) {
22
+ const [, , cmd, ...rest] = argv;
23
+ if (!cmd || cmd === '-h' || cmd === '--help')
24
+ return { command: 'help', args: [] };
25
+ if (cmd === '-v' || cmd === '--version' || cmd === 'version')
26
+ return { command: 'version', args: [] };
27
+ const known = ['start', 'stop', 'status', 'init', 'doctor', 'migrate', 'backup', 'help'];
28
+ if (cmd === 'mcp') {
29
+ throw new CliError('The `mcp` subcommand was removed in v0.2.\n' +
30
+ 'Install the @mem-weave/mcp package and run its `memweave-mcp` bin instead:\n' +
31
+ ' npm install -g @mem-weave/mcp\n' +
32
+ ' memweave-mcp');
33
+ }
34
+ if (known.includes(cmd)) {
35
+ return { command: cmd, args: rest };
36
+ }
37
+ throw new CliError(`Unknown command: ${cmd}\nRun \`memweave help\` for usage.`);
38
+ }
39
+ export class CliError extends Error {
40
+ constructor(message) {
41
+ super(message);
42
+ this.name = 'CliError';
43
+ }
44
+ }
45
+ export async function runCli(invocation) {
46
+ const ctx = {
47
+ env: invocation.env,
48
+ args: invocation.args,
49
+ configPath: invocation.configPath
50
+ };
51
+ return runCommand(invocation.command, ctx);
52
+ }
53
+ export { runCommand } from './commands/index.js';
@@ -0,0 +1,28 @@
1
+ import { copyFileSync, existsSync, statSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { mkdirSync } from 'node:fs';
4
+ import { loadConfig } from '../core/config.js';
5
+ import { expandPath } from '../core/config.js';
6
+ /**
7
+ * `memweave backup [path]` — copy the SQLite DB to a snapshot file.
8
+ *
9
+ * Uses SQLite's recommended "backup" semantics: a simple file copy. For
10
+ * very large DBs a proper `.backup` would be safer, but file copy is
11
+ * acceptable for v1 since we use WAL mode.
12
+ */
13
+ export const backupCommand = async (ctx) => {
14
+ const config = loadConfig(ctx.configPath);
15
+ const dbPath = expandPath(config.storage.path);
16
+ if (!existsSync(dbPath)) {
17
+ return { ok: false, message: `Database file not found: ${dbPath}` };
18
+ }
19
+ const dest = ctx.args[0] ?? `${dbPath}.backup-${new Date().toISOString().replace(/[:.]/g, '-')}.db`;
20
+ mkdirSync(dirname(dest), { recursive: true });
21
+ copyFileSync(dbPath, dest);
22
+ const srcSize = statSync(dbPath).size;
23
+ return {
24
+ ok: true,
25
+ message: `Backed up ${dbPath} (${srcSize} bytes) to ${dest}`,
26
+ data: { source: dbPath, dest, size: srcSize }
27
+ };
28
+ };
@@ -0,0 +1,108 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { tmpdir } from 'node:os';
4
+ import { loadConfig } from '../core/config.js';
5
+ import { expandPath } from '../core/config.js';
6
+ import { openDatabase } from '../db/database.js';
7
+ /**
8
+ * `memweave doctor` — environment / configuration health check.
9
+ *
10
+ * Checks:
11
+ * - config loads cleanly
12
+ * - DB file is reachable and the schema applies
13
+ * - port is free
14
+ * - sqlite-vec extension loads (when used)
15
+ * - LLM provider has a usable API key (when not noop)
16
+ * - Embedding provider has a usable config (when not noop)
17
+ */
18
+ export const doctorCommand = async (ctx) => {
19
+ const results = [];
20
+ // 1. Config loads
21
+ let config;
22
+ try {
23
+ config = loadConfig(ctx.configPath);
24
+ results.push({ name: 'config', ok: true, detail: 'loaded' });
25
+ }
26
+ catch (err) {
27
+ return { ok: false, message: 'Config load failed', data: { results, error: err.message } };
28
+ }
29
+ const dbPath = expandPath(config.storage.path);
30
+ // 2. DB open + schema
31
+ try {
32
+ const db = openDatabase(dbPath, { vectorDimensions: config.embedding.dimensions });
33
+ try {
34
+ const tables = db.prepare(`SELECT COUNT(*) as c FROM sqlite_master WHERE type='table'`).get();
35
+ results.push({ name: 'database', ok: true, detail: `${tables.c} tables; path=${dbPath}` });
36
+ }
37
+ finally {
38
+ db.close();
39
+ }
40
+ }
41
+ catch (err) {
42
+ results.push({ name: 'database', ok: false, detail: err.message });
43
+ }
44
+ // 3. Port available
45
+ try {
46
+ const probe = await fetch(`http://127.0.0.1:${config.server.port}/api/v1/health`, {
47
+ signal: AbortSignal.timeout(500)
48
+ });
49
+ if (probe.ok) {
50
+ results.push({ name: 'port', ok: true, detail: `port ${config.server.port} already serving (likely already running)` });
51
+ }
52
+ else {
53
+ results.push({ name: 'port', ok: true, detail: `port ${config.server.port} responding but not OK` });
54
+ }
55
+ }
56
+ catch {
57
+ results.push({ name: 'port', ok: true, detail: `port ${config.server.port} appears free` });
58
+ }
59
+ // 4. sqlite-vec
60
+ try {
61
+ const db = openDatabase(dbPath, { vectorDimensions: config.embedding.dimensions });
62
+ try {
63
+ const v = db.prepare(`SELECT vec_version() AS v`).get();
64
+ results.push({ name: 'sqlite-vec', ok: true, detail: v.v });
65
+ }
66
+ catch (err) {
67
+ results.push({ name: 'sqlite-vec', ok: false, detail: err.message });
68
+ }
69
+ finally {
70
+ db.close();
71
+ }
72
+ }
73
+ catch {
74
+ results.push({ name: 'sqlite-vec', ok: false, detail: 'extension failed to load' });
75
+ }
76
+ // 5. LLM
77
+ if (config.llm.provider === 'openai-compatible') {
78
+ const hasKey = Boolean(config.llm.apiKey);
79
+ results.push({ name: 'llm', ok: hasKey, detail: hasKey ? 'apiKey set' : 'apiKey missing' });
80
+ }
81
+ else {
82
+ results.push({ name: 'llm', ok: true, detail: 'noop' });
83
+ }
84
+ // 6. Embedding
85
+ if (config.embedding.provider === 'openai-compatible') {
86
+ const ok = Boolean(config.embedding.apiKey) && Boolean(config.embedding.baseUrl);
87
+ results.push({ name: 'embedding', ok, detail: ok ? 'configured' : 'apiKey or baseUrl missing' });
88
+ }
89
+ else if (config.embedding.provider === 'local-xenova') {
90
+ results.push({ name: 'embedding', ok: true, detail: 'local-xenova (stub in v1)' });
91
+ }
92
+ else {
93
+ results.push({ name: 'embedding', ok: true, detail: 'noop' });
94
+ }
95
+ // 7. PID file existence (informational)
96
+ const pidPath = join(tmpdir(), 'memweave.pid');
97
+ results.push({
98
+ name: 'pid-file',
99
+ ok: true,
100
+ detail: existsSync(pidPath) ? `${pidPath} present` : 'none (server not started via CLI)'
101
+ });
102
+ const allOk = results.every((r) => r.ok);
103
+ return {
104
+ ok: allOk,
105
+ message: allOk ? 'All checks passed.' : 'Some checks failed.',
106
+ data: { results }
107
+ };
108
+ };
@@ -0,0 +1,29 @@
1
+ const HELP = `MemWeave — persistent cross-device memory for AI coding agents
2
+
3
+ Usage:
4
+ memweave <command> [options]
5
+
6
+ Commands:
7
+ start Start the HTTP server + background workers
8
+ stop Stop a running memweave-server (via PID file)
9
+ status Probe /api/v1/health
10
+ init Create default config, DB, and device key
11
+ doctor Check dependencies, port, DB, embedding/LLM config
12
+ mcp Start the stdio MCP shim
13
+ migrate Apply schema (idempotent) and preview consolidation
14
+ backup [path] Copy the SQLite DB to a snapshot file
15
+ help Show this help
16
+ version Print version
17
+
18
+ Config (env or ~/.memweave/config.jsonc):
19
+ MEMWEAVE_CONFIG Path to config.jsonc
20
+
21
+ Examples:
22
+ memweave init
23
+ memweave doctor
24
+ memweave start
25
+ memweave backup ~/backups/memweave-$(date +%Y%m%d).db
26
+ `;
27
+ export const helpCommand = async (_ctx) => {
28
+ return { ok: true, message: HELP };
29
+ };
@@ -0,0 +1,27 @@
1
+ import { startCommand } from './start.js';
2
+ import { stopCommand } from './stop.js';
3
+ import { statusCommand } from './status.js';
4
+ import { initCommand } from './init.js';
5
+ import { doctorCommand } from './doctor.js';
6
+ import { migrateCommand } from './migrate.js';
7
+ import { backupCommand } from './backup.js';
8
+ import { helpCommand } from './help.js';
9
+ import { versionCommand } from './version.js';
10
+ const handlers = {
11
+ start: startCommand,
12
+ stop: stopCommand,
13
+ status: statusCommand,
14
+ init: initCommand,
15
+ doctor: doctorCommand,
16
+ migrate: migrateCommand,
17
+ backup: backupCommand,
18
+ help: helpCommand,
19
+ version: versionCommand
20
+ };
21
+ export async function runCommand(command, ctx) {
22
+ const handler = handlers[command];
23
+ if (!handler) {
24
+ return { ok: false, message: `No handler for command: ${command}` };
25
+ }
26
+ return handler(ctx);
27
+ }
@@ -0,0 +1,58 @@
1
+ import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { randomBytes } from 'node:crypto';
5
+ import { loadConfig } from '../core/config.js';
6
+ import { expandPath, expandEnv } from '../core/config.js';
7
+ import { openDatabase } from '../db/database.js';
8
+ const DEFAULT_CONFIG_JSONC = `{
9
+ // MemWeave config (JSONC: comments allowed)
10
+ "server": {
11
+ "host": "127.0.0.1",
12
+ "port": 3131
13
+ },
14
+ "storage": {
15
+ "path": "~/.memweave/data/memweave.db"
16
+ },
17
+ "auth": {
18
+ "defaultTenantName": "default",
19
+ "deviceApiKey": "REPLACE_WITH_RANDOM_KEY"
20
+ }
21
+ }
22
+ `;
23
+ /**
24
+ * `memweave init` — create the config dir, default config file (if missing),
25
+ * the SQLite DB, and a default tenant + device with a random API key.
26
+ *
27
+ * Idempotent: re-running `init` won't clobber existing state. It only
28
+ * creates things that don't already exist.
29
+ */
30
+ export const initCommand = async (ctx) => {
31
+ const configDir = join(homedir(), '.memweave');
32
+ mkdirSync(configDir, { recursive: true });
33
+ mkdirSync(join(configDir, 'data'), { recursive: true });
34
+ const configPath = ctx.configPath ?? join(configDir, 'config.jsonc');
35
+ if (!existsSync(configPath)) {
36
+ const apiKey = randomBytes(24).toString('hex');
37
+ const body = DEFAULT_CONFIG_JSONC.replace('REPLACE_WITH_RANDOM_KEY', apiKey);
38
+ writeFileSync(configPath, body, 'utf8');
39
+ }
40
+ const config = loadConfig(configPath);
41
+ const dbPath = expandPath(config.storage.path);
42
+ // Force env:// expansion in case the config uses placeholders
43
+ void expandEnv(config.auth.deviceApiKey);
44
+ // Open DB (applies schema) and ensure default tenant + device
45
+ const db = openDatabase(dbPath);
46
+ try {
47
+ db.prepare('INSERT OR IGNORE INTO tenants (id, name, api_key_hash, created_at) VALUES (?, ?, ?, ?)')
48
+ .run('tenant_default', config.auth.defaultTenantName, config.auth.deviceApiKey, Date.now());
49
+ }
50
+ finally {
51
+ db.close();
52
+ }
53
+ return {
54
+ ok: true,
55
+ message: `Initialized memweave at ${configDir}`,
56
+ data: { configPath, dbPath }
57
+ };
58
+ };
@@ -0,0 +1,25 @@
1
+ import { loadConfig } from '../core/config.js';
2
+ import { expandPath } from '../core/config.js';
3
+ import { openDatabase } from '../db/database.js';
4
+ import { runConsolidation } from '../workers/consolidator.js';
5
+ /**
6
+ * `memweave migrate` — apply the schema (idempotent: CREATE TABLE IF NOT EXISTS)
7
+ * and run a one-shot consolidation pass.
8
+ */
9
+ export const migrateCommand = async (ctx) => {
10
+ const config = loadConfig(ctx.configPath);
11
+ const dbPath = expandPath(config.storage.path);
12
+ const db = openDatabase(dbPath);
13
+ try {
14
+ const tables = db.prepare(`SELECT COUNT(*) as c FROM sqlite_master WHERE type='table'`).get();
15
+ const consolidation = runConsolidation(db, 'tenant_default', { dryRun: true });
16
+ return {
17
+ ok: true,
18
+ message: `Schema applied. ${tables.c} tables present.`,
19
+ data: { dbPath, tableCount: tables.c, consolidationPreview: consolidation }
20
+ };
21
+ }
22
+ finally {
23
+ db.close();
24
+ }
25
+ };
@@ -0,0 +1,29 @@
1
+ import { loadConfig } from '../core/config.js';
2
+ import { expandPath } from '../core/config.js';
3
+ /**
4
+ * `memweave start` — load config, open DB, start HTTP server + schedulers.
5
+ *
6
+ * This delegates to the same `createHttpServer` + `startConsolidationScheduler`
7
+ * used by the legacy `src/server/bootstrap.ts`, so behavior is identical.
8
+ */
9
+ export const startCommand = async (ctx) => {
10
+ const config = loadConfig(ctx.configPath);
11
+ const dbPath = expandPath(config.storage.path);
12
+ // Lazy import to avoid pulling fastify in tests of the parser itself.
13
+ const { createHttpServer } = await import('../server/http.js');
14
+ const { startConsolidationScheduler } = await import('../server/scheduler.js');
15
+ const app = await createHttpServer({ dbPath });
16
+ if (config.consolidation.enabled) {
17
+ startConsolidationScheduler({
18
+ dbPath,
19
+ intervalMs: config.consolidation.intervalHours * 60 * 60 * 1000,
20
+ runOnStart: true
21
+ });
22
+ }
23
+ await app.listen({ host: config.server.host, port: config.server.port });
24
+ return {
25
+ ok: true,
26
+ message: `memweave-server listening on ${config.server.host}:${config.server.port}`,
27
+ data: { host: config.server.host, port: config.server.port, dbPath }
28
+ };
29
+ };
@@ -0,0 +1,19 @@
1
+ import { loadConfig } from '../core/config.js';
2
+ /**
3
+ * `memweave status` — probe the running memweave-server's health endpoint.
4
+ */
5
+ export const statusCommand = async (ctx) => {
6
+ const config = loadConfig(ctx.configPath);
7
+ const url = `http://${config.server.host}:${config.server.port}/api/v1/health`;
8
+ try {
9
+ const res = await fetch(url, { signal: AbortSignal.timeout(3000) });
10
+ if (!res.ok) {
11
+ return { ok: false, message: `Health check returned ${res.status}` };
12
+ }
13
+ const data = await res.json();
14
+ return { ok: true, message: 'memweave-server is up.', data };
15
+ }
16
+ catch (err) {
17
+ return { ok: false, message: `Cannot reach ${url}: ${err.message}` };
18
+ }
19
+ };
@@ -0,0 +1,46 @@
1
+ import { readFileSync, unlinkSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { tmpdir } from 'node:os';
4
+ import { loadConfig } from '../core/config.js';
5
+ import { expandPath } from '../core/config.js';
6
+ const PID_FILENAME = 'memweave.pid';
7
+ /**
8
+ * `memweave stop` — read PID file, send SIGTERM, wait, SIGKILL fallback.
9
+ */
10
+ export const stopCommand = async (ctx) => {
11
+ const config = loadConfig(ctx.configPath);
12
+ const dbPath = expandPath(config.storage.path);
13
+ const pidPath = join(tmpdir(), PID_FILENAME);
14
+ if (!existsSync(pidPath)) {
15
+ return { ok: true, message: 'No PID file found; nothing to stop.', data: { pidPath } };
16
+ }
17
+ const pid = Number.parseInt(readFileSync(pidPath, 'utf8').trim(), 10);
18
+ if (!Number.isFinite(pid) || pid <= 0) {
19
+ return { ok: false, message: `Invalid PID file: ${pidPath}` };
20
+ }
21
+ try {
22
+ process.kill(pid, 'SIGTERM');
23
+ }
24
+ catch (err) {
25
+ return { ok: false, message: `Failed to send SIGTERM to PID ${pid}: ${err.message}` };
26
+ }
27
+ // Give it a moment to exit
28
+ await new Promise((r) => setTimeout(r, 500));
29
+ try {
30
+ process.kill(pid, 0);
31
+ // Still alive, escalate
32
+ process.kill(pid, 'SIGKILL');
33
+ await new Promise((r) => setTimeout(r, 200));
34
+ }
35
+ catch {
36
+ // Already dead
37
+ }
38
+ try {
39
+ unlinkSync(pidPath);
40
+ }
41
+ catch {
42
+ // ignore
43
+ }
44
+ void dbPath;
45
+ return { ok: true, message: `Sent SIGTERM/SIGKILL to PID ${pid}`, data: { pid } };
46
+ };
@@ -0,0 +1,21 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const PACKAGE_NAME = 'memweave';
4
+ export const versionCommand = async (_ctx) => {
5
+ // Read the package.json of this workspace
6
+ let version = 'unknown';
7
+ try {
8
+ // The compiled dist layout puts package.json at the workspace root;
9
+ // tests are run from the workspace root, so process.cwd() works.
10
+ const pkgPath = join(process.cwd(), 'package.json');
11
+ if (existsSync(pkgPath)) {
12
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
13
+ if (pkg.name === PACKAGE_NAME && pkg.version)
14
+ version = pkg.version;
15
+ }
16
+ }
17
+ catch {
18
+ // ignore
19
+ }
20
+ return { ok: true, message: `${PACKAGE_NAME} ${version}` };
21
+ };