claude-code-session-manager 0.30.0 → 0.32.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/dist/assets/{TiptapBody-Py59G5JF.js → TiptapBody-4qvyGBph.js} +1 -1
- package/dist/assets/{index-BBlKMDCL.js → index-CHTpW79G.js} +4 -4
- package/dist/index.html +1 -1
- package/package.json +3 -2
- package/scripts/postinstall.cjs +52 -0
- package/src/main/lib/cleanEnv.cjs +7 -3
- package/src/main/pluginInstall.cjs +14 -1
- package/src/main/pty.cjs +72 -3
package/dist/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,400&family=Geist:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
10
|
-
<script type="module" crossorigin src="./assets/index-
|
|
10
|
+
<script type="module" crossorigin src="./assets/index-CHTpW79G.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="./assets/monaco-editor-BW5C4Iv1.js">
|
|
12
12
|
<link rel="stylesheet" crossorigin href="./assets/monaco-editor-BTnBOi8r.css">
|
|
13
13
|
<link rel="stylesheet" crossorigin href="./assets/index-DMIi9YZH.css">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-session-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main/index.cjs",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"bin/",
|
|
12
12
|
".claude-plugin/",
|
|
13
13
|
"plugins/",
|
|
14
|
+
"scripts/postinstall.cjs",
|
|
14
15
|
"src/main/",
|
|
15
16
|
"src/preload/",
|
|
16
17
|
"dist/index.html",
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
"build": "vite build",
|
|
27
28
|
"typecheck": "tsc --noEmit",
|
|
28
29
|
"start": "electron .",
|
|
29
|
-
"postinstall": "
|
|
30
|
+
"postinstall": "node scripts/postinstall.cjs",
|
|
30
31
|
"prepublishOnly": "vite build",
|
|
31
32
|
"test:unit": "vitest run",
|
|
32
33
|
"test:verify": "node src/main/__tests__/runVerify.test.cjs",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* postinstall — rebuild node-pty against the bundled Electron's ABI.
|
|
6
|
+
*
|
|
7
|
+
* The terminal needs node-pty's native binary to match the Electron we ship.
|
|
8
|
+
* We run electron-rebuild, but a failure here MUST NOT fail `npm i -g` — that
|
|
9
|
+
* would block the entire app (config tabs, scheduler, etc.) over one native
|
|
10
|
+
* module. So we run it best-effort and, on failure, print the exact manual fix
|
|
11
|
+
* and exit 0. pty.cjs surfaces the same guidance in-app if a tab can't start.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { spawnSync } = require('node:child_process');
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
|
|
18
|
+
// Resolve the electron-rebuild bin from THIS package's own node_modules first,
|
|
19
|
+
// so a global install never has `npx` reach out to the registry (and risk
|
|
20
|
+
// pulling the wrong/deprecated `electron-rebuild` package). Fall back to npx.
|
|
21
|
+
function rebuildArgv() {
|
|
22
|
+
const pkgRoot = path.join(__dirname, '..');
|
|
23
|
+
const local = path.join(pkgRoot, 'node_modules', '.bin', process.platform === 'win32' ? 'electron-rebuild.cmd' : 'electron-rebuild');
|
|
24
|
+
if (fs.existsSync(local)) return [local, ['-f', '-w', 'node-pty']];
|
|
25
|
+
return [process.platform === 'win32' ? 'npx.cmd' : 'npx', ['electron-rebuild', '-f', '-w', 'node-pty']];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function run() {
|
|
29
|
+
const [cmd, args] = rebuildArgv();
|
|
30
|
+
const r = spawnSync(cmd, args, { stdio: 'inherit', cwd: path.join(__dirname, '..') });
|
|
31
|
+
return r.status === 0 && !r.error;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (run()) process.exit(0);
|
|
35
|
+
|
|
36
|
+
const mac = process.platform === 'darwin';
|
|
37
|
+
const bar = '─'.repeat(64);
|
|
38
|
+
console.warn(`\n${bar}`);
|
|
39
|
+
console.warn('claude-code-session-manager: node-pty rebuild did not complete.');
|
|
40
|
+
console.warn('The app will still launch, but terminal tabs may not start until');
|
|
41
|
+
console.warn('the native module is rebuilt for this Electron. To fix it:');
|
|
42
|
+
console.warn('');
|
|
43
|
+
console.warn(' cd "' + __dirname.replace(/\/scripts$/, '') + '"');
|
|
44
|
+
console.warn(' npx electron-rebuild -f -w node-pty');
|
|
45
|
+
if (mac) {
|
|
46
|
+
console.warn('');
|
|
47
|
+
console.warn(' # macOS: if the rebuild fails, install the compiler first:');
|
|
48
|
+
console.warn(' xcode-select --install');
|
|
49
|
+
}
|
|
50
|
+
console.warn(`${bar}\n`);
|
|
51
|
+
// Exit 0 — never fail the install over the terminal native module.
|
|
52
|
+
process.exit(0);
|
|
@@ -25,10 +25,14 @@ function userBinDirs() {
|
|
|
25
25
|
];
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/** Electron's PATH
|
|
29
|
-
* in a spawned child's PATH so `claude`, node, git, etc. resolve
|
|
28
|
+
/** Electron's PATH with the user/Homebrew bin dirs APPENDED as a fallback — the
|
|
29
|
+
* value to put in a spawned child's PATH so `claude`, node, git, etc. resolve
|
|
30
|
+
* on macOS even under a stripped Finder/Dock PATH. Appended (not prepended) so
|
|
31
|
+
* a user's version manager (nvm/asdf/volta/mise) earlier in PATH still wins and
|
|
32
|
+
* isn't shadowed by /opt/homebrew. */
|
|
30
33
|
function pathWithUserBins() {
|
|
31
|
-
|
|
34
|
+
const base = process.env.PATH || '';
|
|
35
|
+
return base ? `${base}:${userBinDirs().join(':')}` : userBinDirs().join(':');
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
function cleanChildEnv(extra = {}) {
|
|
@@ -17,7 +17,16 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
const { ipcMain } = require('electron');
|
|
20
|
-
|
|
20
|
+
// Guard like pty.cjs: a node-pty ABI mismatch must not crash the whole app at
|
|
21
|
+
// module load (index.cjs requires this unconditionally). install() reports a
|
|
22
|
+
// clean error if the native module is unavailable.
|
|
23
|
+
let pty = null;
|
|
24
|
+
try {
|
|
25
|
+
pty = require('node-pty');
|
|
26
|
+
} catch (e) {
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
28
|
+
console.error('[pluginInstall] node-pty failed to load:', e?.message);
|
|
29
|
+
}
|
|
21
30
|
const path = require('node:path');
|
|
22
31
|
const os = require('node:os');
|
|
23
32
|
const fs = require('node:fs');
|
|
@@ -72,6 +81,10 @@ function childEnv() {
|
|
|
72
81
|
*/
|
|
73
82
|
function runStep({ slug, args, register, unregister }) {
|
|
74
83
|
return new Promise((resolve) => {
|
|
84
|
+
if (!pty) {
|
|
85
|
+
resolve({ ok: false, exitCode: -1, error: 'node-pty unavailable (native module failed to load)' });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
75
88
|
const claudeBin = resolveClaudeBin();
|
|
76
89
|
let proc;
|
|
77
90
|
try {
|
package/src/main/pty.cjs
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
// Load the native node-pty lazily-guarded: if its prebuilt/rebuilt binary
|
|
2
|
+
// doesn't match this Electron's ABI, a bare top-level require would throw and
|
|
3
|
+
// crash the WHOLE app at startup (index.cjs requires this module). Instead we
|
|
4
|
+
// capture the error and surface an actionable message when a tab is opened, so
|
|
5
|
+
// the app still boots and the user sees exactly how to fix the terminal.
|
|
6
|
+
let pty = null;
|
|
7
|
+
let ptyLoadError = null;
|
|
8
|
+
try {
|
|
9
|
+
pty = require('node-pty');
|
|
10
|
+
} catch (e) {
|
|
11
|
+
ptyLoadError = e;
|
|
12
|
+
// eslint-disable-next-line no-console
|
|
13
|
+
console.error('[pty] node-pty failed to load:', e?.message);
|
|
14
|
+
}
|
|
2
15
|
const { ipcMain } = require('electron');
|
|
3
16
|
const path = require('node:path');
|
|
4
17
|
const os = require('node:os');
|
|
@@ -8,6 +21,37 @@ const { cleanChildEnv, pathWithUserBins } = require('./lib/cleanEnv.cjs');
|
|
|
8
21
|
const { checkInsideHome } = require('./lib/insideHome.cjs');
|
|
9
22
|
const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
|
|
10
23
|
|
|
24
|
+
// Absolute path to the installed package root (src/main/ -> ../../), shown in
|
|
25
|
+
// the remediation message so the user can cd there and rebuild.
|
|
26
|
+
const PKG_DIR = path.join(__dirname, '..', '..');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* ANSI-formatted terminal text explaining a native-module / immediate-exit
|
|
30
|
+
* failure and exactly how to fix it. Written straight into the tab's output so
|
|
31
|
+
* a dead terminal explains itself instead of just going blank.
|
|
32
|
+
*/
|
|
33
|
+
function nativeModuleHelp(reason) {
|
|
34
|
+
const mac = process.platform === 'darwin';
|
|
35
|
+
const lines = [
|
|
36
|
+
'',
|
|
37
|
+
`\x1b[1;33m[session-manager] Terminal could not start.\x1b[0m`,
|
|
38
|
+
`\x1b[33m${reason}\x1b[0m`,
|
|
39
|
+
'',
|
|
40
|
+
`This is almost always the node-pty native module not matching this`,
|
|
41
|
+
`Electron build. Rebuild it once:`,
|
|
42
|
+
'',
|
|
43
|
+
` \x1b[36mcd ${PKG_DIR}\x1b[0m`,
|
|
44
|
+
` \x1b[36mnpx electron-rebuild -f -w node-pty\x1b[0m`,
|
|
45
|
+
'',
|
|
46
|
+
mac ? `macOS: if the rebuild fails, install the compiler first:` : '',
|
|
47
|
+
mac ? ` \x1b[36mxcode-select --install\x1b[0m` : '',
|
|
48
|
+
mac ? '' : '',
|
|
49
|
+
`Then quit and reopen session-manager.`,
|
|
50
|
+
'',
|
|
51
|
+
].filter((l) => l !== '');
|
|
52
|
+
return lines.join('\r\n') + '\r\n';
|
|
53
|
+
}
|
|
54
|
+
|
|
11
55
|
/**
|
|
12
56
|
* PtyManager — owns every claude PTY process, keyed by tabId (renderer-generated UUID).
|
|
13
57
|
* Streams output to the renderer via IPC channels scoped by tabId.
|
|
@@ -26,6 +70,15 @@ class PtyManager {
|
|
|
26
70
|
spawn({ tabId, cwd, cols = 120, rows = 30 }) {
|
|
27
71
|
console.log('[pty] spawn requested', { tabId });
|
|
28
72
|
|
|
73
|
+
// Native module unavailable — explain in the tab and report a clean exit
|
|
74
|
+
// rather than throwing (which would surface as an opaque IPC error).
|
|
75
|
+
if (!pty || ptyLoadError) {
|
|
76
|
+
const reason = `node-pty failed to load: ${ptyLoadError?.message ?? 'module missing'}`;
|
|
77
|
+
sendIfAlive(this.window, `pty:data:${tabId}`, nativeModuleHelp(reason));
|
|
78
|
+
setImmediate(() => sendIfAlive(this.window, `pty:exit:${tabId}`, { exitCode: 1, signal: undefined }));
|
|
79
|
+
return { pid: null, cwd, reattached: false, error: 'node-pty unavailable' };
|
|
80
|
+
}
|
|
81
|
+
|
|
29
82
|
// Validate that cwd is inside homedir before widening the allowed-root set.
|
|
30
83
|
if (cwd) {
|
|
31
84
|
const r = checkInsideHome(cwd);
|
|
@@ -88,11 +141,19 @@ class PtyManager {
|
|
|
88
141
|
});
|
|
89
142
|
} catch (err) {
|
|
90
143
|
console.error('[pty] pty.spawn threw:', err);
|
|
91
|
-
|
|
144
|
+
// Surface the cause in the tab + report a clean exit rather than throwing
|
|
145
|
+
// an opaque IPC error (covers a missing/invalid cwd or a native fault).
|
|
146
|
+
sendIfAlive(this.window, `pty:data:${tabId}`, nativeModuleHelp(`pty.spawn failed: ${err?.message ?? String(err)}`));
|
|
147
|
+
setImmediate(() => sendIfAlive(this.window, `pty:exit:${tabId}`, { exitCode: 1, signal: undefined }));
|
|
148
|
+
return { pid: null, cwd, reattached: false, error: String(err?.message || 'spawn-failed') };
|
|
92
149
|
}
|
|
93
150
|
console.log('[pty] spawned pid=', proc.pid, 'for tabId=', tabId);
|
|
94
151
|
|
|
152
|
+
const spawnedAt = Date.now();
|
|
153
|
+
let gotData = false;
|
|
154
|
+
|
|
95
155
|
proc.onData((data) => {
|
|
156
|
+
gotData = true;
|
|
96
157
|
sendIfAlive(this.window, `pty:data:${tabId}`, data);
|
|
97
158
|
});
|
|
98
159
|
|
|
@@ -105,11 +166,19 @@ class PtyManager {
|
|
|
105
166
|
this.sessions.delete(tabId);
|
|
106
167
|
return;
|
|
107
168
|
}
|
|
169
|
+
// Fast-exit detector: a shell that dies in <1.2s with a non-zero status
|
|
170
|
+
// and never printed anything almost certainly couldn't exec (broken
|
|
171
|
+
// node-pty spawn-helper / ABI on macOS). Explain it in the tab instead of
|
|
172
|
+
// leaving a blank, instantly-closed terminal. Guarded by !gotData so a
|
|
173
|
+
// real interactive shell (which prints a prompt immediately) is exempt.
|
|
174
|
+
if (!gotData && exitCode !== 0 && Date.now() - spawnedAt < 1200) {
|
|
175
|
+
sendIfAlive(this.window, `pty:data:${tabId}`, nativeModuleHelp(`The shell exited immediately (code ${exitCode}).`));
|
|
176
|
+
}
|
|
108
177
|
sendIfAlive(this.window, `pty:exit:${tabId}`, { exitCode, signal });
|
|
109
178
|
this.sessions.delete(tabId);
|
|
110
179
|
});
|
|
111
180
|
|
|
112
|
-
this.sessions.set(tabId, { proc, cwd, created:
|
|
181
|
+
this.sessions.set(tabId, { proc, cwd, created: spawnedAt });
|
|
113
182
|
return { pid: proc.pid, cwd, reattached: false };
|
|
114
183
|
}
|
|
115
184
|
|