linear-grab-bridge 0.11.0 → 0.13.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.
- package/linear-grab-bridge.mjs +49 -1
- package/package.json +1 -1
package/linear-grab-bridge.mjs
CHANGED
|
@@ -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.
|
|
31
|
+
const VERSION = '0.13.0';
|
|
32
32
|
|
|
33
33
|
/** Best-effort command runner (git/gh introspection). Never throws. */
|
|
34
34
|
function run(cmd, args, cwd = DIR) {
|
|
@@ -65,6 +65,9 @@ const IDLE_KILL_MS = 30 * 60_000; // free an idle interactive session after 30mi
|
|
|
65
65
|
/** @type {Map<string, any>} */
|
|
66
66
|
const tasks = new Map();
|
|
67
67
|
|
|
68
|
+
/** Linear Authorization header supplied by the panel (for the media proxy). */
|
|
69
|
+
let linearAuth = null;
|
|
70
|
+
|
|
68
71
|
// ---- persistence -----------------------------------------------------------
|
|
69
72
|
|
|
70
73
|
const HISTORY_DIR = join(homedir(), '.linear-grab');
|
|
@@ -274,6 +277,8 @@ function ingest(task, event) {
|
|
|
274
277
|
return;
|
|
275
278
|
}
|
|
276
279
|
if (event.type === 'assistant') {
|
|
280
|
+
// The stream tells us the ACTUAL model — surface it even for defaults.
|
|
281
|
+
if (event.message?.model) task.model = event.message.model;
|
|
277
282
|
const usage = event.message?.usage;
|
|
278
283
|
if (usage) {
|
|
279
284
|
const context =
|
|
@@ -544,6 +549,49 @@ createServer(async (req, res) => {
|
|
|
544
549
|
const task = await startTask(body);
|
|
545
550
|
return json(res, 201, summary(task));
|
|
546
551
|
}
|
|
552
|
+
// Panel hands us its Linear auth so /fetch can display Linear-hosted media.
|
|
553
|
+
if (req.method === 'POST' && url.pathname === '/config') {
|
|
554
|
+
const body = await readBody(req);
|
|
555
|
+
if (typeof body.linearAuth === 'string') linearAuth = body.linearAuth;
|
|
556
|
+
return json(res, 200, { ok: true });
|
|
557
|
+
}
|
|
558
|
+
// Media proxy: uploads.linear.app requires Linear auth on every GET — an
|
|
559
|
+
// <img>/<video> tag can't send headers, this local process can.
|
|
560
|
+
if (req.method === 'GET' && url.pathname === '/fetch') {
|
|
561
|
+
const target = url.searchParams.get('url') ?? '';
|
|
562
|
+
let host = '';
|
|
563
|
+
try {
|
|
564
|
+
host = new URL(target).hostname;
|
|
565
|
+
} catch {
|
|
566
|
+
/* invalid */
|
|
567
|
+
}
|
|
568
|
+
if (!/(^|\.)uploads\.linear\.app$/.test(host)) {
|
|
569
|
+
return json(res, 400, { error: 'host not allowed' });
|
|
570
|
+
}
|
|
571
|
+
const upstream = await fetch(target, {
|
|
572
|
+
headers: linearAuth ? { Authorization: linearAuth } : {},
|
|
573
|
+
});
|
|
574
|
+
if (!upstream.ok) return json(res, upstream.status, { error: `upstream ${upstream.status}` });
|
|
575
|
+
res.writeHead(200, {
|
|
576
|
+
'Content-Type': upstream.headers.get('content-type') ?? 'application/octet-stream',
|
|
577
|
+
'Cache-Control': 'private, max-age=300',
|
|
578
|
+
...CORS,
|
|
579
|
+
});
|
|
580
|
+
return res.end(Buffer.from(await upstream.arrayBuffer()));
|
|
581
|
+
}
|
|
582
|
+
// Fast-merge: after reviewing the demo, one click merges the PR via gh.
|
|
583
|
+
if (req.method === 'POST' && url.pathname === '/pr/merge') {
|
|
584
|
+
const body = await readBody(req);
|
|
585
|
+
const prUrl = String(body.url ?? '');
|
|
586
|
+
if (!/^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/\d+$/.test(prUrl)) {
|
|
587
|
+
return json(res, 400, { error: 'invalid PR url' });
|
|
588
|
+
}
|
|
589
|
+
const out = await run('gh', ['pr', 'merge', prUrl, '--squash']);
|
|
590
|
+
if (out === null) {
|
|
591
|
+
return json(res, 502, { error: 'gh pr merge failed — check gh auth / merge conflicts / required checks' });
|
|
592
|
+
}
|
|
593
|
+
return json(res, 200, { ok: true, output: out.slice(0, 500) });
|
|
594
|
+
}
|
|
547
595
|
// Upload proxy: browsers can't PUT to Linear's storage (no CORS there) —
|
|
548
596
|
// this local process can. SSRF-guarded to Linear storage hosts.
|
|
549
597
|
if (req.method === 'POST' && url.pathname === '/put') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linear-grab-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
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": {
|