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 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 body = await res.json();
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 path4 from "path";
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 = path4.join(baseDir, ".claude");
595
- const settingsFile = path4.join(claudeDir, "settings.json");
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 path5 from "path";
862
+ import path6 from "path";
822
863
  import os2 from "os";
823
- var CONFIG_DIR2 = path5.join(os2.homedir(), ".glop");
824
- var CACHE_FILE = path5.join(CONFIG_DIR2, "update-check.json");
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.9.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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glop.dev",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "glop": "./dist/index.js"