@xulongzhe/clawbench-desktop-linux-x64-payload 0.126.0 → 0.128.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/package.json
CHANGED
package/payload.json
CHANGED
package/resources/app.asar
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<!-- The capsule is a fixed, self-contained document: no remote content, no forms,
|
|
6
|
+
no navigation. `file:` is allowed only for the app icon shipped next to
|
|
7
|
+
login.html; counters arrive over IPC, never over the network. -->
|
|
8
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' file: data:; style-src 'unsafe-inline'; script-src 'unsafe-inline'">
|
|
9
|
+
<title>ClawBench</title>
|
|
10
|
+
<style>
|
|
11
|
+
:root {
|
|
12
|
+
--bg: rgba(24, 26, 31, 0.92);
|
|
13
|
+
--text: #f2f4f8;
|
|
14
|
+
--muted: #9aa3b2;
|
|
15
|
+
--accent: #4c8dff;
|
|
16
|
+
/* Status colors are fixed semantic colors on both platforms — they do NOT
|
|
17
|
+
follow the theme, so a running session reads the same on any of the 20+
|
|
18
|
+
themes. See docs/plans/2026-08-28-android-floating-panel.md. */
|
|
19
|
+
--running: #35c05e;
|
|
20
|
+
--pending: #f2b415;
|
|
21
|
+
--unread: #3d8bfd;
|
|
22
|
+
}
|
|
23
|
+
* { box-sizing: border-box; }
|
|
24
|
+
html, body {
|
|
25
|
+
margin: 0; padding: 0; width: 100%; height: 100%;
|
|
26
|
+
background: transparent; overflow: hidden;
|
|
27
|
+
user-select: none; -webkit-user-select: none; cursor: default;
|
|
28
|
+
}
|
|
29
|
+
body { font-family: system-ui, "Segoe UI", "Microsoft YaHei UI", "PingFang SC", sans-serif; }
|
|
30
|
+
|
|
31
|
+
/* A transparent window is rectangular to the compositor: the padding ring is
|
|
32
|
+
dead space, so keep it small and let the pill own the hit area. */
|
|
33
|
+
#hit { width: 100%; height: 100%; padding: 4px; display: flex; align-items: center; }
|
|
34
|
+
|
|
35
|
+
#pill {
|
|
36
|
+
/* Intrinsic height, not 100%: a box stretched to the window would be
|
|
37
|
+
measured as the window (the width case below) and the capsule would grow into
|
|
38
|
+
a disc whenever the window is left taller than the content. */
|
|
39
|
+
height: auto;
|
|
40
|
+
padding-top: 5px; padding-bottom: 5px;
|
|
41
|
+
/* fit-content, not the default block width: the page reports its natural
|
|
42
|
+
size so the window can hug the capsule, and a box stretched to the window
|
|
43
|
+
would measure the window instead — the capsule could then grow but never
|
|
44
|
+
shrink back after the counters drop to zero. */
|
|
45
|
+
width: fit-content; max-width: 100%;
|
|
46
|
+
display: flex; align-items: center; gap: 7px;
|
|
47
|
+
/* Asymmetric on purpose. Do NOT "fix" this back to symmetric.
|
|
48
|
+
|
|
49
|
+
The two ends hold different KINDS of thing, and a filled shape tolerates
|
|
50
|
+
a tighter edge than a glyph does. On the left sits a 26px solid disc
|
|
51
|
+
whose own outline is the visual boundary, so 6px reads as flush. On the
|
|
52
|
+
right sits text, where the same gap makes the last glyph look pinned to
|
|
53
|
+
the capsule wall — the eye measures to the glyph's ink, not to the
|
|
54
|
+
element box.
|
|
55
|
+
|
|
56
|
+
The end radius is NOT the reason: at 36px tall the 18px curve insets
|
|
57
|
+
only 1.6px at the text's top and bottom edges, so geometry cannot
|
|
58
|
+
account for the difference. It is optical weight.
|
|
59
|
+
|
|
60
|
+
Verified by measuring the DOM in all three states (counters / idle only /
|
|
61
|
+
all three): the icon keeps a 6px gap and the text a 15px gap regardless
|
|
62
|
+
of how wide the content gets. */
|
|
63
|
+
padding-left: 6px; padding-right: 15px;
|
|
64
|
+
border-radius: 999px;
|
|
65
|
+
background: var(--bg);
|
|
66
|
+
color: var(--text);
|
|
67
|
+
box-shadow: 0 6px 22px rgba(0, 0, 0, .38), 0 0 0 1px rgba(255, 255, 255, .07) inset;
|
|
68
|
+
/* Deliberately NOT `-webkit-app-region: drag`: that hands the surface to the
|
|
69
|
+
OS as a title bar, so right-click opens the Windows system menu instead of
|
|
70
|
+
ours and no DOM click reaches the page. Dragging is driven from the main
|
|
71
|
+
process instead (see main/floating.ts). */
|
|
72
|
+
transition: transform .12s ease, box-shadow .12s ease;
|
|
73
|
+
}
|
|
74
|
+
body.hover #pill { transform: translateY(-1px); box-shadow: 0 9px 26px rgba(0,0,0,.46), 0 0 0 1px rgba(255,255,255,.12) inset; }
|
|
75
|
+
/* Awaiting approval is the one state worth flagging on the frame itself: a
|
|
76
|
+
blocked session is otherwise indistinguishable from a running one at a
|
|
77
|
+
glance from across the desk. */
|
|
78
|
+
body.needs-me #pill { box-shadow: 0 6px 22px rgba(0, 0, 0, .38), 0 0 0 1px var(--pending); }
|
|
79
|
+
|
|
80
|
+
#logo {
|
|
81
|
+
flex: 0 0 auto;
|
|
82
|
+
width: 26px; height: 26px; border-radius: 50%;
|
|
83
|
+
background: color-mix(in srgb, var(--text) 88%, transparent);
|
|
84
|
+
display: flex; align-items: center; justify-content: center;
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
}
|
|
87
|
+
#logo img { width: 100%; height: 100%; object-fit: contain; }
|
|
88
|
+
#logo i { width: 11px; height: 11px; border-radius: 3px; background: #14161b; display: block; }
|
|
89
|
+
|
|
90
|
+
#lines { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
|
91
|
+
.row { display: flex; align-items: center; gap: 5px; white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
92
|
+
.row.primary { font-size: 13px; font-weight: 650; line-height: 1.15; }
|
|
93
|
+
.row.secondary { font-size: 12px; font-weight: 500; color: var(--muted); line-height: 1.15; }
|
|
94
|
+
.seg { display: inline-flex; align-items: center; gap: 4px; }
|
|
95
|
+
/* A zero counter is hidden rather than shown greyed: the capsule's whole job
|
|
96
|
+
is "is there something to look at", and three zeros is noise. Matches the
|
|
97
|
+
Android capsule ("为 0 的计数不显示"). */
|
|
98
|
+
.seg[hidden] { display: none; }
|
|
99
|
+
.dot { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; }
|
|
100
|
+
.dot.running { background: var(--running); }
|
|
101
|
+
.dot.pending { background: var(--pending); }
|
|
102
|
+
.dot.unread { background: var(--unread); }
|
|
103
|
+
.n { color: var(--text); font-weight: 650; }
|
|
104
|
+
.sep { opacity: .45; padding: 0 1px; }
|
|
105
|
+
.sep[hidden] { display: none; }
|
|
106
|
+
#idle { color: var(--muted); font-size: 13px; font-weight: 500; }
|
|
107
|
+
#idle[hidden] { display: none; }
|
|
108
|
+
.row[hidden] { display: none; }
|
|
109
|
+
|
|
110
|
+
/* Running breathes and pending pulses: both mean "come back soon", and a
|
|
111
|
+
static dot out of the corner of the eye is easy to miss. */
|
|
112
|
+
body.busy .dot.running { animation: breathe 2s ease-in-out infinite; }
|
|
113
|
+
body.needs-me .dot.pending { animation: pulse 1.5s ease-in-out infinite; }
|
|
114
|
+
@keyframes breathe {
|
|
115
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(53, 192, 94, .45); }
|
|
116
|
+
50% { box-shadow: 0 0 0 4px rgba(53, 192, 94, 0); }
|
|
117
|
+
}
|
|
118
|
+
@keyframes pulse {
|
|
119
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(242, 180, 21, .55); }
|
|
120
|
+
50% { box-shadow: 0 0 0 5px rgba(242, 180, 21, 0); }
|
|
121
|
+
}
|
|
122
|
+
</style>
|
|
123
|
+
</head>
|
|
124
|
+
<body>
|
|
125
|
+
<div id="hit">
|
|
126
|
+
<div id="pill">
|
|
127
|
+
<div id="logo"><i></i></div>
|
|
128
|
+
<div id="lines">
|
|
129
|
+
<div class="row primary" id="row-primary">
|
|
130
|
+
<span class="seg" id="seg-running"><span class="dot running"></span><span id="lbl-running">Running</span> <span class="n" id="n-running">0</span></span>
|
|
131
|
+
<span class="seg" id="seg-pending"><span class="dot pending"></span><span id="lbl-pending">Pending</span> <span class="n" id="n-pending">0</span></span>
|
|
132
|
+
<span class="seg" id="seg-unread"><span class="dot unread"></span><span id="lbl-unread">Unread</span> <span class="n" id="n-unread">0</span></span>
|
|
133
|
+
<span id="idle">Idle</span>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
<script>
|
|
139
|
+
(function () {
|
|
140
|
+
'use strict';
|
|
141
|
+
var host = window.FloatingHost || {};
|
|
142
|
+
var $ = function (id) { return document.getElementById(id); };
|
|
143
|
+
var els = {
|
|
144
|
+
pill: $('pill'), hit: $('hit'), logo: $('logo'),
|
|
145
|
+
running: $('seg-running'), pending: $('seg-pending'), unread: $('seg-unread'),
|
|
146
|
+
nRunning: $('n-running'), nPending: $('n-pending'), nUnread: $('n-unread'),
|
|
147
|
+
lRunning: $('lbl-running'), lPending: $('lbl-pending'), lUnread: $('lbl-unread'),
|
|
148
|
+
idle: $('idle')
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
function hexToRgba(color, alpha) {
|
|
152
|
+
if (typeof color !== 'string') return null;
|
|
153
|
+
var m = /^#([0-9a-f]{3,8})$/i.exec(color.trim());
|
|
154
|
+
if (!m) return null;
|
|
155
|
+
var h = m[1];
|
|
156
|
+
if (h.length === 3 || h.length === 4) h = h.split('').map(function (x) { return x + x; }).join('');
|
|
157
|
+
return 'rgba(' + parseInt(h.slice(0, 2), 16) + ',' + parseInt(h.slice(2, 4), 16) +
|
|
158
|
+
',' + parseInt(h.slice(4, 6), 16) + ',' + alpha + ')';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function apply(state) {
|
|
162
|
+
if (!state) return;
|
|
163
|
+
var s = state.stats || {};
|
|
164
|
+
var l = state.labels || {};
|
|
165
|
+
var c = state.colors || {};
|
|
166
|
+
var n = { running: s.running | 0, pending: s.pending | 0, unread: s.unread | 0 };
|
|
167
|
+
|
|
168
|
+
els.nRunning.textContent = n.running;
|
|
169
|
+
els.nPending.textContent = n.pending;
|
|
170
|
+
els.nUnread.textContent = n.unread;
|
|
171
|
+
if (l.running) els.lRunning.textContent = l.running;
|
|
172
|
+
if (l.pending) els.lPending.textContent = l.pending;
|
|
173
|
+
if (l.unread) els.lUnread.textContent = l.unread;
|
|
174
|
+
if (l.idle) els.idle.textContent = l.idle;
|
|
175
|
+
document.documentElement.lang = state.lang === 'en' ? 'en' : 'zh';
|
|
176
|
+
if (l.hint) els.pill.title = l.hint;
|
|
177
|
+
|
|
178
|
+
var idle = !n.running && !n.pending && !n.unread;
|
|
179
|
+
els.running.hidden = idle || n.running === 0;
|
|
180
|
+
els.pending.hidden = n.pending === 0;
|
|
181
|
+
els.unread.hidden = n.unread === 0;
|
|
182
|
+
els.idle.hidden = !idle;
|
|
183
|
+
document.body.classList.toggle('needs-me', n.pending > 0);
|
|
184
|
+
document.body.classList.toggle('busy', n.running > 0);
|
|
185
|
+
|
|
186
|
+
var root = document.documentElement.style;
|
|
187
|
+
if (c.text) root.setProperty('--text', c.text);
|
|
188
|
+
if (c.textSecondary) root.setProperty('--muted', c.textSecondary);
|
|
189
|
+
if (c.accent) root.setProperty('--accent', c.accent);
|
|
190
|
+
// The Android bubble uses a solid theme background; a capsule floating over
|
|
191
|
+
// the desktop needs translucency or its hard edge reads as a sticker.
|
|
192
|
+
root.setProperty('--bg', (c.bg && hexToRgba(c.bg, 0.93)) || 'rgba(24, 26, 31, 0.92)');
|
|
193
|
+
|
|
194
|
+
if (state.logoUrl && !els.logo.dataset.set) {
|
|
195
|
+
els.logo.dataset.set = '1';
|
|
196
|
+
var img = document.createElement('img');
|
|
197
|
+
img.src = state.logoUrl;
|
|
198
|
+
img.alt = '';
|
|
199
|
+
img.onerror = function () { delete els.logo.dataset.set; img.remove(); };
|
|
200
|
+
els.logo.appendChild(img);
|
|
201
|
+
}
|
|
202
|
+
measure();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Tell the main process our natural size so the window hugs the capsule. */
|
|
206
|
+
function measure() {
|
|
207
|
+
var w = Math.ceil(els.pill.scrollWidth + 8);
|
|
208
|
+
var h = Math.ceil(els.pill.scrollHeight + 8);
|
|
209
|
+
var key = w + 'x' + h;
|
|
210
|
+
if (key === measure.last) return;
|
|
211
|
+
measure.last = key;
|
|
212
|
+
if (typeof host.resize === 'function') host.resize(w, h);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (typeof host.onState === 'function') {
|
|
216
|
+
host.onState(apply);
|
|
217
|
+
// Listeners are installed: replay anything the main process queued during load.
|
|
218
|
+
if (typeof host.ready === 'function') host.ready();
|
|
219
|
+
}
|
|
220
|
+
els.pill.addEventListener('mouseenter', function () { document.body.classList.add('hover'); });
|
|
221
|
+
els.pill.addEventListener('mouseleave', function () { document.body.classList.remove('hover'); });
|
|
222
|
+
|
|
223
|
+
// Dragging: the page reports only where the grab happened. The main process
|
|
224
|
+
// follows the OS cursor and decides on release whether this was a drag or a
|
|
225
|
+
// click — it is the only side that can tell "the window moved" from "the mouse
|
|
226
|
+
// moved", and a click must open the app rather than do nothing.
|
|
227
|
+
//
|
|
228
|
+
// Pointer capture matters: the capsule is small, so the cursor leaves it almost
|
|
229
|
+
// immediately and without capture the release can land on another window and
|
|
230
|
+
// never reach this document — leaving the capsule glued to the cursor. The main
|
|
231
|
+
// process has a stillness backstop for the same case.
|
|
232
|
+
var dragging = false;
|
|
233
|
+
function endGesture() {
|
|
234
|
+
if (!dragging) return;
|
|
235
|
+
dragging = false;
|
|
236
|
+
if (typeof host.dragEnd === 'function') host.dragEnd();
|
|
237
|
+
}
|
|
238
|
+
els.pill.addEventListener('pointerdown', function (e) {
|
|
239
|
+
if (e.button !== 0) return;
|
|
240
|
+
e.preventDefault();
|
|
241
|
+
dragging = true;
|
|
242
|
+
try { els.pill.setPointerCapture(e.pointerId); } catch (err) { /* unsupported */ }
|
|
243
|
+
if (typeof host.dragStart === 'function') host.dragStart(e.clientX, e.clientY);
|
|
244
|
+
});
|
|
245
|
+
els.pill.addEventListener('pointerup', endGesture);
|
|
246
|
+
els.pill.addEventListener('pointercancel', endGesture);
|
|
247
|
+
els.pill.addEventListener('lostpointercapture', endGesture);
|
|
248
|
+
window.addEventListener('mouseup', endGesture);
|
|
249
|
+
window.addEventListener('blur', endGesture);
|
|
250
|
+
els.pill.addEventListener('contextmenu', function (e) {
|
|
251
|
+
e.preventDefault();
|
|
252
|
+
if (typeof host.context === 'function') host.context();
|
|
253
|
+
});
|
|
254
|
+
measure();
|
|
255
|
+
})();
|
|
256
|
+
</script>
|
|
257
|
+
</body>
|
|
258
|
+
</html>
|