Skip to content

Commit f181545

Browse files
committed
ci refresh, cache bust, source indicator, one-word names, popup cap
- refresh button (↻) with cache-busting (?t=timestamp) - source indicator (live/cached) in dot title - race guard (ciBusy) on concurrent refreshes - spin tied to promise instead of fixed timeout - ciSource reset on error catch - per_page 10->50 for CI Status visibility - popup capped at 10 rows - workflow names: all one-word lowercase - bot commit author in release.yml - README: remove stale version-bump.yml, match filenames - bot page: plain language
1 parent f1a86aa commit f181545

11 files changed

Lines changed: 111 additions & 47 deletions

File tree

.github/about.html

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@
9696
.ci-run{color:#d29922;animation:ci-spin 1s step-end infinite;}
9797
@keyframes ci-spin{50%{opacity:.3;}}
9898
.ci-unknown{color:var(--dim);}
99+
.ci-refresh{display:inline-block;vertical-align:middle;margin-left:4px;font-size:13px;cursor:pointer;opacity:.5;transition:opacity .3s,transform .3s;user-select:none;}
100+
.ci-refresh:hover{opacity:1;}
101+
.ci-refresh.spin{animation:ci-spin .6s linear 2;}
99102
#quality-label{cursor:pointer;border-bottom:1px dotted;}
100103
.quality-wrap{position:relative;display:inline-block;}
101104
#quality-popup{display:none;position:absolute;bottom:100%;left:50%;transform:translateX(-50%);background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 12px;font-size:12px;line-height:1.8;white-space:nowrap;z-index:99;margin-bottom:6px;}
@@ -264,7 +267,7 @@ <h2>Token Use &amp; Billing</h2>
264267
<a href="https://github.com/bniladridas/cursor/releases">Releases</a> ·
265268
<a href="https://palmshed.github.io/cursor-docs/">Docs</a> ·
266269
<a href="index.html">Cursor</a> ·
267-
<span id="ci-status" class="ci ci-unknown" title="Checking CI…"></span>
270+
<span id="ci-status" class="ci ci-unknown" title="Checking CI…"></span><span id="ci-refresh" class="ci-refresh" title="Refresh + trigger run"></span>
268271
<span class="quality-wrap" id="quality-wrap"><span id="quality-label">quality *</span><div id="quality-popup"></div></span> ·
269272
🇺🇦 stand with Ukraine
270273
</div>
@@ -279,36 +282,55 @@ <h2>Token Use &amp; Billing</h2>
279282
<div class="layout-hint" id="layout-hint">toggle portrait / landscape</div>
280283
<script>
281284
const ciEl = document.getElementById('ci-status');
285+
const crEl = document.getElementById('ci-refresh');
282286
const qwEl = document.getElementById('quality-wrap');
283287
const qpEl = document.getElementById('quality-popup');
284288
var qualityRuns = [];
289+
var ciSource = '';
290+
var ciBusy = false;
285291

286-
async function updateCI() {
292+
async function updateCI(noCache) {
293+
ciEl.className = 'ci ci-unknown';
294+
ciEl.textContent = '\u25CF';
295+
ciEl.title = 'loading\u2026';
287296
try {
288-
const r = await fetch('https://api.github.com/repos/bniladridas/cursor/actions/runs?per_page=10');
297+
const r = await fetch('https://api.github.com/repos/bniladridas/cursor/actions/runs?per_page=50');
289298
if (!r.ok) throw new Error(String(r.status));
290299
const d = await r.json();
291300
qualityRuns = d.workflow_runs || [];
301+
ciSource = 'live';
292302
updateDot();
293303
buildPopup();
294304
return;
295305
} catch(e) {
296306
ciEl.title = 'live failed, trying fallback: ' + e.message;
297307
}
298308
try {
299-
const r = await fetch('https://raw.githubusercontent.com/bniladridas/cursor/ci-cache/.github/ci-status.json');
309+
var url = 'https://raw.githubusercontent.com/bniladridas/cursor/ci-cache/.github/ci-status.json';
310+
if (noCache) url += '?t=' + Date.now();
311+
const r = await fetch(url);
300312
if (!r.ok) throw new Error(String(r.status));
301313
const d = await r.json();
302314
qualityRuns = d.workflow_runs || [];
315+
ciSource = 'cached';
303316
updateDot();
304317
buildPopup();
305318
} catch(e) {
306319
ciEl.className = 'ci ci-unknown';
307320
ciEl.textContent = '\u25CF';
308321
ciEl.title = 'CI error: ' + e.message;
322+
ciSource = '';
309323
}
310324
}
311325

326+
crEl.addEventListener('click', function() {
327+
if (ciBusy) return;
328+
ciBusy = true;
329+
crEl.classList.add('spin');
330+
updateCI(true).finally(function(){ciBusy = false; crEl.classList.remove('spin');});
331+
window.open('https://github.com/bniladridas/cursor/actions/workflows/ci-status.yml', '_blank');
332+
});
333+
312334
function updateDot() {
313335
var run = qualityRuns[0];
314336
if (!run) { ciEl.className = 'ci ci-unknown'; ciEl.textContent = '\u25CF'; ciEl.title=''; return; }
@@ -318,7 +340,7 @@ <h2>Token Use &amp; Billing</h2>
318340
ciEl.textContent = run.status === 'completed'
319341
? (run.conclusion === 'success' ? '\u2713' : '\u2717')
320342
: '\u25CF';
321-
ciEl.title = (run.status === 'completed' ? run.conclusion : 'running') + ' \u2013 ' + run.event + ' / ' + run.name;
343+
ciEl.title = (run.status === 'completed' ? run.conclusion : 'running') + ' \u2013 ' + run.event + ' / ' + run.name + ' (' + ciSource + ')';
322344
}
323345

324346
function statusChar(run) {
@@ -332,7 +354,7 @@ <h2>Token Use &amp; Billing</h2>
332354

333355
function buildPopup() {
334356
var html = '<div class="popup-title">workflow runs</div>';
335-
qualityRuns.forEach(function(run) {
357+
qualityRuns.slice(0,10).forEach(function(run) {
336358
html += '<div class="popup-row"><span class="ci ' + statusClass(run) + '">' + statusChar(run) + '</span>';
337359
html += '<a href="' + run.html_url + '" target="_blank">' + run.name + '</a>';
338360
html += '<span style="color:var(--dim);font-size:10px;">' + run.event + '</span></div>';

.github/github-app.html

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@
142142
.ci-fail{color:#f85149;}
143143
.ci-run{color:#d29922;animation:ci-spin 1s step-end infinite;}
144144
.ci-unknown{color:var(--dim);}
145+
.ci-refresh{display:inline-block;vertical-align:middle;margin-left:4px;font-size:13px;cursor:pointer;opacity:.5;transition:opacity .3s,transform .3s;user-select:none;}
146+
.ci-refresh:hover{opacity:1;}
147+
.ci-refresh.spin{animation:ci-spin .6s linear 2;}
145148

146149
#quality-label{cursor:pointer;border-bottom:1px dotted;}
147150
.quality-wrap{position:relative;display:inline-block;}
@@ -184,40 +187,37 @@
184187
</header>
185188

186189
<p>
187-
GitHub App that automates Cursor release, CI, docs, and repository maintenance workflows.
190+
This bot runs workflows in bniladridas/cursor and palmshed/cursor-docs. It builds releases, publishes to npm and Homebrew, caches CI status for the site, syncs docs, labels PRs, responds to invites, and cleans up stale issues. It does nothing outside these repos.
188191
</p>
189192

190193
<h2>Permissions</h2>
191194

192195
<table>
193196
<tr>
194197
<td>Contents</td>
195-
<td>Release automation, CI status caching, ci-cache branch</td>
198+
<td>Push release artifacts, CI cache to ci-cache branch, sync docs</td>
196199
</tr>
197200
<tr>
198201
<td>Pull Requests</td>
199-
<td>Version updates, PR body formatting</td>
202+
<td>Open and merge version bump PRs, format PR bodies</td>
200203
</tr>
201204
<tr>
202-
<td>Checks</td>
203-
<td>CI verification</td>
205+
<td>Issues</td>
206+
<td>Post invite confirmation, label platform issues</td>
204207
</tr>
205208
<tr>
206-
<td>Issues</td>
207-
<td>Invite auto-response, issue triage</td>
209+
<td>Checks</td>
210+
<td>Read CI status for site quality popup</td>
208211
</tr>
209212
<tr>
210213
<td>Metadata</td>
211-
<td>Repository discovery</td>
214+
<td>Read repo metadata</td>
212215
</tr>
213216
</table>
214217

215218
<h2>Responsibilities</h2>
216219

217-
<p>Release publishing, Homebrew formula updates, npm publishing.</p>
218-
<p>CI status caching – fallback for site quality popup.</p>
219-
<p>Docs sync, Pages deployment, invite auto-response.</p>
220-
<p>Repository maintenance – stale management, auto-labeling, PR body cleanup.</p>
220+
<p>Release builds and npm/Homebrew publish. CI status cache for the site quality popup. Docs sync to palmshed/cursor-docs. Auto-response to invite issues. PR body cleanup, auto-labeling, stale issue/PR management.</p>
221221

222222
<p><a href="https://github.com/apps/palmshed-cursor">Install app</a></p>
223223

@@ -247,7 +247,7 @@ <h2>Responsibilities</h2>
247247
<a href="https://github.com/bniladridas/cursor/releases">Releases</a> ·
248248
<a href="https://palmshed.github.io/cursor-docs/">Docs</a> ·
249249
<a href="about.html">About</a> ·
250-
<span id="ci-status" class="ci ci-unknown" title="Checking CI…"></span>
250+
<span id="ci-status" class="ci ci-unknown" title="Checking CI…"></span><span id="ci-refresh" class="ci-refresh" title="Refresh + trigger run"></span>
251251
<span class="quality-wrap" id="quality-wrap"><span id="quality-label">quality *</span><div id="quality-popup"></div></span> ·
252252
🇺🇦 stand with Ukraine
253253
</div>
@@ -262,36 +262,55 @@ <h2>Responsibilities</h2>
262262
<div class="layout-hint" id="layout-hint">toggle portrait / landscape</div>
263263
<script>
264264
const ciEl = document.getElementById('ci-status');
265+
const crEl = document.getElementById('ci-refresh');
265266
const qwEl = document.getElementById('quality-wrap');
266267
const qpEl = document.getElementById('quality-popup');
267268
var qualityRuns = [];
269+
var ciSource = '';
270+
var ciBusy = false;
268271

269-
async function updateCI() {
272+
async function updateCI(noCache) {
273+
ciEl.className = 'ci ci-unknown';
274+
ciEl.textContent = '\u25CF';
275+
ciEl.title = 'loading\u2026';
270276
try {
271-
const r = await fetch('https://api.github.com/repos/bniladridas/cursor/actions/runs?per_page=10');
277+
const r = await fetch('https://api.github.com/repos/bniladridas/cursor/actions/runs?per_page=50');
272278
if (!r.ok) throw new Error(String(r.status));
273279
const d = await r.json();
274280
qualityRuns = d.workflow_runs || [];
281+
ciSource = 'live';
275282
updateDot();
276283
buildPopup();
277284
return;
278285
} catch(e) {
279286
ciEl.title = 'live failed, trying fallback: ' + e.message;
280287
}
281288
try {
282-
const r = await fetch('https://raw.githubusercontent.com/bniladridas/cursor/ci-cache/.github/ci-status.json');
289+
var url = 'https://raw.githubusercontent.com/bniladridas/cursor/ci-cache/.github/ci-status.json';
290+
if (noCache) url += '?t=' + Date.now();
291+
const r = await fetch(url);
283292
if (!r.ok) throw new Error(String(r.status));
284293
const d = await r.json();
285294
qualityRuns = d.workflow_runs || [];
295+
ciSource = 'cached';
286296
updateDot();
287297
buildPopup();
288298
} catch(e) {
289299
ciEl.className = 'ci ci-unknown';
290300
ciEl.textContent = '\u25CF';
291301
ciEl.title = 'CI error: ' + e.message;
302+
ciSource = '';
292303
}
293304
}
294305

306+
crEl.addEventListener('click', function() {
307+
if (ciBusy) return;
308+
ciBusy = true;
309+
crEl.classList.add('spin');
310+
updateCI(true).finally(function(){ciBusy = false; crEl.classList.remove('spin');});
311+
window.open('https://github.com/bniladridas/cursor/actions/workflows/ci-status.yml', '_blank');
312+
});
313+
295314
function updateDot() {
296315
var run = qualityRuns[0];
297316
if (!run) { ciEl.className = 'ci ci-unknown'; ciEl.textContent = '\u25CF'; ciEl.title=''; return; }
@@ -301,7 +320,7 @@ <h2>Responsibilities</h2>
301320
ciEl.textContent = run.status === 'completed'
302321
? (run.conclusion === 'success' ? '\u2713' : '\u2717')
303322
: '\u25CF';
304-
ciEl.title = (run.status === 'completed' ? run.conclusion : 'running') + ' \u2013 ' + run.event + ' / ' + run.name;
323+
ciEl.title = (run.status === 'completed' ? run.conclusion : 'running') + ' \u2013 ' + run.event + ' / ' + run.name + ' (' + ciSource + ')';
305324
}
306325

307326
function statusChar(run) {
@@ -315,7 +334,7 @@ <h2>Responsibilities</h2>
315334

316335
function buildPopup() {
317336
var html = '<div class="popup-title">workflow runs</div>';
318-
qualityRuns.forEach(function(run) {
337+
qualityRuns.slice(0,10).forEach(function(run) {
319338
html += '<div class="popup-row"><span class="ci ' + statusClass(run) + '">' + statusChar(run) + '</span>';
320339
html += '<a href="' + run.html_url + '" target="_blank">' + run.name + '</a>';
321340
html += '<span style="color:var(--dim);font-size:10px;">' + run.event + '</span></div>';

.github/index.html

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@
119119
.ci-run{color:#d29922;animation:ci-spin 1s step-end infinite;}
120120
@keyframes ci-spin{50%{opacity:.3;}}
121121
.ci-unknown{color:var(--dim);}
122+
.ci-refresh{display:inline-block;vertical-align:middle;margin-left:4px;font-size:13px;cursor:pointer;opacity:.5;transition:opacity .3s,transform .3s;user-select:none;}
123+
.ci-refresh:hover{opacity:1;}
124+
.ci-refresh.spin{animation:ci-spin .6s linear 2;}
122125
#quality-label{cursor:pointer;border-bottom:1px dotted;}
123126
.quality-wrap{position:relative;display:inline-block;}
124127
#quality-popup{display:none;position:absolute;bottom:100%;left:50%;transform:translateX(-50%);background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 12px;font-size:12px;line-height:1.8;white-space:nowrap;z-index:99;margin-bottom:6px;}
@@ -398,7 +401,7 @@ <h2>Example</h2>
398401
<a href="https://github.com/bniladridas/cursor/releases">Releases</a> ·
399402
<a href="https://palmshed.github.io/cursor-docs/">Docs</a> ·
400403
<a href="about.html">About</a> ·
401-
<span id="ci-status" class="ci ci-unknown" title="Checking CI…"></span>
404+
<span id="ci-status" class="ci ci-unknown" title="Checking CI…"></span><span id="ci-refresh" class="ci-refresh" title="Refresh + trigger run"></span>
402405
<span class="quality-wrap" id="quality-wrap"><span id="quality-label">quality *</span><div id="quality-popup"></div></span> ·
403406
🇺🇦 stand with Ukraine
404407
</div>
@@ -413,36 +416,55 @@ <h2>Example</h2>
413416
<div class="layout-hint" id="layout-hint">toggle portrait / landscape</div>
414417
<script>
415418
const ciEl = document.getElementById('ci-status');
419+
const crEl = document.getElementById('ci-refresh');
416420
const qwEl = document.getElementById('quality-wrap');
417421
const qpEl = document.getElementById('quality-popup');
418422
var qualityRuns = [];
423+
var ciSource = '';
424+
var ciBusy = false;
419425

420-
async function updateCI() {
426+
async function updateCI(noCache) {
427+
ciEl.className = 'ci ci-unknown';
428+
ciEl.textContent = '\u25CF';
429+
ciEl.title = 'loading\u2026';
421430
try {
422-
const r = await fetch('https://api.github.com/repos/bniladridas/cursor/actions/runs?per_page=10');
431+
const r = await fetch('https://api.github.com/repos/bniladridas/cursor/actions/runs?per_page=50');
423432
if (!r.ok) throw new Error(String(r.status));
424433
const d = await r.json();
425434
qualityRuns = d.workflow_runs || [];
435+
ciSource = 'live';
426436
updateDot();
427437
buildPopup();
428438
return;
429439
} catch(e) {
430440
ciEl.title = 'live failed, trying fallback: ' + e.message;
431441
}
432442
try {
433-
const r = await fetch('https://raw.githubusercontent.com/bniladridas/cursor/ci-cache/.github/ci-status.json');
443+
var url = 'https://raw.githubusercontent.com/bniladridas/cursor/ci-cache/.github/ci-status.json';
444+
if (noCache) url += '?t=' + Date.now();
445+
const r = await fetch(url);
434446
if (!r.ok) throw new Error(String(r.status));
435447
const d = await r.json();
436448
qualityRuns = d.workflow_runs || [];
449+
ciSource = 'cached';
437450
updateDot();
438451
buildPopup();
439452
} catch(e) {
440453
ciEl.className = 'ci ci-unknown';
441454
ciEl.textContent = '\u25CF';
442455
ciEl.title = 'CI error: ' + e.message;
456+
ciSource = '';
443457
}
444458
}
445459

460+
crEl.addEventListener('click', function() {
461+
if (ciBusy) return;
462+
ciBusy = true;
463+
crEl.classList.add('spin');
464+
updateCI(true).finally(function(){ciBusy = false; crEl.classList.remove('spin');});
465+
window.open('https://github.com/bniladridas/cursor/actions/workflows/ci-status.yml', '_blank');
466+
});
467+
446468
function updateDot() {
447469
var run = qualityRuns[0];
448470
if (!run) { ciEl.className = 'ci ci-unknown'; ciEl.textContent = '\u25CF'; ciEl.title=''; return; }
@@ -452,7 +474,7 @@ <h2>Example</h2>
452474
ciEl.textContent = run.status === 'completed'
453475
? (run.conclusion === 'success' ? '\u2713' : '\u2717')
454476
: '\u25CF';
455-
ciEl.title = (run.status === 'completed' ? run.conclusion : 'running') + ' \u2013 ' + run.event + ' / ' + run.name;
477+
ciEl.title = (run.status === 'completed' ? run.conclusion : 'running') + ' \u2013 ' + run.event + ' / ' + run.name + ' (' + ciSource + ')';
456478
}
457479

458480
function statusChar(run) {
@@ -466,7 +488,7 @@ <h2>Example</h2>
466488

467489
function buildPopup() {
468490
var html = '<div class="popup-title">workflow runs</div>';
469-
qualityRuns.forEach(function(run) {
491+
qualityRuns.slice(0,10).forEach(function(run) {
470492
html += '<div class="popup-row"><span class="ci ' + statusClass(run) + '">' + statusChar(run) + '</span>';
471493
html += '<a href="' + run.html_url + '" target="_blank">' + run.name + '</a>';
472494
html += '<span style="color:var(--dim);font-size:10px;">' + run.event + '</span></div>';

.github/workflows/README.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,30 @@
11
# Workflows
22

33
| Workflow | Trigger | What it does |
4-
|----------|---------|--------------|
4+
|---|---|---|
55
| `ci.yml` | PR, push | Build, test, Docker push |
66
| `cla.yml` | PR | CLA check |
77
| `pages.yml` | Push (HTML only) | Deploy site to GitHub Pages |
88
| `release.yml` | Tag `v*` | Build binaries, release, formula update, npm publish |
9-
| `formula-sha.yml` | Manual | Update Homebrew formula SHA |
10-
| `ci-status.yml` | Cron */5 | Cache workflow runs JSON on `ci-cache` branch for site fallback |
9+
| `brew.yml` | Manual | Update Homebrew formula SHA |
10+
| `status.yml` | Cron */5 | Cache workflow runs JSON on `ci-cache` branch for site fallback |
1111
| `security.yml` | Push, PR | Trivy security scan |
12-
| `version-bump.yml` | Tag `v*` | Bump version |
1312
| `pr-body.yml` | PR | Clean PR body formatting |
14-
| `issue-response.yml` | Issue opened | Label platform, respond |
13+
| `response.yml` | Issue opened | Label platform, respond |
1514
| `stale.yml` | Weekly | Mark/close stale issues/PRs |
1615
| `labeler.yml` | PR | Auto-label PRs |
17-
| `docs-sync.yml` | Push | Sync docs |
16+
| `docs.yml` | Push | Sync docs |
1817

1918
## Secrets
2019

2120
| Secret | Used By |
22-
|--------|---------|
21+
|---|---|
2322
| `NPM_TOKEN` | release.yml |
2423
| `DOCKER_USERNAME` | ci.yml |
2524
| `DOCKER_PASSWORD` | ci.yml |
26-
| `CURSOR_BOT_CLIENT_ID` | formula-sha.yml, pr-body.yml, issue-response.yml, stale.yml |
27-
| `CURSOR_BOT_PRIVATE_KEY` | formula-sha.yml, pr-body.yml, issue-response.yml, stale.yml |
25+
| `CURSOR_BOT_CLIENT_ID` | brew.yml, pr-body.yml, response.yml, stale.yml |
26+
| `CURSOR_BOT_PRIVATE_KEY` | brew.yml, pr-body.yml, response.yml, stale.yml |
2827

2928
## Notes
3029

31-
- `ci-status.yml` pushes to `ci-cache` branch (not main), zero main history noise
30+
- `status.yml` pushes to `ci-cache` branch (not main), zero main history noise

.github/workflows/ci-status.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: CI Status
1+
name: status
22
on:
33
schedule:
44
- cron: '*/5 * * * *'
@@ -13,7 +13,7 @@ jobs:
1313
- uses: actions/checkout@v4
1414
- run: |
1515
curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
16-
'https://api.github.com/repos/bniladridas/cursor/actions/runs?per_page=10' \
16+
'https://api.github.com/repos/bniladridas/cursor/actions/runs?per_page=50' \
1717
> .github/ci-status.json
1818
git config user.name 'palmshed-cursor[bot]'
1919
git config user.email '294867837+palmshed-cursor[bot]@users.noreply.github.com'

.github/workflows/docs-sync.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: docs-sync
1+
name: docs
22
on:
33
push:
44
branches: [main]

0 commit comments

Comments
 (0)