@mclean-capital/neura 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core/version.txt CHANGED
@@ -1 +1 @@
1
- 2.0.0
1
+ 2.1.0
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,cAAc;IAC7B;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,cAAc,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmL7E"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,cAAc;IAC7B;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,cAAc,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyM7E"}
@@ -137,14 +137,31 @@ export async function installCommand(opts = {}) {
137
137
  }
138
138
  await svc.install();
139
139
  console.log(chalk.green(` ✓ Service ${wasInstalled ? 're-registered' : 'registered'} (${getPlatformLabel()})`));
140
+ // Windows has two install paths (Scheduled Task → preferred, or
141
+ // Startup folder shim → fallback). Tell the user which one was
142
+ // used so they understand what to expect — e.g. Task Scheduler
143
+ // manageability vs. runs-on-next-login. Empty import on non-Windows.
144
+ if (process.platform === 'win32') {
145
+ const win = await import('../service/windows.js');
146
+ const mode = win.getLastInstallMode();
147
+ if (mode === 'startup-shim') {
148
+ console.log(chalk.dim(' (Using Startup folder shim — schtasks.exe refused to register\n' +
149
+ ' the Scheduled Task on this machine, likely due to Windows\n' +
150
+ ' policy or corporate restrictions. The core will still run\n' +
151
+ ' at each user login. It can be removed from\n' +
152
+ ' %APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\.)'));
153
+ }
154
+ else if (mode === 'scheduled-task') {
155
+ console.log(chalk.dim(' (Registered in Task Scheduler under name "neura-core")'));
156
+ }
157
+ }
140
158
  svc.start();
141
159
  serviceRegistered = true;
142
160
  }
143
161
  catch (err) {
144
162
  console.log(chalk.yellow(' Service registration skipped:'));
145
163
  console.log(chalk.yellow(' ' + (err instanceof Error ? err.message : String(err))));
146
- console.log(chalk.dim(' Config was saved. You can run core manually:'));
147
- console.log(chalk.dim(' npm run dev -w @neura/core'));
164
+ console.log(chalk.dim(' Config was saved. Try again after resolving the issue.'));
148
165
  }
149
166
  // Wait for health (only if service was started)
150
167
  if (serviceRegistered) {
@@ -1 +1 @@
1
- {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"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"}
@@ -29,7 +29,7 @@ export function isElevated() {
29
29
  }
30
30
  export function getPlatformLabel() {
31
31
  const labels = {
32
- windows: 'Windows Service (winsw)',
32
+ windows: 'Windows Scheduled Task (user logon)',
33
33
  macos: 'launchd Agent',
34
34
  linux: 'systemd User Service',
35
35
  };
@@ -1 +1 @@
1
- {"version":3,"file":"detect.js","sourceRoot":"","sources":["../../src/service/detect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,MAAM,UAAU,cAAc;IAC5B,QAAQ,QAAQ,EAAE,EAAE,CAAC;QACnB,KAAK,OAAO;YACV,OAAO,SAAS,CAAC;QACnB,KAAK,QAAQ;YACX,OAAO,OAAO,CAAC;QACjB;YACE,OAAO,OAAO,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC7C,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,EAAE,EAAE,KAAK,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,MAAM,GAA6B;QACvC,OAAO,EAAE,yBAAyB;QAClC,KAAK,EAAE,eAAe;QACtB,KAAK,EAAE,sBAAsB;KAC9B,CAAC;IACF,OAAO,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;AAClC,CAAC"}
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../../src/service/detect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,MAAM,UAAU,cAAc;IAC5B,QAAQ,QAAQ,EAAE,EAAE,CAAC;QACnB,KAAK,OAAO;YACV,OAAO,SAAS,CAAC;QACnB,KAAK,QAAQ;YACX,OAAO,OAAO,CAAC;QACjB;YACE,OAAO,OAAO,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC7C,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,EAAE,EAAE,KAAK,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,MAAM,GAA6B;QACvC,OAAO,EAAE,qCAAqC;QAC9C,KAAK,EAAE,eAAe;QACtB,KAAK,EAAE,sBAAsB;KAC9B,CAAC;IACF,OAAO,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;AAClC,CAAC"}
@@ -1,5 +1,114 @@
1
+ /**
2
+ * Windows service manager.
3
+ *
4
+ * Windows does not have a clean "run as background service" path for a
5
+ * voice-first AI assistant. A real SCM service (via nssm/WinSW) requires
6
+ * admin rights to install and runs in Session 0, which is isolated from
7
+ * the user's audio devices — the microphone is unreachable from a
8
+ * LocalSystem service. That makes wake-word detection impossible.
9
+ *
10
+ * So we do what `openclaw` does on Windows:
11
+ *
12
+ * 1. Primary path — `schtasks.exe /Create /SC ONLOGON /RL LIMITED` to
13
+ * register a per-user logon-triggered Scheduled Task. `/RL LIMITED`
14
+ * keeps the task out of the elevated-integrity bucket, so no UAC
15
+ * prompt, no admin rights, no bundled service wrapper.
16
+ *
17
+ * 2. Fallback path — if schtasks.exe refuses (GPO, corporate lockdown,
18
+ * "access denied" on `/Create`), drop a `neura-core.cmd` shim into
19
+ * `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\`. The
20
+ * shim runs on next logon; on first install we also spawn the core
21
+ * detached immediately so the user doesn't have to log out to get
22
+ * a working assistant.
23
+ *
24
+ * 3. Dual state — every lifecycle op (`isInstalled`, `stop`, `restart`,
25
+ * `uninstall`) has to handle both "Scheduled Task is registered"
26
+ * and "Startup shim is present". They're mutually-exclusive install
27
+ * states but the code has to check both.
28
+ *
29
+ * Trade-offs we accept:
30
+ * - The core only runs while the user is logged in (dies on logout).
31
+ * - Status telemetry is weaker than a real service (no exit-code
32
+ * history, no restart policy beyond "schtasks will re-run on next
33
+ * logon"). We compensate with a PID file for `isRunning` / `stop`.
34
+ * - No pre-login boot. If you want that, use macOS or Linux.
35
+ */
36
+ interface ShimContext {
37
+ nodePath: string;
38
+ corePath: string;
39
+ home: string;
40
+ logDir: string;
41
+ logFile: string;
42
+ errLogFile: string;
43
+ pidFile: string;
44
+ cmdPath: string;
45
+ }
46
+ declare function getShimContext(): ShimContext;
47
+ declare function getStartupShimPath(): string;
48
+ /**
49
+ * Escape a string for safe embedding in a Windows `cmd.exe` batch file.
50
+ *
51
+ * The rules here are painful: `cmd.exe` treats `%`, `^`, `&`, `|`, `<`, `>`
52
+ * as special outside quoted strings, and `%` is still special inside quotes
53
+ * when it looks like an environment variable reference. We use `^` to
54
+ * escape the metacharacters and double `%%` so environment-variable
55
+ * expansion only happens where we actually want it.
56
+ */
57
+ declare function escapeCmd(str: string): string;
58
+ /**
59
+ * Produce the contents of the launcher `.cmd` file that the Scheduled
60
+ * Task (or the Startup folder shim) will invoke on each logon.
61
+ *
62
+ * The shim is intentionally dumb: set NEURA_HOME so the core knows
63
+ * where to read `config.json` from, redirect output to log files, then
64
+ * exec Node on the core bundle.
65
+ *
66
+ * IMPORTANT: the shim sets ONLY `NEURA_HOME`. It does NOT bake in the
67
+ * port, auth token, or API keys from the install-time config — even
68
+ * though we have those values handy. Reason: the core's config loader
69
+ * in `packages/core/src/config/config.ts` prefers `process.env.*` over
70
+ * `file.*`, so any value baked into the shim would win over the user's
71
+ * live `config.json`. That meant `neura config set port 20000` (or
72
+ * `config set apiKeys.xai ...`) would silently do nothing on Windows
73
+ * until the user re-ran `neura install`, because the old values in the
74
+ * shim's `set` statements still shadowed the new ones in `config.json`.
75
+ * By shipping only `NEURA_HOME`, all runtime config flows through
76
+ * `config.json` → the core reads the latest values on every restart,
77
+ * matching the macOS and Linux behavior.
78
+ *
79
+ * The shim also does NOT track PIDs — the core writes its own
80
+ * `$NEURA_HOME/neura-core.pid` on startup (see
81
+ * `packages/core/src/server/lifecycle.ts`), which `isRunning()` and
82
+ * `stop()` key off of.
83
+ */
84
+ declare function renderCmdShim(ctx: ShimContext): string;
1
85
  export declare function isInstalled(): boolean;
86
+ /**
87
+ * Check if the core process is currently alive.
88
+ *
89
+ * Reads `$NEURA_HOME/neura-core.pid` (written by the core itself in
90
+ * `packages/core/src/server/lifecycle.ts`) and asks `tasklist` whether
91
+ * a process with that PID is running. Any living process is treated as
92
+ * "the core" — we don't fingerprint by name, which matches the contract
93
+ * of `stop()` below.
94
+ *
95
+ * Self-heals stale pid files: if the file exists but refers to a dead
96
+ * or unreadable PID, we delete it as a side-effect so the next
97
+ * `isRunning()` / `start()` call isn't fooled by Windows PID reuse
98
+ * (which is real and can happen within seconds on busy machines).
99
+ */
2
100
  export declare function isRunning(): boolean;
101
+ /**
102
+ * Which of the two dual-path install modes was used.
103
+ *
104
+ * `scheduled-task` is the preferred path — registered via schtasks,
105
+ * managed from Task Scheduler. `startup-shim` is the fallback used when
106
+ * schtasks refuses (access denied, GPO lockdown, etc.). Exposed so the
107
+ * caller can tell the user which path was taken and what it means.
108
+ */
109
+ export type InstallMode = 'scheduled-task' | 'startup-shim';
110
+ /** The install mode used by the most recent install() call, or null. */
111
+ export declare function getLastInstallMode(): InstallMode | null;
3
112
  export declare function install(): void;
4
113
  export declare function uninstall(): void;
5
114
  export declare function start(): void;
@@ -17,4 +126,11 @@ declare const _default: {
17
126
  readonly getLogPath: typeof getLogPath;
18
127
  };
19
128
  export default _default;
129
+ export declare const __test__: {
130
+ TASK_NAME: string;
131
+ getShimContext: typeof getShimContext;
132
+ getStartupShimPath: typeof getStartupShimPath;
133
+ renderCmdShim: typeof renderCmdShim;
134
+ escapeCmd: typeof escapeCmd;
135
+ };
20
136
  //# sourceMappingURL=windows.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"windows.d.ts","sourceRoot":"","sources":["../../src/service/windows.ts"],"names":[],"mappings":"AAgBA,wBAAgB,WAAW,IAAI,OAAO,CAUrC;AAED,wBAAgB,SAAS,IAAI,OAAO,CAUnC;AAED,wBAAgB,OAAO,IAAI,IAAI,CAe9B;AAED,wBAAgB,SAAS,IAAI,IAAI,CAQhC;AAED,wBAAgB,KAAK,IAAI,IAAI,CAG5B;AAED,wBAAgB,IAAI,IAAI,IAAI,CAG3B;AAkBD,wBAAgB,OAAO,IAAI,IAAI,CAK9B;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;;;;;;;;;;;AAED,wBASW"}
1
+ {"version":3,"file":"windows.d.ts","sourceRoot":"","sources":["../../src/service/windows.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,iBAAS,cAAc,IAAI,WAAW,CAarC;AAED,iBAAS,kBAAkB,IAAI,MAAM,CAYpC;AAED;;;;;;;;GAQG;AACH,iBAAS,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,iBAAS,aAAa,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAc/C;AA2GD,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAgBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,IAAI,OAAO,CA0BnC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,gBAAgB,GAAG,cAAc,CAAC;AAI5D,wEAAwE;AACxE,wBAAgB,kBAAkB,IAAI,WAAW,GAAG,IAAI,CAEvD;AAED,wBAAgB,OAAO,IAAI,IAAI,CAuC9B;AAED,wBAAgB,SAAS,IAAI,IAAI,CAOhC;AAED,wBAAgB,KAAK,IAAI,IAAI,CAG5B;AAED,wBAAgB,IAAI,IAAI,IAAI,CA6B3B;AAED,wBAAgB,OAAO,IAAI,IAAI,CAG9B;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;;;;;;;;;;;AAED,wBASW;AAGX,eAAO,MAAM,QAAQ;;;;;;CAMpB,CAAC"}
@@ -1,90 +1,325 @@
1
- import { join } from 'path';
2
- import { execSync } from 'child_process';
1
+ import { writeFileSync, existsSync, unlinkSync, mkdirSync, readFileSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { homedir } from 'os';
4
+ import { execSync, spawnSync, spawn } from 'child_process';
3
5
  import { getNeuraHome } from '../config.js';
4
- import { isElevated } from './detect.js';
5
- const SERVICE_NAME = 'neura-core';
6
- function requireElevation() {
7
- if (!isElevated()) {
8
- throw new Error('Administrator privileges required.\n' +
9
- 'Right-click your terminal and select "Run as administrator", then retry.');
10
- }
6
+ import { getCoreBinaryPath } from '../download.js';
7
+ const TASK_NAME = 'neura-core';
8
+ function getShimContext() {
9
+ const home = getNeuraHome();
10
+ const logDir = join(home, 'logs');
11
+ return {
12
+ nodePath: process.execPath,
13
+ corePath: getCoreBinaryPath(),
14
+ home,
15
+ logDir,
16
+ logFile: join(logDir, 'core.log'),
17
+ errLogFile: join(logDir, 'core.error.log'),
18
+ pidFile: join(home, 'neura-core.pid'),
19
+ cmdPath: join(home, 'neura-core.cmd'),
20
+ };
21
+ }
22
+ function getStartupShimPath() {
23
+ // %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\neura-core.cmd
24
+ const appData = process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming');
25
+ return join(appData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', `${TASK_NAME}.cmd`);
26
+ }
27
+ /**
28
+ * Escape a string for safe embedding in a Windows `cmd.exe` batch file.
29
+ *
30
+ * The rules here are painful: `cmd.exe` treats `%`, `^`, `&`, `|`, `<`, `>`
31
+ * as special outside quoted strings, and `%` is still special inside quotes
32
+ * when it looks like an environment variable reference. We use `^` to
33
+ * escape the metacharacters and double `%%` so environment-variable
34
+ * expansion only happens where we actually want it.
35
+ */
36
+ function escapeCmd(str) {
37
+ return str
38
+ .replace(/\^/g, '^^')
39
+ .replace(/%/g, '%%')
40
+ .replace(/([&|<>])/g, '^$1');
41
+ }
42
+ /**
43
+ * Produce the contents of the launcher `.cmd` file that the Scheduled
44
+ * Task (or the Startup folder shim) will invoke on each logon.
45
+ *
46
+ * The shim is intentionally dumb: set NEURA_HOME so the core knows
47
+ * where to read `config.json` from, redirect output to log files, then
48
+ * exec Node on the core bundle.
49
+ *
50
+ * IMPORTANT: the shim sets ONLY `NEURA_HOME`. It does NOT bake in the
51
+ * port, auth token, or API keys from the install-time config — even
52
+ * though we have those values handy. Reason: the core's config loader
53
+ * in `packages/core/src/config/config.ts` prefers `process.env.*` over
54
+ * `file.*`, so any value baked into the shim would win over the user's
55
+ * live `config.json`. That meant `neura config set port 20000` (or
56
+ * `config set apiKeys.xai ...`) would silently do nothing on Windows
57
+ * until the user re-ran `neura install`, because the old values in the
58
+ * shim's `set` statements still shadowed the new ones in `config.json`.
59
+ * By shipping only `NEURA_HOME`, all runtime config flows through
60
+ * `config.json` → the core reads the latest values on every restart,
61
+ * matching the macOS and Linux behavior.
62
+ *
63
+ * The shim also does NOT track PIDs — the core writes its own
64
+ * `$NEURA_HOME/neura-core.pid` on startup (see
65
+ * `packages/core/src/server/lifecycle.ts`), which `isRunning()` and
66
+ * `stop()` key off of.
67
+ */
68
+ function renderCmdShim(ctx) {
69
+ // Notes:
70
+ // - `@echo off` suppresses command echoing.
71
+ // - `chcp 65001 >nul` sets the console to UTF-8 so pino's log output
72
+ // doesn't mangle when it contains non-ASCII characters.
73
+ // - stdout/stderr are redirected with `>> "<log>" 2>> "<err>"` so
74
+ // we don't need a separate supervisor for log capture.
75
+ return `@echo off\r
76
+ chcp 65001 >nul\r
77
+ set "NEURA_HOME=${escapeCmd(ctx.home)}"\r
78
+ if not exist "${escapeCmd(ctx.logDir)}" mkdir "${escapeCmd(ctx.logDir)}"\r
79
+ echo %~nx0 started at %date% %time% >> "${escapeCmd(ctx.logFile)}"\r
80
+ "${escapeCmd(ctx.nodePath)}" "${escapeCmd(ctx.corePath)}" >> "${escapeCmd(ctx.logFile)}" 2>> "${escapeCmd(ctx.errLogFile)}"\r
81
+ `;
82
+ }
83
+ /**
84
+ * Write the shim .cmd that both the Scheduled Task and the Startup
85
+ * folder fallback invoke. Idempotent — overwrites whatever's there.
86
+ */
87
+ function writeCmdShim(ctx) {
88
+ mkdirSync(dirname(ctx.cmdPath), { recursive: true });
89
+ mkdirSync(ctx.logDir, { recursive: true });
90
+ writeFileSync(ctx.cmdPath, renderCmdShim(ctx), 'utf-8');
91
+ }
92
+ /**
93
+ * Register the Scheduled Task via `schtasks /Create`.
94
+ *
95
+ * Returns true on success, false on failure. Never throws — we want the
96
+ * caller to cleanly fall back to the Startup-folder path without trying
97
+ * to parse schtasks's error output.
98
+ */
99
+ function tryCreateScheduledTask(ctx) {
100
+ // /F = force overwrite of existing task
101
+ // /SC ONLOGON = trigger on user logon (no admin needed)
102
+ // /RL LIMITED = limited run level — NOT elevated, no UAC prompt
103
+ // /TN = task name
104
+ // /TR = the command to run; must be fully quoted when it contains
105
+ // a path with spaces. schtasks does its own quoting layer which
106
+ // means we end up needing \" inside the outer quotes. spawnSync
107
+ // handles the outer argv layer, so we just pass the path with
108
+ // embedded quotes.
109
+ const result = spawnSync('schtasks.exe', [
110
+ '/Create',
111
+ '/F',
112
+ '/SC',
113
+ 'ONLOGON',
114
+ '/RL',
115
+ 'LIMITED',
116
+ '/TN',
117
+ TASK_NAME,
118
+ '/TR',
119
+ `"${ctx.cmdPath}"`,
120
+ ], { stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });
121
+ return result.status === 0;
122
+ }
123
+ /**
124
+ * Check whether the Scheduled Task exists.
125
+ *
126
+ * `schtasks /Query /TN <name>` returns exit 0 if the task exists, 1 if
127
+ * it doesn't (and also writes a message to stderr that we don't need).
128
+ */
129
+ function isTaskRegistered() {
130
+ const result = spawnSync('schtasks.exe', ['/Query', '/TN', TASK_NAME], {
131
+ stdio: ['ignore', 'ignore', 'ignore'],
132
+ windowsHide: true,
133
+ });
134
+ return result.status === 0;
135
+ }
136
+ function deleteScheduledTask() {
137
+ spawnSync('schtasks.exe', ['/Delete', '/F', '/TN', TASK_NAME], {
138
+ stdio: ['ignore', 'ignore', 'ignore'],
139
+ windowsHide: true,
140
+ });
141
+ }
142
+ function isStartupShimInstalled() {
143
+ return existsSync(getStartupShimPath());
11
144
  }
145
+ function installStartupShim(ctx) {
146
+ const shimPath = getStartupShimPath();
147
+ mkdirSync(dirname(shimPath), { recursive: true });
148
+ // The startup-folder file is a tiny launcher that calls the real shim
149
+ // in NEURA_HOME. We keep the real shim in NEURA_HOME so `neura config`
150
+ // can regenerate it without touching Windows-managed directories.
151
+ const launcher = `@echo off\r\nstart "" /min cmd.exe /d /c "${escapeCmd(ctx.cmdPath)}"\r\n`;
152
+ writeFileSync(shimPath, launcher, 'utf-8');
153
+ }
154
+ function removeStartupShim() {
155
+ const shimPath = getStartupShimPath();
156
+ if (existsSync(shimPath))
157
+ unlinkSync(shimPath);
158
+ }
159
+ /**
160
+ * Spawn the launcher `.cmd` as a detached child so the core starts
161
+ * immediately on first install (instead of waiting for the next logon).
162
+ *
163
+ * We use `spawn` + `{ detached: true, stdio: 'ignore' }` + `.unref()` so
164
+ * the parent `neura install` process can exit cleanly while core keeps
165
+ * running. `windowsHide: true` suppresses the flash of a cmd console.
166
+ */
167
+ function spawnDetachedCore(ctx) {
168
+ const child = spawn('cmd.exe', ['/d', '/c', ctx.cmdPath], {
169
+ detached: true,
170
+ stdio: 'ignore',
171
+ windowsHide: true,
172
+ });
173
+ child.unref();
174
+ }
175
+ // ── ServiceManager interface ─────────────────────────────────────────
12
176
  export function isInstalled() {
177
+ return isTaskRegistered() || isStartupShimInstalled();
178
+ }
179
+ /**
180
+ * Unlink the pid file, swallowing "already gone" errors. Used by
181
+ * `isRunning()` to self-heal stale pid files left behind when the core
182
+ * dies without running its cleanup handlers (crash, `taskkill /F`, OS
183
+ * reboot).
184
+ */
185
+ function unlinkPidFile(pidFile) {
13
186
  try {
14
- const result = execSync(`sc query ${SERVICE_NAME}`, {
15
- encoding: 'utf-8',
16
- stdio: ['pipe', 'pipe', 'pipe'],
17
- });
18
- return !result.includes('does not exist');
187
+ unlinkSync(pidFile);
19
188
  }
20
189
  catch {
21
- return false;
190
+ // Already removed; fine.
22
191
  }
23
192
  }
193
+ /**
194
+ * Check if the core process is currently alive.
195
+ *
196
+ * Reads `$NEURA_HOME/neura-core.pid` (written by the core itself in
197
+ * `packages/core/src/server/lifecycle.ts`) and asks `tasklist` whether
198
+ * a process with that PID is running. Any living process is treated as
199
+ * "the core" — we don't fingerprint by name, which matches the contract
200
+ * of `stop()` below.
201
+ *
202
+ * Self-heals stale pid files: if the file exists but refers to a dead
203
+ * or unreadable PID, we delete it as a side-effect so the next
204
+ * `isRunning()` / `start()` call isn't fooled by Windows PID reuse
205
+ * (which is real and can happen within seconds on busy machines).
206
+ */
24
207
  export function isRunning() {
208
+ const ctx = getShimContext();
209
+ if (!existsSync(ctx.pidFile))
210
+ return false;
211
+ let pid;
25
212
  try {
26
- const result = execSync(`sc query ${SERVICE_NAME}`, {
27
- encoding: 'utf-8',
28
- stdio: ['pipe', 'pipe', 'pipe'],
29
- });
30
- return result.includes('RUNNING');
213
+ pid = parseInt(readFileSync(ctx.pidFile, 'utf-8').trim(), 10);
31
214
  }
32
215
  catch {
216
+ unlinkPidFile(ctx.pidFile);
217
+ return false;
218
+ }
219
+ if (!pid || Number.isNaN(pid)) {
220
+ unlinkPidFile(ctx.pidFile);
33
221
  return false;
34
222
  }
223
+ const result = spawnSync('tasklist.exe', ['/FI', `PID eq ${pid}`, '/NH'], {
224
+ encoding: 'utf-8',
225
+ windowsHide: true,
226
+ });
227
+ // tasklist prints "INFO: No tasks are running..." when PID is missing.
228
+ const alive = typeof result.stdout === 'string' && !result.stdout.includes('No tasks');
229
+ if (!alive) {
230
+ unlinkPidFile(ctx.pidFile);
231
+ }
232
+ return alive;
233
+ }
234
+ let lastInstallMode = null;
235
+ /** The install mode used by the most recent install() call, or null. */
236
+ export function getLastInstallMode() {
237
+ return lastInstallMode;
35
238
  }
36
239
  export function install() {
37
- requireElevation();
38
- // Windows Services must implement the SCM protocol (ServiceMain callback).
39
- // A plain Node.js/Bun binary cannot respond to SCM handshakes and will fail
40
- // with error 1053. A service wrapper like WinSW or NSSM is required to bridge
41
- // the gap. This will be implemented in Phase 2 of the CLI service architecture.
240
+ const ctx = getShimContext();
241
+ // Always regenerate the launcher shim so it picks up any new paths
242
+ // or config changes since the last install. Idempotent: overwriting
243
+ // the .cmd file is safe whether or not the task is already registered.
244
+ writeCmdShim(ctx);
245
+ // Prefer the Scheduled Task path — it survives reboots cleanly and
246
+ // shows up in Task Scheduler so the user can manage it from the GUI.
247
+ // If it fails (GPO, corp lockdown, schtasks.exe missing from PATH,
248
+ // or a Windows config that requires elevation for user-level tasks),
249
+ // fall back to a Startup folder shim. Both paths register the same
250
+ // shim; they just differ in how Windows invokes it at logon.
42
251
  //
43
- // See: docs/cli-service-architecture.md Phase 2: OS Service Registration
44
- throw new Error('Windows Service registration requires a service wrapper (WinSW).\n' +
45
- 'This is planned for Phase 2. For now, run core manually:\n\n' +
46
- ' neura-core (if binary installed)\n' +
47
- ' npm run dev -w @neura/core (from source)\n');
48
- }
49
- export function uninstall() {
50
- requireElevation();
51
- try {
52
- execSync(`sc stop ${SERVICE_NAME}`, { stdio: 'ignore' });
252
+ // Both branches clean up the OTHER install mode so we never end up
253
+ // with a task AND a startup shim both firing at next logon. Without
254
+ // this symmetric cleanup, a reinstall that changes which path wins
255
+ // (e.g. a machine that previously accepted schtasks /Create but later
256
+ // tightened GPO) would leave both modes active — the next logon would
257
+ // start two copies of core, and because server.ts retries EADDRINUSE
258
+ // on port+1 the second copy comes up on a different port instead of
259
+ // failing loudly.
260
+ const taskCreated = tryCreateScheduledTask(ctx);
261
+ if (taskCreated) {
262
+ lastInstallMode = 'scheduled-task';
263
+ removeStartupShim();
53
264
  }
54
- catch {
55
- // May already be stopped
265
+ else {
266
+ lastInstallMode = 'startup-shim';
267
+ deleteScheduledTask();
268
+ installStartupShim(ctx);
56
269
  }
57
- execSync(`sc delete ${SERVICE_NAME}`, { stdio: 'inherit' });
270
+ // NOTE: we do NOT spawn the core here. The caller (`installCommand`)
271
+ // always calls `svc.start()` right after `svc.install()`, and start()
272
+ // already handles the "spawn detached so the user gets a working core
273
+ // without logging out" behavior. Starting it here too would race the
274
+ // pid-file write with start()'s own isRunning() check and leave the
275
+ // user with two concurrent cores fighting for the same port.
276
+ }
277
+ export function uninstall() {
278
+ stop();
279
+ deleteScheduledTask();
280
+ removeStartupShim();
281
+ // Leave NEURA_HOME/neura-core.cmd in place — it's harmless and is
282
+ // re-written on next `neura install`. Don't delete NEURA_HOME itself;
283
+ // that's the user's config + memory store.
58
284
  }
59
285
  export function start() {
60
- requireElevation();
61
- execSync(`sc start ${SERVICE_NAME}`, { stdio: 'inherit' });
286
+ if (isRunning())
287
+ return;
288
+ spawnDetachedCore(getShimContext());
62
289
  }
63
290
  export function stop() {
64
- requireElevation();
65
- execSync(`sc stop ${SERVICE_NAME}`, { stdio: 'inherit' });
66
- }
67
- function waitForStopped(timeoutMs = 10_000) {
68
- const deadline = Date.now() + timeoutMs;
69
- while (Date.now() < deadline) {
70
- try {
71
- const result = execSync(`sc query ${SERVICE_NAME}`, {
72
- encoding: 'utf-8',
73
- stdio: ['pipe', 'pipe', 'pipe'],
74
- });
75
- if (result.includes('STOPPED'))
76
- return;
77
- }
78
- catch {
79
- return; // Service doesn't exist or query failed — treat as stopped
80
- }
81
- execSync('timeout /t 1 /nobreak >nul 2>&1', { stdio: 'ignore' });
291
+ const ctx = getShimContext();
292
+ if (!existsSync(ctx.pidFile))
293
+ return;
294
+ let pid;
295
+ try {
296
+ pid = parseInt(readFileSync(ctx.pidFile, 'utf-8').trim(), 10);
82
297
  }
298
+ catch {
299
+ // Corrupt pid file — nothing to kill, just clean the stale file.
300
+ unlinkPidFile(ctx.pidFile);
301
+ return;
302
+ }
303
+ if (!pid || Number.isNaN(pid)) {
304
+ unlinkPidFile(ctx.pidFile);
305
+ return;
306
+ }
307
+ // `taskkill /T` walks the child-process tree so we catch both the
308
+ // launcher cmd.exe and the Node process it spawned. `/F` is force —
309
+ // we don't wait for graceful shutdown; the core's SIGTERM handlers
310
+ // wouldn't fire anyway because Node on Windows can't receive
311
+ // SIGTERM. The core's on('exit') pid-file cleanup also won't run
312
+ // under /F, which is why we unlink the pid file ourselves below.
313
+ try {
314
+ execSync(`taskkill /PID ${pid} /T /F`, { stdio: 'ignore' });
315
+ }
316
+ catch {
317
+ // Process already dead; fall through.
318
+ }
319
+ unlinkPidFile(ctx.pidFile);
83
320
  }
84
321
  export function restart() {
85
- requireElevation();
86
322
  stop();
87
- waitForStopped();
88
323
  start();
89
324
  }
90
325
  export function getLogPath() {
@@ -100,4 +335,12 @@ export default {
100
335
  restart,
101
336
  getLogPath,
102
337
  };
338
+ // Exported for tests only.
339
+ export const __test__ = {
340
+ TASK_NAME,
341
+ getShimContext,
342
+ getStartupShimPath,
343
+ renderCmdShim,
344
+ escapeCmd,
345
+ };
103
346
  //# sourceMappingURL=windows.js.map