gh-glance 0.14.1 → 0.15.1
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/CHANGELOG.md +96 -2
- package/README.md +328 -78
- package/index.mjs +6903 -632
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,99 @@ All notable changes to this project are documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.15.1] - 2026-09-17
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **A failed write no longer wedges every pane on the machine.** The shared
|
|
13
|
+
coordination lock is created with `open("wx")` and its owner record written
|
|
14
|
+
afterwards. When that write failed -- a full disk is the usual cause -- the
|
|
15
|
+
empty lock file stayed behind with no owner to test, so every pane, on every
|
|
16
|
+
request, reported "Coordinating with your other panes" until the file was
|
|
17
|
+
deleted by hand. A failed release (the rename can fail on a full APFS volume)
|
|
18
|
+
left a complete record naming a live owner that then waited on itself. Lock
|
|
19
|
+
creation now removes exactly what it created on any failure, a release that
|
|
20
|
+
cannot rename falls back to unlinking its own record, an acquirer that meets
|
|
21
|
+
its own record releases it, and an unreadable lock or recovery marker that has
|
|
22
|
+
gone untouched for ten seconds is quarantined through the same double-checked
|
|
23
|
+
protocol dead-owner locks already use -- which also clears the files 0.15.0
|
|
24
|
+
panes have already left behind.
|
|
25
|
+
- **`--doctor --probe` names why the governor is unavailable.** The API
|
|
26
|
+
governor line read `unavailable` for a lock nobody released and for a state
|
|
27
|
+
file that failed validation alike, and those are fixed in different places.
|
|
28
|
+
It now reads `unavailable (busy)`, `unavailable (corrupt)`, and so on.
|
|
29
|
+
|
|
30
|
+
## [0.15.0] - 2026-09-15
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **Equivalent panes now share one acquisition.** A private, bounded
|
|
35
|
+
authorization-partitioned store elects one producer for each due canonical
|
|
36
|
+
query and lets local followers adopt its validated generation. Conditional
|
|
37
|
+
validators, last-known-good rows, pagination state, source timestamps, and
|
|
38
|
+
capability evidence survive compatible restarts without treating cache reads
|
|
39
|
+
as fresh GitHub observations.
|
|
40
|
+
- **An optional foreground collector can serve local or SSH-carried clients.**
|
|
41
|
+
`--serve --config`, `--connect local`, `--collector-stdio`, and `--connect
|
|
42
|
+
ssh:<alias>` reuse the same governor and acquisition engine through bounded
|
|
43
|
+
Unix-socket/newline-JSON transport. Clients choose only allowlisted targets,
|
|
44
|
+
start no local GitHub request, and never fall back to standalone polling after
|
|
45
|
+
disconnect.
|
|
46
|
+
- **Collectors can opt in to durable webhook invalidation.** A loopback-only,
|
|
47
|
+
default-off listener verifies the exact raw body with HMAC SHA-256, persists a
|
|
48
|
+
compact bounded delivery queue before acknowledging it, coalesces duplicate
|
|
49
|
+
work, and requests an ordinary governed refresh. Webhook content is never row
|
|
50
|
+
data or freshness evidence, and missed events remain bounded by quiet
|
|
51
|
+
reconciliation polling.
|
|
52
|
+
- **Collectors can opt in to GitHub App installation authentication.** A
|
|
53
|
+
validated provider signs short-lived JWTs locally, mints repository- and
|
|
54
|
+
permission-restricted installation tokens through one bounded native HTTPS
|
|
55
|
+
adapter, and supplies the memory-only token only to its governed `gh api`
|
|
56
|
+
children. Installation and human quota identities remain separate; token
|
|
57
|
+
renewal preserves access identity, while repository or permission changes
|
|
58
|
+
advance an authorization fence.
|
|
59
|
+
- **Acquisition diagnostics now report measured work and freshness.** Local
|
|
60
|
+
doctor output distinguishes HTTP attempts, REST 200/304, proven and uncertain
|
|
61
|
+
core/GraphQL units, observer calls, cache hits, joined followers, queue wait,
|
|
62
|
+
active subscriptions, source success/change, and semantic holds without
|
|
63
|
+
exposing internal identity or credential material.
|
|
64
|
+
- **A deterministic sustained-efficiency gate and measurement report cover the
|
|
65
|
+
complete topology.** The offline workload advances an injected clock through
|
|
66
|
+
one simulated hour across duplicate/distinct panes and a 3+4 client split,
|
|
67
|
+
then reports sharing, reserve, freshness, queue, process, CPU, and RSS
|
|
68
|
+
evidence. Candidate/baseline percentages are emitted only for compatible
|
|
69
|
+
workload, machine, platform, and runtime evidence.
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- **Remote receipt and conditional reuse no longer invent freshness.** Source
|
|
74
|
+
success and content change remain separate across 304s, process restarts,
|
|
75
|
+
collector epochs, SSH reconnects, clock skew, and retained cache reads. A
|
|
76
|
+
disconnect preserves visible rows with an honest source-age lower bound.
|
|
77
|
+
- **The dashboard cache now retains 32 recent authorization-scoped targets, up
|
|
78
|
+
to 60 rows per tab.** Shared-acquisition storage is separately bounded to 32
|
|
79
|
+
MiB, 512 entities, 32 live targets, and 128 subscriptions, with active targets
|
|
80
|
+
pinned ahead of inactive least-recently-used generations.
|
|
81
|
+
- **GraphQL capacity is authoritative and operation-specific.** Issues, pull
|
|
82
|
+
requests, pages, and the shared observer use explicit bounded envelopes and
|
|
83
|
+
reconcile their returned costs. The free `rate_limit` endpoint remains
|
|
84
|
+
diagnostic and cannot admit work or refund a reservation.
|
|
85
|
+
|
|
86
|
+
### Security
|
|
87
|
+
|
|
88
|
+
- **Collector, SSH, webhook, and App boundaries fail closed.** Socket and state
|
|
89
|
+
ownership are nonce/inode fenced; protocol frames and queues are bounded;
|
|
90
|
+
webhook secrets and App private keys must be private regular files; SSH uses a
|
|
91
|
+
fixed forwarding-disabled argument vector; App children scrub competing token
|
|
92
|
+
variables; stale generations cannot cross provider or authorization changes;
|
|
93
|
+
and a missing optional Security permission is visible rather than rendered as
|
|
94
|
+
a clean result.
|
|
95
|
+
- **The installed npm artifact is checked as an exact five-file CLI.** It
|
|
96
|
+
contains only `index.mjs`, `package.json`, `README.md`, `CHANGELOG.md`, and
|
|
97
|
+
`LICENSE`, keeps `exports: {}`, and exercises installed version, help,
|
|
98
|
+
non-TTY, unknown-argument, collector, bridge, and client contracts. Tests,
|
|
99
|
+
fixtures, credentials, keys, webhook secrets, and configuration/state files
|
|
100
|
+
remain outside the tarball.
|
|
9
101
|
|
|
10
102
|
## [0.14.1] - 2026-09-08
|
|
11
103
|
|
|
@@ -1050,7 +1142,9 @@ engineering, security, QA and UX. What follows is what changed as a result.
|
|
|
1050
1142
|
- The `main` field from `package.json`. It advertised the file as importable,
|
|
1051
1143
|
but importing it took over the terminal or exited the host process.
|
|
1052
1144
|
|
|
1053
|
-
[Unreleased]: https://github.com/juan294/gh-glance/compare/v0.
|
|
1145
|
+
[Unreleased]: https://github.com/juan294/gh-glance/compare/v0.15.1...HEAD
|
|
1146
|
+
[0.15.1]: https://github.com/juan294/gh-glance/compare/v0.15.0...v0.15.1
|
|
1147
|
+
[0.15.0]: https://github.com/juan294/gh-glance/compare/v0.14.1...v0.15.0
|
|
1054
1148
|
[0.14.1]: https://github.com/juan294/gh-glance/compare/v0.14.0...v0.14.1
|
|
1055
1149
|
[0.14.0]: https://github.com/juan294/gh-glance/compare/v0.13.3...v0.14.0
|
|
1056
1150
|
[0.13.3]: https://github.com/juan294/gh-glance/compare/v0.13.2...v0.13.3
|
package/README.md
CHANGED
|
@@ -25,10 +25,10 @@ without switching to the browser.
|
|
|
25
25
|
╭─ Actions · owner/repo ──────────────────────────────────────────────────╮
|
|
26
26
|
│ TITLE │WORKFLOW │BRANCH │TIME │UPDATED │
|
|
27
27
|
│ ─────────────────────────────────────────────────────────────────────── │
|
|
28
|
-
│ >+ ci: pin actions to com… #443 CI develop 1m20s
|
|
29
|
-
│ x fix: restore the prima… #442 Code… develop 1m28s
|
|
30
|
-
│ + chore: bump dependenci… #441 CI dependa…int-10 30s
|
|
31
|
-
│ - docs: update the readme #440 CI develop 15s
|
|
28
|
+
│ >+ ci: pin actions to com… #443 CI develop 1m20s 44d ago │
|
|
29
|
+
│ x fix: restore the prima… #442 Code… develop 1m28s 44d ago │
|
|
30
|
+
│ + chore: bump dependenci… #441 CI dependa…int-10 30s 44d ago │
|
|
31
|
+
│ - docs: update the readme #440 CI develop 15s 45d ago │
|
|
32
32
|
│ │
|
|
33
33
|
│ │
|
|
34
34
|
╰──────────────────────────────────────────────────────────────── 4 of 4 ─╯
|
|
@@ -124,9 +124,10 @@ current.
|
|
|
124
124
|
npm install -g gh-glance
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
That is the whole install
|
|
128
|
-
build step, so there is nothing to
|
|
129
|
-
|
|
127
|
+
That is the whole install for the default standalone dashboard. `gh-glance`
|
|
128
|
+
ships as a single `index.mjs` with no build step, so there is nothing to
|
|
129
|
+
compile. The optional collector, SSH, webhook, and GitHub App modes use the
|
|
130
|
+
explicit configuration described below.
|
|
130
131
|
|
|
131
132
|
The installed package is a CLI, not a JavaScript library. The `gh-glance`
|
|
132
133
|
executable is supported; package-root and deep imports are intentionally
|
|
@@ -141,9 +142,9 @@ page](https://www.npmjs.com/package/gh-glance) shows the exact source.
|
|
|
141
142
|
|
|
142
143
|
### First run
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
local `gh` CLI. It does not
|
|
146
|
-
that account before starting the dashboard:
|
|
145
|
+
The default standalone dashboard uses the credentials and active account
|
|
146
|
+
already selected by your local `gh` CLI. It does not persist or display the
|
|
147
|
+
token. Check that account before starting the dashboard:
|
|
147
148
|
|
|
148
149
|
```bash
|
|
149
150
|
gh auth status
|
|
@@ -323,16 +324,233 @@ pane definition. Flags are there when you want them:
|
|
|
323
324
|
| `--background <mode>` | `all` (default) or `off`. `off` never requests data for a tab you are not looking at. Its count keeps the last known value and ages visibly, and switching to the tab fetches it. |
|
|
324
325
|
| `--verbose` | Log one line per dashboard `gh` call to stderr, with timing and outcome. Credential lookup and the account-identity proof are deliberately excluded, so that they cannot log anything derived from a token. stderr must be redirected: `gh-glance --verbose 2>gh-glance.log`. |
|
|
325
326
|
| `--doctor` | Print a diagnostic report and exit. See [Diagnostics](#diagnostics). |
|
|
327
|
+
| `--doctor --probe` | Add bounded, admitted GitHub capability probes to the local diagnostic report. |
|
|
328
|
+
| `--serve --config <path>` | Run the optional local collector in the foreground from a private, versioned JSON allowlist. |
|
|
329
|
+
| `--connect local` | Subscribe the dashboard to the current OS user's collector. No GitHub request runs in the client process. |
|
|
330
|
+
| `--connect ssh:<alias>` | Subscribe through the named SSH config alias. The client starts only the fixed remote collector bridge and never falls back to local GitHub acquisition. |
|
|
331
|
+
| `--collector-stdio` | Bridge bounded newline JSON on stdin/stdout to an already running local collector. |
|
|
326
332
|
|
|
327
333
|
An unrecognised flag exits 2 rather than being ignored, so a typo fails loudly.
|
|
328
334
|
|
|
335
|
+
### Optional local collector
|
|
336
|
+
|
|
337
|
+
The collector is opt-in and stays in the foreground. It is supported on macOS
|
|
338
|
+
and Linux. Windows continues to support the standalone dashboard, but rejects
|
|
339
|
+
collector hosting and bridge modes. The endpoint is always
|
|
340
|
+
`collector-v1.sock` inside gh-glance's private config directory; there is no TCP
|
|
341
|
+
dashboard/client transport or custom socket flag. The optional loopback webhook
|
|
342
|
+
ingress described below is the only HTTP listener.
|
|
343
|
+
|
|
344
|
+
Create a mode-0600 configuration file:
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{
|
|
348
|
+
"version": 1,
|
|
349
|
+
"providers": {
|
|
350
|
+
"personal": { "type": "gh", "host": "github.com" }
|
|
351
|
+
},
|
|
352
|
+
"targets": [
|
|
353
|
+
{ "host": "github.com", "repo": "owner/repository", "provider": "personal" }
|
|
354
|
+
]
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Then run the service and dashboard in separate terminals:
|
|
359
|
+
|
|
360
|
+
```sh
|
|
361
|
+
gh-glance --serve --config ~/.config/gh-glance/collector.json
|
|
362
|
+
gh-glance --connect local --repo owner/repository
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Providers use the server OS user's existing host-specific `gh` login. Clients
|
|
366
|
+
select only an allowlisted `(host, repo)` pair and cannot submit credentials,
|
|
367
|
+
providers, API paths, queries, commands, or filesystem paths. Renamed aliases
|
|
368
|
+
that resolve to one repository cannot cross provider ownership. Disconnecting
|
|
369
|
+
keeps last-known-good rows and their source age, then reconnects to a new server
|
|
370
|
+
epoch without silently falling back to standalone polling.
|
|
371
|
+
|
|
372
|
+
#### Optional GitHub App provider
|
|
373
|
+
|
|
374
|
+
The default `gh` provider above is unchanged. A collector can instead opt in to
|
|
375
|
+
one GitHub App installation. gh-glance does not create or register the App,
|
|
376
|
+
install it, grant permissions, or generate its private key. Put the existing
|
|
377
|
+
PEM key in an absolute, current-user-owned mode-0600 file and name every allowed
|
|
378
|
+
repository ID and read permission explicitly:
|
|
379
|
+
|
|
380
|
+
```json
|
|
381
|
+
{
|
|
382
|
+
"version": 1,
|
|
383
|
+
"providers": {
|
|
384
|
+
"automation": {
|
|
385
|
+
"type": "github-app",
|
|
386
|
+
"host": "github.com",
|
|
387
|
+
"clientId": "Iv1.example",
|
|
388
|
+
"installationId": 123456,
|
|
389
|
+
"privateKeyFile": "/absolute/private/path/github-app.pem",
|
|
390
|
+
"repositoryIds": [123456789],
|
|
391
|
+
"permissions": {
|
|
392
|
+
"metadata": "read",
|
|
393
|
+
"actions": "read",
|
|
394
|
+
"issues": "read",
|
|
395
|
+
"pull_requests": "read",
|
|
396
|
+
"vulnerability_alerts": "read",
|
|
397
|
+
"security_events": "read",
|
|
398
|
+
"secret_scanning_alerts": "read"
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
"targets": [
|
|
403
|
+
{ "host": "github.com", "repo": "owner/repository", "provider": "automation" }
|
|
404
|
+
]
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
`metadata`, `actions`, `issues`, and `pull_requests` are required for the four
|
|
409
|
+
collector tabs. The three Security permissions are independent and optional:
|
|
410
|
+
|
|
411
|
+
| Security surface | Configuration permission |
|
|
412
|
+
|---|---|
|
|
413
|
+
| Dependabot alerts | `vulnerability_alerts: read` |
|
|
414
|
+
| Code scanning alerts | `security_events: read` |
|
|
415
|
+
| Secret scanning alerts | `secret_scanning_alerts: read` |
|
|
416
|
+
|
|
417
|
+
An omitted Security permission produces an explicit unavailable capability for
|
|
418
|
+
that source without disabling the other tabs or permitted Security sources.
|
|
419
|
+
The collector signs a short-lived JWT locally, requests only the configured
|
|
420
|
+
repositories and permissions, and keeps the installation token in memory. Data
|
|
421
|
+
still travels through `gh api`; the selected token is supplied only in that
|
|
422
|
+
child's environment. Token refresh, failure, or expiry never falls back to the
|
|
423
|
+
server user's personal `gh` credential. Configuration changes require a
|
|
424
|
+
collector restart. Installation deletion, suspension, and repository removal
|
|
425
|
+
webhooks fence old data when webhook ingress is also enabled.
|
|
426
|
+
|
|
427
|
+
#### Optional webhook invalidation
|
|
428
|
+
|
|
429
|
+
Webhook ingress is disabled unless the collector configuration contains an
|
|
430
|
+
enabled `webhook` object. It listens only on the configured loopback address and
|
|
431
|
+
fixed `/webhooks/github` route. Exposing that route requires a user-managed
|
|
432
|
+
HTTPS reverse proxy; gh-glance does not create a public listener, register a
|
|
433
|
+
GitHub webhook, or manage proxy/TLS infrastructure.
|
|
434
|
+
|
|
435
|
+
Create a separate mode-0600 secret file with no trailing newline, then add the
|
|
436
|
+
exact repositories and resource families covered by the GitHub webhook:
|
|
437
|
+
|
|
438
|
+
```json
|
|
439
|
+
{
|
|
440
|
+
"version": 1,
|
|
441
|
+
"providers": {
|
|
442
|
+
"personal": { "type": "gh", "host": "github.com" }
|
|
443
|
+
},
|
|
444
|
+
"targets": [
|
|
445
|
+
{ "host": "github.com", "repo": "owner/repository", "provider": "personal" }
|
|
446
|
+
],
|
|
447
|
+
"webhook": {
|
|
448
|
+
"enabled": true,
|
|
449
|
+
"address": "127.0.0.1",
|
|
450
|
+
"port": 8787,
|
|
451
|
+
"secretFile": "/absolute/private/path/webhook-secret",
|
|
452
|
+
"targets": [
|
|
453
|
+
{
|
|
454
|
+
"host": "github.com",
|
|
455
|
+
"repo": "owner/repository",
|
|
456
|
+
"provider": "personal",
|
|
457
|
+
"resources": ["actions", "issues", "prs", "security"]
|
|
458
|
+
}
|
|
459
|
+
]
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Configure GitHub to send JSON payloads and select only the events needed for
|
|
465
|
+
the covered resources: `workflow_run`/`workflow_job` for Actions, `issues` and
|
|
466
|
+
issue comments for Issues, pull requests/reviews/review comments/review threads
|
|
467
|
+
and PR issue comments for PRs, and the supported Dependabot, repository
|
|
468
|
+
vulnerability, code-scanning, and secret-scanning alert events for Security.
|
|
469
|
+
Installation and repository-access events fence permissions. Event availability
|
|
470
|
+
depends on the target GitHub host. The collector verifies the SHA-256 signature
|
|
471
|
+
over the exact request bytes before parsing, persists only delivery IDs and
|
|
472
|
+
compact invalidations, and returns 202 only after durable acceptance. The
|
|
473
|
+
listener buffers at most 25 MiB per request and 50 MiB across concurrent
|
|
474
|
+
requests; aggregate pressure returns 503 so GitHub or the proxy can retry. A
|
|
475
|
+
local signed-delivery check can use the same payload bytes and secret to POST
|
|
476
|
+
`Content-Type: application/json`, `X-GitHub-Event`, `X-GitHub-Delivery`, and
|
|
477
|
+
`X-Hub-Signature-256: sha256=<hex digest>` to
|
|
478
|
+
`http://127.0.0.1:8787/webhooks/github`.
|
|
479
|
+
|
|
480
|
+
For example, with the collector running and the paths adjusted locally:
|
|
481
|
+
|
|
482
|
+
```sh
|
|
483
|
+
printf '%s' '{"action":"opened","repository":{"full_name":"owner/repository"},"issue":{"number":1}}' > /tmp/gh-glance-hook.json
|
|
484
|
+
signature=$(node --input-type=module - /absolute/private/path/webhook-secret /tmp/gh-glance-hook.json <<'NODE'
|
|
485
|
+
import { createHmac } from "node:crypto";
|
|
486
|
+
import { readFileSync } from "node:fs";
|
|
487
|
+
const [, , secretPath, bodyPath] = process.argv;
|
|
488
|
+
process.stdout.write(createHmac("sha256", readFileSync(secretPath)).update(readFileSync(bodyPath)).digest("hex"));
|
|
489
|
+
NODE
|
|
490
|
+
)
|
|
491
|
+
curl -i --data-binary @/tmp/gh-glance-hook.json \
|
|
492
|
+
-H 'Content-Type: application/json' \
|
|
493
|
+
-H 'X-GitHub-Event: issues' \
|
|
494
|
+
-H 'X-GitHub-Delivery: 11111111-1111-4111-8111-111111111111' \
|
|
495
|
+
-H "X-Hub-Signature-256: sha256=$signature" \
|
|
496
|
+
http://127.0.0.1:8787/webhooks/github
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
The expected response is 202. Repeating the same delivery ID is acknowledged
|
|
500
|
+
without scheduling duplicate work.
|
|
501
|
+
|
|
502
|
+
Webhook events only request an ordinary governed GitHub refresh. They never
|
|
503
|
+
become rows or freshness evidence, and they do not bypass quota or secondary
|
|
504
|
+
cooldowns. After two validated unchanged observations, covered quiet resources
|
|
505
|
+
reconcile at least every
|
|
506
|
+
`max(--refresh, 300 seconds)`; Actions with running work keep their fast cadence,
|
|
507
|
+
and manual refresh remains available. Missing deliveries therefore delay an
|
|
508
|
+
update until reconciliation instead of disabling polling.
|
|
509
|
+
|
|
510
|
+
### Optional SSH collector client
|
|
511
|
+
|
|
512
|
+
An SSH client connects to a collector already running under your account on a
|
|
513
|
+
computer you control. Configure the host, user, port, key, jump host, and host
|
|
514
|
+
trust in `~/.ssh/config`; the CLI accepts only a simple alias:
|
|
515
|
+
|
|
516
|
+
```sh
|
|
517
|
+
# On the collector computer
|
|
518
|
+
gh-glance --serve --config ~/.config/gh-glance/collector.json
|
|
519
|
+
|
|
520
|
+
# On a client computer, after normal ssh host-key/key setup
|
|
521
|
+
gh-glance --connect ssh:my-collector --repo owner/repository
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
The client runs exactly `ssh -T -o BatchMode=yes -o ClearAllForwardings=yes -o
|
|
525
|
+
ForwardAgent=no -- <alias> 'gh-glance --collector-stdio'`. It does not disable host-key checking,
|
|
526
|
+
install software or keys, start the collector, create forwarding, or send the
|
|
527
|
+
repository through a shell command. `gh-glance` must already be on the remote
|
|
528
|
+
login's `PATH`, and the collector must already be running there.
|
|
529
|
+
|
|
530
|
+
Local `GH_*`, `GITHUB_*`, `GH_CONFIG_DIR`, and `XDG_CONFIG_HOME` values are not
|
|
531
|
+
given to the SSH child. Repository rows arrive through the same bounded protocol
|
|
532
|
+
as a local client. A disconnect keeps them visible as `Disconnected` and retries
|
|
533
|
+
after a bounded 1, 2, 4, 8, 16, then 30-second ladder with jitter. A new server
|
|
534
|
+
epoch resubscribes once; old epoch/generation frames cannot overwrite it.
|
|
535
|
+
|
|
536
|
+
Remote snapshots carry the source observation time and collector wall time.
|
|
537
|
+
The client persists the resulting conservative age under a private digest of
|
|
538
|
+
the SSH alias plus target. Reconnect, receipt time, a backward local clock, and
|
|
539
|
+
a cold offline restart cannot make the same observation look younger. A newer
|
|
540
|
+
successful source observation can reset the age. Pressing `Enter` still opens a
|
|
541
|
+
validated HTTPS target URL on the client computer and makes no GitHub API call.
|
|
542
|
+
|
|
543
|
+
`gh-glance --connect ssh:my-collector --repo owner/repository --doctor` is a
|
|
544
|
+
local-only report of the selected source and disabled fallback. Live endpoint
|
|
545
|
+
probes are intentionally rejected for collector clients.
|
|
546
|
+
|
|
329
547
|
Environment variables work too, and the flags take precedence:
|
|
330
548
|
|
|
331
549
|
| Variable | Effect |
|
|
332
550
|
|---|---|
|
|
333
551
|
| `GH_REPO=[host/]owner/name` | Watch a specific repository instead of the current directory's. A qualified value supplies the host; an unqualified value means `github.com`. An explicit `--repo` wins. |
|
|
334
552
|
| `GH_HOST=<host>` | When no explicit `--repo` overrides it, send every call and the account governor to a GitHub Enterprise or EMU host instead of `github.com`. |
|
|
335
|
-
| `GH_TOKEN`, `GITHUB_TOKEN`, `GH_ENTERPRISE_TOKEN`, `GITHUB_ENTERPRISE_TOKEN` | Used by `gh
|
|
553
|
+
| `GH_TOKEN`, `GITHUB_TOKEN`, `GH_ENTERPRISE_TOKEN`, `GITHUB_ENTERPRISE_TOKEN` | Used by the default `gh` provider with `gh`'s host-specific precedence. gh-glance never logs or stores the raw values. One-way credential digests keep private rows access-partitioned; credentials GitHub verifies as the same principal share its quota ledger. App-provider children remove these variables before setting their memory-only installation token. |
|
|
336
554
|
| `GH_CONFIG_DIR` | Selects `gh`'s account configuration and contributes its normalized identity to the cache and governor namespaces. Separate values can isolate simultaneous panes on different accounts; see [No account pinning](#limitations). |
|
|
337
555
|
| `GH_GLANCE_REFRESH=<seconds>` | Minimum active-tab poll interval, 2-3600. Sets the floor for every pane in a shell; `--refresh` takes precedence. See [Rate limit](#rate-limit). |
|
|
338
556
|
| `GH_GLANCE_ICONS=unicode` | Unicode status glyphs and single-cell text substitutes for Nerd Font row icons |
|
|
@@ -376,13 +594,15 @@ $XDG_CONFIG_HOME/gh-glance/dashboard-cache.json
|
|
|
376
594
|
~/.config/gh-glance/dashboard-cache.json
|
|
377
595
|
```
|
|
378
596
|
|
|
379
|
-
The cache is scoped to the repository, host, and effective
|
|
380
|
-
|
|
381
|
-
the identity, so rows from one checkout or credential context cannot
|
|
382
|
-
another.
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
597
|
+
The cache is scoped to the repository, host, provider, and effective access
|
|
598
|
+
partition. In inferred standalone mode, the host and current working directory
|
|
599
|
+
also form the identity, so rows from one checkout or credential context cannot
|
|
600
|
+
appear in another. Human-provider access includes one-way digests of the
|
|
601
|
+
selected credential and its authorization generation; App-provider access also
|
|
602
|
+
binds the configured repository and permission restrictions. Raw credentials
|
|
603
|
+
are never stored. It retains at most 32 recent targets and 60 rows per tab; a
|
|
604
|
+
shortened cached tab keeps its `+` marker rather than presenting the saved count
|
|
605
|
+
as exact.
|
|
386
606
|
|
|
387
607
|
Only successfully parsed, non-blind observations replace saved rows. A failed
|
|
388
608
|
request can add a live error or `?` marker, but it cannot turn known Security
|
|
@@ -486,30 +706,38 @@ It will not claim a feature is "not enabled" because of a lapse.
|
|
|
486
706
|
gh-glance --doctor > report.txt
|
|
487
707
|
```
|
|
488
708
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
709
|
+
By default, doctor reads only local evidence. It reports the executable and
|
|
710
|
+
configuration context, repository target, and shared-acquisition metrics and
|
|
711
|
+
freshness without making a GitHub API request or resolving a credential. The
|
|
712
|
+
acquisition section keeps the last successful observation, last payload change,
|
|
713
|
+
next due time, current hold, active query/subscriber count, request outcomes,
|
|
714
|
+
known charges, conservative outstanding charges, cache hits, joined followers,
|
|
715
|
+
measured queue time, observer calls, and producer epoch separate. A joined
|
|
716
|
+
follower is counted only when it waits on an existing producer generation; a
|
|
717
|
+
cached subscription is a cache hit, not a coalesced request. Active queries are
|
|
718
|
+
queries with a live subscriber. Retained queries remain listed as `cache-only`
|
|
719
|
+
without inflating that count. Holds distinguish observer failure, primary and
|
|
720
|
+
secondary limits, disconnected acquisition, shared producer waits, and local
|
|
721
|
+
coordination.
|
|
722
|
+
|
|
723
|
+
Use `gh-glance --doctor --probe` when live capability evidence is needed. Probe
|
|
724
|
+
mode uses an ephemeral governor lease and admits every quota-consuming endpoint
|
|
725
|
+
at its exact declared cost. It includes the repository and bounded dashboard
|
|
726
|
+
probes, including Security priority lanes, with the exact argv, outcome, and
|
|
727
|
+
classification (`unavailable`, `rate-limited`, `auth-problem` or `other`). A
|
|
728
|
+
probe whose safe slot is later, whose resource is held, or whose budget is
|
|
729
|
+
unavailable is reported as `SKIPPED`; diagnostics do not bypass the reserve.
|
|
730
|
+
The `API governor` section then reports its measured health and resource state.
|
|
731
|
+
Neither mode prints scope hashes, account identity keys, state paths, lock
|
|
732
|
+
owners, reservation IDs, response bodies, or credentials.
|
|
506
733
|
|
|
507
734
|
The `Repository access` probe shows whether the target resolves for the active
|
|
508
735
|
`gh` credentials. A failed GitHub resolution response cannot distinguish a
|
|
509
736
|
nonexistent, renamed, or stale target from a private repository hidden from
|
|
510
737
|
that identity.
|
|
511
738
|
|
|
512
|
-
Add `--verbose` to get a log of every `gh` call it makes alongside
|
|
739
|
+
Add `--verbose` to probe mode to get a log of every `gh` call it makes alongside
|
|
740
|
+
the report. Plain doctor has no GitHub API calls to log.
|
|
513
741
|
|
|
514
742
|
It exits 0 and prints a report even when `gh` is missing or you are outside a
|
|
515
743
|
git repository -- those are conditions worth reporting rather than failing on --
|
|
@@ -548,30 +776,24 @@ payload never counts as one: a tab that cannot be read is not a quiet tab. One
|
|
|
548
776
|
inactive tab is considered per wake, in rotation, so three of them falling due
|
|
549
777
|
together do not start together.
|
|
550
778
|
|
|
551
|
-
The resources are independent. Actions and Security
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
full uses the three base endpoint calls and lands near 2,280 REST requests per
|
|
570
|
-
hour. A full page activates bounded critical/high lanes for Dependabot and code
|
|
571
|
-
scanning, raising the safe projection to about 4,440. Actions is not cheap
|
|
572
|
-
either: one `gh run list` issues two REST requests. `gh-glance --doctor` reports
|
|
573
|
-
this projection beside the server's actual REST and GraphQL budgets. Enterprise
|
|
574
|
-
ceilings can differ from 5,000.
|
|
779
|
+
The resources are independent. Actions and Security use REST `core`; Issues
|
|
780
|
+
and Pull Requests use explicit GraphQL documents. The governor's declared costs
|
|
781
|
+
are conservative admission bounds. They are not presented as fixed hourly
|
|
782
|
+
charges because pagination, conditional REST 304 responses, quiet cadence,
|
|
783
|
+
workflow-name fallback, capability backoff, and shared followers all change
|
|
784
|
+
the actual result.
|
|
785
|
+
|
|
786
|
+
Plain `--doctor` reports reconciled local acquisition totals: HTTP attempts,
|
|
787
|
+
failed outcomes, REST 200/304 outcomes, proven actual core and GraphQL units,
|
|
788
|
+
conservative outstanding units, and measured follower queue delay. Probe mode
|
|
789
|
+
adds live governor evidence. These measured
|
|
790
|
+
figures stay separate from the policy-derived `projected demand` range.
|
|
791
|
+
|
|
792
|
+
Security can require the most REST work: each source starts with one newest
|
|
793
|
+
page, and full pages can activate bounded critical/high lanes for Dependabot
|
|
794
|
+
and code scanning. Actions normally requests its runs endpoint and admits a
|
|
795
|
+
separate conditional workflow-catalog request only when displayed runs lack a
|
|
796
|
+
known workflow name. Enterprise ceilings can differ from GitHub.com's.
|
|
575
797
|
|
|
576
798
|
For each resource, gh-glance calculates 20% of the reported limit for other work:
|
|
577
799
|
|
|
@@ -590,11 +812,12 @@ clean probe can account for it. Missing, stale, corrupt, locked, or unwritable
|
|
|
590
812
|
coordination denies the call instead of returning to five-second polling.
|
|
591
813
|
|
|
592
814
|
Core observations come from real response headers, so the core reserve has a
|
|
593
|
-
closed feedback loop. GraphQL
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
815
|
+
closed feedback loop. GraphQL uses explicit bounded queries whose envelopes
|
|
816
|
+
report actual cost and counter evidence. One shared, separately accounted
|
|
817
|
+
GraphQL observer establishes spendable capacity; the free `rate_limit` endpoint
|
|
818
|
+
is diagnostic only and can never authorize or refund work. Missing or stale
|
|
819
|
+
authoritative evidence fails closed rather than falling back to an inferred
|
|
820
|
+
counter.
|
|
598
821
|
|
|
599
822
|
One pane owns the free `rate_limit` probe for a control window and publishes it
|
|
600
823
|
for the others. Manual and diagnostic work is considered before tab-switch,
|
|
@@ -620,17 +843,44 @@ age remains explicit through the `stale` label, and the rate-limit banner still
|
|
|
620
843
|
describes the current failed request; cached data never turns a failure into a
|
|
621
844
|
false success.
|
|
622
845
|
|
|
623
|
-
The enforceable boundary is
|
|
624
|
-
evidence. Another program can spend after the probe, and panes
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
846
|
+
The enforceable boundary is admission from fresh, conservatively debited
|
|
847
|
+
evidence. Another program can spend after the probe, and standalone panes in a
|
|
848
|
+
different local account scope cannot share the same coordination file. Optional
|
|
849
|
+
SSH clients can instead share the collector machine's one acquisition and quota
|
|
850
|
+
scope while making zero client-side GitHub requests. GitHub does not offer an
|
|
851
|
+
atomic global quota reservation. The account-wide counter still lets gh-glance
|
|
852
|
+
measure external use and reduce future capacity, but the hard reserve is not a
|
|
853
|
+
claim that unrelated consumers can never cross it.
|
|
629
854
|
|
|
630
855
|
`GH_GLANCE_REFRESH=30` sets a wider floor for every pane in a shell; `--refresh`
|
|
631
856
|
still wins per pane. A single pane on a healthy budget normally stays at its
|
|
632
857
|
floor and shows Watching between checks.
|
|
633
858
|
|
|
859
|
+
### Efficiency verification
|
|
860
|
+
|
|
861
|
+
The repository includes an offline sustained-workload gate for the coordination
|
|
862
|
+
contracts described above:
|
|
863
|
+
|
|
864
|
+
```sh
|
|
865
|
+
npm run test:efficiency
|
|
866
|
+
npm run measure:efficiency
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
The deterministic test advances an injected clock through one simulated hour
|
|
870
|
+
while using the production acquisition and governor machinery. It covers one,
|
|
871
|
+
two, seven, and ten panes, duplicate and distinct repositories, and a 3+4 remote
|
|
872
|
+
client split, with running and quiet CI, conditional responses, external spend,
|
|
873
|
+
primary reset, secondary hold, producer loss, and an account switch. The fake
|
|
874
|
+
SSH route invokes the real bridge and collector; no real GitHub account, App,
|
|
875
|
+
webhook, or second computer is used.
|
|
876
|
+
|
|
877
|
+
The measurement command reports operation-level HTTP outcomes, proven and
|
|
878
|
+
uncertain quota, observer work, coalescing, queue delay, source-to-display
|
|
879
|
+
percentiles, subprocesses, wall time, CPU, and RSS. A percentage is shown only
|
|
880
|
+
when the candidate and baseline used a compatible workload, machine, platform,
|
|
881
|
+
and runtime. Missing or incompatible baseline values stay labelled as such;
|
|
882
|
+
fixture performance is not presented as live GitHub performance.
|
|
883
|
+
|
|
634
884
|
## Limitations
|
|
635
885
|
|
|
636
886
|
- **Security tab**: code scanning and secret scanning alerts require [GitHub
|
|
@@ -720,23 +970,23 @@ GH_GLANCE_ICONS=ascii gh-glance
|
|
|
720
970
|
| `No GitHub remote found` | Press `Enter` to start `gh repo create`, then choose **Push an existing local repository**. Or press `q` and run `gh-glance --repo owner/name` to watch an existing repository without attaching this folder. |
|
|
721
971
|
| `GitHub login or authorization required` | Run `gh auth status`. With no account, run `gh auth login`; with an expired authorization, run `gh auth refresh`; then press `R`, which also clears the endpoint backoff the failures accrued. |
|
|
722
972
|
| `Repository not found or inaccessible to the active gh account` | The active identity cannot resolve the target. Check `gh auth status`, `git remote -v` or the explicit `--repo`, and use `gh auth switch` only if the wrong account is active. |
|
|
723
|
-
| `GraphQL: Could not resolve to a Repository...` in older gh-glance versions | The response has the same ambiguity: a missing or renamed target, or a private repository not visible to the active account. Run `gh-glance --doctor`. |
|
|
724
|
-
| Actions says `not available for this repository` while Repository access is `ok` | The repository resolved, but that endpoint is unavailable; inspect the corresponding doctor block. |
|
|
973
|
+
| `GraphQL: Could not resolve to a Repository...` in older gh-glance versions | The response has the same ambiguity: a missing or renamed target, or a private repository not visible to the active account. Run `gh-glance --doctor --probe`. |
|
|
974
|
+
| Actions says `not available for this repository` while Repository access is `ok` | The repository resolved, but that endpoint is unavailable; inspect the corresponding `--doctor --probe` block. |
|
|
725
975
|
| Row icons are blank boxes | Your terminal font is not a Nerd Font. Use `GH_GLANCE_ICONS=unicode`. |
|
|
726
976
|
| Security tab shows a "not enabled" note | Code scanning and secret scanning need GitHub Advanced Security. Dependabot alerts work independently. The note now appears only when the feature genuinely is unavailable: auth, SSO and network failures show the real error instead. |
|
|
727
977
|
| `none of the git remotes ... point to a known GitHub host` | `gh` is not authenticated for that host. Run `gh auth login --hostname <host>`. |
|
|
728
978
|
| Security tab empty on an enterprise host in an older gh-glance version | Older versions could route alert endpoints separately. Current gh-glance routes every API call and the governor to one effective host; use `--repo host/owner/name`, `GH_HOST`, or a qualified `GH_REPO`, then confirm the resolved host with `--doctor`. |
|
|
729
|
-
| Tabs start failing after working for a while | The enterprise SAML session lapsed. Re-authorize in the browser; the dashboard recovers within about 30 seconds. Run `gh-glance --doctor` to confirm. |
|
|
979
|
+
| Tabs start failing after working for a while | The enterprise SAML session lapsed. Re-authorize in the browser; the dashboard recovers within about 30 seconds. Run `gh-glance --doctor --probe` to confirm. |
|
|
730
980
|
| A tab's count is red | That tab's last fetch failed. The error itself is shown when you switch to it, translated into what to do about it where `gh-glance` recognises the failure. |
|
|
731
|
-
| 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. |
|
|
981
|
+
| 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 --probe` to see which probe failed and how it was classified. |
|
|
732
982
|
| `Watching` with `next 2m` | The active tab has a shared budget probe or safe grant scheduled. The interval names this grant only; it is not a recurring polling interval. Pressing `r` raises safe priority but cannot bypass the lane or reserve. |
|
|
733
|
-
| `
|
|
983
|
+
| `Shared` | This pane is using a generation acquired for matching local subscribers. This is coalescing, not quota scarcity. |
|
|
734
984
|
| `Paused` with a reset time | The active tab's REST or GraphQL resource is at its reserve, exhausted, or under a shared rate-limit block. Wait for the stated reset/probe. Other tabs can continue when they use the healthy resource. |
|
|
735
985
|
| `Paused` without a reset time | Budget or coordinator evidence is unknown, corrupt, locked, or unwritable. No data call is started. Run `gh-glance --doctor`; also check the config directory permissions and whether another live process owns its private lock. |
|
|
736
986
|
| A failing tab seems to have stopped retrying | Recognized endpoint failures back off rather than re-spawning `gh` at the floor. Press `r` to request a higher-priority retry, or `R` to also clear the endpoint backoff; either way the retry waits for a safe grant, so the tab remains Watching or Paused. |
|
|
737
987
|
| `unknown argument: -v` | `-v` used to mean `--version` and no longer does, because this CLI also has `--verbose`. Use `--version` or `--verbose` explicitly. |
|
|
738
|
-
| Cached rows plus `Paused`
|
|
739
|
-
| Repeated GitHub rate-limit messages | A classified rate-limit response is published as one shared resource block. Local panes make no data retry before that block's probe/reset deadline. Use `--doctor` to inspect the resource and reset; repeated manual refresh cannot override it. |
|
|
988
|
+
| Cached rows plus `Paused`, `Stale`, or `Disconnected` | The rows are last-known-good data while the live source is blocked, overdue, or unavailable. A cache read never advances source success. The footer never calls stale or disconnected rows `Watching`. |
|
|
989
|
+
| Repeated GitHub rate-limit messages | A classified rate-limit response is published as one shared resource block. Local panes make no data retry before that block's probe/reset deadline. Use `--doctor --probe` to inspect the live resource and reset; repeated manual refresh cannot override it. |
|
|
740
990
|
| It exits immediately when piped | Intentional. It is a full-screen dashboard, not a reporting command. |
|
|
741
991
|
| `Restart required: close older gh-glance panes` | A pane from an older release still holds a live lease in the previous coordination format, which this version cannot join. Quit those panes; this one resumes on its own. Nothing is killed for you, and no state needs deleting. |
|
|
742
992
|
| `Upgrade waiting for legacy quota reset` | The older panes are gone, but left spend that cannot be proven settled. It waits for the affected rate-limit window to reset rather than assume the quota is free. This one has a deadline and clears itself. |
|