@groupchatai/claude-runner 0.2.0 → 0.2.2
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 +67 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -111,11 +111,12 @@ Due: ${new Date(detail.task.dueDate).toLocaleDateString()}`);
|
|
|
111
111
|
[
|
|
112
112
|
"\n---",
|
|
113
113
|
"RULES:",
|
|
114
|
-
"-
|
|
114
|
+
"- When you have made code changes, you MUST create a new branch, commit your changes, push to the remote, and open a PR using `gh pr create`. Do NOT skip PR creation \u2014 always open a real PR.",
|
|
115
|
+
"- You may push to existing branches and contribute to existing PRs.",
|
|
115
116
|
"- NEVER run `gh pr merge`. Do NOT merge any PR.",
|
|
116
117
|
"- NEVER run `gh pr close`. Do NOT close any PR.",
|
|
117
118
|
"- NEVER run `git push --force` or `git push -f`. No force pushes.",
|
|
118
|
-
"- When you are done, provide a clear summary of what you accomplished
|
|
119
|
+
"- When you are done, provide a clear summary of what you accomplished including the PR URL and branch name."
|
|
119
120
|
].join("\n")
|
|
120
121
|
);
|
|
121
122
|
return parts.join("\n");
|
|
@@ -125,6 +126,7 @@ var C = {
|
|
|
125
126
|
dim: "\x1B[2m",
|
|
126
127
|
white: "\x1B[97m",
|
|
127
128
|
grey: "\x1B[90m",
|
|
129
|
+
lightGrey: "\x1B[37m",
|
|
128
130
|
green: "\x1B[32m",
|
|
129
131
|
red: "\x1B[31m",
|
|
130
132
|
pid: "\x1B[38;2;193;95;60m"
|
|
@@ -132,8 +134,21 @@ var C = {
|
|
|
132
134
|
function pidTag(pid) {
|
|
133
135
|
return ` ${C.pid}[pid ${pid}]${C.reset}`;
|
|
134
136
|
}
|
|
137
|
+
function padForTag(pid) {
|
|
138
|
+
const tagLen = ` [pid ${pid}] `.length;
|
|
139
|
+
return " ".repeat(tagLen);
|
|
140
|
+
}
|
|
141
|
+
function wrapLines(tag, pad, text, color) {
|
|
142
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
143
|
+
if (lines.length === 0) return "";
|
|
144
|
+
const first = `${tag} ${color}${lines[0]}${C.reset}`;
|
|
145
|
+
if (lines.length === 1) return first;
|
|
146
|
+
const rest = lines.slice(1).map((l) => `${pad}${color}${l}${C.reset}`);
|
|
147
|
+
return [first, ...rest].join("\n");
|
|
148
|
+
}
|
|
135
149
|
function formatStreamEvent(event, pid) {
|
|
136
150
|
const tag = pidTag(pid);
|
|
151
|
+
const pad = padForTag(pid);
|
|
137
152
|
switch (event.type) {
|
|
138
153
|
case "system":
|
|
139
154
|
if (event.subtype === "init") {
|
|
@@ -149,18 +164,17 @@ function formatStreamEvent(event, pid) {
|
|
|
149
164
|
const parts = [];
|
|
150
165
|
for (const block of blocks) {
|
|
151
166
|
if (block.type === "text" && block.text) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
167
|
+
const wrapped = wrapLines(tag, pad, block.text, C.white);
|
|
168
|
+
if (wrapped) parts.push(wrapped);
|
|
155
169
|
} else if (block.type === "tool_use" && block.name) {
|
|
156
170
|
let detail = block.name;
|
|
157
171
|
const input = block.input;
|
|
158
172
|
if (input) {
|
|
159
173
|
if (typeof input.file_path === "string") {
|
|
160
|
-
detail += ` ${C.
|
|
174
|
+
detail += ` ${C.grey}\u2192${C.lightGrey} ${input.file_path}`;
|
|
161
175
|
} else if (typeof input.command === "string") {
|
|
162
176
|
const cmd = input.command.length > 80 ? input.command.slice(0, 77) + "\u2026" : input.command;
|
|
163
|
-
detail += ` ${C.
|
|
177
|
+
detail += ` ${C.grey}\u2192${C.lightGrey} ${cmd}`;
|
|
164
178
|
}
|
|
165
179
|
}
|
|
166
180
|
parts.push(`${tag} ${C.grey}${detail}${C.reset}`);
|
|
@@ -177,8 +191,8 @@ function formatStreamEvent(event, pid) {
|
|
|
177
191
|
lines.push(`${tag} ${C.dim}cost: $${cost?.toFixed(4)}${C.reset}`);
|
|
178
192
|
}
|
|
179
193
|
if (event.result) {
|
|
180
|
-
const
|
|
181
|
-
lines.push(
|
|
194
|
+
const wrapped = wrapLines(tag, pad, `\u2713 ${event.result}`, C.green);
|
|
195
|
+
if (wrapped) lines.push(wrapped);
|
|
182
196
|
}
|
|
183
197
|
return lines.length > 0 ? lines.join("\n") : null;
|
|
184
198
|
}
|
|
@@ -350,19 +364,50 @@ function extractCost(stdout) {
|
|
|
350
364
|
return void 0;
|
|
351
365
|
}
|
|
352
366
|
var GITHUB_PR_URL_RE = /https:\/\/github\.com\/[^\s"'<>]+\/pull\/\d+/g;
|
|
353
|
-
function
|
|
367
|
+
function extractPullRequestUrlFromText(text) {
|
|
368
|
+
const match = text.match(GITHUB_PR_URL_RE);
|
|
369
|
+
if (match) return match[match.length - 1];
|
|
370
|
+
return void 0;
|
|
371
|
+
}
|
|
372
|
+
function extractPullRequestUrlFromOutput(stdout) {
|
|
354
373
|
const event = findResultEvent(stdout);
|
|
355
374
|
if (event) {
|
|
356
375
|
const text = typeof event.result === "string" ? event.result : typeof event.text === "string" ? event.text : "";
|
|
357
|
-
const
|
|
358
|
-
if (
|
|
376
|
+
const found = extractPullRequestUrlFromText(text);
|
|
377
|
+
if (found) return found;
|
|
378
|
+
}
|
|
379
|
+
return extractPullRequestUrlFromText(stdout);
|
|
380
|
+
}
|
|
381
|
+
async function detectPullRequestUrl(workDir) {
|
|
382
|
+
try {
|
|
383
|
+
const branch = await runShellCommand("git", ["rev-parse", "--abbrev-ref", "HEAD"], workDir);
|
|
384
|
+
if (!branch || branch === "main" || branch === "master") return void 0;
|
|
385
|
+
const prUrl = await runShellCommand(
|
|
386
|
+
"gh",
|
|
387
|
+
["pr", "view", branch, "--json", "url", "--jq", ".url"],
|
|
388
|
+
workDir
|
|
389
|
+
);
|
|
390
|
+
if (prUrl && prUrl.includes("github.com")) return prUrl;
|
|
391
|
+
} catch {
|
|
359
392
|
}
|
|
360
|
-
const match = stdout.match(GITHUB_PR_URL_RE);
|
|
361
|
-
if (match) return match[match.length - 1];
|
|
362
393
|
return void 0;
|
|
363
394
|
}
|
|
395
|
+
function runShellCommand(cmd, args, cwd) {
|
|
396
|
+
return new Promise((resolve, reject) => {
|
|
397
|
+
const child = spawn(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
398
|
+
const chunks = [];
|
|
399
|
+
child.stdout?.on("data", (d) => chunks.push(d));
|
|
400
|
+
child.on("error", reject);
|
|
401
|
+
child.on("close", (code) => {
|
|
402
|
+
if (code !== 0) return reject(new Error(`${cmd} exited with ${code}`));
|
|
403
|
+
resolve(Buffer.concat(chunks).toString("utf-8").trim());
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
}
|
|
364
407
|
async function processRun(client, run, config) {
|
|
365
|
-
const
|
|
408
|
+
const runTag = ` ${C.pid}[${run.id.slice(-8)}]${C.reset}`;
|
|
409
|
+
const log = (msg) => console.log(`${runTag} ${msg}`);
|
|
410
|
+
const logGreen = (msg) => console.log(`${runTag} ${C.green}${msg}${C.reset}`);
|
|
366
411
|
const detail = await client.getRunDetail(run.id);
|
|
367
412
|
const ownerName = run.owner?.name ?? detail.owner?.name ?? "unknown";
|
|
368
413
|
log(`\u{1F4CB} "${run.taskTitle}" \u2014 delegated by ${ownerName}`);
|
|
@@ -408,22 +453,21 @@ async function processRun(client, run, config) {
|
|
|
408
453
|
const { process: child, output } = spawnClaudeCode(prompt, config, runOptions);
|
|
409
454
|
log(`\u{1F916} Claude Code spawned (pid ${child.pid})`);
|
|
410
455
|
const { stdout, rawOutput, exitCode } = await output;
|
|
411
|
-
const pullRequestUrl =
|
|
456
|
+
const pullRequestUrl = await detectPullRequestUrl(config.workDir) ?? extractPullRequestUrlFromOutput(stdout) ?? extractPullRequestUrlFromOutput(rawOutput);
|
|
412
457
|
if (exitCode !== 0) {
|
|
413
458
|
const errorMsg = `Claude Code exited with code ${exitCode}:
|
|
414
459
|
\`\`\`
|
|
415
460
|
${stdout.slice(0, 2e3)}
|
|
416
461
|
\`\`\``;
|
|
417
462
|
await client.errorRun(run.id, errorMsg, { pullRequestUrl });
|
|
418
|
-
log(
|
|
463
|
+
log(`${C.red}\u274C Run errored (exit code ${exitCode})${C.reset}`);
|
|
419
464
|
return;
|
|
420
465
|
}
|
|
421
466
|
const resultText = extractResultText(stdout);
|
|
422
467
|
const cost = extractCost(stdout);
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
log(`\u2705 Run completed`);
|
|
468
|
+
await client.completeRun(run.id, resultText, { ...cost, pullRequestUrl });
|
|
469
|
+
if (pullRequestUrl) logGreen(`\u{1F517} PR: ${pullRequestUrl}`);
|
|
470
|
+
logGreen(`\u2705 Run completed`);
|
|
427
471
|
} catch (err) {
|
|
428
472
|
const message = err instanceof Error ? err.message : String(err);
|
|
429
473
|
const errorBody = `Claude Code runner error:
|
|
@@ -433,9 +477,9 @@ ${message.slice(0, 2e3)}
|
|
|
433
477
|
try {
|
|
434
478
|
await client.errorRun(run.id, errorBody);
|
|
435
479
|
} catch {
|
|
436
|
-
log(
|
|
480
|
+
log(`${C.dim}\u26A0 Failed to report error to API${C.reset}`);
|
|
437
481
|
}
|
|
438
|
-
log(
|
|
482
|
+
log(`${C.red}\u274C Error: ${message}${C.reset}`);
|
|
439
483
|
}
|
|
440
484
|
}
|
|
441
485
|
function loadEnvFile() {
|