@remnic/server 1.0.5 → 9.3.515

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @remnic/server
2
2
 
3
- Standalone Remnic memory server -- HTTP and MCP interfaces without requiring OpenClaw.
3
+ Standalone Remnic memory and context server -- HTTP and MCP interfaces without requiring OpenClaw.
4
4
 
5
- Part of [Remnic](https://github.com/joshuaswarren/remnic), the universal memory layer for AI agents.
5
+ Part of [Remnic](https://github.com/joshuaswarren/remnic), open-source memory and context for user-aware agents.
6
6
 
7
7
  ## Install
8
8
 
@@ -23,15 +23,28 @@ Both interfaces connect to the same [`@remnic/core`](https://www.npmjs.com/packa
23
23
 
24
24
  ## Usage
25
25
 
26
+ Run the standalone server:
27
+
28
+ ```bash
29
+ npx --package @remnic/server remnic-server --help
30
+ npx --package @remnic/server remnic-server --port 4318
31
+ ```
32
+
33
+ The package also ships the legacy `engram-server` binary for compatibility.
34
+ The bin wrappers are source-controlled so package managers can link them during
35
+ workspace installs; release builds verify that both targets have Node shebangs
36
+ and can start their help command before publish.
37
+
26
38
  ```typescript
27
- import { createServer } from "@remnic/server";
39
+ import { startServer } from "@remnic/server";
28
40
 
29
- const server = createServer({
41
+ const server = await startServer({
30
42
  port: 3141,
31
43
  authToken: process.env.REMNIC_AUTH_TOKEN,
32
44
  });
33
45
 
34
- await server.start();
46
+ console.log(`Remnic server listening on http://${server.host}:${server.port}`);
47
+ await server.stop();
35
48
  ```
36
49
 
37
50
  ## License
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runServerBin } from "./server-bin.js";
3
+
4
+ await runServerBin("engram-server");
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runServerBin } from "./server-bin.js";
3
+
4
+ await runServerBin("remnic-server");
@@ -0,0 +1,41 @@
1
+ export function shouldPrintHelpWithoutCli(argv) {
2
+ return argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h");
3
+ }
4
+
5
+ export async function runServerBin(commandName, options = {}) {
6
+ const argv = options.argv ?? process.argv.slice(2);
7
+ const help = `
8
+ ${commandName} - Standalone Remnic memory server
9
+
10
+ Usage:
11
+ ${commandName} [options]
12
+
13
+ Options:
14
+ --config <path> Path to config file (default: remnic.config.json)
15
+ --host <addr> Bind address (default: 127.0.0.1)
16
+ --port <number> Port number (default: 4318)
17
+ --auth-token <tok> Bearer token for auth (or set REMNIC_AUTH_TOKEN)
18
+ --help Show this help
19
+
20
+ Environment:
21
+ REMNIC_CONFIG_PATH Config file path (ENGRAM_CONFIG_PATH also supported)
22
+ REMNIC_PORT Server port (ENGRAM_PORT also supported)
23
+ REMNIC_HOST Bind address (ENGRAM_HOST also supported)
24
+ REMNIC_AUTH_TOKEN Auth bearer token (ENGRAM_AUTH_TOKEN also supported)
25
+ REMNIC_MEMORY_DIR Override memory directory (ENGRAM_MEMORY_DIR also supported)
26
+ OPENAI_API_KEY OpenAI API key for extraction; ignored when config sets openaiApiKey=false
27
+ `;
28
+
29
+ if (shouldPrintHelpWithoutCli(argv)) {
30
+ (options.stdout ?? console.log)(help);
31
+ return;
32
+ }
33
+
34
+ const loadCliMain = options.loadCliMain ?? (() => import("../dist/index.js"));
35
+ const { cliMain } = await loadCliMain();
36
+
37
+ await cliMain(argv).catch((err) => {
38
+ (options.stderr ?? process.stderr.write.bind(process.stderr))(`Fatal: ${err instanceof Error ? err.message : String(err)}\n`);
39
+ (options.exit ?? process.exit)(1);
40
+ });
41
+ }
@@ -0,0 +1,64 @@
1
+ import { PluginConfig, EngramAccessService, EngramAccessHttpServer } from '@remnic/core';
2
+
3
+ /**
4
+ * @remnic/server
5
+ *
6
+ * Standalone Remnic memory server.
7
+ *
8
+ * Loads config from `remnic.config.json` (or env vars), creates an Orchestrator,
9
+ * and starts the HTTP access server with MCP endpoint — no OpenClaw required.
10
+ *
11
+ * Usage:
12
+ * npx --package @remnic/server remnic-server
13
+ * npx --package @remnic/server remnic-server --config ./my-remnic.json
14
+ * npx --package @remnic/server remnic-server --port 4320
15
+ */
16
+
17
+ interface ServerConfig {
18
+ remnic: Record<string, unknown>;
19
+ server: {
20
+ host?: string;
21
+ port?: unknown;
22
+ authToken?: string;
23
+ principal?: string;
24
+ maxBodyBytes?: number;
25
+ adminConsoleEnabled?: boolean;
26
+ adminConsolePublicDir?: string;
27
+ };
28
+ }
29
+ interface ParsedServerConfig {
30
+ host: string;
31
+ port: number;
32
+ authToken?: string;
33
+ principal?: string;
34
+ maxBodyBytes?: number;
35
+ adminConsoleEnabled: boolean;
36
+ adminConsolePublicDir?: string;
37
+ }
38
+ declare function parseServerConfig(raw: Partial<ServerConfig["server"]>, options?: {
39
+ portSource?: string;
40
+ }): ParsedServerConfig;
41
+ declare function loadConfigFile(configPath: string): ServerConfig;
42
+ declare function mergeRemnicConfigForServer(fileRemnic: Record<string, unknown>, envRemnic: Record<string, unknown> | undefined): Record<string, unknown>;
43
+ interface ServerResult {
44
+ config: PluginConfig;
45
+ service: EngramAccessService;
46
+ httpServer: EngramAccessHttpServer;
47
+ host: string;
48
+ port: number;
49
+ /** Stop HTTP, cancel startup work, abort deferred init, and destroy the orchestrator. */
50
+ stop: () => Promise<void>;
51
+ /** Cancel any pending startup-sync retry timers. Called automatically on shutdown. */
52
+ cancelStartupSync: () => void;
53
+ /** Abort deferred orchestrator initialization (QMD sync, warmup, cache). */
54
+ abortDeferredInit: () => void;
55
+ }
56
+ declare function startServer(options?: {
57
+ configPath?: string;
58
+ host?: string;
59
+ port?: number;
60
+ authToken?: string;
61
+ }): Promise<ServerResult>;
62
+ declare function cliMain(argv?: string[]): Promise<void>;
63
+
64
+ export { type ParsedServerConfig, type ServerConfig, type ServerResult, cliMain, loadConfigFile, mergeRemnicConfigForServer, parseServerConfig, startServer };
package/dist/index.js CHANGED
@@ -3,14 +3,72 @@
3
3
  // src/index.ts
4
4
  import fs from "fs";
5
5
  import path from "path";
6
- import { parseConfig, Orchestrator, EngramAccessService, EngramAccessHttpServer, initLogger, log, getAllValidTokens, getAllValidTokensCached } from "@remnic/core";
6
+ import { parseConfig, isOpenaiApiKeyDisabled, Orchestrator, EngramAccessService, EngramAccessHttpServer, initLogger, log, getAllValidTokens, getAllValidTokensCached, expandTildePath } from "@remnic/core";
7
7
  function readCompatEnv(primary, legacy) {
8
8
  return process.env[primary] ?? process.env[legacy];
9
9
  }
10
+ function parseServerPort(value, source) {
11
+ const port = typeof value === "string" ? Number(value.trim()) : value;
12
+ if (typeof port !== "number" || !Number.isInteger(port) || port < 1 || port > 65535) {
13
+ throw new Error(`Invalid ${source}: expected an integer port from 1 to 65535`);
14
+ }
15
+ return port;
16
+ }
17
+ function parseOptionalString(value, source) {
18
+ if (value === void 0) return void 0;
19
+ if (typeof value !== "string") {
20
+ throw new Error(`Invalid ${source}: expected a string`);
21
+ }
22
+ return value;
23
+ }
24
+ function parseOptionalNonEmptyString(value, source) {
25
+ const parsed = parseOptionalString(value, source);
26
+ if (parsed === void 0) return void 0;
27
+ if (parsed.trim() === "") {
28
+ throw new Error(`Invalid ${source}: expected a non-empty string`);
29
+ }
30
+ return parsed;
31
+ }
32
+ function parseOptionalPositiveInteger(value, source) {
33
+ if (value === void 0) return void 0;
34
+ const parsed = typeof value === "string" ? Number(value.trim()) : value;
35
+ if (typeof parsed !== "number" || !Number.isInteger(parsed) || parsed < 1) {
36
+ throw new Error(`Invalid ${source}: expected a positive integer`);
37
+ }
38
+ return parsed;
39
+ }
40
+ function parseOptionalBoolean(value, source) {
41
+ if (value === void 0) return void 0;
42
+ if (typeof value === "boolean") return value;
43
+ if (typeof value === "string") {
44
+ const normalized = value.trim().toLowerCase();
45
+ if (["true", "1", "yes", "on"].includes(normalized)) return true;
46
+ if (["false", "0", "no", "off"].includes(normalized)) return false;
47
+ }
48
+ throw new Error(`Invalid ${source}: expected a boolean`);
49
+ }
50
+ function parseServerConfig(raw, options) {
51
+ return {
52
+ host: parseOptionalNonEmptyString(raw.host, "server.host") ?? "127.0.0.1",
53
+ port: raw.port === void 0 ? 4318 : parseServerPort(raw.port, options?.portSource ?? "server.port"),
54
+ authToken: parseOptionalString(raw.authToken, "server.authToken"),
55
+ principal: parseOptionalString(raw.principal, "server.principal"),
56
+ maxBodyBytes: parseOptionalPositiveInteger(raw.maxBodyBytes, "server.maxBodyBytes"),
57
+ adminConsoleEnabled: parseOptionalBoolean(raw.adminConsoleEnabled, "server.adminConsoleEnabled") ?? false,
58
+ adminConsolePublicDir: parseOptionalString(raw.adminConsolePublicDir, "server.adminConsolePublicDir")
59
+ };
60
+ }
61
+ function resolveUserPath(value) {
62
+ return path.resolve(expandTildePath(value));
63
+ }
10
64
  function resolveConfigPath(cliPath) {
11
- if (cliPath) return path.resolve(cliPath);
65
+ if (cliPath) {
66
+ return { path: resolveUserPath(cliPath), explicit: true, source: "--config" };
67
+ }
12
68
  const envPath = readCompatEnv("REMNIC_CONFIG_PATH", "ENGRAM_CONFIG_PATH");
13
- if (envPath) return path.resolve(envPath);
69
+ if (envPath) {
70
+ return { path: resolveUserPath(envPath), explicit: true, source: "REMNIC_CONFIG_PATH/ENGRAM_CONFIG_PATH" };
71
+ }
14
72
  const homeDir = process.env.HOME ?? "~";
15
73
  const candidates = [
16
74
  path.join(process.cwd(), "remnic.config.json"),
@@ -19,24 +77,59 @@ function resolveConfigPath(cliPath) {
19
77
  path.join(homeDir, ".config", "engram", "config.json")
20
78
  ];
21
79
  for (const candidate of candidates) {
22
- if (fs.existsSync(candidate)) return candidate;
80
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
81
+ return { path: candidate, explicit: false, source: "auto-discovery" };
82
+ }
23
83
  }
24
- return path.join(homeDir, ".config", "remnic", "config.json");
84
+ return { path: path.join(homeDir, ".config", "remnic", "config.json"), explicit: false, source: "auto-discovery" };
85
+ }
86
+ function isPlainRecord(value) {
87
+ return !!value && typeof value === "object" && !Array.isArray(value);
88
+ }
89
+ function requirePlainConfigBlock(raw, key, configPath) {
90
+ const value = raw[key];
91
+ if (value === void 0) return void 0;
92
+ if (!isPlainRecord(value)) {
93
+ throw new Error(`Invalid config file ${configPath}: ${key} must be a JSON object`);
94
+ }
95
+ return value;
25
96
  }
26
97
  function loadConfigFile(configPath) {
27
98
  const raw = JSON.parse(fs.readFileSync(configPath, "utf8"));
99
+ if (!isPlainRecord(raw)) {
100
+ throw new Error(`Invalid config file ${configPath}: top-level config must be a JSON object`);
101
+ }
102
+ const remnic = requirePlainConfigBlock(raw, "remnic", configPath);
103
+ const engram = requirePlainConfigBlock(raw, "engram", configPath);
104
+ const server = requirePlainConfigBlock(raw, "server", configPath);
28
105
  return {
29
- remnic: raw.remnic ?? raw.engram ?? raw ?? {},
30
- server: raw.server ?? {}
106
+ remnic: remnic ?? engram ?? raw,
107
+ server: server ?? {}
31
108
  };
32
109
  }
110
+ function loadResolvedConfig(resolved) {
111
+ if (!fs.existsSync(resolved.path)) {
112
+ if (resolved.explicit) {
113
+ throw new Error(`Config file from ${resolved.source} not found: ${resolved.path}`);
114
+ }
115
+ return { remnic: {}, server: {} };
116
+ }
117
+ const stat = fs.statSync(resolved.path);
118
+ if (!stat.isFile()) {
119
+ if (!resolved.explicit) {
120
+ return { remnic: {}, server: {} };
121
+ }
122
+ throw new Error(`Config file from ${resolved.source} is not a regular file: ${resolved.path}`);
123
+ }
124
+ return loadConfigFile(resolved.path);
125
+ }
33
126
  function envOverrides() {
34
127
  const overrides = {};
35
128
  const remnic = {};
36
129
  const port = readCompatEnv("REMNIC_PORT", "ENGRAM_PORT");
37
130
  const host = readCompatEnv("REMNIC_HOST", "ENGRAM_HOST");
38
131
  const authToken = readCompatEnv("REMNIC_AUTH_TOKEN", "ENGRAM_AUTH_TOKEN");
39
- if (port) overrides.port = parseInt(port, 10);
132
+ if (port) overrides.port = port;
40
133
  if (host) overrides.host = host;
41
134
  if (authToken) overrides.authToken = authToken;
42
135
  if (process.env.OPENAI_API_KEY) remnic.openaiApiKey = process.env.OPENAI_API_KEY;
@@ -44,6 +137,13 @@ function envOverrides() {
44
137
  if (memoryDir) remnic.memoryDir = memoryDir;
45
138
  return { ...overrides, ...Object.keys(remnic).length > 0 ? { remnic } : {} };
46
139
  }
140
+ function mergeRemnicConfigForServer(fileRemnic, envRemnic) {
141
+ const effectiveEnvRemnic = { ...envRemnic ?? {} };
142
+ if (isOpenaiApiKeyDisabled(fileRemnic.openaiApiKey)) {
143
+ delete effectiveEnvRemnic.openaiApiKey;
144
+ }
145
+ return { ...fileRemnic, ...effectiveEnvRemnic };
146
+ }
47
147
  function abortableDelay(ms, signal) {
48
148
  if (signal.aborted) return Promise.resolve();
49
149
  return new Promise((resolve) => {
@@ -55,47 +155,87 @@ function abortableDelay(ms, signal) {
55
155
  signal.addEventListener("abort", onAbort, { once: true });
56
156
  });
57
157
  }
158
+ async function cleanupFailedStartup(orchestrator, httpServer) {
159
+ try {
160
+ await httpServer.stop();
161
+ } catch (err) {
162
+ log.warn(`HTTP startup failure cleanup could not stop server: ${err}`);
163
+ }
164
+ try {
165
+ await orchestrator.destroy();
166
+ } catch (err) {
167
+ log.warn(`HTTP startup failure cleanup could not destroy orchestrator: ${err}`);
168
+ }
169
+ }
58
170
  async function startServer(options) {
59
171
  initLogger();
60
- const configPath = resolveConfigPath(options?.configPath);
61
- const fileConfig = fs.existsSync(configPath) ? loadConfigFile(configPath) : { remnic: {}, server: {} };
172
+ const resolvedConfigPath = resolveConfigPath(options?.configPath);
173
+ const fileConfig = loadResolvedConfig(resolvedConfigPath);
62
174
  const env = envOverrides();
63
- const remnicConfig = { ...fileConfig.remnic, ...env.remnic ?? {} };
175
+ const { remnic: envRemnic, ...envServer } = env;
176
+ const remnicConfig = mergeRemnicConfigForServer(fileConfig.remnic, envRemnic);
177
+ const cliServerConfig = {};
178
+ if (options?.host !== void 0) cliServerConfig.host = options.host;
179
+ if (options?.port !== void 0) cliServerConfig.port = parseServerPort(options.port, "options.port");
180
+ if (options?.authToken !== void 0) cliServerConfig.authToken = options.authToken;
64
181
  const serverConfig = {
65
182
  ...fileConfig.server,
66
- ...env,
67
- ...options?.host ? { host: options.host } : {},
68
- ...options?.port ? { port: options.port } : {},
69
- ...options?.authToken ? { authToken: options.authToken } : {}
183
+ ...envServer,
184
+ ...cliServerConfig
70
185
  };
186
+ const portSource = cliServerConfig.port !== void 0 ? "options.port" : envServer.port !== void 0 ? "REMNIC_PORT/ENGRAM_PORT" : "server.port";
187
+ const parsedServerConfig = parseServerConfig(serverConfig, { portSource });
71
188
  const config = parseConfig(remnicConfig);
72
189
  const orchestrator = new Orchestrator(config);
73
190
  await orchestrator.initialize();
74
191
  const service = new EngramAccessService(orchestrator);
75
- const authToken = serverConfig.authToken ?? readCompatEnv("REMNIC_AUTH_TOKEN", "ENGRAM_AUTH_TOKEN") ?? "";
192
+ const authToken = parsedServerConfig.authToken ?? readCompatEnv("REMNIC_AUTH_TOKEN", "ENGRAM_AUTH_TOKEN") ?? "";
76
193
  if (!authToken && getAllValidTokens().length === 0) {
77
194
  log.warn("No auth token set \u2014 server will reject all requests. Set REMNIC_AUTH_TOKEN, server.authToken in config, or generate tokens with 'remnic token generate'.");
78
195
  }
79
196
  const httpServer = new EngramAccessHttpServer({
80
197
  service,
81
- host: serverConfig.host ?? "127.0.0.1",
82
- port: serverConfig.port ?? 4318,
198
+ host: parsedServerConfig.host,
199
+ port: parsedServerConfig.port,
83
200
  authToken: authToken || void 0,
84
201
  authTokensGetter: () => getAllValidTokensCached(),
85
- principal: serverConfig.principal,
86
- maxBodyBytes: serverConfig.maxBodyBytes,
87
- adminConsoleEnabled: serverConfig.adminConsoleEnabled ?? false,
202
+ principal: parsedServerConfig.principal,
203
+ maxBodyBytes: parsedServerConfig.maxBodyBytes,
204
+ adminConsoleEnabled: parsedServerConfig.adminConsoleEnabled,
205
+ adminConsolePublicDir: parsedServerConfig.adminConsolePublicDir ? path.resolve(expandTildePath(parsedServerConfig.adminConsolePublicDir)) : void 0,
88
206
  citationsEnabled: config.citationsEnabled,
89
207
  citationsAutoDetect: config.citationsAutoDetect
90
208
  });
91
- const { host, port } = await httpServer.start();
209
+ let host;
210
+ let port;
211
+ try {
212
+ ({ host, port } = await httpServer.start());
213
+ } catch (err) {
214
+ await cleanupFailedStartup(orchestrator, httpServer);
215
+ throw err;
216
+ }
92
217
  const startupSyncAbort = new AbortController();
93
218
  const originalStop = httpServer.stop.bind(httpServer);
94
- httpServer.stop = async () => {
95
- startupSyncAbort.abort();
96
- return originalStop();
219
+ let stopPromise;
220
+ const stop = async () => {
221
+ if (stopPromise) return stopPromise;
222
+ stopPromise = (async () => {
223
+ startupSyncAbort.abort();
224
+ orchestrator.abortDeferredInit();
225
+ try {
226
+ await originalStop();
227
+ } finally {
228
+ await orchestrator.destroy();
229
+ }
230
+ })();
231
+ return stopPromise;
97
232
  };
233
+ httpServer.stop = stop;
98
234
  orchestrator.deferredReady.then(() => {
235
+ if (startupSyncAbort.signal.aborted) {
236
+ log.debug("QMD startup-sync: cancelled before deferred init completed");
237
+ return;
238
+ }
99
239
  if (!config.qmdEnabled || orchestrator.qmd.debugStatus() === "backend=noop") {
100
240
  log.debug("QMD startup-sync: search disabled or noop backend, skipping retries");
101
241
  return;
@@ -106,6 +246,10 @@ async function startServer(options) {
106
246
  return;
107
247
  }
108
248
  const RETRY_DELAYS_MS = [5e3, 15e3, 3e4, 6e4, 12e4];
249
+ if (startupSyncAbort.signal.aborted) {
250
+ log.debug("QMD startup-sync retry: cancelled before retry task started");
251
+ return;
252
+ }
109
253
  (async () => {
110
254
  for (const delay of RETRY_DELAYS_MS) {
111
255
  await abortableDelay(delay, startupSyncAbort.signal);
@@ -131,21 +275,39 @@ async function startServer(options) {
131
275
  }).catch((err) => {
132
276
  log.warn(`Deferred init error: ${err}`);
133
277
  });
134
- return { config, service, httpServer, host, port, cancelStartupSync: () => startupSyncAbort.abort(), abortDeferredInit: () => orchestrator.abortDeferredInit() };
278
+ return { config, service, httpServer, host, port, stop, cancelStartupSync: () => startupSyncAbort.abort(), abortDeferredInit: () => orchestrator.abortDeferredInit() };
135
279
  }
280
+ var BOOLEAN_CLI_OPTIONS = /* @__PURE__ */ new Set(["help"]);
281
+ var VALUE_CLI_OPTIONS = /* @__PURE__ */ new Set(["config", "host", "port", "auth-token"]);
136
282
  function parseCliArgs(argv) {
137
283
  const args = {};
138
284
  for (let i = 0; i < argv.length; i++) {
139
285
  const token = argv[i];
286
+ if (token === "-h") {
287
+ args.help = "true";
288
+ continue;
289
+ }
140
290
  if (token.startsWith("--")) {
141
- const key = token.slice(2);
142
- const next = argv[i + 1];
143
- if (next && !next.startsWith("--")) {
144
- args[key] = next;
145
- i++;
146
- } else {
291
+ const [key, inlineValue] = token.slice(2).split(/=(.*)/s, 2);
292
+ if (!key) {
293
+ throw new Error(`Invalid option ${token}`);
294
+ }
295
+ if (BOOLEAN_CLI_OPTIONS.has(key)) {
296
+ if (inlineValue !== void 0) {
297
+ throw new Error(`Option --${key} does not accept a value`);
298
+ }
147
299
  args[key] = "true";
300
+ continue;
301
+ }
302
+ if (!VALUE_CLI_OPTIONS.has(key)) {
303
+ throw new Error(`Unknown option --${key}`);
304
+ }
305
+ const value = inlineValue ?? argv[i + 1];
306
+ if (value === void 0 || inlineValue === void 0 && value.startsWith("--") || value.trim() === "") {
307
+ throw new Error(`Missing value for --${key}`);
148
308
  }
309
+ args[key] = value;
310
+ if (inlineValue === void 0) i++;
149
311
  }
150
312
  }
151
313
  return args;
@@ -172,29 +334,27 @@ Environment:
172
334
  REMNIC_HOST Bind address (ENGRAM_HOST also supported)
173
335
  REMNIC_AUTH_TOKEN Auth bearer token (ENGRAM_AUTH_TOKEN also supported)
174
336
  REMNIC_MEMORY_DIR Override memory directory (ENGRAM_MEMORY_DIR also supported)
175
- OPENAI_API_KEY OpenAI API key for extraction
337
+ OPENAI_API_KEY OpenAI API key for extraction; ignored when config sets openaiApiKey=false
176
338
  `);
177
339
  process.exit(0);
178
340
  }
179
341
  const result = await startServer({
180
342
  configPath: args.config,
181
343
  host: args.host,
182
- port: args.port ? parseInt(args.port, 10) : void 0,
344
+ port: args.port === void 0 ? void 0 : parseServerPort(args.port, "--port"),
183
345
  authToken: args["auth-token"]
184
346
  });
185
347
  console.log(`Remnic server listening on http://${result.host}:${result.port}`);
186
348
  const shutdown = async (signal) => {
187
349
  console.log(`
188
350
  Received ${signal}, shutting down...`);
189
- result.cancelStartupSync();
190
- result.abortDeferredInit();
191
- await result.httpServer.stop();
351
+ await result.stop();
192
352
  process.exit(0);
193
353
  };
194
354
  process.on("SIGINT", () => shutdown("SIGINT"));
195
355
  process.on("SIGTERM", () => shutdown("SIGTERM"));
196
356
  }
197
- if (process.argv[1] && (/remnic-server[\\/](?:dist|src)[\\/]index\.[jt]s$/.test(process.argv[1]) || /engram-server[\\/](?:dist|src)[\\/]index\.[jt]s$/.test(process.argv[1]) || process.argv[1].endsWith("remnic-server") || process.argv[1].endsWith("engram-server"))) {
357
+ if (process.argv[1] && /(?:remnic-server|engram-server)[\\/](?:dist|src)[\\/]index\.[jt]s$/.test(process.argv[1])) {
198
358
  cliMain().catch((err) => {
199
359
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
200
360
  `);
@@ -203,6 +363,9 @@ if (process.argv[1] && (/remnic-server[\\/](?:dist|src)[\\/]index\.[jt]s$/.test(
203
363
  }
204
364
  export {
205
365
  cliMain,
366
+ loadConfigFile,
367
+ mergeRemnicConfigForServer,
368
+ parseServerConfig,
206
369
  startServer
207
370
  };
208
371
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @remnic/server\n *\n * Standalone Remnic memory server.\n *\n * Loads config from `remnic.config.json` (or env vars), creates an Orchestrator,\n * and starts the HTTP access server with MCP endpoint — no OpenClaw required.\n *\n * Usage:\n * npx remnic-server\n * npx remnic-server --config ./my-remnic.json\n * npx remnic-server --port 4320\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parseConfig, Orchestrator, EngramAccessService, EngramAccessHttpServer, initLogger, log, getAllValidTokens, getAllValidTokensCached, type PluginConfig } from \"@remnic/core\";\n\n// ── Config loading ──────────────────────────────────────────────────────────\n\nexport interface ServerConfig {\n remnic: Record<string, unknown>;\n server: {\n host?: string;\n port?: number;\n authToken?: string;\n principal?: string;\n maxBodyBytes?: number;\n adminConsoleEnabled?: boolean;\n };\n}\n\nfunction readCompatEnv(primary: string, legacy: string): string | undefined {\n return process.env[primary] ?? process.env[legacy];\n}\n\nfunction resolveConfigPath(cliPath?: string): string {\n if (cliPath) return path.resolve(cliPath);\n\n const envPath = readCompatEnv(\"REMNIC_CONFIG_PATH\", \"ENGRAM_CONFIG_PATH\");\n if (envPath) return path.resolve(envPath);\n\n const homeDir = process.env.HOME ?? \"~\";\n const candidates = [\n path.join(process.cwd(), \"remnic.config.json\"),\n path.join(process.cwd(), \"engram.config.json\"),\n path.join(homeDir, \".config\", \"remnic\", \"config.json\"),\n path.join(homeDir, \".config\", \"engram\", \"config.json\"),\n ];\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) return candidate;\n }\n\n return path.join(homeDir, \".config\", \"remnic\", \"config.json\");\n}\n\nfunction loadConfigFile(configPath: string): ServerConfig {\n const raw = JSON.parse(fs.readFileSync(configPath, \"utf8\"));\n return {\n remnic: raw.remnic ?? raw.engram ?? raw ?? {},\n server: raw.server ?? {},\n };\n}\n\nfunction envOverrides(): Partial<ServerConfig[\"server\"]> & { remnic?: Record<string, unknown> } {\n const overrides: Record<string, unknown> = {};\n const remnic: Record<string, unknown> = {};\n\n const port = readCompatEnv(\"REMNIC_PORT\", \"ENGRAM_PORT\");\n const host = readCompatEnv(\"REMNIC_HOST\", \"ENGRAM_HOST\");\n const authToken = readCompatEnv(\"REMNIC_AUTH_TOKEN\", \"ENGRAM_AUTH_TOKEN\");\n if (port) overrides.port = parseInt(port, 10);\n if (host) overrides.host = host;\n if (authToken) overrides.authToken = authToken;\n\n if (process.env.OPENAI_API_KEY) remnic.openaiApiKey = process.env.OPENAI_API_KEY;\n const memoryDir = readCompatEnv(\"REMNIC_MEMORY_DIR\", \"ENGRAM_MEMORY_DIR\");\n if (memoryDir) remnic.memoryDir = memoryDir;\n\n return { ...overrides, ...(Object.keys(remnic).length > 0 ? { remnic } : {}) };\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * Like `setTimeout` wrapped in a Promise, but respects an `AbortSignal`.\n * Resolves immediately (without throwing) when the signal fires so the\n * caller can check `signal.aborted` and exit cleanly.\n */\nfunction abortableDelay(ms: number, signal: AbortSignal): Promise<void> {\n if (signal.aborted) return Promise.resolve();\n return new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n const onAbort = () => {\n clearTimeout(timer);\n resolve();\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n// ── Server startup ──────────────────────────────────────────────────────────\n\nexport interface ServerResult {\n config: PluginConfig;\n service: EngramAccessService;\n httpServer: EngramAccessHttpServer;\n host: string;\n port: number;\n /** Cancel any pending startup-sync retry timers. Called automatically on shutdown. */\n cancelStartupSync: () => void;\n /** Abort deferred orchestrator initialization (QMD sync, warmup, cache). */\n abortDeferredInit: () => void;\n}\n\nexport async function startServer(options?: {\n configPath?: string;\n host?: string;\n port?: number;\n authToken?: string;\n}): Promise<ServerResult> {\n initLogger();\n\n const configPath = resolveConfigPath(options?.configPath);\n const fileConfig = fs.existsSync(configPath)\n ? loadConfigFile(configPath)\n : { remnic: {}, server: {} };\n\n const env = envOverrides();\n\n // Merge: file < env < cli flags\n const remnicConfig = { ...fileConfig.remnic, ...(env.remnic ?? {}) };\n const serverConfig = {\n ...fileConfig.server,\n ...env,\n ...(options?.host ? { host: options.host } : {}),\n ...(options?.port ? { port: options.port } : {}),\n ...(options?.authToken ? { authToken: options.authToken } : {}),\n };\n\n const config = parseConfig(remnicConfig);\n const orchestrator = new Orchestrator(config);\n await orchestrator.initialize();\n\n // Start the HTTP server immediately so health checks, MCP handshakes,\n // and liveness probes can connect while deferred init is still running.\n const service = new EngramAccessService(orchestrator);\n\n const authToken = serverConfig.authToken ?? readCompatEnv(\"REMNIC_AUTH_TOKEN\", \"ENGRAM_AUTH_TOKEN\") ?? \"\";\n\n // Connector tokens are loaded dynamically per request via authTokensGetter\n // so that token generate/revoke takes effect without server restart\n if (!authToken && getAllValidTokens().length === 0) {\n log.warn(\"No auth token set — server will reject all requests. Set REMNIC_AUTH_TOKEN, server.authToken in config, or generate tokens with 'remnic token generate'.\");\n }\n\n const httpServer = new EngramAccessHttpServer({\n service,\n host: serverConfig.host ?? \"127.0.0.1\",\n port: serverConfig.port ?? 4318,\n authToken: authToken || undefined,\n authTokensGetter: () => getAllValidTokensCached(),\n principal: serverConfig.principal,\n maxBodyBytes: serverConfig.maxBodyBytes,\n adminConsoleEnabled: serverConfig.adminConsoleEnabled ?? false,\n citationsEnabled: config.citationsEnabled,\n citationsAutoDetect: config.citationsAutoDetect,\n });\n\n const { host, port } = await httpServer.start();\n\n // Fire-and-forget: wait for deferred init (QMD probe, collection setup,\n // warmup) then check QMD availability and retry if needed. This does NOT\n // block the server listener — connections are accepted immediately above.\n // An AbortController allows the shutdown handler to cancel pending retries.\n const startupSyncAbort = new AbortController();\n\n // Wrap httpServer.stop() so that stopping the HTTP server also cancels any\n // in-flight startup-sync retry timers. This ensures callers that only have\n // a reference to httpServer (e.g. test harnesses) don't leave dangling timers\n // even if they never call cancelStartupSync() directly.\n const originalStop = httpServer.stop.bind(httpServer);\n httpServer.stop = async (): Promise<void> => {\n startupSyncAbort.abort();\n return originalStop();\n };\n\n orchestrator.deferredReady.then(() => {\n // Skip retries when search is explicitly disabled via config or when the\n // orchestrator already resolved to a noop backend (e.g. missing collection\n // detected during deferredInitialize). Both cases mean no sync should ever\n // run; scheduling retries would create misleading operational noise and\n // unnecessary background work on every server start.\n if (!config.qmdEnabled || orchestrator.qmd.debugStatus() === \"backend=noop\") {\n log.debug(\"QMD startup-sync: search disabled or noop backend, skipping retries\");\n return;\n }\n\n // Retry when either: (a) QMD is not available yet (cold-start race), or\n // (b) QMD is available but the deferred init sync step failed silently\n // (e.g., update errors swallowed by backend, throttle skip, transient\n // network failure). Without (b), the daemon permanently serves stale\n // recall after a failed sync despite healthy QMD probe.\n const needsRetry = !orchestrator.qmd.isAvailable() || !orchestrator.deferredSyncSucceeded;\n if (!needsRetry) {\n log.debug(\"QMD startup-sync: deferred init completed successfully, no retries needed\");\n return;\n }\n\n const RETRY_DELAYS_MS = [5_000, 15_000, 30_000, 60_000, 120_000];\n (async () => {\n for (const delay of RETRY_DELAYS_MS) {\n await abortableDelay(delay, startupSyncAbort.signal);\n\n if (startupSyncAbort.signal.aborted) {\n log.debug(\"QMD startup-sync retry: cancelled by shutdown\");\n return;\n }\n\n const synced = await orchestrator.startupSearchSync(startupSyncAbort.signal);\n if (!synced) {\n if (orchestrator.qmd.debugStatus() === \"backend=noop\") {\n log.debug(\"QMD startup-sync retry: search intentionally disabled; stopping retries\");\n return;\n }\n log.debug(`QMD startup-sync retry: not available yet (next retry in ${RETRY_DELAYS_MS[RETRY_DELAYS_MS.indexOf(delay) + 1] ?? \"n/a\"}ms)`);\n continue;\n }\n\n return; // sync succeeded, stop retrying\n }\n\n log.warn(\"QMD startup-sync retry: exhausted all retries; search index may be stale\");\n })().catch((err) => {\n log.warn(`QMD startup-sync retry: unexpected error: ${err}`);\n });\n }).catch((err) => {\n log.warn(`Deferred init error: ${err}`);\n });\n\n return { config, service, httpServer, host, port, cancelStartupSync: () => startupSyncAbort.abort(), abortDeferredInit: () => orchestrator.abortDeferredInit() };\n}\n\n// ── CLI entry point ──────────────────────────────────────────────────────────\n\nfunction parseCliArgs(argv: string[]): Record<string, string | undefined> {\n const args: Record<string, string | undefined> = {};\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i];\n if (token.startsWith(\"--\")) {\n const key = token.slice(2);\n const next = argv[i + 1];\n if (next && !next.startsWith(\"--\")) {\n args[key] = next;\n i++;\n } else {\n args[key] = \"true\";\n }\n }\n }\n return args;\n}\n\nexport async function cliMain(argv: string[] = process.argv.slice(2)): Promise<void> {\n const args = parseCliArgs(argv);\n\n if (args.help) {\n console.log(`\nremnic-server — Standalone Remnic memory server\n\nUsage:\n remnic-server [options]\n\nOptions:\n --config <path> Path to config file (default: remnic.config.json)\n --host <addr> Bind address (default: 127.0.0.1)\n --port <number> Port number (default: 4318)\n --auth-token <tok> Bearer token for auth (or set REMNIC_AUTH_TOKEN)\n --help Show this help\n\nEnvironment:\n REMNIC_CONFIG_PATH Config file path (ENGRAM_CONFIG_PATH also supported)\n REMNIC_PORT Server port (ENGRAM_PORT also supported)\n REMNIC_HOST Bind address (ENGRAM_HOST also supported)\n REMNIC_AUTH_TOKEN Auth bearer token (ENGRAM_AUTH_TOKEN also supported)\n REMNIC_MEMORY_DIR Override memory directory (ENGRAM_MEMORY_DIR also supported)\n OPENAI_API_KEY OpenAI API key for extraction\n`);\n process.exit(0);\n }\n\n const result = await startServer({\n configPath: args.config,\n host: args.host,\n port: args.port ? parseInt(args.port, 10) : undefined,\n authToken: args[\"auth-token\"],\n });\n\n console.log(`Remnic server listening on http://${result.host}:${result.port}`);\n\n // Graceful shutdown\n const shutdown = async (signal: string) => {\n console.log(`\\nReceived ${signal}, shutting down...`);\n result.cancelStartupSync();\n result.abortDeferredInit();\n await result.httpServer.stop();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n}\n\n// Auto-run when executed directly\n// Matches: `node .../remnic-server/dist/index.js`, `node .../remnic-server/src/index.ts`,\n// `npx remnic-server`, `npx engram-server`, but NOT test files under those directories\nif (\n process.argv[1] &&\n (/remnic-server[\\\\/](?:dist|src)[\\\\/]index\\.[jt]s$/.test(process.argv[1]) ||\n /engram-server[\\\\/](?:dist|src)[\\\\/]index\\.[jt]s$/.test(process.argv[1]) ||\n process.argv[1].endsWith(\"remnic-server\") ||\n process.argv[1].endsWith(\"engram-server\"))\n) {\n cliMain().catch((err) => {\n process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @remnic/server\n *\n * Standalone Remnic memory server.\n *\n * Loads config from `remnic.config.json` (or env vars), creates an Orchestrator,\n * and starts the HTTP access server with MCP endpoint — no OpenClaw required.\n *\n * Usage:\n * npx --package @remnic/server remnic-server\n * npx --package @remnic/server remnic-server --config ./my-remnic.json\n * npx --package @remnic/server remnic-server --port 4320\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parseConfig, isOpenaiApiKeyDisabled, Orchestrator, EngramAccessService, EngramAccessHttpServer, initLogger, log, getAllValidTokens, getAllValidTokensCached, expandTildePath, type PluginConfig } from \"@remnic/core\";\n\n// ── Config loading ──────────────────────────────────────────────────────────\n\nexport interface ServerConfig {\n remnic: Record<string, unknown>;\n server: {\n host?: string;\n port?: unknown;\n authToken?: string;\n principal?: string;\n maxBodyBytes?: number;\n adminConsoleEnabled?: boolean;\n adminConsolePublicDir?: string;\n };\n}\n\nfunction readCompatEnv(primary: string, legacy: string): string | undefined {\n return process.env[primary] ?? process.env[legacy];\n}\n\nfunction parseServerPort(value: unknown, source: string): number {\n const port = typeof value === \"string\" ? Number(value.trim()) : value;\n if (\n typeof port !== \"number\" ||\n !Number.isInteger(port) ||\n port < 1 ||\n port > 65535\n ) {\n throw new Error(`Invalid ${source}: expected an integer port from 1 to 65535`);\n }\n return port;\n}\n\nfunction parseOptionalString(value: unknown, source: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"string\") {\n throw new Error(`Invalid ${source}: expected a string`);\n }\n return value;\n}\n\nfunction parseOptionalNonEmptyString(value: unknown, source: string): string | undefined {\n const parsed = parseOptionalString(value, source);\n if (parsed === undefined) return undefined;\n if (parsed.trim() === \"\") {\n throw new Error(`Invalid ${source}: expected a non-empty string`);\n }\n return parsed;\n}\n\nfunction parseOptionalPositiveInteger(value: unknown, source: string): number | undefined {\n if (value === undefined) return undefined;\n const parsed = typeof value === \"string\" ? Number(value.trim()) : value;\n if (\n typeof parsed !== \"number\" ||\n !Number.isInteger(parsed) ||\n parsed < 1\n ) {\n throw new Error(`Invalid ${source}: expected a positive integer`);\n }\n return parsed;\n}\n\nfunction parseOptionalBoolean(value: unknown, source: string): boolean | undefined {\n if (value === undefined) return undefined;\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"string\") {\n const normalized = value.trim().toLowerCase();\n if ([\"true\", \"1\", \"yes\", \"on\"].includes(normalized)) return true;\n if ([\"false\", \"0\", \"no\", \"off\"].includes(normalized)) return false;\n }\n throw new Error(`Invalid ${source}: expected a boolean`);\n}\n\nexport interface ParsedServerConfig {\n host: string;\n port: number;\n authToken?: string;\n principal?: string;\n maxBodyBytes?: number;\n adminConsoleEnabled: boolean;\n adminConsolePublicDir?: string;\n}\n\nexport function parseServerConfig(\n raw: Partial<ServerConfig[\"server\"]>,\n options?: { portSource?: string },\n): ParsedServerConfig {\n return {\n host: parseOptionalNonEmptyString(raw.host, \"server.host\") ?? \"127.0.0.1\",\n port: raw.port === undefined\n ? 4318\n : parseServerPort(raw.port, options?.portSource ?? \"server.port\"),\n authToken: parseOptionalString(raw.authToken, \"server.authToken\"),\n principal: parseOptionalString(raw.principal, \"server.principal\"),\n maxBodyBytes: parseOptionalPositiveInteger(raw.maxBodyBytes, \"server.maxBodyBytes\"),\n adminConsoleEnabled: parseOptionalBoolean(raw.adminConsoleEnabled, \"server.adminConsoleEnabled\") ?? false,\n adminConsolePublicDir: parseOptionalString(raw.adminConsolePublicDir, \"server.adminConsolePublicDir\"),\n };\n}\n\ninterface ResolvedConfigPath {\n path: string;\n explicit: boolean;\n source: string;\n}\n\nfunction resolveUserPath(value: string): string {\n return path.resolve(expandTildePath(value));\n}\n\nfunction resolveConfigPath(cliPath?: string): ResolvedConfigPath {\n if (cliPath) {\n return { path: resolveUserPath(cliPath), explicit: true, source: \"--config\" };\n }\n\n const envPath = readCompatEnv(\"REMNIC_CONFIG_PATH\", \"ENGRAM_CONFIG_PATH\");\n if (envPath) {\n return { path: resolveUserPath(envPath), explicit: true, source: \"REMNIC_CONFIG_PATH/ENGRAM_CONFIG_PATH\" };\n }\n\n const homeDir = process.env.HOME ?? \"~\";\n const candidates = [\n path.join(process.cwd(), \"remnic.config.json\"),\n path.join(process.cwd(), \"engram.config.json\"),\n path.join(homeDir, \".config\", \"remnic\", \"config.json\"),\n path.join(homeDir, \".config\", \"engram\", \"config.json\"),\n ];\n for (const candidate of candidates) {\n if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {\n return { path: candidate, explicit: false, source: \"auto-discovery\" };\n }\n }\n\n return { path: path.join(homeDir, \".config\", \"remnic\", \"config.json\"), explicit: false, source: \"auto-discovery\" };\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction requirePlainConfigBlock(\n raw: Record<string, unknown>,\n key: \"remnic\" | \"engram\" | \"server\",\n configPath: string,\n): Record<string, unknown> | undefined {\n const value = raw[key];\n if (value === undefined) return undefined;\n if (!isPlainRecord(value)) {\n throw new Error(`Invalid config file ${configPath}: ${key} must be a JSON object`);\n }\n return value;\n}\n\nexport function loadConfigFile(configPath: string): ServerConfig {\n const raw = JSON.parse(fs.readFileSync(configPath, \"utf8\"));\n if (!isPlainRecord(raw)) {\n throw new Error(`Invalid config file ${configPath}: top-level config must be a JSON object`);\n }\n const remnic = requirePlainConfigBlock(raw, \"remnic\", configPath);\n const engram = requirePlainConfigBlock(raw, \"engram\", configPath);\n const server = requirePlainConfigBlock(raw, \"server\", configPath);\n return {\n remnic: remnic ?? engram ?? raw,\n server: server ?? {},\n };\n}\n\nfunction loadResolvedConfig(resolved: ResolvedConfigPath): ServerConfig {\n if (!fs.existsSync(resolved.path)) {\n if (resolved.explicit) {\n throw new Error(`Config file from ${resolved.source} not found: ${resolved.path}`);\n }\n return { remnic: {}, server: {} };\n }\n\n const stat = fs.statSync(resolved.path);\n if (!stat.isFile()) {\n if (!resolved.explicit) {\n return { remnic: {}, server: {} };\n }\n throw new Error(`Config file from ${resolved.source} is not a regular file: ${resolved.path}`);\n }\n\n return loadConfigFile(resolved.path);\n}\n\nfunction envOverrides(): Partial<ServerConfig[\"server\"]> & { remnic?: Record<string, unknown> } {\n const overrides: Record<string, unknown> = {};\n const remnic: Record<string, unknown> = {};\n\n const port = readCompatEnv(\"REMNIC_PORT\", \"ENGRAM_PORT\");\n const host = readCompatEnv(\"REMNIC_HOST\", \"ENGRAM_HOST\");\n const authToken = readCompatEnv(\"REMNIC_AUTH_TOKEN\", \"ENGRAM_AUTH_TOKEN\");\n if (port) overrides.port = port;\n if (host) overrides.host = host;\n if (authToken) overrides.authToken = authToken;\n\n if (process.env.OPENAI_API_KEY) remnic.openaiApiKey = process.env.OPENAI_API_KEY;\n const memoryDir = readCompatEnv(\"REMNIC_MEMORY_DIR\", \"ENGRAM_MEMORY_DIR\");\n if (memoryDir) remnic.memoryDir = memoryDir;\n\n return { ...overrides, ...(Object.keys(remnic).length > 0 ? { remnic } : {}) };\n}\n\nexport function mergeRemnicConfigForServer(\n fileRemnic: Record<string, unknown>,\n envRemnic: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n const effectiveEnvRemnic = { ...(envRemnic ?? {}) };\n if (isOpenaiApiKeyDisabled(fileRemnic.openaiApiKey)) {\n // A local/gateway-only deployment can explicitly disable the direct\n // OpenAI client. Preserve that opt-out even when the process has a\n // global OPENAI_API_KEY for unrelated tools.\n delete effectiveEnvRemnic.openaiApiKey;\n }\n return { ...fileRemnic, ...effectiveEnvRemnic };\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * Like `setTimeout` wrapped in a Promise, but respects an `AbortSignal`.\n * Resolves immediately (without throwing) when the signal fires so the\n * caller can check `signal.aborted` and exit cleanly.\n */\nfunction abortableDelay(ms: number, signal: AbortSignal): Promise<void> {\n if (signal.aborted) return Promise.resolve();\n return new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n const onAbort = () => {\n clearTimeout(timer);\n resolve();\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nasync function cleanupFailedStartup(\n orchestrator: Orchestrator,\n httpServer: EngramAccessHttpServer,\n): Promise<void> {\n try {\n await httpServer.stop();\n } catch (err) {\n log.warn(`HTTP startup failure cleanup could not stop server: ${err}`);\n }\n\n try {\n await orchestrator.destroy();\n } catch (err) {\n log.warn(`HTTP startup failure cleanup could not destroy orchestrator: ${err}`);\n }\n}\n\n// ── Server startup ──────────────────────────────────────────────────────────\n\nexport interface ServerResult {\n config: PluginConfig;\n service: EngramAccessService;\n httpServer: EngramAccessHttpServer;\n host: string;\n port: number;\n /** Stop HTTP, cancel startup work, abort deferred init, and destroy the orchestrator. */\n stop: () => Promise<void>;\n /** Cancel any pending startup-sync retry timers. Called automatically on shutdown. */\n cancelStartupSync: () => void;\n /** Abort deferred orchestrator initialization (QMD sync, warmup, cache). */\n abortDeferredInit: () => void;\n}\n\nexport async function startServer(options?: {\n configPath?: string;\n host?: string;\n port?: number;\n authToken?: string;\n}): Promise<ServerResult> {\n initLogger();\n\n const resolvedConfigPath = resolveConfigPath(options?.configPath);\n const fileConfig = loadResolvedConfig(resolvedConfigPath);\n\n const env = envOverrides();\n const { remnic: envRemnic, ...envServer } = env;\n\n // Merge: file < env < cli flags\n const remnicConfig = mergeRemnicConfigForServer(fileConfig.remnic, envRemnic);\n const cliServerConfig: Partial<ServerConfig[\"server\"]> = {};\n if (options?.host !== undefined) cliServerConfig.host = options.host;\n if (options?.port !== undefined) cliServerConfig.port = parseServerPort(options.port, \"options.port\");\n if (options?.authToken !== undefined) cliServerConfig.authToken = options.authToken;\n\n const serverConfig = {\n ...fileConfig.server,\n ...envServer,\n ...cliServerConfig,\n };\n const portSource = cliServerConfig.port !== undefined\n ? \"options.port\"\n : envServer.port !== undefined\n ? \"REMNIC_PORT/ENGRAM_PORT\"\n : \"server.port\";\n const parsedServerConfig = parseServerConfig(serverConfig, { portSource });\n\n const config = parseConfig(remnicConfig);\n const orchestrator = new Orchestrator(config);\n await orchestrator.initialize();\n\n // Start the HTTP server immediately so health checks, MCP handshakes,\n // and liveness probes can connect while deferred init is still running.\n const service = new EngramAccessService(orchestrator);\n\n const authToken = parsedServerConfig.authToken ?? readCompatEnv(\"REMNIC_AUTH_TOKEN\", \"ENGRAM_AUTH_TOKEN\") ?? \"\";\n\n // Connector tokens are loaded dynamically per request via authTokensGetter\n // so that token generate/revoke takes effect without server restart\n if (!authToken && getAllValidTokens().length === 0) {\n log.warn(\"No auth token set — server will reject all requests. Set REMNIC_AUTH_TOKEN, server.authToken in config, or generate tokens with 'remnic token generate'.\");\n }\n\n const httpServer = new EngramAccessHttpServer({\n service,\n host: parsedServerConfig.host,\n port: parsedServerConfig.port,\n authToken: authToken || undefined,\n authTokensGetter: () => getAllValidTokensCached(),\n principal: parsedServerConfig.principal,\n maxBodyBytes: parsedServerConfig.maxBodyBytes,\n adminConsoleEnabled: parsedServerConfig.adminConsoleEnabled,\n adminConsolePublicDir: parsedServerConfig.adminConsolePublicDir\n ? path.resolve(expandTildePath(parsedServerConfig.adminConsolePublicDir))\n : undefined,\n citationsEnabled: config.citationsEnabled,\n citationsAutoDetect: config.citationsAutoDetect,\n });\n\n let host: string;\n let port: number;\n try {\n ({ host, port } = await httpServer.start());\n } catch (err) {\n await cleanupFailedStartup(orchestrator, httpServer);\n throw err;\n }\n\n // Fire-and-forget: wait for deferred init (QMD probe, collection setup,\n // warmup) then check QMD availability and retry if needed. This does NOT\n // block the server listener — connections are accepted immediately above.\n // An AbortController allows the shutdown handler to cancel pending retries.\n const startupSyncAbort = new AbortController();\n\n // Wrap httpServer.stop() so that existing callers also get full lifecycle\n // cleanup: retry timers, deferred init, HTTP listener, and orchestrator.\n const originalStop = httpServer.stop.bind(httpServer);\n let stopPromise: Promise<void> | undefined;\n const stop = async (): Promise<void> => {\n if (stopPromise) return stopPromise;\n stopPromise = (async () => {\n startupSyncAbort.abort();\n orchestrator.abortDeferredInit();\n try {\n await originalStop();\n } finally {\n await orchestrator.destroy();\n }\n })();\n return stopPromise;\n };\n httpServer.stop = stop;\n\n orchestrator.deferredReady.then(() => {\n if (startupSyncAbort.signal.aborted) {\n log.debug(\"QMD startup-sync: cancelled before deferred init completed\");\n return;\n }\n\n // Skip retries when search is explicitly disabled via config or when the\n // orchestrator already resolved to a noop backend (e.g. missing collection\n // detected during deferredInitialize). Both cases mean no sync should ever\n // run; scheduling retries would create misleading operational noise and\n // unnecessary background work on every server start.\n if (!config.qmdEnabled || orchestrator.qmd.debugStatus() === \"backend=noop\") {\n log.debug(\"QMD startup-sync: search disabled or noop backend, skipping retries\");\n return;\n }\n\n // Retry when either: (a) QMD is not available yet (cold-start race), or\n // (b) QMD is available but the deferred init sync step failed silently\n // (e.g., update errors swallowed by backend, throttle skip, transient\n // network failure). Without (b), the daemon permanently serves stale\n // recall after a failed sync despite healthy QMD probe.\n const needsRetry = !orchestrator.qmd.isAvailable() || !orchestrator.deferredSyncSucceeded;\n if (!needsRetry) {\n log.debug(\"QMD startup-sync: deferred init completed successfully, no retries needed\");\n return;\n }\n\n const RETRY_DELAYS_MS = [5_000, 15_000, 30_000, 60_000, 120_000];\n if (startupSyncAbort.signal.aborted) {\n log.debug(\"QMD startup-sync retry: cancelled before retry task started\");\n return;\n }\n (async () => {\n for (const delay of RETRY_DELAYS_MS) {\n await abortableDelay(delay, startupSyncAbort.signal);\n\n if (startupSyncAbort.signal.aborted) {\n log.debug(\"QMD startup-sync retry: cancelled by shutdown\");\n return;\n }\n\n const synced = await orchestrator.startupSearchSync(startupSyncAbort.signal);\n if (!synced) {\n if (orchestrator.qmd.debugStatus() === \"backend=noop\") {\n log.debug(\"QMD startup-sync retry: search intentionally disabled; stopping retries\");\n return;\n }\n log.debug(`QMD startup-sync retry: not available yet (next retry in ${RETRY_DELAYS_MS[RETRY_DELAYS_MS.indexOf(delay) + 1] ?? \"n/a\"}ms)`);\n continue;\n }\n\n return; // sync succeeded, stop retrying\n }\n\n log.warn(\"QMD startup-sync retry: exhausted all retries; search index may be stale\");\n })().catch((err: unknown) => {\n log.warn(`QMD startup-sync retry: unexpected error: ${err}`);\n });\n }).catch((err: unknown) => {\n log.warn(`Deferred init error: ${err}`);\n });\n\n return { config, service, httpServer, host, port, stop, cancelStartupSync: () => startupSyncAbort.abort(), abortDeferredInit: () => orchestrator.abortDeferredInit() };\n}\n\n// ── CLI entry point ──────────────────────────────────────────────────────────\n\nconst BOOLEAN_CLI_OPTIONS = new Set([\"help\"]);\nconst VALUE_CLI_OPTIONS = new Set([\"config\", \"host\", \"port\", \"auth-token\"]);\n\nfunction parseCliArgs(argv: string[]): Record<string, string | undefined> {\n const args: Record<string, string | undefined> = {};\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i];\n if (token === \"-h\") {\n args.help = \"true\";\n continue;\n }\n\n if (token.startsWith(\"--\")) {\n const [key, inlineValue] = token.slice(2).split(/=(.*)/s, 2);\n if (!key) {\n throw new Error(`Invalid option ${token}`);\n }\n\n if (BOOLEAN_CLI_OPTIONS.has(key)) {\n if (inlineValue !== undefined) {\n throw new Error(`Option --${key} does not accept a value`);\n }\n args[key] = \"true\";\n continue;\n }\n\n if (!VALUE_CLI_OPTIONS.has(key)) {\n throw new Error(`Unknown option --${key}`);\n }\n\n const value = inlineValue ?? argv[i + 1];\n if (\n value === undefined ||\n (inlineValue === undefined && value.startsWith(\"--\")) ||\n value.trim() === \"\"\n ) {\n throw new Error(`Missing value for --${key}`);\n }\n\n args[key] = value;\n if (inlineValue === undefined) i++;\n }\n }\n return args;\n}\n\nexport async function cliMain(argv: string[] = process.argv.slice(2)): Promise<void> {\n const args = parseCliArgs(argv);\n\n if (args.help) {\n console.log(`\nremnic-server — Standalone Remnic memory server\n\nUsage:\n remnic-server [options]\n\nOptions:\n --config <path> Path to config file (default: remnic.config.json)\n --host <addr> Bind address (default: 127.0.0.1)\n --port <number> Port number (default: 4318)\n --auth-token <tok> Bearer token for auth (or set REMNIC_AUTH_TOKEN)\n --help Show this help\n\nEnvironment:\n REMNIC_CONFIG_PATH Config file path (ENGRAM_CONFIG_PATH also supported)\n REMNIC_PORT Server port (ENGRAM_PORT also supported)\n REMNIC_HOST Bind address (ENGRAM_HOST also supported)\n REMNIC_AUTH_TOKEN Auth bearer token (ENGRAM_AUTH_TOKEN also supported)\n REMNIC_MEMORY_DIR Override memory directory (ENGRAM_MEMORY_DIR also supported)\n OPENAI_API_KEY OpenAI API key for extraction; ignored when config sets openaiApiKey=false\n`);\n process.exit(0);\n }\n\n const result = await startServer({\n configPath: args.config,\n host: args.host,\n port: args.port === undefined ? undefined : parseServerPort(args.port, \"--port\"),\n authToken: args[\"auth-token\"],\n });\n\n console.log(`Remnic server listening on http://${result.host}:${result.port}`);\n\n // Graceful shutdown\n const shutdown = async (signal: string) => {\n console.log(`\\nReceived ${signal}, shutting down...`);\n await result.stop();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n}\n\n// Auto-run when executed directly\n// Matches direct execution of `node .../remnic-server/dist/index.js` or\n// `node .../remnic-server/src/index.ts`. Package command names are handled by\n// the bin wrappers in ../bin so importing this module cannot start twice.\nif (\n process.argv[1] &&\n /(?:remnic-server|engram-server)[\\\\/](?:dist|src)[\\\\/]index\\.[jt]s$/.test(process.argv[1])\n) {\n cliMain().catch((err) => {\n process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,19 +1,22 @@
1
1
  {
2
2
  "name": "@remnic/server",
3
- "version": "1.0.5",
3
+ "version": "9.3.515",
4
4
  "description": "Standalone Remnic memory server — HTTP + MCP without OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
+ "types": "./dist/index.d.ts",
7
8
  "exports": {
8
9
  ".": {
10
+ "types": "./dist/index.d.ts",
9
11
  "import": "./dist/index.js"
10
12
  }
11
13
  },
12
14
  "bin": {
13
- "remnic-server": "./dist/bin/remnic-server.js",
14
- "engram-server": "./dist/bin/engram-server.js"
15
+ "remnic-server": "./bin/remnic-server.js",
16
+ "engram-server": "./bin/engram-server.js"
15
17
  },
16
18
  "files": [
19
+ "bin/*.js",
17
20
  "dist"
18
21
  ],
19
22
  "publishConfig": {
@@ -21,11 +24,12 @@
21
24
  "provenance": true
22
25
  },
23
26
  "dependencies": {
24
- "@remnic/core": "^1.0.3"
27
+ "@remnic/core": "^9.3.515"
25
28
  },
26
29
  "devDependencies": {
27
30
  "tsup": "^8.5.1",
28
- "typescript": "^5.9.3"
31
+ "typescript": "^5.9.3",
32
+ "@remnic/core": "^9.3.515"
29
33
  },
30
34
  "license": "MIT",
31
35
  "repository": {
@@ -34,7 +38,9 @@
34
38
  "directory": "packages/remnic-server"
35
39
  },
36
40
  "scripts": {
37
- "build": "tsup src/index.ts --format esm --target es2022 --platform node --outDir dist && tsup bin/remnic-server.ts bin/engram-server.ts --format esm --target es2022 --platform node --outDir dist/bin",
41
+ "build": "npm run check-types && tsup src/index.ts --format esm --target es2022 --platform node --outDir dist --dts && node scripts/verify-bin.mjs",
42
+ "verify:bin": "node scripts/verify-bin.mjs",
43
+ "precheck-types": "node ../../scripts/ensure-bench-build-deps.mjs",
38
44
  "check-types": "tsc --noEmit"
39
45
  }
40
46
  }
@@ -1,208 +0,0 @@
1
- // openclaw-engram: Local-first memory plugin
2
-
3
- // src/index.ts
4
- import fs from "fs";
5
- import path from "path";
6
- import { parseConfig, Orchestrator, EngramAccessService, EngramAccessHttpServer, initLogger, log, getAllValidTokens, getAllValidTokensCached } from "@remnic/core";
7
- function readCompatEnv(primary, legacy) {
8
- return process.env[primary] ?? process.env[legacy];
9
- }
10
- function resolveConfigPath(cliPath) {
11
- if (cliPath) return path.resolve(cliPath);
12
- const envPath = readCompatEnv("REMNIC_CONFIG_PATH", "ENGRAM_CONFIG_PATH");
13
- if (envPath) return path.resolve(envPath);
14
- const homeDir = process.env.HOME ?? "~";
15
- const candidates = [
16
- path.join(process.cwd(), "remnic.config.json"),
17
- path.join(process.cwd(), "engram.config.json"),
18
- path.join(homeDir, ".config", "remnic", "config.json"),
19
- path.join(homeDir, ".config", "engram", "config.json")
20
- ];
21
- for (const candidate of candidates) {
22
- if (fs.existsSync(candidate)) return candidate;
23
- }
24
- return path.join(homeDir, ".config", "remnic", "config.json");
25
- }
26
- function loadConfigFile(configPath) {
27
- const raw = JSON.parse(fs.readFileSync(configPath, "utf8"));
28
- return {
29
- remnic: raw.remnic ?? raw.engram ?? raw ?? {},
30
- server: raw.server ?? {}
31
- };
32
- }
33
- function envOverrides() {
34
- const overrides = {};
35
- const remnic = {};
36
- const port = readCompatEnv("REMNIC_PORT", "ENGRAM_PORT");
37
- const host = readCompatEnv("REMNIC_HOST", "ENGRAM_HOST");
38
- const authToken = readCompatEnv("REMNIC_AUTH_TOKEN", "ENGRAM_AUTH_TOKEN");
39
- if (port) overrides.port = parseInt(port, 10);
40
- if (host) overrides.host = host;
41
- if (authToken) overrides.authToken = authToken;
42
- if (process.env.OPENAI_API_KEY) remnic.openaiApiKey = process.env.OPENAI_API_KEY;
43
- const memoryDir = readCompatEnv("REMNIC_MEMORY_DIR", "ENGRAM_MEMORY_DIR");
44
- if (memoryDir) remnic.memoryDir = memoryDir;
45
- return { ...overrides, ...Object.keys(remnic).length > 0 ? { remnic } : {} };
46
- }
47
- function abortableDelay(ms, signal) {
48
- if (signal.aborted) return Promise.resolve();
49
- return new Promise((resolve) => {
50
- const timer = setTimeout(resolve, ms);
51
- const onAbort = () => {
52
- clearTimeout(timer);
53
- resolve();
54
- };
55
- signal.addEventListener("abort", onAbort, { once: true });
56
- });
57
- }
58
- async function startServer(options) {
59
- initLogger();
60
- const configPath = resolveConfigPath(options?.configPath);
61
- const fileConfig = fs.existsSync(configPath) ? loadConfigFile(configPath) : { remnic: {}, server: {} };
62
- const env = envOverrides();
63
- const remnicConfig = { ...fileConfig.remnic, ...env.remnic ?? {} };
64
- const serverConfig = {
65
- ...fileConfig.server,
66
- ...env,
67
- ...options?.host ? { host: options.host } : {},
68
- ...options?.port ? { port: options.port } : {},
69
- ...options?.authToken ? { authToken: options.authToken } : {}
70
- };
71
- const config = parseConfig(remnicConfig);
72
- const orchestrator = new Orchestrator(config);
73
- await orchestrator.initialize();
74
- const service = new EngramAccessService(orchestrator);
75
- const authToken = serverConfig.authToken ?? readCompatEnv("REMNIC_AUTH_TOKEN", "ENGRAM_AUTH_TOKEN") ?? "";
76
- if (!authToken && getAllValidTokens().length === 0) {
77
- log.warn("No auth token set \u2014 server will reject all requests. Set REMNIC_AUTH_TOKEN, server.authToken in config, or generate tokens with 'remnic token generate'.");
78
- }
79
- const httpServer = new EngramAccessHttpServer({
80
- service,
81
- host: serverConfig.host ?? "127.0.0.1",
82
- port: serverConfig.port ?? 4318,
83
- authToken: authToken || void 0,
84
- authTokensGetter: () => getAllValidTokensCached(),
85
- principal: serverConfig.principal,
86
- maxBodyBytes: serverConfig.maxBodyBytes,
87
- adminConsoleEnabled: serverConfig.adminConsoleEnabled ?? false,
88
- citationsEnabled: config.citationsEnabled,
89
- citationsAutoDetect: config.citationsAutoDetect
90
- });
91
- const { host, port } = await httpServer.start();
92
- const startupSyncAbort = new AbortController();
93
- const originalStop = httpServer.stop.bind(httpServer);
94
- httpServer.stop = async () => {
95
- startupSyncAbort.abort();
96
- return originalStop();
97
- };
98
- orchestrator.deferredReady.then(() => {
99
- if (!config.qmdEnabled || orchestrator.qmd.debugStatus() === "backend=noop") {
100
- log.debug("QMD startup-sync: search disabled or noop backend, skipping retries");
101
- return;
102
- }
103
- const needsRetry = !orchestrator.qmd.isAvailable() || !orchestrator.deferredSyncSucceeded;
104
- if (!needsRetry) {
105
- log.debug("QMD startup-sync: deferred init completed successfully, no retries needed");
106
- return;
107
- }
108
- const RETRY_DELAYS_MS = [5e3, 15e3, 3e4, 6e4, 12e4];
109
- (async () => {
110
- for (const delay of RETRY_DELAYS_MS) {
111
- await abortableDelay(delay, startupSyncAbort.signal);
112
- if (startupSyncAbort.signal.aborted) {
113
- log.debug("QMD startup-sync retry: cancelled by shutdown");
114
- return;
115
- }
116
- const synced = await orchestrator.startupSearchSync(startupSyncAbort.signal);
117
- if (!synced) {
118
- if (orchestrator.qmd.debugStatus() === "backend=noop") {
119
- log.debug("QMD startup-sync retry: search intentionally disabled; stopping retries");
120
- return;
121
- }
122
- log.debug(`QMD startup-sync retry: not available yet (next retry in ${RETRY_DELAYS_MS[RETRY_DELAYS_MS.indexOf(delay) + 1] ?? "n/a"}ms)`);
123
- continue;
124
- }
125
- return;
126
- }
127
- log.warn("QMD startup-sync retry: exhausted all retries; search index may be stale");
128
- })().catch((err) => {
129
- log.warn(`QMD startup-sync retry: unexpected error: ${err}`);
130
- });
131
- }).catch((err) => {
132
- log.warn(`Deferred init error: ${err}`);
133
- });
134
- return { config, service, httpServer, host, port, cancelStartupSync: () => startupSyncAbort.abort(), abortDeferredInit: () => orchestrator.abortDeferredInit() };
135
- }
136
- function parseCliArgs(argv) {
137
- const args = {};
138
- for (let i = 0; i < argv.length; i++) {
139
- const token = argv[i];
140
- if (token.startsWith("--")) {
141
- const key = token.slice(2);
142
- const next = argv[i + 1];
143
- if (next && !next.startsWith("--")) {
144
- args[key] = next;
145
- i++;
146
- } else {
147
- args[key] = "true";
148
- }
149
- }
150
- }
151
- return args;
152
- }
153
- async function cliMain(argv = process.argv.slice(2)) {
154
- const args = parseCliArgs(argv);
155
- if (args.help) {
156
- console.log(`
157
- remnic-server \u2014 Standalone Remnic memory server
158
-
159
- Usage:
160
- remnic-server [options]
161
-
162
- Options:
163
- --config <path> Path to config file (default: remnic.config.json)
164
- --host <addr> Bind address (default: 127.0.0.1)
165
- --port <number> Port number (default: 4318)
166
- --auth-token <tok> Bearer token for auth (or set REMNIC_AUTH_TOKEN)
167
- --help Show this help
168
-
169
- Environment:
170
- REMNIC_CONFIG_PATH Config file path (ENGRAM_CONFIG_PATH also supported)
171
- REMNIC_PORT Server port (ENGRAM_PORT also supported)
172
- REMNIC_HOST Bind address (ENGRAM_HOST also supported)
173
- REMNIC_AUTH_TOKEN Auth bearer token (ENGRAM_AUTH_TOKEN also supported)
174
- REMNIC_MEMORY_DIR Override memory directory (ENGRAM_MEMORY_DIR also supported)
175
- OPENAI_API_KEY OpenAI API key for extraction
176
- `);
177
- process.exit(0);
178
- }
179
- const result = await startServer({
180
- configPath: args.config,
181
- host: args.host,
182
- port: args.port ? parseInt(args.port, 10) : void 0,
183
- authToken: args["auth-token"]
184
- });
185
- console.log(`Remnic server listening on http://${result.host}:${result.port}`);
186
- const shutdown = async (signal) => {
187
- console.log(`
188
- Received ${signal}, shutting down...`);
189
- result.cancelStartupSync();
190
- result.abortDeferredInit();
191
- await result.httpServer.stop();
192
- process.exit(0);
193
- };
194
- process.on("SIGINT", () => shutdown("SIGINT"));
195
- process.on("SIGTERM", () => shutdown("SIGTERM"));
196
- }
197
- if (process.argv[1] && (/remnic-server[\\/](?:dist|src)[\\/]index\.[jt]s$/.test(process.argv[1]) || /engram-server[\\/](?:dist|src)[\\/]index\.[jt]s$/.test(process.argv[1]) || process.argv[1].endsWith("remnic-server") || process.argv[1].endsWith("engram-server"))) {
198
- cliMain().catch((err) => {
199
- process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
200
- `);
201
- process.exit(1);
202
- });
203
- }
204
-
205
- export {
206
- cliMain
207
- };
208
- //# sourceMappingURL=chunk-FFTS3VKE.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/index.ts"],"sourcesContent":["/**\n * @remnic/server\n *\n * Standalone Remnic memory server.\n *\n * Loads config from `remnic.config.json` (or env vars), creates an Orchestrator,\n * and starts the HTTP access server with MCP endpoint — no OpenClaw required.\n *\n * Usage:\n * npx remnic-server\n * npx remnic-server --config ./my-remnic.json\n * npx remnic-server --port 4320\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parseConfig, Orchestrator, EngramAccessService, EngramAccessHttpServer, initLogger, log, getAllValidTokens, getAllValidTokensCached, type PluginConfig } from \"@remnic/core\";\n\n// ── Config loading ──────────────────────────────────────────────────────────\n\nexport interface ServerConfig {\n remnic: Record<string, unknown>;\n server: {\n host?: string;\n port?: number;\n authToken?: string;\n principal?: string;\n maxBodyBytes?: number;\n adminConsoleEnabled?: boolean;\n };\n}\n\nfunction readCompatEnv(primary: string, legacy: string): string | undefined {\n return process.env[primary] ?? process.env[legacy];\n}\n\nfunction resolveConfigPath(cliPath?: string): string {\n if (cliPath) return path.resolve(cliPath);\n\n const envPath = readCompatEnv(\"REMNIC_CONFIG_PATH\", \"ENGRAM_CONFIG_PATH\");\n if (envPath) return path.resolve(envPath);\n\n const homeDir = process.env.HOME ?? \"~\";\n const candidates = [\n path.join(process.cwd(), \"remnic.config.json\"),\n path.join(process.cwd(), \"engram.config.json\"),\n path.join(homeDir, \".config\", \"remnic\", \"config.json\"),\n path.join(homeDir, \".config\", \"engram\", \"config.json\"),\n ];\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) return candidate;\n }\n\n return path.join(homeDir, \".config\", \"remnic\", \"config.json\");\n}\n\nfunction loadConfigFile(configPath: string): ServerConfig {\n const raw = JSON.parse(fs.readFileSync(configPath, \"utf8\"));\n return {\n remnic: raw.remnic ?? raw.engram ?? raw ?? {},\n server: raw.server ?? {},\n };\n}\n\nfunction envOverrides(): Partial<ServerConfig[\"server\"]> & { remnic?: Record<string, unknown> } {\n const overrides: Record<string, unknown> = {};\n const remnic: Record<string, unknown> = {};\n\n const port = readCompatEnv(\"REMNIC_PORT\", \"ENGRAM_PORT\");\n const host = readCompatEnv(\"REMNIC_HOST\", \"ENGRAM_HOST\");\n const authToken = readCompatEnv(\"REMNIC_AUTH_TOKEN\", \"ENGRAM_AUTH_TOKEN\");\n if (port) overrides.port = parseInt(port, 10);\n if (host) overrides.host = host;\n if (authToken) overrides.authToken = authToken;\n\n if (process.env.OPENAI_API_KEY) remnic.openaiApiKey = process.env.OPENAI_API_KEY;\n const memoryDir = readCompatEnv(\"REMNIC_MEMORY_DIR\", \"ENGRAM_MEMORY_DIR\");\n if (memoryDir) remnic.memoryDir = memoryDir;\n\n return { ...overrides, ...(Object.keys(remnic).length > 0 ? { remnic } : {}) };\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * Like `setTimeout` wrapped in a Promise, but respects an `AbortSignal`.\n * Resolves immediately (without throwing) when the signal fires so the\n * caller can check `signal.aborted` and exit cleanly.\n */\nfunction abortableDelay(ms: number, signal: AbortSignal): Promise<void> {\n if (signal.aborted) return Promise.resolve();\n return new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n const onAbort = () => {\n clearTimeout(timer);\n resolve();\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n// ── Server startup ──────────────────────────────────────────────────────────\n\nexport interface ServerResult {\n config: PluginConfig;\n service: EngramAccessService;\n httpServer: EngramAccessHttpServer;\n host: string;\n port: number;\n /** Cancel any pending startup-sync retry timers. Called automatically on shutdown. */\n cancelStartupSync: () => void;\n /** Abort deferred orchestrator initialization (QMD sync, warmup, cache). */\n abortDeferredInit: () => void;\n}\n\nexport async function startServer(options?: {\n configPath?: string;\n host?: string;\n port?: number;\n authToken?: string;\n}): Promise<ServerResult> {\n initLogger();\n\n const configPath = resolveConfigPath(options?.configPath);\n const fileConfig = fs.existsSync(configPath)\n ? loadConfigFile(configPath)\n : { remnic: {}, server: {} };\n\n const env = envOverrides();\n\n // Merge: file < env < cli flags\n const remnicConfig = { ...fileConfig.remnic, ...(env.remnic ?? {}) };\n const serverConfig = {\n ...fileConfig.server,\n ...env,\n ...(options?.host ? { host: options.host } : {}),\n ...(options?.port ? { port: options.port } : {}),\n ...(options?.authToken ? { authToken: options.authToken } : {}),\n };\n\n const config = parseConfig(remnicConfig);\n const orchestrator = new Orchestrator(config);\n await orchestrator.initialize();\n\n // Start the HTTP server immediately so health checks, MCP handshakes,\n // and liveness probes can connect while deferred init is still running.\n const service = new EngramAccessService(orchestrator);\n\n const authToken = serverConfig.authToken ?? readCompatEnv(\"REMNIC_AUTH_TOKEN\", \"ENGRAM_AUTH_TOKEN\") ?? \"\";\n\n // Connector tokens are loaded dynamically per request via authTokensGetter\n // so that token generate/revoke takes effect without server restart\n if (!authToken && getAllValidTokens().length === 0) {\n log.warn(\"No auth token set — server will reject all requests. Set REMNIC_AUTH_TOKEN, server.authToken in config, or generate tokens with 'remnic token generate'.\");\n }\n\n const httpServer = new EngramAccessHttpServer({\n service,\n host: serverConfig.host ?? \"127.0.0.1\",\n port: serverConfig.port ?? 4318,\n authToken: authToken || undefined,\n authTokensGetter: () => getAllValidTokensCached(),\n principal: serverConfig.principal,\n maxBodyBytes: serverConfig.maxBodyBytes,\n adminConsoleEnabled: serverConfig.adminConsoleEnabled ?? false,\n citationsEnabled: config.citationsEnabled,\n citationsAutoDetect: config.citationsAutoDetect,\n });\n\n const { host, port } = await httpServer.start();\n\n // Fire-and-forget: wait for deferred init (QMD probe, collection setup,\n // warmup) then check QMD availability and retry if needed. This does NOT\n // block the server listener — connections are accepted immediately above.\n // An AbortController allows the shutdown handler to cancel pending retries.\n const startupSyncAbort = new AbortController();\n\n // Wrap httpServer.stop() so that stopping the HTTP server also cancels any\n // in-flight startup-sync retry timers. This ensures callers that only have\n // a reference to httpServer (e.g. test harnesses) don't leave dangling timers\n // even if they never call cancelStartupSync() directly.\n const originalStop = httpServer.stop.bind(httpServer);\n httpServer.stop = async (): Promise<void> => {\n startupSyncAbort.abort();\n return originalStop();\n };\n\n orchestrator.deferredReady.then(() => {\n // Skip retries when search is explicitly disabled via config or when the\n // orchestrator already resolved to a noop backend (e.g. missing collection\n // detected during deferredInitialize). Both cases mean no sync should ever\n // run; scheduling retries would create misleading operational noise and\n // unnecessary background work on every server start.\n if (!config.qmdEnabled || orchestrator.qmd.debugStatus() === \"backend=noop\") {\n log.debug(\"QMD startup-sync: search disabled or noop backend, skipping retries\");\n return;\n }\n\n // Retry when either: (a) QMD is not available yet (cold-start race), or\n // (b) QMD is available but the deferred init sync step failed silently\n // (e.g., update errors swallowed by backend, throttle skip, transient\n // network failure). Without (b), the daemon permanently serves stale\n // recall after a failed sync despite healthy QMD probe.\n const needsRetry = !orchestrator.qmd.isAvailable() || !orchestrator.deferredSyncSucceeded;\n if (!needsRetry) {\n log.debug(\"QMD startup-sync: deferred init completed successfully, no retries needed\");\n return;\n }\n\n const RETRY_DELAYS_MS = [5_000, 15_000, 30_000, 60_000, 120_000];\n (async () => {\n for (const delay of RETRY_DELAYS_MS) {\n await abortableDelay(delay, startupSyncAbort.signal);\n\n if (startupSyncAbort.signal.aborted) {\n log.debug(\"QMD startup-sync retry: cancelled by shutdown\");\n return;\n }\n\n const synced = await orchestrator.startupSearchSync(startupSyncAbort.signal);\n if (!synced) {\n if (orchestrator.qmd.debugStatus() === \"backend=noop\") {\n log.debug(\"QMD startup-sync retry: search intentionally disabled; stopping retries\");\n return;\n }\n log.debug(`QMD startup-sync retry: not available yet (next retry in ${RETRY_DELAYS_MS[RETRY_DELAYS_MS.indexOf(delay) + 1] ?? \"n/a\"}ms)`);\n continue;\n }\n\n return; // sync succeeded, stop retrying\n }\n\n log.warn(\"QMD startup-sync retry: exhausted all retries; search index may be stale\");\n })().catch((err) => {\n log.warn(`QMD startup-sync retry: unexpected error: ${err}`);\n });\n }).catch((err) => {\n log.warn(`Deferred init error: ${err}`);\n });\n\n return { config, service, httpServer, host, port, cancelStartupSync: () => startupSyncAbort.abort(), abortDeferredInit: () => orchestrator.abortDeferredInit() };\n}\n\n// ── CLI entry point ──────────────────────────────────────────────────────────\n\nfunction parseCliArgs(argv: string[]): Record<string, string | undefined> {\n const args: Record<string, string | undefined> = {};\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i];\n if (token.startsWith(\"--\")) {\n const key = token.slice(2);\n const next = argv[i + 1];\n if (next && !next.startsWith(\"--\")) {\n args[key] = next;\n i++;\n } else {\n args[key] = \"true\";\n }\n }\n }\n return args;\n}\n\nexport async function cliMain(argv: string[] = process.argv.slice(2)): Promise<void> {\n const args = parseCliArgs(argv);\n\n if (args.help) {\n console.log(`\nremnic-server — Standalone Remnic memory server\n\nUsage:\n remnic-server [options]\n\nOptions:\n --config <path> Path to config file (default: remnic.config.json)\n --host <addr> Bind address (default: 127.0.0.1)\n --port <number> Port number (default: 4318)\n --auth-token <tok> Bearer token for auth (or set REMNIC_AUTH_TOKEN)\n --help Show this help\n\nEnvironment:\n REMNIC_CONFIG_PATH Config file path (ENGRAM_CONFIG_PATH also supported)\n REMNIC_PORT Server port (ENGRAM_PORT also supported)\n REMNIC_HOST Bind address (ENGRAM_HOST also supported)\n REMNIC_AUTH_TOKEN Auth bearer token (ENGRAM_AUTH_TOKEN also supported)\n REMNIC_MEMORY_DIR Override memory directory (ENGRAM_MEMORY_DIR also supported)\n OPENAI_API_KEY OpenAI API key for extraction\n`);\n process.exit(0);\n }\n\n const result = await startServer({\n configPath: args.config,\n host: args.host,\n port: args.port ? parseInt(args.port, 10) : undefined,\n authToken: args[\"auth-token\"],\n });\n\n console.log(`Remnic server listening on http://${result.host}:${result.port}`);\n\n // Graceful shutdown\n const shutdown = async (signal: string) => {\n console.log(`\\nReceived ${signal}, shutting down...`);\n result.cancelStartupSync();\n result.abortDeferredInit();\n await result.httpServer.stop();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n}\n\n// Auto-run when executed directly\n// Matches: `node .../remnic-server/dist/index.js`, `node .../remnic-server/src/index.ts`,\n// `npx remnic-server`, `npx engram-server`, but NOT test files under those directories\nif (\n process.argv[1] &&\n (/remnic-server[\\\\/](?:dist|src)[\\\\/]index\\.[jt]s$/.test(process.argv[1]) ||\n /engram-server[\\\\/](?:dist|src)[\\\\/]index\\.[jt]s$/.test(process.argv[1]) ||\n process.argv[1].endsWith(\"remnic-server\") ||\n process.argv[1].endsWith(\"engram-server\"))\n) {\n cliMain().catch((err) => {\n process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env node
2
- // openclaw-engram: Local-first memory plugin
3
- import {
4
- cliMain
5
- } from "./chunk-FFTS3VKE.js";
6
-
7
- // bin/engram-server.ts
8
- cliMain().catch((err) => {
9
- process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
10
- `);
11
- process.exit(1);
12
- });
13
- //# sourceMappingURL=engram-server.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../bin/engram-server.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * engram-server binary entry point.\n * Delegates to @remnic/server CLI main.\n */\nimport { cliMain } from \"../src/index.js\";\n\ncliMain().catch((err) => {\n process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n"],"mappings":";;;;;;;AAOA,QAAQ,EAAE,MAAM,CAAC,QAAQ;AACvB,UAAQ,OAAO,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AACnF,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env node
2
- // openclaw-engram: Local-first memory plugin
3
- import {
4
- cliMain
5
- } from "./chunk-FFTS3VKE.js";
6
-
7
- // bin/remnic-server.ts
8
- cliMain().catch((err) => {
9
- process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
10
- `);
11
- process.exit(1);
12
- });
13
- //# sourceMappingURL=remnic-server.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../bin/remnic-server.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * remnic-server binary entry point.\n * Delegates to @remnic/server CLI main.\n */\nimport { cliMain } from \"../src/index.js\";\n\ncliMain().catch((err) => {\n process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n"],"mappings":";;;;;;;AAOA,QAAQ,EAAE,MAAM,CAAC,QAAQ;AACvB,UAAQ,OAAO,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AACnF,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}