@phenx-inc/ctlsurf 0.17.0 → 0.17.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phenx-inc/ctlsurf",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "description": "Agent-agnostic terminal and desktop app for ctlsurf — run Claude Code, Codex, or any coding agent with live session logging and remote control",
5
5
  "main": "out/main/index.js",
6
6
  "bin": {
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Fallback installer for the Electron binary inside node_modules.
5
+ *
6
+ * electron <40 unzips its binary in postinstall with extract-zip@2/yauzl@2,
7
+ * which on Node >= 26 silently stops partway through the archive: the
8
+ * process exits 0, dist/ is left half-extracted and path.txt is never
9
+ * written, so require('electron') throws "Electron failed to install
10
+ * correctly". npm then considers the package installed and won't re-run
11
+ * the postinstall.
12
+ *
13
+ * This fetches the same zip through @electron/get (normally a cache hit —
14
+ * the broken postinstall already downloaded it), extracts it with the OS's
15
+ * own unzip tool, and writes path.txt the same way electron/install.js does.
16
+ *
17
+ * Run as a child process by bin/ctlsurf-worker.js; exits non-zero on failure.
18
+ */
19
+
20
+ const fs = require('fs')
21
+ const path = require('path')
22
+ const { execFileSync } = require('child_process')
23
+
24
+ const ROOT = path.resolve(__dirname, '..')
25
+ const ELECTRON_DIR = path.join(ROOT, 'node_modules', 'electron')
26
+ const SENTINEL = path.join(ELECTRON_DIR, '.ctlsurf-rebranded')
27
+
28
+ function getPlatformPath() {
29
+ switch (process.platform) {
30
+ case 'darwin': return 'Electron.app/Contents/MacOS/Electron'
31
+ case 'linux': return 'electron'
32
+ case 'win32': return 'electron.exe'
33
+ default: throw new Error(`Electron builds are not available on platform: ${process.platform}`)
34
+ }
35
+ }
36
+
37
+ function getArch() {
38
+ // Mirror electron/install.js: x64 node under Rosetta still wants arm64.
39
+ if (process.platform === 'darwin' && process.arch === 'x64') {
40
+ try {
41
+ if (execFileSync('sysctl', ['-in', 'sysctl.proc_translated']).toString().trim() === '1') return 'arm64'
42
+ } catch { /* ignore */ }
43
+ }
44
+ return process.arch
45
+ }
46
+
47
+ function unzip(zipPath, distDir) {
48
+ if (process.platform === 'darwin') {
49
+ // ditto keeps the framework symlinks inside Electron.app intact
50
+ execFileSync('ditto', ['-x', '-k', zipPath, distDir], { stdio: 'inherit' })
51
+ } else if (process.platform === 'win32') {
52
+ // bsdtar ships with Windows 10+ and reads zip archives
53
+ execFileSync('tar', ['-xf', zipPath, '-C', distDir], { stdio: 'inherit' })
54
+ } else {
55
+ execFileSync('unzip', ['-q', '-o', zipPath, '-d', distDir], { stdio: 'inherit' })
56
+ }
57
+ }
58
+
59
+ async function extractElectron() {
60
+ const platformPath = getPlatformPath()
61
+ const { version } = require(path.join(ELECTRON_DIR, 'package.json'))
62
+ const { downloadArtifact } = require(require.resolve('@electron/get', { paths: [ELECTRON_DIR] }))
63
+
64
+ const zipPath = await downloadArtifact({
65
+ version,
66
+ artifactName: 'electron',
67
+ cacheRoot: process.env.electron_config_cache,
68
+ checksums: require(path.join(ELECTRON_DIR, 'checksums.json')),
69
+ platform: process.platform,
70
+ arch: getArch(),
71
+ })
72
+
73
+ const distDir = path.join(ELECTRON_DIR, 'dist')
74
+ fs.rmSync(distDir, { recursive: true, force: true })
75
+ fs.rmSync(path.join(ELECTRON_DIR, 'path.txt'), { force: true })
76
+ // Fresh binary — let rebrand-electron.js run again
77
+ fs.rmSync(SENTINEL, { force: true })
78
+ fs.mkdirSync(distDir, { recursive: true })
79
+ unzip(zipPath, distDir)
80
+
81
+ // Same housekeeping as electron/install.js
82
+ const srcTypeDefs = path.join(distDir, 'electron.d.ts')
83
+ if (fs.existsSync(srcTypeDefs)) {
84
+ fs.renameSync(srcTypeDefs, path.join(ELECTRON_DIR, 'electron.d.ts'))
85
+ }
86
+
87
+ if (!fs.existsSync(path.join(distDir, platformPath))) {
88
+ throw new Error(`binary missing after extract: dist/${platformPath}`)
89
+ }
90
+ fs.writeFileSync(path.join(ELECTRON_DIR, 'path.txt'), platformPath)
91
+ }
92
+
93
+ // ─── Entry ──────────────────────────────────────────
94
+
95
+ if (require.main === module) {
96
+ extractElectron().then(
97
+ () => process.exit(0),
98
+ (err) => {
99
+ console.error(`[ctlsurf] Electron extract failed: ${err.message}`)
100
+ process.exit(1)
101
+ }
102
+ )
103
+ }
104
+
105
+ module.exports = { extractElectron }