@bakapiano/ccsm 0.22.5 → 0.22.7
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.md +538 -538
- package/README.md +189 -189
- package/bin/ccsm.js +235 -235
- package/lib/cliActivity.js +139 -139
- package/lib/codexSeed.js +183 -183
- package/lib/config.js +279 -274
- package/lib/devices.js +229 -229
- package/lib/folders.js +124 -124
- package/lib/localCliSessions.js +519 -519
- package/lib/persistedSessions.js +129 -129
- package/lib/tunnel.js +621 -621
- package/lib/webTerminal.js +225 -225
- package/lib/workspace.js +233 -233
- package/package.json +57 -57
- package/public/css/base.css +99 -99
- package/public/css/cards.css +183 -183
- package/public/css/feedback.css +504 -504
- package/public/css/forms.css +453 -453
- package/public/css/layout.css +177 -176
- package/public/css/modal.css +190 -190
- package/public/css/responsive.css +176 -176
- package/public/css/sidebar.css +707 -707
- package/public/css/terminals.css +547 -553
- package/public/css/tokens.css +81 -81
- package/public/css/wco.css +196 -196
- package/public/css/widgets.css +2725 -2725
- package/public/index.html +152 -152
- package/public/js/api.js +371 -371
- package/public/js/backend.js +149 -149
- package/public/js/components/App.js +73 -73
- package/public/js/components/DirectoryPicker.js +203 -203
- package/public/js/components/EntityFormModal.js +153 -153
- package/public/js/components/Modal.js +57 -57
- package/public/js/components/OfflineBanner.js +67 -67
- package/public/js/components/PageTitleBar.js +13 -13
- package/public/js/components/PendingApprovalOverlay.js +128 -128
- package/public/js/components/Picker.js +179 -179
- package/public/js/components/Popover.js +55 -55
- package/public/js/components/RestartOverlay.js +36 -36
- package/public/js/components/Sidebar.js +380 -380
- package/public/js/components/TerminalInstance.js +28 -9
- package/public/js/components/XtermTerminal.js +62 -2
- package/public/js/components/useDragSort.js +67 -67
- package/public/js/dialog.js +67 -67
- package/public/js/icons.js +212 -212
- package/public/js/main.js +296 -296
- package/public/js/pages/AboutPage.js +90 -90
- package/public/js/pages/ConfigurePage.js +728 -713
- package/public/js/pages/LaunchPage.js +421 -421
- package/public/js/pages/RemotePage.js +743 -743
- package/public/js/pages/SessionsPage.js +73 -80
- package/public/js/state.js +335 -335
- package/scripts/dev.js +149 -149
- package/scripts/install.js +153 -153
- package/scripts/restart-helper.js +96 -96
- package/scripts/upgrade-helper.js +687 -687
- package/server.js +1820 -1807
- package/public/manifest.webmanifest +0 -25
- package/public/setup/index.html +0 -567
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// Sessions page · the main pane. Shows the terminal for the currently
|
|
2
|
-
// selected session (activeSessionId), with a thin header providing
|
|
3
|
-
// session metadata + a session-tabs strip (future multi-tab support)
|
|
4
|
-
// and a kebab menu top-right for per-session actions. When a session is
|
|
5
|
-
// selected but not running we auto-resume it — no manual button.
|
|
6
|
-
|
|
1
|
+
// Sessions page · the main pane. Shows the terminal for the currently
|
|
2
|
+
// selected session (activeSessionId), with a thin header providing
|
|
3
|
+
// session metadata + a session-tabs strip (future multi-tab support)
|
|
4
|
+
// and a kebab menu top-right for per-session actions. When a session is
|
|
5
|
+
// selected but not running we auto-resume it — no manual button.
|
|
6
|
+
|
|
7
7
|
import { html } from '../html.js';
|
|
8
8
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
9
9
|
import { activeSessionId, sessions, config, selectTab, selectSession, clockTick } from '../state.js';
|
|
@@ -29,7 +29,7 @@ function SessionTabs({ activeId, openSessions, onActivate, onClose, onReorder, o
|
|
|
29
29
|
<div class="session-tabs" role="tablist">
|
|
30
30
|
<div class="session-tabs-list">
|
|
31
31
|
${open.map((s) => {
|
|
32
|
-
const cli = (config.value?.clis || []).find((c) => c.id === s.cliId);
|
|
32
|
+
const cli = (config.value?.clis || []).find((c) => c.id === s.cliId);
|
|
33
33
|
const Icon = IconForCliType(cli?.type) || IconTerminal;
|
|
34
34
|
const t = s.title || s.workspace || s.id.slice(0, 12);
|
|
35
35
|
const isActive = s.id === activeId;
|
|
@@ -73,28 +73,28 @@ function SessionTabs({ activeId, openSessions, onActivate, onClose, onReorder, o
|
|
|
73
73
|
</button>
|
|
74
74
|
</div>`;
|
|
75
75
|
})}
|
|
76
|
-
${/* <button class="session-tab session-tab-add" onClick=${onNew} title="New session">
|
|
77
|
-
<${IconPlus} />
|
|
78
|
-
</button> */ null}
|
|
79
|
-
</div>
|
|
80
|
-
${kebab ? html`<div class="session-tabs-right">${kebab}</div>` : null}
|
|
81
|
-
</div>`;
|
|
82
|
-
}
|
|
83
|
-
|
|
76
|
+
${/* <button class="session-tab session-tab-add" onClick=${onNew} title="New session">
|
|
77
|
+
<${IconPlus} />
|
|
78
|
+
</button> */ null}
|
|
79
|
+
</div>
|
|
80
|
+
${kebab ? html`<div class="session-tabs-right">${kebab}</div>` : null}
|
|
81
|
+
</div>`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
84
|
function SessionMenu({ session, switchableClis, onRename, onDelete, onOpenEditor, onSwitchCli }) {
|
|
85
85
|
const [open, setOpen] = useState(false);
|
|
86
86
|
const anchor = useRef(null);
|
|
87
87
|
return html`
|
|
88
88
|
<button class="session-menu-btn" ref=${anchor}
|
|
89
89
|
aria-label="Session actions" title="Session actions"
|
|
90
|
-
onClick=${() => setOpen((v) => !v)}>
|
|
91
|
-
<${IconMoreVert} />
|
|
92
|
-
</button>
|
|
93
|
-
${open ? html`
|
|
94
|
-
<${Popover} anchor=${anchor} align="right" width=${200}
|
|
95
|
-
onClose=${() => setOpen(false)}>
|
|
96
|
-
<div class="session-menu">
|
|
97
|
-
<button class="session-menu-item" onClick=${() => { setOpen(false); onOpenEditor(); }}>
|
|
90
|
+
onClick=${() => setOpen((v) => !v)}>
|
|
91
|
+
<${IconMoreVert} />
|
|
92
|
+
</button>
|
|
93
|
+
${open ? html`
|
|
94
|
+
<${Popover} anchor=${anchor} align="right" width=${200}
|
|
95
|
+
onClose=${() => setOpen(false)}>
|
|
96
|
+
<div class="session-menu">
|
|
97
|
+
<button class="session-menu-item" onClick=${() => { setOpen(false); onOpenEditor(); }}>
|
|
98
98
|
<${IconExternal} /> Open in editor
|
|
99
99
|
</button>
|
|
100
100
|
${switchableClis.length ? html`
|
|
@@ -114,8 +114,8 @@ function SessionMenu({ session, switchableClis, onRename, onDelete, onOpenEditor
|
|
|
114
114
|
</button>
|
|
115
115
|
<button class="session-menu-item danger" onClick=${() => { setOpen(false); onDelete(); }}>
|
|
116
116
|
<${IconClose} /> Delete
|
|
117
|
-
</button>
|
|
118
|
-
</div>
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
119
|
</${Popover}>` : null}`;
|
|
120
120
|
}
|
|
121
121
|
|
|
@@ -146,24 +146,23 @@ export function SessionsPage() {
|
|
|
146
146
|
const id = activeSessionId.value;
|
|
147
147
|
const list = sessions.value;
|
|
148
148
|
const session = id ? list.find((s) => s.id === id) : null;
|
|
149
|
-
const runningSessions = list.filter((s) => s.status === 'running');
|
|
150
149
|
const [resumeError, setResumeError] = useState(null);
|
|
151
150
|
const [actionBusy, setActionBusy] = useState(false);
|
|
152
151
|
const [openTerminalIds, setOpenTerminalIds] = useState(() => new Set());
|
|
153
152
|
// Bumps to force the auto-resume effect to re-run on Retry without
|
|
154
153
|
// mutating any signal. Primitive in the dep array → identity changes.
|
|
155
154
|
const [retryNonce, setRetryNonce] = useState(0);
|
|
156
|
-
|
|
157
|
-
// No session selected → bounce to the Launch page. Done in an effect so
|
|
158
|
-
// we don't mutate signals during render. Returning null while the bounce
|
|
159
|
-
// is in flight avoids a flash of empty content.
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
if (!session) selectTab('launch');
|
|
162
|
-
}, [session]);
|
|
163
|
-
|
|
164
|
-
// Auto-resume when the active session is exited. resumeSession() in
|
|
165
|
-
// api.js dedups in-flight calls per session id, so simultaneous fires
|
|
166
|
-
// from here and from Sidebar.onClick collapse into one request.
|
|
155
|
+
|
|
156
|
+
// No session selected → bounce to the Launch page. Done in an effect so
|
|
157
|
+
// we don't mutate signals during render. Returning null while the bounce
|
|
158
|
+
// is in flight avoids a flash of empty content.
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (!session) selectTab('launch');
|
|
161
|
+
}, [session]);
|
|
162
|
+
|
|
163
|
+
// Auto-resume when the active session is exited. resumeSession() in
|
|
164
|
+
// api.js dedups in-flight calls per session id, so simultaneous fires
|
|
165
|
+
// from here and from Sidebar.onClick collapse into one request.
|
|
167
166
|
useEffect(() => {
|
|
168
167
|
if (!session) return;
|
|
169
168
|
if (session.status === 'running') { setResumeError(null); return; }
|
|
@@ -175,39 +174,40 @@ export function SessionsPage() {
|
|
|
175
174
|
}, [session?.id, session?.status, session?.cliId, session?.manualStopped, retryNonce]);
|
|
176
175
|
|
|
177
176
|
useEffect(() => {
|
|
178
|
-
const
|
|
177
|
+
const existingIds = new Set(list.map((s) => s.id));
|
|
179
178
|
setOpenTerminalIds((prev) => {
|
|
180
179
|
const next = new Set();
|
|
181
180
|
let changed = false;
|
|
182
181
|
for (const sid of prev) {
|
|
183
|
-
if (
|
|
182
|
+
if (existingIds.has(sid)) {
|
|
184
183
|
next.add(sid);
|
|
185
184
|
} else {
|
|
186
185
|
changed = true;
|
|
187
186
|
}
|
|
188
187
|
}
|
|
189
|
-
if (session?.
|
|
188
|
+
if (session?.id && existingIds.has(session.id) && !next.has(session.id)) {
|
|
190
189
|
next.add(session.id);
|
|
191
190
|
changed = true;
|
|
192
191
|
}
|
|
193
192
|
return changed || next.size !== prev.size ? next : prev;
|
|
194
193
|
});
|
|
195
|
-
}, [list, session?.id
|
|
194
|
+
}, [list, session?.id]);
|
|
196
195
|
|
|
197
196
|
if (!session) return null;
|
|
198
|
-
|
|
197
|
+
|
|
199
198
|
const cli = (config.value?.clis || []).find((c) => c.id === session.cliId);
|
|
200
199
|
const cliForSession = (s) => (config.value?.clis || []).find((c) => c.id === s.cliId);
|
|
201
200
|
const switchableClis = cli
|
|
202
201
|
? (config.value?.clis || []).filter((c) => c.id !== cli.id && c.type === cli.type)
|
|
203
202
|
: [];
|
|
204
203
|
const running = session.status === 'running';
|
|
205
|
-
const
|
|
204
|
+
const openSessions = Array.from(openTerminalIds)
|
|
206
205
|
.map((sid) => list.find((s) => s.id === sid))
|
|
207
|
-
.filter(
|
|
208
|
-
const
|
|
209
|
-
? [...
|
|
210
|
-
:
|
|
206
|
+
.filter(Boolean);
|
|
207
|
+
const tabSessions = session && !openSessions.some((s) => s.id === session.id)
|
|
208
|
+
? [...openSessions, session]
|
|
209
|
+
: openSessions;
|
|
210
|
+
const terminalSessions = tabSessions.filter((s) => s.status === 'running');
|
|
211
211
|
const title = session.title || session.workspace || session.id.slice(0, 12);
|
|
212
212
|
|
|
213
213
|
const onCloseTab = (sid) => {
|
|
@@ -219,8 +219,8 @@ export function SessionsPage() {
|
|
|
219
219
|
});
|
|
220
220
|
|
|
221
221
|
if (sid !== session.id) return;
|
|
222
|
-
const currentIndex =
|
|
223
|
-
const remaining =
|
|
222
|
+
const currentIndex = tabSessions.findIndex((s) => s.id === sid);
|
|
223
|
+
const remaining = tabSessions.filter((s) => s.id !== sid);
|
|
224
224
|
const replacement = currentIndex >= 0
|
|
225
225
|
? remaining[Math.min(currentIndex, remaining.length - 1)] || remaining[remaining.length - 1]
|
|
226
226
|
: remaining[0];
|
|
@@ -233,14 +233,14 @@ export function SessionsPage() {
|
|
|
233
233
|
};
|
|
234
234
|
|
|
235
235
|
const onReorderTabs = (orderedIds) => {
|
|
236
|
-
const
|
|
236
|
+
const existingIds = new Set(list.map((s) => s.id));
|
|
237
237
|
setOpenTerminalIds((prev) => {
|
|
238
238
|
const nextIds = [];
|
|
239
239
|
for (const sid of orderedIds) {
|
|
240
|
-
if (
|
|
240
|
+
if (existingIds.has(sid) && !nextIds.includes(sid)) nextIds.push(sid);
|
|
241
241
|
}
|
|
242
242
|
for (const sid of prev) {
|
|
243
|
-
if (
|
|
243
|
+
if (existingIds.has(sid) && !nextIds.includes(sid)) nextIds.push(sid);
|
|
244
244
|
}
|
|
245
245
|
return new Set(nextIds);
|
|
246
246
|
});
|
|
@@ -275,21 +275,21 @@ export function SessionsPage() {
|
|
|
275
275
|
setActionBusy(false);
|
|
276
276
|
}
|
|
277
277
|
};
|
|
278
|
-
const onRename = async () => {
|
|
279
|
-
const next = await ccsmPrompt('Rename session', title, { okLabel: 'Save' });
|
|
280
|
-
if (next === null) return;
|
|
281
|
-
try { await setSessionTitle(session.id, next.trim()); }
|
|
282
|
-
catch (e) { setToast(e.message, 'error'); }
|
|
283
|
-
};
|
|
284
|
-
const onDelete = async () => {
|
|
285
|
-
const ok = await ccsmConfirm(`Delete session ${title}? PTY will be killed if alive.`, {
|
|
286
|
-
title: 'Delete session', okLabel: 'Delete', danger: true });
|
|
287
|
-
if (!ok) return;
|
|
288
|
-
try {
|
|
289
|
-
await deleteSession(session.id);
|
|
290
|
-
activeSessionId.value = null;
|
|
291
|
-
} catch (e) { setToast(e.message, 'error'); }
|
|
292
|
-
};
|
|
278
|
+
const onRename = async () => {
|
|
279
|
+
const next = await ccsmPrompt('Rename session', title, { okLabel: 'Save' });
|
|
280
|
+
if (next === null) return;
|
|
281
|
+
try { await setSessionTitle(session.id, next.trim()); }
|
|
282
|
+
catch (e) { setToast(e.message, 'error'); }
|
|
283
|
+
};
|
|
284
|
+
const onDelete = async () => {
|
|
285
|
+
const ok = await ccsmConfirm(`Delete session ${title}? PTY will be killed if alive.`, {
|
|
286
|
+
title: 'Delete session', okLabel: 'Delete', danger: true });
|
|
287
|
+
if (!ok) return;
|
|
288
|
+
try {
|
|
289
|
+
await deleteSession(session.id);
|
|
290
|
+
activeSessionId.value = null;
|
|
291
|
+
} catch (e) { setToast(e.message, 'error'); }
|
|
292
|
+
};
|
|
293
293
|
const onOpenEditor = async () => {
|
|
294
294
|
try {
|
|
295
295
|
const r = await openSessionInEditor(session.id);
|
|
@@ -316,22 +316,15 @@ export function SessionsPage() {
|
|
|
316
316
|
}
|
|
317
317
|
} catch (e) { setToast(e.message, 'error'); }
|
|
318
318
|
};
|
|
319
|
-
|
|
320
|
-
return html`
|
|
321
|
-
<${PageTitleBar} title=${html`
|
|
322
|
-
<span class="session-title-text">${title}</span>
|
|
323
|
-
<span class="session-title-
|
|
324
|
-
<span class="mono">${session.cwd}</span>
|
|
325
|
-
<span>·</span>
|
|
326
|
-
<span>${cli ? cli.name : session.cliId}</span>
|
|
327
|
-
${session.repos.length ? html`<span>·</span><span>${session.repos.join(', ')}</span>` : null}
|
|
328
|
-
<span>·</span>
|
|
329
|
-
<span>${running ? 'running' : (resumeError ? 'resume failed' : (session.manualStopped ? 'stopped' : 'resuming…'))}</span>
|
|
330
|
-
</span>
|
|
319
|
+
|
|
320
|
+
return html`
|
|
321
|
+
<${PageTitleBar} title=${html`
|
|
322
|
+
<span class="session-title-text" title=${title}>${title}</span>
|
|
323
|
+
<span class="session-title-cwd" title=${session.cwd}>${session.cwd}</span>
|
|
331
324
|
`} />
|
|
332
325
|
<${SessionTabs}
|
|
333
326
|
activeId=${session.id}
|
|
334
|
-
openSessions=${
|
|
327
|
+
openSessions=${tabSessions}
|
|
335
328
|
onActivate=${(sid) => selectSession(sid)}
|
|
336
329
|
onClose=${onCloseTab}
|
|
337
330
|
onReorder=${onReorderTabs}
|