@mclean-capital/neura 2.0.0 → 2.1.1

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
@@ -16,6 +16,34 @@ This fetches the CLI **plus** the bundled core service and its native dependenci
16
16
 
17
17
  Requires **Node.js >= 22**.
18
18
 
19
+ ### Supported platforms
20
+
21
+ | Platform | Supported |
22
+ | ----------------------------------- | :-------: |
23
+ | macOS — Apple Silicon (M1/M2/M3/M4) | Yes |
24
+ | macOS — Intel (x64) | **No** |
25
+ | Windows — x64 / arm64 | Yes |
26
+ | Linux — x64 / arm64 | Yes |
27
+
28
+ **Intel Macs are not supported.** Neura's wake-word detector runs on
29
+ `onnxruntime-node`, and upstream dropped Intel Mac (`darwin/x64`) binaries
30
+ starting with version 1.24. Because voice is a required feature — not an
31
+ optional one — `npm install -g @mclean-capital/neura` will appear to
32
+ succeed but core will crash at startup with
33
+ `Cannot find module '../bin/napi-v6/darwin/x64/onnxruntime_binding.node'`.
34
+
35
+ If you're on an Apple Silicon Mac but see the `darwin/x64` error, you've
36
+ installed the Intel build of Node under Rosetta. Reinstall Node as arm64:
37
+
38
+ ```bash
39
+ nvm uninstall <version>
40
+ arch -arm64 nvm install <version>
41
+ ```
42
+
43
+ For true Intel Macs, there's no workaround short of self-building against
44
+ an older onnxruntime-node — we recommend running Neura on a supported
45
+ machine instead.
46
+
19
47
  ## Quick start
20
48
 
21
49
  ```bash
@@ -26,7 +54,21 @@ neura listen # voice chat (mic + speaker, wake-word ready)
26
54
  neura chat # text chat from your terminal
27
55
  ```
28
56
 
29
- After `neura install` completes, core runs as a background OS service (launchd on macOS, systemd on Linux). Say your wake word — by default **"jarvis"** — and Neura activates a voice session. Stop talking for 5 minutes and it drops back to passive listening.
57
+ After `neura install` completes, core runs as a background OS service launchd on macOS, systemd on Linux, a Scheduled Task (or Startup folder shim as fallback) on Windows. Say your wake word — by default **"jarvis"** — and Neura activates a voice session. Stop talking for 5 minutes and it drops back to passive listening.
58
+
59
+ ### Windows notes
60
+
61
+ Windows doesn't have a clean "run as background service" path for a voice-first assistant. A proper Windows Service (via `sc.exe`, nssm, or WinSW) runs in **Session 0**, which is isolated from every interactive user session and cannot access the user's microphone or audio devices — a hard blocker for wake-word detection. So on Windows we use a per-user **Scheduled Task** instead, registered by `schtasks.exe` with `/SC ONLOGON /RL LIMITED`. No UAC prompt, no admin rights, no bundled binaries.
62
+
63
+ If `schtasks /Create` refuses (some corporate / GPO-locked Windows configurations require elevation even for user-level tasks), `neura install` transparently falls back to a `neura-core.cmd` launcher in `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\`. The core still starts on next logon — `neura install` will tell you which path was taken so you know what to expect.
64
+
65
+ Trade-offs you should know about on Windows:
66
+
67
+ - **The core only runs while you're logged in.** When you log out, it stops; when you log back in, Windows starts it again. There's no pre-login boot. If you need 24/7 availability, use macOS or Linux.
68
+ - **Status telemetry is thinner** than a real service. `neura status` reports up/down by pid, but there's no Windows event-log integration or automatic crash-restart policy beyond "start fresh on next logon".
69
+ - **The Task Scheduler path is manageable from the GUI.** Open Task Scheduler and look for `neura-core` under the top-level Task Scheduler Library. If you took the Startup folder fallback, the shim is at the path above — delete the file to disable.
70
+
71
+ `neura config set` works normally on Windows. The launcher shim only exports `NEURA_HOME`, so the core re-reads `config.json` on every restart — same behavior as macOS and Linux. Changes take effect on the next `neura restart` without needing `neura install`.
30
72
 
31
73
  ## Update
32
74
 
@@ -39,410 +39,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
39
39
  mod
40
40
  ));
41
41
 
42
- // ../../node_modules/dotenv/package.json
43
- var require_package = __commonJS({
44
- "../../node_modules/dotenv/package.json"(exports, module) {
45
- module.exports = {
46
- name: "dotenv",
47
- version: "16.6.1",
48
- description: "Loads environment variables from .env file",
49
- main: "lib/main.js",
50
- types: "lib/main.d.ts",
51
- exports: {
52
- ".": {
53
- types: "./lib/main.d.ts",
54
- require: "./lib/main.js",
55
- default: "./lib/main.js"
56
- },
57
- "./config": "./config.js",
58
- "./config.js": "./config.js",
59
- "./lib/env-options": "./lib/env-options.js",
60
- "./lib/env-options.js": "./lib/env-options.js",
61
- "./lib/cli-options": "./lib/cli-options.js",
62
- "./lib/cli-options.js": "./lib/cli-options.js",
63
- "./package.json": "./package.json"
64
- },
65
- scripts: {
66
- "dts-check": "tsc --project tests/types/tsconfig.json",
67
- lint: "standard",
68
- pretest: "npm run lint && npm run dts-check",
69
- test: "tap run --allow-empty-coverage --disable-coverage --timeout=60000",
70
- "test:coverage": "tap run --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov",
71
- prerelease: "npm test",
72
- release: "standard-version"
73
- },
74
- repository: {
75
- type: "git",
76
- url: "git://github.com/motdotla/dotenv.git"
77
- },
78
- homepage: "https://github.com/motdotla/dotenv#readme",
79
- funding: "https://dotenvx.com",
80
- keywords: [
81
- "dotenv",
82
- "env",
83
- ".env",
84
- "environment",
85
- "variables",
86
- "config",
87
- "settings"
88
- ],
89
- readmeFilename: "README.md",
90
- license: "BSD-2-Clause",
91
- devDependencies: {
92
- "@types/node": "^18.11.3",
93
- decache: "^4.6.2",
94
- sinon: "^14.0.1",
95
- standard: "^17.0.0",
96
- "standard-version": "^9.5.0",
97
- tap: "^19.2.0",
98
- typescript: "^4.8.4"
99
- },
100
- engines: {
101
- node: ">=12"
102
- },
103
- browser: {
104
- fs: false
105
- }
106
- };
107
- }
108
- });
109
-
110
- // ../../node_modules/dotenv/lib/main.js
111
- var require_main = __commonJS({
112
- "../../node_modules/dotenv/lib/main.js"(exports, module) {
113
- var fs3 = __require("fs");
114
- var path2 = __require("path");
115
- var os = __require("os");
116
- var crypto3 = __require("crypto");
117
- var packageJson = require_package();
118
- var version = packageJson.version;
119
- var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
120
- function parse(src) {
121
- const obj = {};
122
- let lines = src.toString();
123
- lines = lines.replace(/\r\n?/mg, "\n");
124
- let match;
125
- while ((match = LINE.exec(lines)) != null) {
126
- const key = match[1];
127
- let value = match[2] || "";
128
- value = value.trim();
129
- const maybeQuote = value[0];
130
- value = value.replace(/^(['"`])([\s\S]*)\1$/mg, "$2");
131
- if (maybeQuote === '"') {
132
- value = value.replace(/\\n/g, "\n");
133
- value = value.replace(/\\r/g, "\r");
134
- }
135
- obj[key] = value;
136
- }
137
- return obj;
138
- }
139
- function _parseVault(options) {
140
- options = options || {};
141
- const vaultPath = _vaultPath(options);
142
- options.path = vaultPath;
143
- const result = DotenvModule.configDotenv(options);
144
- if (!result.parsed) {
145
- const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
146
- err.code = "MISSING_DATA";
147
- throw err;
148
- }
149
- const keys = _dotenvKey(options).split(",");
150
- const length = keys.length;
151
- let decrypted;
152
- for (let i2 = 0; i2 < length; i2++) {
153
- try {
154
- const key = keys[i2].trim();
155
- const attrs = _instructions(result, key);
156
- decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
157
- break;
158
- } catch (error) {
159
- if (i2 + 1 >= length) {
160
- throw error;
161
- }
162
- }
163
- }
164
- return DotenvModule.parse(decrypted);
165
- }
166
- function _warn(message) {
167
- console.log(`[dotenv@${version}][WARN] ${message}`);
168
- }
169
- function _debug(message) {
170
- console.log(`[dotenv@${version}][DEBUG] ${message}`);
171
- }
172
- function _log(message) {
173
- console.log(`[dotenv@${version}] ${message}`);
174
- }
175
- function _dotenvKey(options) {
176
- if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
177
- return options.DOTENV_KEY;
178
- }
179
- if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
180
- return process.env.DOTENV_KEY;
181
- }
182
- return "";
183
- }
184
- function _instructions(result, dotenvKey) {
185
- let uri;
186
- try {
187
- uri = new URL(dotenvKey);
188
- } catch (error) {
189
- if (error.code === "ERR_INVALID_URL") {
190
- const err = new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
191
- err.code = "INVALID_DOTENV_KEY";
192
- throw err;
193
- }
194
- throw error;
195
- }
196
- const key = uri.password;
197
- if (!key) {
198
- const err = new Error("INVALID_DOTENV_KEY: Missing key part");
199
- err.code = "INVALID_DOTENV_KEY";
200
- throw err;
201
- }
202
- const environment = uri.searchParams.get("environment");
203
- if (!environment) {
204
- const err = new Error("INVALID_DOTENV_KEY: Missing environment part");
205
- err.code = "INVALID_DOTENV_KEY";
206
- throw err;
207
- }
208
- const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
209
- const ciphertext = result.parsed[environmentKey];
210
- if (!ciphertext) {
211
- const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
212
- err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
213
- throw err;
214
- }
215
- return { ciphertext, key };
216
- }
217
- function _vaultPath(options) {
218
- let possibleVaultPath = null;
219
- if (options && options.path && options.path.length > 0) {
220
- if (Array.isArray(options.path)) {
221
- for (const filepath of options.path) {
222
- if (fs3.existsSync(filepath)) {
223
- possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
224
- }
225
- }
226
- } else {
227
- possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
228
- }
229
- } else {
230
- possibleVaultPath = path2.resolve(process.cwd(), ".env.vault");
231
- }
232
- if (fs3.existsSync(possibleVaultPath)) {
233
- return possibleVaultPath;
234
- }
235
- return null;
236
- }
237
- function _resolveHome(envPath) {
238
- return envPath[0] === "~" ? path2.join(os.homedir(), envPath.slice(1)) : envPath;
239
- }
240
- function _configVault(options) {
241
- const debug = Boolean(options && options.debug);
242
- const quiet = options && "quiet" in options ? options.quiet : true;
243
- if (debug || !quiet) {
244
- _log("Loading env from encrypted .env.vault");
245
- }
246
- const parsed = DotenvModule._parseVault(options);
247
- let processEnv = process.env;
248
- if (options && options.processEnv != null) {
249
- processEnv = options.processEnv;
250
- }
251
- DotenvModule.populate(processEnv, parsed, options);
252
- return { parsed };
253
- }
254
- function configDotenv(options) {
255
- const dotenvPath = path2.resolve(process.cwd(), ".env");
256
- let encoding = "utf8";
257
- const debug = Boolean(options && options.debug);
258
- const quiet = options && "quiet" in options ? options.quiet : true;
259
- if (options && options.encoding) {
260
- encoding = options.encoding;
261
- } else {
262
- if (debug) {
263
- _debug("No encoding is specified. UTF-8 is used by default");
264
- }
265
- }
266
- let optionPaths = [dotenvPath];
267
- if (options && options.path) {
268
- if (!Array.isArray(options.path)) {
269
- optionPaths = [_resolveHome(options.path)];
270
- } else {
271
- optionPaths = [];
272
- for (const filepath of options.path) {
273
- optionPaths.push(_resolveHome(filepath));
274
- }
275
- }
276
- }
277
- let lastError;
278
- const parsedAll = {};
279
- for (const path3 of optionPaths) {
280
- try {
281
- const parsed = DotenvModule.parse(fs3.readFileSync(path3, { encoding }));
282
- DotenvModule.populate(parsedAll, parsed, options);
283
- } catch (e2) {
284
- if (debug) {
285
- _debug(`Failed to load ${path3} ${e2.message}`);
286
- }
287
- lastError = e2;
288
- }
289
- }
290
- let processEnv = process.env;
291
- if (options && options.processEnv != null) {
292
- processEnv = options.processEnv;
293
- }
294
- DotenvModule.populate(processEnv, parsedAll, options);
295
- if (debug || !quiet) {
296
- const keysCount = Object.keys(parsedAll).length;
297
- const shortPaths = [];
298
- for (const filePath of optionPaths) {
299
- try {
300
- const relative = path2.relative(process.cwd(), filePath);
301
- shortPaths.push(relative);
302
- } catch (e2) {
303
- if (debug) {
304
- _debug(`Failed to load ${filePath} ${e2.message}`);
305
- }
306
- lastError = e2;
307
- }
308
- }
309
- _log(`injecting env (${keysCount}) from ${shortPaths.join(",")}`);
310
- }
311
- if (lastError) {
312
- return { parsed: parsedAll, error: lastError };
313
- } else {
314
- return { parsed: parsedAll };
315
- }
316
- }
317
- function config(options) {
318
- if (_dotenvKey(options).length === 0) {
319
- return DotenvModule.configDotenv(options);
320
- }
321
- const vaultPath = _vaultPath(options);
322
- if (!vaultPath) {
323
- _warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
324
- return DotenvModule.configDotenv(options);
325
- }
326
- return DotenvModule._configVault(options);
327
- }
328
- function decrypt(encrypted, keyStr) {
329
- const key = Buffer.from(keyStr.slice(-64), "hex");
330
- let ciphertext = Buffer.from(encrypted, "base64");
331
- const nonce = ciphertext.subarray(0, 12);
332
- const authTag = ciphertext.subarray(-16);
333
- ciphertext = ciphertext.subarray(12, -16);
334
- try {
335
- const aesgcm = crypto3.createDecipheriv("aes-256-gcm", key, nonce);
336
- aesgcm.setAuthTag(authTag);
337
- return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
338
- } catch (error) {
339
- const isRange = error instanceof RangeError;
340
- const invalidKeyLength = error.message === "Invalid key length";
341
- const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
342
- if (isRange || invalidKeyLength) {
343
- const err = new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
344
- err.code = "INVALID_DOTENV_KEY";
345
- throw err;
346
- } else if (decryptionFailed) {
347
- const err = new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
348
- err.code = "DECRYPTION_FAILED";
349
- throw err;
350
- } else {
351
- throw error;
352
- }
353
- }
354
- }
355
- function populate(processEnv, parsed, options = {}) {
356
- const debug = Boolean(options && options.debug);
357
- const override = Boolean(options && options.override);
358
- if (typeof parsed !== "object") {
359
- const err = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
360
- err.code = "OBJECT_REQUIRED";
361
- throw err;
362
- }
363
- for (const key of Object.keys(parsed)) {
364
- if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
365
- if (override === true) {
366
- processEnv[key] = parsed[key];
367
- }
368
- if (debug) {
369
- if (override === true) {
370
- _debug(`"${key}" is already defined and WAS overwritten`);
371
- } else {
372
- _debug(`"${key}" is already defined and was NOT overwritten`);
373
- }
374
- }
375
- } else {
376
- processEnv[key] = parsed[key];
377
- }
378
- }
379
- }
380
- var DotenvModule = {
381
- configDotenv,
382
- _configVault,
383
- _parseVault,
384
- config,
385
- decrypt,
386
- parse,
387
- populate
388
- };
389
- module.exports.configDotenv = DotenvModule.configDotenv;
390
- module.exports._configVault = DotenvModule._configVault;
391
- module.exports._parseVault = DotenvModule._parseVault;
392
- module.exports.config = DotenvModule.config;
393
- module.exports.decrypt = DotenvModule.decrypt;
394
- module.exports.parse = DotenvModule.parse;
395
- module.exports.populate = DotenvModule.populate;
396
- module.exports = DotenvModule;
397
- }
398
- });
399
-
400
- // ../../node_modules/dotenv/lib/env-options.js
401
- var require_env_options = __commonJS({
402
- "../../node_modules/dotenv/lib/env-options.js"(exports, module) {
403
- var options = {};
404
- if (process.env.DOTENV_CONFIG_ENCODING != null) {
405
- options.encoding = process.env.DOTENV_CONFIG_ENCODING;
406
- }
407
- if (process.env.DOTENV_CONFIG_PATH != null) {
408
- options.path = process.env.DOTENV_CONFIG_PATH;
409
- }
410
- if (process.env.DOTENV_CONFIG_QUIET != null) {
411
- options.quiet = process.env.DOTENV_CONFIG_QUIET;
412
- }
413
- if (process.env.DOTENV_CONFIG_DEBUG != null) {
414
- options.debug = process.env.DOTENV_CONFIG_DEBUG;
415
- }
416
- if (process.env.DOTENV_CONFIG_OVERRIDE != null) {
417
- options.override = process.env.DOTENV_CONFIG_OVERRIDE;
418
- }
419
- if (process.env.DOTENV_CONFIG_DOTENV_KEY != null) {
420
- options.DOTENV_KEY = process.env.DOTENV_CONFIG_DOTENV_KEY;
421
- }
422
- module.exports = options;
423
- }
424
- });
425
-
426
- // ../../node_modules/dotenv/lib/cli-options.js
427
- var require_cli_options = __commonJS({
428
- "../../node_modules/dotenv/lib/cli-options.js"(exports, module) {
429
- var re = /^dotenv_config_(encoding|path|quiet|debug|override|DOTENV_KEY)=(.+)$/;
430
- module.exports = function optionMatcher(args) {
431
- const options = args.reduce(function(acc, cur) {
432
- const matches = cur.match(re);
433
- if (matches) {
434
- acc[matches[1]] = matches[2];
435
- }
436
- return acc;
437
- }, {});
438
- if (!("quiet" in options)) {
439
- options.quiet = "true";
440
- }
441
- return options;
442
- };
443
- }
444
- });
445
-
446
42
  // ../../node_modules/pino-std-serializers/lib/err-helpers.js
447
43
  var require_err_helpers = __commonJS({
448
44
  "../../node_modules/pino-std-serializers/lib/err-helpers.js"(exports, module) {
@@ -2296,7 +1892,7 @@ var require_on_exit_leak_free = __commonJS({
2296
1892
  });
2297
1893
 
2298
1894
  // ../../node_modules/thread-stream/package.json
2299
- var require_package2 = __commonJS({
1895
+ var require_package = __commonJS({
2300
1896
  "../../node_modules/thread-stream/package.json"(exports, module) {
2301
1897
  module.exports = {
2302
1898
  name: "thread-stream",
@@ -2433,7 +2029,7 @@ var require_indexes = __commonJS({
2433
2029
  var require_thread_stream = __commonJS({
2434
2030
  "../../node_modules/thread-stream/index.js"(exports, module) {
2435
2031
  "use strict";
2436
- var { version } = require_package2();
2032
+ var { version } = require_package();
2437
2033
  var { EventEmitter } = __require("events");
2438
2034
  var { Worker } = __require("worker_threads");
2439
2035
  var { join: join6 } = __require("path");
@@ -5147,7 +4743,7 @@ var require_extend = __commonJS({
5147
4743
  });
5148
4744
 
5149
4745
  // ../../node_modules/gaxios/package.json
5150
- var require_package3 = __commonJS({
4746
+ var require_package2 = __commonJS({
5151
4747
  "../../node_modules/gaxios/package.json"(exports, module) {
5152
4748
  module.exports = {
5153
4749
  name: "gaxios",
@@ -5263,7 +4859,7 @@ var require_package3 = __commonJS({
5263
4859
  var require_util = __commonJS({
5264
4860
  "../../node_modules/gaxios/build/cjs/src/util.cjs"(exports, module) {
5265
4861
  "use strict";
5266
- var pkg = require_package3();
4862
+ var pkg = require_package2();
5267
4863
  module.exports = { pkg };
5268
4864
  }
5269
4865
  });
@@ -16965,7 +16561,7 @@ var require_util2 = __commonJS({
16965
16561
  });
16966
16562
 
16967
16563
  // ../../node_modules/google-auth-library/package.json
16968
- var require_package4 = __commonJS({
16564
+ var require_package3 = __commonJS({
16969
16565
  "../../node_modules/google-auth-library/package.json"(exports, module) {
16970
16566
  module.exports = {
16971
16567
  name: "google-auth-library",
@@ -17068,7 +16664,7 @@ var require_shared2 = __commonJS({
17068
16664
  "use strict";
17069
16665
  Object.defineProperty(exports, "__esModule", { value: true });
17070
16666
  exports.USER_AGENT = exports.PRODUCT_NAME = exports.pkg = void 0;
17071
- var pkg = require_package4();
16667
+ var pkg = require_package3();
17072
16668
  exports.pkg = pkg;
17073
16669
  var PRODUCT_NAME = "google-api-nodejs-client";
17074
16670
  exports.PRODUCT_NAME = PRODUCT_NAME;
@@ -50503,17 +50099,6 @@ var require_express2 = __commonJS({
50503
50099
  }
50504
50100
  });
50505
50101
 
50506
- // ../../node_modules/dotenv/config.js
50507
- (function() {
50508
- require_main().config(
50509
- Object.assign(
50510
- {},
50511
- require_env_options(),
50512
- require_cli_options()(process.argv)
50513
- )
50514
- );
50515
- })();
50516
-
50517
50102
  // src/server/server.ts
50518
50103
  import { createServer } from "http";
50519
50104
 
@@ -50558,7 +50143,7 @@ var Logger = class _Logger {
50558
50143
  };
50559
50144
 
50560
50145
  // src/server/lifecycle.ts
50561
- import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync, unlinkSync } from "fs";
50146
+ import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
50562
50147
  import { dirname as dirname2, join as join2 } from "path";
50563
50148
  import { fileURLToPath } from "url";
50564
50149
 
@@ -69098,6 +68683,23 @@ async function initServices() {
69098
68683
  if (config.xaiApiKey && !process.env.XAI_API_KEY) process.env.XAI_API_KEY = config.xaiApiKey;
69099
68684
  if (config.googleApiKey && !process.env.GOOGLE_API_KEY)
69100
68685
  process.env.GOOGLE_API_KEY = config.googleApiKey;
68686
+ if (process.platform === "win32") {
68687
+ const corePidFile = join2(config.neuraHome, "neura-core.pid");
68688
+ try {
68689
+ writeFileSync2(corePidFile, String(process.pid));
68690
+ const cleanup = () => {
68691
+ try {
68692
+ unlinkSync(corePidFile);
68693
+ } catch {
68694
+ }
68695
+ };
68696
+ process.on("exit", cleanup);
68697
+ process.on("SIGINT", cleanup);
68698
+ process.on("SIGTERM", cleanup);
68699
+ } catch (err) {
68700
+ log6.warn("failed to write neura-core.pid", { err: String(err) });
68701
+ }
68702
+ }
69101
68703
  let store = null;
69102
68704
  const backupPath = join2(config.neuraHome, "memory-backup.json");
69103
68705
  if (config.pgDataPath) {