paneful 0.7.3 → 0.8.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/README.md +21 -1
- package/assets/icons/Contents.json +318 -0
- package/assets/icons/icon-mac-128x128.png +0 -0
- package/assets/icons/icon-mac-128x128@2x.png +0 -0
- package/assets/icons/icon-mac-16x16.png +0 -0
- package/assets/icons/icon-mac-16x16@2x.png +0 -0
- package/assets/icons/icon-mac-256x256.png +0 -0
- package/assets/icons/icon-mac-256x256@2x.png +0 -0
- package/assets/icons/icon-mac-32x32.png +0 -0
- package/assets/icons/icon-mac-32x32@2x.png +0 -0
- package/assets/icons/icon-mac-512x512.png +0 -0
- package/assets/icons/icon-mac-512x512@2x.png +0 -0
- package/dist/server/browser.js +12 -10
- package/dist/server/index.js +85 -10
- package/dist/server/install-app.js +348 -139
- package/dist/server/ipc.js +5 -4
- package/dist/server/pty-manager.js +1 -1
- package/dist/server/settings-store.js +64 -0
- package/dist/server/ws-handler.js +23 -1
- package/dist/web/assets/{index-Dbp11YgF.css → index-B4-CUyGw.css} +1 -1
- package/dist/web/assets/{index-DSbEoJ0O.js → index-CoI7fA_K.js} +72 -72
- package/dist/web/icon-192.png +0 -0
- package/dist/web/icon-512.png +0 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -18,6 +18,8 @@ paneful --port 8080 # Use a specific port
|
|
|
18
18
|
paneful --spawn # Add current directory as a project
|
|
19
19
|
paneful --list # List all projects
|
|
20
20
|
paneful --kill my-project # Kill a project by name
|
|
21
|
+
paneful update # Update to the latest version
|
|
22
|
+
paneful --install-app # Install as a native macOS app
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
## Features
|
|
@@ -39,7 +41,7 @@ Save a workspace layout as a favourite — name, layout preset, and per-pane com
|
|
|
39
41
|
Automatically switches the active project based on which editor window is in focus. Works with VS Code, Cursor, Zed, and Windsurf on macOS. Toggle via the monitor icon in the sidebar header.
|
|
40
42
|
|
|
41
43
|
Requires:
|
|
42
|
-
1.
|
|
44
|
+
1. **Paneful** (native app) or **Terminal** (CLI) added to **System Settings > Privacy & Security > Accessibility**
|
|
43
45
|
2. Editor window title includes the folder name (default in VS Code/Cursor)
|
|
44
46
|
|
|
45
47
|
### Resizable Sidebar
|
|
@@ -50,6 +52,24 @@ Drag the right edge of the sidebar to resize it. Width persists across sessions.
|
|
|
50
52
|
|
|
51
53
|
Press `Cmd+R` or click the dashboard icon in the toolbar to automatically pick the best layout for your current pane count.
|
|
52
54
|
|
|
55
|
+
### Native macOS App
|
|
56
|
+
|
|
57
|
+
Install Paneful as a standalone macOS app with its own Dock icon and window:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
paneful --install-app
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
A folder picker dialog lets you choose the install location (defaults to `/Applications`). The app launches Paneful in a native WebKit window — no browser tab needed. Updating via `paneful update` automatically rebuilds the `.app` in place.
|
|
64
|
+
|
|
65
|
+
### Updating
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
paneful update
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Checks npm for the latest version, installs it globally, and rebuilds the native `.app` if one is installed. The Dock icon stays valid automatically.
|
|
72
|
+
|
|
53
73
|
### Update Notifications
|
|
54
74
|
|
|
55
75
|
Paneful checks for newer versions on npm and shows a notification in the sidebar when an update is available.
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
{
|
|
2
|
+
"images": [
|
|
3
|
+
{
|
|
4
|
+
"idiom": "universal",
|
|
5
|
+
"platform": "ios",
|
|
6
|
+
"scale": "2x",
|
|
7
|
+
"size": "20x20",
|
|
8
|
+
"filename": "icon-ios-20x20@2x.png"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"idiom": "universal",
|
|
12
|
+
"platform": "ios",
|
|
13
|
+
"scale": "3x",
|
|
14
|
+
"size": "20x20",
|
|
15
|
+
"filename": "icon-ios-20x20@3x.png"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"idiom": "universal",
|
|
19
|
+
"platform": "ios",
|
|
20
|
+
"scale": "2x",
|
|
21
|
+
"size": "29x29",
|
|
22
|
+
"filename": "icon-ios-29x29@2x.png"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"idiom": "universal",
|
|
26
|
+
"platform": "ios",
|
|
27
|
+
"scale": "3x",
|
|
28
|
+
"size": "29x29",
|
|
29
|
+
"filename": "icon-ios-29x29@3x.png"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"idiom": "universal",
|
|
33
|
+
"platform": "ios",
|
|
34
|
+
"scale": "2x",
|
|
35
|
+
"size": "38x38",
|
|
36
|
+
"filename": "icon-ios-38x38@2x.png"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"idiom": "universal",
|
|
40
|
+
"platform": "ios",
|
|
41
|
+
"scale": "3x",
|
|
42
|
+
"size": "38x38",
|
|
43
|
+
"filename": "icon-ios-38x38@3x.png"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"idiom": "universal",
|
|
47
|
+
"platform": "ios",
|
|
48
|
+
"scale": "2x",
|
|
49
|
+
"size": "40x40",
|
|
50
|
+
"filename": "icon-ios-40x40@2x.png"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"idiom": "universal",
|
|
54
|
+
"platform": "ios",
|
|
55
|
+
"scale": "3x",
|
|
56
|
+
"size": "40x40",
|
|
57
|
+
"filename": "icon-ios-40x40@3x.png"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"idiom": "universal",
|
|
61
|
+
"platform": "ios",
|
|
62
|
+
"scale": "2x",
|
|
63
|
+
"size": "60x60",
|
|
64
|
+
"filename": "icon-ios-60x60@2x.png"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"idiom": "universal",
|
|
68
|
+
"platform": "ios",
|
|
69
|
+
"scale": "3x",
|
|
70
|
+
"size": "60x60",
|
|
71
|
+
"filename": "icon-ios-60x60@3x.png"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"idiom": "universal",
|
|
75
|
+
"platform": "ios",
|
|
76
|
+
"scale": "2x",
|
|
77
|
+
"size": "64x64",
|
|
78
|
+
"filename": "icon-ios-64x64@2x.png"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"idiom": "universal",
|
|
82
|
+
"platform": "ios",
|
|
83
|
+
"scale": "3x",
|
|
84
|
+
"size": "64x64",
|
|
85
|
+
"filename": "icon-ios-64x64@3x.png"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"idiom": "universal",
|
|
89
|
+
"platform": "ios",
|
|
90
|
+
"scale": "2x",
|
|
91
|
+
"size": "68x68",
|
|
92
|
+
"filename": "icon-ios-68x68@2x.png"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"idiom": "universal",
|
|
96
|
+
"platform": "ios",
|
|
97
|
+
"scale": "2x",
|
|
98
|
+
"size": "76x76",
|
|
99
|
+
"filename": "icon-ios-76x76@2x.png"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"idiom": "universal",
|
|
103
|
+
"platform": "ios",
|
|
104
|
+
"scale": "2x",
|
|
105
|
+
"size": "83.5x83.5",
|
|
106
|
+
"filename": "icon-ios-83.5x83.5@2x.png"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"idiom": "universal",
|
|
110
|
+
"platform": "ios",
|
|
111
|
+
"size": "1024x1024",
|
|
112
|
+
"filename": "icon-ios-1024x1024.png"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"idiom": "mac",
|
|
116
|
+
"scale": "1x",
|
|
117
|
+
"size": "16x16",
|
|
118
|
+
"filename": "icon-mac-16x16.png"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"idiom": "mac",
|
|
122
|
+
"scale": "2x",
|
|
123
|
+
"size": "16x16",
|
|
124
|
+
"filename": "icon-mac-16x16@2x.png"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"idiom": "mac",
|
|
128
|
+
"scale": "1x",
|
|
129
|
+
"size": "32x32",
|
|
130
|
+
"filename": "icon-mac-32x32.png"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"idiom": "mac",
|
|
134
|
+
"scale": "2x",
|
|
135
|
+
"size": "32x32",
|
|
136
|
+
"filename": "icon-mac-32x32@2x.png"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"idiom": "mac",
|
|
140
|
+
"scale": "1x",
|
|
141
|
+
"size": "128x128",
|
|
142
|
+
"filename": "icon-mac-128x128.png"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"idiom": "mac",
|
|
146
|
+
"scale": "2x",
|
|
147
|
+
"size": "128x128",
|
|
148
|
+
"filename": "icon-mac-128x128@2x.png"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"idiom": "mac",
|
|
152
|
+
"scale": "1x",
|
|
153
|
+
"size": "256x256",
|
|
154
|
+
"filename": "icon-mac-256x256.png"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"idiom": "mac",
|
|
158
|
+
"scale": "2x",
|
|
159
|
+
"size": "256x256",
|
|
160
|
+
"filename": "icon-mac-256x256@2x.png"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"idiom": "mac",
|
|
164
|
+
"scale": "1x",
|
|
165
|
+
"size": "512x512",
|
|
166
|
+
"filename": "icon-mac-512x512.png"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"idiom": "mac",
|
|
170
|
+
"scale": "2x",
|
|
171
|
+
"size": "512x512",
|
|
172
|
+
"filename": "icon-mac-512x512@2x.png"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"idiom": "universal",
|
|
176
|
+
"platform": "watchos",
|
|
177
|
+
"scale": "2x",
|
|
178
|
+
"size": "22x22",
|
|
179
|
+
"filename": "icon-watchos-22x22@2x.png"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"idiom": "universal",
|
|
183
|
+
"platform": "watchos",
|
|
184
|
+
"scale": "2x",
|
|
185
|
+
"size": "24x24",
|
|
186
|
+
"filename": "icon-watchos-24x24@2x.png"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"idiom": "universal",
|
|
190
|
+
"platform": "watchos",
|
|
191
|
+
"scale": "2x",
|
|
192
|
+
"size": "27.5x27.5",
|
|
193
|
+
"filename": "icon-watchos-27.5x27.5@2x.png"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"idiom": "universal",
|
|
197
|
+
"platform": "watchos",
|
|
198
|
+
"scale": "2x",
|
|
199
|
+
"size": "29x29",
|
|
200
|
+
"filename": "icon-watchos-29x29@2x.png"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"idiom": "universal",
|
|
204
|
+
"platform": "watchos",
|
|
205
|
+
"scale": "2x",
|
|
206
|
+
"size": "30x30",
|
|
207
|
+
"filename": "icon-watchos-30x30@2x.png"
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"idiom": "universal",
|
|
211
|
+
"platform": "watchos",
|
|
212
|
+
"scale": "2x",
|
|
213
|
+
"size": "32x32",
|
|
214
|
+
"filename": "icon-watchos-32x32@2x.png"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"idiom": "universal",
|
|
218
|
+
"platform": "watchos",
|
|
219
|
+
"scale": "2x",
|
|
220
|
+
"size": "33x33",
|
|
221
|
+
"filename": "icon-watchos-33x33@2x.png"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"idiom": "universal",
|
|
225
|
+
"platform": "watchos",
|
|
226
|
+
"scale": "2x",
|
|
227
|
+
"size": "40x40",
|
|
228
|
+
"filename": "icon-watchos-40x40@2x.png"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"idiom": "universal",
|
|
232
|
+
"platform": "watchos",
|
|
233
|
+
"scale": "2x",
|
|
234
|
+
"size": "43.5x43.5",
|
|
235
|
+
"filename": "icon-watchos-43.5x43.5@2x.png"
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"idiom": "universal",
|
|
239
|
+
"platform": "watchos",
|
|
240
|
+
"scale": "2x",
|
|
241
|
+
"size": "44x44",
|
|
242
|
+
"filename": "icon-watchos-44x44@2x.png"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"idiom": "universal",
|
|
246
|
+
"platform": "watchos",
|
|
247
|
+
"scale": "2x",
|
|
248
|
+
"size": "46x46",
|
|
249
|
+
"filename": "icon-watchos-46x46@2x.png"
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"idiom": "universal",
|
|
253
|
+
"platform": "watchos",
|
|
254
|
+
"scale": "2x",
|
|
255
|
+
"size": "50x50",
|
|
256
|
+
"filename": "icon-watchos-50x50@2x.png"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"idiom": "universal",
|
|
260
|
+
"platform": "watchos",
|
|
261
|
+
"scale": "2x",
|
|
262
|
+
"size": "51x51",
|
|
263
|
+
"filename": "icon-watchos-51x51@2x.png"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"idiom": "universal",
|
|
267
|
+
"platform": "watchos",
|
|
268
|
+
"scale": "2x",
|
|
269
|
+
"size": "54x54",
|
|
270
|
+
"filename": "icon-watchos-54x54@2x.png"
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"idiom": "universal",
|
|
274
|
+
"platform": "watchos",
|
|
275
|
+
"scale": "2x",
|
|
276
|
+
"size": "86x86",
|
|
277
|
+
"filename": "icon-watchos-86x86@2x.png"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"idiom": "universal",
|
|
281
|
+
"platform": "watchos",
|
|
282
|
+
"scale": "2x",
|
|
283
|
+
"size": "98x98",
|
|
284
|
+
"filename": "icon-watchos-98x98@2x.png"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"idiom": "universal",
|
|
288
|
+
"platform": "watchos",
|
|
289
|
+
"scale": "2x",
|
|
290
|
+
"size": "108x108",
|
|
291
|
+
"filename": "icon-watchos-108x108@2x.png"
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"idiom": "universal",
|
|
295
|
+
"platform": "watchos",
|
|
296
|
+
"scale": "2x",
|
|
297
|
+
"size": "117x117",
|
|
298
|
+
"filename": "icon-watchos-117x117@2x.png"
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"idiom": "universal",
|
|
302
|
+
"platform": "watchos",
|
|
303
|
+
"scale": "2x",
|
|
304
|
+
"size": "129x129",
|
|
305
|
+
"filename": "icon-watchos-129x129@2x.png"
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
"idiom": "universal",
|
|
309
|
+
"platform": "watchos",
|
|
310
|
+
"size": "1024x1024",
|
|
311
|
+
"filename": "icon-watchos-1024x1024.png"
|
|
312
|
+
}
|
|
313
|
+
],
|
|
314
|
+
"info": {
|
|
315
|
+
"author": "makeappicon",
|
|
316
|
+
"version": 1
|
|
317
|
+
}
|
|
318
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/server/browser.js
CHANGED
|
@@ -58,20 +58,22 @@ export function openBrowser(port) {
|
|
|
58
58
|
function tryChromeAppMode(url) {
|
|
59
59
|
const appArg = `--app=${url}`;
|
|
60
60
|
if (process.platform === 'darwin') {
|
|
61
|
+
// Use "open -na" to launch via macOS app framework — directly executing
|
|
62
|
+
// the binary is unreliable from .app bundles and can trigger the
|
|
63
|
+
// "choose application" dialog.
|
|
61
64
|
const browsers = [
|
|
62
|
-
'/Applications/Google Chrome.app
|
|
63
|
-
'/Applications/Chromium.app
|
|
64
|
-
'/Applications/Microsoft Edge.app
|
|
65
|
-
'/Applications/Brave Browser.app
|
|
66
|
-
'/Applications/Arc.app/Contents/MacOS/Arc',
|
|
65
|
+
{ app: 'Google Chrome', path: '/Applications/Google Chrome.app' },
|
|
66
|
+
{ app: 'Chromium', path: '/Applications/Chromium.app' },
|
|
67
|
+
{ app: 'Microsoft Edge', path: '/Applications/Microsoft Edge.app' },
|
|
68
|
+
{ app: 'Brave Browser', path: '/Applications/Brave Browser.app' },
|
|
67
69
|
];
|
|
68
|
-
for (const
|
|
69
|
-
if (fs.existsSync(
|
|
70
|
-
execFile(
|
|
70
|
+
for (const { app, path: appPath } of browsers) {
|
|
71
|
+
if (fs.existsSync(appPath)) {
|
|
72
|
+
execFile('open', ['-na', app, '--args', appArg], (err) => {
|
|
71
73
|
if (err)
|
|
72
|
-
console.warn(`Failed to launch ${
|
|
74
|
+
console.warn(`Failed to launch ${app}:`, err.message);
|
|
73
75
|
});
|
|
74
|
-
console.log(`Opened in app mode via ${
|
|
76
|
+
console.log(`Opened in app mode via ${app}`);
|
|
75
77
|
return true;
|
|
76
78
|
}
|
|
77
79
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -37,6 +37,17 @@ async function getLatestVersion() {
|
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
+
function isNewerVersion(latest, current) {
|
|
41
|
+
const l = latest.split('.').map(Number);
|
|
42
|
+
const c = current.split('.').map(Number);
|
|
43
|
+
for (let i = 0; i < 3; i++) {
|
|
44
|
+
if ((l[i] || 0) > (c[i] || 0))
|
|
45
|
+
return true;
|
|
46
|
+
if ((l[i] || 0) < (c[i] || 0))
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
40
51
|
// ── Paths ──
|
|
41
52
|
function dataDir() {
|
|
42
53
|
const dir = path.join(os.homedir(), '.paneful');
|
|
@@ -171,7 +182,7 @@ async function handleKill(name) {
|
|
|
171
182
|
async function startServer(devMode, port) {
|
|
172
183
|
// Lazy-load heavy dependencies (express, node-pty, ws, etc.)
|
|
173
184
|
// so CLI commands that don't need the server start instantly
|
|
174
|
-
const [{ default: http }, { default: express }, { execFile }, { v4: uuidv4 }, { PtyManager }, { ProjectStore }, { WsHandler }, { startIpcListener },] = await Promise.all([
|
|
185
|
+
const [{ default: http }, { default: express }, { execFile }, { v4: uuidv4 }, { PtyManager }, { ProjectStore }, { WsHandler }, { startIpcListener }, { SettingsStore },] = await Promise.all([
|
|
175
186
|
import('node:http'),
|
|
176
187
|
import('express'),
|
|
177
188
|
import('node:child_process'),
|
|
@@ -180,11 +191,13 @@ async function startServer(devMode, port) {
|
|
|
180
191
|
import('./project-store.js'),
|
|
181
192
|
import('./ws-handler.js'),
|
|
182
193
|
import('./ipc.js'),
|
|
194
|
+
import('./settings-store.js'),
|
|
183
195
|
]);
|
|
184
196
|
const app = express();
|
|
185
197
|
app.use(express.json());
|
|
186
198
|
const ptyManager = new PtyManager();
|
|
187
199
|
const projectStore = new ProjectStore(dataDir());
|
|
200
|
+
const settingsStore = new SettingsStore(dataDir());
|
|
188
201
|
// API routes
|
|
189
202
|
app.get('/api/projects', (_req, res) => {
|
|
190
203
|
res.json(projectStore.list());
|
|
@@ -205,6 +218,12 @@ async function startServer(devMode, port) {
|
|
|
205
218
|
const latest = await getLatestVersion();
|
|
206
219
|
res.json({ current: CURRENT_VERSION, latest });
|
|
207
220
|
});
|
|
221
|
+
app.get('/api/settings', (_req, res) => {
|
|
222
|
+
res.json(settingsStore.get());
|
|
223
|
+
});
|
|
224
|
+
app.put('/api/settings', (req, res) => {
|
|
225
|
+
res.json(settingsStore.update(req.body));
|
|
226
|
+
});
|
|
208
227
|
app.post('/api/projects/:id/kill', (req, res) => {
|
|
209
228
|
const killed = ptyManager.killProject(req.params.id);
|
|
210
229
|
res.json({ killed: killed.length });
|
|
@@ -272,6 +291,9 @@ async function startServer(devMode, port) {
|
|
|
272
291
|
else if (parts.length === 2) {
|
|
273
292
|
projectName = parts[0];
|
|
274
293
|
}
|
|
294
|
+
else {
|
|
295
|
+
projectName = title;
|
|
296
|
+
}
|
|
275
297
|
}
|
|
276
298
|
const prev = editorCache.projectName;
|
|
277
299
|
editorCache = { projectName };
|
|
@@ -367,7 +389,7 @@ async function startServer(devMode, port) {
|
|
|
367
389
|
}
|
|
368
390
|
const server = http.createServer(app);
|
|
369
391
|
// WebSocket handler
|
|
370
|
-
const wsHandler = new WsHandler(server, ptyManager, projectStore);
|
|
392
|
+
const wsHandler = new WsHandler(server, ptyManager, projectStore, { onIdle: () => shutdown() });
|
|
371
393
|
// IPC listener
|
|
372
394
|
const ipcServer = startIpcListener(socketPath(), ptyManager, projectStore, wsHandler);
|
|
373
395
|
server.on('error', (err) => {
|
|
@@ -383,18 +405,25 @@ async function startServer(devMode, port) {
|
|
|
383
405
|
const actualPort = typeof addr === 'object' && addr ? addr.port : port;
|
|
384
406
|
writeLockfile(process.pid, actualPort);
|
|
385
407
|
console.log(`Paneful running on http://localhost:${actualPort}`);
|
|
386
|
-
if (!devMode) {
|
|
408
|
+
if (!devMode && !process.env.PANEFUL_APP) {
|
|
387
409
|
openBrowser(actualPort);
|
|
388
410
|
}
|
|
389
411
|
});
|
|
390
412
|
// Graceful shutdown
|
|
413
|
+
let shuttingDown = false;
|
|
391
414
|
const shutdown = () => {
|
|
415
|
+
if (shuttingDown)
|
|
416
|
+
return;
|
|
417
|
+
shuttingDown = true;
|
|
392
418
|
console.log('Shutting down...');
|
|
393
419
|
ptyManager.killAll();
|
|
394
420
|
removeLockfile();
|
|
395
421
|
ipcServer.close();
|
|
396
|
-
server.close()
|
|
397
|
-
|
|
422
|
+
server.close(() => {
|
|
423
|
+
process.exit(0);
|
|
424
|
+
});
|
|
425
|
+
// Force exit if server.close() hangs
|
|
426
|
+
setTimeout(() => process.exit(0), 2000);
|
|
398
427
|
};
|
|
399
428
|
process.on('SIGINT', shutdown);
|
|
400
429
|
process.on('SIGTERM', shutdown);
|
|
@@ -406,13 +435,59 @@ program
|
|
|
406
435
|
.option('--spawn', 'Spawn a new project in the current directory')
|
|
407
436
|
.option('--list', 'List all projects')
|
|
408
437
|
.option('--kill <name>', 'Kill a project by name')
|
|
409
|
-
.option('--install-app', 'Create Paneful.app
|
|
438
|
+
.option('--install-app', 'Create Paneful.app (macOS only)')
|
|
439
|
+
.option('--app-path <path>', 'Custom path for Paneful.app (default: /Applications/Paneful.app)')
|
|
410
440
|
.option('--dev', 'Run in development mode (proxy to Vite dev server)')
|
|
411
|
-
.option('--port <number>', 'Port to listen on (default: random
|
|
412
|
-
|
|
441
|
+
.option('--port <number>', 'Port to listen on (default: random)', parseInt);
|
|
442
|
+
program
|
|
443
|
+
.command('update')
|
|
444
|
+
.description('Update paneful to the latest version')
|
|
445
|
+
.action(async () => {
|
|
446
|
+
const { execSync } = await import('node:child_process');
|
|
447
|
+
const latest = await getLatestVersion();
|
|
448
|
+
if (!latest) {
|
|
449
|
+
console.log('Could not check for updates. Try: npm install -g paneful@latest');
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
if (!isNewerVersion(latest, CURRENT_VERSION)) {
|
|
453
|
+
console.log(`Already on latest version (v${CURRENT_VERSION})`);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
console.log(`Updating paneful v${CURRENT_VERSION} → v${latest}...`);
|
|
457
|
+
execSync('npm install -g paneful@latest', { stdio: 'inherit' });
|
|
458
|
+
// Find existing Paneful.app to rebuild it in place
|
|
459
|
+
let appPath = null;
|
|
460
|
+
if (process.platform === 'darwin') {
|
|
461
|
+
try {
|
|
462
|
+
const found = execSync("mdfind \"kMDItemCFBundleIdentifier == 'com.paneful.app'\"", { encoding: 'utf-8' }).trim();
|
|
463
|
+
if (found)
|
|
464
|
+
appPath = found.split('\n')[0];
|
|
465
|
+
}
|
|
466
|
+
catch { /* spotlight unavailable */ }
|
|
467
|
+
// Fallback: check common locations
|
|
468
|
+
if (!appPath) {
|
|
469
|
+
for (const p of [
|
|
470
|
+
path.join(os.homedir(), 'Applications', 'Paneful.app'),
|
|
471
|
+
'/Applications/Paneful.app',
|
|
472
|
+
]) {
|
|
473
|
+
if (fs.existsSync(p)) {
|
|
474
|
+
appPath = p;
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (appPath) {
|
|
481
|
+
console.log(`\nRebuilding ${appPath}...`);
|
|
482
|
+
const { installApp } = await import('./install-app.js');
|
|
483
|
+
await installApp(appPath);
|
|
484
|
+
}
|
|
485
|
+
console.log('\nUpdate complete!');
|
|
486
|
+
});
|
|
487
|
+
program.action(async (opts) => {
|
|
413
488
|
if (opts.installApp) {
|
|
414
489
|
const { installApp } = await import('./install-app.js');
|
|
415
|
-
await installApp();
|
|
490
|
+
await installApp(opts.appPath);
|
|
416
491
|
return;
|
|
417
492
|
}
|
|
418
493
|
if (opts.list) {
|
|
@@ -439,6 +514,6 @@ program
|
|
|
439
514
|
if (lock) {
|
|
440
515
|
removeLockfile();
|
|
441
516
|
}
|
|
442
|
-
await startServer(opts.dev || false, opts.port ||
|
|
517
|
+
await startServer(opts.dev || false, opts.port || 0);
|
|
443
518
|
});
|
|
444
519
|
program.parse();
|