@remnic/server 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/{chunk-MKNXEH3P.js → chunk-FFTS3VKE.js} +58 -2
- package/dist/bin/chunk-FFTS3VKE.js.map +1 -0
- package/dist/bin/engram-server.js +1 -1
- package/dist/bin/remnic-server.js +1 -1
- package/dist/index.js +57 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/bin/chunk-MKNXEH3P.js.map +0 -1
|
@@ -44,6 +44,17 @@ function envOverrides() {
|
|
|
44
44
|
if (memoryDir) remnic.memoryDir = memoryDir;
|
|
45
45
|
return { ...overrides, ...Object.keys(remnic).length > 0 ? { remnic } : {} };
|
|
46
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
|
+
}
|
|
47
58
|
async function startServer(options) {
|
|
48
59
|
initLogger();
|
|
49
60
|
const configPath = resolveConfigPath(options?.configPath);
|
|
@@ -59,6 +70,7 @@ async function startServer(options) {
|
|
|
59
70
|
};
|
|
60
71
|
const config = parseConfig(remnicConfig);
|
|
61
72
|
const orchestrator = new Orchestrator(config);
|
|
73
|
+
await orchestrator.initialize();
|
|
62
74
|
const service = new EngramAccessService(orchestrator);
|
|
63
75
|
const authToken = serverConfig.authToken ?? readCompatEnv("REMNIC_AUTH_TOKEN", "ENGRAM_AUTH_TOKEN") ?? "";
|
|
64
76
|
if (!authToken && getAllValidTokens().length === 0) {
|
|
@@ -77,7 +89,49 @@ async function startServer(options) {
|
|
|
77
89
|
citationsAutoDetect: config.citationsAutoDetect
|
|
78
90
|
});
|
|
79
91
|
const { host, port } = await httpServer.start();
|
|
80
|
-
|
|
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() };
|
|
81
135
|
}
|
|
82
136
|
function parseCliArgs(argv) {
|
|
83
137
|
const args = {};
|
|
@@ -132,6 +186,8 @@ Environment:
|
|
|
132
186
|
const shutdown = async (signal) => {
|
|
133
187
|
console.log(`
|
|
134
188
|
Received ${signal}, shutting down...`);
|
|
189
|
+
result.cancelStartupSync();
|
|
190
|
+
result.abortDeferredInit();
|
|
135
191
|
await result.httpServer.stop();
|
|
136
192
|
process.exit(0);
|
|
137
193
|
};
|
|
@@ -149,4 +205,4 @@ if (process.argv[1] && (/remnic-server[\\/](?:dist|src)[\\/]index\.[jt]s$/.test(
|
|
|
149
205
|
export {
|
|
150
206
|
cliMain
|
|
151
207
|
};
|
|
152
|
-
//# sourceMappingURL=chunk-
|
|
208
|
+
//# sourceMappingURL=chunk-FFTS3VKE.js.map
|
|
@@ -0,0 +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":[]}
|
package/dist/index.js
CHANGED
|
@@ -44,6 +44,17 @@ function envOverrides() {
|
|
|
44
44
|
if (memoryDir) remnic.memoryDir = memoryDir;
|
|
45
45
|
return { ...overrides, ...Object.keys(remnic).length > 0 ? { remnic } : {} };
|
|
46
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
|
+
}
|
|
47
58
|
async function startServer(options) {
|
|
48
59
|
initLogger();
|
|
49
60
|
const configPath = resolveConfigPath(options?.configPath);
|
|
@@ -59,6 +70,7 @@ async function startServer(options) {
|
|
|
59
70
|
};
|
|
60
71
|
const config = parseConfig(remnicConfig);
|
|
61
72
|
const orchestrator = new Orchestrator(config);
|
|
73
|
+
await orchestrator.initialize();
|
|
62
74
|
const service = new EngramAccessService(orchestrator);
|
|
63
75
|
const authToken = serverConfig.authToken ?? readCompatEnv("REMNIC_AUTH_TOKEN", "ENGRAM_AUTH_TOKEN") ?? "";
|
|
64
76
|
if (!authToken && getAllValidTokens().length === 0) {
|
|
@@ -77,7 +89,49 @@ async function startServer(options) {
|
|
|
77
89
|
citationsAutoDetect: config.citationsAutoDetect
|
|
78
90
|
});
|
|
79
91
|
const { host, port } = await httpServer.start();
|
|
80
|
-
|
|
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() };
|
|
81
135
|
}
|
|
82
136
|
function parseCliArgs(argv) {
|
|
83
137
|
const args = {};
|
|
@@ -132,6 +186,8 @@ Environment:
|
|
|
132
186
|
const shutdown = async (signal) => {
|
|
133
187
|
console.log(`
|
|
134
188
|
Received ${signal}, shutting down...`);
|
|
189
|
+
result.cancelStartupSync();
|
|
190
|
+
result.abortDeferredInit();
|
|
135
191
|
await result.httpServer.stop();
|
|
136
192
|
process.exit(0);
|
|
137
193
|
};
|
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// ── Server startup ──────────────────────────────────────────────────────────\n\nexport interface ServerResult {\n config: PluginConfig;\n service: EngramAccessService;\n httpServer: EngramAccessHttpServer;\n host: string;\n port: number;\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 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 return { config, service, httpServer, host, port };\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 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 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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Standalone Remnic memory server — HTTP + MCP without OpenClaw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"provenance": true
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@remnic/core": "^1.0.
|
|
24
|
+
"@remnic/core": "^1.0.3"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"tsup": "^8.5.1",
|
|
@@ -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// ── Server startup ──────────────────────────────────────────────────────────\n\nexport interface ServerResult {\n config: PluginConfig;\n service: EngramAccessService;\n httpServer: EngramAccessHttpServer;\n host: string;\n port: number;\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 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 return { config, service, httpServer, host, port };\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 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":[]}
|