gh-glance 0.6.1 → 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.
Files changed (4) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +144 -28
  3. package/index.mjs +1911 -184
  4. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,79 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-08-11
11
+
12
+ ### Added
13
+
14
+ - **Last-known-good dashboard rows now survive process restarts.** Successfully
15
+ parsed Actions, Issues, Pull Requests, and Security state is stored in a
16
+ private, target-scoped cache. It retains five recent targets and 60 rows per
17
+ tab, ignores corrupt or unwritable state, and never stores credentials.
18
+
19
+ ### Changed
20
+
21
+ - **Development tooling was refreshed.** The paired CodeQL actions now use
22
+ 4.37.6, and the development-only `globals` lock entry is 17.9.0.
23
+
24
+ ### Fixed
25
+
26
+ - **Status updates no longer accumulate old footer lines during prolonged rate
27
+ limiting.** The dashboard leaves the terminal's physical bottom row as a
28
+ scroll guard while preserving incremental rendering.
29
+ - **Restarting during a live rate limit no longer shows an empty dashboard.**
30
+ Cached rows remain visible below the current error with an explicit `stale`
31
+ age, while repository targets remain isolated.
32
+ - **Blind Security refreshes preserve known alerts.** A failed observation
33
+ cannot replace last-known-good Security rows with a false empty result, and an
34
+ identical healthy response clears the warning after recovery.
35
+
36
+ ## [0.7.0] - 2026-08-10
37
+
38
+ ### Added
39
+
40
+ - **Full-table columns can be resized with a mouse or keyboard.** Visible header
41
+ grips support left-button dragging, while `w` opens a keyboard width mode for
42
+ one- and five-cell adjustments and selected-column or active-tab resets.
43
+ - **Width choices persist automatically per user and per tab.** Only deviations
44
+ from the built-in defaults are stored, so resetting a column or tab cleanly
45
+ returns it to the source-controlled layout.
46
+ - **`gh-glance` now widens its own poll interval when the API budget is
47
+ draining.** The rate limit is per token, not per process, so several panes
48
+ spent one budget while each throttled as though it were alone: seven panes
49
+ exhausted a 5,000/hour REST limit in under half an hour (measured 8,500
50
+ calls/hour). Each pane now reads `gh api rate_limit` once a minute -- a probe
51
+ that does not itself count against the limit -- infers how much of the budget
52
+ is its own by comparing its spend against the token's total, and slows down to
53
+ fit, up to a 60-second ceiling. A single pane is unaffected and stays at 5
54
+ seconds. The status bar shows `throttled 18s` whenever the interval has
55
+ widened, and `r` still refreshes immediately.
56
+ - **`GH_GLANCE_REFRESH` sets the poll interval for every pane in a shell.** Same
57
+ 2-3600 second range as `--refresh`, which still takes precedence.
58
+
59
+ ### Changed
60
+
61
+ - **Oversized saved widths fit safely in narrower panes.** They are temporarily
62
+ reduced toward the defaults when the stock full table fits, without changing
63
+ the saved preference; widening the pane restores the preferred widths.
64
+ - **Terminal mouse reporting is released on every cleanup path.** Clean quit,
65
+ signals, crashes, and the interactive remote-setup handoff disable button and
66
+ SGR reporting before the primary screen is restored.
67
+
68
+ ### Fixed
69
+
70
+ - **The cost model understated the Actions tab by half.** `gh run list` issues
71
+ two REST requests, not one -- `GET /actions/runs` and
72
+ `GET /actions/workflows` -- so a pane on Actions costs about 1,620 requests an
73
+ hour rather than the 900 `--doctor` reported, and roughly three panes fill an
74
+ hourly budget rather than five. The figure was wrong in `--doctor`, in the
75
+ README's per-tab table, and in ADR 0001's request count. Measured with
76
+ `GH_DEBUG=api`; the projection and the new throttle now read one shared table.
77
+ - **`--doctor` reads the API budget from the right server.** `gh api rate_limit`
78
+ was the one `gh api` call sent without `--hostname`, so with
79
+ `--repo host/owner/name` the reported budget came from github.com while every
80
+ other call went to the tenant. A rate limit is per token *per server*, so the
81
+ two figures belong to unrelated limits.
82
+
10
83
  ## [0.6.1] - 2026-08-06
11
84
 
12
85
  ### Security
@@ -574,6 +647,9 @@ engineering, security, QA and UX. What follows is what changed as a result.
574
647
  - The `main` field from `package.json`. It advertised the file as importable,
575
648
  but importing it took over the terminal or exited the host process.
576
649
 
650
+ [Unreleased]: https://github.com/juan294/gh-glance/compare/v0.8.0...HEAD
651
+ [0.8.0]: https://github.com/juan294/gh-glance/compare/v0.7.0...v0.8.0
652
+ [0.7.0]: https://github.com/juan294/gh-glance/compare/v0.6.1...v0.7.0
577
653
  [0.6.1]: https://github.com/juan294/gh-glance/releases/tag/v0.6.1
578
654
  [0.6.0]: https://github.com/juan294/gh-glance/releases/tag/v0.6.0
579
655
  [0.5.1]: https://github.com/juan294/gh-glance/releases/tag/v0.5.1
package/README.md CHANGED
@@ -21,16 +21,16 @@ without switching to the browser.
21
21
  [1:Actions (4)] 2:Issues (3) 3:PRs (2) 4:Security (0)
22
22
  ────────────────────────────────────────────────────────────────────────────
23
23
  ╭─ Actions ────────────────────────────────────────────────────────────────╮
24
- │ TITLE WORKFLOW BRANCH TIME UPDATED │
24
+ │ TITLE │WORKFLOW │BRANCH │TIME │UPDATED │
25
25
  │ ──────────────────────────────────────────────────────────────────────── │
26
- │ >+ ci: pin actions to comm… #443 CI develop 1m20s 2d ago │
27
- │ x fix: restore the primar… #442 Code… develop 1m28s 2d ago │
28
- │ ! chore: bump dependencies #441 CI dependa…int-10 56h49m 2d ago │
29
- │ - docs: update the readme #440 CI develop 15s 3d ago │
26
+ │ >+ ci: pin actions to comm… #443 CI develop 1m20s 9d ago │
27
+ │ x fix: restore the primar… #442 Code… develop 1m28s 9d ago │
28
+ │ ! chore: bump dependencies #441 CI dependa…int-10 222h8m 9d ago │
29
+ │ - docs: update the readme #440 CI develop 15s 10d ago │
30
30
  │ │
31
31
  │ │
32
32
  ╰───────────────────────────────────────────────────────────────── 4 of 4 ─╯
33
- ⣾ Fetching Move: ↑↓ | Open: Ent | Refresh: r | Quit: q
33
+ ⣾ Fetching Move: ↑↓ | Open: Ent | Refresh: r | Width: w | Quit: q 0.8.0
34
34
  ```
35
35
 
36
36
  > Captured from a real run at 76 columns with `GH_GLANCE_ICONS=unicode`, so the
@@ -73,6 +73,8 @@ current.
73
73
  that have GitHub Advanced Security enabled -- see [Limitations](#limitations).
74
74
  - Tab bar with live counts, pinned to the top of the pane above a divider,
75
75
  switchable via `1`-`4`, arrow keys, or `Tab`/`Shift+Tab`
76
+ - Adjustable full-table columns, with visible header grips, a keyboard width
77
+ mode, and preferences kept separately for each tab
76
78
  - `lazygit`-style panel frame: the tab name sits in the top border, the
77
79
  visible-of-total row count in the bottom
78
80
  - A `Fetching` indicator that brightens on every refresh and animates during the
@@ -88,10 +90,13 @@ current.
88
90
  answers "is CI red" without colour
89
91
  - Says when it is stale rather than showing old data silently, and marks a tab
90
92
  whose last refresh failed
93
+ - Keeps the last successfully parsed rows visible during temporary failures and
94
+ across a restart, scoped to the repository being watched
91
95
  - Adapts row count to the terminal pane's height live, on resize
92
96
  - Enters the terminal's alternate screen buffer on launch (like `lazygit`,
93
97
  `htop`, `vim`) so the shell prompt that launched it stays out of view, and
94
- is restored cleanly on exit
98
+ is restored cleanly on exit without old status lines accumulating below the
99
+ frame
95
100
 
96
101
  ## Prerequisites
97
102
 
@@ -230,8 +235,42 @@ stdout isn't a terminal rather than streaming redraw frames into a pipe.
230
235
  | `PgUp` / `PgDn` | Move a page at a time |
231
236
  | `Enter` | Open the selected item, or accept an onboarding prompt |
232
237
  | `r` | Refresh the current tab now |
238
+ | `w` | Adjust table column widths |
233
239
  | `?` | Show the keys without leaving the dashboard (any key closes it) |
234
- | `q` / `Esc` / `Ctrl+C` | Quit |
240
+ | `q` / `Esc` / `Ctrl+C` | Quit (`Esc` leaves width mode) |
241
+
242
+ ### Adjusting column widths
243
+
244
+ Press `w` to adjust the named fixed-width columns in the visible full table.
245
+ The flexible **TITLE** or **SUMMARY** column, status and severity cells, and
246
+ compact layouts are not adjustable. Outside width mode, the normal arrow,
247
+ `Tab`, `Enter`, and refresh meanings above are unchanged.
248
+
249
+ | Key | In width mode |
250
+ |---|---|
251
+ | `Tab` / `Shift+Tab` | Select the next / previous adjustable column |
252
+ | `←` / `→` | Resize the selected column by one cell |
253
+ | `Shift+←` / `Shift+→` | Resize the selected column by five cells |
254
+ | `r` | Reset the selected column to its default |
255
+ | `R` | Reset every width on the active tab |
256
+ | `Enter` / `Esc` / `w` | Finish and leave width mode |
257
+ | `q` / `Ctrl+C` | Quit globally |
258
+
259
+ While the mode is active, the ordinary status hints are replaced by a bounded
260
+ line such as:
261
+
262
+ ```text
263
+ Width: BRANCH 14 Tab select <- -> resize r reset Esc done
264
+ ```
265
+
266
+ In an interactive terminal, you can also left-drag a visible `│` header grip.
267
+ Pressing a grip selects its column and enters width mode; releasing the button,
268
+ including outside the header, ends the drag. Row clicking, hover behavior, and
269
+ pointer-shape changes are not supported. Mouse resizing has the same interactive
270
+ terminal requirement as keyboard input.
271
+
272
+ While gh-glance is running, terminal-native text selection may require your
273
+ terminal's mouse-reporting bypass modifier, commonly `Shift`.
235
274
 
236
275
  The cursor tracks the *item*, not the row position, so it stays on what you
237
276
  selected as new rows arrive above it. `Enter` works on Actions, Issues and Pull
@@ -278,11 +317,62 @@ Environment variables work too, and the flags take precedence:
278
317
  | `GH_REPO=owner/name` | Watch a specific repository instead of the current directory's. A host-qualified `GH_REPO` does **not** route the security-alert endpoints -- use `GH_HOST` or `--repo host/owner/name` for that |
279
318
  | `GH_HOST=<host>` | Send every call to a GitHub Enterprise or EMU host instead of `github.com`. Routes both the list commands and the alert endpoints |
280
319
  | `GH_TOKEN`, `GH_ENTERPRISE_TOKEN`, `GH_CONFIG_DIR` | Not read by `gh-glance` -- passed through to `gh` untouched, along with the proxy variables. `gh-glance` handles no credentials of its own. Separate `GH_CONFIG_DIR` values can isolate simultaneous panes on different accounts; see [No account pinning](#limitations) |
320
+ | `GH_GLANCE_REFRESH=<seconds>` | Active-tab poll interval, 2-3600. Sets it once for every pane in a shell; `--refresh` takes precedence. This is a floor -- see [Rate limit](#rate-limit) |
281
321
  | `GH_GLANCE_ICONS=unicode` | Plain ASCII status icons, for terminals without a Nerd Font |
282
322
  | `GH_GLANCE_NO_ANIMATION=1` | Freeze the spinner — no motion at all |
283
323
  | `NO_COLOR=1` | Disable colour. Status stays readable: severity has its own column, a failing newest run puts a `!` on the Actions tab, and the active tab is bracketed. Note that run-state glyphs are not all distinct -- see the feature list above |
284
324
  | `INK_SCREEN_READER=true` | Switch the renderer to a linear, unthrottled mode. The status icons carry text labels for it, but this path has not been tested against a real screen reader — treat it as unverified rather than supported. |
285
325
 
326
+ ### Saved column widths
327
+
328
+ Width choices are saved automatically, globally for the current user and
329
+ separately for each tab. They are not repository-specific. Only deviations from
330
+ the built-in defaults are stored: `r` removes the selected column's deviation,
331
+ and `R` removes every deviation for the active tab.
332
+
333
+ The versioned preference file is resolved in this order:
334
+
335
+ ```text
336
+ $XDG_CONFIG_HOME/gh-glance/preferences.json absolute XDG root
337
+ ~/Library/Application Support/gh-glance/preferences.json macOS fallback
338
+ ~/.config/gh-glance/preferences.json Linux fallback
339
+ ```
340
+
341
+ `XDG_CONFIG_HOME` is used only when it is a non-empty absolute path. An unknown
342
+ file version or a corrupt, malformed, or unreadable document falls back to the
343
+ built-in defaults rather than preventing the dashboard from starting. Within an
344
+ otherwise valid document, unknown or invalid entries are ignored individually
345
+ while valid deviations still load. If a write fails, resizing still works for
346
+ the live session and width mode reports `Widths not saved`; a later successful
347
+ write clears the warning. The UI owns this file, so direct editing is not the
348
+ primary configuration workflow.
349
+
350
+ ### Last-known-good dashboard cache
351
+
352
+ Successfully parsed dashboard rows are also saved automatically, in
353
+ `dashboard-cache.json` beside `preferences.json`:
354
+
355
+ ```text
356
+ $XDG_CONFIG_HOME/gh-glance/dashboard-cache.json
357
+ ~/Library/Application Support/gh-glance/dashboard-cache.json
358
+ ~/.config/gh-glance/dashboard-cache.json
359
+ ```
360
+
361
+ The cache is scoped to the repository and host being watched. In inferred mode,
362
+ the host and current working directory form the identity, so rows from one
363
+ checkout cannot appear in another. It retains at most five recent targets and
364
+ 60 rows per tab; a shortened cached tab keeps its `+` marker rather than
365
+ presenting the saved count as complete.
366
+
367
+ Only successfully parsed, non-blind observations replace saved rows. A failed
368
+ request can add a live error or `?` marker, but it cannot turn known Security
369
+ alerts or other last-known-good rows into an empty result. Missing, corrupt,
370
+ unknown-version, or unwritable cache state is ignored rather than blocking the
371
+ dashboard. On POSIX systems, gh-glance restricts its config directory to `0700`
372
+ and the cache file to `0600`. The file contains repository data, including
373
+ titles, authors, branches, and Security findings, but never GitHub credentials.
374
+ It is recovery state owned by the UI, not a configuration interface.
375
+
286
376
  ## GitHub Enterprise and EMU
287
377
 
288
378
  **The common case needs no configuration.** Authenticate once --
@@ -363,15 +453,17 @@ through GraphQL instead — a separate 5,000/hour allowance. With the default
363
453
 
364
454
  | Visible tab | REST / hour | GraphQL / hour |
365
455
  |---|---|---|
366
- | Actions | ~900 | ~240 |
367
- | Issues or Pull Requests | ~240 | ~1,560 |
368
- | Security | ~2,220 | ~240 |
369
-
370
- Security is the expensive one because it is three endpoints rather than one,
371
- and watching it costs just under half the REST budget. You do not have to take
372
- these figures on trust: `gh-glance --doctor` reports your actual remaining
373
- budget alongside what your configuration projects, which matters on a GitHub
374
- Enterprise tenant where the ceiling may not be 5,000 at all.
456
+ | Actions | ~1,620 | ~240 |
457
+ | Issues or Pull Requests | ~300 | ~1,560 |
458
+ | Security | ~2,280 | ~240 |
459
+
460
+ Security is the most expensive, because it is three endpoints rather than one,
461
+ and watching it costs just under half the REST budget. But Actions is not cheap
462
+ either -- it is a third of the budget, and it is the tab every pane opens on,
463
+ because one `gh run list` issues two REST requests rather than one. You do not
464
+ have to take these figures on trust: `gh-glance --doctor` reports your actual
465
+ remaining budget alongside what your configuration projects, which matters on a
466
+ GitHub Enterprise tenant where the ceiling may not be 5,000 at all.
375
467
 
376
468
  Two things pull the real number down. Any endpoint that fails in a way
377
469
  `gh-glance` recognises backs off instead of being re-asked every tick — a
@@ -380,9 +472,25 @@ hour, an authorization failure every 30 seconds, a rate limit every minute.
380
472
  Pressing `r` clears that backoff and retries immediately. And `--refresh`
381
473
  scales the whole figure: doubling the interval halves it.
382
474
 
383
- So a pane left open all day shares the budget with your other `gh` commands
384
- rather than exhausting it, but it is not free, and parking it on the Security
385
- tab is the case worth knowing about.
475
+ When a refresh fails, the last successful rows stay visible below the live
476
+ error instead of disappearing. If gh-glance is restarted while GitHub is still
477
+ rate-limiting, it loads the saved rows for that same target immediately. Their
478
+ age remains explicit through the `stale` label, and the rate-limit banner still
479
+ describes the current failed request; cached data never turns a failure into a
480
+ false success.
481
+
482
+ One pane on Actions costs about a third of an hourly REST budget, so roughly
483
+ three panes fill it. Past that, `gh-glance` widens its own poll interval: it
484
+ reads your remaining budget once a minute, works out how much of it belongs to
485
+ this pane by comparing its own spend against the token's total, and slows down
486
+ to fit -- up to a 60-second ceiling. The status bar says `throttled 18s` while
487
+ that is in effect, and `r` still refreshes immediately. Nothing is shared
488
+ between panes: the token's own counter is what they all read, so it also notices
489
+ a `gh pr checks --watch` or anything else spending on the same token.
490
+
491
+ `GH_GLANCE_REFRESH=30` sets a wider floor for every pane in a shell; `--refresh`
492
+ still wins per pane. The adaptive interval only ever widens from that floor, so
493
+ a single pane on a healthy budget stays at 5 seconds and shows no badge.
386
494
 
387
495
  ## Limitations
388
496
 
@@ -406,13 +514,21 @@ tab is the case worth knowing about.
406
514
  runs requested -- so scrolling that tab has little to move through. The count
407
515
  in the bottom edge always says how much you are seeing out of how much was
408
516
  fetched.
409
- - **Minimum width.** Each tab drops to a compact layout (icon, title, and one
410
- other column) at its own threshold, so a narrow pane keeps as much as that
411
- particular tab can fit: Security holds its full set down to 44 columns,
412
- Issues to 50, Actions to 56, Pull Requests to 61. Below 24 columns nothing
413
- useful fits, so the pane says `too narrow` and keeps the frame, tab bar and
414
- quit hint; widening it recovers immediately. The status bar has its own
415
- breakpoint and drops to bare keys (`↑↓ Ent r q`) rather than truncating.
517
+ - **Minimum width and compact layout.** Compact descriptors remain fixed and
518
+ non-adjustable. With the built-in full widths, Security fits down to 44
519
+ columns, Issues to 50, Actions to 56, and Pull Requests to 61. A narrower
520
+ preference can lower the active tab's full-layout floor. An oversized
521
+ preference is fitted temporarily toward the defaults as the pane narrows, so
522
+ it never forces compact while the stock full table still fits. The saved width
523
+ itself is not changed, and widening restores it. When no eligible full layout
524
+ fits, the active tab uses its fixed compact layout. Below 24 columns the pane
525
+ says `too narrow` and keeps the frame, tab bar and quit hint. The status bar
526
+ has its own breakpoint and drops to bare keys (`↑↓ Ent r q`) rather than
527
+ truncating.
528
+ - **Mouse reporting lifecycle.** Interactive sessions enable terminal mouse
529
+ reporting for header drags and disable it before handing the terminal back.
530
+ See [Adjusting column widths](#adjusting-column-widths) for text-selection
531
+ behavior while it is active.
416
532
  - Issues and pull requests are fetched 150 at a time and alerts 100 at a time.
417
533
  A count is shown as `n+` when it was truncated, so the number is never
418
534
  presented as exact when it is not.
@@ -464,7 +580,7 @@ in some terminals, which would shift every column to their right.
464
580
  | Security tab shows `?` instead of a number | The alert endpoints could not be read at all -- an expired SAML session, a token without `security_events`, or an org OAuth restriction. `?` means "unknown", not "zero"; run `gh-glance --doctor` to see which probe failed and how it was classified. |
465
581
  | A failing tab seems to have stopped retrying | It backs off deliberately, rather than re-spawning `gh` every five seconds against an endpoint that is refusing. Press `r` to clear the backoff and retry now. |
466
582
  | `unknown argument: -v` | `-v` used to mean `--version` and no longer does, because this CLI also has `--verbose`. Use `--version` or `--verbose` explicitly. |
467
- | `stale 2m` in the status bar | The visible tab has not refreshed successfully for a while — usually a network drop. |
583
+ | `stale 2m` in the status bar | The visible tab has not refreshed successfully for a while — usually a network drop or rate limit. Last-known-good rows can remain visible, including after restart; the live error above the table explains why they are stale. |
468
584
  | It exits immediately when piped | Intentional. It is a full-screen dashboard, not a reporting command. |
469
585
  | It stopped updating and you cannot tell why | Run `gh-glance --verbose 2>gh-glance.log`, reproduce, then read the log: one line per `gh` call with its duration and outcome. It is redacted the same way `--doctor` is, so it is safe to attach to a bug report. `--doctor --verbose` logs the probes too. |
470
586
  | `--verbose` refuses to start | stderr is still your terminal, where the log would draw over the dashboard. Redirect it to a file. |