hjworktree-cli 2.2.0 → 2.3.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/.claude/settings.local.json +11 -2
- package/README.md +8 -0
- package/dist/server/socketHandlers.d.ts.map +1 -1
- package/dist/server/socketHandlers.js +39 -5
- package/dist/server/socketHandlers.js.map +1 -1
- package/dist/shared/constants.d.ts +1 -1
- package/dist/shared/constants.js +1 -1
- package/dist/web/assets/{index-D8dr9mJa.js → index-De6xm4hO.js} +2 -2
- package/dist/web/assets/{index-D8dr9mJa.js.map → index-De6xm4hO.js.map} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +3 -2
- package/scripts/fix-pty-permissions.js +89 -0
- package/server/socketHandlers.ts +42 -5
- package/shared/constants.ts +1 -1
package/dist/web/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=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-De6xm4hO.js"></script>
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/index-CsixHL-D.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hjworktree-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Web-based git worktree parallel AI coding agent runner",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server/index.js",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"build:server": "tsc",
|
|
16
16
|
"build:web": "vite build --config web/vite.config.ts",
|
|
17
17
|
"start": "node dist/server/index.js",
|
|
18
|
+
"postinstall": "node scripts/fix-pty-permissions.js",
|
|
18
19
|
"prepublishOnly": "npm run build"
|
|
19
20
|
},
|
|
20
21
|
"keywords": [
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"cors": "^2.8.5",
|
|
39
40
|
"express": "^4.21.0",
|
|
40
|
-
"node-pty": "^1.
|
|
41
|
+
"node-pty": "^1.1.0",
|
|
41
42
|
"open": "^10.1.0",
|
|
42
43
|
"simple-git": "^3.27.0",
|
|
43
44
|
"socket.io": "^4.7.5"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fix node-pty spawn-helper permissions on macOS/Linux
|
|
5
|
+
*
|
|
6
|
+
* This script addresses the posix_spawnp failed error that occurs when
|
|
7
|
+
* spawn-helper lacks execute permissions after npm install.
|
|
8
|
+
*
|
|
9
|
+
* Reference: https://github.com/microsoft/node-pty/issues/670
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { chmod, access, constants } from 'fs';
|
|
13
|
+
import { resolve, dirname } from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
18
|
+
// Skip on Windows
|
|
19
|
+
if (process.platform === 'win32') {
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Possible spawn-helper locations
|
|
24
|
+
const possiblePaths = [
|
|
25
|
+
// When installed as dependency
|
|
26
|
+
resolve(__dirname, '../node_modules/node-pty/build/Release/spawn-helper'),
|
|
27
|
+
// When this package is installed globally
|
|
28
|
+
resolve(__dirname, '../node_modules/.pnpm/node-pty@1.1.0/node_modules/node-pty/build/Release/spawn-helper'),
|
|
29
|
+
// Direct prebuild location
|
|
30
|
+
resolve(__dirname, '../node_modules/node-pty/prebuilds'),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Find and fix spawn-helper permissions
|
|
34
|
+
async function fixPermissions() {
|
|
35
|
+
for (const spawnHelperPath of possiblePaths) {
|
|
36
|
+
try {
|
|
37
|
+
await new Promise((resolve, reject) => {
|
|
38
|
+
access(spawnHelperPath, constants.F_OK, (err) => {
|
|
39
|
+
if (err) reject(err);
|
|
40
|
+
else resolve();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// File exists, set execute permission
|
|
45
|
+
await new Promise((resolve, reject) => {
|
|
46
|
+
chmod(spawnHelperPath, 0o755, (err) => {
|
|
47
|
+
if (err) reject(err);
|
|
48
|
+
else resolve();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
console.log(`[fix-pty-permissions] Fixed: ${spawnHelperPath}`);
|
|
53
|
+
} catch {
|
|
54
|
+
// File doesn't exist at this path, try next
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Also try to find spawn-helper recursively in node_modules
|
|
59
|
+
try {
|
|
60
|
+
const { execSync } = await import('child_process');
|
|
61
|
+
const result = execSync(
|
|
62
|
+
'find node_modules -name "spawn-helper" -type f 2>/dev/null || true',
|
|
63
|
+
{ cwd: resolve(__dirname, '..'), encoding: 'utf-8' }
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const files = result.trim().split('\n').filter(Boolean);
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
try {
|
|
69
|
+
const fullPath = resolve(__dirname, '..', file);
|
|
70
|
+
await new Promise((resolve, reject) => {
|
|
71
|
+
chmod(fullPath, 0o755, (err) => {
|
|
72
|
+
if (err) reject(err);
|
|
73
|
+
else resolve();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
console.log(`[fix-pty-permissions] Fixed: ${fullPath}`);
|
|
77
|
+
} catch {
|
|
78
|
+
// Ignore errors for individual files
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// find command failed, ignore
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fixPermissions().catch(() => {
|
|
87
|
+
// Don't fail the install if this script fails
|
|
88
|
+
process.exit(0);
|
|
89
|
+
});
|
package/server/socketHandlers.ts
CHANGED
|
@@ -9,11 +9,32 @@ import type {
|
|
|
9
9
|
AgentId
|
|
10
10
|
} from '../shared/types/index.js';
|
|
11
11
|
import { AI_AGENTS } from '../shared/constants.js';
|
|
12
|
-
import fs from 'fs';
|
|
12
|
+
import fs, { realpathSync } from 'fs';
|
|
13
|
+
import path from 'path';
|
|
13
14
|
import { promisify } from 'util';
|
|
14
15
|
|
|
15
16
|
const access = promisify(fs.access);
|
|
16
17
|
|
|
18
|
+
// Environment and path utilities for macOS compatibility
|
|
19
|
+
function sanitizeEnv(env: NodeJS.ProcessEnv): Record<string, string> {
|
|
20
|
+
const sanitized: Record<string, string> = {};
|
|
21
|
+
for (const [key, value] of Object.entries(env)) {
|
|
22
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
23
|
+
sanitized[key] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return sanitized;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizePath(targetPath: string): string {
|
|
30
|
+
try {
|
|
31
|
+
const absolutePath = path.resolve(targetPath);
|
|
32
|
+
return realpathSync(absolutePath);
|
|
33
|
+
} catch {
|
|
34
|
+
return path.resolve(targetPath);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
17
38
|
// Validation utilities
|
|
18
39
|
async function validatePath(pathToCheck: string): Promise<{ valid: boolean; error?: string }> {
|
|
19
40
|
try {
|
|
@@ -99,7 +120,20 @@ function getAgentCommand(agentType: AgentId): string {
|
|
|
99
120
|
}
|
|
100
121
|
|
|
101
122
|
function getShell(): string {
|
|
102
|
-
|
|
123
|
+
if (process.platform === 'win32') {
|
|
124
|
+
return 'powershell.exe';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const shellPath = process.env.SHELL || '/bin/bash';
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const resolvedShell = realpathSync(shellPath);
|
|
131
|
+
fs.accessSync(resolvedShell, fs.constants.X_OK);
|
|
132
|
+
return resolvedShell;
|
|
133
|
+
} catch {
|
|
134
|
+
// Fallback: use shell name only (let PATH resolve it)
|
|
135
|
+
return path.basename(shellPath);
|
|
136
|
+
}
|
|
103
137
|
}
|
|
104
138
|
|
|
105
139
|
export function setupSocketHandlers(io: Server, cwd: string) {
|
|
@@ -131,17 +165,20 @@ export function setupSocketHandlers(io: Server, cwd: string) {
|
|
|
131
165
|
// Wait for spawn interval to prevent resource contention
|
|
132
166
|
await waitForSpawnInterval();
|
|
133
167
|
|
|
168
|
+
// Normalize cwd path (resolve symlinks like /tmp -> /private/tmp on macOS)
|
|
169
|
+
const normalizedCwd = normalizePath(worktreePath);
|
|
170
|
+
|
|
134
171
|
const ptyProcess = await spawnWithRetry(shell, [], {
|
|
135
172
|
name: 'xterm-256color',
|
|
136
173
|
cols: 120,
|
|
137
174
|
rows: 30,
|
|
138
|
-
cwd:
|
|
139
|
-
env: {
|
|
175
|
+
cwd: normalizedCwd,
|
|
176
|
+
env: sanitizeEnv({
|
|
140
177
|
...process.env,
|
|
141
178
|
TERM: 'xterm-256color',
|
|
142
179
|
FORCE_COLOR: '1',
|
|
143
180
|
COLORTERM: 'truecolor',
|
|
144
|
-
}
|
|
181
|
+
}),
|
|
145
182
|
});
|
|
146
183
|
|
|
147
184
|
sessions.set(sessionId, {
|
package/shared/constants.ts
CHANGED