@cldmv/gh-broker 0.7.0 → 0.9.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 (107) hide show
  1. package/README.md +165 -41
  2. package/config.example.json +38 -9
  3. package/config.sample.json +45 -0
  4. package/deploy/README.md +76 -17
  5. package/deploy/install.mjs +171 -104
  6. package/deploy/install.sh +0 -0
  7. package/deploy/lib/config-merge.mjs +174 -16
  8. package/deploy/lib/credentials.mjs +143 -0
  9. package/deploy/lib/install-args.mjs +144 -0
  10. package/deploy/lib/npm-setup.mjs +164 -0
  11. package/deploy/lib/secret-store.mjs +20 -2
  12. package/deploy/lib/system.mjs +26 -2
  13. package/dist/index.mjs +5864 -1809
  14. package/dist/index.mjs.map +4 -4
  15. package/package.json +18 -8
  16. package/.configs/fix-headers.json +0 -3
  17. package/.configs/vitest.config.mjs +0 -44
  18. package/.github/dependabot.yml +0 -111
  19. package/.github/workflows/branch-retention.yml +0 -44
  20. package/.github/workflows/bundle-size.yml +0 -52
  21. package/.github/workflows/ci.yml +0 -389
  22. package/.github/workflows/cla.yml +0 -66
  23. package/.github/workflows/codeql.yml +0 -87
  24. package/.github/workflows/dependabot-auto-merge.yml +0 -62
  25. package/.github/workflows/dependabot-recreate.yml +0 -67
  26. package/.github/workflows/dependency-review.yml +0 -46
  27. package/.github/workflows/feature-pr.yml +0 -75
  28. package/.github/workflows/hotfix-redirector.yml +0 -67
  29. package/.github/workflows/hotfixes-release.yml +0 -70
  30. package/.github/workflows/labeler.yml +0 -48
  31. package/.github/workflows/master-commit-audit.yml +0 -56
  32. package/.github/workflows/member-auto-merge.yml +0 -75
  33. package/.github/workflows/next-release.yml +0 -76
  34. package/.github/workflows/next-reset.yml +0 -50
  35. package/.github/workflows/pr-notify.yml +0 -44
  36. package/.github/workflows/pr-title-normalizer.yml +0 -52
  37. package/.github/workflows/provenance.yml +0 -51
  38. package/.github/workflows/publish.yml +0 -146
  39. package/.github/workflows/release-merge.yml +0 -104
  40. package/.github/workflows/release-notify.yml +0 -41
  41. package/.github/workflows/scorecard.yml +0 -64
  42. package/.github/workflows/stale.yml +0 -50
  43. package/.github/workflows/tag-health.yml +0 -70
  44. package/.github/workflows/update-major-version-tags.yml +0 -94
  45. package/.github/workflows/v4-bootstrap.yml +0 -115
  46. package/.github/workflows/welcome.yml +0 -42
  47. package/docs/TOOLS.md +0 -347
  48. package/docs/agent-user-model.md +0 -89
  49. package/docs/changelog/v0/v0.2.0.md +0 -26
  50. package/docs/changelog/v0/v0.2.1.md +0 -23
  51. package/docs/changelog/v0/v0.2.2.md +0 -23
  52. package/docs/changelog/v0/v0.2.3.md +0 -33
  53. package/docs/changelog/v0/v0.2.4.md +0 -25
  54. package/docs/changelog/v0/v0.2.5.md +0 -29
  55. package/docs/changelog/v0/v0.3.0.md +0 -84
  56. package/docs/changelog/v0/v0.3.1.md +0 -30
  57. package/docs/changelog/v0/v0.3.2.md +0 -24
  58. package/docs/changelog/v0/v0.3.3.md +0 -28
  59. package/docs/changelog/v0/v0.4.0.md +0 -58
  60. package/docs/changelog/v0/v0.4.1.md +0 -33
  61. package/docs/changelog/v0/v0.4.2.md +0 -45
  62. package/docs/changelog/v0/v0.5.0.md +0 -39
  63. package/docs/changelog/v0/v0.5.1.md +0 -28
  64. package/docs/changelog/v0/v0.6.0.md +0 -52
  65. package/docs/changelog/v0/v0.6.1.md +0 -48
  66. package/docs/changelog/v0/v0.7.0.md +0 -71
  67. package/docs/deploy-architecture.md +0 -82
  68. package/src/appauth.ts +0 -56
  69. package/src/audit.ts +0 -34
  70. package/src/catalog.ts +0 -165
  71. package/src/config.ts +0 -130
  72. package/src/index.ts +0 -107
  73. package/src/paths.ts +0 -60
  74. package/src/router.ts +0 -258
  75. package/src/secrets.ts +0 -137
  76. package/src/server.ts +0 -91
  77. package/src/types.ts +0 -104
  78. package/src/upstream.ts +0 -85
  79. package/src/verbs/issue_labels.ts +0 -329
  80. package/src/verbs/link_issue.ts +0 -84
  81. package/src/verbs/release-analysis.ts +0 -210
  82. package/src/verbs/repo-tier.ts +0 -90
  83. package/src/verbs/report_ci_status.ts +0 -358
  84. package/src/verbs/report_release_prs.ts +0 -238
  85. package/src/verbs/report_security_alerts.ts +0 -442
  86. package/src/verbs/report_unlabeled.ts +0 -194
  87. package/src/verbs/rest-client.ts +0 -181
  88. package/src/verbs/review_thread.ts +0 -138
  89. package/tests/appauth.test.vitest.mts +0 -76
  90. package/tests/audit.test.vitest.mts +0 -48
  91. package/tests/catalog.test.vitest.mts +0 -180
  92. package/tests/config-merge.test.vitest.mts +0 -58
  93. package/tests/config.test.vitest.mts +0 -314
  94. package/tests/issue-labels.test.vitest.mts +0 -347
  95. package/tests/paths.test.vitest.mts +0 -52
  96. package/tests/release-prs.test.vitest.mts +0 -237
  97. package/tests/rest-client.test.vitest.mts +0 -115
  98. package/tests/router.test.vitest.mts +0 -399
  99. package/tests/run-vitest.mjs +0 -51
  100. package/tests/secret-store.test.vitest.mts +0 -106
  101. package/tests/secrets.test.vitest.mts +0 -155
  102. package/tests/security-alerts.test.vitest.mts +0 -328
  103. package/tests/server.test.vitest.mts +0 -20
  104. package/tests/upstream.test.vitest.mts +0 -91
  105. package/tests/verb-helpers.test.vitest.mts +0 -331
  106. package/tests/verbs.test.vitest.mts +0 -624
  107. package/tsconfig.json +0 -21
package/README.md CHANGED
@@ -1,23 +1,39 @@
1
1
  # @cldmv/gh-broker
2
2
 
3
- A local **credential-routing MCP proxy** for GitHub. It lets an autonomous agent read and write GitHub issues/PRs across multiple orgs **without ever possessing a token** — the agent gets to *act*, never to *hold* the credential.
3
+ **@cldmv/gh-broker** is a local **credential-routing MCP proxy** for GitHub. It lets an autonomous agent read and write issues, pull requests and more across several orgs **without ever holding a token**: the agent gets to *act*, never to *possess* the credential.
4
4
 
5
- ## Why
5
+ The hosted GitHub MCP authenticates with one token per server, so several orgs means several servers, and the token sits in a config file the agent can read. A classic PAT spans orgs but is coarse; a GitHub App acts as a bot and can't drive the MCP; `gh` and raw PATs are readable by the agent (`gh auth token`). gh-broker covers what none of them do together: the user's own identity, least privilege, several orgs, one server, and a secret the agent cannot exfiltrate.
6
6
 
7
- The hosted GitHub MCP authenticates with one token per server, so multi-org means many servers (tool-list bloat) and the token sits in an agent-readable config file. A classic PAT spans orgs but is coarse; a GitHub App gives bot attribution and can't drive the MCP. `gh` and raw PATs are readable by the agent (`gh auth token`, `cat ~/.claude.json`). None satisfy: **user identity + least-privilege + multi-org + one server + the agent cannot exfiltrate the secret.**
7
+ It runs as a dedicated `gh-broker` system user, holds a read-scoped and a write-scoped fine-grained PAT per org in an agent-unreadable secret store (`systemd-creds` or a `0640` file), and picks the right one per call by repo owner and read/write mode. The toolset is config-driven and default-deny: mostly proxied to GitHub's hosted MCP, plus custom verbs for what the hosted MCP has no tool for, and augmented tools that extend a proxied tool's result. The agent reaches it over a permissioned Unix socket that carries MCP frames and never credential material.
8
8
 
9
- ## How
9
+ > _The agent gets to act, never to hold the credential._
10
10
 
11
- A small daemon (this package) runs as a dedicated **`gh-broker` system user** and:
11
+ [![npm version]][npm_version_url] [![npm downloads]][npm_downloads_url] [![GitHub downloads]][github_downloads_url] [![Last commit]][last_commit_url] [![npm last update]][npm_last_update_url] [![Coverage]][coverage_url]
12
12
 
13
- - holds per-org **read-scoped** and **write-scoped** fine-grained PATs, read from an agent-unreadable secret store — `systemd-creds` (encrypted at rest, default) or a plaintext file, chosen at install — via `cred:` references in a token-free config, into memory only;
14
- - routes the right PAT by **repo owner + read/write mode** on each call;
15
- - exposes a **config-driven, default-deny** toolset — mostly **proxied** to GitHub's hosted MCP (which already does labels, type, issue fields, dedup), plus **custom verbs** for gaps the hosted MCP has no tool for, and **augmented** tools that extend a proxied tool's response in place. See [Key Features](#-key-features) and [docs/TOOLS.md](docs/TOOLS.md) for the full set;
16
- - talks to the agent over a **Unix socket** (bytes only; no token crosses).
13
+ > [!NOTE]
14
+ > **Status: 0.x, in production.** Running under systemd and brokering live GitHub reads and writes. Linux and systemd only for now ([#9](https://github.com/CLDMV/gh-broker/issues/9)). `link_issue` (App-token only) is implemented but not wired in, see [deploy/README.md](deploy/README.md#enabling-link_issue-github-app--deferred).
17
15
 
18
- The agent (uid 1003, rootless docker, no passwordless sudo — verified) cannot read another user's mode-0400 secrets, so the boundary is real. The **agent-as-separate-user** model — the users, the bridge group, and the permissions that make the boundary real, as a reproducible example setup — is documented in [`docs/agent-user-model.md`](docs/agent-user-model.md).
16
+ [![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url]
19
17
 
20
- Full design + threat model: `~/.claude/designs/github-mcp-broker.md`.
18
+ ---
19
+
20
+ ## ✨ What's New
21
+
22
+ ### Latest: v0.9.0 (November 2026)
23
+
24
+ - **Three report verbs for the release and fleet skills.** `report_alert_status` buckets each open Dependabot alert by where its fix stands (released but still open, fixed on `next`, open PR, closed PR, nothing). `report_release_changes` returns the PRs a release actually ships, grouped by changelog section, with the net `package.json` and dependency deltas. `report_dependents` finds every repo that declares an npm package, including dev-trunk repos on `dev`, and skips a `dev` that has fallen behind the default branch.
25
+ - **`ciVerbosity` on `list_pull_requests`.** The same opt-in CI detail as `pull_request_read`, across a whole PR list with one shared call budget.
26
+ - **Install from npm.** `sudo npx @cldmv/gh-broker <agent-user>` runs the installer from the prebuilt package. The tarball now ships only runtime files.
27
+ - [View full v0.9.0 Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.9.0.md)
28
+
29
+ ### Recent Releases
30
+
31
+ - **v0.8.0** (November 2026) — `npm_scaffold` creates an npm package and its trusted publisher, `ghcr_push` pushes images without an agent-held token, and optional credentials hide their tools ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.8.0.md))
32
+ - **v0.7.0** (October 2026) — `issue_labels` adds, removes or swaps single labels without replacing the set, and `issue_write` warns that its `labels` does ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.7.0.md))
33
+ - **v0.6.1** (October 2026) — MCP SDK 1.32.0 (same-origin redirects only) and a refreshed vitest toolchain; no broker source changes ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.6.1.md))
34
+ - **v0.6.0** (October 2026) — Compact per-repo roll-up, filters and flow tier for `report_security_alerts`, and release-readiness analysis for `report_release_prs` ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.6.0.md))
35
+
36
+ 📚 For the complete version history, see the [docs/changelog/](https://github.com/CLDMV/gh-broker/tree/master/docs/changelog/) folder.
21
37
 
22
38
  ---
23
39
 
@@ -35,7 +51,7 @@ A tool not listed in `config.tools` is neither advertised nor executable. Adding
35
51
 
36
52
  ### 📊 **CI Visibility Without Per-Call Log Fetches**
37
53
 
38
- `report_ci_status` answers "does this PR have failing checks" at whatever detail level you ask for — counts only by default, escalating to which job failed, which step failed, and a tail of the failing job's actual log — paying for that detail only on runs that are genuinely failing. The same status can ride inline on a normal `pull_request_read` via an opt-in `ciVerbosity` parameter.
54
+ `report_ci_status` answers "does this PR have failing checks" at whatever detail level you ask for — counts only by default, escalating to which job failed, which step failed, and a tail of the failing job's actual log — paying for that detail only on runs that are genuinely failing. The same status can ride inline on a normal `pull_request_read` or `list_pull_requests` via an opt-in `ciVerbosity` parameter.
39
55
 
40
56
  ```json
41
57
  { "owner": "cldmv", "repo": "droidsock", "pull": 35, "verbosity": 2 }
@@ -59,18 +75,56 @@ The hosted MCP's `issue_write` takes `labels` as a full replacement, so changing
59
75
 
60
76
  🏷️ **Full parameter reference: [docs/TOOLS.md](docs/TOOLS.md#issue_labels)**
61
77
 
78
+ ### 📦 **New npm Packages Without a Manual First Publish**
79
+
80
+ npm only accepts a trusted publisher on a package that already exists, so every new package used to need a hand-run first publish and a form on npmjs.com. `npm_scaffold` does both: it publishes a broker-built placeholder (`0.0.0-stage`, no code) and registers the repo's publish workflow as the trusted publisher, after which CI publishes every release through OIDC. The broker's npm token has no 2FA bypass, so every npm write needs a one-time password from the user: one call publishes, a second call with the next code adds the trusted publisher. The verb refuses any package that already exists.
81
+
82
+ ```json
83
+ { "owner": "cldmv", "repo": "new-thing", "package": "@cldmv/new-thing", "otp": "123456" }
84
+ ```
85
+
86
+ 📦 **Full parameter reference: [docs/TOOLS.md](docs/TOOLS.md#npm_scaffold)**
87
+
62
88
  ### 🛡️ **Org-Wide Security Alerts**
63
89
 
64
90
  `report_security_alerts` lists every open Dependabot, CodeQL and secret-scanning alert — for one repo or across everything an owner has — using the org-level endpoints where the token can read them and a per-repo sweep where it can't. Secret values are never returned.
65
91
 
66
92
  🛡️ **Full parameter reference: [docs/TOOLS.md](docs/TOOLS.md#report_security_alerts)**
67
93
 
94
+ ### 🩹 **Where Each Alert's Fix Stands**
95
+
96
+ `report_alert_status` puts every open Dependabot alert in one bucket — `released-still-open`, `fixed-on-next`, `open-pr`, `closed-unmerged` or `no-pr` — by reading the lockfile nearest the alert's manifest on the default branch, `next` and `hotfixes`, and the repo's PR history. An alert that reads open because its fix is waiting on `next` stops looking like work to do.
97
+
98
+ 🩹 **Full parameter reference: [docs/TOOLS.md](docs/TOOLS.md#report_alert_status)**
99
+
68
100
  ### 🚢 **Release PR Discovery**
69
101
 
70
102
  `report_release_prs` finds every open release PR across an owner's repos and says which ones still lack their per-version changelog file — the list an agent needs during a mass update.
71
103
 
72
104
  🚢 **Full parameter reference: [docs/TOOLS.md](docs/TOOLS.md#report_release_prs)**
73
105
 
106
+ ### 📝 **Changelog Source Material**
107
+
108
+ `report_release_changes` returns the PRs one release actually ships — already-released and reverted PRs dropped — grouped by changelog section at the detail level you ask for, with the release's `package.json` and dependency deltas and an optional skeleton in the per-version template.
109
+
110
+ 📝 **Full parameter reference: [docs/TOOLS.md](docs/TOOLS.md#report_release_changes)**
111
+
112
+ ### 📦 **Package Dependents**
113
+
114
+ `report_dependents` finds every repo an owner has that declares an npm package, with the declared range, the lockfile-resolved version and the repo's flow tier — where a fleet-wide version bump starts. It reads `dev` branches too, so dev-trunk repos are included, and skips a `dev` whose version has fallen behind the default branch.
115
+
116
+ 📦 **Full parameter reference: [docs/TOOLS.md](docs/TOOLS.md#report_dependents)**
117
+
118
+ ### 🐳 **Container Image Pushes Without a Registry Token**
119
+
120
+ `ghcr_push` pushes a `docker save` archive to `ghcr.io/<owner>/<image>:<tag>` with a classic `write:packages` PAT the agent never sees. The image must be on an allowlist, the archive must sit in a dedicated drop directory (symlinks, hard links and paths outside it are refused), and an existing release tag is never overwritten. The push runs as a background job, uploads only the blobs the registry doesn't already have, and is tracked with `ghcr_push_status`; `ghcr_tags` checks the result.
121
+
122
+ ```json
123
+ { "owner": "cldmv", "image": "hoot", "tag": "1.4.0", "archive": "hoot-1.4.0.tar" }
124
+ ```
125
+
126
+ 🐳 **Full parameter reference and threat model: [docs/TOOLS.md](docs/TOOLS.md#ghcr_push)**
127
+
74
128
  ### 🔧 **Extensible By Design**
75
129
 
76
130
  The broker presents its **own** MCP contract to the agent — a proxied tool's schema and response are not required to match the hosted MCP's exactly. Adding a custom verb or extending a proxied tool with new parameters follows a documented pattern, not a one-off hack.
@@ -85,29 +139,40 @@ The broker presents its **own** MCP contract to the agent — a proxied tool's s
85
139
 
86
140
  ---
87
141
 
88
- ## ✨ What's New
142
+ ## 📦 Installation
89
143
 
90
- ### Latest: v0.7.0 (October 2026)
144
+ ### Requirements
91
145
 
92
- - **`issue_labels`: edit labels without replacing the set.** The new write verb adds, removes or swaps individual labels on an issue or PR (`swap { prefix: "status: ", to: "status: verified" }` moves a status in one call), so the priority, area and type labels next to it are never dropped the way they are by `issue_write`'s whole-set `labels`. Unknown label names are refused unless `create_missing` is set, and the call returns the resulting set.
93
- - **`issue_write` warns about the replacement.** Its advertised description now says that `labels` replaces the whole set and points at `issue_labels`.
94
- - **Dependencies.** MCP SDK 1.32.1 and a patched `proxy-addr` (CVE-2026-90711) at runtime; `@cldmv/configs` 1.3.0 and other dev-tooling bumps.
95
- - [View full v0.7.0 Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.7.0.md)
146
+ - **Linux with systemd.** The broker runs as a hardened systemd service and listens on a Unix socket.
147
+ - **Node.js 22.12.0 or later** (`engines.node` is `>=22.12.0`). The installer points `/usr/local/bin/node` at the node it runs under.
148
+ - **The agent runs as its own Linux user**, without passwordless sudo, so it can't read the broker's secrets. The model behind that is in [docs/agent-user-model.md](docs/agent-user-model.md).
149
+ - **Two fine-grained PATs per org** (read and write). The scopes are listed in [deploy/README.md](deploy/README.md).
96
150
 
97
- ### Recent Releases
151
+ ### Install
98
152
 
99
- - **v0.6.1** (October 2026) — MCP SDK 1.32.0 (same-origin redirects only) and a refreshed vitest toolchain; no broker source changes ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.6.1.md))
100
- - **v0.6.0** (October 2026) — Compact per-repo roll-up, filters and flow tier for `report_security_alerts`, and release-readiness analysis for `report_release_prs` ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.6.0.md))
101
- - **v0.5.1** (October 2026) — The installer keeps the existing secrets backend instead of prompting on every upgrade ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.5.1.md))
102
- - **v0.5.0** (October 2026) — New `report_security_alerts` and `report_release_prs` verbs, and a permission 403 is no longer mistaken for a rate limit ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.5.0.md))
153
+ gh-broker is a system service, not a library. The installer handles both the first install and every upgrade, and you can run it from npm or from a checkout.
103
154
 
104
- 📚 For the complete version history, see the [docs/changelog/](https://github.com/CLDMV/gh-broker/tree/master/docs/changelog/) folder.
155
+ From npm ([`@cldmv/gh-broker`](https://www.npmjs.com/package/@cldmv/gh-broker)), with the prebuilt bundle:
156
+
157
+ ```sh
158
+ sudo npx @cldmv/gh-broker <agent-user>
159
+ ```
160
+
161
+ From a checkout:
162
+
163
+ ```sh
164
+ git clone https://github.com/CLDMV/gh-broker.git
165
+ cd gh-broker
166
+ sudo node deploy/install.mjs <agent-user>
167
+ ```
168
+
169
+ Both take the same flags. The installer creates the service user and the socket group, builds as the checkout's owner when there's no bundle yet, merges the shipped defaults with the deployed config, stores the PATs in the backend you pick, installs the unit and runs a smoke test. Prerequisites, PAT scopes, unattended installs and every flag: [deploy/README.md](deploy/README.md).
105
170
 
106
171
  ---
107
172
 
108
- ## Calling a tool
173
+ ## 🚀 Quick Start
109
174
 
110
- The agent's MCP client sees an ordinary `tools/call`; the broker resolves the credential, routes it, and (for custom/augmented tools) does the extra work in between:
175
+ Point the agent's MCP client at the socket ([deploy/README.md § 3](deploy/README.md#3-point-claude-at-the-broker)). From then on the client sees an ordinary `tools/call`, and the broker resolves the credential, routes it, and (for custom/augmented tools) does the extra work in between:
111
176
 
112
177
  ```json
113
178
  // tools/call
@@ -123,7 +188,7 @@ No token ever appears in the request, the response, or anywhere the agent can re
123
188
 
124
189
  ---
125
190
 
126
- ## Layout
191
+ ## 📁 Layout
127
192
 
128
193
  ```
129
194
  src/
@@ -140,15 +205,24 @@ src/
140
205
  report_unlabeled.ts issues/PRs with no labels, one repo or org-wide
141
206
  report_security_alerts.ts open Dependabot/CodeQL/secret alerts, one repo or org-wide
142
207
  report_release_prs.ts open release PRs + whether each has its changelog file
208
+ report_dependents.ts repos that declare an npm package, with range, resolved version and flow tier
209
+ report_alert_status.ts where each open Dependabot alert's fix stands (lockfiles + PRs)
210
+ report_release_changes.ts the PRs a release ships, grouped, with net package/dependency deltas
211
+ lockfile.ts npm / pnpm / yarn lockfile readers shared by the report verbs
143
212
  report_ci_status.ts tiered CI status for a PR or every open PR in a repo
144
213
  rest-client.ts shared rate-limit-floor + call-budget REST helpers
145
214
  issue_labels.ts add/remove/swap/set labels on an issue or PR (write PAT)
215
+ npm_scaffold.ts create an npm package + its trusted publisher (npm token + user OTP)
216
+ npm-registry.ts the npm registry requests npm_scaffold makes
217
+ npm-tarball.ts minimal npm tarball builder for the placeholder
146
218
  review_thread.ts resolve/reply PR review threads (write PAT)
219
+ ghcr_push.ts ghcr_push / ghcr_push_status / ghcr_tags (GHCR classic PAT)
220
+ ghcr/ config policy, tar + image-archive reader, OCI registry client, push, job queue
147
221
  link_issue.ts addClosingIssueReference (App token) — DEFERRED, not wired in
148
222
  deploy/
149
223
  install.mjs interactive installer — first install AND upgrade (sudo node)
150
224
  install.sh optional Linux bootstrap — set up node, then hand off to install.mjs
151
- lib/ installer pieces (config merge, secret store, system ops, smoke test)
225
+ lib/ installer pieces (config merge, secret store, system ops, smoke test, npm token setup)
152
226
  gh-broker.service systemd unit (hardened)
153
227
  README.md prerequisites + PAT scopes + install/upgrade/wiring
154
228
  docs/
@@ -156,31 +230,81 @@ docs/
156
230
  deploy-architecture.md config merge, secret store, node handling, installer flow
157
231
  agent-user-model.md the agent-as-separate-user isolation model (example setup)
158
232
  changelog/ per-version release notes
159
- config.example.json shipped defaults (token-free); merged into the deployed config
233
+ config.example.json shipped generic defaults (socket, upstream, tools allowlist)
234
+ config.sample.json a full config with placeholder values, for reference only
160
235
  ```
161
236
 
237
+ ---
238
+
162
239
  ## 📚 Documentation
163
240
 
164
- - **[Tools Reference](docs/TOOLS.md)** — every custom and augmented tool: `report_unlabeled`, `report_security_alerts`, `report_release_prs`, `report_ci_status`, the `pull_request_read` `ciVerbosity` extension, `issue_labels`, `resolve_review_thread`/`reply_review_thread`, and how to add a new one
241
+ - **[Tools Reference](docs/TOOLS.md)** — every custom and augmented tool: `report_unlabeled`, `report_security_alerts`, `report_alert_status`, `report_release_prs`, `report_release_changes`, `report_dependents`, `report_ci_status`, the `pull_request_read` and `list_pull_requests` `ciVerbosity` extensions, `issue_labels`, `npm_scaffold`, `resolve_review_thread`/`reply_review_thread`, `ghcr_push`/`ghcr_push_status`/`ghcr_tags`, and how to add a new one
165
242
  - **[Agent-as-Separate-User Model](docs/agent-user-model.md)** — the isolation model behind "use ≠ possession": users, groups, filesystem permissions, verified against a real host
166
243
  - **[Deploy Architecture](docs/deploy-architecture.md)** — config merge, secret store backends, node handling, the installer's full flow
167
244
  - **[Install Guide](deploy/README.md)** — prerequisites, PAT scopes, install/upgrade command, flags
168
245
  - **[Changelog](docs/changelog/)** — per-version release notes
169
246
 
170
- ## Status
247
+ [![CodeFactor]][codefactor_url] [![OpenSSF Scorecard]][ossf_scorecard_url] [![npms.io score]][npms_url] [![npm unpacked size]][npm_size_url] [![Repo size]][repo_size_url]
248
+
249
+ ---
171
250
 
172
- **Deployed and in production** — running under systemd, brokering live GitHub reads and writes across the configured org. The Unix-socket MCP server, the default-deny allowlist, owner/mode→PAT routing, and every custom/augmented tool in [docs/TOOLS.md](docs/TOOLS.md) are exercised in normal use — `report_ci_status` and the `pull_request_read` augmentation have both been verified against real failing CI data, not just mocked tests. `link_issue` (App-token-only) is implemented but deferred — see [`deploy/README.md`](deploy/README.md#enabling-link_issue-github-app--deferred). Install/upgrade with `sudo node deploy/install.mjs` (see [`deploy/README.md`](deploy/README.md)).
251
+ ## 🤝 Contributing
173
252
 
174
- ## Install / build
253
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/CLDMV/gh-broker/issues). Pull requests target the `next` branch; releases ship from `next` to `master`. Run `npm run build:ci` and `npm run ci:coverage` before opening one; coverage is held at 100%.
175
254
 
176
- ```sh
177
- npm ci
178
- npm run typecheck
179
- npm run build
180
- ```
255
+ [![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url]
181
256
 
182
- ## License
257
+ ---
183
258
 
184
- Apache-2.0 © Shinrai / CLDMV
259
+ ## 🔗 Links
260
+
261
+ - **npm**: [@cldmv/gh-broker](https://www.npmjs.com/package/@cldmv/gh-broker)
262
+ - **GitHub**: [CLDMV/gh-broker](https://github.com/CLDMV/gh-broker)
263
+ - **Issues**: [GitHub Issues](https://github.com/CLDMV/gh-broker/issues)
264
+ - **Changelog**: [docs/changelog/](https://github.com/CLDMV/gh-broker/tree/master/docs/changelog/)
265
+ - **Releases**: [GitHub Releases](https://github.com/CLDMV/gh-broker/releases)
266
+
267
+ ---
185
268
 
186
- Published on npm as [`@cldmv/gh-broker`](https://www.npmjs.com/package/@cldmv/gh-broker). It's a systemd daemon, not a typical CLI tool — install it with [`deploy/install.mjs`](deploy/README.md), not `npm install -g`.
269
+ ## 📄 License
270
+
271
+ [![GitHub license]][github_license_url] [![npm license]][npm_license_url]
272
+
273
+ Apache-2.0 © Shinrai / CLDMV. See [LICENSE](https://github.com/CLDMV/gh-broker/blob/master/LICENSE) for the full text.
274
+
275
+ <!-- Badge definitions -->
276
+ <!-- [github release]: https://img.shields.io/github/v/release/CLDMV/gh-broker?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 -->
277
+ <!-- [github_release_url]: https://github.com/CLDMV/gh-broker/releases -->
278
+ <!-- [release date]: https://img.shields.io/github/release-date/CLDMV/gh-broker?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 -->
279
+ <!-- [release_date_url]: https://github.com/CLDMV/gh-broker/releases -->
280
+
281
+ [npm version]: https://img.shields.io/npm/v/%40cldmv%2Fgh-broker.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
282
+ [npm_version_url]: https://www.npmjs.com/package/@cldmv/gh-broker
283
+ [npm downloads]: https://img.shields.io/npm/dm/%40cldmv%2Fgh-broker.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
284
+ [npm_downloads_url]: https://www.npmjs.com/package/@cldmv/gh-broker
285
+ [github downloads]: https://img.shields.io/github/downloads/CLDMV/gh-broker/total?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
286
+ [github_downloads_url]: https://github.com/CLDMV/gh-broker/releases
287
+ [last commit]: https://img.shields.io/github/last-commit/CLDMV/gh-broker?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
288
+ [last_commit_url]: https://github.com/CLDMV/gh-broker/commits
289
+ [npm last update]: https://img.shields.io/npm/last-update/%40cldmv%2Fgh-broker?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
290
+ [npm_last_update_url]: https://www.npmjs.com/package/@cldmv/gh-broker
291
+ [coverage]: https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2FCLDMV%2Fgh-broker%2Fbadges%2Fcoverage.json&style=for-the-badge&logo=vitest&logoColor=white
292
+ [coverage_url]: https://github.com/CLDMV/gh-broker/blob/badges/coverage.json
293
+ [contributors]: https://img.shields.io/github/contributors/CLDMV/gh-broker.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
294
+ [contributors_url]: https://github.com/CLDMV/gh-broker/graphs/contributors
295
+ [sponsor shinrai]: https://img.shields.io/github/sponsors/shinrai?style=for-the-badge&logo=githubsponsors&logoColor=white&labelColor=EA4AAA&label=Sponsor
296
+ [sponsor_url]: https://github.com/sponsors/shinrai
297
+ [codefactor]: https://img.shields.io/codefactor/grade/github/CLDMV/gh-broker?style=for-the-badge&logo=codefactor&logoColor=white&labelColor=F44A6A
298
+ [codefactor_url]: https://www.codefactor.io/repository/github/cldmv/gh-broker
299
+ [openssf scorecard]: https://img.shields.io/ossf-scorecard/github.com/CLDMV/gh-broker?style=for-the-badge&label=OpenSSF%20Scorecard
300
+ [ossf_scorecard_url]: https://scorecard.dev/viewer/?uri=github.com/CLDMV/gh-broker
301
+ [npms.io score]: https://img.shields.io/npms-io/final-score/%40cldmv%2Fgh-broker?style=for-the-badge&logo=npms&logoColor=white&labelColor=0B5D57
302
+ [npms_url]: https://npms.io/search?q=%40cldmv%2Fgh-broker
303
+ [npm unpacked size]: https://img.shields.io/npm/unpacked-size/%40cldmv%2Fgh-broker.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
304
+ [npm_size_url]: https://www.npmjs.com/package/@cldmv/gh-broker
305
+ [repo size]: https://img.shields.io/github/repo-size/CLDMV/gh-broker?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
306
+ [repo_size_url]: https://github.com/CLDMV/gh-broker
307
+ [npm license]: https://img.shields.io/npm/l/%40cldmv%2Fgh-broker.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
308
+ [npm_license_url]: https://www.npmjs.com/package/@cldmv/gh-broker
309
+ [github license]: https://img.shields.io/github/license/CLDMV/gh-broker.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
310
+ [github_license_url]: https://github.com/CLDMV/gh-broker/blob/master/LICENSE
@@ -28,7 +28,8 @@
28
28
  },
29
29
  {
30
30
  "name": "list_pull_requests",
31
- "mode": "read"
31
+ "mode": "read",
32
+ "augment": true
32
33
  },
33
34
  {
34
35
  "name": "pull_request_read",
@@ -240,6 +241,11 @@
240
241
  "mode": "write",
241
242
  "custom": true
242
243
  },
244
+ {
245
+ "name": "npm_scaffold",
246
+ "mode": "write",
247
+ "custom": true
248
+ },
243
249
  {
244
250
  "name": "resolve_review_thread",
245
251
  "mode": "write",
@@ -260,22 +266,45 @@
260
266
  "mode": "read",
261
267
  "custom": true
262
268
  },
269
+ {
270
+ "name": "report_dependents",
271
+ "mode": "read",
272
+ "custom": true
273
+ },
263
274
  {
264
275
  "name": "report_release_prs",
265
276
  "mode": "read",
266
277
  "custom": true
267
278
  },
279
+ {
280
+ "name": "report_release_changes",
281
+ "mode": "read",
282
+ "custom": true
283
+ },
284
+ {
285
+ "name": "report_alert_status",
286
+ "mode": "read",
287
+ "custom": true
288
+ },
268
289
  {
269
290
  "name": "report_ci_status",
270
291
  "mode": "read",
271
292
  "custom": true
293
+ },
294
+ {
295
+ "name": "ghcr_push",
296
+ "mode": "write",
297
+ "custom": true
298
+ },
299
+ {
300
+ "name": "ghcr_push_status",
301
+ "mode": "read",
302
+ "custom": true
303
+ },
304
+ {
305
+ "name": "ghcr_tags",
306
+ "mode": "read",
307
+ "custom": true
272
308
  }
273
- ],
274
- "owners": {
275
- "cldmv": {
276
- "read": "cred:cldmv-read",
277
- "write": "cred:cldmv-write"
278
- }
279
- },
280
- "defaultOwner": "cldmv"
309
+ ]
281
310
  }
@@ -0,0 +1,45 @@
1
+ {
2
+ "listen": "unix:/run/gh-broker/gh-broker.sock",
3
+ "upstream": "https://api.githubcopilot.com/mcp/x/all",
4
+ "defaultOwner": "your-org",
5
+ "owners": {
6
+ "your-org": {
7
+ "read": "cred:your-org-read",
8
+ "write": "cred:your-org-write",
9
+ "installationId": 12345678
10
+ }
11
+ },
12
+ "app": {
13
+ "appId": 123456,
14
+ "privateKey": "cred:gh-app-key"
15
+ },
16
+ "secrets": {
17
+ "provider": "file",
18
+ "path": "/etc/gh-broker/secrets.json"
19
+ },
20
+ "npm": {
21
+ "token": "cred:npm-token",
22
+ "scopes": [
23
+ "@your-org"
24
+ ],
25
+ "registry": "https://registry.npmjs.org",
26
+ "tokenOrgs": [
27
+ "your-org"
28
+ ]
29
+ },
30
+ "ghcr": {
31
+ "dropDir": "/srv/gh-broker/ghcr-drop",
32
+ "protectReleaseTags": true,
33
+ "maxArchiveBytes": 17179869184,
34
+ "owners": {
35
+ "your-org": {
36
+ "username": "your-login",
37
+ "credential": "cred:your-org-packages",
38
+ "images": [
39
+ "your-image",
40
+ "your-image-worker-*"
41
+ ]
42
+ }
43
+ }
44
+ }
45
+ }
package/deploy/README.md CHANGED
@@ -9,45 +9,53 @@ One interactive installer does first-install and upgrade. Design and rationale:
9
9
 
10
10
  The installer maintains `/usr/local/bin/node` as the single node indirection (CLI and service resolve the same link). Bumping node is re-pointing that symlink — which the installer does on every run.
11
11
 
12
- ## 1. Mint the two CLDMV fine-grained PATs
12
+ ## 1. Mint the two fine-grained PATs
13
13
 
14
- GitHub → Settings → Developer settings → **Fine-grained tokens** → Generate new token, **twice**. Resource owner: **CLDMV**. Repository access: **All repositories**.
14
+ For each GitHub owner (user or org) the broker serves, `<org>`: GitHub → Settings → Developer settings → **Fine-grained tokens** → Generate new token, **twice**. Resource owner: `<org>`. Repository access: **All repositories**.
15
15
 
16
- **`cldmv-read`** — Issues: Read, Pull requests: Read, Contents: Read, Metadata: Read (auto).
17
- **`cldmv-write`** — Issues: R/W, Pull requests: R/W, Contents: R/W, Metadata: Read (auto).
16
+ **`<org>-read`** — Issues: Read, Pull requests: Read, Contents: Read, Metadata: Read (auto).
17
+ **`<org>-write`** — Issues: R/W, Pull requests: R/W, Contents: R/W, Metadata: Read (auto).
18
18
 
19
19
  No organization or admin permissions. If the org requires approval for fine-grained PATs, approve them in Org → Settings → Personal access tokens. The two-PAT split (read vs write) is a deliberate least-privilege control — see the design doc.
20
20
 
21
+ ### GHCR pushes: one classic PAT (`<org>-packages`)
22
+
23
+ `ghcr_push` needs a third credential, because the GitHub Container Registry does not accept fine-grained PATs. Create a **classic** token with **`write:packages` only** (it implies `read:packages`; do not add `delete:packages`, and add `repo` only if pushes to a private-repo-linked package are refused without it), and set an expiry: [create the token](https://github.com/settings/tokens/new?scopes=write:packages&description=gh-broker%20ghcr). Enable it with `--ghcr-owner <org> --ghcr-username <login> --ghcr-image <name>` (or a `ghcr` block in a `--config` file, see [Configuring the install](#configuring-the-install)). The installer then prompts for the token as `<org>-packages`; it is an **optional credential**, so you can press Enter to skip it (an unattended run skips it with a note), and the broker then starts normally with the `ghcr_*` tools hidden until the token is stored. Supply it on a later run with `--secret <org>-packages=<token>`, `--secrets-file` or env `GH_BROKER_SECRET_<ORG>_PACKAGES`. `--ghcr-username` (`ghcr.owners.<org>.username`) must be the GitHub login that owns the token.
24
+
25
+ The installer also creates the drop directory from `ghcr.dropDir` (default `/srv/gh-broker/ghcr-drop`) as `2750 <agent-user>:gh-broker`: the agent writes archives there, the setgid bit gives each file the broker's group, and nobody else can look in. Archives must be group-readable (`chmod g+r`); the broker can only read them, so the agent deletes them after a push. See [`../docs/TOOLS.md`](../docs/TOOLS.md#ghcr_push).
26
+
21
27
  ## 2. Install (first install AND upgrade — same command)
22
28
 
23
29
  ```sh
24
- sudo node deploy/install.mjs [agent-user]
30
+ sudo npx @cldmv/gh-broker [agent-user] # from npm
31
+ sudo node deploy/install.mjs [agent-user] # from a checkout
25
32
  ```
26
33
 
27
- `agent-user` defaults to `$SUDO_USER` (the account you ran `sudo` from). The installer walks the whole thing and is idempotent:
34
+ Both run the same installer and take the same flags; the npm package ships a prebuilt `dist/`, so step 5 has nothing to build. `agent-user` defaults to `$SUDO_USER` (the account you ran `sudo` from). The installer walks the whole thing and is idempotent:
28
35
 
29
36
  1. checks prerequisites,
30
37
  2. **creates or confirms the agent user** (stops if it can't — no silent half-install),
31
- 3. creates the `gh-broker` service user + `gh-broker-clients` bridge group, adds the agent user to it,
38
+ 3. creates the `gh-broker` service user + `gh-broker-clients` bridge group, adds the agent user to it (and, when the config has a `ghcr` block, creates the GHCR drop directory),
32
39
  4. points `/usr/local/bin/node` at the running node,
33
40
  5. builds `dist/` if it isn't already built, or rebuilds it if a prior run left it (or `node_modules/`) owned by someone other than the checkout's owner — the build itself runs via `sudo -u '#<uid>'` as whoever owns `deploy/install.mjs`, not as root, so the installer never leaves root-owned files behind in your checkout,
34
- 6. regenerates the config by **merging** `config.example.json` with whatever is deployed (your data is preserved; new keys/allowlist changes from the repo arrive),
35
- 7. **asks how to store the credentials** — `systemd-creds` (encrypted at rest, host-bound; recommended) or a plaintext `file` (`0640 root:gh-broker`; portable) — then stores the tokens in the chosen backend: it keeps existing creds, decrypts a legacy `systemd-creds` token when you switch to `file`, and **prompts only for what it can't find** (input hidden),
36
- 8. validates the config with the real loader (`--check`),
37
- 9. installs the unit, deploys `dist/`, restarts, and
38
- 10. runs a **smoke test** (MCP handshake + a real read over the socket) → ✅/❌.
41
+ 6. regenerates the config by layering `config.example.json`, whatever is deployed, your `--config` file and your flags (your data is preserved; allowlist changes from the repo arrive; see [Configuring the install](#configuring-the-install)); a first install with no owner asks for one,
42
+ 7. sets up the optional **npm integration** when asked (offered on a first install, or `--npm`): an npm web login it waits on, then the token it creates for `npm_scaffold` (see [npm integration](#npm-integration-npm_scaffold)),
43
+ 8. **asks how to store the credentials** — `systemd-creds` (encrypted at rest, host-bound; recommended) or a plaintext `file` (`0640 root:gh-broker`; portable) — then stores the tokens in the chosen backend: it keeps existing creds, decrypts a legacy `systemd-creds` token when you switch to `file`, and **prompts only for what it can't find** (input hidden),
44
+ 9. validates the config with the real loader (`--check`),
45
+ 10. installs the unit, deploys `dist/`, restarts, and
46
+ 11. runs a **smoke test** (MCP handshake + a real read over the socket) → ✅/❌.
39
47
 
40
48
  ### Unattended (single non-interactive command)
41
49
 
42
- Pass flags plus `--non-interactive` (`-y`) to skip every prompt: `--agent-user <name>`, `--secrets-provider systemd-creds|file`, `--create-agent-user`, and supply tokens via `--secrets-file <json>` (`{ "<cred-name>": "<token>" }`) or env `GH_BROKER_SECRET_<NAME>` (NAME upper-cased, non-alnum → `_`). Example:
50
+ Pass flags plus `--non-interactive` (`-y`) to skip every prompt: `--agent-user <name>`, `--secrets-provider systemd-creds|file`, `--create-agent-user`, and supply tokens via `--secrets-file <json>` (`{ "<cred-name>": "<token>" }`), env `GH_BROKER_SECRET_<NAME>` (NAME upper-cased, non-alnum → `_`), or `--secret <cred-name>=<token>` (repeatable; a name the config doesn't use is refused, so a typo can't silently do nothing; it is visible in the process list and shell history, so prefer the file or env). Precedence is `--secret`, then the file, then env, then the interactive prompt; a token from any of the first three replaces a stored one. Example:
43
51
 
44
52
  ```sh
45
- sudo GH_BROKER_SECRET_CLDMV_READ=… GH_BROKER_SECRET_CLDMV_WRITE=… \
46
- /usr/local/bin/node deploy/install.mjs --agent-user repo \
53
+ sudo GH_BROKER_SECRET_ACME_READ=… GH_BROKER_SECRET_ACME_WRITE=… \
54
+ /usr/local/bin/node deploy/install.mjs --agent-user repo --owner acme \
47
55
  --secrets-provider systemd-creds --non-interactive
48
56
  ```
49
57
 
50
- Migrating an existing `systemd-creds` install needs **no tokens** — `--secrets-provider systemd-creds --non-interactive` keeps the existing `.cred` files (nothing to prompt). `--help` lists every flag.
58
+ Re-running on an existing install needs no `--owner`: the deployed owners are kept. Migrating an existing `systemd-creds` install needs **no tokens** — `--secrets-provider systemd-creds --non-interactive` keeps the existing `.cred` files (nothing to prompt). `--help` lists every flag.
51
59
 
52
60
  **Upgrade** is just re-running it: config merged (edits kept), your previously-chosen storage backend pre-selected, existing tokens kept. You can **switch backends** on any run — pick `file` and it decrypts the existing `systemd-creds` tokens into the file (no re-entry); pick `systemd-creds` and it encrypts the tokens into `.cred` files. `launch.sh` is no longer used — remove it after a successful run: `sudo rm -f /opt/gh-broker/launch.sh` (and `sudo rm -rf /etc/gh-broker/creds` only if you've moved fully to the `file` backend).
53
61
 
@@ -60,6 +68,39 @@ The installer asks which backend holds the tokens at rest:
60
68
 
61
69
  Either way the **agent user cannot read the tokens** (that's filesystem perms, not encryption) and the **config stays token-free**. The choice only affects at-rest protection vs portability. See [`../docs/deploy-architecture.md`](../docs/deploy-architecture.md).
62
70
 
71
+ ## Configuring the install
72
+
73
+ `config.example.json` ships only the generic defaults: `listen`, `upstream` and the `tools` allowlist. Everything specific to your deployment (owners, GHCR, App, npm, secrets) is supplied at install time. `config.sample.json` shows a complete config with placeholder values; nothing loads it.
74
+
75
+ Settings are layered, lowest precedence first:
76
+
77
+ 1. `config.example.json` (shipped defaults),
78
+ 2. the config already deployed on the host,
79
+ 3. `--config <file>`,
80
+ 4. command-line flags.
81
+
82
+ A scalar is replaced by a higher layer. `owners` and `ghcr.owners` are merged per owner, so adding an owner never drops the deployed ones. `tools` always comes from the example: the allowlist is a security control, so a `--config` file that contains `tools` is refused. The `--config` file is JSON in the deployed config's shape, holds only `cred:` references (a value shaped like a token is refused), and the merged result is validated by the real loader before anything is deployed. An upgrade run with no config flags keeps the deployed owners, `ghcr` and everything else as they are.
83
+
84
+ | Flag | Sets |
85
+ |---|---|
86
+ | `--config <file>` | JSON config file merged on top of the deployed config |
87
+ | `--owner <org>` (repeatable) | `owners.<org>` = `cred:<org>-read` / `cred:<org>-write` |
88
+ | `--installation-id <org>=<id>` (repeatable) | `owners.<org>.installationId` |
89
+ | `--default-owner <org>` | `defaultOwner` (a first install with a single owner defaults to it) |
90
+ | `--listen <unix:path>` | `listen` |
91
+ | `--upstream <url>` | `upstream` |
92
+ | `--app-id <n>`, `--app-private-key <cred-name>` | `app.appId`, `app.privateKey` (`cred:gh-app-key` unless named) |
93
+ | `--secrets-provider <p>`, `--secrets-path <path>` | `secrets.provider`, `secrets.path` (path is for the `file` provider) |
94
+ | `--npm`, `--npm-scope`, `--npm-org`, `--npm-registry <url>`, `--npm-token-days` | the `npm` block, see [npm integration](#npm-integration-npm_scaffold) |
95
+ | `--ghcr-owner <org>`, `--ghcr-username <login>`, `--ghcr-image <glob>` (repeatable) | `ghcr.owners.<org>`; the credential is `cred:<org>-packages` |
96
+ | `--ghcr-drop-dir <path>` | `ghcr.dropDir` (default `/srv/gh-broker/ghcr-drop`) |
97
+ | `--ghcr-max-archive-bytes <n>` | `ghcr.maxArchiveBytes` |
98
+ | `--protect-release-tags`, `--no-protect-release-tags` | `ghcr.protectReleaseTags` |
99
+
100
+ Flags that build a single GHCR owner do so for one owner per run; use `--config` for several, or for a credential name other than the derived one.
101
+
102
+ With no owner anywhere (a first install with no flags and no `--config`), an interactive run asks for the GitHub owner name and then for its two tokens; `--non-interactive` fails and names `--owner` / `--config`.
103
+
63
104
  ## 3. Point Claude at the broker
64
105
 
65
106
  The installer prints this; add it to the agent user's `~/.claude.json` `mcpServers`:
@@ -76,7 +117,25 @@ Re-login as the agent user first, so its `gh-broker-clients` membership is activ
76
117
 
77
118
  ## Adding an org later
78
119
 
79
- Add an `owners` entry to `config.example.json` (name + `cred:<org>-read` / `cred:<org>-write` refs), then re-run the installer — the merge brings the new owner through, and it prompts for the new credentials. No code change.
120
+ Re-run the installer with `--owner <org>` (repeatable). The deployed owners stay, the new one is added with `cred:<org>-read` / `cred:<org>-write`, and the installer prompts for its two tokens. No code change.
121
+
122
+ ## npm integration (`npm_scaffold`)
123
+
124
+ Optional. It lets `npm_scaffold` create new npm packages and their trusted publishers. The broker holds an npm granular access token; the installer creates it for you.
125
+
126
+ **Enable or rotate:** re-run the installer with `--npm` (a first interactive install also offers it). The installer then:
127
+
128
+ 1. asks which scopes to allow (`--npm-scope @your-org`, repeatable; defaults to the existing setting, else `@<first owner>`),
129
+ and, with `--npm-org your-org` (repeatable), also grants the token org read-write on those orgs; the choice is saved as `npm.tokenOrgs` so a later rotation creates the same kind of token,
130
+ 2. starts an npm web login and shows the login link, then **waits** until you approve it in a browser (the same flow as `npm login --auth-type=web`),
131
+ 3. asks for your npm password and a one-time password (both hidden; the password is used for this one request and never stored), and creates a granular token: those scopes read-write, `--npm-token-days` lifetime (default 90), **no 2FA bypass**,
132
+ 4. revokes the login session, stores the token as `npm-token` in the chosen credential backend, and adds `"npm": { "token": "cred:npm-token", "scopes": [...] }` to the config.
133
+
134
+ Without 2FA bypass the token can't publish or change trust settings on its own: every npm write `npm_scaffold` makes carries its own one-time password from you (one for the publish, one for the trusted publisher). Running `--npm` again creates a fresh token and replaces the stored one. The installer does not revoke the old token; it says so, and the old one stays valid until it expires or you revoke it in your npm account's Access Tokens settings.
135
+
136
+ **Unattended:** the web login needs a browser, so `--non-interactive --npm` takes an existing token instead, via `--secrets-file` (`{ "npm-token": "npm_…" }`) or env `GH_BROKER_SECRET_NPM_TOKEN`.
137
+
138
+ Without the `npm` block nothing changes: the installer says "not enabled", and the broker does not expose `npm_scaffold` at all (startup logs why). The token is an **optional credential**: if it is missing (skipped at the prompt, or an unattended run without it), the installer carries on and the broker starts normally with `npm_scaffold` hidden. Only the owners' PATs are required.
80
139
 
81
140
  ## Enabling `link_issue` (GitHub App) — DEFERRED
82
141