glop.dev 0.8.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 +83 -11
- package/dist/lib/pr-comment-worker.js +86 -0
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -73,6 +73,26 @@ function getGitUserEmail() {
|
|
|
73
73
|
return null;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
function getCommitDiffStats() {
|
|
77
|
+
try {
|
|
78
|
+
const output = execSync("git diff --shortstat HEAD~1", {
|
|
79
|
+
encoding: "utf-8",
|
|
80
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
81
|
+
timeout: 3e3
|
|
82
|
+
}).trim();
|
|
83
|
+
if (!output) return null;
|
|
84
|
+
const files = output.match(/(\d+)\s+file/);
|
|
85
|
+
const insertions = output.match(/(\d+)\s+insertion/);
|
|
86
|
+
const deletions = output.match(/(\d+)\s+deletion/);
|
|
87
|
+
return {
|
|
88
|
+
files_changed: files ? parseInt(files[1], 10) : 0,
|
|
89
|
+
lines_added: insertions ? parseInt(insertions[1], 10) : 0,
|
|
90
|
+
lines_removed: deletions ? parseInt(deletions[1], 10) : 0
|
|
91
|
+
};
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
76
96
|
|
|
77
97
|
// src/lib/config.ts
|
|
78
98
|
var CONFIG_DIR = path.join(os.homedir(), ".glop");
|
|
@@ -435,6 +455,15 @@ var doctorCommand = new Command3("doctor").description("Check that glop is set u
|
|
|
435
455
|
} catch {
|
|
436
456
|
fail("CLI in PATH", "hooks won't fire \u2014 ensure `glop` is in your PATH");
|
|
437
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
|
+
}
|
|
438
467
|
console.log();
|
|
439
468
|
if (hasFailure) {
|
|
440
469
|
process.exit(1);
|
|
@@ -446,6 +475,10 @@ var doctorCommand = new Command3("doctor").description("Check that glop is set u
|
|
|
446
475
|
// src/commands/hook.ts
|
|
447
476
|
import { Command as Command4 } from "commander";
|
|
448
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+)/;
|
|
449
482
|
function extractSlugFromTranscript(transcriptPath) {
|
|
450
483
|
try {
|
|
451
484
|
const fd = openSync(transcriptPath, "r");
|
|
@@ -480,11 +513,26 @@ var hookCommand = new Command4("__hook").description("internal").action(async ()
|
|
|
480
513
|
payload.machine_id = config.machine_id;
|
|
481
514
|
payload.git_user_name = getGitUserName();
|
|
482
515
|
payload.git_user_email = getGitUserEmail();
|
|
516
|
+
if (payload.hook_event_name === "PostToolUse" && payload.tool_name === "Bash" && typeof payload.tool_response === "string" && /\bgit\s+commit\b/.test(
|
|
517
|
+
typeof payload.tool_input?.command === "string" ? payload.tool_input.command : ""
|
|
518
|
+
) && /\[\w[^\]]*\s+[a-f0-9]{7,}\]/.test(payload.tool_response)) {
|
|
519
|
+
const diffStats = getCommitDiffStats();
|
|
520
|
+
if (diffStats) {
|
|
521
|
+
payload.commit_diff_stats = diffStats;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
483
524
|
const skipSlugEvents = /* @__PURE__ */ new Set(["PostToolUse"]);
|
|
484
525
|
if (!skipSlugEvents.has(payload.hook_event_name) && typeof payload.transcript_path === "string") {
|
|
485
526
|
const slug = extractSlugFromTranscript(payload.transcript_path);
|
|
486
527
|
if (slug) payload.slug = slug;
|
|
487
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
|
+
}
|
|
488
536
|
try {
|
|
489
537
|
const res = await fetch(`${config.server_url}/api/v1/ingest/hook`, {
|
|
490
538
|
method: "POST",
|
|
@@ -495,10 +543,10 @@ var hookCommand = new Command4("__hook").description("internal").action(async ()
|
|
|
495
543
|
body: JSON.stringify(payload),
|
|
496
544
|
signal: AbortSignal.timeout(5e3)
|
|
497
545
|
});
|
|
546
|
+
const resBody = res.ok ? await res.json().catch(() => ({})) : null;
|
|
498
547
|
if (payload.hook_event_name === "SessionStart") {
|
|
499
|
-
if (res.ok) {
|
|
500
|
-
const
|
|
501
|
-
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;
|
|
502
550
|
console.log(`glop: connected to ${config.server_url}`);
|
|
503
551
|
if (runUrl) {
|
|
504
552
|
console.log(`glop: Add "Glop-Run: ${runUrl}" as a git trailer in all commit messages and PR descriptions.`);
|
|
@@ -509,6 +557,22 @@ var hookCommand = new Command4("__hook").description("internal").action(async ()
|
|
|
509
557
|
console.log(`glop: server returned HTTP ${res.status}`);
|
|
510
558
|
}
|
|
511
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
|
+
}
|
|
512
576
|
} catch {
|
|
513
577
|
if (payload.hook_event_name === "SessionStart") {
|
|
514
578
|
console.log(`glop: server unreachable at ${config.server_url}`);
|
|
@@ -520,7 +584,7 @@ var hookCommand = new Command4("__hook").description("internal").action(async ()
|
|
|
520
584
|
import { Command as Command5 } from "commander";
|
|
521
585
|
import { execSync as execSync3 } from "child_process";
|
|
522
586
|
import fs4 from "fs";
|
|
523
|
-
import
|
|
587
|
+
import path5 from "path";
|
|
524
588
|
function hasGlopHooks(settings) {
|
|
525
589
|
const hooks = settings.hooks;
|
|
526
590
|
if (!hooks) return false;
|
|
@@ -558,13 +622,18 @@ var initCommand = new Command5("init").description("Install Claude Code hooks in
|
|
|
558
622
|
} catch {
|
|
559
623
|
console.warn("Warning: `glop` not found in PATH. Hooks won't fire until it's accessible.");
|
|
560
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
|
+
}
|
|
561
630
|
const repoRoot = getRepoRoot();
|
|
562
631
|
if (!repoRoot) {
|
|
563
632
|
console.warn("Warning: not in a git repository. Repo and branch tracking will be limited.");
|
|
564
633
|
}
|
|
565
634
|
const baseDir = repoRoot || process.cwd();
|
|
566
|
-
const claudeDir =
|
|
567
|
-
const settingsFile =
|
|
635
|
+
const claudeDir = path5.join(baseDir, ".claude");
|
|
636
|
+
const settingsFile = path5.join(claudeDir, "settings.json");
|
|
568
637
|
if (!fs4.existsSync(claudeDir)) {
|
|
569
638
|
fs4.mkdirSync(claudeDir, { recursive: true });
|
|
570
639
|
}
|
|
@@ -604,7 +673,9 @@ var updateCommand = new Command6("update").description("Update glop to the lates
|
|
|
604
673
|
console.log("Updating glop\u2026");
|
|
605
674
|
try {
|
|
606
675
|
execSync4("npm install -g glop.dev@latest", { stdio: "inherit" });
|
|
607
|
-
|
|
676
|
+
const version = execSync4("glop --version", { encoding: "utf-8" }).trim();
|
|
677
|
+
console.log(`
|
|
678
|
+
glop has been updated successfully to v${version}.`);
|
|
608
679
|
} catch {
|
|
609
680
|
process.exitCode = 1;
|
|
610
681
|
}
|
|
@@ -788,10 +859,10 @@ var workspaceCommand = new Command7("workspace").description("View or switch wor
|
|
|
788
859
|
|
|
789
860
|
// src/lib/update-check.ts
|
|
790
861
|
import fs5 from "fs";
|
|
791
|
-
import
|
|
862
|
+
import path6 from "path";
|
|
792
863
|
import os2 from "os";
|
|
793
|
-
var CONFIG_DIR2 =
|
|
794
|
-
var CACHE_FILE =
|
|
864
|
+
var CONFIG_DIR2 = path6.join(os2.homedir(), ".glop");
|
|
865
|
+
var CACHE_FILE = path6.join(CONFIG_DIR2, "update-check.json");
|
|
795
866
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
796
867
|
function ensureConfigDir2() {
|
|
797
868
|
if (!fs5.existsSync(CONFIG_DIR2)) {
|
|
@@ -852,7 +923,7 @@ async function checkForUpdate(currentVersion) {
|
|
|
852
923
|
// package.json
|
|
853
924
|
var package_default = {
|
|
854
925
|
name: "glop.dev",
|
|
855
|
-
version: "0.
|
|
926
|
+
version: "0.10.0",
|
|
856
927
|
type: "module",
|
|
857
928
|
bin: {
|
|
858
929
|
glop: "./dist/index.js"
|
|
@@ -871,6 +942,7 @@ var package_default = {
|
|
|
871
942
|
commander: "^13.0.0"
|
|
872
943
|
},
|
|
873
944
|
devDependencies: {
|
|
945
|
+
"@types/node": "^25.5.0",
|
|
874
946
|
tsup: "^8.3.0",
|
|
875
947
|
tsx: "^4.19.0",
|
|
876
948
|
typescript: "^5.7.0",
|
|
@@ -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));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glop.dev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"glop": "./dist/index.js"
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"commander": "^13.0.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
+
"@types/node": "^25.5.0",
|
|
22
23
|
"tsup": "^8.3.0",
|
|
23
24
|
"tsx": "^4.19.0",
|
|
24
25
|
"typescript": "^5.7.0",
|