@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.
@@ -190,11 +190,14 @@ if (subcommand === 'doctor') {
190
190
  console.log(` ${D}Run: ctlsurf setup${R}`)
191
191
  }
192
192
 
193
- // Desktop
194
- try {
195
- require.resolve('electron')
193
+ // Desktop — check the binary actually resolves, not just that the package
194
+ // exists (a half-extracted electron still passes require.resolve)
195
+ if (electronResolves()) {
196
196
  console.log(` ${G}✓${R} Electron ${D}(desktop mode available)${R}`)
197
- } catch {
197
+ } else if (fs.existsSync(path.join(ROOT, 'node_modules', 'electron'))) {
198
+ console.log(` ${Y}✗${R} Electron ${D}(installed but binary incomplete — desktop mode unavailable)${R}`)
199
+ console.log(` ${D}Repair: cd ${ROOT} && node scripts/extract-electron.js${R}`)
200
+ } else {
198
201
  console.log(` ${D}-${R} Electron ${D}(not installed — desktop mode unavailable)${R}`)
199
202
  console.log(` ${D}Install: cd ${ROOT} && npm install${R}`)
200
203
  }
@@ -238,10 +241,34 @@ const mode = detectMode(args)
238
241
  checkApiKey(mode).catch(() => {}).then(() => launch(mode))
239
242
 
240
243
  function launch(launchMode) {
244
+ ensurePtyHelperExecutable()
241
245
  if (launchMode === 'desktop') runDesktop()
242
246
  else runTerminal()
243
247
  }
244
248
 
249
+ // node-pty runs spawn-helper from whichever native dir it loaded (build/Release,
250
+ // else prebuilds/<platform>-<arch>). The prebuilt copy ships without the execute
251
+ // bit, so when our postinstall didn't compile build/Release (install scripts
252
+ // blocked, or no Xcode CLT) every spawn fails with "posix_spawnp failed".
253
+ function ensurePtyHelperExecutable() {
254
+ const ptyDir = path.join(ROOT, 'node_modules', 'node-pty')
255
+ const dirs = [path.join(ptyDir, 'build', 'Release')]
256
+ try {
257
+ for (const d of fs.readdirSync(path.join(ptyDir, 'prebuilds'))) {
258
+ if (d.startsWith(`${process.platform}-`)) dirs.push(path.join(ptyDir, 'prebuilds', d))
259
+ }
260
+ } catch { /* no prebuilds */ }
261
+
262
+ for (const dir of dirs) {
263
+ const helper = path.join(dir, 'spawn-helper')
264
+ try {
265
+ if ((fs.statSync(helper).mode & 0o111) === 0) fs.chmodSync(helper, 0o755)
266
+ } catch (err) {
267
+ if (err.code !== 'ENOENT') console.error(`${D}[ctlsurf] could not make ${helper} executable: ${err.message}${R}`)
268
+ }
269
+ }
270
+ }
271
+
245
272
  function runDesktop() {
246
273
  let electronPath
247
274
  try {
@@ -297,13 +324,43 @@ function installElectron() {
297
324
  cwd: ROOT,
298
325
  stdio: ['ignore', 'ignore', 'inherit']
299
326
  })
300
- console.log(` ${G}✓${R} Electron installed.\n`)
301
- return true
302
327
  } catch (err) {
303
328
  console.error(`\n${Y}!${R} Electron install failed: ${err.message}`)
304
329
  console.error(` Try manually: cd ${ROOT} && npm install electron\n`)
305
330
  return false
306
331
  }
332
+
333
+ // electron's postinstall can exit 0 with a half-extracted binary (its
334
+ // extract-zip stalls on Node >= 26), and npm won't re-run it for a package
335
+ // that's already present — so unzip it ourselves with the system tool.
336
+ if (!electronResolves()) {
337
+ console.log(` ${D}Electron binary incomplete — extracting with system unzip...${R}`)
338
+ try {
339
+ execFileSync(process.execPath, [path.join(ROOT, 'scripts', 'extract-electron.js')], {
340
+ stdio: ['ignore', 'ignore', 'inherit']
341
+ })
342
+ } catch (err) {
343
+ console.error(`\n${Y}!${R} Electron extract failed: ${err.message}`)
344
+ console.error(` Try manually: cd ${ROOT} && node scripts/extract-electron.js\n`)
345
+ return false
346
+ }
347
+ if (!electronResolves()) {
348
+ console.error(`\n${Y}!${R} Electron binary still missing after extract.\n`)
349
+ return false
350
+ }
351
+ }
352
+
353
+ console.log(` ${G}✓${R} Electron installed.\n`)
354
+ return true
355
+ }
356
+
357
+ function electronResolves() {
358
+ try {
359
+ delete require.cache[require.resolve('electron')]
360
+ return fs.existsSync(require('electron'))
361
+ } catch {
362
+ return false
363
+ }
307
364
  }
308
365
 
309
366
  function runTerminal() {
@@ -17314,7 +17314,7 @@ var require_package = __commonJS({
17314
17314
  "package.json"(exports, module) {
17315
17315
  module.exports = {
17316
17316
  name: "@phenx-inc/ctlsurf",
17317
- version: "0.17.0",
17317
+ version: "0.17.2",
17318
17318
  description: "Agent-agnostic terminal and desktop app for ctlsurf \u2014 run Claude Code, Codex, or any coding agent with live session logging and remote control",
17319
17319
  main: "out/main/index.js",
17320
17320
  bin: {