@bakapiano/ccsm 0.6.0 → 0.8.3
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 +172 -38
- package/bin/ccsm.js +194 -0
- package/lib/favorites.js +23 -45
- package/lib/jsonStore.js +60 -0
- package/lib/labels.js +21 -41
- package/lib/webTerminal.js +173 -0
- package/package.json +11 -3
- package/public/css/base.css +82 -0
- package/public/css/cards.css +149 -0
- package/public/css/feedback.css +219 -0
- package/public/css/forms.css +282 -0
- package/public/css/layout.css +107 -0
- package/public/css/modal.css +169 -0
- package/public/css/responsive.css +10 -0
- package/public/css/sidebar.css +165 -0
- package/public/css/tables.css +266 -0
- package/public/css/terminals.css +112 -0
- package/public/css/tokens.css +63 -0
- package/public/css/wco.css +70 -0
- package/public/css/widgets.css +204 -0
- package/public/favicon.svg +1 -1
- package/public/index.html +52 -490
- package/public/js/actions.js +87 -0
- package/public/js/api.js +103 -0
- package/public/js/backend.js +28 -0
- package/public/js/components/App.js +45 -0
- package/public/js/components/Card.js +24 -0
- package/public/js/components/DialogHost.js +45 -0
- package/public/js/components/Fab.js +11 -0
- package/public/js/components/FavoritesTable.js +81 -0
- package/public/js/components/Footer.js +12 -0
- package/public/js/components/NewSessionModal.js +142 -0
- package/public/js/components/OfflineBanner.js +52 -0
- package/public/js/components/PageHead.js +33 -0
- package/public/js/components/Pagination.js +27 -0
- package/public/js/components/ProgressList.js +32 -0
- package/public/js/components/RecentTable.js +68 -0
- package/public/js/components/RepoPicker.js +40 -0
- package/public/js/components/ReposEditor.js +74 -0
- package/public/js/components/ServerStatus.js +18 -0
- package/public/js/components/SessionsTable.js +71 -0
- package/public/js/components/Sidebar.js +52 -0
- package/public/js/components/SnapshotPanel.js +77 -0
- package/public/js/components/TerminalView.js +108 -0
- package/public/js/components/TitleCell.js +40 -0
- package/public/js/components/Toast.js +8 -0
- package/public/js/components/WorkspacePicker.js +19 -0
- package/public/js/components/WorkspacesGrid.js +41 -0
- package/public/js/dialog.js +59 -0
- package/public/js/html.js +6 -0
- package/public/js/icons.js +114 -0
- package/public/js/main.js +81 -0
- package/public/js/pages/AboutPage.js +85 -0
- package/public/js/pages/ConfigurePage.js +194 -0
- package/public/js/pages/LaunchPage.js +117 -0
- package/public/js/pages/SessionsPage.js +47 -0
- package/public/js/pages/TerminalsPage.js +74 -0
- package/public/js/state.js +87 -0
- package/public/js/streaming.js +96 -0
- package/public/js/toast.js +14 -0
- package/public/js/util.js +24 -0
- package/public/manifest.webmanifest +14 -0
- package/scripts/install.js +111 -0
- package/scripts/uninstall.js +56 -0
- package/server.js +286 -30
- package/public/app.js +0 -1353
- package/public/styles.css +0 -1639
package/public/index.html
CHANGED
|
@@ -3,504 +3,66 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<!-- Bleeds the cream surface into the Edge/Chrome --app= title bar
|
|
7
|
+
so it visually disappears against the body. The browser does
|
|
8
|
+
honor this in standalone app windows. -->
|
|
9
|
+
<meta name="theme-color" content="#faf9f5" />
|
|
10
|
+
<meta name="color-scheme" content="light" />
|
|
6
11
|
<title>CCSM — Claude CLI Sessions Manager</title>
|
|
7
|
-
|
|
12
|
+
<!-- All asset paths are RELATIVE so the same index.html works when
|
|
13
|
+
served from localhost:7777/ (backend bundle) AND from
|
|
14
|
+
https://bakapiano.github.io/cssm/v1/ (GH Pages hosted). -->
|
|
15
|
+
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
|
|
16
|
+
<link rel="manifest" href="./manifest.webmanifest" />
|
|
8
17
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
18
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
19
|
<link
|
|
11
20
|
rel="stylesheet"
|
|
12
21
|
href="https://fonts.googleapis.com/css2?family=Geist:wght@300..700&family=JetBrains+Mono:wght@400..600&display=swap"
|
|
13
22
|
/>
|
|
14
|
-
<link rel="stylesheet" href="/
|
|
23
|
+
<link rel="stylesheet" href="./css/tokens.css" />
|
|
24
|
+
<link rel="stylesheet" href="./css/base.css" />
|
|
25
|
+
<link rel="stylesheet" href="./css/layout.css" />
|
|
26
|
+
<link rel="stylesheet" href="./css/sidebar.css" />
|
|
27
|
+
<link rel="stylesheet" href="./css/cards.css" />
|
|
28
|
+
<link rel="stylesheet" href="./css/tables.css" />
|
|
29
|
+
<link rel="stylesheet" href="./css/forms.css" />
|
|
30
|
+
<link rel="stylesheet" href="./css/widgets.css" />
|
|
31
|
+
<link rel="stylesheet" href="./css/feedback.css" />
|
|
32
|
+
<link rel="stylesheet" href="./css/modal.css" />
|
|
33
|
+
<link rel="stylesheet" href="./css/terminals.css" />
|
|
34
|
+
<link rel="stylesheet" href="./css/wco.css" />
|
|
35
|
+
<link rel="stylesheet" href="./css/responsive.css" />
|
|
36
|
+
|
|
37
|
+
<script type="importmap">
|
|
38
|
+
{
|
|
39
|
+
"imports": {
|
|
40
|
+
"preact": "https://esm.sh/preact@10.27.0",
|
|
41
|
+
"preact/hooks": "https://esm.sh/preact@10.27.0/hooks",
|
|
42
|
+
"@preact/signals": "https://esm.sh/@preact/signals@1.3.2?deps=preact@10.27.0",
|
|
43
|
+
"htm": "https://esm.sh/htm@3.1.1",
|
|
44
|
+
"@xterm/xterm": "https://esm.sh/@xterm/xterm@5.5.0",
|
|
45
|
+
"@xterm/addon-fit": "https://esm.sh/@xterm/addon-fit@0.10.0?deps=@xterm/xterm@5.5.0",
|
|
46
|
+
"@xterm/addon-web-links": "https://esm.sh/@xterm/addon-web-links@0.11.0?deps=@xterm/xterm@5.5.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
<link rel="stylesheet" href="https://esm.sh/@xterm/xterm@5.5.0/css/xterm.css" />
|
|
15
51
|
</head>
|
|
16
52
|
<body>
|
|
17
|
-
<div
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
dominant-baseline="central"
|
|
32
|
-
font-family="'JetBrains Mono', 'Cascadia Mono', 'Consolas', monospace"
|
|
33
|
-
font-weight="700"
|
|
34
|
-
font-size="10"
|
|
35
|
-
fill="#faf9f5">ccsm</text>
|
|
36
|
-
</svg>
|
|
37
|
-
</span>
|
|
38
|
-
<span class="brand-name">CCSM<span class="brand-dot">.</span></span>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<nav class="sidebar-nav" role="tablist" aria-label="Sections">
|
|
42
|
-
<button class="nav-item" data-tab="sessions" role="tab" aria-selected="true">
|
|
43
|
-
<span class="nav-icon" aria-hidden="true">
|
|
44
|
-
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
45
|
-
<line x1="3" y1="6" x2="21" y2="6"/>
|
|
46
|
-
<line x1="3" y1="12" x2="21" y2="12"/>
|
|
47
|
-
<line x1="3" y1="18" x2="14" y2="18"/>
|
|
48
|
-
</svg>
|
|
49
|
-
</span>
|
|
50
|
-
<span class="nav-label">Sessions</span>
|
|
51
|
-
<span class="nav-badge" id="navCount-sessions">0</span>
|
|
52
|
-
</button>
|
|
53
|
-
<button class="nav-item" data-tab="launch" role="tab" aria-selected="false">
|
|
54
|
-
<span class="nav-icon" aria-hidden="true">
|
|
55
|
-
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
56
|
-
<path d="M7 17L17 7"/>
|
|
57
|
-
<path d="M9 7h8v8"/>
|
|
58
|
-
</svg>
|
|
59
|
-
</span>
|
|
60
|
-
<span class="nav-label">Launch</span>
|
|
61
|
-
</button>
|
|
62
|
-
<button class="nav-item" data-tab="configure" role="tab" aria-selected="false">
|
|
63
|
-
<span class="nav-icon" aria-hidden="true">
|
|
64
|
-
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
65
|
-
<circle cx="12" cy="12" r="3"/>
|
|
66
|
-
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h0a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51h0a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v0a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
|
67
|
-
</svg>
|
|
68
|
-
</span>
|
|
69
|
-
<span class="nav-label">Configure</span>
|
|
70
|
-
</button>
|
|
71
|
-
</nav>
|
|
72
|
-
|
|
73
|
-
<div class="sidebar-divider"></div>
|
|
74
|
-
|
|
75
|
-
<div class="sidebar-utility">
|
|
76
|
-
<button class="util-item" id="refreshBtn" title="refresh">
|
|
77
|
-
<span class="nav-icon" aria-hidden="true">
|
|
78
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
79
|
-
<polyline points="23 4 23 10 17 10"/>
|
|
80
|
-
<polyline points="1 20 1 14 7 14"/>
|
|
81
|
-
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
|
|
82
|
-
</svg>
|
|
83
|
-
</span>
|
|
84
|
-
<span class="nav-label">Refresh</span>
|
|
85
|
-
</button>
|
|
86
|
-
</div>
|
|
87
|
-
|
|
88
|
-
<div class="sidebar-foot">
|
|
89
|
-
<button class="collapse-toggle" id="collapseBtn" aria-label="collapse sidebar">
|
|
90
|
-
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
91
|
-
<polyline points="15 18 9 12 15 6"/>
|
|
92
|
-
</svg>
|
|
93
|
-
</button>
|
|
94
|
-
</div>
|
|
95
|
-
</aside>
|
|
96
|
-
|
|
97
|
-
<!-- ─────────── Main ─────────── -->
|
|
98
|
-
<main class="main">
|
|
99
|
-
<header class="page-head">
|
|
100
|
-
<div class="page-head-inner">
|
|
101
|
-
<h1 class="page-title" id="pageTitle">Sessions</h1>
|
|
102
|
-
<p class="page-subtitle" id="pageSubtitle">Live and recently-closed Claude Code sessions on this machine.</p>
|
|
103
|
-
</div>
|
|
104
|
-
<div class="page-head-meta">
|
|
105
|
-
<span class="server-status" id="serverStatus" data-state="connecting" title="backend status">
|
|
106
|
-
<span class="status-pulse" aria-hidden="true"></span>
|
|
107
|
-
<span class="server-status-label" id="serverStatusLabel">connecting…</span>
|
|
108
|
-
</span>
|
|
109
|
-
<span class="ph-divider">·</span>
|
|
110
|
-
<span class="ph-stat"><span class="ph-key">Port</span> <span class="ph-val" id="hdPort">—</span></span>
|
|
111
|
-
<span class="ph-divider">·</span>
|
|
112
|
-
<span class="ph-stat"><span class="ph-key">Terminal</span> <span class="ph-val" id="hdTerminal">—</span></span>
|
|
113
|
-
<span class="ph-divider">·</span>
|
|
114
|
-
<span class="ph-stat"><span class="ph-key">Now</span> <span class="ph-val" id="hdTime">—</span></span>
|
|
115
|
-
</div>
|
|
116
|
-
</header>
|
|
117
|
-
|
|
118
|
-
<div class="content">
|
|
119
|
-
<!-- ─── Sessions tab ─── -->
|
|
120
|
-
<section class="tab-panel" data-panel="sessions" data-active>
|
|
121
|
-
<div class="page-actions">
|
|
122
|
-
<span class="page-actions-hint">Looking through your past conversations?</span>
|
|
123
|
-
<button class="action primary" id="finderInlineBtn" title="open a Claude session with context on the ccsm data dir">
|
|
124
|
-
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
125
|
-
<circle cx="11" cy="11" r="7"/>
|
|
126
|
-
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
|
127
|
-
</svg>
|
|
128
|
-
Ask Claude to find a session
|
|
129
|
-
</button>
|
|
130
|
-
</div>
|
|
131
|
-
|
|
132
|
-
<article class="card" id="favoritesCard" data-fold-key="favorites">
|
|
133
|
-
<header class="card-head">
|
|
134
|
-
<button class="card-fold" data-fold aria-label="collapse">
|
|
135
|
-
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
|
|
136
|
-
</button>
|
|
137
|
-
<div class="card-titles">
|
|
138
|
-
<h2 class="card-title">
|
|
139
|
-
Favorites
|
|
140
|
-
<svg class="title-icon title-icon-after" viewBox="0 0 24 24" width="14" height="14" fill="currentColor" stroke="none" aria-hidden="true">
|
|
141
|
-
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
|
|
142
|
-
</svg>
|
|
143
|
-
</h2>
|
|
144
|
-
<p class="card-meta" id="favoritesMeta">click ☆ on any row to pin sessions here</p>
|
|
145
|
-
</div>
|
|
146
|
-
</header>
|
|
147
|
-
<div class="card-body card-body-flush">
|
|
148
|
-
<div class="table-scroll">
|
|
149
|
-
<table class="data" id="favoritesTable">
|
|
150
|
-
<thead>
|
|
151
|
-
<tr>
|
|
152
|
-
<th>Title</th>
|
|
153
|
-
<th>Working directory</th>
|
|
154
|
-
<th>Branch</th>
|
|
155
|
-
<th class="num">Pinned</th>
|
|
156
|
-
<th class="col-actions"></th>
|
|
157
|
-
</tr>
|
|
158
|
-
</thead>
|
|
159
|
-
<tbody></tbody>
|
|
160
|
-
</table>
|
|
161
|
-
</div>
|
|
162
|
-
<div class="empty" id="favoritesEmpty">No favorites yet. Star a session row to pin it here.</div>
|
|
163
|
-
<footer class="pagination" id="favoritesPagination" hidden>
|
|
164
|
-
<button class="action subtle small" id="favPrevBtn" disabled>← Prev</button>
|
|
165
|
-
<span class="pagination-info">Page <strong id="favPageNum">1</strong> of <strong id="favPageTotal">1</strong> · <span id="favTotal">0</span> total</span>
|
|
166
|
-
<button class="action subtle small" id="favNextBtn" disabled>Next →</button>
|
|
167
|
-
<select id="favPageSize" class="input" style="max-width: 100px;">
|
|
168
|
-
<option value="10" selected>10 / page</option>
|
|
169
|
-
<option value="20">20 / page</option>
|
|
170
|
-
<option value="50">50 / page</option>
|
|
171
|
-
</select>
|
|
172
|
-
</footer>
|
|
173
|
-
</div>
|
|
174
|
-
</article>
|
|
175
|
-
|
|
176
|
-
<article class="card" data-fold-key="sessions">
|
|
177
|
-
<header class="card-head">
|
|
178
|
-
<button class="card-fold" data-fold aria-label="collapse">
|
|
179
|
-
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
|
|
180
|
-
</button>
|
|
181
|
-
<div class="card-titles">
|
|
182
|
-
<h2 class="card-title">Live sessions</h2>
|
|
183
|
-
<p class="card-meta" id="sessionsMeta">awaiting…</p>
|
|
184
|
-
</div>
|
|
185
|
-
</header>
|
|
186
|
-
<div class="card-body card-body-flush">
|
|
187
|
-
<div class="table-scroll">
|
|
188
|
-
<table class="data" id="sessionsTable">
|
|
189
|
-
<thead>
|
|
190
|
-
<tr>
|
|
191
|
-
<th class="col-mark"></th>
|
|
192
|
-
<th>Title</th>
|
|
193
|
-
<th>Working directory</th>
|
|
194
|
-
<th class="num">Updated</th>
|
|
195
|
-
<th class="num">Started</th>
|
|
196
|
-
<th class="num">PID</th>
|
|
197
|
-
<th class="col-actions"></th>
|
|
198
|
-
</tr>
|
|
199
|
-
</thead>
|
|
200
|
-
<tbody></tbody>
|
|
201
|
-
</table>
|
|
202
|
-
</div>
|
|
203
|
-
<div class="empty" id="sessionsEmpty" hidden>No live sessions detected.</div>
|
|
204
|
-
<footer class="pagination" id="sessionsPagination" hidden>
|
|
205
|
-
<button class="action subtle small" id="sessPrevBtn" disabled>← Prev</button>
|
|
206
|
-
<span class="pagination-info">Page <strong id="sessPageNum">1</strong> of <strong id="sessPageTotal">1</strong> · <span id="sessTotal">0</span> total</span>
|
|
207
|
-
<button class="action subtle small" id="sessNextBtn" disabled>Next →</button>
|
|
208
|
-
<select id="sessPageSize" class="input" style="max-width: 100px;">
|
|
209
|
-
<option value="10" selected>10 / page</option>
|
|
210
|
-
<option value="20">20 / page</option>
|
|
211
|
-
<option value="50">50 / page</option>
|
|
212
|
-
</select>
|
|
213
|
-
</footer>
|
|
214
|
-
</div>
|
|
215
|
-
</article>
|
|
216
|
-
|
|
217
|
-
<article class="card" data-fold-key="recent">
|
|
218
|
-
<header class="card-head">
|
|
219
|
-
<button class="card-fold" data-fold aria-label="collapse">
|
|
220
|
-
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
|
|
221
|
-
</button>
|
|
222
|
-
<div class="card-titles">
|
|
223
|
-
<h2 class="card-title">Recently closed</h2>
|
|
224
|
-
<p class="card-meta" id="recentMeta">…</p>
|
|
225
|
-
</div>
|
|
226
|
-
</header>
|
|
227
|
-
<div class="card-body card-body-flush">
|
|
228
|
-
<div class="table-scroll">
|
|
229
|
-
<table class="data" id="recentTable">
|
|
230
|
-
<thead>
|
|
231
|
-
<tr>
|
|
232
|
-
<th>Title</th>
|
|
233
|
-
<th>Working directory</th>
|
|
234
|
-
<th>Branch</th>
|
|
235
|
-
<th class="num">Last activity</th>
|
|
236
|
-
<th class="num">Started</th>
|
|
237
|
-
<th class="col-actions"></th>
|
|
238
|
-
</tr>
|
|
239
|
-
</thead>
|
|
240
|
-
<tbody></tbody>
|
|
241
|
-
</table>
|
|
242
|
-
</div>
|
|
243
|
-
<div class="empty" id="recentEmpty" hidden>Nothing in <code>~/.claude/projects/</code>.</div>
|
|
244
|
-
<footer class="pagination" id="recentPagination" hidden>
|
|
245
|
-
<button class="action subtle small" id="recentPrevBtn" disabled>← Prev</button>
|
|
246
|
-
<span class="pagination-info">Page <strong id="recentPageNum">1</strong> of <strong id="recentPageTotal">1</strong> · <span id="recentTotal">0</span> total</span>
|
|
247
|
-
<button class="action subtle small" id="recentNextBtn" disabled>Next →</button>
|
|
248
|
-
<select id="recentPageSize" class="input" style="max-width: 100px;">
|
|
249
|
-
<option value="10" selected>10 / page</option>
|
|
250
|
-
<option value="20">20 / page</option>
|
|
251
|
-
<option value="50">50 / page</option>
|
|
252
|
-
</select>
|
|
253
|
-
</footer>
|
|
254
|
-
</div>
|
|
255
|
-
</article>
|
|
256
|
-
</section>
|
|
257
|
-
|
|
258
|
-
<!-- ─── Launch tab ─── -->
|
|
259
|
-
<section class="tab-panel" data-panel="launch">
|
|
260
|
-
<article class="card">
|
|
261
|
-
<header class="card-head">
|
|
262
|
-
<div class="card-titles">
|
|
263
|
-
<h2 class="card-title">New session</h2>
|
|
264
|
-
<p class="card-meta">Picks an unused workspace, clones missing repos, opens <code>claude</code> in a fresh terminal.</p>
|
|
265
|
-
</div>
|
|
266
|
-
</header>
|
|
267
|
-
<div class="card-body">
|
|
268
|
-
<div class="form-row">
|
|
269
|
-
<span class="form-label">Repos</span>
|
|
270
|
-
<div class="chip-row" id="repoPicker">
|
|
271
|
-
<span class="muted-text">no repos configured · add some in <strong>Configure</strong></span>
|
|
272
|
-
</div>
|
|
273
|
-
</div>
|
|
274
|
-
<div class="form-row">
|
|
275
|
-
<label class="form-label" for="workspaceSelect">Workspace</label>
|
|
276
|
-
<select id="workspaceSelect" class="input narrow">
|
|
277
|
-
<option value="">auto — find or create unused</option>
|
|
278
|
-
</select>
|
|
279
|
-
<button class="action primary" id="newSessionBtn">Launch new session</button>
|
|
280
|
-
</div>
|
|
281
|
-
<div id="newSessionProgress" class="progress-list"></div>
|
|
282
|
-
<div class="post-result" id="newSessionResult"></div>
|
|
283
|
-
</div>
|
|
284
|
-
</article>
|
|
285
|
-
|
|
286
|
-
<article class="card">
|
|
287
|
-
<header class="card-head">
|
|
288
|
-
<div class="card-titles">
|
|
289
|
-
<h2 class="card-title">Snapshot & restore</h2>
|
|
290
|
-
<p class="card-meta" id="snapshotMeta">…</p>
|
|
291
|
-
</div>
|
|
292
|
-
</header>
|
|
293
|
-
<div class="card-body">
|
|
294
|
-
<div class="row gap-row">
|
|
295
|
-
<button class="action" id="snapshotSaveBtn">Save snapshot now</button>
|
|
296
|
-
<button class="action primary" id="snapshotRestoreBtn">Restore latest</button>
|
|
297
|
-
<span class="divider-dot">·</span>
|
|
298
|
-
<select id="historySelect" class="input narrow">
|
|
299
|
-
<option value="">history…</option>
|
|
300
|
-
</select>
|
|
301
|
-
<button class="action" id="historyRestoreBtn">Restore selected</button>
|
|
302
|
-
</div>
|
|
303
|
-
<details class="snapshot-detail">
|
|
304
|
-
<summary>View snapshot contents</summary>
|
|
305
|
-
<pre id="snapshotPreview" class="preview"></pre>
|
|
306
|
-
</details>
|
|
307
|
-
</div>
|
|
308
|
-
</article>
|
|
309
|
-
|
|
310
|
-
<article class="card">
|
|
311
|
-
<header class="card-head">
|
|
312
|
-
<div class="card-titles">
|
|
313
|
-
<h2 class="card-title">Workspaces on disk</h2>
|
|
314
|
-
<p class="card-meta">Under <code id="workDirDisplay">…</code></p>
|
|
315
|
-
</div>
|
|
316
|
-
</header>
|
|
317
|
-
<div class="card-body">
|
|
318
|
-
<div id="workspaceList" class="workspace-grid"></div>
|
|
319
|
-
</div>
|
|
320
|
-
</article>
|
|
321
|
-
</section>
|
|
322
|
-
|
|
323
|
-
<!-- ─── Configure tab ─── -->
|
|
324
|
-
<section class="tab-panel" data-panel="configure">
|
|
325
|
-
<div class="dirty-banner" id="configDirtyBanner" hidden>
|
|
326
|
-
<span class="dirty-dot"></span>
|
|
327
|
-
<span class="dirty-text">You have unsaved changes</span>
|
|
328
|
-
<button class="action small primary" id="dirtyBannerSaveBtn">Save now</button>
|
|
329
|
-
<button class="action small subtle" id="dirtyBannerDiscardBtn">Discard</button>
|
|
330
|
-
</div>
|
|
331
|
-
<article class="card">
|
|
332
|
-
<header class="card-head">
|
|
333
|
-
<div class="card-titles">
|
|
334
|
-
<h2 class="card-title">Settings</h2>
|
|
335
|
-
<p class="card-meta">Persisted to <code>~/.ccsm/config.json</code></p>
|
|
336
|
-
</div>
|
|
337
|
-
</header>
|
|
338
|
-
<div class="card-body">
|
|
339
|
-
<div class="config-grid">
|
|
340
|
-
<label class="field">
|
|
341
|
-
<span class="label">Port</span>
|
|
342
|
-
<input id="cfgPort" type="number" />
|
|
343
|
-
<span class="hint">restart server to apply</span>
|
|
344
|
-
</label>
|
|
345
|
-
<label class="field">
|
|
346
|
-
<span class="label">Work directory</span>
|
|
347
|
-
<input id="cfgWorkDir" type="text" />
|
|
348
|
-
</label>
|
|
349
|
-
<label class="field">
|
|
350
|
-
<span class="label">Snapshot interval (ms)</span>
|
|
351
|
-
<input id="cfgInterval" type="number" min="5000" />
|
|
352
|
-
</label>
|
|
353
|
-
<label class="field">
|
|
354
|
-
<span class="label">History kept</span>
|
|
355
|
-
<input id="cfgKeep" type="number" min="1" />
|
|
356
|
-
</label>
|
|
357
|
-
<label class="field">
|
|
358
|
-
<span class="label">Claude command</span>
|
|
359
|
-
<input id="cfgClaudeCommand" type="text" placeholder="claude" />
|
|
360
|
-
<span class="hint">alias / function / exe name</span>
|
|
361
|
-
</label>
|
|
362
|
-
<label class="field">
|
|
363
|
-
<span class="label">Terminal</span>
|
|
364
|
-
<select id="cfgTerminal" class="input"></select>
|
|
365
|
-
</label>
|
|
366
|
-
<label class="field">
|
|
367
|
-
<span class="label">Command shell <span class="hint inline">(wt only)</span></span>
|
|
368
|
-
<select id="cfgCommandShell" class="input">
|
|
369
|
-
<option value="pwsh">pwsh · PowerShell 7</option>
|
|
370
|
-
<option value="powershell">powershell · Windows PowerShell 5.1</option>
|
|
371
|
-
<option value="none">none · run command directly</option>
|
|
372
|
-
</select>
|
|
373
|
-
</label>
|
|
374
|
-
<label class="field">
|
|
375
|
-
<span class="label">Browser open mode</span>
|
|
376
|
-
<select id="cfgBrowserMode" class="input">
|
|
377
|
-
<option value="app">app · Edge/Chrome chromeless</option>
|
|
378
|
-
<option value="tab">tab · default browser</option>
|
|
379
|
-
<option value="none">off · don't open</option>
|
|
380
|
-
</select>
|
|
381
|
-
</label>
|
|
382
|
-
<label class="field toggle">
|
|
383
|
-
<input id="cfgAutoFocus" type="checkbox" />
|
|
384
|
-
<span class="toggle-text">
|
|
385
|
-
<span class="label">Auto-focus on launch</span>
|
|
386
|
-
<span class="hint">raise newly-launched terminal window</span>
|
|
387
|
-
</span>
|
|
388
|
-
</label>
|
|
389
|
-
<label class="field toggle">
|
|
390
|
-
<input id="cfgFocusCenter" type="checkbox" />
|
|
391
|
-
<span class="toggle-text">
|
|
392
|
-
<span class="label">Move focused window to screen center</span>
|
|
393
|
-
<span class="hint">centers the focused window on whichever monitor the cursor is on</span>
|
|
394
|
-
</span>
|
|
395
|
-
</label>
|
|
396
|
-
<label class="field full">
|
|
397
|
-
<span class="label">Finder prompt</span>
|
|
398
|
-
<textarea id="cfgFinderPrompt" rows="3"></textarea>
|
|
399
|
-
<span class="hint">passed as initial prompt to the finder session</span>
|
|
400
|
-
</label>
|
|
401
|
-
|
|
402
|
-
<div class="field full">
|
|
403
|
-
<div class="repos-head">
|
|
404
|
-
<span class="label">Repositories</span>
|
|
405
|
-
<button class="action small" id="addRepoBtn">+ Add repo</button>
|
|
406
|
-
</div>
|
|
407
|
-
<table class="data repos-table" id="reposTable">
|
|
408
|
-
<thead>
|
|
409
|
-
<tr>
|
|
410
|
-
<th>Name</th>
|
|
411
|
-
<th>URL</th>
|
|
412
|
-
<th class="num">Default</th>
|
|
413
|
-
<th class="col-actions"></th>
|
|
414
|
-
</tr>
|
|
415
|
-
</thead>
|
|
416
|
-
<tbody></tbody>
|
|
417
|
-
</table>
|
|
418
|
-
</div>
|
|
419
|
-
|
|
420
|
-
<div class="form-actions full">
|
|
421
|
-
<button class="action primary" id="saveConfigBtn">Save configuration</button>
|
|
422
|
-
<span class="muted-text" id="configSavedAt"></span>
|
|
423
|
-
</div>
|
|
424
|
-
</div>
|
|
425
|
-
</div>
|
|
426
|
-
</article>
|
|
427
|
-
</section>
|
|
428
|
-
</div>
|
|
429
|
-
|
|
430
|
-
<footer class="footer-status">
|
|
431
|
-
<span class="fs-key">Data</span> <span class="fs-val" id="footData">—</span>
|
|
432
|
-
<span class="fs-divider">·</span>
|
|
433
|
-
<span class="fs-key">Workspaces</span> <span class="fs-val" id="footWorkDir">—</span>
|
|
434
|
-
</footer>
|
|
435
|
-
</main>
|
|
436
|
-
</div>
|
|
437
|
-
|
|
438
|
-
<!-- Floating "new session" action button — always visible bottom-right -->
|
|
439
|
-
<button class="fab" id="newSessionFab" title="Launch new session" aria-label="Launch new session">
|
|
440
|
-
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
441
|
-
<line x1="12" y1="5" x2="12" y2="19"/>
|
|
442
|
-
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
443
|
-
</svg>
|
|
444
|
-
</button>
|
|
445
|
-
|
|
446
|
-
<!-- New-session modal -->
|
|
447
|
-
<div class="modal-backdrop" id="newSessionModal" hidden role="dialog" aria-modal="true" aria-labelledby="modalTitle">
|
|
448
|
-
<div class="modal">
|
|
449
|
-
<header class="modal-head">
|
|
450
|
-
<h2 id="modalTitle">Launch new session</h2>
|
|
451
|
-
<button class="modal-close" id="modalCloseBtn" aria-label="close">
|
|
452
|
-
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
453
|
-
</button>
|
|
454
|
-
</header>
|
|
455
|
-
<div class="modal-body">
|
|
456
|
-
<p class="modal-hint">Pick an unused workspace, clone any missing repos, open <code>claude</code> in a fresh terminal.</p>
|
|
457
|
-
|
|
458
|
-
<div class="form-row">
|
|
459
|
-
<span class="form-label">Repos</span>
|
|
460
|
-
<div class="chip-row" id="modalRepoPicker"></div>
|
|
461
|
-
</div>
|
|
462
|
-
|
|
463
|
-
<details class="repos-inline-config">
|
|
464
|
-
<summary>Manage repos</summary>
|
|
465
|
-
<div class="repos-inline-body">
|
|
466
|
-
<table class="data repos-table" id="modalReposTable">
|
|
467
|
-
<thead>
|
|
468
|
-
<tr>
|
|
469
|
-
<th>Name</th>
|
|
470
|
-
<th>URL</th>
|
|
471
|
-
<th class="num">Default</th>
|
|
472
|
-
<th class="col-actions"></th>
|
|
473
|
-
</tr>
|
|
474
|
-
</thead>
|
|
475
|
-
<tbody></tbody>
|
|
476
|
-
</table>
|
|
477
|
-
<div class="repos-inline-actions">
|
|
478
|
-
<button class="action small" id="modalAddRepoBtn">+ Add repo</button>
|
|
479
|
-
<button class="action small primary" id="modalSaveReposBtn">Save changes</button>
|
|
480
|
-
<span class="muted-text" id="modalReposSavedAt"></span>
|
|
481
|
-
</div>
|
|
482
|
-
</div>
|
|
483
|
-
</details>
|
|
484
|
-
|
|
485
|
-
<div class="form-row">
|
|
486
|
-
<label class="form-label" for="modalWorkspaceSelect">Workspace</label>
|
|
487
|
-
<select id="modalWorkspaceSelect" class="input narrow">
|
|
488
|
-
<option value="">auto — find or create unused</option>
|
|
489
|
-
</select>
|
|
490
|
-
</div>
|
|
491
|
-
|
|
492
|
-
<div id="modalProgress" class="progress-list"></div>
|
|
493
|
-
<div class="post-result" id="modalResult"></div>
|
|
494
|
-
</div>
|
|
495
|
-
<footer class="modal-foot">
|
|
496
|
-
<button class="action" id="modalCancelBtn">Cancel</button>
|
|
497
|
-
<button class="action primary" id="modalLaunchBtn">Launch new session</button>
|
|
498
|
-
</footer>
|
|
499
|
-
</div>
|
|
500
|
-
</div>
|
|
501
|
-
|
|
502
|
-
<div id="toast" class="toast" role="status" aria-live="polite"></div>
|
|
503
|
-
|
|
504
|
-
<script src="/app.js" defer></script>
|
|
53
|
+
<div id="app"></div>
|
|
54
|
+
<script type="module" src="./js/main.js"></script>
|
|
55
|
+
<script>
|
|
56
|
+
// Dev hot-reload — only active when the page itself loads from a
|
|
57
|
+
// local backend (the /api/dev/ping endpoint exists only on a dev
|
|
58
|
+
// checkout). On the GH Pages copy this skips entirely.
|
|
59
|
+
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
|
|
60
|
+
fetch('/api/dev/ping', { cache: 'no-store' }).then((r) => {
|
|
61
|
+
if (!r.ok) return;
|
|
62
|
+
const es = new EventSource('/api/dev/reload');
|
|
63
|
+
es.addEventListener('reload', () => location.reload());
|
|
64
|
+
}).catch(() => {});
|
|
65
|
+
}
|
|
66
|
+
</script>
|
|
505
67
|
</body>
|
|
506
68
|
</html>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Mutation actions shared by SessionsPage, FavoritesTable etc. — each
|
|
2
|
+
// optimistically updates the relevant signal and rolls back on error.
|
|
3
|
+
|
|
4
|
+
import { favorites, labels, sessions, recent } from './state.js';
|
|
5
|
+
import { api, loadSessions, loadRecent } from './api.js';
|
|
6
|
+
import { setToast } from './toast.js';
|
|
7
|
+
import { ccsmPrompt } from './dialog.js';
|
|
8
|
+
|
|
9
|
+
export async function renameSession(sessionId, currentLabel) {
|
|
10
|
+
const next = await ccsmPrompt('Rename session', currentLabel || '', {
|
|
11
|
+
title: 'Rename session',
|
|
12
|
+
placeholder: 'leave empty to clear the label',
|
|
13
|
+
okLabel: 'Save',
|
|
14
|
+
});
|
|
15
|
+
if (next === null) return;
|
|
16
|
+
const trimmed = next.trim();
|
|
17
|
+
const prev = labels.value[sessionId];
|
|
18
|
+
const nextLabels = { ...labels.value };
|
|
19
|
+
if (trimmed) nextLabels[sessionId] = trimmed;
|
|
20
|
+
else delete nextLabels[sessionId];
|
|
21
|
+
labels.value = nextLabels;
|
|
22
|
+
try {
|
|
23
|
+
if (trimmed) {
|
|
24
|
+
await api('PUT', `/api/labels/${sessionId}`, { label: trimmed });
|
|
25
|
+
setToast(`renamed · ${sessionId.slice(0, 8)}`);
|
|
26
|
+
} else {
|
|
27
|
+
await api('DELETE', `/api/labels/${sessionId}`);
|
|
28
|
+
setToast(`cleared label · ${sessionId.slice(0, 8)}`);
|
|
29
|
+
}
|
|
30
|
+
} catch (e) {
|
|
31
|
+
const rollback = { ...labels.value };
|
|
32
|
+
if (prev !== undefined) rollback[sessionId] = prev;
|
|
33
|
+
else delete rollback[sessionId];
|
|
34
|
+
labels.value = rollback;
|
|
35
|
+
setToast('rename failed: ' + e.message, 'error');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// snapshotData: { cwd, title, gitBranch } — captured from the source row so
|
|
40
|
+
// the favorite stays renderable after the session leaves live/recent.
|
|
41
|
+
export async function toggleFavorite(sessionId, snapshotData = {}) {
|
|
42
|
+
const wasFav = !!favorites.value[sessionId];
|
|
43
|
+
if (wasFav) {
|
|
44
|
+
const next = { ...favorites.value };
|
|
45
|
+
delete next[sessionId];
|
|
46
|
+
favorites.value = next;
|
|
47
|
+
try { await api('DELETE', `/api/favorites/${sessionId}`); }
|
|
48
|
+
catch (e) { setToast('unfavorite failed: ' + e.message, 'error'); }
|
|
49
|
+
} else {
|
|
50
|
+
const { cwd = '', title = '', gitBranch = '' } = snapshotData;
|
|
51
|
+
favorites.value = {
|
|
52
|
+
...favorites.value,
|
|
53
|
+
[sessionId]: { sessionId, cwd, title, gitBranch, addedAt: Date.now() },
|
|
54
|
+
};
|
|
55
|
+
try { await api('POST', `/api/favorites/${sessionId}`, { cwd, title, gitBranch }); }
|
|
56
|
+
catch (e) { setToast('favorite failed: ' + e.message, 'error'); }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function focusSession(sessionId) {
|
|
61
|
+
try {
|
|
62
|
+
const r = await api('POST', `/api/sessions/${sessionId}/focus`);
|
|
63
|
+
if (r.ok && r.activated) setToast(`focused · ${r.windowTitle || sessionId.slice(0, 8)}`);
|
|
64
|
+
else if (r.ok) setToast(`window found, focus blocked (${r.windowProcess})`, 'error');
|
|
65
|
+
else setToast(`no window for pid · ${(r.chain || []).map((c) => c.name).join('→')}`, 'error');
|
|
66
|
+
} catch (e) { setToast(e.message, 'error'); }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function resumeSession(sessionId, cwd, { kind = 'resume' } = {}) {
|
|
70
|
+
if (!cwd) return setToast('no cwd for this session', 'error');
|
|
71
|
+
try {
|
|
72
|
+
await api('POST', `/api/sessions/${sessionId}/resume`, { cwd });
|
|
73
|
+
const verb = kind === 'continue' ? 'continuing' : 'opening wt';
|
|
74
|
+
setToast(`${verb} · ${sessionId.slice(0, 8)}…`);
|
|
75
|
+
if (kind === 'continue') {
|
|
76
|
+
setTimeout(() => loadSessions().catch(() => {}), 3000);
|
|
77
|
+
setTimeout(() => loadRecent().catch(() => {}), 4000);
|
|
78
|
+
}
|
|
79
|
+
} catch (e) { setToast(e.message, 'error'); }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function runFinder() {
|
|
83
|
+
try {
|
|
84
|
+
await api('POST', '/api/sessions/finder');
|
|
85
|
+
setToast('finder session launching in a new wt window');
|
|
86
|
+
} catch (e) { setToast(e.message, 'error'); }
|
|
87
|
+
}
|