centralu 0.1.0-beta.23 → 0.1.0-beta.24

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/bin/centralu.mjs CHANGED
@@ -56,6 +56,7 @@ import {
56
56
  SERVE_HELP,
57
57
  } from './serve.mjs'
58
58
  import { swapInto } from './swap.mjs'
59
+ import { belowFloor, floorOf, oldNodeSentence } from './node-floor.mjs'
59
60
  import { chmodSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs'
60
61
  import { createRequire } from 'node:module'
61
62
  import { homedir } from 'node:os'
@@ -65,6 +66,8 @@ const require = createRequire(import.meta.url)
65
66
  const pkg = require('../package.json')
66
67
 
67
68
  const APP_NAME = 'Centralu'
69
+ /** The oldest Node the host runs on: this package's own `engines.node` (node-floor.mjs) */
70
+ const NODE_FLOOR = floorOf(pkg.engines.node)
68
71
  const BUNDLE = `${APP_NAME}.app`
69
72
  /*
70
73
  * `CENTRALU_INSTALLED_COPY` points `install` and the copy checks at another bundle path. It exists
@@ -591,7 +594,7 @@ const HELP = `${APP_NAME} ${pkg.version}
591
594
  to reach over SSH (\`centralu serve --help\`)
592
595
  centralu --version Prints the version
593
596
 
594
- Requires: Node 22+, and the claude or codex CLI (the app's first screen reports the status)`
597
+ Requires: Node ${NODE_FLOOR}+, and the claude or codex CLI (the app's first screen reports the status)`
595
598
 
596
599
  const [cmd, ...rest] = process.argv.slice(2)
597
600
  switch (cmd) {
@@ -621,6 +624,26 @@ switch (cmd) {
621
624
  process.exitCode = rotateToken({ env: process.env, home: HOME })
622
625
  break
623
626
  }
627
+ /*
628
+ * Before anything spawns a host: under a Node below the floor the host crashes on its first
629
+ * database call, with nothing in its log (issue #699). `--detach` also answers in the line the
630
+ * hub reads, so it can offer its managed install instead of quoting a crash. `--autostart on`
631
+ * writes a boot entry that would start that host. `--connection`, `--stop`, `--autostart
632
+ * off|status` and `--rotate-token` start no host and keep working, so the hub can still ask this
633
+ * machine what Node it has.
634
+ */
635
+ const startsHost = args.mode === 'serve' || args.mode === 'detach' || (args.mode === 'autostart' && args.autostart === 'on')
636
+ if (startsHost && belowFloor(process.versions.node, NODE_FLOOR)) {
637
+ const message = oldNodeSentence(process.versions.node, NODE_FLOOR)
638
+ console.error(`centralu serve: ${message}`)
639
+ if (args.mode === 'detach') {
640
+ const line = { v: 1, detach: { ok: false, how: null, reason: 'node_too_old', message, node: process.versions.node, floor: NODE_FLOOR } }
641
+ process.stdout.write(`${JSON.stringify(line)}\n`)
642
+ }
643
+ // Not process.exit: the line above is on a pipe, and exiting at once may cut it
644
+ process.exitCode = 1
645
+ break
646
+ }
624
647
  // A launcher `--detach` started: what it carried (the data folder, a host entry) applies before
625
648
  // anything below reads the environment
626
649
  const spec = args.child !== undefined ? decodeChildSpec(args.child) : null
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The oldest Node the bundled host runs on, and what the launcher says when it is run on an older one.
3
+ *
4
+ * **The floor is `engines.node` of this package, read here and nowhere restated.** The host's native
5
+ * database module (better-sqlite3 13) declares Node-API 10, which Node has from 22.14.0. Under an
6
+ * older 22.x (and under 20) it loads, then crashes on its first use with an access violation
7
+ * (`0xC0000005`, exit 3221225477, on Windows) or SIGSEGV (measured on 22.12.0, Windows and macOS,
8
+ * issue #699), before the host logs a line. The hub reads the same value from its copy of this
9
+ * package's `package.json` (`packages/agent-host/src/links/node-floor.ts`), and
10
+ * `tooling/engines.test.ts` keeps the other declarations equal to it.
11
+ *
12
+ * Lives in its own file so it can be tested (tooling/launcher-node-floor.test.ts): the launcher
13
+ * itself runs commands at import time. Plain JavaScript on Node's own modules and `semver.mjs`.
14
+ */
15
+ import { isNewer } from './semver.mjs'
16
+
17
+ /** `>=22.14.0` or `>=22.14` → `22.14.0`; throws on any other shape, which `engines` must not take */
18
+ export function floorOf(range) {
19
+ const m = /^>=\s*(\d+)\.(\d+)(?:\.(\d+))?$/.exec(String(range).trim())
20
+ if (!m) throw new Error(`engines.node must be a plain floor like ">=22.14.0", not ${JSON.stringify(range)}`)
21
+ return `${m[1]}.${m[2]}.${m[3] ?? '0'}`
22
+ }
23
+
24
+ /** Whether `found` (`22.12.0` or `v22.12.0`) is older than `floor`; a version that does not parse is let through */
25
+ export function belowFloor(found, floor) {
26
+ const v = String(found).trim().replace(/^v/i, '')
27
+ if (!/^\d+\.\d+\.\d+/.test(v)) return false
28
+ return isNewer(floor, v)
29
+ }
30
+
31
+ /**
32
+ * The one sentence for a launcher that will not start the host on this Node: the Node it found, the
33
+ * floor, and the two ways out. It names no command, since it is also what the hub reads out of the
34
+ * `--detach` answer.
35
+ */
36
+ export function oldNodeSentence(found, floor) {
37
+ const v = String(found).replace(/^v/i, '')
38
+ return (
39
+ `centralu serve needs Node ${floor} or newer and this is Node ${v}: update Node on this machine, ` +
40
+ 'or link this machine from a Centralu window on another computer, which installs a Node of its own there.'
41
+ )
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "centralu",
3
- "version": "0.1.0-beta.23",
3
+ "version": "0.1.0-beta.24",
4
4
  "description": "Run, watch and steer several Claude Code and Codex CLI sessions from one window",
5
5
  "license": "MIT",
6
6
  "author": "ijun17",
@@ -21,6 +21,7 @@
21
21
  "windows",
22
22
  "desktop"
23
23
  ],
24
+ "protocolVersion": 1,
24
25
  "type": "module",
25
26
  "bin": {
26
27
  "centralu": "bin/centralu.mjs"
@@ -29,7 +30,7 @@
29
30
  "bin"
30
31
  ],
31
32
  "engines": {
32
- "node": ">=22"
33
+ "node": ">=22.14.0"
33
34
  },
34
35
  "os": [
35
36
  "darwin",
@@ -37,9 +38,9 @@
37
38
  "win32"
38
39
  ],
39
40
  "optionalDependencies": {
40
- "@centralu/darwin-arm64": "0.1.0-beta.23",
41
- "@centralu/linux-arm64": "0.1.0-beta.23",
42
- "@centralu/linux-x64": "0.1.0-beta.23",
43
- "@centralu/win32-x64": "0.1.0-beta.23"
41
+ "@centralu/darwin-arm64": "0.1.0-beta.24",
42
+ "@centralu/linux-arm64": "0.1.0-beta.24",
43
+ "@centralu/linux-x64": "0.1.0-beta.24",
44
+ "@centralu/win32-x64": "0.1.0-beta.24"
44
45
  }
45
46
  }