linear-grab-bridge 0.20.0 → 0.22.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 +57 -2
- 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.22.0';
|
|
32
32
|
|
|
33
33
|
/** Best-effort command runner (git/gh introspection). Never throws. */
|
|
34
34
|
function run(cmd, args, cwd = DIR) {
|
|
@@ -175,6 +175,7 @@ function summary(t) {
|
|
|
175
175
|
subagents: t.subagents ?? 0,
|
|
176
176
|
permissionMode: t.permissionMode ?? 'acceptEdits',
|
|
177
177
|
worktree: t.worktree ?? null,
|
|
178
|
+
lastEventAt: t.tail?.length ? t.tail[t.tail.length - 1].at : (t.startedAt ?? null),
|
|
178
179
|
};
|
|
179
180
|
}
|
|
180
181
|
|
|
@@ -464,11 +465,19 @@ createServer(async (req, res) => {
|
|
|
464
465
|
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
465
466
|
|
|
466
467
|
if (req.method === 'GET' && url.pathname === '/health') {
|
|
468
|
+
// Committed project config — read fresh each poll so edits apply live.
|
|
469
|
+
let projectConfig = null;
|
|
470
|
+
try {
|
|
471
|
+
projectConfig = JSON.parse(readFileSync(join(DIR, '.lineargrab.json'), 'utf8'));
|
|
472
|
+
} catch {
|
|
473
|
+
/* missing or invalid — fine */
|
|
474
|
+
}
|
|
467
475
|
return json(res, 200, {
|
|
468
476
|
ok: true,
|
|
469
477
|
version: VERSION,
|
|
470
478
|
cwd: DIR,
|
|
471
479
|
active: [...tasks.values()].filter((t) => t.status === 'running').length,
|
|
480
|
+
projectConfig,
|
|
472
481
|
});
|
|
473
482
|
}
|
|
474
483
|
if (req.method === 'GET' && url.pathname === '/tasks') {
|
|
@@ -620,6 +629,38 @@ createServer(async (req, res) => {
|
|
|
620
629
|
);
|
|
621
630
|
return json(res, 200, { statuses, previews });
|
|
622
631
|
}
|
|
632
|
+
// Reset the staging branch: delete + recreate from the default branch.
|
|
633
|
+
if (req.method === 'POST' && url.pathname === '/branch/reset') {
|
|
634
|
+
const body = await readBody(req);
|
|
635
|
+
const prUrl = String(body.url ?? '');
|
|
636
|
+
const base = String(body.base || 'staging').replace(/[^\w./-]/g, '');
|
|
637
|
+
const m = prUrl.match(/^https:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/pull\/\d+$/);
|
|
638
|
+
if (!m) return json(res, 400, { error: 'invalid PR url' });
|
|
639
|
+
const repo = `${m[1]}/${m[2]}`;
|
|
640
|
+
await run('gh', ['api', '-X', 'DELETE', `repos/${repo}/git/refs/heads/${base}`]);
|
|
641
|
+
const def = ((await run('gh', ['api', `repos/${repo}`, '-q', '.default_branch'])) ?? 'main').trim();
|
|
642
|
+
const sha = ((await run('gh', ['api', `repos/${repo}/git/ref/heads/${def}`, '-q', '.object.sha'])) ?? '').trim();
|
|
643
|
+
if (!sha) return json(res, 500, { error: `could not read ${def}` });
|
|
644
|
+
const created = await run('gh', ['api', '-X', 'POST', `repos/${repo}/git/refs`, '-f', `ref=refs/heads/${base}`, '-f', `sha=${sha}`]);
|
|
645
|
+
if (created == null) return json(res, 500, { error: `could not recreate ${base}` });
|
|
646
|
+
return json(res, 200, { ok: true, base, from: def });
|
|
647
|
+
}
|
|
648
|
+
// Vercel build logs for a deployment URL — the panel's terminal card.
|
|
649
|
+
if (req.method === 'POST' && url.pathname === '/deploy/logs') {
|
|
650
|
+
const body = await readBody(req);
|
|
651
|
+
const deployUrl = String(body.deployUrl ?? '');
|
|
652
|
+
if (!/^https:\/\/[\w.-]+\.vercel\.app/.test(deployUrl))
|
|
653
|
+
return json(res, 400, { error: 'invalid deployment url' });
|
|
654
|
+
const out = await new Promise((resolve) => {
|
|
655
|
+
execFile(
|
|
656
|
+
'vercel',
|
|
657
|
+
['inspect', deployUrl, '--logs'],
|
|
658
|
+
{ timeout: 30_000, maxBuffer: 4_000_000 },
|
|
659
|
+
(err, stdout, stderr) => resolve(stdout || stderr || (err ? String(err.message) : '')),
|
|
660
|
+
);
|
|
661
|
+
});
|
|
662
|
+
return json(res, 200, { logs: String(out).split('\n').slice(-400).join('\n') });
|
|
663
|
+
}
|
|
623
664
|
// Live status of the staging deploy: Vercel mirrors every branch deploy
|
|
624
665
|
// into GitHub Deployments (state + environment_url) — pollable via gh.
|
|
625
666
|
if (req.method === 'POST' && url.pathname === '/branch/status') {
|
|
@@ -633,7 +674,21 @@ createServer(async (req, res) => {
|
|
|
633
674
|
const deps = JSON.parse(
|
|
634
675
|
(await run('gh', ['api', `repos/${repo}/deployments?ref=${base}&per_page=1`])) ?? '[]',
|
|
635
676
|
);
|
|
636
|
-
if (!deps.length)
|
|
677
|
+
if (!deps.length) {
|
|
678
|
+
// Not every Vercel project populates GitHub Deployments — the commit
|
|
679
|
+
// status ('vercel' context) is the reliable fallback.
|
|
680
|
+
const combined = JSON.parse(
|
|
681
|
+
(await run('gh', ['api', `repos/${repo}/commits/${base}/status`])) ?? '{}',
|
|
682
|
+
);
|
|
683
|
+
const st = (combined.statuses ?? []).find((x) => /vercel/i.test(x.context ?? '')) ?? null;
|
|
684
|
+
const map = { success: 'success', pending: 'in_progress', failure: 'failure', error: 'error' };
|
|
685
|
+
return json(res, 200, {
|
|
686
|
+
state: st ? (map[st.state] ?? st.state) : 'none',
|
|
687
|
+
url: st?.target_url ?? null,
|
|
688
|
+
at: st?.updated_at ?? null,
|
|
689
|
+
sha: (combined.sha ?? '').slice(0, 7),
|
|
690
|
+
});
|
|
691
|
+
}
|
|
637
692
|
const statuses = JSON.parse(
|
|
638
693
|
(await run('gh', ['api', `repos/${repo}/deployments/${deps[0].id}/statuses?per_page=1`])) ?? '[]',
|
|
639
694
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linear-grab-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.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": {
|