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.
@@ -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-D8dr9mJa.js"></script>
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.2.0",
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.0.0",
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
+ });
@@ -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
- return process.platform === 'win32' ? 'powershell.exe' : process.env.SHELL || '/bin/bash';
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: worktreePath,
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
- } as Record<string, string>,
181
+ }),
145
182
  });
146
183
 
147
184
  sessions.set(sessionId, {
@@ -31,5 +31,5 @@ export const DEFAULT_PARALLEL_COUNT = 3;
31
31
  export const BRANCH_POLL_INTERVAL = 5000; // 5 seconds
32
32
 
33
33
  export const APP_NAME = 'hjWorktree CLI';
34
- export const APP_VERSION = '2.0.0';
34
+ export const APP_VERSION = '2.3.0';
35
35
  export const DEFAULT_PORT = 3847;