@reneturcios/greenlight 0.9.0 → 0.9.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/dist/proxy.js ADDED
@@ -0,0 +1,111 @@
1
+ /**
2
+ * `greenlight proxy`: a tiny localhost forwarder that attaches the user's
3
+ * GitHub token server-side. Greenlight's own reads are unauthenticated at
4
+ * 60/hr on the shared anonymous quota; the proxy lifts a user's setup to
5
+ * the authenticated 5000/hr budget without their token ever touching a
6
+ * browser, a log, or a tool's arguments beyond one env var.
7
+ *
8
+ * export GITHUB_TOKEN=<classic-token>
9
+ * greenlight proxy & # default 127.0.0.1:8792
10
+ * export GREENLIGHT_GITHUB_BASE_URL=http://127.0.0.1:8792 GITHUB_TOKEN=proxy
11
+ *
12
+ * Security model, stated plainly: the token is read once at startup from
13
+ * GITHUB_TOKEN (or GH_TOKEN) and is never logged, never stored, and never
14
+ * echoed. Incoming Authorization headers are ignored so a stray value
15
+ * cannot leak through. The server binds 127.0.0.1 only, and it forwards
16
+ * to exactly one upstream: https://api.github.com. Absolute-form request
17
+ * targets (http://...) are rejected so the proxy cannot be aimed at other
18
+ * hosts.
19
+ */
20
+ import { createServer, request as httpRequest } from "node:http";
21
+ import { request as httpsRequest } from "node:https";
22
+ export const PROXY_DEFAULT_PORT = 8792;
23
+ export const PROXY_UPSTREAM = "https://api.github.com";
24
+ /** The exact client env line the command prints on startup. */
25
+ export function proxyClientEnv(port) {
26
+ return `GREENLIGHT_GITHUB_BASE_URL=http://127.0.0.1:${port} GITHUB_TOKEN=proxy`;
27
+ }
28
+ /**
29
+ * Read the proxy configuration from the environment. Pure: returns the
30
+ * config or the human-readable refusal, and never the token on the
31
+ * refusal path either.
32
+ */
33
+ export function resolveProxyConfig(env, port = PROXY_DEFAULT_PORT, upstream = PROXY_UPSTREAM) {
34
+ const tokenVar = env.GITHUB_TOKEN ? "GITHUB_TOKEN" : env.GH_TOKEN ? "GH_TOKEN" : undefined;
35
+ const token = tokenVar ? env[tokenVar] : undefined;
36
+ if (!tokenVar || !token) {
37
+ return {
38
+ error: "greenlight proxy needs a token: export GITHUB_TOKEN=<your-classic-token> first " +
39
+ "(a classic personal access token; no scopes needed for public repos). The token is read once at startup and never logged or stored.",
40
+ };
41
+ }
42
+ return { token, tokenVar, port, upstream };
43
+ }
44
+ function forwardRequest(upstream, token, clientReq, clientRes) {
45
+ const target = clientReq.url ?? "/";
46
+ // Absolute-form targets would let a client aim the proxy at another
47
+ // host; refuse them outright.
48
+ if (/^https?:\/\//i.test(target)) {
49
+ clientRes.writeHead(400, { "content-type": "text/plain" });
50
+ clientRes.end("greenlight proxy: absolute-form request targets are rejected");
51
+ return;
52
+ }
53
+ const upstreamUrl = new URL(target, upstream);
54
+ const useHttps = upstreamUrl.protocol === "https:";
55
+ const doRequest = useHttps ? httpsRequest : httpRequest;
56
+ const headers = { ...clientReq.headers };
57
+ delete headers["authorization"];
58
+ delete headers["host"];
59
+ delete headers["connection"];
60
+ // The whole point: attach the startup token server-side.
61
+ headers["authorization"] = `Bearer ${token}`;
62
+ const upstreamReq = doRequest({
63
+ protocol: upstreamUrl.protocol,
64
+ hostname: upstreamUrl.hostname,
65
+ port: upstreamUrl.port || (useHttps ? 443 : 80),
66
+ path: upstreamUrl.pathname + upstreamUrl.search,
67
+ method: clientReq.method,
68
+ headers: headers,
69
+ }, (upstreamRes) => {
70
+ const out = {};
71
+ for (const [k, v] of Object.entries(upstreamRes.headers)) {
72
+ if (k.toLowerCase() === "transfer-encoding")
73
+ continue;
74
+ out[k] = v;
75
+ }
76
+ clientRes.writeHead(upstreamRes.statusCode ?? 502, out);
77
+ upstreamRes.pipe(clientRes);
78
+ });
79
+ upstreamReq.on("error", () => {
80
+ if (!clientRes.headersSent)
81
+ clientRes.writeHead(502, { "content-type": "text/plain" });
82
+ clientRes.end("greenlight proxy: upstream unreachable");
83
+ });
84
+ upstreamReq.setTimeout(60000, () => upstreamReq.destroy(new Error("upstream timeout")));
85
+ clientReq.pipe(upstreamReq);
86
+ }
87
+ /**
88
+ * Start the proxy. Resolves when the socket is bound; the returned stop()
89
+ * drains it. The token travels only in the Authorization header toward the
90
+ * fixed upstream.
91
+ */
92
+ export async function startProxy(config) {
93
+ const server = createServer((req, res) => forwardRequest(config.upstream, config.token, req, res));
94
+ await new Promise((resolve, reject) => {
95
+ server.once("error", reject);
96
+ server.listen(config.port, "127.0.0.1", () => {
97
+ server.off("error", reject);
98
+ resolve();
99
+ });
100
+ });
101
+ const address = server.address();
102
+ const port = typeof address === "object" && address ? address.port : config.port;
103
+ return {
104
+ server,
105
+ port,
106
+ stop: () => new Promise((resolve, reject) => {
107
+ server.close((err) => (err ? reject(err) : resolve()));
108
+ }),
109
+ };
110
+ }
111
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
@@ -41,7 +41,7 @@ without it the tools still work but share the small anonymous budget.
41
41
  ## Verify it worked
42
42
 
43
43
  Ask the agent: "list your MCP tools." You should see the `greenlight`
44
- server with all 15 tools:
44
+ server with all 17 tools:
45
45
 
46
46
  - `check_issue` — verdict on whether an issue is landable (GO / RISKY / TAKEN / GATED / STALE)
47
47
  - `scan_repo` — find starter issues worth working in a repo
@@ -58,6 +58,8 @@ server with all 15 tools:
58
58
  - `verify_comment` — draft the verified-finding comment for maintainer-only repos (draft only, never posts)
59
59
  - `babysit_prs` — watch your open PRs: one prioritized action list (needs you now, waiting on them, quiet), diffed against stored watermarks
60
60
  - `evidence_pack` — the Verified PR evidence pack for one PR: guard verdict, CI triage, review triage, protocol status of linked issues, every claim citing its source
61
+ - `scout_campaign` — campaign mode: scan repos, rank survivors, append SCOUTED rows to a campaign file, budget-aware
62
+ - `my_claims` — bucket your my-claims registry by live protocol status (linked, healthy-active, heartbeat-due, expiring, unlinked-stale, queue-lost)
61
63
 
62
64
  ## Format notes
63
65
 
@@ -7,9 +7,10 @@ your project's CLAUDE.md (or keep the file and reference it from CLAUDE.md).
7
7
  ## Greenlight: check before you open a PR
8
8
 
9
9
  You have the Greenlight MCP server connected (server name: greenlight,
10
- 15 tools: check_issue, scan_repo, repo_friction, claim_issue, prep_issue,
10
+ 17 tools: check_issue, scan_repo, repo_friction, claim_issue, prep_issue,
11
11
  open_pr, ci_triage, review_triage, guard_pr, repo_digest, claim_status,
12
- preflight, verify_comment, babysit_prs, evidence_pack). It answers one
12
+ preflight, verify_comment, babysit_prs, evidence_pack, scout_campaign,
13
+ my_claims). It answers one
13
14
  question at every step: is this contribution worth doing, and is the
14
15
  problem mine?
15
16
 
@@ -7,7 +7,7 @@ file form is:
7
7
 
8
8
  Copy the `cline_mcp_settings.json` file in this folder there (merging with
9
9
  any servers you already have), then check the Cline "MCP Servers" panel:
10
- the `greenlight` server should list 15 tools. The `disabled: false` field is
10
+ the `greenlight` server should list 17 tools. The `disabled: false` field is
11
11
  Cline's way of keeping the server enabled without deleting it.
12
12
 
13
13
  Workflow: see the integrations README — your agent checks Greenlight
@@ -6,7 +6,7 @@ Windsurf's global MCP config lives at:
6
6
 
7
7
  Copy the `mcp_config.json` file in this folder there (merging with any
8
8
  servers you already have), then restart Windsurf and check its MCP panel:
9
- the `greenlight` server should list 15 tools. Newer Windsurf versions also
9
+ the `greenlight` server should list 17 tools. Newer Windsurf versions also
10
10
  accept a project-level `.windsurf/mcp.json` file using the same
11
11
  `mcpServers` format.
12
12
 
package/package.json CHANGED
@@ -25,7 +25,7 @@
25
25
  "test": "tsx --test test/*.test.ts"
26
26
  },
27
27
  "type": "module",
28
- "version": "0.9.0",
28
+ "version": "0.9.1",
29
29
  "mcpName": "io.github.CloudCorpRecords/greenlight",
30
30
  "publishConfig": {
31
31
  "access": "public"
package/server.json CHANGED
@@ -6,13 +6,13 @@
6
6
  "url": "https://github.com/CloudCorpRecords/greenlight",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.9.0",
9
+ "version": "0.9.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "registryBaseUrl": "https://registry.npmjs.org",
14
14
  "identifier": "@reneturcios/greenlight",
15
- "version": "0.9.0",
15
+ "version": "0.9.1",
16
16
  "runtimeHint": "npx",
17
17
  "packageArguments": [
18
18
  {