linear-grab-bridge 0.13.0 → 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.
@@ -28,7 +28,7 @@ const flag = (name, fallback) => {
28
28
  const PORT = Number(flag('--port', '4577'));
29
29
  const DIR = flag('--dir', process.cwd());
30
30
  const CLAUDE_BIN = flag('--claude', 'claude');
31
- const VERSION = '0.13.0';
31
+ const VERSION = '0.15.1';
32
32
 
33
33
  /** Best-effort command runner (git/gh introspection). Never throws. */
34
34
  function run(cmd, args, cwd = DIR) {
@@ -68,6 +68,9 @@ const tasks = new Map();
68
68
  /** Linear Authorization header supplied by the panel (for the media proxy). */
69
69
  let linearAuth = null;
70
70
 
71
+ /** PR state cache (gh pr view) — 60s TTL. */
72
+ const prStatusCache = new Map();
73
+
71
74
  // ---- persistence -----------------------------------------------------------
72
75
 
73
76
  const HISTORY_DIR = join(homedir(), '.linear-grab');
@@ -579,6 +582,35 @@ createServer(async (req, res) => {
579
582
  });
580
583
  return res.end(Buffer.from(await upstream.arrayBuffer()));
581
584
  }
585
+ // Real PR states (OPEN/MERGED/CLOSED) via gh — the panel can't know
586
+ // merge status from Linear attachments alone.
587
+ if (req.method === 'POST' && url.pathname === '/pr/status') {
588
+ const body = await readBody(req);
589
+ const urls = (Array.isArray(body.urls) ? body.urls : []).slice(0, 20);
590
+ const statuses = {};
591
+ await Promise.all(
592
+ urls.map(async (u) => {
593
+ const prUrl = String(u);
594
+ if (!/^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/\d+$/.test(prUrl)) return;
595
+ const cached = prStatusCache.get(prUrl);
596
+ if (cached && Date.now() - cached.at < 60_000) {
597
+ statuses[prUrl] = cached.state;
598
+ return;
599
+ }
600
+ const out = await run('gh', ['pr', 'view', prUrl, '--json', 'state']);
601
+ try {
602
+ const state = JSON.parse(out ?? '').state;
603
+ if (state) {
604
+ prStatusCache.set(prUrl, { state, at: Date.now() });
605
+ statuses[prUrl] = state;
606
+ }
607
+ } catch {
608
+ /* gh missing / not accessible */
609
+ }
610
+ }),
611
+ );
612
+ return json(res, 200, { statuses });
613
+ }
582
614
  // Fast-merge: after reviewing the demo, one click merges the PR via gh.
583
615
  if (req.method === 'POST' && url.pathname === '/pr/merge') {
584
616
  const body = await readBody(req);
@@ -587,6 +619,7 @@ createServer(async (req, res) => {
587
619
  return json(res, 400, { error: 'invalid PR url' });
588
620
  }
589
621
  const out = await run('gh', ['pr', 'merge', prUrl, '--squash']);
622
+ prStatusCache.delete(prUrl);
590
623
  if (out === null) {
591
624
  return json(res, 502, { error: 'gh pr merge failed — check gh auth / merge conflicts / required checks' });
592
625
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linear-grab-bridge",
3
- "version": "0.13.0",
3
+ "version": "0.15.1",
4
4
  "description": "Local bridge for Linear Grab — delegate issues from the browser panel to headless Claude Code sessions running in your repo, with live status and an upload relay.",
5
5
  "type": "module",
6
6
  "bin": {