glop.dev 0.9.0 → 0.10.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/dist/index.js +51 -10
- package/dist/lib/pr-comment-worker.js +86 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -455,6 +455,15 @@ var doctorCommand = new Command3("doctor").description("Check that glop is set u
|
|
|
455
455
|
} catch {
|
|
456
456
|
fail("CLI in PATH", "hooks won't fire \u2014 ensure `glop` is in your PATH");
|
|
457
457
|
}
|
|
458
|
+
try {
|
|
459
|
+
const ghWhich = execSync2("which gh", {
|
|
460
|
+
encoding: "utf-8",
|
|
461
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
462
|
+
}).trim();
|
|
463
|
+
check("pass", "GitHub CLI (gh)", ghWhich);
|
|
464
|
+
} catch {
|
|
465
|
+
check("warn", "GitHub CLI (gh)", "PR comment features won't work \u2014 install from https://cli.github.com");
|
|
466
|
+
}
|
|
458
467
|
console.log();
|
|
459
468
|
if (hasFailure) {
|
|
460
469
|
process.exit(1);
|
|
@@ -466,6 +475,10 @@ var doctorCommand = new Command3("doctor").description("Check that glop is set u
|
|
|
466
475
|
// src/commands/hook.ts
|
|
467
476
|
import { Command as Command4 } from "commander";
|
|
468
477
|
import { openSync, readSync, closeSync, readFileSync } from "fs";
|
|
478
|
+
import { spawn } from "child_process";
|
|
479
|
+
import { fileURLToPath } from "url";
|
|
480
|
+
import path4 from "path";
|
|
481
|
+
var PR_URL_RE = /(https:\/\/github\.com\/[^\s]+\/pull\/\d+)/;
|
|
469
482
|
function extractSlugFromTranscript(transcriptPath) {
|
|
470
483
|
try {
|
|
471
484
|
const fd = openSync(transcriptPath, "r");
|
|
@@ -513,6 +526,13 @@ var hookCommand = new Command4("__hook").description("internal").action(async ()
|
|
|
513
526
|
const slug = extractSlugFromTranscript(payload.transcript_path);
|
|
514
527
|
if (slug) payload.slug = slug;
|
|
515
528
|
}
|
|
529
|
+
let prUrl = null;
|
|
530
|
+
if (payload.hook_event_name === "PostToolUse" && payload.tool_name === "Bash" && typeof payload.tool_response === "string" && /\bgh\s+pr\s+create\b/.test(
|
|
531
|
+
typeof payload.tool_input?.command === "string" ? payload.tool_input.command : ""
|
|
532
|
+
)) {
|
|
533
|
+
const prMatch = payload.tool_response.match(PR_URL_RE);
|
|
534
|
+
if (prMatch) prUrl = prMatch[1];
|
|
535
|
+
}
|
|
516
536
|
try {
|
|
517
537
|
const res = await fetch(`${config.server_url}/api/v1/ingest/hook`, {
|
|
518
538
|
method: "POST",
|
|
@@ -523,10 +543,10 @@ var hookCommand = new Command4("__hook").description("internal").action(async ()
|
|
|
523
543
|
body: JSON.stringify(payload),
|
|
524
544
|
signal: AbortSignal.timeout(5e3)
|
|
525
545
|
});
|
|
546
|
+
const resBody = res.ok ? await res.json().catch(() => ({})) : null;
|
|
526
547
|
if (payload.hook_event_name === "SessionStart") {
|
|
527
|
-
if (res.ok) {
|
|
528
|
-
const
|
|
529
|
-
const runUrl = body.run_id ? `${config.server_url}/runs/${body.run_id}` : null;
|
|
548
|
+
if (res.ok && resBody) {
|
|
549
|
+
const runUrl = resBody.run_id ? `${config.server_url}/runs/${resBody.run_id}` : null;
|
|
530
550
|
console.log(`glop: connected to ${config.server_url}`);
|
|
531
551
|
if (runUrl) {
|
|
532
552
|
console.log(`glop: Add "Glop-Run: ${runUrl}" as a git trailer in all commit messages and PR descriptions.`);
|
|
@@ -537,6 +557,22 @@ var hookCommand = new Command4("__hook").description("internal").action(async ()
|
|
|
537
557
|
console.log(`glop: server returned HTTP ${res.status}`);
|
|
538
558
|
}
|
|
539
559
|
}
|
|
560
|
+
if (prUrl && resBody?.run_id) {
|
|
561
|
+
try {
|
|
562
|
+
const workerPath = path4.join(
|
|
563
|
+
path4.dirname(fileURLToPath(import.meta.url)),
|
|
564
|
+
"lib",
|
|
565
|
+
"pr-comment-worker.js"
|
|
566
|
+
);
|
|
567
|
+
const child = spawn(
|
|
568
|
+
process.execPath,
|
|
569
|
+
[workerPath, config.server_url, resBody.run_id, prUrl],
|
|
570
|
+
{ detached: true, stdio: "ignore", env: { ...process.env, GLOP_API_KEY: config.api_key } }
|
|
571
|
+
);
|
|
572
|
+
child.unref();
|
|
573
|
+
} catch {
|
|
574
|
+
}
|
|
575
|
+
}
|
|
540
576
|
} catch {
|
|
541
577
|
if (payload.hook_event_name === "SessionStart") {
|
|
542
578
|
console.log(`glop: server unreachable at ${config.server_url}`);
|
|
@@ -548,7 +584,7 @@ var hookCommand = new Command4("__hook").description("internal").action(async ()
|
|
|
548
584
|
import { Command as Command5 } from "commander";
|
|
549
585
|
import { execSync as execSync3 } from "child_process";
|
|
550
586
|
import fs4 from "fs";
|
|
551
|
-
import
|
|
587
|
+
import path5 from "path";
|
|
552
588
|
function hasGlopHooks(settings) {
|
|
553
589
|
const hooks = settings.hooks;
|
|
554
590
|
if (!hooks) return false;
|
|
@@ -586,13 +622,18 @@ var initCommand = new Command5("init").description("Install Claude Code hooks in
|
|
|
586
622
|
} catch {
|
|
587
623
|
console.warn("Warning: `glop` not found in PATH. Hooks won't fire until it's accessible.");
|
|
588
624
|
}
|
|
625
|
+
try {
|
|
626
|
+
execSync3("which gh", { stdio: ["pipe", "pipe", "pipe"] });
|
|
627
|
+
} catch {
|
|
628
|
+
console.warn("Warning: `gh` (GitHub CLI) not found. PR comment features won't work. Install from https://cli.github.com");
|
|
629
|
+
}
|
|
589
630
|
const repoRoot = getRepoRoot();
|
|
590
631
|
if (!repoRoot) {
|
|
591
632
|
console.warn("Warning: not in a git repository. Repo and branch tracking will be limited.");
|
|
592
633
|
}
|
|
593
634
|
const baseDir = repoRoot || process.cwd();
|
|
594
|
-
const claudeDir =
|
|
595
|
-
const settingsFile =
|
|
635
|
+
const claudeDir = path5.join(baseDir, ".claude");
|
|
636
|
+
const settingsFile = path5.join(claudeDir, "settings.json");
|
|
596
637
|
if (!fs4.existsSync(claudeDir)) {
|
|
597
638
|
fs4.mkdirSync(claudeDir, { recursive: true });
|
|
598
639
|
}
|
|
@@ -818,10 +859,10 @@ var workspaceCommand = new Command7("workspace").description("View or switch wor
|
|
|
818
859
|
|
|
819
860
|
// src/lib/update-check.ts
|
|
820
861
|
import fs5 from "fs";
|
|
821
|
-
import
|
|
862
|
+
import path6 from "path";
|
|
822
863
|
import os2 from "os";
|
|
823
|
-
var CONFIG_DIR2 =
|
|
824
|
-
var CACHE_FILE =
|
|
864
|
+
var CONFIG_DIR2 = path6.join(os2.homedir(), ".glop");
|
|
865
|
+
var CACHE_FILE = path6.join(CONFIG_DIR2, "update-check.json");
|
|
825
866
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
826
867
|
function ensureConfigDir2() {
|
|
827
868
|
if (!fs5.existsSync(CONFIG_DIR2)) {
|
|
@@ -882,7 +923,7 @@ async function checkForUpdate(currentVersion) {
|
|
|
882
923
|
// package.json
|
|
883
924
|
var package_default = {
|
|
884
925
|
name: "glop.dev",
|
|
885
|
-
version: "0.
|
|
926
|
+
version: "0.10.0",
|
|
886
927
|
type: "module",
|
|
887
928
|
bin: {
|
|
888
929
|
glop: "./dist/index.js"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// src/lib/pr-comment-worker.ts
|
|
2
|
+
import { execFileSync } from "child_process";
|
|
3
|
+
var [serverUrl, runId, prUrl] = process.argv.slice(2);
|
|
4
|
+
var apiKey = process.env.GLOP_API_KEY;
|
|
5
|
+
if (!serverUrl || !apiKey || !runId || !prUrl) {
|
|
6
|
+
process.exit(1);
|
|
7
|
+
}
|
|
8
|
+
async function main() {
|
|
9
|
+
const contextRes = await fetch(`${serverUrl}/api/v1/runs/${runId}/context`, {
|
|
10
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
11
|
+
signal: AbortSignal.timeout(1e4)
|
|
12
|
+
});
|
|
13
|
+
if (!contextRes.ok) {
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const context = await contextRes.json();
|
|
17
|
+
const prompt = [
|
|
18
|
+
"Generate a concise GitHub PR comment summarizing this AI coding session.",
|
|
19
|
+
"Output ONLY the markdown body \u2014 no wrapping, no ```markdown fences, no preamble.",
|
|
20
|
+
"",
|
|
21
|
+
`Session title: ${context.title || "Untitled"}`,
|
|
22
|
+
`Session summary: ${context.summary || "No summary"}`,
|
|
23
|
+
"",
|
|
24
|
+
"Developer prompts:",
|
|
25
|
+
...context.prompts.map((p, i) => `${i + 1}. ${p}`),
|
|
26
|
+
"",
|
|
27
|
+
"Actions taken:",
|
|
28
|
+
...context.tool_use_labels.map((l, i) => `${i + 1}. ${l}`),
|
|
29
|
+
"",
|
|
30
|
+
"Files touched:",
|
|
31
|
+
...context.files_touched.map((f) => `- ${f}`),
|
|
32
|
+
"",
|
|
33
|
+
"Format the comment with:",
|
|
34
|
+
"- A blockquote with the developer's core request",
|
|
35
|
+
"- 2-3 sentences on how the AI approached the task",
|
|
36
|
+
"- A bullet list of key decisions (if any)",
|
|
37
|
+
"- A collapsible <details> section listing files touched",
|
|
38
|
+
`- Stats line: ${context.event_count} events \xB7 ${context.file_count} files`
|
|
39
|
+
].join("\n");
|
|
40
|
+
let commentBody;
|
|
41
|
+
try {
|
|
42
|
+
commentBody = execFileSync("claude", ["-p", prompt], {
|
|
43
|
+
encoding: "utf-8",
|
|
44
|
+
timeout: 6e4,
|
|
45
|
+
maxBuffer: 1024 * 1024
|
|
46
|
+
}).trim();
|
|
47
|
+
} catch {
|
|
48
|
+
commentBody = buildTemplate(context);
|
|
49
|
+
}
|
|
50
|
+
if (!commentBody) {
|
|
51
|
+
commentBody = buildTemplate(context);
|
|
52
|
+
}
|
|
53
|
+
const runUrl = `${serverUrl}/runs/${runId}`;
|
|
54
|
+
const fullComment = [
|
|
55
|
+
commentBody,
|
|
56
|
+
"",
|
|
57
|
+
`<sub>[View in Glop](${runUrl}) \xB7 Posted by [Glop](${serverUrl})</sub>`
|
|
58
|
+
].join("\n");
|
|
59
|
+
execFileSync("gh", ["pr", "comment", prUrl, "--body", fullComment], {
|
|
60
|
+
encoding: "utf-8",
|
|
61
|
+
timeout: 15e3
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function buildTemplate(context) {
|
|
65
|
+
const parts = [];
|
|
66
|
+
parts.push(`> ${context.prompts[0] || "No prompt recorded"}
|
|
67
|
+
`);
|
|
68
|
+
parts.push(`${context.summary || context.title || "No summary available"}
|
|
69
|
+
`);
|
|
70
|
+
if (context.files_touched.length > 0) {
|
|
71
|
+
parts.push(
|
|
72
|
+
"<details>",
|
|
73
|
+
`<summary>Files touched (${context.files_touched.length})</summary>
|
|
74
|
+
`
|
|
75
|
+
);
|
|
76
|
+
for (const file of context.files_touched) {
|
|
77
|
+
parts.push(`- \`${file}\``);
|
|
78
|
+
}
|
|
79
|
+
parts.push("\n</details>\n");
|
|
80
|
+
}
|
|
81
|
+
parts.push(
|
|
82
|
+
`<sub>${context.event_count} events \xB7 ${context.file_count} files</sub>`
|
|
83
|
+
);
|
|
84
|
+
return parts.join("\n");
|
|
85
|
+
}
|
|
86
|
+
main().catch(() => process.exit(1));
|